c语言(数组,函数,变量的存储类别,预处理,动态库和静态库)
数组,函数,变量的存储类别,预处理,动态库和静态库
1. 数组
1.1 基本概念
数组是若干个相同类型的变量在内存中有序存储的集合。
有序存储:在连续的内存空间中存储数据
定义数组的语法:
数据类型 数组名[N];
数据类型:代表是数组中存储的每一个变量的数据类型
数组名:符合变量名的命名规范
N:代表数组元素的个数,只能用常量,不能使用变量
1.2 数组的分类
按数据类型分:
字符数组: char xxx[N];
短整型数组: short xxx[N];
整型数组: int xxx[N];
长整型数组: long xxx[N];
浮点型数组: float|double xxx[N];
指针数组: char *xxx[N]; short *xxx[N];....
数组中的每一个元素的数据类型为 char*或short*
结构体数组: struct STU xxx[N];
数组中的每一个元素的数据类型为struct STU;
按维度分:
一维数组: int xxx[N];
二维数组: int xxx[N][M];
有N个一维数组,每一个数组存在M个元素,每一个元素数 据类型为int。
多维数组
1.3 数组的定义与初始化
1.3.1 一维数组
定义数组:数据类型 数组名[N]={初始化N个数值};
如果定义数组时,给定的完全初始化值,则N可以省略。
完全初始化:成员全部初始化
部分初始化:只给定部分数据,其他位置的数据采用编译器给定类型的默认值
指定位置初始化:指定的位置初始化,其他位置保持默认值(指定位置可以先后面的位置,再前面的位置,这是编译器允许的。但写程序时习惯先前再后)
1.3.2 二维数组
定义语法:
数组类型 数组名[行数][列数];
数组类型 数组名[行数][列数] = {初始化值};
逐个初始化(完全初始化)
一行一行的初始化,够一行的数据,则自动换行
逐个初始化(部分初始化)
一行一行的初始化,够一行的数据,则自动换行,剩余部分补0
部分初始化+指定位置初始化
指定的位置初始化,其他位置保持默认值
1.4 字符数组
1.4.1 一维字符数组
定义与初始化:
1)完全字符数组初始化
例如char teacher[] = {'t', 'e', 'a', 'c', 'h','e','r'};
2)字符串初始化
"字符串内容"编译器默认在字符串尾部自动添加'\0',当打印或输出时,自动'\0'位置结束。例 char teacher[32] = "teacher";//结果是"teacher\0"。
【注】1.sizeof计算变量分配的内存空间,即使没有用完,也是分配的大小。
2.strlen返回第一个\0之前所有字符个数。
3.使用字符串初始化字符数组时,自动追加'\0'
4.gets()获取键盘输入的字符串,包含空格(ASCII 32)【过时的函数】。
char *gets(char *);
5.fgets()从标准设备流中读取一行数据,包含换行符。
char *fgets(char *s,int size,FILE *stream);(s表示接收字符串的内存地址,size表示接收多少个字符或字节数,stream是流对象,可以给定stdin(标准输入设备))。
6.math.h编译时,需要添加 -lm(参数)
1.4.2 二维字符数组
一维字符数组即为一个字符串
二维字符数组,可以表示为多个字符串
如果字符数组存储汉字,默认文件字符编码是UTF-8,即一个汉字占3个字节
2. 函数
2.1 概念
函数是c语言的功能单位,实现一个功能可以封装一个函数来实现
定义函数的时候一切以功能为目的,根据功能去定函数的参数和返回值
函数的参数:形参和实参,形参是定义函数时的参数名称,实参在调用函数时参数的名称。一个函数在定义时,可以存在多个参数,也可以没有参数。每一个参数具有数据类型和参数名的。多个参数之间用逗号(,)分隔。
定义函数的语法格式:
返回值类型 函数名(数据类型 形参1,数据类型 形参2,...){
函数体的语句;
[return 表达式或变量;]
}
函数执行到return表示,函数结束了
如果函数不存在返回值时,返回值类型一般为void
定义好的函数,调用:
[函数返回值类型 变量名=[(强转的数据类型)]] 函数名(实参1,实参2);
【注意】原则上,函数只有先声明或先定义,再能调用它的。
2.2 函数的分类
1)按定义分类:
1. 系统调用(内核提供给用户的借口函数)
2. 库函数(C语言封装好的函数,如printf,fgets)
3. 自定义函数(用户依据功能定义的函数)
2)按函数的参数分类
1. 有参函数
2. 无参函数
3)按函数的返回值分类
1. 无返回值 void
在函数体中可以没有return语句,也可以有return语句
2. 有返回值
2.3 函数的声明
声明函数的作用:防止编译器不能正常访问或编译失败
声明函数场景:模块化(.h头,.c源文件分离)
2.4 函数的重载
函数名相同,参数列表不同,从而构成的函数的重载(相同的函数名存在多种形式)。
参数列表不同:
1)个数不同
2)个数相同时,类型不同
3)参数顺序不同
【注意】函数的重载中,与返回值的类型无关
【注意】c语言中不支持函数的重载,c++支持
2.5 函数的调用过程
当前程序启动后,则执行main函数(先入栈),当在main函数中调用其他函数时,需要先保存主函数的运行状态,再将调用的函数入栈,当调用的函数执行完成后,主函数再恢复状态,程序向下继续执行,直到主函数结束,才退出程序
【提示】如果程序一直运行,则按ctrl+c或ctrl+z停止或退出程序
2.6 函数使用的小结
定义函数时,函数的形参与返回值依据函数的功能决定的。
定义函数的好处:
1)一次定义,多次使用,提供代码复用,减少代码冗余
2)提高代码的开发效率、开发标准(模块化),提高代码的可扩展能力和可维护能力。
3. 变量的存储类别
3.1 内存空间的分区
物理内存:实实在在的内存空间(内存条)
虚拟内存:操作系统从物理内存映射出来的,在32位机,每一个进程(启动程序时系统会创建进程-主进程)自动分配内存空间4G(最大可分的)
4G虚拟内存地址的范围:0x00000000~0xffffffff
程序中的数据都存在虚拟内存指定的位置上,虚拟内存又分:栈区、堆区、全局区、常量区和代码区
1)堆区:在动态申请内存的时候,在堆里开辟内存
2)栈区:主要存放局部变量,只作用于局部代码块
3)(静态)全局区:
1. 未初始化的静态全局区
静态变量(定义变量的时候,前面加static修饰),或全局变量,没有初始化的,存在次区
2. 初始化的静态全局区
全局变量、静态变量、赋过初值的,存放在此区
4)常量区:常量
5)代码区:存储程序的指令(语句)
3.2 变量存储位置
3.2.1 全局变量
作用域:程序的所有地方,如果是跨程序文件时,提前通过extern关键字进行声明
定义位置:在函数的外部
生命周期:程序退出时,释放变量所占内存空间
3.2.2 全局静态变量
作用范围(域):只能在定义它的源文件(.c)中使用
定义方式:在函数之外,数据类型前加static关键字
生命周期:程序退出时,释放变量所占的空间
【注意】定义静态全局变量的时候,如果不赋初值,它的值默认为0
3.2.3 局部变量
作用范围(域):函数内部,函数未调用之前,局部变量不会占内存。函数内的局部变量包含形参变量和自定义函数
生命周期:函数被调用是 局部变量才会定义(分配内存),函数结束后释放变量的内存空间
3.2.4 局部静态变量
在函数内,使用static关键字定义局部的静态变量
作用范围(域):函数内部使用
生命周期:只会在函数第一次调用时,定义变量(创建内存空间),如果再次使用变量时,直接使用不会定义(创建内存空间)。
【注意】局部的静态变量只要定义,直到程序结束才会释放。
【使用场景】多线程、多进程、共享数据。
3.3 外部函数
定义普通函数即为外部函数,程序的任何部分都可以访问
【建议】如果存在头文件,可以在头文件声明
3.4 内部函数
只能在当前的源文件中使用,不能在外部源文件中使用
定义函数时,在返回值类型的前面加static
4. 预处理、动态库和静态库
4.1 四步编译
预处理:gcc -E xx.c -o xx.i
编 译: gcc -S xx.i -o xx.S
汇 编: gcc -C xx.s -o xx.o
链 接: gcc xx.o -o xx
4.2 预处理相关语法
4.2.1 include包含头文件
通过
#include
说明当前源文件中使用外部的功能函数或变量【注意】预处理只是对include等预处理操作进行处理并不会进行语法检查
两种格式:
1)#include <xxx.h>从系统存放头文件的目录中查找,并包含到当前源文件中
2)#include "当前工作目录下的头文件.h" 如果当前目录不存在,再从系统指定目录查找
4.2.2 define宏
define用于定义宏
宏:是在预编译的时候进行替换
1.无参宏的用法
结尾不需要;分号
#define 宏名 值
在预编译时,如果程序中使用了宏,则用后面的值全部替换
【注】宏的名称一般采用全大写字母
2.有参宏的用法
有参的宏,功能类似于函数,宏中参数是无数据类型的
#define 宏名(参数1,参数2) 表达式
3.带参宏和带参函数的区别:
1)带参宏被调用多少次就会展开多少次,执行代码的时候没有函数调用的过程,不需要压栈弹栈。所以带参宏,是浪费了空间,因为被展开多次,但是节省时间(快)。
2)带参函数,代码只有一份,存在代码段(区),调用的时候区代码段取指令,调用的时候要压栈弹栈。有个调用的过程。所以说,带参函数是浪费了时间,节省了空间
3)带参函数的形参是有类型的,带参宏的形参没有类型名
4.2.3 选择性预编译
1.#ifdef
格式:
#define 宏名 值
#ifdef 宏名
代码片段
#else
代码片段
#endif
2.ifndef
ifndef用于判断某一个宏是否定义或存在
语法结构:
#ifndef 宏名
代码段
#else
代码段2
#endif
3.#if
判断某一个表达式的值为1(真)或假的时候执行不同的代码段
#if 表达式
代码段1
#else
代码段2
#endif
定义标准的头文件的宏
为了避免重复包含
如:
#ifndef __文件名_H__
#define __文件名_H__
声明函数和变量
#endif
4.3 静态库与动态库
静态库和动态库:都是包含至少一个实现功能的函数的库文件。
静态库的文件扩展名:.a
动态库的文件扩展名:.so(Linux), .dll(Window)
【注】编译源文件时,默认生成动态库
4.3.1 静态库的生成
动态编译:默认编译方式,使用动态库
gcc xx.c -o xx
静态编译:使用静态库
gcc -static xxx.c -o xxx
区别:
1)静态编译会将静态库打包到可执行程序文件中。【可执行程序的内容较多】。好处是发布时方便,只需要一个可执行程序即可,不需要静态库文件。
2)动态编译,不会将动态库打包到可执行程序文件中,只是进行库链接。当执行程序时,动态链接执行目标函数。【可执行程序的内容较少】。好处时可执行程序的体积小,库可以扩展功能(升级)
生成静态库文件的命令:
1)将源文件生成汇编文件: gcc -c xx.c -o xx.o
2)将汇编文件生成静态库文件: ar rc libxx.a xx.o
【注意】静态库文件名以lib
开头,以.a
结尾
4.3.2 使用静态库文件
gcc -static x.c -o x -L/usr/lib -lxx -I/usr/include
相对位置:源文件与库在相同的位置
指定位置:指定库文件所在的位置,库名及头文件的位置
-L库文件所在的目录位置
-l不带lib和.a的库文件名
-I库函数的头文件的目录位置
4.3.3 动态库的生成
gcc -shared xx.c -o libxx.so
4.3.4 动态库的使用
类似于静态库的使用,需要将库文件的目录加入到LD_LIBRARY_PATH环境变量中或者将库文件及头文件添加到系统库目录(/usr/lib)和系统头文件目录(/usr/include)。
动态编译:
gcc x.c -o x -L/usr/lib -lxx -I/usr/include
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)