[C语言]进阶|程序结构
全局变量:
// main.c // Created by weichen on 15/7/14. // Copyright (c) 2015年 weichen. All rights reserved. #include <stdio.h> int gAll; // int g2 = gAll; 编译不通过;如果是 const int gAll = 10;int g2 = gAll;是可以的,但是不推荐这么写 void f(int a); void t(void); int main(int argc, const char * argv[]) { /* 1. 全局变量 定义在函数外面的变量是全局变量 全局变量具有全局的生存期和作用域 它们与任何函数无关 在任何函数内部都可以使用它们 2. 全局变量初始化 没有做初始化的全局变量会得到0值,编译器会加上;本地变量是内存里有什么就得到什么 指针没有初始化会得到NULL值 只能用编译时刻已知的值来初始化全局变量 它们的初始化发生在main函数之前 3. 静态本地变量 在本地变量定义时加上static修饰符就成为静态本地变量 当函数离开的时候,静态本地变量会继续存在并保持其值 静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值 静态本地变量实际上是特殊的全局变量 它们位于相同的内存区域;&gAll和&all相差正好是一个int的大小,而实际的本地变量放在另外的地方 静态变量具有全局的生存期,函数内的局部作用域 static在这里的意思是局部作用域(本地可访问) 4. 返回指针的函数 返回本地变量的地址是危险的;因为离开函数,本地变量就不存在了 返回全局变量或静态本地变量的地址是安全的 返回在函数内malloc的内存是安全的,但是容易造成问题 最好的做法是返回传入的指针 5. Tips 不要使用全局变量来在函数间传递参数和结果 尽量避免使用全局变量 丰田汽车的案子 使用全局变量和静态本地变量的函数是线程不安全的 */ printf("in %s is %d\n", __func__, gAll); // in main is 0 f(gAll); printf("again in %s is %d\n", __func__, gAll); // again in main is 2 t(); // all is 6 t(); // all is 7 , 没有被重新初始化为5,使用上次得到的变量值 t(); // all is 8 return 0; } // 全局变量 void f(int a) { printf("in %s is %d\n", __func__, a); // in f is 0 gAll += 2; printf("again in %s is %d\n", __func__, gAll); // again in f is 2 int gAll = 1; // 重新声明一个与全局变量同名的本地变量,此时全局变量gAll被隐藏 printf("last in %s is %d\n", __func__, gAll); // last in f is 1 } // 静态本地变量 void t(void) { static int all = 5; int k = 0; all += 1; printf("all is %d\n", all); printf("&gAll = %p\n", &gAll); // &gAll = 0x10000101c printf("&all = %p\n", &all); // &all = 0x100001018 printf("&k = %p\n", &k); // &k = 0x7fff5fbff80c } /* int* g(void) { int x = 10; return &x; // 返回本地变量的地址,编译要么不通过要么提示warning } */
编译预处理:
// main.c // Created by weichen on 15/7/15. // Copyright (c) 2015年 weichen. All rights reserved. #include <stdio.h> // const double PI = 3.14159; // C99可以使用的方式 #define PI 3.14159 // C99之前使用的方式,#define是编译预处理指令 #define FORMAT "%f\n" #define PI2 2*PI // pi * 2 #define PRT printf("%f ", PI);\ printf("%f\n", PI2) #define cube(x) ( (x) * (x) * (x) ) #define RADTODEG1(x) (x * 57.3) #define RADTODEG2(x) (x) * 57.3 int main(int argc, const char * argv[]) { /* 编译预处理指令 #开头的是编译预处理指令 它们不是C语言的成分,但是C语言程序离不开它们 #define用来定义一个宏 如何看到编译这个过程(保存编译过程中的临时文件,加--save-temps选项): 终端下 gcc main.c -o 1.out --save-temps 【 main.c -> main.i(预处理后的文件) -> main.s(汇编代码)-> main.o(目标代码文件) 1.out(可执行文件) 】 tail main.i 看到后面几行中PI被替换成值 宏 #define <名字> <值> 注意结尾不能加分号,因为不是C的语句 名字必须是一个单词,值可以是各种东西 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值 完全的文本替换 gcc --save-temps 如果一个宏的值中有其他的宏的名字,也是会被再次替换 如果一个宏的值超过一行,最后一行之前的行末需要加\ 宏的值后面出现的注释不会被当做宏的值得一部分,C语言的注释有效 没有值的宏 #define _DEBUG 这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了(如果定义了执行这一部分代码,没有定义执行另一部分代码) 预定义的宏 __LINE__ // 当前代码行号 __FILE__ // 源代码包含路径的文件名 __DATE__ // 编译时日期 __TIME__ // 编译时时间 __STDC__ // 当要求程序严格遵循ANSIC标准时,标识符__STDC__就会被赋值为1 带参数的宏 #define cube(x) ( (x) * (x) * (x) ) 宏可以带参数 可以带多个参数 #define MIN(a, b) ((a) > (b) ? (b) : (a)) 也可以组合(嵌套)使用其它宏 在大型程序的代码中使用非常普遍(在代替函数时运行效率比函数高,但是代码大小比函数大) 可以非常复杂,如“产生”函数 在#和##这两个运算符的帮助下 存在中西方文化差异(国外使用宏的项目更多) 部分宏会被inline函数替代(加上inline就代表这个函数是声明而不是定义,使用inline时,相当于把当前调用替换成函数里的代码,增加了代码但是减少了函数调用的额外开销,是以空间换时间的做法;什么时候用inline:函数2-3行很小或在循环里频繁调用的函数,什么时候不用inline:超过20行的函数或递归的函数) 错误定义的宏 #define RADTODEG1(x) (x * 57) #define RADTODEG2(x) (x) * 57 带参数的宏的原则 一切都要括号 整个值要括号 参数出现的每个地方都要括号 #define RADTODEG(x) ((x) * 57.3) 其它编译预处理指令 条件编译 error ... */ printf("%f\n", 2*PI); // 6.283180 printf(FORMAT, PI2); // 6.283180 PRT; // 3.141590 6.283180 printf("%s:%d\n", __FILE__, __LINE__); // Users/weichen/.../main.c:66 #通常用于调试 printf("%s:%s\n", __DATE__, __TIME__); // Jul 15 2015:01:47:36 #区分程序版本 if (__STDC__ == 1) { printf("ANSIC\n"); } else { printf("Not ANSIC\n"); } int i = 2; printf("%d\n", cube(5)); // 125 printf("%d\n", cube(i)); // 8 printf("%d\n", cube(i+2)); // 64 printf("%f\n", RADTODEG1(5+2)); // 119.600000 printf("%f\n", 180/RADTODEG2(1)); // 10314.000000 #我们希望的是180/57.3, 而实际却不是 return 0; }
大程序结构:
// main.c // Created by weichen on 15/7/15. // Copyright (c) 2015年 weichen. All rights reserved. #include <stdio.h> int main(int argc, const char * argv[]) { /* 大程序结构 main()里地代码太长了适合分成几个函数 一个源代码.c文件太长了适合分成几个文件 两个独立的源代码文件不能编译形成可执行的程序 项目 在DevC++中新建一个项目,然后把几个源代码文件加入进去 对于项目,DevC++的编译会把一个项目中所有的源代码文件都编译后,链接起来 有得IDE有分开的编译和构建两个按钮,前者是对单个源代码文件编译,后者是对整个项目做链接 编译单元 一个.c文件是一个编译单元 编译器每次编译只处理一个编译单元,编译形成.o文件,由链接器链接它们 头文件 如果main里面调用的函数没有进行声明,C语言会将函数的所有参数和返回值默认当做int对待,这种情况下不能保证函数被正确使用 把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型 #include是一个编译预处理指令,和宏一样,在编译之前就处理了 它把那个文件的全部文本内容原封不动的插入到它所在的地方 所以也不是一定要在.c文件的最前面#include #include有两种形式来指出要插入的文件 " "要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,如果没有,到编译器指定的目录去找 < >让编译器只在指定的系统目录去找(/usr/include) 编译器自己知道自己的标准库的头文件在哪里 环境变量和编译器命令行参数也可以指定寻找头文件的目录 在使用和定义这个函数的地方都应该#include这个头文件 一般的做法是除了main之外的任何.c都有对应的同名的.h, 把所有对外公开的函数的原型和全局变量的声明都放进去 #include的误区 #include不是用来引入库的 stdio.h里只有printf的原型,printf的代码在另外的地方,某个.lib(windows)或.a(Unix)中 现在的C语言编译器默认会引入所有的标准库 #include <stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给出的参数值是正确地类型 不对外公开的函数 在函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数(函数不希望别人用,仅当前文件中能使用) 在全局变量前面加上static就使得它成为只能在所在的编译单元中被使用的全局变量(仅当前文件中能使用的全局变量) 变量定义和声明的区别 int i; 是变量的定义 extern int i; 是变量的声明,不能初始化,放在头文件中;用来告诉编译器,其它用到的变量i是存在的 声明是不产生代码的东西 函数原型 变量声明 结构声明 宏声明 枚举声明 类型声明 inline声明 定义是产生代码的东西 函数 全局变量 只有声明可以被放在头文件中(是规则不是法律) 否则会造成一个项目中多个编译单元里有重名的实体 某些编译器允许几个编译单元中存在同名的函数,或者用weak修饰符来强调这种存在 同一个编译单元里,同名的结构不能被重复声明 如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次 所以需要"标准头文件结构",运用条件编译和宏,保证这个头文件在一个编译单元里只会被#include一次 #ifndef _MAX_H_ #define _MAX_H_ ..... #endif #pragma once也能起到相同的作用,但不是所有的编译器都支持 */return 0; }
Refer:C语言如何进阶