目录
前言
一. 什么是指针?
二. 指针和指针类型
2.1 指针与整数的加减
2.2 指针的解引用
三. 野指针
3.1 野指针的成因
3.2 如何规避野指针
四. 指针运算
4.1 指针+-整数
4.2 指针-指针
4.3 指针的关系运算
五. 指针和数组
六. 二级指针
七. 指针数组
小结
Hello,各位小伙伴们,我们在前面讲了三期C语言了,分别是函数,操作符和数组,这三期如果是对于C语言有一个了解,那从这期开始,我们讲的内容就要逐步走入C语言的核心了,下面,我们就一起进入今天的主题——指针。
很多小伙伴在学习C语言之前,对于指针印象大部分应该就是指针?那不是钟上面的东西吗?还分时针,分针还有秒针呢?其实在C语言中,指针的意义是:指针是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。指针描述了数据在内存中的位置,标示了一个占据存储空间的实体,在这一段空间起始位置的相对距离值。在C和C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。通俗来讲,我们每在编译器上创建一个变量就会在内存中生成一个地址,而这个地址就是每个变量的家,这个地址也被称之为指针。
其中对于理解指针有两个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常所指的就是指针变量。
每一个存储单元就是一个字节(就是上图的一个格子),在操作符那一篇中我们讲到了取地址符(&),这个取地址符,就是找到变量的存储位置,我们将这个存储地址再存入一个变量中,这个变量就称为指针变量。
#include
int main() { int a=10; int *p=&a;/*这个*p中存放的就是a所在的地址,其中a变量占用四个字节,因此将存放a的第一个字节的地址存放到变量p中*/ return 0; } 总结:指针变量就是用来存放地址的。
但是这里还有两个问题:
一个小的单元到底是多大?
(1个字节) 如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电 平(低电压)就是(1或者0); 那么32根地址线产生的地址就会是:
00000000000000000000000000000001
00000000000000000000000000000010
00000000000000000000000000000011
00000000000000000000000000000100
……
10000000000000000000000000000000
10000000000000000000000000000001
10000000000000000000000000000010
……
11111111111111111111111111111101
11111111111111111111111111111110
11111111111111111111111111111111
这一共就有2的32次方个地址。
每个地址标识一个字节,2^32byte除以1024再除以1024再除以1024得到4个G的空间来进行编址。同理,64位机器,就会给出64根地址线,那就可以编址很大很大的一块空间了。
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
那上面说了指针在不同平台的大小是不一样的,那在同一个平台上,我们为什么会有不同的指针类型呢?它们又有什么样子的区别呢?
#include
int main() { int a=10; char b="12"; float c=3.0; int *pa=&a; char *pb=&b; float *pc=&c; } 我们可以看到指针的定义方式是类型名+“*”,我们发现int类型的指针就是int*,char类型的指针就是char*,float类型的指针类型就是float*,那我们需要指针类型的意义是什么?反正都是只有4个字节(32位机器中),不如创建一个tmp*作为统一的指针类型,免得那么麻烦,那指针类型的意义到底是什么?请往下看。
#include
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);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
通过实践我们发现int*类型一次可以访问4个字节,而char*类型一次就只能访问1个字节的大小,这便是我们不同类型的指针变量的区别,可以根据自己的不同需求来使用不同的指针变量,其中short*类型一次可以访问两个字节。
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
#include
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
我们发现在内存窗口里面*pc只改变了最外面的44,*pi却改变了所有数字,这里也可以看出不同类型指针的作用。
总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
野指针的意思指针所指向的位置是未知的。
1. 指针未初始化
我们在定义一个变量的时候可能经常为给变量定义,但是对于指针来说就一定不能不初始化,这样的指针就会成为野指针。例如:
#include
int main()
{
int *p;
*p=1;//这个指针就是野指针,因为我们并未初始化,我们不知道这个指针存放在哪里,原本存放的又是什么
return 0;
}
2. 指针的越界访问
#include
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*p = i;
p++;
}
return 0;
}
这个数组我们只定义了10个元素,而指针却访问了12个元素,而我们并不知道第十一个和第十二个的指针指向哪里?所以这个也会出现野指针。
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
#include
int main() { int *p = NULL; //.... int a = 10; p = &a; if(p != NULL) { *p = 20; } return 0; }
指针加减整数实际上就是指针之间的关系运算,例如:
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
指针与指针相减,可能有很多小伙伴就懵了,指针和指针不就是两个地址,两个地址相减又有什么用呢?其实不然,例如:
#include
int main()
{
int arr[10]={0};
printf("%d",&arr[9]-&arr[0]);
printf("%d",&arr[0]-&arr[9]);
return 0;
}
大家猜猜这个代码会打印出来什么呢?有人可能会猜40,40,也有人会说10,10,但是答案是9和-9,知道答案的话,相信大家都可以看出来,指针与指针相减得到的是两个指针地址之间有几个地址。
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
这两个代码表达的意思都是一样的,不过一个将条件放置for里面,一个放在循环体里面,虽然两个for循环表达的意思一样,并且在绝大部分的编译器上是可以顺利完成任务的,但是我们应该避免使用第二种写法,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
大家先看这个例子,我们发现打印数组名和打印数组首元素的地址是同一个位置,可见数组名和数组首元素的地址是一样的。 结论:数组名表示的是数组首元素的地址。(2种情况除外,数组章节讲解了) 那么这样写代码是可行的:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;//p存放的是数组首元素的地址
既然可以把数组名当成地址存放到一个指针中,我们就可以通过首元素的地址从而来访问整个数组的指针,如下:
其中p+i实质上就是访问arr数组中下标为i的元素地址。
我们创建的变量存放到指针当中,指针变量又应该存放到哪里呢?指针变量一定也有对应的指针来存放,这个存放指针变量的指针就称之为二级指针。
#include
int main() { int a = 10; int* pa = &a; int** ppa = &pa; printf("%p\n", pa); printf("%p\n", ppa); return 0; } 我们对于二级指针的运算有:
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
指针数组,和数组指针,到底谁是谁,指针数组是指针又或是数组?答案显而易见肯定是数组, 我们在数组那章学过不同类型的数组,有int类型,char类型,float类型等等,而指针数组就是指针类型的数组。
就例如:int *arr[5];
这个数组代表的是什么意思?就是这个数组中有5个整型的指针。
#include
int main() { int a = 1, b = 2, c = 3, d = 4, e = 5; int* pa = &a; int* pb = &b; int* pc = &c; int* pd = &d; int* pe = &e; int* arr[5] = { pa,pb,pc,pd,pe }; for (int i = 0; i < 5; i++) { printf("%p\n", arr[i]); } return 0; }
本章就C语言中的指针进行了初略的讲解,最近临近期末,更新属实很随意,指针对于C语言的重要性不言而喻,后面我们还会对于指针进行详细的讲解,并且想更加深入的了解指针,小编推荐大家可以去看《C和指针》,书上的知识很全面,适合有基础的同学去观看,小编自己也在看这本书,大家如果看的满意,给个三连吧!