1、函数调用流程
1.1 函数回顾
define定义宏函数
- 并不是一个真正的函数,只在预处理进行简单的文本替换;
- 一定的条件下,效率会比函数高。
#include<stdio.h>
#define MyAdd(x, y) (x+y)
int main()
{
int a = 1, b = 2;
printf("%d", MyAdd(a, b));
return 0;
}
>>> result:3
真正函数
- 返回值类型
- 参数类型
- 函数体
1.2 函数调用流程
- 栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,
没有栈就没有函数
,没有局部变量,也就没有我们如今能见到的所有计算机的语言。在解释为仕么栈如此重要之前,我们先了解一下传统的栈的定义。- 在经典的计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(
入栈
,push),也可以将压入栈中的数据弹出(出栈
,pop),但是栈容器必须遵循一条规则∶先进后出
。- 在经典的操作系统中,栈总是
向下增长
的。压栈的操作使得栈顶的地址减小
,弹出操作使得栈顶地址增大
。栈的内存是固定的,所以入栈内存会被占用,变小,出栈变大。
该过程包含的信息:
- 函数的返回
地址
【该函数的下一个语句,先入栈(排在该函数的参数后面) 】;- 函数的
参数
;临时变量
;- 保存上下文,包括在函数调用前后需要保持不变的
寄存器
。
1.3 调用惯例
- 现在,我们大致了解了函数调用的过程,这期间有一个现象,那就是函数的调用者和被调用者对函数调用有着一致的理解,例如,它们
双方都一致
的认为函数的参数是按照某个固定的方式
压入栈中。如果不这样的话,函数将无法正确运行。- 如果函数调用方在传递参数的时候先压入a参数,再压入b参数,而被调用函数则认为先压入的是b,后压入的是a,那么被调用函数在使用a,b值时候,就会颠倒。
- 因此,函数的调用方和被调用方对于函数是如何调用的必须有一个明确的约定,只有双方都遵循同样的约定,函数才能够被正确的调用,这样的约定被称为”调用惯例(CallingConvention)”.一个调用惯例一般包含以下几个方面:
- 函数参数的传递顺序和方式:函数的传递有很多种方式,最常见的是通过
栈传递
。函数的调用方将参数压入栈中,函数自己再从栈中将参数取出。对于有多个参数的函数,调用惯例要规定函数调用方将参数压栈的顺序:从左向右,还是从右向左。有些调用惯例还允许使用寄存器传递参数,以提高性能。- 栈的维护方式:
- 在函数将参数压入栈中之后函数体会被调用,此后需要将被压入栈中的参数全部弹出,以使得栈在函数调用前后保持致。这个弹出的工作可以由函数的调用方来完成,也可以由函数本身来完成。
- 为了在链接的时候对调用惯例进行区分,调用惯例要对函数本身的名字进行修饰。不同的调用惯例有不同的名字修饰策略。
- 事实上,在c语言里,存在着多个调用惯例,而默认的是
cdecl
。任何一个没有显示指定调用惯例的函数都是默认是cdecl
惯例。比如我们上面对于func函数的声明,它的完整写法应该是:
int _cdecl func(int a,int b);
注意:_cdecl不是标准的关键字,在不同的编译器里可能有不同的写法,例如gcc里就不存在_cdecl这样的关键字,而是使用_attribute((cdecl))。
调用惯例 | 出栈方 | 参数传递 | 名字修饰 |
---|---|---|---|
cdecl | 函数调用方 | 从右至左参数入栈 | 下划线+函数名 |
stdcall | 函数本身 | 从右至左参数入栈 | 下划线+函数名+@+参数字节数 |
fastcall | 函数本身 | 前俩个参数由寄存器传递,其余参数通过堆栈传递 | @+函数名+@+参数的字节数 |
psstcall | 函数本身 | 从左到右参数入栈 | 较为复杂 |
1.4 函数变量传递分析
1.5 栈的生长方向和内存存放方向
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
// 4字节 高地址->低地址
int a = 0xaabbccdd;
// 1字节,使用无符号字符型访问一个整形地址
unsigned char *p = &a;
printf("第一位%x\n", *p);
printf("第二位%x\n", *(p+1));
printf("第三位%x\n", *(p+2));
printf("第四位%x\n", *(p+3));
return 0;
}
运行结果
由以上运行结果可知,dd变为第一位。
高位放在高地址
。
2、指针
2.1 指针变量
是一种
数据类型
,占用内存空间【4字节】,保存地址
。
void test02()
{
int *p = 1;
printf("%p\n", p); // 0000000000000001 十六进制值
printf("%d\n", p); // 1 十进制值
printf("%d\n", &p); // 6421992 十进制地址
printf("%p\n", &p); // 000000000061FDE8 十六进制地址
}
int main()
{
test02();
return 0;
}
2.2 空指针
- 定义的时候为
0
或NULL
。不指向任何东西。- 不允许向
NULL
和非法地址
拷贝内存。
2.3 野指针
- 使用指针时,要避免野指针的出现。
- 野指针指向一个已删除的对象或未申请访问受限内存区域的指针。野指针无法通过NULL来判断避免。
什么情况下会导致野指针
指针变量未初始化
- 任何指针创建时,他的值是随机的。所以,在创建时应一起
初始化
。指针释放后未置空
- 指针释放后应赋值未
NULL
,否则有可能产生野指针
。作用域
- 不要返回指向
栈内存的指针
或引用,函数结束会被释放
。
#include<stdio.h>
int main()
{
int a = 10;
int *p = NULL;
p = &a;
printf("%d\n", *p);
return 0;
}
2.3 指针步长
- 主要看数据类型:
指针变量 + 1
跳【数据类型】字节;- 决定解引用的时候从
给定地址
开始取类型大小的字节数
;- 跳步长的时候可以添加
强制转换
,根据数据类型跳步长。
2.4 间接赋值
三大条件
- 2个变量;
- 建立关系;
- 通过
*
操作指针指向的内存。
间接赋值
修改
变量
的值
#include<stdio.h>
void changeValue(int *p)
{
*p = 10;
}
int main()
{
int a = 12;
changeValue(&a);
printf("%d\n", a);
return 0;
}
一般修改
一级指针
的值,改变了p的指向
void changePointer(int **p)
{
*p = 0x002;
}
int main()
{
int *p = NULL;
changePointer(&p);
printf("%p", p);
return 0;
}
- 用
n级
指针形参
,取间接修改n-1级
指针(实参
)的值。- 数组名做函数参数会退化未指向数组
首元素
的指针。
2.5 指针作为形参
输入特性
主调函数分配内存,被调函数使用内存。
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// 被调函数使用内存
void test01(char *arr)
{
printf("%s\n", arr);
}
int main()
{
// 主调函数分配内存
char *p = malloc(sizeof(char)*20);
memset(p, 0, 20);
strcpy(p, "Hello, world");
test01(p);
return 0;
}
以及指针内部嵌套数组,使用二级指针为形参。
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void test01(char **arr)
{
int i;
for(i=0; i<3; i++)
{
printf("%s\n", arr[i]);
}
}
int main()
{
char *p[] = {"ass", "wqe", "uio"}; // 内部依旧是一个char *类型的数组
test01(p);
return 0;
}
输出特性
被调函数分配内存,主调函数使用内存
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void test01(char **arr)
{
char *a = malloc(sizeof(char)*20);
memset(a, 0, 20);
strcpy(a, "Hello, world");
*arr = a;
}
int main()
{
char *p;
test01(&p);
printf("%s\n", p);
return 0;
}
2.6 二级指针
简单来说,指针存的就是
地址
。
- 若该二级指针为p,则*p就是一级指针。
3、分区模型
3.1 堆分配内存
calloc
#include<stdlib.h>
void *calloc(size_t nmemb, size_t size)
功能:
在内存动态存储区中分配nmemb块长度为size字节的连续区域。calloc自动将分配的内存置0。
参数:
· nmemb:所需内存单元数量;
· size:每个内存单元的大小
返回值:
成功:返回分配空间的起始地址
失败:NULL
举例
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int *arr = calloc(5, sizeof(int));
int i;
for(i=0; i<5; i++)
{
arr[i] = i;
}
for(i=0; i<5; i++)
{
printf("%d\n", arr[i]);
}
free(arr);
return 0;
}
realloc
#include<stdlib.h>
void *realloc(void *ptr, size_t size)
功能:【扩展内存】
重新分配用malloc或者calloc函数在堆中分配空间的大小。realloc不会自动清理增加的内存,需要手动
清理,如果指定的地址后面有连续的空间,那么久会在已有的地址基础上增加内存,如果指定的地址后面没有空间,
那么realloc会重新分配新的连续内存,把旧的内存的值拷贝到新内存,同时释放就旧内存。
参数:
· ptr:为之前malloc或者calloc分配的内存地址,如果此参数等于NULL,那么和realloc与malloc
功能一致。
· size:为重新分配内存大小。
返回值:
· 成功:返回新分配的堆内存地址
· 失败:NULL
4、格式化输出
4.1 格式化输出
sscanf(void *str, format, void *str_);
功能:从str中符合format的数据,赋值给str_。
- 忽略字符串空格或者\t
格式 | 作用 |
---|---|
%*s 或%*d | 跳过数据 |
%[width]s | 读指定宽度的数据 |
%[a-z] | 匹配a到z中任意字符(尽可能多的匹配) |
%[aBc] | 匹配a、B、c中一员, 贪婪性 |
%[^a] | 匹配非a的任意字符、贪婪性 |
%[^a-z] | 表示读取除a-z以外的所有字符 |
案例
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void test()
{
char *arr = "123abc";
char *arr1 = "abc123";
char buf[1024] = {0};
// 以下匹配是从头开始匹配,若前面匹配不到则退出
/*
sscanf(arr, "%s", buf);
printf("%s\n", buf); // result:123abc
*/
/*
sscanf(arr, "%*d%s", buf);
printf("%s\n", buf); // result:abc
*/
/* 字符串与数字分隔为空格或\t才能分开
sscanf(arr1, "%*s%d", buf);
printf("%s\n", buf); // result:无
*/
/*
sscanf(arr1, "%[a-z]", buf);
printf("%s\n", buf); // result:abc
*/
/*
sscanf(arr1, "%[ac]", buf);
printf("%s\n", buf); // result:a
*/
/*
sscanf(arr1, "%[^c]", buf);
printf("%s\n", buf); // result:ab
*/
}
5、const
- 一般在传参过程中,不传值,
传地址
;- 为了避免值
不被修改
的情况下,参数用const
修饰。
6、位逻辑运算符
按位取反:~
~(10011010)
=>01100101
注意:运算符不会改变本身的值。
位与:&
011
&101
=>001
或与:|
010
|101
=>111
左移运算符:<<
001<<1
=>010
左移几位
相当于乘2的几次方
。
右移运算符:>>
100>>1
=>010
右移几位
相当于除2的几次方
。
7、数组
7.1 指针与数组是否等价?
- 两者
不等价
;- 当数组名在表达式使用时,编译器才会产生一个指针常量;
- 数组除以下俩种情况下,数组名都是指向首元素的指针;
- 数组是常量指针;
- 声明数组的同时分配了内存,而声明指针值分配了容纳指针本身的空间。
什么情况下不能为指针常量?
- 当数组在
sizeof
中使用的时候。返回数组的长度
;- 当数组与
&
一起使用时,返回的是一个指向数组的指针
,不是指向某个数组元素的指针常量。数组名是数组类型
。
//当数组与&结合时,数组名是`数组类型`
#include<stdio.h>
int main()
{
int arr[] = {1,2,3};
printf("&arr的地址:%d", &arr);
printf("&arr + 1的地址:%d", &arr + 1);
// result:&arr的地址:6422036
// &arr + 1的地址:6422048
// 若为指针着&arr+1应为6422040。
return 0;
}
7.2 多维数组
int arr[][]
若赋值第一个[]阔以省略不赋值,会根据后面一个维度进行分配。
7.3 指针数据排序案例
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void sortArray(int **arr, int len)
{
int i, j, min;
for(i=0; i<len; i++)
{
min = i; // 保存一个最小值
for(j=i+1; j<len; j++)
{
if(arr[j] < arr[min]) // 判断该值往后的所有数值是否比他小
{
min = j;
}
}
if(min != i) // 若有小的值,则交换
{
int * temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
}
int main()
{
int *arr[] = {2,1,3,5,4};
int len = sizeof(arr)/ sizeof(arr[0]), i; // 获取数组长度
sortArray(arr, len);
for(i=0; i<len; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
8、结构体
定义结构体时,不能赋值。因为只是定义还
未分配
内存空间。需定义变量
后,才能对其赋值。
8.1 深拷贝和浅拷贝
若结构体内部有指针,并且指针指向堆空间。那么如果发生赋值行为,会产生
同一块空间被释放俩次
以及内存泄漏
。
- 若要实现,则需要手动赋值【先将其空间释放在创建后赋值】。
8.2 结构体嵌套多级指针
#include<stdio.h>
#include<stdlib.h>
#define LEN 4
/*
在一个班级下有多个学生
*/
typedef struct
{
int *classroom;
char **students;
}roomInfo;
void addInfo(roomInfo ***temp)
{
int i, j;
// 首先为整个结构体分配空间
roomInfo **Info = (roomInfo **)malloc(sizeof(roomInfo *) * LEN);
for(i=0; i< LEN; i++)
{
// 为每一个结构体分配空间
Info[i] = (roomInfo *)malloc(sizeof(roomInfo));
// 为每一个结构体的classroom分配空间
Info[i]->classroom = (int *)malloc(sizeof(int));
Info[i]->classroom = i+1;
// 为每一个结构体的students分配空间
Info[i]->students = (char **)malloc(sizeof(char *) * LEN);
for(j=0; j<LEN; j++)
{
// 为每一个结构体的students下的每一个student分配空间
Info[i]->students[j] = (char *)malloc(sizeof(char)*64);
sprintf(Info[i]->students[j], "第%d班的学生00%d", Info[i]->classroom, j+1);
//printf("%s\n" ,Info[i]->students[j]);
}
//printf("\n");
}
temp = &Info;
}
int main()
{
roomInfo **p = NULL;
int i,j;
addInfo(&p);
for(i=0; i<LEN; i++)
{
for(j=0; j<LEN; j++)
{
printf("%s\n", p[i]->students[j]);
}
}
return 0;
}
8.3 结构体偏移量
offset(x, y)
返回x和y之间的
地址偏移量
。
#include<stdio.h>
#include<stddef.h>
typedef struct
{
int a;
double b;
}inner;
typedef struct
{
char c;
int d;
inner f;
}outer;
int main()
{
outer out = {'a', 1, 2, 5.20};
int offset_inner = offsetof(outer, f);
int offset_b = offsetof(inner, b);
// 取b的地址
/*
(char *)&out:out的首地址;
offset_inner:out到f之间的地址;
offset_b:inner到b之间的地址;
*/
printf("%d, %d\n", ((char *)&out+offset_inner+offset_b), &(out.f.b));
// 取值
/*
(char *)&out:out的首地址;
offset_inner:out到f之间的地址;
offset_b:inner到b之间的地址;
(double *):上述结构体中的b为double类型,即转化为该类型,在用*进行取值。
*/
printf("%f, %f\n", *(double *)((char *)&out+offset_inner+offset_b), out.f.b);
/*
(inner *):将out首地址+out到f的地址转化为inner结构体指针类型
*/
printf("%f", ((inner *)((char*)&out + offset_inner))->b);
return 0;
}
8.4 结构体内存对齐
内存
的最小单元是一个字节,当cpu从内存读取数据的时候,是一个一个字节读取。但实际上cpu将内存分成多块
,每次从内存中读取一块
【可能为2,4,6,8…】。- 内存对齐是操作系统为了
提高访问内存
的策略。操作系统在访问内存的时候,每次读取一定长度
(此长度为默认对齐数,或是对齐数的整数倍)。若没对齐,为了访问一个变量可能产生二次访问
。
为什么要内存对齐
提高存取数据的速度
。一般地址都为偶数
,防止一个偶数的数据类型存储在奇数地址块中,需要读取2次才能读取该变量。- 某些平台只能在
特定的地址
访问特定类型的数据,否则抛出硬件异常给操作系统。
如何内存对齐
- 对于标准数据类型,他的地址只要是他的长度的整数倍。
- 对于非标准数据类型,如结构体,需要遵循一下对齐规则。
- 1.数组成员对齐规则。第一个数组成员应该放在offset为0的地方,以后每个数
组成员应该放在offset为min (当前成员的大小,#pargama pack(n))
整数倍的地方开始(比如 int在32位机器为4字节,#pargama pack(2)
,那么从2的倍数地方开始存储)。- 2.结构体总的大小,也就是 sizeof 的结果,必须是
min(结构体内部最大成员,#pargama pack(n))
的整数倍,不足要补齐。- 3.结构体做为成员的
对齐规则
。如果一个结构体B里嵌套另一个结构体A,还是以最大成员
类型的大小对齐,但是结构体A的起点为A内部最大成员的整数倍的地方。【structB里存有struct A ,A里有char ,int ,double等成员,那A应该从8的整数倍开始存储】,结构体A中的成员的对齐规则仍满足原则1、原则2。
手动设置对齐参数
#pragma pack(show)
- 显示当前
packing alignment
的字节数,以warming message
的形式被显示。#pargma pack(push)
- 将当前指定的
packing alignment
数组进行压栈操作,这里的栈式the internal compiler stack
,同时设置当前packing alignment
为n;如果n没有指定,则将当前的packing alignment
数组压线。#pragma pack(pop)
- 从
internal compiler stack
中删除最顶端的record
;如果没有指定n,则当前栈顶record即为新的packing alignment
数值;如果指定了n,则n成为新的packing alignment
值。#pragma pack(n)
- 指定
packing
的数值,以字节为单位,缺省数值是8,合法的数值分别为1,2,4,8,16。
9、文件操作
9.1 流的概念
- 流是一个动态的概念,可以将一个字节形象地比喻成一滴水,
字节在设备、文件和程序之间的传输
就是流,类似于水在管道中的传输,可以看出,流是对输入输出源的一种抽象,也是对传输信息的一种抽象。- C语言中,I/O操作可以简单地看作是从
程序移进或移出字节
,这种搬运的过程便称为o流(stream)。程序只需要关心是否正确地输出了字节数据,以及是否正确地输入了要读取字节数据,特定I/O设备的细节对程序员是隐藏的。
文件流
- 1.程序为同时处于活动状态的每个文件声明一个指针变量,其类型为FTIF*,这个指针指向这个
FILE
结构,当它处于活动状态时由流使用。- 2.流通过
fopen
函数打开。为了打开一个流,我们必须指定需要访问的文件或设备以及他们的访问方式(读、写、或者读写)。Fopen
和操作系统验证文件或者设备是否存在并初始化FILE。- 3.根据需要对文件进行读写操作。
- 4.最后调用
fclose
函数关闭流。关闭一个流可以防止与它相关的文件被再次访问,保证任何存储于缓冲区
中的数据被正确写入到文件中并且释放FILE
结构。
文本流
文本流
,也就是我们常说的以文本模式读取文件
。文本流的有些特性在不同的系统中可能不同。其中之一就是文本行的最大长度
。标准规定至少允许254个字符。另一个可能不同的特性是文本行的结束方式。例如在 Windows系统中,文本文件约定以一个回车符和一个换行符结尾。但是在Linux下只使用一个换行符结尾。
二进制流
二进制流中的字节将完全根据
程序编写它们的形式
写入到文件中,而且完全根据它们从文件或设备读取的形式
读入到程序中。它们并未做任何改变
。这种类型的流适用于非文本数据,但是如果你不希望I/O函数修改文本文件的行末字符,也可以把它们用于文本文件。
9.2 文件读写注意点
使用feof
读取注意点
// 遍历读取时,在末尾还会读出一个空格,【该空格为EOF】
while(!feof(f))
// 改善
while(str = fgetc(f) != EOF)
9.3 配置文件读写程序
主要文件
本程序读取配置文件中的键值对,
#
为开的是注释,:
为键值对。
main.c
#include<stdio.h>
#include<stdlib.h>
#include "ConfigFile.h"
/*
可根据输入的键去查询所对应的值。
*/
int main()
{
char **data = NULL;
ConfigInfo *Info = NULL;
int lines = 0;
loadFile_ConfigFile("config.ini", &data, &lines);
parseFile_ConfigFile(data, lines, &Info);
char *text = getInfo_ConfigFile("port", Info, lines);
printf("%s\n", text);
return 0;
}
ConfigFile.c
/*
@Author : Jaibuti
@Software: CodeBlocks
*/
#include "ConfigFile.h"
// 获取文件有效行数
int getLine_ConfigFile(FILE *file)
{
// 读取每一行计算出有效行数
char buf[1024] = {0};
int lines=0;
while(fgets(buf, 1024, file) != NULL)
{
if (!isValid_ConfigFile(buf))
{
continue;
}
memset(buf, 0, 1024);
lines++;
}
// 将文件指针重置到文件开头
fseek(file, 0, SEEK_SET);
return lines;
// 重置文件指针到文件开头
}
// 加载配置文件
void loadFile_ConfigFile(const char *filePath, char ***fileData, int *line)
{
FILE *file = fopen(filePath, "r"); // 打开文件
if (file == NULL)
return;
int i=0;
int lines = getLine_ConfigFile(file); // 读取文件行数
char **temp = (char **)malloc(sizeof(char *) * lines); // 创建空间保存每一行数据
char buf[1024] = {0}; // 暂存一行数据
while(fgets(buf, 1024, file) != NULL)
{
if(!isValid_ConfigFile(buf))
continue;
temp[i] = (char *)malloc(strlen(buf)+1);
strcpy(temp[i], buf);
memset(buf, 0, 1024);
i++;
}
//fclose(file);
// 读取所有的数据并返回
*fileData = temp;
*line = lines;
}
//解析配置文件
void parseFile_ConfigFile(char **fileData, int lines, ConfigInfo **Info)
{
int i;
ConfigInfo *tempInfo = (ConfigInfo *)malloc(sizeof(ConfigInfo *)*lines);
memset(tempInfo, 0, sizeof(ConfigInfo *)*lines);
for(i=0; i<lines; i++)
{
char *index = strchr(fileData[i], ':');
printf("********");
strncpy(tempInfo[i].key, fileData[i], index-fileData[i]);
strncpy(tempInfo[i].value, index+1, strlen(index+1)-1);
printf("%s, %s\n", tempInfo[i].key, tempInfo[i].value);
}
// 释放文件信息
for(i=0; i<lines; i++)
{
if (fileData[i] != NULL){
free(fileData[i]);
fileData[i] = NULL;
}
}
*Info = tempInfo;
}
// 获取指定配置信息
char *getInfo_ConfigFile(const char *key, ConfigInfo *Info, int lines)
{
int i=0;
for(i=0; i<lines; i++)
{
if (strcmp(key, Info[i].key) == 0){
return Info[i].value;
}
}
return NULL;
}
// 释放配置文件信息
void destroInfo_ConfigFile(ConfigInfo *Info)
{
if (NULL == Info)
{
return;
}
free(Info);
Info = NULL;
}
// 判断当前行是否有效
int isValid_ConfigFile(const char *buf)
{
if (buf[0] == '\n' || buf[0] == '#' || strchr(buf, ':') == NULL)
{
return 0;
}
return 1;
}
ConfigFile.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct configinfo
{
char key[145];
char value[150];
}ConfigInfo;
#ifdef __cplusplus
extern "C"{
#endif // __cplusplus
// 获取文件有效行数
int getLine_ConfigFile(FILE *file);
// 加载配置文件
void loadFile_ConfigFile(const char *filePath, char ***fileData, int *lines);
//解析配置文件
void parseFile_ConfigFile(char **fileData, int lines, ConfigInfo **Info);
// 获取指定配置信息
char *getInfo_ConfigFile(const char *key, ConfigInfo *Info, int lines);
// 释放配置文件信息
void destroInfo_ConfigFile(ConfigInfo *Info);
// 判断当前行是否有效
int isValid_ConfigFile(const char *buf);
#ifdef __cplusplus
}
#endif // __cplusplus
config.ini
# ip地址
ip:127.0.0.1
# 这是端口
port:8080
# 这是用户
username:root
# 这是密码
password:admin
woshi:nide