目录
前言:在这篇博客中,我们将讲到C语言中非常重要的一块内容——指针。通过对指针的熟练运用,我们可以实现之前的一些我们认为不能实现的操作,比如:常变量数值的交换等问题。指针的知识众多且复杂,在这里我们先简单地介绍一下指针的一些基础操作。
一.指针是什么
想清楚指针,首先我们先说一下内存
指针理解的2个要点:
1.指针式内存中一个最小单元的编号,也就是地址
2.平时我们说的指针,实际上指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针实际上是说指针变量
那这些编号是怎样产生的呢?
我们平时的电脑一般是32位,或者是64位的。
32位,代表着32根地址线,这些地址线都是物理的电线,这些地址线通过通电产生高电压和低电压,再把电信号转化为数字信号就成了二进制中的1和0。
这32根地址线说产生的地址有:
00000000000000000000000000000000
00000000000000000000000000000001
00000000000000000000000000000010
........
01111111111111111111111111111111
10000000000000000000000000000000
.......
11111111111111111111111111111111
这里有2的32次方个地址
每个地址标识一个字节,那我们就有2的32次方个byte的空间也就是4GB的空间
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
二.指针和指针类型
我们知道变量由不同的数据类型,比如:
char c = '0';
short s = 0;
int i = 0;
float f = 0;
double d = 0;
而我们的指针作为指向变量的存在,也有自己的变量类型,即type+*,比如,当我们定义了一个int类型的变量现在需要一个指向它的指针就需要定义一个类型为int*的指针。
int a = 10;
int* pa = &a;
2.1 指针+-整数
注:%#x表示以十六进制进行输出,并带前缀0x
但是在给指针pnum+1之后,发现相比之前,指针向后走了4个比特位。
这与num的数据类型有关。
pnum+1的意思是指向num后面的对象的开头,由上面的代码截图我们知道,pnum里面含的内容是num开头的地址0x57f6f0,假如pnum+1的结果是0x57f6f1的话,那他所指的对象仍是num,要想让它指向的对象变成num后面的东西,0x57f6f0必须加4。
2.2指针的解引用
*是指针的解引用操作符,指针是指向对象的地址,如果在指针的前面加一个*,这是*指针就是所指的对象,比如
这里还有一个关于访问权限的知识点
注:接下来的操作都是在VS编译器下进行的
先看下面的代码
#include <stdio.h>
int main()
{
int num = 1000;
char* pcnum =(char*) #
int* pinum = #
*pcnum = 0;
printf("%d\n", num);
*pinum = 0;
printf("%d\n", num);
return 0;
}
这个代码的结果是这样的:
我们发现两个指针归零时,对num这个变量产生了不同的影响,这是为什么呢?
进行上面的操作我们就可以在编译器上面,通过搜索num的地址来观察到num数值的存储方式。
再次按下f10进行调试,
注:这边显示的是十六进制,e8就占一个字节。
当把第8行的代码运行了之后,num数值的变化如下:
我们知道十进制的1000在十六进制中的数值是3E8, 我们发现当第8行的归零代码运行之后,E8变成了00,原来的3E8变成了300,而十六进制的300换算为十进制就是768,所以第一个printf打印出来的数值是768,产生这样的情况就是因为我们定义的指针pcnum是char类型的,通过强制类型转换,让它指向了num,因为char*类型的指针的访问权限只有1个字节,所以通过解引用指针进行归零时,只能归零1个字节,故3E8变成了300。
同理,因为int*型的指针访问权限是4个字节,所以它可以把3E8全部归零。
所以输出的结果一个是768,一个是0;
三.野指针
野指针就是指针指向的位置是不可知的
3.1 野指针成因
1.指针未初始化;
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针的越界访问
#include <stdio.h>
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.指针指向的空间释放(在动态内存开辟再说)
3.2 如何规避野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放时,让指针指向NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
四.指针运算
4.1 指针+-整数
这里的内容在前面有讲过,就不多赘述。(该操作常用于数组)
总结:指针+n(整数),指针指向它所指向对象后面的第n个元素
指针-n(整数),指针指向它所指向对象前面的第n个元素
4.2 指针-指针
指针-指针,可以用来计算两个指针间的元素个数
比如计算字符串的长度
4.3 指针的关系运算
这里简单的举个例子:
假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *p++、*++p、(*p)++ 分别是什么意思呢?
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素。
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ 会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 1 个元素,并且第 1 个元素的值为 99,执行完该语句后,第 1 个元素的值就会变为 100。
五.指针和数组
在学习数组时,我们知道数组名其实是数组的首元素地址,而我们常用的数组名[元素序号],在电脑运算时,其实是数组名+元素序号,而指针就是地址,那我们就可以把数组的首元素地址赋给指针,通过指针的加减来找到我们想要的元素,这时,寻找元素的写法就变得多种多样起来
六.二级指针
我们前面说的都是一级指针,一级指针可以指向变量,那用啥可以指向一级指针呢?答案就是二级指针。
通过对二级指针的两次解引用我们就可以找到num。