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安装所需的…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
