ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C语言课程.8.1指针详解

C语言课程.8.1指针详解

文章目录

  • 1. 内存和地址
  • 2. 指针变量和地址
    • 2.1 取地址操作符
    • 2.2 解引用操作符
    • 2.3 指针变量的大小
  • 3.指针变量类型的意义
  • 4.指针运算
  • 5. const 修饰指针
  • 6. 野指针的成因
    • 6.1 指针未初始化
    • 6.2 指针越界访问
    • 6.3 指针指向的空间释放
  • 7. assert 断言

1. 内存和地址

内存就是我们在运行时将数据存储的地方。
一个比特位可以存储一个 2 进制的位 1 或者 0。
计算机常见单位:
1Byte = 8bit
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

而每一个内存都有其独一无二的地址。
地址==指针

2. 指针变量和地址

2.1 取地址操作符

取地址操作符&用于获取变量在内存中的实际地址。

intmain(){inta=10;printf("变量 a 的值是:%d\n",a);printf("变量 a 的地址是:%p\n",&a);// %p 用于打印指针/地址,格式化输出十六进制return0;}

2.2 解引用操作符

解引用操作符*用于访问指针所指向的内存地址中的内容。

#include<stdio.h>intmain(){inta=10;// 定义一个整型变量 a,初始值为 10int*pa=&a;// 定义一个指针变量 pa,存储 a 的地址printf("a 的值:%d\n",a);// 输出:10printf("pa 存储的地址:%p\n",pa);// 输出:0x7fffffffe45c(假设 a 的地址)printf("pa 指向的内容:%d\n",*pa);// 输出:10(读取地址处的值)*pa=20;// 修改 pa 所指向的内存中的值为 20printf("修改后 a 的值:%d\n",a);// 输出:20(因为 a 被改变了)printf("pa 存储的地址:%p\n",pa);// 输出:0x7fffffffe45c(地址未发生改变)return0;}

int* pa = &a是将a的地址放在pa中。
printf("%d ", *pa);*pa的作用是读取pa存储的地址,得到地址内存放的内容。
*pa = 20*pa的作用是读取pa存储的地址,将地址中存放的内容改为 20。

2.3 指针变量的大小

指针变量的大小与类型无关,只与平台有关。
64 位机器 8 个字节,32 位机器 4 个字节。

#include<stdio.h>//指针变量的⼤⼩取决于地址的⼤⼩//32位平台下地址是32个bit位(即4个字节)/64位平台下地址是64个bit位(即8个字节)intmain(){printf("%zd\n",sizeof(char*));printf("%zd\n",sizeof(short*));printf("%zd\n",sizeof(int*));printf("%zd\n",sizeof(double*));return0;}

3.指针变量类型的意义

#include<stdio.h>intmain(){intn=10;char*pc=(char*)&n;int*pi=&n;printf("%p\n",&n);printf("%p\n",pc);printf("%p\n",pc+1);printf("%p\n",pi);printf("%p\n",pi+1);return0;}


我们可以看出,char* 类型的指针变量 +1 跳过 1 个字节,int* 类型的指针变量 +1 跳过了 4 个字节。
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。

4.指针运算

因为数组在内存中是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就能找到后面的所有元素。

intarr[10]={1,2,3,4,5,6,7,8,9,10};

指针 ± 整数 = 指针

#include<stdio.h>//指针+- 整数intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};int*xp=&arr[0];inti=0;intsz=sizeof(arr)/sizeof(arr[0]);for(i=0;i<sz;i++){printf("%d ",*(p+i));//p+i 这里就是指针+整数}return0;}

指针 - 指针 = 整数

//指针-指针#include<stdio.h>intmy_strlen(char*s){char*xp=s;while(*p!='\0')p++;returnp-s;}intmain(){printf("%d\n",my_strlen("abc"));return0;}

5. const 修饰指针

修饰指针一共有两种情况,就接下来我们来讲解一下。

#include<stdio.h>//代码1 - 测试无const修饰的情况voidtest1(){intn=10;intm=20;int*p=&n;*p=20;//ok?p=&m;//ok?}//代码2 - 测试const放在*的左边情况voidtest2(){intn=10;intm=20;constint*p=&n;*p=20;//ok?p=&m;//ok?}//代码3 - 测试const放在*的右边情况voidtest3(){intn=10;intm=20;int*constp=&n;*p=20;//ok?p=&m;//ok?}//代码4 - 测试*的左右两边都有constvoidtest4(){intn=10;intm=20;intconst*constp=&n;*p=20;//ok?p=&m;//ok?}intmain(){//测试无const修饰的情况test1();//测试const放在*的左边情况test2();//测试const放在*的右边情况test3();//测试*的左右两边都有consttest4();return0;}

代码 1 没有影响,代码 2 无法改变指针指向的值,代码 3 无法改变指针的地址,代码 4 均无法改变。

声明名称指针本身指向的内容
int *p普通指针可改可改
const int *p(=int const *p)指向常量的指针可改不可通过 p 改
int * const p常量指针不可改可改

6. 野指针的成因

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

6.1 指针未初始化

#include<stdio.h>intmain(){int*p;// 局部变量指针未初始化,默认为随机值*p=20;return0;}

6.2 指针越界访问

#include<stdio.h>intmain(){intarr[10]={0};int*p=&arr[0];inti=0;for(i=0;i<=11;i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++)=i;}return0;}

6.3 指针指向的空间释放

#include<stdio.h>int*test(){intn=100;return&n;}intmain(){int*p=test();printf("%d\n",*p);return0;}

7. assert 断言

需要assert.h,用来调试,当条件为假时,会终止执行并报错。
常用来判断是否为空指针。

assert(ps!=NULL);//assert(ps)也对

如果程序已经确认没有 BUG,那么可以在 #include <assert.h> 之前加上 #define NDEBUG。

#defineNDEBUG#include<assert.h>

这样就会将程序中的所有 assert 全部注释掉。
缺点:因为引入了额外的检查,增加了程序的运行时间。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进