初阶指针
- 指针是什么?
指针是什么? 指针理解的2个要点: 1. 指针是内存中一个最小单元的编号,也就是地址 2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
总结:指针就是地址,口语中说的指针通常指的是指针变量。
如果我们电脑是32位虚拟地址空间/64位虚拟地址空间
CPU-32位地址-地址线传输->内存/ CPU-32位地址-地址线传输->内存
Int a=10;
Int *p=&a;
则会在内存上开辟四个字节大小的空间(int是整型占四个字节)
四个内存都有地址,但是指针只取第一个地址,详细解释请看之前第三期指针入门里面有。并且将这个地址存起来,那么这时的p占多大,如果是32位虚拟地址空间的话,一次产生32位也就是32bit(4个字节4byte),两个十六进制位是一个字节,假设申请的内存第一个的地址是0x0012ff40,pa在内存条上开辟四个字节的空间,然后只存第一个地址0x0012ff40,这个地址可以帮助我们快速的访问内存单元,这个专门用来存放地址的变量就被我们称为指针变量
总结: 指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。 那这里的问题是:
一个小的单元到底是多大?(1个字节)
如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,理论上是4GBx2^32大小的内存
但是由于内存造价太高,我们日常生活中的电脑大多数还是8g/16g
这里我们就明白: 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址
总结: 指针是用来存放地址的,地址是唯一标示一块地址空间的。 指针的大小在32位平台是4个字节,在64位平台是8个字节。
- 指针和指针类型
这里我们发现无论是不是int型,指针的空间都是4个字节,因为上面已经讲到,指针的空间只跟电脑的虚拟地址空间有关,这里我们是32位虚拟内存空间,所以指针开辟的是32bite也就是4个字节,如果是64位这里就是8个字节
这时候我们就引出一个问题既然,无论类型是什么指针都开辟一样大小的内存,那么为什么还要区分指针类型,没有一个万能的指针吗?
首先肯定的说是没有的,下面我们举例验证
int main()
{
int a = 0x11223344;//这里按照16进制来写
int* p = &a;
*p = 0;
return 0;
}
因为我们书写a的时候采用了16进制,这里观察a的值的时候,也采取16进制
在内存观察这里&a观察
这里也是11223344,要反着看
当按f10调试的时候,最后调试完*p=0的时候我们发现
无论是a还是&a还是*p都变成了0,之后我们来看char类型的
int main()
{
int a = 0x11223344;//这里按照16进制来写
//int* p = &a;
//*p = 0;
char* p = &a;
*p = 0;
return 0;
}
我们将代码修改成这样,看看这时的char型指针能否存的下这个a
这次只改变了一个字节。
这时候我们发现指针类型还是有区别的,指针类型决定了,指针再被解引用的时候访问的权限。
整型指针解引用访问了4个字节,
字符指针解引用访问1个字节
……….
这里的char是只能访问一个字节的权限,并不是说其他数据丢失了
我们再举一个例子
这里可以看出pa,pc存的是同一个值,当+1操作的时候,pa增加了4,pc增加了1
指针类型决定了指针向前或者向后走一步走多大距离。
Int*+1ó+1*sizeof(int)==+4
char*+1ó+1*sizeof(char)==+1
Char :*p对内存访问一次,我们从内存上往后可以访问一个字节,这时p+1我们往后跳一个字节
Int:*p在访问的时候一次访问4个字节的大小,这时p+1就跳四个字节
所以指针类型的意义就在这。
这里区分一个概念,p+1,p不改变,p++,发生改变
假设p=0x0012ff40
P+1->(int)0x0012ff44/->(char)0x0012ff41
P++
0x0012ff44;
Sizeof(long)>=sizeof(int)\
32位环境下:sizeof(long)-4
64位环境下:sizeof(long)-8
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
- 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
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;
}
- 指针指向的空间释放
int *test()
{
int a = 10;
printf("%d\n", a);
return &a;
}
int main()
{
int*p=test();
*p = 100;
return 0;
}
这里看似程序没什么问题,其实逻辑出现了问题,调用了test函数临时创建了变量a=10,这时*p存入了10的地址,但是出了函数这个10就会被销毁,这时再让p去访问这时就成了野指针;
3.2 如何规避野指针
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
int main()
{
int* p = NULL;
//....
int a = 10;
p = &a;
if (p != NULL)
{
*p = 20;
}
return 0;
}
- 指针运算
指针+- 整数
指针-指针
指针的关系运算
4.1 指针+-整数
#define N_VALUES 5
float values[N_VALUES];
float* vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
指针是地址,可以进行大小比较
指针既可以加减运算也可以进行大小比较
4.2 指针-指针
前提:指针-指针是,两个指针必须指向同一块空间
指针-指针的绝对值 得到的是指针和指针之间元素的个数
Char ch【5】;
Int arr【6】;
&arr【4】-&arr【3】;//err
下面是实际应用案例
4.3 指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
数组是一块连续的空间
指针是存放地址的变量
详细部分参考之前数组内篇文章,内里有详细的解答
二级指针
int main()
{
int a = 10;
int* pa = &a;//pas是指针变量(一级指针)
int** ppa = &pa;
return 0;
}
对于二级指针的运算有:
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
int b = 20;
*ppa = &b;//等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a
**ppa = 30; //等价于*pa = 30; //等价于a = 30;
同理三级指针也是如此
7.指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组
int arr[5];//存放整型的数组
char ch[6];//存放字符的数组
int* arr2[5];//指针数组,数组的每个元素都是一个指针
这里的arr就是我们的指针数组;
这里也可以换一种方法打印