🎬 乀艨ic:个人主页
⛺️七天连班终于要结束啦,大家加油哇~
⭐️一起来看看这次的博客吧~
🌳前言
大家在学习C语言的时候,想必有很多人会对指针这一块知识感到很头疼吧,本次就带大家0基础由浅到深的为大家刨析指针的知识,希望大家在看完之后都可以有所收获~
🌳指针概念
在学习指针的知识之前,我们先来思考一下什么是指针?指针到底是什么?
首先我们要知道,当我们声明了一个变量的时候,我们需要在内存上开辟空间去储存这个变量,而内存中的每个字节都有属于自己的编号,犹如我们家的门牌号一样表明地址,而指针变量就是用来存储这个地址的变量。
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址。
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
总结:指针就是地址,口语中说的指针通常指的是指针变量。
那我们就可以这样理解:
指针:
内存
指针变量:
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。
🌲指针大小
说完了指针的概念,我们来思考一下指针变量的大小,不过在说明这个问题之前,我们还需要解决另一个问题:
既然我们刚刚说了指针变量(后面就简称指针了)是存储计算机内存最小单元编号的变量,那么计算机内存的最小单元又有多大呢?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于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根地址线,那能编址多大空间,大家就可以自己计算试试啦~
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台上是8个字节!
🌲指针用法
说了那么久,那么指针到底怎么用呢?我们看一段代码:
int a = 10;
int* pa = &a;//pa是一个指针变量
printf("%d %d\n", a, *pa);
*pa = 20;
printf("%d %d\n", a, *pa);
我们来看看运行结果:
我们注意到,我并没有改变a的大小,但是在第二次打印的时候a的值却发生了改变,这是在那么回事呢?
首先,我们要认识两个运算符 & 和 * 。
1)&(取地址运算符):可以将一个变量的地址拿出来,&a就是a的地址的意思。
2)*(解引用运算符):让指针变量可以访问它所存储地址的变量的内容。
上面的变量pa就是一个指针,我们通过&获取a的地址赋值给pa,再通过*将指针解引用,用pa来访问a的值,并做到改变a的值。没错,这就是指针的用法。
🌳指针类型
上次的博客中,我们介绍了不同种类的变量类型,现在我们去思考一个问题,指针会有不同的类型吗?
首先可以很明确的说,是有的。
比如说我们写一段这样的代码:
int a = 10;
int* pa = &a;
printf("%d\n", a);
printf("%d\n", *pa);
我们上面说明过&符号其实就是把一个变量的地址拿出来,所以&a其实就是代表a的地址,我们将a的地址给予变量pa,所以我们就可以知道pa其实就是一个指针变量,那他的类型是什么呢?根据经验我们可以猜测,int*其实就是pa的变量类型。
这是存储整型变量的方法,让我们来看看存储其他变量的方法:
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 类型变量的地址。
以此类比。
🌲指针类型的意义
那么可能就有人要问了,既然说指针的大小在同样的机器下都一样,存的都是某个变量的地址,那为什么要给指针设计这么多的类型呢?
接下来我们来看一看指针的类型有什么用。
🍃指针±整数
我们看一段代码:
#include <stdio.h>
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;
}
运行结果:
注:%p的意思是打印地址。
由此我们可以得到一个结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。比如说一个字符指针加1,那么它存的地址就会加1,但如果是一个整型指针,就会加4。
🍃 指针的解引用
当然,指针类型的意义不只是这个,我们再来看一段代码:
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0;
*pi = 0;
return 0;
}
我们来看看调试过程中变量n的变化:
这是运行到*pc = 0;时的代码
这是*pi = 0;时的代码
我们可以观察到在使用*解引用的时候n变量的内存只有一个字节发生了改变。
因此我们又有了结论:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
🌳指针和数组
至此,我们大概了解了指针的一些基础知识,下面我们反过头看一眼数组。
我们再看一段代码:
#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;
}
运行结果:
可见数组名和数组首元素的地址是一样的。
我们再次得到结论:数组名表示的是数组首元素的地址
不过这有两个特殊情况:
sizeof(arr)
&arr
以上两种情况数组名就表示整个数组了,后面会详谈它的意义。
既然如此,我们便可以这么写代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素地址
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);//计算数组元素个数
for(int i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组。
这跟访问数组没什么本质区别,也就是说 arr[i] == *(arr + i) == *(p + i)。
注:以下的一条内容可能会颠覆你的认知
我可以这么说,其实从底层来看**arr[i]的实现其实就是化为(arr + i)**进行访问。那么我们便可以得到一个等式。
arr[i] == *(arr + i) == *(p + i) == *(i + p) == i[p] == i[arr]
=>arr[i] == i[arr]
如果你很震惊,我们可以验证一下:
不会出任何问题,甚至不给你报⚠警告。
当然,一般也不会这么写代码,可读性很差也不正规。
🌳结尾
本期内容先到这里,指针内容太多一次写不完啦(),大家可以期待下一次的博客,会带大家认识数组指针,指针数组,函数指针,函数指针数组等等相关的指针内容,可以点一个关注避免错过哦~
感谢观看,喜欢的话可以给一个大大的三连哦~
期待我们的下次相遇,喜欢的请点个关注再走哦!之后会持续更新更多的内容!
个人主页:传送门