闲言少叙,直接切入主题。
一、Linux下的多文件编译(Ubuntu系统)。
对于小程序来说,往往一个.c文件就足够了,里面包含了各种主函数和功能函数,以及函数的声明等等。但是这样的弊端主要有两点,一是可读性较差,所有程序都在一个文件中造成检查和分析时候很不方便,特别是对于较大的工程来说,是无法想象的。第二点就是保密性,在工作中,我们要将程序交给客户,但是我们并不想让客户知道我们的源代码,所以要用这种方式将功能函数以.o的文件(二进制码)交给客户,确保自己的劳动成果不被别人剽窃,可以做到保护知识产权。
说明了缘由,接下来具体介绍多文件编译。一般我们将文件分为三类,第一类是.h的头文件,第二类是.c的主函数文件,第三类是.c的功能函数文件。头文件用来存放各种函数的声明,主函数是调用功能函数来实现工程需求,功能函数文件是用来存放各种实现各种功能模块的函数定义。
头文件的作用就是在预处理时候将头文件的内容放在有#include包含的地方。#include后面跟着的头文件有两种形式,一种是<>,一种是“”的形式,如:
#include<stdio.h>
#include"stdio.h"
第一种是去默认的目录去寻找该头文件,第二种是先在当前目录下去寻找该头文件,若当前目录没有,则去默认的目录去寻找。
在Ubuntu下用gcc进行多文件编译的命令形式是gcc main.c fun.c -o fun生成的fun就是编程出来的可执行文件。
二、extern和static
sxtern是修饰符,可以用来修饰变量或者函数。它的作用就是调用外源文件中定义的变量和函数。比如说我在a.c文件中定义了一个全局变量int hello = 0,那么我要是想在b.c文件中调用这个hello变量,那么我就可以在b文件要引用的地方声明extern int hello就可以在该作用域下使用hello这个变量了。这里需要注意的是extern int hello是声明这个变量,并不是定义这个变量,c语言不允许重载,所以不能重复定义。
static同样也是修饰符,用来修饰变量和函数。在用来修饰全局变量和函数时,他的作用和extern是相反的,static修饰的全局变量和函数是禁止外源文件调用,比如我在a.c文件中定义一个全局变量static int hello,那么我想在b.c文件中引用,我使用extern int hello,并且使用,程序在编译的时候会报错。
static在修饰局部变量时,会将该变量和全局变量一样存入静态存储区,他的生存周期和全局变量一样,一直到程序运行结束才被释放,但是该变量的作用域仍然是原来的作用域,比如一个变量是fun()函数中的一个局部变量,那么在函数调用结束后,该变量就不能使用,当再次调用该函数的时候,该局部变量任然是那个地址,,存储的还是那个值。
三、数组
首先,数组的本质是连续的内存。
数组就是开辟了一段连续的内存来存储相同类型的数据。int a[10]就是开辟了是个int型的空间来存储十个int型的数据,所以数组a占用的内存空间就是四十个字节。
数组的初始化可以分为全部初始化和部分初始化,数组也只有在初始化的时候才能全部赋值,初始化之后只能一个一个赋值或者采用字符串输入scanf(%s,a)的方式进行整体赋值。部分初始化时,没有得到数值的空间默认是0,是绝对的0,不是字符‘0’。这个0也就是‘\0‘。说到这,就可以引用字符串数组的概念。字符串数组本质上也是数组,我们可以采用这个方式来初始化字符串数组:char a[10] = "abc",这种初始化等同于cha a[10] = {‘a‘,‘b‘,‘c‘,‘\0‘},可以看出,字符串数组的初始化会在结束时加一个’\0‘,在c语言中,字符串与连续字符的区别就在这里,字符串是通过最后的‘\0‘来判断该字符串是否结束,也就是说,‘\0‘是字符串的结束标识符。当用%s输出字符串时,就是输出到‘\0‘为止,且‘\0‘不输出,统计字符串长度(strlen()函数)也是一样,‘\0‘不统计在内。在用scanf输入用%s的方式输入字符串时,会在敲下回车的时候结束字符串的输入,此时会在将字符串最后的那个字符后面加一个‘\0‘作为字符串的接鼠标识。而回车并不会存入字符串中去。
这里提一下二维数组,二维数组本质上和一维数组一样,都是连续的内存,只是二位数组就是将一维数组分段,然后二维数组的特点就是既可以方便的一个一个访问,也可以方便的一段一段访问。
数组中还有一个重要的概念就是数组名。如char a[ 10 ] = {0};a就是这个数组的数组名,她就代表了这个数组。可以将数组名a理解成一个指针常量,注意这里的常量,因为数组名并不占内存,仅仅是一个形式而已,所以a++这样的操作都是错误的,编译也是不会通过的。但是在用数组名访问里面的数组元素的时候,她又和指针一样,可以用解引用(*(a+i))访问,也可以用下标方式(a[i])访问,而且这两种方式是等同的。这里需要注意的是数组名在两种情况是比较特殊的,比如用sizeof(a)操作时,如果a是指针,那么得到的是4(32位系统)或者8(64位系统),而如果a是数组名的话,那么得到的是数组a占用的内存空间,这里是10,这是一种情况,而另一种情况是&a,照理来说a不占用内存,&a应该是空,但是c语言中将&a的操作认为是取数组a首元素的地址,&a的值和a的值是相等的,都是数组首元素的地址。
最后再讲一个字符指针,如char *p_char = "abc";这里的abc存储的位置是文字常量区,这个区域的特点就是只读,所以*p_char 是不能作为左值操作的,即不能赋值,不能改变。而普通的数组元素是储存在栈区,是可读写的。
这里提一下sizeof()和strlen()。sizeof()和strlen()是两个完全不一样的东西,sizeof是运算符,strlen()是函数,sizeof()中()内可以是数组(计算数组空间大小),函数(计算返回值类型的空间),类型(该类型所占的空间),对象(对象实际占用的空间)和指针(指针变量的空,一般为4(32位系统)或者8(64位系统))。而strlen()函数则是专门用作计算字符床长度的函数,她是以输入的首地址开始,到‘\0‘时该字符串所占的长度,‘\0‘并不计算在内。
四、函数
函数的本质是一个值,就是他的返回值。
函数的形式:返回值类型 函数名(参数列表){语句列表}函数可以没有返回值,那么前面的类型就是void。函数的本质就是一个值,只是他在实现的过程中也可以进行输入输出等完成一系列的功能。主函数也是一种函数,主函数的类型是int型,在c语言中,一般主函数用return 0来表示主函数运行正常。函数是模块化编程的一种体现,利用函数可以将复杂的工程分解成许多小的功能模块,分模块化实现。 函数在使用前要先声明,并且要有相应的定义。函数的声明放在头文件中,函数的定义则在相应的函数库文件中。
形参和实参:在定义函数时,函数的参数列表中的参数就是函数形参;调用函数时,在函数的()中放的参数就是实参。形参是局部变量,又叫临时变量,只有在函数被调用的时候才给他分配地址空间,在调用结束时候就收回,因此这个变量的生存周期就是该函数的运行周期,作用域就仅限于这个函数的范围。在函数被调用的时候,函数{}中的语句列表中的形参全部被实参替代,这就是形参和实参的关系。
函数的返回值只能有一个或者没有。所以在需要多个数据返回的时候怎么办?这里用了一个巧妙的办法,将函数的形参定为一个指针变量,然后再调用函数的时候,将需要的参数的地址放在参数列表中,这样就可以实现对个数值返回的效果了。