本章概述
内存和地址
在讲解指针之前,我们先讲一下内存和地址,为后面的指针讲解进行铺垫。
- 内存:对于内存,我相信大家都不陌生。我们买电脑或者手机时都会关注它本身的内存大小,电脑中常见的内存大小8G/16G/32G等。内存很珍贵,容量越大价格也越贵,它的容量可没有1T这样的说法,1T这里只有硬盘才有的容量。大家要把内存和硬盘区分开,对于区分不开的人,可点击链接:内存和硬盘的区别。
- 内存的形象化理解:内存就像学校的宿舍楼一样,宿舍楼里有很多分好的房间,而且每个房间你都可以住人,或者存放东西。内存也是一样,它也是被划分了很多小的空间,这些空间就跟房间一样,它里面可存放数据。我们都知道,房间有大小,存放的东西是有限制的。内存也是一样,它里面的每个空间都是有范围的,每个空间为一个字节的容量。 总结:内存被划分为很多的单元,每个单元为一个字节的容量,内存的存储容量单位为字节。如图所示:
在这里我们讲一下数据存储的单位和大小:
1 byte = 8 bit
1 kb = 1024 bye
1 mb = 1024 kb
1 gb = 1024 mb
1 tb = 1024 gb
1 pb = 1024 tb
- 地址:我们讲过了,内存就像一栋宿舍楼,一栋宿舍楼里面有很多的房间(寝室),而且每个寝室都有门牌号。如图所示:
当我们的朋友过来找我们玩的时候,假如每个寝室没有门牌号,他就需要一个一个房间的找,只有那样做才能最终找到你。虽然那也是一种方法,但是很不高效。但当我们有了门牌号,他就可以很迅速的找到你。内存也是如此,内存被划分为很多的一个一个的小单元,每个小单元都被进行了编码,这些编码就是门牌号,就是地址,而在C语言中给起了个新名字——指针。 所以,编码== 地址 ==指针。内存通过这些门牌号(指针),对里面的空间单元进行数据的访问。 - 内存地址的理解:我们都知道,CPU(中央处理器)是数据处理的中心,跟我们的大脑一样。它从内存内读取数据,进行大量的运算(处理),然后把处理后的数据 (处理后的结果)再放回内存中。如图所示:
对内存进行数据的访问时,就要知道每个空间单元的地址。我们都知道,我们寝室门口会标注门牌号,以便被看到。但在内存中可不是这样被标注的。计算机是由很多个硬件组成的, 比如,在计算机里面有很多的电线,电子零件等。内存的地址就是由硬件设计的,它不会像门牌号那样进行标注,是由地址总线这个硬件设计好的。如图所示:
在CPU与内存之间有地址总线,数据总线和控制总线组成。地址总线就是一根一根电线,每根数据线连接一个比特位。我们知道每个比特位储存0或1,也就是说每根地址线会向所对应的比特位,发0或1,也就是说每根线有两种状态(结果),即0或1。所以,两根线就是四种状态(结果),三根线就是八种状态(结果)……。地址总线的个数由计算机平台决定,在32位计算机中就有32根线,就有2的32次方状态(结果),在64位计算机中就有64根线,就有2的64次方状态(结果)。一台电脑生产好后,它里面的内存地址就已经通过地址总线这个硬件设计好了。就跟我们弹钢琴一样,钢琴的每个键上面并没有标注 ,do,re,mi,……si这些音符,但是每个键都对应一个音,就是因为在生产钢琴的时候,每个键对应什么音就已经设计好了。当CPU要处理某个空间单元的数据是,它就会先通过控制总线,通知内存它要读取数据了,然后再通过地址总线,向内存发送要读取的空间单元的地址,然后内存就会根据地址找到对应的空间单元,再把里面的数据通过数据总线传给CPU。总结:编码 == 地址 == 指针。
指针的应用
指针在计算机科学中具有广泛的应用。以下是一些常见的应用场景:
-
内存管理:指针可用于动态分配和释放内存。通过指针,可以在运行时分配和管理内存,使程序更加灵活和高效。
-
数据结构:指针是实现各种数据结构(如链表、树、图等)的基本工具。通过指针,可以在内存中动态创建和操作数据结构,实现高效的数据访问和操作。
-
函数指针:函数指针是指向函数的指针变量,可用于在运行时动态选择和调用不同的函数。函数指针广泛用于实现回调机制、事件处理和动态加载等场景。
-
数组访问:通过指针,可以直接访问和操作数组元素,提高数组访问的效率。
-
字符串处理:指针可用于字符串的操作,如拷贝、连接、比较等。通过指针,可以对字符串进行高效的处理和操作。
-
系统级编程:在操作系统和底层编程中,指针是必不可少的工具。通过指针,可以直接访问硬件、进行内存映射、优化性能等。
-
动态数据结构:指针可用于实现动态数据结构,如动态栈、堆等。通过指针,可以在运行时分配和释放动态数据结构,提高程序的灵活性和效率
指针变量和地址
在了解完什么是内存,什么是指针(地址)后,我们回归C语言中。在以前的文章中,我们有讲过——变量的创建本质就是向内存申请空间。既然申请了空间,那就肯定有地址,所以怎样获取我们的变量地址呢?
- &取地址操作符: 这个符号咱们前面也见过了,格式 :& 变量。进行代码展示:
创建一个变量,并用 & 取出变量的地址。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
printf("%p\n", &a);
return 0;
}
结果运行图:
- 指针变量:前面,咱们讲过数据类型,比如,
int , char ……
。指针变量也是个变量,是专门用来存放指针(地址)的变量。竟然是变量,那就有数据类型,那么指针变量的数据类型是什么样的呢? 就是在数据类型后面加个*
比如,int * , char *,……
。进行代码展示:
// 格式 : 数据类型 * 变量名 ;
int a = 10 ;
int * p = &a ; //p里面存放的就是 a 的地址
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%p\n",p );
return 0;
}
结果运算图:
有个东西忘了告诉大家了——对于地址的打印用占位符%p。从结果运行图中能看出来,指针存放的就是变量的地址。同理,比如,char *……
。大家可以自行试试。
- 指针变量的大小:前面,我们有讲过,数据类型是有大小的,也就是因为有了大小,才会申请不同的空间。对于空间大小的测量,我们用
sizeof
测量(前面有讲过),进行代码展示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(float*));
printf("%d\n", sizeof(double*));
return 0;
}
结果运行图:
从结果运行图来看,不知道你有没有感到惊讶。不同的指针变量,它们的空间大小竟然相同!!!是不是感到很惊讶——它们占有的空间相同。我们先说结论:指针变量的大小与它的类型无关,只与所在的计算机平台有关,即,32位平台上占有4个字节,64位平台上占有8个字节。 接下来,我们进行解释:前面,我们讲过了地址总线,它的数据线数有计算机平台决定,它的每一根线都对应一个bit位,一个字节有8个bit,所以在32位的平台下就对应4个字节,在64位平台下对应8个字节。肯定有人有疑问——既然都一样,那为什么还要规定那么多指针数据类型呢?规定一个不就行了吗?。对于这个问题的解答,我们就要知道指针变量的真正含义,我们接下来讲。
- 指针变量的解读:我们以整形指针变量为例子,比如,
int a = 10 ;int * p = &a
。*
号就说明了p
是个指针变量,int
就说明了p
所指变量a
里面的数据类型是个整形,指针变量存放的是地址,我们通过地址找到变量a里面的数据,就可以对其内容进行更改(就像根据门牌号找到了对应的房间,我们可拿或放入东西)。总结:指针变量就是通过地址找到对应的变量数据。如图所示:
接下来,我们通过代码进行展示:
// 创建一个变量,不通过直接更改内容,用指针进行更改。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
printf("更改前a=%d\n", a);
int* p = &a;
*p = 30;
printf("更改后a=%d\n", a);
return 0;
}
结果运行图:
- 解引用操作符:
*
就是解引用操作符,p
里面存放的是地址,我们通过*
这个操作符对p
里面的地址解读(通过 *和p里面的地址就找到了a里面的数据,就可以进行更改)。也就是说,* p==10
。进行代码展示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("a=%d\n", *p);
return 0;
}
结果运行图:
补充个知识点,地址的表示是十六进制,对于代码的研究具有可观性(后面就会体会到)。地址的十六位进制表示,总共有8位活32位(由计算机平台决定),比如,0X00AFF9F4。因为在32位平台上,总共有32根线,一个十六进制位,可由4个bit位表示,所以只要8个十六进制位就OK了,依此推理64位平台。读到这里,我想有些人肯定有疑惑——对a
的值进行更改,我们完全可以直接写成a = 30
;为什么还费事的要用指针呢?其实,对于某个变量进行值的更改,通过直接更改是一种手段,通过指针进行更改是另一种手段。这两东西就相当于我们的工具,工具多了,我们解决问题的方法就多了。其实,通过指针进行变量的更改是很方便的,这个特征我们以后就会感受到的。
指针变量类型的意义
前面,我们已经讲过了,指针变量的大小与指针变量类型无关,只与计算机平台有关。这里我们就来讲一下,为什么要有那么多指针变量。这里我们直接说结论:指针变量的类型决定了能访问的空间大小。比如,int *
能访问4
个字节空间,char *
能访问1
个字节空间。进行代码展示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* p = &a;
*p = 0;
printf("%d\n", a);
return 0;
}
结果运行图:
- 指针变量+ -整数:指针变量是能+ -整数的,想不到吧。我们直接上代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
char* pc = (char*)&a;
printf(" p=%p\n", p);
printf("p+1=%p\n", p+1);
printf(" pc=%p\n", pc);
printf("pc+1=%p\n", pc + 1);
return 0;
}
结果运行图:
从结果运行图中,我们可以看出来,int *
的指针变量对应的p
,当p+1时,地址就加了4个字节,char *的指针变量对应的pc,当pc+1时,地址就加了1个字节。所以不同的指针变量类型,对应着指针变量加减时,在地址上加减多少个字节。指针变量不只是可以加减1,可以加减任意数字。地址加多少就等于数字乘以对应的变量类型对应的字节。
const修饰指针-1
const
的单词意思:常量,常属性。在普通常量中(除了指针变量):被const修饰的变量是无法进行更改的,被const修饰的变量被称为常变量。在C语言中,被const修饰的变量本质还是变量,只不过语法上规定它不可以进行更改,就跟常量似的。但在C++中,被const修饰的变量,它的本质就被改变了,就变成了常量。进行代码展示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
a = 30;
printf("%d\n", a);
return 0;
}
结果运行图:
代码展示2:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
const int a = 10;
a = 30;
printf("%d\n", a);
return 0;
}
结果运行图:
在C语言中,const
修饰的普通变量的特性是可以用变长数组来验证的。进行展示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
int arr[a];
return 0;
}
结果运行图:
代码展示2;
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
const int a = 10;
int arr[a];
return 0;
}
结果运行图:
void * 指针变量
前面,我们讲过了,每个数据的变量类型都有相对应的指针变量类型。我们自己写代码,我们大概率不会乱用指针变量,或者我们自己清楚自己用了什么指针变量。但当我们接过来别人的代码后,对于别人所使用的指针变量类型可能不太清楚,这个时候就可以用 void *
这个指针变量来接收。void 就是空,无的意思
,对于什么样的指针变量类型都能接收。进行代码展示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
char* d = "abbb";
void* p = &a;
void* b = &d;
return 0;
}
结果运行图:
大家要注意下,void *
所修饰的指针变量是不能直接被 *
操作的。因为,void *
什么指针变量类型都接收,所以它不能确定你要哪个,必须进行类型转换才能进行*
。进行代码展示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
void* p = &a;
*p = 40;
printf("%d\n", a);
return 0;
}
结果运行图:
代码展示2:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
void* p = &a;
*(int*)p = 40;
printf("%d\n", a);
return 0;
}
结果运行图:
每章的彩蛋时刻!!!
https://www.bilibili.com/video/BV1LU4y1S7Ni/?spm_id_from=333.337.search-card.all.click&vd_source=7d0d6d43e38f977d947fffdf92c1dfad
每章一句:“在任何一个你没有察觉的时刻,包括现在,通过行动去改变命运的机会,一直都在。”
感谢你能看到这里,点赞+关注+收藏是对我的最大鼓励,咱们下期见!!!