赞
踩
提示:编程的本质其实就是操控数据,而数据存放在内存中。因此,如果能更好地理解内存的模型,以及 C 如何管理内存,那么就引入了一个新的概念 “指针” ,大家都在书中看到过「指针存储的是变量的内存地址」这句话,但是什么是指针呢,下面我们开始直面了解指针
提示:以下是本篇文章正文内容,下面案例可供参考
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
指针是个变量,存放内存单元的地址(编号)。说的形象一点就是指针就相当于一个门牌号 里面存的的是住户的编号
上面说指针是个变量 ,那么变量是什么。
有了内存,接下来我们需要考虑,int、double 这些变量是如何存储在 0、1 单元格的。
在 C 语言中我们会这样定义变量:
int a = 999;
char c = 'c';
当你写下一个变量定义的时候,实际上是向内存申请了一块空间来存放你的变量。我们都知道 int 类型占 4 个字节,并且在计算机中数字都是用补码表示的。
999换算成补码就是:0000 0011 1110 0111
这里有 4 个byte,所以需要四个单元格来存储:
当然,这样就引出了大端和小端。
像上面这种将高位字节放在内存低地址的方式叫做大端
反之,将低位字节放在内存低地址的方式就叫做小端:
上面只说明了 int 型的变量如何存储在内存,而 float、char 等类型实际上也是一样的,都需要先转换为补码。
对于多字节的变量类型,还需要按照大端或者小端的格式,依次将字节写入到内存单元。
上面我说,定义一个变量实际就是向计算机申请了一块内存来存放。
那如果我们要想知道变量到底放在哪了呢?
可以通过运算符&来取得变量实际的地址,这个值就是变量所占内存块的起始地址。
printf("%x", &b);
打印出来大概会是像这样的一串数字:0x7ffcad3b8f3c。
上面说,我们可以通过&符号获取变量的内存地址,那获取之后如何来表示这是一个地址,而不是一个普通的值呢?
也就是在 C 语言中如何表示地址这个概念呢?
对,就是指针,你可以这样:
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//将a的地址存放在p变量中,p就是一个之指针变量。
return 0;
}
大家都看到了 用int类型定义了一个指针p。
所以说到底,指针他的本质也就是一个变量,只不过是用来存放地址的变量。(存放在指针中的值都被当成地址处理)
就相当于刻在门牌上的号码,只能被当作门牌号处理了。
上面的问题,就是为了引出指针解引用的。
p中存储的是a变量的内存地址,那如何通过地址去获取a的值呢?
这个操作就叫做解引用,在 C 语言中通过运算符 *就可以拿到一个指针所指地址的内容了。
比如*p就能获得a的值。
形象的说
就相当于p里面是住户的门牌号(地址) *p就是你通过这个门牌号地址找到了里面的住户(内容)。
指针的类型 我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢? 准确的说:有的。
上面不是说过 指针是一个变量 变量有类型 那么指针肯定也有类型。
我们给指针变量相应的类型。
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这里可以看到,指针的定义方式是: type + * 。
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放int 类型变量的地址。
我们先看一段代码
可以看出 &n pc pi所得到的地址相同
&n 本来就是取n 的地址 而pc pi 是指针 保存了n的地址 有人会疑问说不是一个是char类型 一个是int类型 怎么会一样呢?
那么pc+1 和 pi+1 为什么会不同 这就和指针的类型有关了 pc是char*
类型, +1 相当于加一个char类型字节大小 , 为一个字节, 而pi是int*类型 ,+1相当于加一个整形类型字节大小, 为4个字节。所以会有所不同 , 这也就是为什么指针会有不同的类型了。
指针的类型决定了指针向前或向后走一步有多大。
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
没有初始化造成了他的随机 不确定性 成了野指针。
#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 main()
{
vector<int> v={ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it); //迭代器失效了 因为将2删除后 就不能在对其进行迭代
++it;
}
return 0;
}
原因就是指针指向那个空间时 将其删除后,没有给指针重新赋值,导致指针指向的位置又变成了未知的 随机的 成为野指针 从而造成程序崩溃。这个后边动态内存开辟在详细说这种情况。
1.指针初始化
2.小心指针越界
3.指针所指空间释放了即设置NULL
4.指针使用之前检查其有效性 (断言)
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
结果如下
我们会发现结果相同 我们就可以得出结论
数组名表示的是数组首元素的地址。
这样的话我们就可以这样写代码
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p=arr;//p存放的是数组首元素的地址
数组是 C 自带的基本数据结构,彻底理解数组及其用法是开发高效应用程序的基础。
int array[10] = {10, 9, 8, 7};
printf("%d\n", *array); // 输出 10
printf("%d\n", array[0]); // 输出 10
printf("%d\n", array[1]); // 输出 9
printf("%d\n", *(array+1)); // 输出 9
int *p = array;
printf("%d\n", *p); // 输出 10
printf("%d\n", p[0]); // 输出 10
printf("%d\n", p[1]); // 输出 9
printf("%d\n", *(p+1)); // 输出 9
第 0 个元素的地址称为数组的首地址,数组名实际就是指向数组首地址,
当我们通过array[1]或者*(array + 1)去访问数组元素的时候。
实际上可以看做 address[offset],address为起始地址,offset为偏移量,
但是注意这里的偏移量offset不是直接和 address相加,而是要乘以数组类型所占字节数。
也就是: address + sizeof(int) * offset 就是和指针的类型大小有关了。
特别要注意是:
尽管数组名字有时候可以当做指针来用,但数组的名字不是指针。
比如
printf("%u", sizeof(array));
printf("%u", sizeof(p));
结果如下
一个是数组的大小 一个是指针的大小
第一个输出 40,因为 array包含有 10 个int类型的元素,
而第二个在 32 位机器上输出 4,也就是指针的长度。
另外说一下 指针就像门牌 它是一个标准的 在32位机器上永远就是 4
不要认为二维数组在内存中就是按行、列这样二维存储的,实际上,它在存储上和一维数组没有本质区别,举个例子:
int array[3][3] = {{1, 2,3}, {4, 5,6},{7, 8, 9}};
array[1][2] = 6;
或许你以为在内存中 array数组会像一个二维矩阵:
1 2 3
4 5 6
7 8 9
其实是这个样子哒
1 2 3 4 5 6 7 8 9
和一维数组没有什么区别,都是一维线性排列。
去访问的时候,编译器会怎么去计算我们真正所访问元素的地址呢?
假设数组定义是这样的:
int array[n][m] 定义几行几列
访问: array[a][b] 比如要找array[1][2]
那么被访问元素地址的计算方式就是: array + (m * a + b)
这个就是二维数组在内存中的本质,其实和一维数组是一样的,只是语法将其包装成一个二维的样子。
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa
int b = 20;
*ppa = &b;//等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作:*pa,那找到的就是a
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
指针数组是 “指针” 还是“数组”
答案 当然是数组
下面用图形的方式看一下 就会一目了然。
int arr1[5];
char arr2[5]
int *arr3[5]
arr3是一个数组,有五个元素,每个元素是一个整形指针
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。