🌈write in front :
🔍个人主页 : 😊@啊森要自信的主页
✨ 作者寄语 🌈: 小菜鸟的力量不在于它的体型,而在于它内心的勇气和无限的潜能,只要你有决心,就没有什么事情是不可能的。
欢迎大家关注🔍点赞👍收藏⭐️留言📝>希望看完我的文章对你有小小的帮助,如有错误,可以指出,让我们一起探讨学习交流,一起加油鸭。
文章目录
- 开端
- 一、内存和地址
- 1.1 内存
- 1.2 怎么理解编址呢?
- 二、 指针变量和地址
- 2.1 取地址操作符(&)
- 2.2指针变量和解引⽤操作符(*)
- 2.2.1 如何拆解指针类型
- 2.2.2 解引⽤操作符
- 2.3 指针变量的⼤⼩
- 三、 指针变量类型的意义
- 3.1 指针的解引⽤
- 3.2 指针+ - 整数
- 3.3 void* 指针
开端
C语言中的指针😃是一种特殊的变量,它存储了一个内存地址
,该地址指向另一个变量的位置。指针允许程序直接访问和操作内存中的数据,而不需要将数据复制到另一个位置
。
指针在C语言中具有重要的作用,它可以用于动态内存分配、数组和字符串操作、函数传递参数等方面。通过指针,程序可以更灵活地处理内存中的数据,提高了程序的效率和性能。
看到这里,你可能会想到指针竟然有这么多的用处,但是我都不会呀?接下来博主带你一起解开指针的面纱,体会不一样的指针!
一、内存和地址
1.1 内存
在学习内存和地址之前,我们想想这个:
当我们早八要去教室上课时,我们冲到教学楼,我们怎么找到我们上课的教室呢?像教室有这么多个,我们不可能一个一个的找,不然这样很容易迟到,同样效率也就低了,但是如果我们给每个教室门口上个牌子,然后编上号:一栋教学楼—>
- 一楼:101,102,103…
- 二楼:201,202,203…
- …
有了这个教室门牌号,你就可以立马跑上去,快速找到,并签到,不会迟到。
接下来,我们把上面的案例对比我们的计算中,又会怎么样呢?
CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,买电脑的,电脑上的内存8G/16G/32G等,这些内存空间是怎么高效的管理我们的数据的呢?
在内存中,内存划分为一个一个内存单元,每个内存单元的大小取1个字节一个字节有多大呢?
计算机中常⻅的单位(补充):
⼀个⽐特位(bit)可以存储⼀个2进制的位1或者0
bit - ⽐特位
byte - 字节
KB - 千字节
MB -兆字节
GB - 千兆字节
TB - 千千兆字节
PB - 拍字节
- 1byte = 8bit
- 1KB = 1024byte
- 1MB = 1024KB
- 1GB = 1024MB
- 1TB = 1024GB
- 1PB = 1024TB
每个内存单元也都有⼀个编号(这个编号就相当于教室的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到这个内存空间。
生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫:指针。
所以我们可以理解为:
内存单元的编号 ==地址
== 指针
1.2 怎么理解编址呢?
CPU访问内存中的某个字节空间,必须知道这个
字节空间在内存的什么位置,而因为内存中字节
很多,所以需要给内存进行编址(就如同教室很
多,需要给教室编号一样)。
小知识来了:计算机中的编址,并不是把每个字节的地址记录下来,⽽是通过硬件设计完成的。也就是说计算中的编址,不是把整个地址编号记录下来,而是制造商已经在硬件层⾯上设计好了。
计算机内部有许多硬件单元,这些单元需要相互协作。协作的意思是它们至少要能够进行数据传输。但是,硬件单元之间是相互独立的,那么它们如何进行通信呢?
答案很简单,通过连接线进行通信。
CPU想读取内存中某一个数据中时,控制总线发出一个信号,CPU通过地址总线把内存中的这个位置找到,然后再通过数据总线传给CPU.
CPU和内存之间也需要进行大量的数据交互,因此它们必须通过连接线进行连接。然而,今天我们要关注的是一组连接线,称为地址总线。
学到这里,同学们可能有个疑问,地址总线有多少根,怎么构建联系的呢?32位机器有32根地址总线,64位机器有64根地址总线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器。
二、 指针变量和地址
2.1 取地址操作符(&)
当我们理解了内存和地址的关系,我们再回到C语⾔,在C语⾔中创建变量其实有两种含义:
#include <stdio.h>
int main()
{
int a = 66;内层含义
return 0;
}
&a取出的是类型为a所占4个字节中地址较小的地址。我们知道a的地址,就能推出他剩下的三个地址了,就可以访问到他4个字节的数据。
16进制转换为2进制:
怎么观测到a的地址呢?
按F10调试起来,打开窗口找到内存窗口,4个点击哪一个都可以。
输入&取地址操作符
(&a)
就可以找到对应的地址,当然也可以在监视看,以下在内存中观察:
2.2指针变量和解引⽤操作符(*)
那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006ffae0,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。
⽐如:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;//取出a的地址并存储到指针变量pa中
return 0
}
指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。
2.2.1 如何拆解指针类型
我们看到pa的类型是 int*
,我们该如何理解指针的类型呢?
nt a = 10;
int * pa = &a;
这⾥pa左边写的是 int*
, *
是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int)
类型的对象。
那如果有⼀个char
类型的变量ch
,ch
的地址,要放在什么类型的指针变量中呢?
char ch = 'R';
pc = &ch;//pc 的类型怎么写呢?
答案是:
char *pc = &ch;
2.2.2 解引⽤操作符
解引用运算符( * )
将指针变量所指向的对象的值赋给左值变量。当使用指针变量时,使用解引用运算符来访问指针变量所指向的对象。
#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;
printf("%d", a);
return 0;
}
*pa
的意思就是通过pa中存放的地址,找到指向的空间,
*pa
其实就是a变量了;所以*pa = 0
,这个操作符是把a
改成了0
#include <stdio.h>
int main()
{
int num1 = 10;
int *ptr1 = &num1;
printf("The value of num1 is %d\n", num1);
printf("The address of num1 is %d\n", &num1);
printf("The value of ptr1 is %d\n", ptr1);
printf("The value of *ptr1 is %d\n", *ptr1);
return 0;
}
变量 num1 的值为 10,因为它被赋值为 10。
变量 num1 的地址为 13629256,因为它在内存中的位置是 13629256。
变量 ptr1 的值为 13629256,因为它被赋值为 num1 的地址。
变量 *ptr1 的值为 10,因为它是 ptr1 所指向的对象的值。
2.3 指针变量的⼤⼩
指针变量的⼤⼩取决于地址的⼤⼩
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。
#include <stdio.h>
int main()
{
int* a = 10;
short* b = 10;
char* c = 10;
double* d = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(b));
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(d));
return 0;
}
在debug X86也就是调试状态下的32位环境下:
在debug X64也就是调试状态下的64位环境下:
三、 指针变量类型的意义
当你看到这里,你发现指针变量的⼤⼩和类型⽆关,只要是指针变量,在同⼀个平台下,⼤⼩都是⼀样的, 那还要那么多的指针类型干嘛呢?统一归为一种指针类型不就好了,就没那么麻烦了,bug或许可能就没有那么多了!同学带着你的疑问,让我们一起走下去。看看为什么这么设计的?
3.1 指针的解引⽤
首先,我们观察两组代码的变化环境为debug x86
- 代码1:
#include <stdio.h>
int main()
{
int n = 0x66778899;
int* pk = &n;
*pk = 0;
return 0;
}
- 代码2:
#include <stdio.h>
int main()
{
int n = 0x66778899;
char* pk = (char*)&n;
*pk = 0;
return 0;
}
调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char*
的指针解引⽤就只能访问⼀个字节,⽽ int*
的指针的解引⽤就能访问四个字节
3.2 指针+ - 整数
指针加减整数的语法如下:
ptr + n
ptr - n
其中,ptr 是
指针变量,n
是整数。
当我们向一个指针加减整数时,我们实际上是在向指针所指向的内存地址加减整数。这意味着,如果我们向一个指针加 1,则指针会指向内存中下一个字节的位置。如果我们向一个指针减 1,则指针会指向内存中上一个字节的位置。
以下是不同类型的指针加减整数的示例:
整数指针:
int *p = 0;
p++; // 指向内存中下一个字节的位置
p--; // 指向内存中上一个字节的位置
指针数组:
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;//int*类型加4
p++; // 指向内存中下一个元素的位置
p--; // 指向内存中上一个元素的位置
以下是指针加减 1 的示例:
#include <stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
int* arr[] = { 1,2,3,4,5 };
int* pk = arr;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("%p\n", pi);
printf("%p\n", pi + 1);
printf("%p\n", pk);
printf("%p\n", pk + 1);
return 0;
}
运行结果:
可以看到,
char类型的指针变量+1跳过1个字节,int类型的指针变量+1跳过了4个字节。这表明指针变量的类型差异会导致步长的变化。
结论:指针的类型决定了指针向前或向后移动一步的距离。
3.3 void* 指针
void
指针是 C 语言中一种特殊的指针,它可以指向任何类型的数据。void
指针的类型是 void
,它不指向任何特定的数据类型。
void* 类型的指针不能直接进⾏指针的±整数和解引⽤的运算。
void 指针也可以用来解引用,但必须在解引用之前使用类型转换。void` 指针可以用来存储指向任何类型数据的指针。
#include <stdio.h>
int main()
{
int a = 88;
void* p=&a;
int* q = (int*)p;//类型转换
printf("n = %d\n", *q);
return 0;
}
void* 类型的指针不能直接进⾏指针的±整数和解引⽤的运算。