前言:
都说指针难,网上铺天盖地都是C语言指针难学,让你直接从开始到放弃。难是因为他们不懂,所以才会有这样的言论。跟着下面这篇详解指针,全程无废话,只讲干货,让你看完受益匪浅,提高对指针认知的维度.
1.什么是指针
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量
指针变量:
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量
int main() { int a = 10;//在内存中开辟一块空间 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。 //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。 return 0; }
总结: 指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
地址单元的大小:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。e总结: 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在 64 位平台是 8个字节
2.指针和指针类型
int num = 10; p = #//将&num(num的地址)保存到p中 //指针定义方式:类型+*+变量名 char *pc = NULL; int *pi = NULL; short *ps = NULL; long *pl = NULL; float *pf = NULL; double *pd = NULL; char* 类型的指针是为了存放 char 类型变量的地址。 short* 类型的指针是为了存放 short 类型变量的地址。 int* 类型的指针是为了存放 int 类型变量的地址 long* 类型的指针是为了存放 long 类型变量的地址 float* 类型的指针是为了存放 float 类型变量的地址 double* 类型的指针是为了存放 double 类型变量的地址
指针+-整数的意义:
指针的类型决定了指针向前或者向后走一步有多大(距离)
int main() { int n = 10; char *pc = (char*)&n; int *pi = &n; printf("%p\n", &n); printf("%p\n", pc); printf("%p\n", pc+1);//pc是字符类型的指针+1跳过一个字节 printf("%p\n", pi); printf("%p\n", pi+1);//pi是整型类型的指针+1跳过4个字节 return 0; }
指针的解引用:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
int main() { int n = 0x11223344; //16进制的方式将值存入 char *pc = (char *)&n;//强制类型转换 int *pi = &n; *pc = 0; //解引用pc是字符类型只占一个字节,将n第一个字节上的44改为00 *pi = 0; //解引用整型4个字节,n = 0; return 0; }
3.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1. 指针未初始化
int main() { int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0; }
2. 指针越界访问
int main() { int arr[10] = {0}; int *p = arr; int i = 0; for(i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; }
3. 指针指向的空间释放
int main() { int* p = (int*)malloc(40);//给p动态内存开辟了一块空间 free(p);//使用完后释放p开辟的空间 p = NULL;//此时p指向的空间已经被系统回收,p就是个野指针,需要将其置为空指针 return 0; }
看不懂这段代码的的友友们不要慌,指针进阶部分会给大家详细讲解!
4.规避野指针
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置 NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
4.指针运算
4.1指针加减整数
指针加减整数是指将指针变量加上或减去一个整数值,以实现指针的偏移。偏移的单位是指针所指向的类型大小。例如,对于一个指向int类型的指针p,p+1表示将p指向的地址向后偏移一个int类型的大小。
指针加减整数的实现原理是:将指针的值(即指向的地址)加上或减去整数值n,然后将结果转换为指针类型。例如,对于指向int类型的指针p,p+1的实现过程如下:
- 将p的值(即指向的地址)加上sizeof(int)的值,得到新的地址。
- 将新的地址转换为指向int类型的指针,得到p+1的结果。
注意:指针的加减偏移量只跟指针的类型的有关!(字符指针+1一次走一个字节大小,整型指针+1一次走4个字节大小).
4.2指针 - 指针
指针减指针是指两个指针相减,得到它们之间的距离。这个距离是以它们所指向的内存单元为单位的。
假设有一个指向数组的指针p和另一个指向数组中的某个元素q,那么p-q将得到一个整数值,表示q在数组中的索引位置。
定义了一个整型数组arr,并初始化了两个指针p1和p2,分别指向数组中的第3个元素arr[2]和第1个元素arr[0]。
然后,输出指针减指针p1和p2之间的距离.输出2,这表明p1和p2之间相隔2个元素,即p1指向的元素在数组中的索引位置是p2指向的元素的索引位置加2。
需要注意的是,只有指向同一数组的指针才能进行减法操作。如果指针指向不同的数组,或者指向同一数组中不同的对象,那么结果是未定义的。
4.3指针的关系运算
指针的关系运算就是比较两个指针的大小关系!
==
:判断两个指针是否相等,如果指向同一个对象或者都是空指针,则返回true
,否则返回false
。!=
:判断两个指针是否不相等,如果指向不同的对象或者一个是空指针而另一个不是,则返回true
,否则返回false
。<
:判断一个指针是否小于另一个指针,比较的是指针所指向的内存地址。如果第一个指针的地址小于第二个指针的地址,则返回true
,否则返回false
。>
:判断一个指针是否大于另一个指针,比较的是指针所指向的内存地址。如果第一个指针的地址大于第二个指针的地址,则返回true
,否则返回false
。<=
:判断一个指针是否小于等于另一个指针,比较的是指针所指向的内存地址。如果第一个指针的地址小于或等于第二个指针的地址,则返回true
,否则返回false
。
>=
:判断一个指针是否大于等于另一个指针,比较的是指针所指向的内存地址。如果第一个指针的地址大于或等于第二个指针的地址,则返回true
,否则返回false
。
注意:
指针的关系运算符只能用于指向相同类型的指针,否则会导致编译错误。此外,对于空指针和非空指针之间的比较不能使用大于小于运算符,只能使用相等或不相等.
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针和数组
我们先来看一段代码
根据上面这段代码,我们可以知道数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址
既然如此,那么使用指针访问数组便成了一种可能!
代码中的p+i 其实计算的是数组 arr 下标为i的地址.因此我们还可以有别的访问方式
指针和数组之间是什么关系呢?
指针和数组名的关系是非常紧密的,但它们并不完全相同。数组名不能被赋值,而指针可以被赋值。另外,数组名作为指针时,它是一个常量指针,不能修改它的值。而指针可以被重新赋值,指向不同的对象。指针变量就是指针变量,不是数组,指针变量的大小是4/8个字节,专门是用来存放地址的数组一块连续的空间,可以存放1个或者多个类型相同的数据。数组就是数组,不是指针.
联系:
数组中,数组名其实是数组首元素的地址,数组名 == 地址 == 指针当我们知道数组首元素的地址的时候,因为数组又是连续存放的,所以通过指针就可以遍历访问数组数组是可以通过指针来访问的.
6.二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是二级指针了!
二级指针是指一个指向指针的指针,也就是说,它是一个指针变量的地址,该指针变量又指向另一个指针变量,最终指向某个实际的变量或者地址。
二级指针运算:
7.指针数组
指针数组是指针还是数组?
答案是:数组。是存放指针的数组!
我们来推一下,整形数组是一个存放整型值的数组数组内的每个元素都是整型,字符数组是一个存放字符值的数组,数组内的每个元素都是字符型.
那指针数组呢?
指针数组是一个数组,其元素都是指针类型.
我们定义了一个指针数组arr3
,数组内的元素都是指向整型变量的指针。通过*解引用就可以访问指针的值.
本章内容已完:
如果本篇文章有给你带来收获,欢迎点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。