记录一下c语言的学习过程,笔记如有错误,欢迎指出!!!
文章目录
- 13 内存四区
- 14.宏函数和普通函数的调用流程
- 1.calloc 与relloc
- 15 sscanf的使用
- 16.链表
- 1.数组和链表的优缺点
- 2.带头节点单向链表基本操作
- 1.链表的初始化,遍历,插入,删除,清空,销毁
- 17.宏定义
- 18 动态库和静态库
- 1.静态链接库
- 2.动态链接库
13 内存四区
memcpy(dst, src, size):在数据类型位置的情况下,将以src为首地址的size个字节内容copy到以dst为首地址的内存中
内存4区模型:
代码段:.text段。 程序源代码(二进制形式)。
数据段:只读数据段 .rodata段。初始化数据段 .data段。 未初始化数据段 .bss 段。
stack:栈。 在其之上开辟 栈帧。 windows 1M --- 10M Linux: 8M --- 16M
heap:堆。 给用户自定义数据提供空间。 约 1.3G+
.rodata存放全局常量,不能通过指针修改此值,而局部常量存放在栈中,可以通过指针间接修改
通过malloc,calloc,realloc在栈区开辟的二级指针(二级指针的元素也为开辟的栈区地址)释放时,需要由内向外释放,不然释放不彻底
开辟释放 heap 空间:
void *malloc(size_t size); 申请 size 大小的空间
返回实际申请到的内存空间首地址。 【我们通常拿来当数组用】
void free(void *ptr); 释放申请的空间
参数: malloc返回的地址值。
使用 heap 空间:
空间时连续。 当成数组使用。
free后的空间,不会立即失效。 通常将free后的 地址置为NULL。
free 地址必须 是 malloc申请地址。否则出错。
如果malloc之后的地址一定会变化,那么使用临时变量tmp 保存。
14.宏函数和普通函数的调用流程
#define MYADD(x,y) ((x) + (y)
将一些频繁短小的函数 写成宏函数
宏函数优点:以空间换时间,比普通函数在一定程度上效率高,省去了普通函数入、出栈时间的开销
因为宏函数是替换,所以为了保证运算顺序,宏函数需要加括号修饰,
变量,函数入栈,出栈流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9ec3qxn-1721147978070)(https://i-blog.csdnimg.cn/direct/14633b4fc095424384700d410114a7ac.png#pic_center)]
为了解决上面问题,函数的调用方和被调用放对于函数如何调用必须有一个明确约定,这样的约定成为调用惯例
cdecl,C/C++默认调用惯例, C++也可使用stdcall(标准调用惯例)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOzYAD2D-1721147978072)(https://i-blog.csdnimg.cn/direct/e0bf44d9bc44428da0854716760e9f7d.png#pic_center)]
栈
C语言中,栈顶是低地址,栈底是高地址,高位数据放高地址,低位数据放低地址 –小端对齐
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mu3agD6c-1721147978072)(https://i-blog.csdnimg.cn/direct/8a171e238dc4443b923b5f4a70e193c3.png#pic_center)]
main函数中定义普通变量如 int a = 9; int b = 10 时,int类型占的字节不是4,因为还要存放上下地址,两个int的地址不一定连续
1.calloc 与relloc
//maloc, calloc, realloc 分配的内存连续(个人猜测)
//calloc 分配在堆区,与malloc不同的是 calloc会初始化数据为0
int * p = malloc(sizeof(int)* 10);
int * p = calloc(10, sizeof(int));
//realloc 重新分配内存
int * p = malloc(sizeof(int)* 10);
//如果重新分配的内存比原来大,那么不会初始化新空间为0
p = realloc(p, sizeof(int)* 20);
//如果重新分配的内存比原来小,那么释放后序空间,只有权限操作申请空间
p = realloc(p, sizeof(int)* 5);
15 sscanf的使用
%*s或%*d | 跳过数据 |
---|---|
%[width]s | 读指定宽度的数据 |
%[a-z] | 匹配a到z中任意字符(尽可能多的匹配) |
%[aBc] | 匹配a、B、c中一员,贪婪性 |
%[^a] | 匹配非a的任意字符,贪婪性 |
%[^a-z] | 表示读取除a-z以外的所有字符 |
1、%*s或%*d 跳过数据
char * str = "12345abcde";
char buf[1024] = { 0 };
sscanf(str, "%*d%s", buf); //匹配非数字
char * str = "abcde12345"; //忽略遇到空格或者 \t 代表忽略结束
char buf[1024] = { 0 };
sscanf(str, "%*[a-z]%s", buf);
//sscanf(str, "%*s%s", buf); //全都没匹配,若在e1之间加上空格或\t,可以匹配上
char * str = "12345abcde";
char buf[1024] = { 0 };
sscanf(str, "%6s", buf); //匹配前6个
char * str = "12345abcdeaaa";
char buf[1024] = { 0 };
sscanf(str,"%*d%[a-c]", buf); //只要匹配失败,那么就不继续匹配了, 结果 abc
char * str = "12345abcdeaaa";
char buf[1024] = { 0 };
sscanf(str, "%[0-9]", buf); //只要匹配失败,那么就不继续匹配了, 结果 12345
char * str = "abcCdef";
char buf[1024] = { 0 };
sscanf(str, "%[abC]", buf); //只要匹配失败,那么就不继续匹配了, 结果 ab
char * str = "abcCdef";
char buf[1024] = { 0 };
sscanf(str, "%[^C]", buf); //结果 abc
char * str = "abcCdef123456";
char buf[1024] = { 0 };
sscanf(str, "%[^0-9]", buf); //结果abcCdef
char * ip = "127.0.0.1";
int num1 = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
sscanf(ip, "%d.%d.%d.%d", &num1, &num2, &num3, &num4); //sscanf用的是地址,
printf("%d\n", num1);
printf("%d\n", num2);
printf("%d\n", num3);
printf("%d\n", num4);
char * str = "abcdef#zhangtao@123456";
char buf[1024] = { 0 };
sscanf(str, "%*[^#]#%[^@]", buf); //结果zhangtao
char * str = "helloworld@itcast.cn";
char buf1[1024] = { 0 };
char buf2[1024] = { 0 };
sscanf(str, "%[a-z]%*[@]%s", buf1, buf2); // 第一个%和第三个%做匹配,中间的%不做匹配
16.链表
1.数组和链表的优缺点
数组:
- 静态空间,一旦分配内存就不可以动态扩展,如果分配过多,会造成资源浪费
- 对于头部插入的效率较低
- 索引方便
- 内存空间连续
链表:由数据域和指针域构成,数据域维护数据,指针域记录下一个节点的位置
- 动态空间,便于分配资源,内存可以动态扩展
- 在任何位置插入效率较高
- 索引不方便
- 占用空间大于数组
- 内存空间不连续
链表的分类一
- 静态链表:在栈上分配内存
- 动态链表:在堆区分配内存
链表的分类二:
- 单向链表:每个节点只记录一个地址
- 双向链表:每个节点记录两个地址
- 单向循环链表:
- 双向循环链表:
不带头结点的链表:当进行头插时,第一个节点的地址会改变,链表的访问地址也会改变
带头结点的链表:头节点的地址是固定的,头节点只记录第一个节点的地址
2.带头节点单向链表基本操作
1.链表的初始化,遍历,插入,删除,清空,销毁
见文件夹 链表操作
17.宏定义
注意事项
- 宏名一般用大写,以便于与变量区别;
- 宏定义可以是常数、表达式等;
- 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错;
- 宏定义不是C语言,不在行末加分号;
- 宏名有效范围为从定义到本源文件结束;
- 可以用#undef命令终止宏定义的作用域;
- 在宏定义中,可以引用已定义的宏名;
在项目中,经常把一些短小而又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈、跳转、返回等的开销,可以调高程序的效率。
宏通过使用参数,可以创建外形和作用都与函数类似地类函数宏(function-like macro). 宏的参数也用圆括号括起来。
宏定义函数注意事项
- 宏的名字中不能有空格,但是在替换的字符串中可以有空格。ANSI C允许在参数列表中使用空格;
- 用括号括住每一个参数,并括住宏的整体定义。
- 用大写字母表示宏的函数名。
- 如果打算宏代替函数来加快程序运行速度。假如在程序中只使用一次宏对程序的运行时间没有太大提高。
例如
防止头文件被重复包含引用
\#ifndef _SOMEFILE_H
\#define _SOMEFILE_H
//需要声明的变量、函数
//宏定义
//结构体
\#endif
特殊宏定义 两边各由两个_
// FILE 宏所在文件的源文件名,
// LINE 宏所在行的行号
// DATE 代码编译的日期
// TIME 代码编译的时间
18 动态库和静态库
库可以简单看成一组目标文件的集合,将这些目标文件经过压缩打包之后形成的一个文件。像在Windows这样的平台上,最常用的c语言库是由集成按开发环境所附带的运行库,这些库一般由编译厂商提供。
1.静态链接库
优缺点:
- 静态库对函数库的链接是放在编译时期完成的,静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
- 程序在运行时与函数库再无瓜葛,移植方便。
- 浪费空间和资源,所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
2.动态链接库
要解决空间浪费和更新困难这两个问题,最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不是将他们静态的链接在一起。简单地讲,就是不对哪些组成程序的目标程序进行链接,等程序运行的时候才进行链接。也就是说,把整个链接过程推迟到了运行时再进行,这就是动态链接的基本思想。
导出函数和内部函数
动态链接库中定义有两种函数:导出函数(export function)和内部函数(internal function)。 导出函数可以被其它模块调用,内部函数在定义它们的DLL程序内部使用
__declspec(dllexport) 用来声名导出函数
24] = { 0 };
sscanf(str, “%[^C]”, buf); //结果 abc
char * str = "abcCdef123456";
char buf[1024] = { 0 };
sscanf(str, "%[^0-9]", buf); //结果abcCdef
char * ip = "127.0.0.1";
int num1 = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
sscanf(ip, "%d.%d.%d.%d", &num1, &num2, &num3, &num4); //sscanf用的是地址,
printf("%d\n", num1);
printf("%d\n", num2);
printf("%d\n", num3);
printf("%d\n", num4);
char * str = "abcdef#zhangtao@123456";
char buf[1024] = { 0 };
sscanf(str, "%*[^#]#%[^@]", buf); //结果zhangtao
char * str = "helloworld@itcast.cn";
char buf1[1024] = { 0 };
char buf2[1024] = { 0 };
sscanf(str, "%[a-z]%*[@]%s", buf1, buf2); // 第一个%和第三个%做匹配,中间的%不做匹配