Day1 【hard】
相关知识点
指针
引入
int a;//定义了一个变量a,类型为int,实质上分配了一个4字节的内存单元
a = 100;//把数值100存放到变量a对应的存储单元
int b = a;//取出a的值,然后存放到变量b对应的存储单元
--> 在C语言中,任意一个变量名,都有两层含义:
(1)代表该变量的存储单元 左值
(2)代表是该变量的值 右值
而且,我们对变量的访问只有两种情况:
(1)把一个值存储到变量对应的存储空间 (write)
(2)从变量的存储空间取值 (read)
我们知道系统已经把变量名与变量的地址关联起来,系统实质上是通过变量名来找到地址。于是,能不能直接找到地址
进行访问 -->指针
指针
地址:系统把内存以一个字节为单位划分成许多份,每一份进行编号,这个编号就是内存单元的地址
【注意】:
1、在C语言中,指针跟地址的概念差不多,你可以把指针认为是(有类型的)地址【决定了你后面指针运算时,偏移量的大小】
2、一个变量的首地址,称之为该变量的"指针",它标志着该变量的内容从哪里开始。指针的类型决定了"大小"
指针变量【本质就是变量,内部存储地址】
定义格式:指向空间的类型 * 变量名 【例如:int *a】
【考点】:
1、在不同的处理器中,指针的大小是不一样的:
(一) 在32位处理器当中,指针大小4个字节
(二) 在64位处理器当中,指针大小8个字节
2、指针变量的类型决定了:指针变量于整数之间运算的规律:
指针 +/- 常量: 实际上是指针变量内的地址偏移了 n 个指向类型的大小
3、指针变量与指针变量之间只存在 减法运算
如何获得地址?
【取址运算符】&
(1)通过取地址符 &
&对象名 :表示取该对象的地址
对象:变量、数组元素......
(2)有些对象的名字本身就表示
如:数组名、函数名.......
注意:这两种方式获取到的地址都是都类型的
如何访问指针指向的空间?
【指向运算符】*
*(地址)-->地址对应的那个变量
即*(地址)可以作为左值也可以作为右值,还可以取地址
*(&a)-->a
*&可以直接约掉 逆运算
注意: *要与乘运算符和定义时的*的区别
int *p,a=3;
p=&a;
printf("%d\n",(*p)*2);
数组和指针的关系
1、一维数组( 以int a[3]为例 )
表达式 | 类型 | 含义 | 值 |
---|---|---|---|
a | int[3] | 整个一维数组 | 12 |
*a | int | 一维数组的首地址元素 | a[0] & 4 |
a+i | int* | 以a为基准点偏移 i 个位置的指针 | 4 / 8 |
*(a+i) | int | 以a为基准点偏移 i 个位置对于的元素 | a[i] & 4 |
2、二维数组( 以char a[3][4]为例 )
表达式 | 类型 | 含义 | 值 |
---|---|---|---|
a | char[3][4] | 整个二维数组 | 12 |
a+i | char [4]* | 以a为基准点偏移 i 个char[4]空间的地址 | 4 / 8 |
*(a+i) | char[4] | 第 i 行字符串的首地址 | &a[i][0] & 4 |
*(a+i)+j | char* | 第a[ i ][ j ]元素的地址 | 4 / 8 |
*(*(a+i)+j) | char | 元素a[ i ] [ j ] | 1 |
【注意】:一维数组 a[] 和二维数组 a[][] 的 a 与 a+i 两者的区别
练习作业
1、练习
练习1
eg:
int a;
char b;
int *p1 = &a;
char *p2 = &b;
printf("p1:%p\n",p1);
printf("p1+1:%p\n",p1+1);
printf("p1+2:%p\n",p1+2);
printf("p2:%p\n",p2);
printf("p2+1:%p\n",p2+1);
printf("p2+2:%p\n",p2+2);
答案:
p1:0x7ffedc760234
p1+1:0x7ffedc760238
p1+2:0x7ffedc76023c
p2:0x7ffedc760233
p2+1:0x7ffedc760234
p2+2:0x7ffedc760235
练习2
eg:
char a[3]={'a','b','c'};
char b[2][3]={{'1','2','3'},{'4','5','\0'}};
printf("a:%p\n",b);
// b的地址
printf("1:%p\n",&b+1);
// b的地址 + char[2][3]的字节大小
printf("2:%p\n",b+1);//&b[0]+1
// b的地址 + char[3]的字节大小
printf("%ld %ld %c\n",sizeof(a),sizeof(a+1),*(a+1));
//3 4/8 b
printf("%ld %ld %ld %s\n",sizeof(b),sizeof(b+1),sizeof(*(b+1)),*(b+1));
//6 4/8 3 45
思考拓展
(一) KMP算法
Day2 + Day3 + Day4【hard】
相关知识点
调试方法(Debug)
(一) printf()
1、printf("任意内容")【相当于断点】
2、printf(”%s %d“,__FUNCTION__,__LINE__)【打印当前运行到的函数和列数】
(二) gdb 调试工具【个人用不习惯】
(三) 相关软件的编译器自带的Debug【个人推荐vscode】
段错误问题总结
1、访问NULL指针
【补充】:NULL指针 ---> #define NULL (void)*0
(一) 在逻辑判断中NULL表示”假“
(二) NULL指针是不可访问的
2、内存泄漏
(一) 无限递归
(二) 野指针问题【指向一个不确定的空间(大多情况为非法空间)】
a.指针没有初始化
b.指针被释放空间后再次访问
例如:char *p = (char*)malloc(100);
strcpy(p,"hello"); // 合法
free(p);
strcpy(p,"world"); // 非法
指针集合【前方高能!!!】
1、数组指针 & 指针数组
数组指针【本质是一个指针,指向一个数组】
例如:
int (*p)[10]
个人理解:
上述表达式本质为: int[10] (*p)
可与: int *p 进行类比
相当于定义了一个int[10]类型的指针,这个指针指向一个int[10]的首地址
类似写法:
int p[][10]
指针数组【本质是一个数组,存放了连续的指针】
例如:
int *p[10]
个人理解:
上述表达式本质为: (int*)[10] p
可与: int[10] p 进行类比
相当于定义了一个有10个int*空间大小的数组
【补充】:数组指针和二级指针的关系:
1、首先二级指针可以保存 —> 指针变量的地址
2、如果一个数组中的元素是一级指针,则数组名就是二级指针;同理一个数组中的元素是二级指针, 则数组名就是三级指针
2、函数指针 & 指针函数
函数指针【本质是一个指针,指向一个函数】
例如:
int (*p)(int a, int b)
指针函数【本质是一个函数,返回值是一个指针】
例如:
int *p(int a, int b)
3、常量指针 & 指针常量 & 常量指针常量
常量指针【本质是一个指针,指向一个常量】
例如:
int const *p=&a;
个人理解:
const 修饰的是 *p【常量】
所以不能修改 *p 的值
指针常量【本质是一个常量,这个常量是一个指针】
例如:
int *const p=&a;
个人理解:
const 修饰的是 p【指针】
所以不能修改 p 的指针值
常量指针常量【是上述两者的结合】
例如:
int const *const p=&a;
综上所述:定义之后既不可以改变p指针和指针指向的值*p
【补充】:函数指针数组
例题:解释 void(*a[3])(int,int);
1、定义了一个函数指针数组a
2、这个数组有3个元素
3、每个元素都是一个函数指针
4、都指向了一个返回值是void,参数‘是两个int型的函数
动态内存分配
1、malloc【常用】
定义:
1、用来在进程的动态内存区域(堆)分配一块内存,并把该内存的首地址作为函数的返回值返回
2、是随进程持续性。一旦分配,如果不手动释放(free),那么就会一直存在,直到进程退出
2、free【常用】
定义:用来释放malloc/calloc申请的动态内存区域
3、calloc(可用malloc函数来替换)
定义:用来分配一段内存用来存数组
举例:若int *p;
calloc(1,sizeof(int)) == malloc(sizeof(int))
4、realloc
定义:重新分配之前申请的内存
注意:若int *p=NULL;
1、realloc(p,sizeof(int)) == p=malloc(sizeof(int))
2、realloc(p,0) == free(p)
区分C语言中的*(p++),p++,(p)++,(++p),++p
1、*(p++):先把变量p的值作为表达式p++的结果,然后将*p作为*(p++)的结果,最后将p进行加1
2、*p++:由于++(后缀)比*优先级高,所以*p++等效于*(p++)
3、(*p)++:先执行*p,把*p的值作为表达式的值,最后把*p+1
4、*(++p):首先将p进行加1操作,将p的值作为表达式++p的结果,最后取*
5、*++p:++(前缀)和*运算符优先级一致,结合性从右往左 所以*++p等效于*(++p)
练习作业
1、练习
练习1
利用自己的函数,完成string.h的常用函数
1、strlen
2、strcmp
3、strcpy
4、strcat
练习2
寻找字符串中的子串
思考拓展
(一) 数字字符串转成整型数字: atoi()
(二) 试卷错题
1、野指针问题
题目当中可能有隐性的野指针问题:
1、初始化定义时没有赋值【NULL或者是变量地址】
2、空间释放之后继续赋值【free()】
2、指针与整数的运算【尤其是二维指针】
例题:在int a[2][3]中:
a+1: 原式可以转换为:&a[0]+1 由于a[0]的类型为int[3],所以加一就是位移 sizeof(int[3])*1
a[1]+1: 原式可以转换为:&a[1][0]+1 由于a[1][0]的类型为int,所以 sizeof(int)*1
Day5
相关知识点
(1) 结构体
用计算机去解决现实世界问题时,需要把现实世界中的物体抽象成计算机中的一个"模型"(此部分和面向对象语言中的“对象”有些相似)
由于我们之前所学的数组只能存储相同类型的数据 -----> 所以引出结构体的概念
(一) 定义
struct 结构体名
{
数据类型1 变量名1;
数据类型2 变量名2;
···
};
【注意】:
1、结构体类型定义不会占用空间【可以理解成 int 数据类型不会占据内存,除了在定义变量之后】
2、成员名之间不可重复
-----------------------------------------------------------------------------------------------------------
(二) 内存布局【重点】
1、【结构体所占空间会大于等于成员变量所占空间之和 (回填充空白)】
2、结构体各成员变量按照它们定义的时候出现的顺序,依次保存
3、结构体字节对齐的规则
(一)自身对齐值:最大成员的字节数
(二)指定对齐值:
a.32位的CPU默认的指定对齐值就是4字节,同理64位CPU,默认就是8字节
b.也可以自己指定:
#pragma pack(n) //n只能是2的幂的值
但实际上真正对齐的字节数是取上述的自身对齐值与指定对齐值两者较小的那个,结构体的分配空间一定是实际对齐值的整数倍
-----------------------------------------------------------------------------------------------------------
(三) 结构体变量的引用【注意!!!优先级仅次于()】
1、结构体变量.成员变量名
2、(*结构体指针).成员变量名
3、结构体指针->成员变量名
-----------------------------------------------------------------------------------------------------------
(四) 结构体变量的定义并初始化
1、按定义时的顺序依次初始化各成员变量,用逗号隔开
2、不按顺序,.成员变量名=值
3、结构体数组初始化
a.按数组元素的顺序初始化
b.不按照数组元素的顺序 [下标]=【这种方式不同的编译器,情况和限制不一样】
-----------------------------------------------------------------------------------------------------------
(五) 柔性数组【拓展】(往往位于结构体的最后)
1、格式:char data[0];
2、优势:
a.不占用内存,仅仅只记录一个地址,可灵活调整
b.方便管理空间
c.可以使数据变得连续,减少内存的碎片化
(2) 共用体(联合体)【同一时刻只能用同一个成员变量,为了节省内存才提出来的】
共用体是各个成员共用同一段内存空间的数据类型
(一) 定义
union 共用体名
{
数据类型1 变量名1;
数据类型2 变量名2;
数据类型3 变量名3;
......
};
共用体所占内存大小各个成员变量之间最大的那个
(二) 笔试题【重要】
如何判断电脑的模式:大端/小端
(3) 枚举
把该类型变量的所有的值都列举出来
(一) 定义
enum 类型名{元素列表};
"元素列表":用一些标识符来表示一些数值,默认从0开始
(4) typedef
typedef 用来声明一个新的数据类型
(一) 定义
typedef 现有的类型名 新的类型名;
-------------------------------------------------------------------------------------------------------------
(二) 常见应用场所
a.数组
int a[10]
typedef typeof(a) array;
array b;
或者:
typedef int a[10];
a b;
b.结构体
typedef struct student
{
int num;
char name[32];
}STU;
c.函数指针
typedef int (*fun_t)(int, int);
fun_t p;
【个人总结】:
举例:
int a ----> 此处的a为变量名
typedef int a ----> 此处的a为类型名
-------------------------------------------------------------------------------------------------------------
(三) #define和typedef的区别
1、工作阶段的不同:
a.前者是在函数预处理阶段的发挥作用,不会对代码的实际执行产生影响。
b.后者是在编译过程中的指令(typedef 可以提供类型检查的功能,这是 `define` 所不具备的)
2、作用功能不同:
a.前者主要用于定义常量或编写复杂的宏定义。它通常用于设置宏参数值或者定义宏开关。
b.后者主要用于定义一种数据类型的别名,以增强程序的可读性和封装性。它可以用来定义内部类型
3、作用域的不同:
a.前者没有作用域的限制,可以在整个文件中使用
b.后者有着自己的作用域,只能在其作用域范围内使用有效
4、对指针操作的影响
a.前者不会改变指针的类型
b.后者会创建指向新类型的指针
练习作业
思考拓展
(一) 二级指针的内存申请操作
需要申请两次空间
例:如果要分配成 p[3][3];
step1:
int **p = (int **)malloc(sizeof(int*)*3); // 分配3个指针的空间
step2:
for(int i = 0; i < 3; i++)
{
*(p + i) = (int*)malloc(sizeof(int)*3); // 给每个指针分配一个3个int的空间
}