当前位置 博文首页 > jtwqwq的博客:翁恺老师C语言程序设计网课(12)
定义在函数内部的是本地变量。而定义在外面的是全局变量。
本地变量最大特点是生存期、作用域都在函数内部,出来就用不了了。
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)
在本地变量前加上static修饰符,成为静态本地变量。离开函数之后,静态本地变量还会存在。
静态本地变量的初始化只会在第一次进入这个函数时做,以后再进入函数时还是会保持上次离开的值。就是即便输入:
f(){
static int a=1;
a+=2;
}
f()连续执行三次,不是每次都会a=1,而是1 3 3 5 5 7。
静态本地变量实际是特殊的全局变量。他们位于相同的内存区域;(可以试着看下全局变量、本地变量、静态本地变量的地址%p,静态本地变量和全局变量地址都是一样的)
静态本地变量有全局内的生存期,但是只有函数内的局部作用域(static在这里的意思是局部作用域,本地可访问。)
关于指针以前讲过,如果指针返回本地变量的地址,这是一件很危险的事情。因为离开函数之后本地变量就不存在了。
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.不要用全局变量在函数之间传递参数和结果(可以,但是有问题。详见丰田汽车案??)。使用全局变量和静态本地变量的函数是线程不安全的,尽量避免全局变量。
编译预处理指令
我们在第一节课就见过了。就是#开头的(#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__
还可以定义像函数一样的宏,带参数。
#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/157.29578。这样就能看出来如果宏带有参数,整个宏的值和每个参数都要有括号。
可以带有多个函数
#define MIN(a,b) ((a)>(b)? (b):(a))
也可以嵌套、组合使用其他宏。
千万不要加分号,这不是c的语句,比如if和else中间多了个加分号的宏,展开时就会有两个分号;第二个分号相当于有个空行,就把if和else分开了。
大型的参数中,带参数的宏非常常见,运行效率比函数高。(牺牲空间换取效率)
宏有一个缺点:没有类型检查。
C语言有inline机制,有类型检查,也许会逐渐取代宏。
还有很多编译预处理指令,比如条件编译、error等等,这些补充内容本课中不会讲到了。