6.1 全局变量
6.2 编译预处理
6.3 大程序结构
6.1 全局变量
全局变量
定义在函数外面的变量是全局变量
全局变量具有全局的生存期和作用域
它们与任何函数都无关
在任何函数内部都可以使用它们
全局变量初始化
没有做初始化的全局变量会得到0值
指针会得到NULL值
只能用编译时刻已知的值来初始化全局变量
它们的初始化发生在main函数之前
被隐藏的全局变量
如果函数内部存在与全局变量同名的变量,则全局变量被隐藏
静态本地变量
在本地变量定义时加上static修饰符就成为静态本地变量
当函数离开的时候,静态本地变量会继续存在并保持其值
静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值
静态本地变量
静态本地变量实际上是特殊的全局变量
它们位于相同的内存区域
静态本地变量具有全局的生存期,函数内的局部作用域
static在这里的意思是局部作用域(本地可访问)
返回指针的函数
返回本地变量的指针式危险的
返回全局变量或静态本地变量的地址是安全的
返回在函数内malloc的内存是安全的,但是容易造成问题
最好的做法是返回传入的指针
tips
不要使用全局变量来在函数间传递参数和结果
尽量避免使用全局变量
使用全局变量和静态本地变量的函数是线程不安全的
6.2 编译预处理
#开头的是编译预处理指令
它们不是C语言的成分,但是C语言程序离不开它们
#define用来定义一个宏
#define
#define<名字><值>
注意没有结尾的分号,因为不是C的语言
名字必须是一个单词,值可以是各种东西
在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值
完全的文本替换
gcc--save-temps
宏
如果一个宏的值中有其他的宏的名字,也是会被替换的
如果一个宏的值超过一行,最后一行之前的行末需要加\
宏的值后面出现的注释不会被当作宏的值的一部分
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define PI2 2*PI 5 #define PI 3.14159 6 7 void main() 8 { 9 printf("%lf\n", PI); 10 11 printf("%lf\n", PI2); 12 13 system("pause"); 14 }
没有值的宏
#define _DEBUG
这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了
预定义的宏
__LINE__
在源代码中插入当前源代码行号
__FILE__
在源代码中插入当前源代码文件名
__DATA__
在源代码中插入当前编译日期〔注意和当前系统日期区别开来〕
__TIME__
在源代码中插入当前编译时间〔注意和当前系统时间区别开来〕
__STDC__
当要求程序严格遵循ANSIC标准时该标识符被赋值为1
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 printf("%s:%d\n", __FILE__, __LINE__); 7 8 printf("%s,%s\n", __DATE__, __TIME__); 9 10 system("pause"); 11 }
像函数的宏
#define cube(x) ((x)*(x)*(x))
宏可以带参数
错误定义的宏
#define RADTODEG(x) (x*57.29578)
#define RADTODEG(x) (x)*57.29578
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define RADTODEG1(x) (x*57.29578) 5 6 #define RADTODEG2(x) (x)*57.29578 7 8 void main() 9 { 10 printf("%lf\n", RADTODEG1(5 + 2)); 11 12 printf("%lf\n", RADTODEG2(5 + 2)); 13 14 system("pause"); 15 }
带参数的宏的原则
一切都要括号
整个值要括号
参数出现的每个地方都要括号
#define RADTODEG(x) ((x)*57.29578)
带参数的宏
可以带多个参数
#define MIN(a,b) ((a)>(b)?(b):(a))
也可以组合(嵌套)使用其他宏
分号?
结尾不要加上分号;
带参数的宏
在大型程序的代码中使用非常普遍
可以非常复杂,如“产生”函数
在#和##这两个运算符的帮助下
存在中西方文化差异
部分宏会被inline函数替代
其他编译预处理指令
条件编译
error
6.3 大程序结构
多个.c文件
main()里的代码太长了适合分成几个函数
一个源代码文件太长了适合分成几个文件
两个独立的源代码文件不能编译形成可执行的程序
编译单元
一个.c文件是一个编译单元
编译器每次编译只处理一个编译单元
头文件
把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型
#include
#include是一个编译预处理指令。和宏一样,在编译之前就处理了
它把那个文件的全部文本内容原封不动地插入到它所在的地方
所以也不是一定要在.c文件的最前面#include
""还是<>
#include有两种形式来指出要插入的文件
""要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,如果没有,到编译器指定的目录去找
<>让编译器只在指定的目录去找
编译器自己知道自己的标准库的头文件在哪里
环境变量和编译器命令行参数也可以指定寻找头文件的目录
#include的误区
#include不是用来引入库的
stdio.h里只有printf的原型,printf的代码在另外的地方,某个.lib(Windows)或.a(Unix)中
现在的C语言编译器默认会引入所有的标准库
#include <stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给的参数值是正确的类型
头文件
在使用和定义这个函数的地方都应该#include这个头文件
一般的做法就是任何.c都有对应的同名.h,把所有的对外公开的函数原型和全局变量的声明都放进去
不对外公开的函数
在函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数
在全局变量前面加上static就使得它成为只能在所在的编译单元中被使用的全局变量
变量的声明
int i;是变量的定义
extern int i;是变量的声明
声明和定义
声明是不产生代码的东西
函数原型
变量声明
结构声明
宏声明
枚举声明
类型声明
inline声明
定义是产生代码的东西
头文件
只有声明可以被放在头文件中
是规则不是法律
否则会造成一个项目中多个编译单元里有重名的实体
某些编译器允许几个编译单元中存在同名的函数,或者用weak修饰符来强调这种存在
重复声明
同一个编译单元里,同名的结构不能被重复声明
如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次
所以需要“标准头文件结构”
标准头文件结构
1 #ifndef __LIST_HEAD__ 2 #define __LIST_HEAD__ 3 4 #include "node.h" 5 6 typedef struct _list 7 { 8 Node *head; 9 Node *tail; 10 }List; 11 12 #endif // !
运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次
#pragma once也能起到相同的作用,但是不是所有的编译器都支持