文章目录
- 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 全部注释掉。
缺点:因为引入了额外的检查,增加了程序的运行时间。