网站设计 素材,电子企业网络推广方案,深圳燃气公司有哪几家,重庆建新建设工程有限公司网站一、各种变量
在学习多文件编程之前#xff0c;先要了解清楚各种变量的作用范围以及生命周期。
1.普通变量
1.1普通局部变量
定义形式#xff1a;在复合语句{}里面定义的变量为普通局部变量#xff1b;作用范围#xff1a;在复合语句{}里面有效#xff1b;生命周期先要了解清楚各种变量的作用范围以及生命周期。
1.普通变量
1.1普通局部变量
定义形式在复合语句{}里面定义的变量为普通局部变量作用范围在复合语句{}里面有效生命周期进入复合语句{}时开始复合语句结束局部变量被释放内存区域栈区注意事项 局部变量不初始化内容不确定局部变量如果同名遵循就近原则。
1.2普通全局变量
定义形式在函数外定义的变量作用范围当前源文件以及其他源文件都有效生命周期进程开始到运行到程序结束后才释放内存区域全局区注意事项 全局变量不初始化内容为 0如果其他源文件要使用全局变量必须在使用处加 extern 声明全局变量和局部变量同名时优先选择局部变量。
2.static修饰的变量
2.1静态局部变量
定义形式在复合语句{}里面定义加 static 修饰的变量作用范围在复合语句{}里面有效生命周期进程开始到运行到程序结束后才释放内存区域全局区注意事项 静态局部变量不初始化内容为 0静态局部变量只会定义一次。
代码演示
void func()
{static int num 10;num 10;printf(%d\n, num);
}int main()
{func();func();func();func();return 0;
}运行结果
20
30
40
50说明根据以前的结果函数内部的临时局部变量函数调用结束就释放了会打印4次20但加了 static 修饰以后函数调用结束并未释放而是到整个进程结束才释放。
2.2静态全局变量
定义形式全局变量前加 static 修饰作用范围只在当前源文件有效生命周期进程开始到运行到程序结束后才释放内存区域全局区注意事项 不初始化为 0只在当前源文件有效。
说明和普通全局变量相比静态全局变量只是作用范围发生了改变目的是为了定义一个全局变量只在当前文件有效不希望其它文件去修改。
3.static修饰的函数
3.1全局函数
我们之前定义的函数默认为全局函数只要在其他源文件加 extern 声明就可以在其他源文件中使用。
其特性和全局变量差不多不过函数是存储在代码区的。
3.2静态函数
定义函数时函数返回值类型前加 static 修饰为静态函数。
和全局函数相比静态函数不能被其他源文件使用只能在当前文件使用。
二、gcc编译
1.编译过程
gcc编译过程主要分为预处理、编译、汇编、链接四个步骤。 预处理主要进行头文件包含、宏替换、条件编译、删除注释 不作语法检查 代码演示 #define NUM 100
#include stdio.hint main()
{// 打印 NUM 的值printf(NUM %d, NUM);return 0;
}gcc -E test.c -o test.i // 预处理预处理后的代码 // ......省略头文件里的代码int main()
{printf(NUM %d, 100);return 0;
}说明预处理后头文件的代码会被拷贝到当前文件宏被直接替换成了数值常量注释也被删除了。 编译将预处理好的.i文件编译成汇编文件.s (作语法检查) gcc -S test.i -o test.s // 编译汇编将汇编文件.s生成 二进制文件.o gcc -c test.s -o test.o // 汇编链接将各个独立的二进制文件库函数启动代码生成可执行文件。 gcc test.o -o test // 链接上面的编译过程是具体的编译步骤我们在实际编译的过程中基本上一条命令就解决了 gcc 源文件 -o 可执行文件名gcc 源文件 这种编译方式默认生成的可执行文件名为 a.out。
2.头文件包含
前面提到头文件包含在预处理阶段会将头文件里的代码拷贝到当前文件头文件包含有两种方式
#include stdio.h
#include func.h#include stdio.h只从系统指定目录去找头文件一般用于包含系统头文件#include func.h先从当前目录查找头文件如果找不到才从系统指定目录找头文件。
3.宏定义
3.1无参的宏
前面编译过程中已经提到了宏在预处理阶段会被替换成宏所对应的常量数据而在编译阶段才会进行语法检查如果代码中宏的使用发生了错误是没法定位到错误语句的同时也可以通过宏来定义数组。
代码演示
#define NUM 10int main()
{int nums[NUM];printf(%zu\n, sizeof(nums));return 0;
}运行结果
40之前我们在定义数组的时候[]里只能传整数常量这里通过宏也可以定义成功因为宏的预处理阶段就替换成了对应的整数常量了编译阶段进行语法检查不会有问题。定义宏的时候不要在末尾加分号;。同时还可以通过另外一种方法在linux终端输入编译命令的时候定义宏
gcc test -D NUM10 // NUM10等号两边没有空格注意命令里定义了宏在代码文件里就不要定义同名的宏了。宏只在当前文件有效。
3.2有参的宏
有参的宏又叫宏函数。
3.2.1宏函数的特性
代码演示
#define MUL(a,b) a*bint main()
{printf(%d\n, MUL(3, 5)); // 3 5printf(%d\n, MUL(3 2, 5 1)); // 3 2 * 5 1printf(%d\n, MUL2(3 2, 5 1)); // (3 2) * (5 1)return 0;
}运行结果
15
14
30说明 定义宏的时候宏名一般用大写字母宏函数定义格式#define 宏名(参数1,参数2...) 表达式宏的参数在传递的时候是整体替换的替换后再按照相应运算符的优先级进行计算无法保证参数的完整性为了保证参数完整性可以在参数外面加上()。
3.2.2宏函数与普通函数对比
宏函数 宏函数在预处理阶段展开有大量重复代码占空间但没有函数调用带来的出入栈的开销用空间换时间宏的参数没有类型不能保证参数的完整性宏没有作用域的限制不能作为结构体或类类在c阶段学习的成员。 普通函数 普通函数代码只有一份节约空间但调用需要出入栈的开销消耗时间用时间换空间函数的参数有类型可以保证参数的完整性有作用域的限制能作为结构体或类的成员。
3.3取消宏
可以通过以下方式取消已经定义的宏
#undef N // 取消宏定义4.条件编译
条件编译可以分为三种情况。
4.1条件编译之ifdef
语法格式
#ifdef 宏语句1
#else语句2
#endif 说明 如果定义了相应的宏则执行语句1如果没有定义相应的宏则执行语句2这里的条件编译和前面学习的 if 条件语句是有区别的条件编译在预处理阶段会将不满足条件的代码删除而 if 条件语句不会。 这种写法通常用来分割代码
#define ADDint main()
{int a, b;printf(请输入两个整数);scanf(%d %d,a, b);#ifdef ADDint ret a b;#elseint ret a - b;#endifprintf(计算结果%d\n, ret);return 0;
}运行结果 当定义了宏 ADD 时执行加法运算或者也可以不在代码里定义宏在编译时定义gcc test.c -D ADD当未定义宏 ADD 时执行减法运算。
4.2条件编译之ifndef
语法格式
#ifndef 宏语句1
#else语句2
#endif 说明如果没有定义相应的宏则执行语句1如果定义了相应的宏则执行语句2。这种写法一般用于防止头文件包含如下面案例
头文件a.h
#include b.h头文件b.h
int num 100;主函数main.c
#include a.h
#include b.hint main()
{printf(%d\n, num);return 0;
}上面的代码会报错原因是变量重复定义了通过预处理就能看出包含头文件以后num变量定义了两次
// #include a.h
// #include b.h
int num 100;// #include b.h
int num 100;int main()
{printf(%d\n, num);return 0;
}解决办法通过 ifndef 条件编译
头文件a.h
#ifndef __A_H__ // 两个下划线头文件名大写文件名的.用一个_代替两个下划线
#define __A_H__#include b.h#endif头文件b.h
#ifndef __B_H__
#define __B_H__int num 100;#endif主函数和上面一样包含头文件以后
#ifndef __A_H__
#define __A_H__
#include b.h
#endif#ifndef __B_H__
#define __B_H__
int num 100; // 因为上面已经包含了 b.h 头文件所以这里条件不满足不会再包含一遍了
#endifint main()
{printf(%d\n, num);return 0;
}上面是 linux 环境下win 环境下在头文件写上如下一句代码即可
#pragma once4.3条件编译之if
语法格式
#if 宏语句1;
#else语句2;
#endif说明如果宏的值为真非0则执行语句1如果宏的值为假0则执行语句2。
三、多文件编程
多文件编程即相似的功能函数写在一个文件里main.c只负责整个项目的主体框架和各种功能函数的调用函数的声明放到同名的头文件里同时头文件里还主要放一下结构体类型类等。
代码演示
功能文件my_func.c
int my_add(int a, int b)
{return ab;
}
int my_sub(int a, int b)
{return a-b;
}
int my_mul(int a, int b)
{return a*b;
}
int my_div(int a, int b)
{return a/b;
}头文件my_func.h
#ifndef __MY_FUNC_H__
#define __MY_FUNC_H__
extern int my_add(int a, int b);
extern int my_sub(int a, int b);
extern int my_mul(int a, int b);
extern int my_div(int a, int b);
#endif主函数main.c
#include stdio.h
#include my_func.hint main()
{printf(%d\n, my_add(100,20));printf(%d\n, my_sub(100,20));printf(%d\n, my_mul(100,20));printf(%d\n, my_div(100,20));return 0;
}说明编译的时候两个文件要一起编译gcc main.c my_func.c。
四、静态库与动态库
1.静态库和动态库的区别
静态链接
将静态库的所有函数都链接到可执行文件中即使库删除了也不影响以及链接的文件的运行优点对库的依赖不大缺点 可执行文件大如果库发生变化需要重新链接。
动态链接
在链接阶段仅仅建立和所需库函数的链接关系在运行阶段才将所需的库函数包含在可执行文件中优点生成可执行文件小缺点对库的环境依赖大如果库被删除了就无法执行了。
我们之前的编译方式就是动态链接gcc test.c静态链接gcc test.c --static。
2.制作静态库
2.1静态库的制作流程
将需要制作库的源文件生成二进制文件.o gcc -c test.c -o test.o 使用二进制文件生成库 ar rc libmylib.a test.o注意以 lib 开头.a 结尾库名称是 mylib库名前一定要加lib。
2.2使用静态库
使用静态库用三种方法。
2.2.1将库放入项目目录
即将静态库和项目文件放入同一级目录下编译格式gcc 项目文件.c lib库文件.o如
gcc main.c libmylib.a // linux命令2.2.2将库放入指定目录
即将静态库放入一个其它创建好的目录这个目录里也可以放自定义的头文件。
编译格式gcc 项目文件.c -I指定文件目录 -L指定文件目录 -l库文件文件名如
gcc main.h -I./fun -L./fun -lmylib说明 -I 指头文件的路径-L 指库的路径-l 指库的名称它们和相应目录、库文件之间没有空格如果将头文件放入其它目录不通过 -L 指定那么包含头文件的时候需要包含路径一起比较麻烦因此还是推荐这种方式。
2.2.3将库放入系统指定目录
将头文件和库文件移动到下面指定的文件路径下 系统默认的头文件路径/usr/include 系统默认的库的路径/usr/lib。
包含头文件的时候直接和包含系统头文件一样就可以链接库也只需要加上-l库名称就行。
3.制作动态库
3.1动态库的制作
制作动态库的格式gcc -shared 用于制作动态库的文件.c -0 lib动态库名.s0如
gcc -shared test.c -o libmylib.so3.2动态库的使用
和静态库一样使用动态库也有三种方法。
3.2.1动态库在项目目录
即将动态库和项目文件放入同一级目录下编译格式gcc 项目文件.c lib库文件.so。
LD_LIBRARY_PATH是 linux系统中的一个环境变量用于指定动态链接的搜索路径这里需要加上项目路径如
gcc main.c libmylib.so
export LD_LIBRARY_PATH./:$LD_LIBRARY_PATH说明./是当前路径即项目路径:用于分割不同路径$LD_LIBRARY_PATH取出原本的路径。
3.2.2将动态库放入指定路径
和静态库操作一样只不过这里也还需要修改LD_LIBRARY_PATH环境变量如
gcc main.h -I./fun -L./fun -lmylib
export LD_LIBRARY_PATH./:$LD_LIBRARY_PATH3.2.3将动态库放入系统目录
和静态库一样直接将动态库移动到系统指定目录就行然后编译格式也一样gcc 项目文件.c -l动态库名
gcc main.c -lmylb说明如果静态库和动态库同名默认是使用动态链接使用静态库需要加-static放到指定系统目录就不需要配置环境变量了会从系统指定的默认路径查找动态库。