飞道的博客

六.初阶指针

359人阅读  评论(0)

 前言:大家好哇!今天带大家认识下C语言中的指针,指针的用法等,希望对大家有所帮助!


目录

一.指针是什么

1.指针是什么?

2.如何理解指针变量

二.指针和指针的类型

1.指针类型

2.指针类型的意义

(1)指针类型决定了指针解引用时,能够访问的内存大小是多少

(2)指针加减整数

(3)指针修改数组元素

三.野指针

1.野指针的概念

2.野指针的成因

(1)指针未初始化

(2)指针越界访问

(3)指针指向的空间已经释放

 3.那该如何规避野指针问题?

四.指针运算

1.指针加整数

2.指针减整数

4.指针减指针

5.指针的关系运算

五.指针和数组

1.数组名

2.使用指针访问数组

六.二级指针

七.指针数组


一.指针是什么

1.指针是什么?

理解指针是什么首先要引入内存-------电脑上的一种存储设备,分4/8/16GB。

如上图所示:我们把整个内存划分为一个个小的内存单元,每个内存单元的大小是一个字节,然后将这些内存单元进行编号,未来我们想使用哪一个内存单元时只需要知道对应的编号就能找到,这个编号就叫做内存单元的地址,也就是C语言中的指针

那么这里还有一个问题,我们是如何给内存单元编号的呢?

其实对于32位机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(1)和低电平(0),那么这32根地址线产生的地址就会是:

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

.......

111111111 111111111 111111111 111111111

 总共有个地址,每个地址对应一个内存空间,每个内存空间的大小为一个字节,那么我们就能对4G的空间进行编址寻址了。

2.如何理解指针变量

通过&(取地址操作符)取出变量的内存地址,把地址存放到一个变量中,这个变量就是指针变量。

所以指针变量是用来存放地址的变量,我们平时口语所说的指针指的就是指针变量。

上文提到,在32位机器上,地址是由32个0或1组成的二进制(bit)序列,那地址就得占4个字节,需要4个字节的空间来存储,所以一个指针变量的大小为4个字节

同理,64位机器上,一个指针变量的大小为8个字节

总结:

  1. 指针就是地址,地址就是指针;
  2. 指针变量就是变量,用来存放地址的变量(存放在之中的值都被当成地址处理);
  3. 一个小的内存单元大小为 1 个字节;
  4. 指针是用来存放地址的,地址是唯一标识一块内存空间的;
  5. 指针的大小在 32 位平台上是 4 个字节,在 64 位平台上是 8 个字节;

二.指针和指针的类型

1.指针类型

指针的本质是地址。


  
  1. int main()
  2. {
  3. int a = 0x11223344;
  4. int* pa = &a;
  5. char* pc = &a;
  6. printf( "%p\n", pa);
  7. printf( "%p\n", pc);
  8. return 0;
  9. }

上面int 型指针pa和char型指针pc都可以存储a,他们的运行结果是一样的。

2.指针类型的意义

(1)指针类型决定了指针解引用时,能够访问的内存大小是多少

int  *p         //*p能够访问4个字节

char *p       //*p能够访问4个字节

double *p   //*p能够访问4个字节

.......

不同的指针类型,访问的大小不同:


  
  1. int main()
  2. {
  3. int a = 0x11223344;
  4. int* pa = &a; // 44 33 22 11 (至于为什么是倒着的,后面会讲。)
  5. *pa = 0; // 00 00 00 00
  6. char* pc = &a; // 44 33 22 11
  7. *pc = 0; // 00 33 22 11 // 在内存中仅仅改变了一个字节
  8. // 解引用操作时就不一样了:
  9. // 整型指针操作了4个字节,让四个字节变为0
  10. // 字符指针能把地址存到内存中,但是解引用操作时,只能动1个字节
  11. return 0;
  12. }

(2)指针加减整数

公理:指针类型决定指针步长(指针走一步多远)

int *p;p+1              //跳过4个字节

char *p;p+1           //跳过1个字节

double *p;p+1       //跳过8个字节


  
  1. int main()
  2. {
  3. int a = 0x11223344;
  4. int* pa = &a;
  5. char* pc = &a;
  6. printf( "%p\n", pa); // 0095FB58
  7. printf( "%p\n", pa+ 1); // 0095FB5C + 4
  8. printf( "%p\n", pc); // 0095FB58
  9. printf( "%p\n", pc+ 1); // 0095FB59 + 1
  10. return 0;
  11. }

(3)指针修改数组元素


  
  1. int main()
  2. {
  3. int arr[ 10] = { 0};
  4. int brr[ 10] = { 0};
  5. int* pa = arr; //数组名 - 首元素地址
  6. char* pc= brr;
  7. /* 修改 */
  8. int i = 0;
  9. for(i= 0; i< 10; i++) {
  10. *(pa+i) = 1; //成功,arr里的元素都变为了1
  11. }
  12. for(i= 0; i< 10; i++) {
  13. *(pc+i) = 1; //不成功,只能一个字节一个字节的改,只改动了10个字节。
  14. }
  15. /* 打印 */
  16. for(i= 0; i< 10; i++) {
  17. printf( "%d ", arr[i]);
  18. printf( "%d ", brr[i]);
  19. }
  20. return 0;
  21. }

这是因为:char*的指针解引用只能访问一个字节 

总结:

 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节);

 譬如,char* 的指针解引用只能访问1个字节,而 int* 的指针解引用就能够访问4个字节

三.野指针

1.野指针的概念

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

2.野指针的成因

  1.  指针未初始化;
  2. 指针越界访问;
  3. 指针指向的空间已释放;

(1)指针未初始化


  
  1. int main()
  2. {
  3. int *p; //初始化随机值
  4. *p = 20; //*p未初始化就使用,虽然有指向的对象,但其存放的内存是随机的
  5. return 0;
  6. }

(2)指针越界访问

常出现在数中


  
  1. int main()
  2. {
  3. int arr[ 10] = { 0};
  4. int *p = arr;
  5. int i = 0;
  6. for(i= 0; i< 12; i++)
  7. {
  8. //当指针越出arr管理的范围时,p就称为野指针
  9. p++;
  10. }
  11. return 0;
  12. }

(3)指针指向的空间已经释放


  
  1. int* test()
  2. {
  3. int a = 10;
  4. return &a;
  5. }
  6. int main()
  7. {
  8. int *pa = test();
  9. *pa = 20;
  10. return 0;
  11. }

 这是因为:

  • 一进入test 函数内时就创建一个临时变量 a,这个a是局部变量,进入函数时创建,一旦出去就销毁,销毁就意味着这个内存空间还给了操作系统,这块空间就不再是 a 的了;
  • 进入这个函数时创建了 a,有了地址,ruturn &a 把地址返回去了,但是这个函数一结束,这块空间就不属于自己了,pa调用使用时,这块空间已经释放了,指针指向的空间被释放了,这种情况就会导致野指针的问题;
  • 只要是返回临时变量的地址,都会存在问题(除非这个变量出了这个范围不销毁);

 3.那该如何规避野指针问题?

答:乖乖初始化,指针指向空间释放及时置空,指针使用前检查有效性。


  
  1. //初始化
  2. int main()
  3. {
  4. int a = 10;
  5. int* pa = &a; // 初始化
  6. int* p = NULL; // 当你不知道给什么值的时候用NULL
  7. return 0;
  8. }
  9. //不想用了就置空
  10. int main()
  11. {
  12. int a = 10;
  13. int *pa = &a;
  14. *pa = 20;
  15. //假设已经把a操作好了,pa指针已经不打算用它了
  16. pa = NULL; //置成空指针
  17. return 0;
  18. }
  19. //指针使用前检查有效性
  20. int main()
  21. {
  22. int a = 10;
  23. int *pa = &a;
  24. *pa = 20;
  25. pa = NULL;
  26. //*pa = 10; 崩溃,访问发生错误,指针为空时不能访问
  27. if(pa != NULL) { // 检查 如果指针不是空指针
  28. *pa = 10; // 检查通过才执行
  29. }
  30. return 0;
  31. }

四.指针运算

1.指针加整数


  
  1. int main()
  2. {
  3. int arr[ 10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  4. int i = 0;
  5. int sz = sizeof(arr) / sizeof(arr[ 0]);
  6. int* p = arr; // 指向数组的首元素 - arr[0] - 1
  7. for(i= 0; i<sz; i++) {
  8. printf( "%d ", *p);
  9. p = p + 1; //p++ 第一次循环+1之后指向2
  10. }
  11. return 0;
  12. }

 打印结果为 1 2 3 4 5 6 7 8 9 10

2.指针减整数


  
  1. int main()
  2. {
  3. int arr[ 10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  4. int i = 0;
  5. int sz = sizeof(arr) / sizeof(arr[ 0]);
  6. int* p = &arr[ 9]; // 取出数组最后一个元素的地址
  7. for(i= 0; i<sz/ 2; i++) {
  8. printf( "%d ", *p);
  9. p = p - 2; //指针每次向前移动2个步长
  10. }
  11. return 0;
  12. }

 打印结果为 10 8 6 4 2

4.指针减指针

公理:指针减指针的绝对值得到的是元素之间的元素个数(必须指向用一块内存空间)


  
  1. int main()
  2. {
  3. int arr[ 10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  4. printf( "%d\n", &arr[ 9] - &arr[ 0]); // 得到指针和指针之间元素的个数 9
  5. return 0;
  6. }
  7. //错位示范:
  8. int ch[ 5] = {0};
  9. int arr[ 10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  10. printf( "%d\n", &arr[ 9] - &ch[ 0]); // 没有意义,结果是不可预知的

手写streln:


  
  1. //用指针方法实现;
  2. int my_strlen(char* str)
  3. {
  4. char* start = str;
  5. char* end = str;
  6. while(*end != '\0') {
  7. end++;
  8. }
  9. return end - start; //return
  10. }
  11. //用指针间做差化简:
  12. int my_strlen(const char* str)
  13. {
  14. const char* end = str;
  15. while(*end++);
  16. return (end - str - 1);
  17. }
  18. int main()
  19. {
  20. //strlen - 求字符串长度
  21. //递归 - 模拟实现了strlen - 计数器方式1, 递归的方式2
  22. char arr[] = "abcdef";
  23. int len = my_strlen(arr); //arr是首元素的地址
  24. printf( "%d\n", len);
  25. return 0;
  26. }

5.指针的关系运算


  
  1. //vp从最大位置的后一个内存位置的指针开始比较
  2. for(vp = & values[N_VALUES]; vp > & values[ 0];)
  3. {
  4. *--vp = 0;
  5. }

对于这样一段代码,我们可以做这样的化简,使vp从数组末尾开始比较,一直比较到数组首元素的前一个元素


  
  1. //最后一次比较是vp与数组首元素的前一个内存位置的指针进行比较
  2. for(vp = & values[N_VALUES- 1]; vp >= & values[ 0];vp--)
  3. {
  4. *vp = 0;
  5. }

虽然第二种写法更容易理解,但我们应避免这么写,因为标准并不保证它可以执行:

标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的拿个内存位置的指针比较,但是不允许与指向第一个元素之前的拿个内存位置的指针进行比较;

 

 

五.指针和数组

1.数组名

一般情况下,数组名是首元素的地址,但是有两个特例:

  • sizeof(数组名):计算的是整个数组的大小,单位是字节。
  • &数组名:取出的是整个数组的地址,此时的数组名表示的是整个数组。

2.使用指针访问数组

p+i访问的是数组arr下标为i的地址:


  
  1. int main()
  2. {
  3. int arr[ 10] = {0};
  4. int* p = arr; // 用指针访问数组, arr为数组首元素的地址
  5. int i = 0;
  6. for(i= 0; i< 10; i++) {
  7. printf( "%d ", arr[i]);
  8. }
  9. for(i= 0; i< 10; i++) {
  10. *(p+i) = i; //用指针对数组进行赋值操作
  11. }
  12. for(i= 0; i< 10; i++) {
  13. printf( "%d ", arr[i]);
  14. }
  15. return 0;
  16. }

打印结果为:0 0 0 0 0 0 0 0 0 0

                      0 1 2 3 4 5 6 7 8 9

所以通过指针是可以对数组进行访问的,一般情况下,数组名是数组首元素的地址。

六.二级指针

什么是二级指针? 如下图所示:

变量a的地址存放在pa中,变量pa的地址存放在ppa中,pa是一级指针,ppa是二级指针。

 总结:指针变量也是变量,是变量就有地址,一级指针的地址就存放在二级指针中。

 二级指针的应用:


  
  1. int main()
  2. {
  3. int a = 10;
  4. int* pa = &a;
  5. int** ppa = &pa; // ppa是二级指针
  6. int*** pppa = &ppa; // pppa是三级指针
  7. ...
  8. **ppa = 20;
  9. printf( "%d\n", **ppa); // 20
  10. printf( "%d\n", a); // 20
  11. return 0;
  12. }

 *ppa通过对ppa中的地址解引用,找到了pa,*ppa访问的是pa。

**ppa先通过*ppa找到pa,然后*pa再找到a,所以**ppa访问的是a。

七.指针数组

指针数组的概念:

指针数组本质上数组,但数组元素类型是指针


  
  1. int a = 10;
  2. int b = 20;
  3. int c = 30;
  4. int arr1[ 10]; //arr1是一个存放整型的数组
  5. int* arr2[ 3] = { &a,&b,&c }; //arr2是一个存放整型指针的数组

注意数组arr2里存放的是变量a,b,c的地址,要访问a,b,c还要进行解引用操作。


  
  1. //打印a,b,c的值
  2. for(int i= 0;i< 3;i++)
  3. {
  4. printf("%d",*arr2[i]);
  5. }

打印结果为:10 20 30


本篇到此结束,谢谢大家的观看!

码文不易,还请多多支持哦~


转载:https://blog.csdn.net/weixin_67401157/article/details/127938634
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场