当前位置 博文首页 > jtwqwq的博客:翁恺老师C语言程序设计网课(12)

    jtwqwq的博客:翁恺老师C语言程序设计网课(12)

    作者:[db:作者] 时间:2021-08-19 09:51

    12.1.1 全局变量:定义在函数之外的变量,全局的生存期和作用域

    定义在函数内部的是本地变量。而定义在外面的是全局变量。
    本地变量最大特点是生存期、作用域都在函数内部,出来就用不了了。

    int gAll=12;
    int main()
    {
    	printf("in %s, gAll is %d",__func__,gAll);//__func__是输出当前函数名称
    	return 0;
    }
    

    如果在主函数中先输出gAll,然后执行函数f,在函数f中把gAll+2,然后返回主函数,再次输出gAll,会发现输出结果是12 14。可见我们可以在任何函数中直接访问、改变全局变量。
    如果全局变量没赋初值,会自动被赋予0值,不像本地变量会出现奇怪的值。
    如果是指针的话,没赋初值会得到NULL,因为只能用编译时已知的值来初始全局变量。而初始化发生在main函数前。
    如果int gAll=f();不可以,因为这时候电脑表示他还不知道f()是什么
    如果int gAll=12; int g2=gAll;也不行(Dev C++可以)
    但是如果const int gAll=12; int g2=gAll;就可以了。但是不建议这样做。
    如果函数内部有和全局变量同名的变量,那么全局变量会被隐藏。
    比如在主函数中输出gAll,然后进入函数f,int gAll=1,输出gAll,再回到主函数再次输出gAll,会得到12 1 12(因为f函数里的gAll生存期就是f)

    12.1.2 静态本地变量:能在函数结束后继续保持原值的本地变量

    在本地变量前加上static修饰符,成为静态本地变量。离开函数之后,静态本地变量还会存在。
    静态本地变量的初始化只会在第一次进入这个函数时做,以后再进入函数时还是会保持上次离开的值。就是即便输入:

    f(){
    	static int a=1;
    	a+=2;
    }
    

    f()连续执行三次,不是每次都会a=1,而是1 3 3 5 5 7。
    静态本地变量实际是特殊的全局变量。他们位于相同的内存区域;(可以试着看下全局变量、本地变量、静态本地变量的地址%p,静态本地变量和全局变量地址都是一样的)
    静态本地变量有全局内的生存期,但是只有函数内的局部作用域(static在这里的意思是局部作用域,本地可访问。)

    12.1.3 后记:返回指针的函数,使用全局变量的贴士

    关于指针以前讲过,如果指针返回本地变量的地址,这是一件很危险的事情。因为离开函数之后本地变量就不存在了。

    int *f();
    void g();
    
    int main()
    {
    	int *p=f();
    	printf("*p=%d",*p);
    	g();
    	printf("*p=%d",*p);
    	return 0;
    }
    int *f()
    {
    	int i=12;
    	return &i;
    }
    void g()
    {
    	int k=24;
    	printf("k=%d",k);
    	return 0;
    }
    

    输出*p=12,k=24,*p=24.
    可以得知,在函数f中本地变量i那个地址又给了k使用。这就容易出问题。
    但是全局变量和静态本地变量可以。
    返回函数在malloc内的地址是安全的,但是也容易出问题。最好的办法是返回传入的指针。
    tips
    1.不要用全局变量在函数之间传递参数和结果(可以,但是有问题。详见丰田汽车案??)。使用全局变量和静态本地变量的函数是线程不安全的,尽量避免全局变量。

    12.2.1 宏定义

    编译预处理指令
    我们在第一节课就见过了。就是#开头的(#include)。他们不是C语言的一部分,但是C语言离不开他们。
    现在要定义PI=3.14159,我们可以定义一个const的全局变量;但以前没有const的时候,用#define来定义一个宏

    #define PI 3.14159
    

    这样C语言在编译预处理时会把所有PI变成3.14159。
    我们可以通过下面的方法看在编译过程中留下来的零始文件
    gcc:

    gcc xxx.c --save-temps
    ls-l
    

    可以看到会出现xxx. c,xxx. i, xxx. s, xxx. o, xxx. out
    文件c->i这一步做了π的替换,i->s这一步产生汇编代码文件,s->o产生目标代码文件 ,最后再和其他可链接的东西链接起来生成可执行的out。
    可以发现.i比.c大很多
    如果用tail来输出c和i的结尾部分

    tail xxx.c;
    tail xxx.i;
    

    可以看到.i中把已经所有的PI替换为3.14159。
    同样的,我们也可以#define FORMAT "%f\n"FORMAT是按格式输出。
    在main函数中写:printf(FORMAT,2*PI);编译出来也是没有问题的。不过注意如果FORMAT是写在双引号里面,printf输出的就是FORMAT这六个字母了。
    #define会先对程序进行预处理,把宏都替换掉;
    #define 单词 值
    #define会对后面的值原封不动地进行文本替换,所以千万小心结尾不要加分号。因为这不是c的语句,c的语句结尾才需要加分号。
    如果一个宏中嵌套有其他宏的名字,还会再次被替换;
    如果一个宏的值超过一行,最后一行之前的行末需要加\,应该是处理回车的问题。
    宏的结尾可以有注释。

    #define prt printf("123");\
    			printf("456");
    

    我们还可以定义没有值的宏#define -DEBUG用来做条件编译,例如如果存在编译这一部分代码;如果不存在编译其他部分代码。
    c编译器里有一些预先定义的宏,可以直接用的

    __LINE__//行号
    __FILE__//文件名
    __DATE__//编译时日期
    __TIME__//编译时时间
    __STDC__
    

    12.2.2 带参数的宏

    还可以定义像函数一样的宏,带参数。
    #define cube (x)((x)*(x)*(x))
    第一个括号内是单词名字,后面是它的值。

    printf("%d\n",cube(5));//输入tail 文件名,输出((5)*(5)*(5))
    

    也可以括号内做运算,如输出cube(i+2)。
    一些细节
    x要加括号。否则比如:(rad to deg 弧度制转化为角度制)

    RADTODEG(x) (x*57.29578);
    RADTODEG(x) (x)*57.29578;
    RADTODEG(x) ((x)*57.29578);
    

    如果x=5+2,第一个计算的就是5+257.29578;
    如果计算180/RADTODEG(1),第二个计算的就是180/1
    57.29578。这样就能看出来如果宏带有参数,整个宏的值和每个参数都要有括号。
    可以带有多个函数
    #define MIN(a,b) ((a)>(b)? (b):(a))
    也可以嵌套、组合使用其他宏。
    千万不要加分号,这不是c的语句,比如if和else中间多了个加分号的宏,展开时就会有两个分号;第二个分号相当于有个空行,就把if和else分开了。
    大型的参数中,带参数的宏非常常见,运行效率比函数高。(牺牲空间换取效率)
    宏有一个缺点:没有类型检查。
    C语言有inline机制,有类型检查,也许会逐渐取代宏。
    还有很多编译预处理指令,比如条件编译、error等等,这些补充内容本课中不会讲到了。

    cs