敲上瘾-CSDN博客 指针进阶(深入理解)-CSDN博客 指针强化练习(详解)-CSDN博客
目录
1.内存与地址
认识指针之前我们得先了解一下内存,内存划分为一个个的单元,一个单元是一个字节
内存单位以及转化:
计算机中有很多硬件,硬件之间相互协调工作,那么两块硬件之间是如何产生联系的呢?
答案很简单,就是用一些线来连接,为了更好的了解指针,我们先需要了解一下CPU与内存的关系。
CPU在运算的时候的数据是需要从内存中读取的,那么CPU是如何从内存中准确的找到数据呢?
其实CPU和内存之间连接着一些地址线,我们常说的32位机器指的是32根地址线,64位机器指的就是64根地址线,一根地直线有两种状态,把高电平的状态用1表示,低电平状态用0表示,那么32根地址线就可以表示2^32种状态,每种状态都可以对应找到内存上的一个字节空间。
而指针变量就是来存储地址线的状态的,也就是存储地址,每个地址对应一个内存单元,所以我们说指针就是地址,也就是内存单元。
2.指针变量和地址
指针变量是一个变量,不要把它看得有多特别,应该把它和整型变量,字符型变量,结构体变量等同等看待。
指针变量同样需要向内存申请空间来存某一块地址,整型需要申请4个字节空间,字符型需要申请1个字节空间,那么指针类型要申请多少空间呢?这个很容易理解,上面讲了指针存的是地址,也就是地址线的状态。每一根地址线的状态需要用一个比特位来存0或1。32位的机器就需要32个比特位(也就是4个字节) 来存地址。64位的机器需要用64个比特位(也就是8个字节)来存地址。所以只要是指针变量,他申请的内存只可能有两种情况 4(32位机器)或 8(64位机器)。
所以int*,char*,int**,char**等等他们sizeof的结果都是一样4或8。
3.指针的运算
大家也许会想,既然用int*,char*等等它们存的都是地址申请内存空间大小都是相同的,为什么不把它们总的用一个符号来表示?表面上他们没有什么区别,但他们在解引用和指针运算的时候,可大有不同?
指针变量是只能存一个字节的地址。比如int类型占4个字节(也就是有四个地址),指针变量只会存他第1个字节的地址。但在程序运行时识别出int*后会依次连续读取4个字节。
那么程序识别出b为int*类型后会连续读取4个字节,识别出u为char*类型只会读取1个字节所以*p=1,*u=0(当然这里还与大小端储存有关)。
指针的加法运算
a+1跳过4个字节
b+1跳过1个字节
c+1跳过2个字节
如下int a[5]的数组。int* p=a。
下面来用不同方式读取一个数组来加深理解。
#include<stdio.h>
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,100 };
int* p = a;//这里数组名就表示第一个元素的地址
char* u = (char*)a;//(而&a表示整个数组的地址具体区别与应用在指针进阶会详细讲到)
for (int i = 0; i < 10; i++)//for循环,while循环用法和if同理,
printf("%d ", a[i]);//只要内部只有一条语句就可以不用加括号。
for (int i = 0; i < 10; i++)
printf("%d ", *(p + i));//通过用指针的加法运算来改变起始字节。
for (int i = 0; i < 10; i++)
printf("%d ", p[i]);//p和a是等价的,因为a[i]在运行时会被解释为*(a+i)。
for (int i = 0; i < 10; i++)
printf("%d ", *(a + i));//p和a是等价的,数组名a也相当于一个指针。
for (int i = 0; i < 10; i++)
printf("%d ", i[a]);//a[i]=*(a+i)=*(i+a)=i[a]。
for (int i = 0; i < 10; i++)
printf("%d ", *(u + 4*i));//因为char*的指针加一只能跳过一个字节,而a[10]中一个元素占4个字节
return 0;//(用char*类型的u来输出a[10],不一定可行这里还与大小端储存和数据的大小有关)
}
int a[10] = { 1,2,3,4,5,6,7,8,9,100 };
int* p = a;//这里数组名就表示第一个元素的地址(即&a[0])
char* u = (char*)a;//(&a表示整个数组的地址具体区别与应用在指针进阶会详细讲到)
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);//for循环,while循环用法和if同理,只要内部只有一条语句就不用加括号也合法。
for (int i = 0; i < 10; i++)
printf("%d ", *(p + i));//通过用指针的加法运算来改变起始字节。
for (int i = 0; i < 10; i++)
printf("%d ", p[i]);//p和a是等价的,因为a[i]在运行时会被解释为*(a+i)。
for (int i = 0; i < 10; i++)
printf("%d ", *(a + i));//p和a是等价的,数组名a也相当于一个指针。
for (int i = 0; i < 10; i++)
printf("%d ", i[a]);//a[i]=*(a+i)=*(i+a)=i[a]。
for (int i = 0; i < 10; i++)
printf("%d ", *(u + 4*i));//因为char*的指针加一只能跳过一个字节,而a[10]中一个元素占4个字节,所以这里用*(u+4*i)来实现与*(p+i)相同效果(用char*类型的u来输出a[10],不一定可行这里还与大小端储存和数据的大小有关)
指针减法,指针减去一个数,与加法同理,不再赘述。
指针减指针(相同类型)得到的是两个地址之间相差的元素个数,注意:不是两个地址之间相差的字节个数。
指针加指针没有这种运算,有也是豪无意义的。
下面来给大家一个思考题,在文章末尾我会给出答案。
4.void*的理解
void*可以理解为无具体类型的指针。void*类型的指针是不能进行解引用和指针运算的,那么它有什么用呢?
void*常被用作函数形参类型,比如qsort函数(点击学习qsort函数)这是因为void*可以接受任意类型的指针。写函数时往往不知道用户要传什么类型的数据。
5.const的修饰
const是用来修饰变量的,被修饰的变量不可改变。否则会报错。例如:
但是用指针是可以改的如下:
const修饰指针变量
const是为了保护一些重要的数据防止被误改。
6.野指针
野指针是指向未知内存的指针,比如未初始化的指针,越界访问的指针,使用野指针危险是因为它指向的内存是未知的可能会不小心把一些重要的数据给改变。
7.assert断言
assert的用法是把一个条件放入assert的中。当实际与条件不符时,程序就会停止运行而且报错。
比如:
上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
assert的断言的好处是方便查找错误或避免一些潜在的风险, 缺点是增加了额外的查找时间降低了运行效率使用它需要包含头文件<assert.h>。等程序运行符合预期的时候,我们可以在assert前面加一个#define NDEBUG来关闭它。注意:assert断言在Release版本是不执行,因为assert断言在Release版本下被优化掉,使程序执行效率高。
指针的运算 答案:B