14-C语言多文件编程
一、各种变量
在学习多文件编程之前,先要了解清楚各种变量的作用范围以及生命周期。
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.h>int 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 NUM=10 // NUM=10等号两边没有空格
注意:命令里定义了宏,在代码文件里就不要定义同名的宏了。宏只在当前文件有效。
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 时:执行减法运算。
- 当定义了宏 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.h"int 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 once
4.3条件编译之if
- 语法格式
#if 宏语句1;
#else语句2;
#endif
- 说明:如果宏的值为真(非0),则执行语句1,如果宏的值为假(0),则执行语句2。
三、多文件编程
多文件编程,即相似的功能函数写在一个文件里,main.c
只负责整个项目的主体框架和各种功能函数的调用,函数的声明放到同名的头文件里,同时头文件里还主要放一下结构体类型,类等。
- 代码演示
功能文件:my_func.c
int my_add(int a, int b)
{return a+b;
}
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.h"int 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.so
3.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_PATH
3.2.3将动态库放入系统目录
和静态库一样,直接将动态库移动到系统指定目录就行,然后编译格式也一样:gcc 项目文件.c -l动态库名
gcc main.c -lmylb
- 说明:
- 如果静态库和动态库同名,默认是使用动态链接,使用静态库需要加
-static
; - 放到指定系统目录,就不需要配置环境变量了,会从系统指定的默认路径查找动态库。
相关文章:

14-C语言多文件编程
一、各种变量 在学习多文件编程之前,先要了解清楚各种变量的作用范围以及生命周期。 1.普通变量 1.1普通局部变量 定义形式:在复合语句{}里面定义的变量为普通局部变量;作用范围:在复合语句{}里面有效;生命周期&am…...

基于Springboot的在线问卷调查系统【附源码】
基于Springboot的在线问卷调查系统 效果如下: 系统主页面 问卷列表页面 个人中心页面 系统登陆页面 管理员主页面 问卷管理页面 研究背景 随着互联网技术的飞速发展,传统的问卷调查方式因其时间和地点的限制,难以高效地收集到足够的数据。…...

Redis热点数据管理全解析:从MySQL同步到高效缓存的完整解决方案
1. 引言 1.1 背景介绍:MySQL与Redis在高性能场景下的结合 在现代互联网应用中,MySQL作为关系型数据库,承担了大量业务数据的存储任务。然而,随着业务的增长,海量数据的查询性能成为一个瓶颈。为了应对高并发和低延迟…...

【图书介绍】】几本Linux C\C++编程图书
Linux C\C编程,是IT领域比较稳定的职业发展方向,本文介绍几本Linux开发方面的图书。 《Linux C与C一线开发实践(第2版)》 《Linux C与C一线开发实践(第2版)(Linux技术丛书)》(朱文…...

MFC/C++学习系列之简单记录7
MFC/C学习系列之简单记录7 前言句柄的介绍句柄的使用AFX开头的函数都是干什么用的?总结 前言 在MFC的使用中发现了句柄,今天来详细学习一下MFC中如何使用句柄吧! 句柄的介绍 句柄的使用是资源管理和传递的关键机制,通过句柄将系…...

使用GPT进行SCI论文润色常用语句
声明:本文仅作为本人记录学习使用。 You are now a professional academic touch-up specialist. Please polish the English draft I am sending you next. After analyzing the paragraph, give suggestions for polishing in terms of sentence structure, gram…...

Redis密码设置与访问限制(网络安全)
现在用redis缓存热数据越来越常见了,甚至一些配置,开关等等的东西也写到redis里。原因就是redis简单高效。redis里的数据也越来越重要了,例如一些业务的中间数据会暂时存放在redis里,所以限制redis的访问还是很有必要。 本文通过…...

php的线程安全与非线程安全版本的区别
PHP的线程安全(Thread Safe,简称TS)与非线程安全(Non-Thread Safe,简称NTS)版本主要在多线程环境下的行为特性、性能、以及适用场景上存在差异。以下是两者的详细对比: 一、定义与概念 线程安…...

标贝科技受邀出席2024ADD数据应用场景大会 共议数据要素发展新契机
12月13日,由北京市通州区人民政府主办,通州区经济和信息化局、通州区台湖镇人民政府承办的2024-ADD数据应用场景大会成功举办。标贝科技作为AI数据领域代表企业受邀出席大会,与数据要素创业者、投资人一起走进通州台湖,共话数据要…...

electron-vite打包后图标不生效问题
在electron-builder.yml中,通过icon配置自己的图标,以下是正确代码 win:executableName: 名称icon: build/icon.ico nsis:artifactName: ${name}-${version}.${ext}shortcutName: ${productName}uninstallDisplayName: ${productName}createDesktopShor…...

systemverilog中的unique if
1 基本概念 在 SystemVerilog 中,unique if是一种条件判断结构。它用于检查多个互斥的条件,以确保在给定的情况下只有一个条件分支被执行。这有助于提高代码的可读性和可维护性,同时也能帮助发现潜在的逻辑错误,报错原因有以下两个…...

【MySQL篇】事务的认识以及四大特性
何为事务? 事务(Transaction)是指一组操作的集合,这些操作要么全部执行成功,要么全部不执行。事务通常用于保证数据库的一致性、完整性和可靠性,确保数据的完整性与正确性。 有效避免部分执行࿰…...

Windows 11 安装 Dify 完整指南 非docker环境
# Windows 11 安装 Dify 完整指南## 前置要求- Python 3.11 - Node.js 18 - PostgreSQL 14 - Redis for Windows - Git - Ollama (可选,用于本地模型)## 详细安装步骤### 1. 安装必要软件1. **Python 3.11**- 从 https://www.python.org/downloads/release/python-…...

电子电气架构 --- 什么是EPS?
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所谓鸡汤,要么蛊惑你认命,要么怂恿你拼命,但都是回避问题的根源&…...

12寸半导体厂等保安全的设计思路
等级保护(等保)二级和三级的主要区别在于安全要求的严格程度、所需部署的安全措施和设备、以及对安全事件响应和处理的能力。以下是等保二级和三级之间的一些关键区别: 一、 安全要求严格程度: - 等保二级:适用于需要较高安全保护的信息系统,要求能够防范轻微的恶意攻击…...

【Chrome Extension】一、CSDN计时扩展设计
【Chrome Extension】一、CSDN计时扩展设计 重点内容内容脚本 content_scripts 文件目录1、整体目录2、manifest.json3、scripts/content.js4、css/content.css 重点内容 内容脚本 content_scripts 1、manifest.json文件配置 {"manifest_version": 3, # *依赖Chro…...

C语言——数据在内存中的存储
目录 前言 一数据类型 类型归类 二整形在内存中的存储 原反补码 大小端 相关练习题 三浮点数在内存中的储存 浮点数储存规则 前言 只有取学习数据在内存中的存储,我们在以后才能定义好(用好)各种类型的数据! 一数据类型…...

Python(二)str、list、tuple、dict、set
string name abcdefprint(name[0]) #a # 切片:取部分数据 print(name[0:3]) # 取 下标为0,1,2的字符 abc print(name[2:]) # 取 下标为2开始到最后的字符 cdef print(name…...

如何在谷歌浏览器中设置邮件客户端
在日常生活和工作中,电子邮件已经成为不可或缺的沟通工具。对于使用谷歌浏览器的用户来说,将Chrome设置为默认邮件客户端可以带来诸多便利。本文将详细介绍如何在谷歌浏览器中设置邮件客户端,帮助大家轻松实现这一目标。 在谷歌浏览器中设置邮…...

Robot Framework搭建自动化测试框架
1.配置环境 需要安装jdk8,andrid sdk(安装adb),pycharm编译环境以及软件 安装Robot Framework 首先,你需要安装Robot Framework,可以使用 pip 进行安装: pip install robotframework安装所需的…...

Linux——字符设备驱动控制LED
目录 驱动模块的加载和卸载 驱动程序Makefile编写 字符设备注册与注销 字符设备驱动模板 应用程序对驱动读写操作 iounmap函数 LED寄存器物理地址映射到虚拟地址 应用程序代码编写 Linux驱动的两种运行方式: 1、将驱动编译进Linux内核中,也就是zIm…...

云边端架构的优势是什么?面临哪些挑战?
一、云边端架构的优势 降低网络延迟:在传统集中式架构中,数据需传输到云计算中心处理,导致网络延迟较高。而云边端架构将计算和存储推向边缘设备,可在离用户更近的地方处理数据,大大降低了网络延迟,提升了用…...

Effective C++ 条款 01:视 C++ 为一个语言联邦
文章目录 条款 01:视 C 为一个语言联邦关键点 条款 01:视 C 为一个语言联邦 C 是一个复杂而多样化的编程语言,可以将其视为由多个子语言(sublanguage)组成的联邦。为了更好地理解和使用 C,需要认识它的四个…...

Redis分布式锁释放锁是否必须用lua脚本?
无lua脚本释放锁: public void unlock(String key, String uniqueValue) {String value redisDao.getString(key);if (value ! null && value.equals(uniqueValue))redisDao.delete(key); }使用lua脚本释放锁: // LUA脚本 -> 分布式锁解锁原…...

JVM系列(十三) -常用调优工具介绍
最近对 JVM 技术知识进行了重新整理,再次献上 JVM系列文章合集索引,感兴趣的小伙伴可以直接点击如下地址快速阅读。 JVM系列(一) -什么是虚拟机JVM系列(二) -类的加载过程JVM系列(三) -内存布局详解JVM系列(四) -对象的创建过程JVM系列(五) -对象的内存分…...

数据中心虚拟化与高可用性架构实施指南
数据中心虚拟化与高可用性架构实施指南 项目背景 随着业务的不断扩展和技术的迭代更新,公司决定采用虚拟化技术和构建高可用性架构来提高数据中心的资源利用率和业务连续性。本项目旨在详细描述运维人员在实施数据中心虚拟化和高可用性架构过程中的关键步骤和任务…...

对文件内的文件名生成目录,方便查阅
import os import re# 定义要查找的目录路径 path r"J:\...\顺序目录" # 要遍历的主目录路径# 定义输出的目录文件路径 output_file r"J:\...\目录_中文文件.txt" # 保存结果的文件路径# 判断文件名是否包含中文字符 def contains_chinese(text):retur…...

leetcode hot100 轮转数组
189. 轮转数组 已解答 中等 相关标签 相关企业 提示 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮…...

定位方式:css
使用相对路径 div ul #div下的所有ul,空格表示相对路径(这个实际中用的多一些) 绝对路径-一般不用绝对路径 html>head>div,“>”表示根路径 使用class名称定位 使用.表示 使用id定位 使用#表示 使用属性定位 [属性名…...

谷歌浏览器的网络连接问题解决方案
在数字化时代,网络浏览器已成为日常工作和生活中不可或缺的工具。谷歌浏览器以其快速、稳定和丰富的功能深受用户喜爱。然而,就像其他软件一样,谷歌浏览器也可能遇到网络连接问题,这可能由多种因素引起。本文将为您提供一系列解决…...