PoEdu - C提高班 【Po学校】-Lesson08 -指针专题(一):指针&变量&数组- 课堂笔记

1--> 变量 (占用指定长度的内存空间)

1.0 变量的本质是什么?为什么要用“变量”?看两面模拟变量出现的演变过程:

每一个[]中表示一个内存地址;



回头看变量的表示:




如上图所示,“变量”出现就有了意义。
变量名 = 地址 + 长度的值
2--> 指针 (代表一个起始地址)

2.0 指针是一个变量
2.1 拥有所有变量的属性
2.2 指针变量 = 指向变量的地址
2.3 指针类型决定使用时*(解)的长度
2.4 指针它代表一个内存地址,不代表长度以及解析方式
2.5 示例
#include <stdio.h> int main() { int *pn = NULL; //pn变量--> 空间地址 + 长度 printf("指针变量的地址:%p, 指针变量的长度:%u, 指针变量的值:%p \n",&pn, sizeof(pn), pn); return 0; }
运行结果:指针变量的地址:005EFDD4, 指针变量的长度:4, 指针变量的值:00000000
2.6 与变量作比较
修改代码:
#include <stdio.h> int main() { int pn = NULL; //pn变量--> 空间地址 + 长度 这里的NULL可以改为0,也可以理解为0。 printf("int变量的地址:%p, int变量的长度:%u, int变量的值:%p \n", &pn, sizeof(pn), pn); return 0; }

对比指针:

验证:指针也是一个变量
2.7 指针和变量的区别:值所表示的意义不同。
2.8 指针变量存放的是一个内存地址,而int类型的变量存放的只是一个整数。所以相对普通的变量,指针变量就有了解引用:*pn =10 。
2.9 指针的解引用流程图:
2.9.0 代码:
#include <stdio.h> int main() { int n = 100; int *pN = &n; printf("n:%d, &n:%p, pN:%p, &pN:%p \n", n, &n, pN, &pN); *pN = 10; printf("n:%d, &n:%p, pN:%p, &pN:%p \n", n, &n, pN, &pN); pN = 10; printf("n:%d, &n:%p, pN:%p, &pN:%p \n", n, &n, pN, &pN); return 0; }
2.9.1 *pN = 10; 左边*pN分成3个动作:1找到pN的内存地址;2读取这段空间的值,发现它是一个内存地址(指针);3用这个内存地址(指针)所指向的位置,找到另外一段内存空间。

2.9.2 第1步 找到pN地址

2.9.3 第2步 找到里面的指针(内存地址)

2.9.4 第3步 找到指针指向的空间
2.9.5 第4步 用10把里面的值覆盖

2.10 指针的类型 (解引用时需要)
2.10.0 看代码:
#include <stdio.h> int main() { int n = 100; int *pN = &n; char c = 'a'; pN = &c; printf("c:%d, &c:%p, pN:%p, &pN:%p, *pN:%d \n", c, &c, pN, &pN,*pN); return 0; }
2.10.1 看解引用 *pN的流程图:

2.10.1.0 第一步 找到pN的空间

2.10.1.1 第二步 找到pN里面的值:指针(内存地址)

2.10.1.2 第三步 找到指针指向的空间地址

2.10.1.3 第四步 读取空间里面的值,读取多长呢?看指针的类型。


2.10.1.4 最终*pN和结果不是我们想要的‘a’或者97。
2.10.1.5 这种未定义的行为一定要避免,知道第一个它是97,后面3个是什么谁也不知道,就会导致程序里面出现各种各样的错误。
2.10.1.6 为什么说指针代表的只是简单的地址,但是我们在定义指针变量的时候,还是要给出一个类型呢?如上流程图所展示的意义,当解引用的时候,我们需要知道要读取多长的空间。
3--> 变量与指针
3.0 什么是变量?变量就是存储空间的别名。
3.1 为什么要使用变量?为了方便访问存储空间。
3.2 除了用变量名访问存储空间,还有指针可以访问对应的存储空间。
3.3 指针最大的作用是:使用指针,对存储空间进行访问。
3.4 访问存储空间,变量名访问的方式,比之指针访问方式更加安全。什么时候用变量名,什么时候用指针?只有在变量名无法使用的场合,在使用变量名无法达到需求的时候,就可以使用指针。
3.5 在哪些场合下,变量名访问无法达到需求呢? 1 局部变量,传递参数时。 2 动态分配内存时。
3.6 指针也是一个变量,指针变量的名相对普通变量,也可以当作是指针的变量名。它同样是存储空间的别名。
3.7 指针变量也会有指针(内存地址),指针变量最大的作用是保存一根指针(内存地址)。保存内存地址的变量,我们叫做“指针”。
3.8 是凡使用了“指针”,对存储空间的访问就一定是间接访问,因为“指针”对应的空间里面存储的是一根指针(内存地址);
3.9 从汇编来看变量名访问存储空间,以及用指针来间接访问存储空间,他们之间的效率:
3.9.0 代码:
#include <stdio.h> int main() { int n = 10; //直接访问 int *pn = &n; //间接访问 n = 100; *pn = 1000; return 0; }
#include <stdio.h> int main() { 01321680 push ebp 01321681 mov ebp,esp 01321683 sub esp,0DCh 01321689 push ebx 0132168A push esi 0132168B push edi 0132168C lea edi,[ebp-0DCh] 01321692 mov ecx,37h 01321697 mov eax,0CCCCCCCCh 0132169C rep stos dword ptr es:[edi] 0132169E mov eax,dword ptr [__security_cookie (01328004h)] 013216A3 xor eax,ebp 013216A5 mov dword ptr [ebp-4],eax int n = 10; //直接访问 013216A8 mov dword ptr [n],0Ah int *pn = &n; //间接访问 013216AF lea eax,[n] //lea命令 传递有效的地址。 013216B2 mov dword ptr [pn],eax n = 100; 013216B5 mov dword ptr [n],64h //直接一步到位 *pn = 1000; 013216BC mov eax,dword ptr [pn] //dword 双字 就是四个字节 //ptr pointer缩写 即指针 013216BF mov dword ptr [eax],3E8h //3E8的16进制是1000 //比如mov eax, dword ptr [12345678] 把内存地址12345678中的双字型(32位)数据赋给eax return 0; 013216C5 xor eax,eax }
3.9.1 以上代码可以看出,使用变量和使用指针赋值的区别:1 使用变量赋值,一步到位。 2 使用指针赋值,先要有一步“寻址”的步骤,需要两步
3.9.2 此时变量直接赋值效率要高,另外,指针也是要占用空间的。所以此例中,使用指针赋值是没有使用变量名直接赋值效率高的。
3.9.3 只有当一个函数的参数传递,函数运行中要改变变量的值,此时才是指针的合适用处。代码:
#include <stdio.h> void foo(int *n) { *n = 100; } int main() { int n = 10; //直接访问 int *pn = &n; //间接访问 n = 100; *pn = 1000; return 0; }
上面的foo函数,需要改变n的值,此时运用指针才是恰当的。
3.9.4 指针与const结合的助记口诀:左数右指

4--> 数组
4.0 数组与指针的关系
#include <stdio.h> int main() { int array[10] = { 1, 2, 3, 4, 5 }; int *parray = array; printf("%d \r\n",*parray); printf("%d \r\n", *parray+1); printf("%d \r\n", *parray+2); printf("%d \r\n", *parray+3); return 0; }
用指针+1可以遍历数组成员。
运行:

4.1 大部分时候,数组名可以认为是一个常量指针,但在两种情况下另外:1 在作sizeof运算时,它就不等于常量指针;2 在作&运算时,数组名就不能认为是常量指针。
4.2 我们对一个数组名作&(取址)运算时,会得出什么呢?
4.3 分析数组名:数组名就是数组首个元素地址的别名。数组名作&(取址)运算得出的地址还是这个地址,此时“&数组名”就包含了两层含义:1 此地址为数组的地址; 2 此地址为首个元素的地址。这里可以理解为一个套装的模块(数组)里面有很多小的元素块,首个元素块有一个唯一的标记(首地址),这个标记是此首元素的标记(地址),也是整个套装模块的标记(地址)。当我不加&(取址),直接用数组名运算+,或者运算减,此时是元素指针的运算。当我加上&(取址),那么“&数组名”就是数组指针的运算;差之毫厘,缪以千里。
4.4 看代码 ,array 与 &array 的不同。
#include <stdio.h> int main() { int array[10] = { 1, 2, 3, 4, 5 }; int *parray = array; int paddrarrray = &array; printf("%p \t%p \r\n", parray+1, paddrarrray+1); printf("%p \t%p \r\n",array +1, &array+1); return 0; }
上下两个printf函数中,两者不同在于,上面的是int类型的指针参与运算,下面的&array是数组地址参与运算。前者计算公式: array + sizeof(*array),后者计算公式:*array + sizeof(*&array); 此例parray+1, paddrarrray+1始终相同,在于它们都是一个int*类型的指针,所以始终相同。
4.5 看示例,理解偏移量的不同:当元素大小为1byte时,偏移+1byte,而当数组大小为100时,偏移+100byte

4.6 看二维数组:

5--> 数组与指针的区别
5.0.0 先看个示例:把一段指针的定义写入另外一个.c文件,在main.c中以数组形式引用,会发生什么样子的状况呢?
char *pstr = "I Love Mark!~"; //指针写在other.c中
5.0.1 main.c中以数组方式欺骗编译器。
#include <stdio.h> extern char pstr[]; //以数组方式 欺骗编译器 int main() { printf("%s",pstr); return 0; }
5.0.2 运行结果:4k 不是我们想要的结果(I Love Mark!~)。

5.0.3 指针有寻址动作,间接访问一根指针(内存地址)指向的空间,数组是直接访问,没有寻址动作,原始数据是什么,就反馈什么,没有其它的动作。

5.0.4 把指针看作数组后,它就失去了指针指向、寻址的意义,直接访问里面的数据。
5.0.5 如上示例,怎么把数组的直接访问,改写成指针的间接访问,达到目的呢? 第1步:要把pstr变成一个指针变量,(unsigned int*)pstr; 第2步: 把里面的数据取出,解引用;第3步:把解引用出来的数据强制转换成一根指向常量区的char*指针。

#include <stdio.h> extern char pstr[]; int main() { printf("%s",(char*)(*(unsigned int*)pstr)); return 0; }
5.0.6 指针在内存中是一个unsigned int 的类型,所以要先把pstr[]转换成unsigned int*,得以正确的格式处理里面的地址数据 。
5.1.0 指针的运算: 1 有加运算 +1 ; 2 有减运算 -1 ; 3 有“指针”与“指针”之间的“减”; 4 有“指针”与“指针”之间的等于等于 == ;5 p1 !=p2 指针之间的“非等” 。
5.1.1 指针的运算公式:1 p + sizeof(*p) 2 p - sizeof(*p) 3 (sizeof(p1) - sizeof(p2))/sizeof(*p), "指针"之间的“偏移量” 4 p1 == p2; “指针”是否相同 5 p1 !=p2 指针之间不相同
5.1.2 利用指针之间的“偏移量”(指针之间的相减),能够控制指针之间的范围区间:
#include <stdio.h> int main() { int array[10] = { 1,2,3,4,5 }; int *parray = array + 3; int *pother = array + 7; for (; pother - parray >=0; parray++) { printf("%d \n",*parray); } return 0; }
运行

5.1.3 堆上的指针运算,是未定义的结果,是无意义的,就相当于对“野指针”进行操作。

5.1.4 只有连续内存空间的指针运算才是有意义的。
5.1.5 看示例:地址之间的比较,同样要在连续的空间才是有意义的。
#include <stdio.h> int main() { int array[10] = { 1,2,3,4,5 }; int *parray = array + 3; int *pother = array + 7; while (parray != pother) { printf("%d \n",*parray); parray++; } return 0; }
运行:

5.2 使用指针访问数组元素,与之使用下标访问数组元素,两者效率比较,指针访问的效率高于下标访问效率。

5.2.0 但现在的编译器已经做了很多的优化,在Release下面,已经没有区别了。
6--> 数组作为函数的参数传递
6.0.0 数组当作为函数参数进行传递时,会降级降维,变成指针。
6.0.1 传递参数时降级,会很是彻底:在函数外部作为数组时,数组名是一个常量的指针,数组名不可以被更改。而作为函数的参数传递降级,函数里面指针是可以被修改的。
6.0.2 作为参数传递是创建一个副本,是一个copy,在函数里面,它完全成了另外一个普通的指针数据。
6.0.3 如果要函数改变外部数组元素,需要传递指针的指针。

#include <stdio.h> int foo(int **array) { int n = 10; *array = &n; return 0; } int main() { int array[10] = { 1,2,3,4,5 }; printf("%p \r\n",array); foo(array); printf("%p\r\n",array); for (int i = 0; i<10; i++) { printf("%d\r\n", array[i]); } return 0; }
运行

浙公网安备 33010602011771号