程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<1>

大家好啊,我是小象٩(๑òωó๑)۶
我的博客:Xiao Xiangζั͡ޓއއ
很高兴见到大家,希望能够和大家一起交流学习,共同进步

这一节我们来学习指针的相关知识,学习内存和地址,指针变量和地址,包括取地址操作符,指针变量和解引用操作符,指针变量类型的意义,指针变量的大小,指针的解引用,指针±整数,void指针

文章目录

  • 一、内存和地址
    • [1.1 内存](#1.1 内存)
    • [1.2 如何理解编址](#1.2 如何理解编址)
  • 二、指针变量和地址
    • [2.1 取地址操作符(&)](#2.1 取地址操作符(&))
    • [2.2 指针变量和解引用操作符(*)](#2.2 指针变量和解引用操作符(*))
      • [2.2.1 指针变量](#2.2.1 指针变量)
      • [2.2.2 如何拆解指针类型](#2.2.2 如何拆解指针类型)
      • [2.2.3 解引用操作符](#2.2.3 解引用操作符)
    • [2.3 指针变量的大小](#2.3 指针变量的大小)
  • 三、指针变量类型的意义
    • [3.1 指针的解引用](#3.1 指针的解引用)
    • [3.2 指针+-整数](#3.2 指针+-整数)
    • [3.3 void指针](#3.3 void指针)
  • 四、结尾

一、内存和地址

1.1 内存

在 C 语言中,内存是程序运行的基础,用于存储程序中的数据和代码。

我们来举一个生活中的例子:假设有⼀栋宿舍楼,把你放在楼里,楼上有100个房间,但是房间没有编号,你的一个朋友来找你玩,如果想找到你,就得挨个房子去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号,如:

c 复制代码
⼀楼:101,102,103...
⼆楼:201,202,203...
...

有了房间号,如果你的朋友得到房间号,就可以快速的找房间,找到你。

同理,我们在买电脑的时候也会发现电脑的内存有8GB、16GB、32GB等,那这些内存空间如何高效的管理呢?其实一样也是把内存划分为一个个的内存单元,每个内存单元的大小取1个字节。

PS:计算机中的常见单位:一个比特位可以存储一个2进制的位1或者0

c 复制代码
bit - ⽐特位
Byte - 字节
KB
MB
GB
TB
PB
c 复制代码
1Byte = 8bit
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

其中,每个内存单元,相当于一个学生宿舍,一个字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是一个比特位。每个内存单元也都有一个编号(这个编号就相当于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到一个内存空间。生活中我们把门牌号也叫地址,在计算机中我们

把内存单元的编号也称为地址。C语言中给地址起了新的名字叫做:指针。

所以我们可以理解为:

内存单元的编号=地址=指针

1.2 如何理解编址

编址是计算机系统中一个关键的概念,它涉及为计算机中的各种存储单元或设备分配唯一标识符,以便能够对它们进行准确的访问和管理。

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,⽽因为内存中字节很多,所以需要给内存进而编址(就如同宿舍很多,需要给宿舍编号一样)。计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。钢琴、吉他上面没有写上"剁、来、咪、发、唆、拉、西"这样的信息,但演奏者照样能够准确找到每一个琴弦的每⼀个位置,这是为何?因为制造商已经在乐器硬件层面上设计好了,并且所有的演奏者都知道。本质是一种约定出来的共识!

首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协工作的。所谓的协同,至少相互之间要能够进行数据传递。但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单,用"线"连起来。而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。不过,我们今天关心一组线,叫做地址总线。硬件编址也是如此我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么一根线,就能表示2种含义,2根线就能表示4种含义,依次类推。32根地址线,就能表示2^32种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。

举个例子,如果我们想要访问某个地址,首先控制总线会发出指令,如何根据地址总线找到内存中对应的位置,接着在通过数据总线传递回来。

二、指针变量和地址

2.1 取地址操作符(&)

在 C 语言中,取地址操作符是"&"。它用于获取一个变量的内存地址。例如,如果有一个变量 int a; ,那么 &a 就表示变量 a 的地址。

PS:变量创建的本质其实是:在内存中申请空间,向内存申请4个字节的空间,存放10

比如,上述的代码就是创建了整型变量a,内存中申请4个字节,用于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:

c 复制代码
0x004FF9D4  
0x004FF9D5  
0x004FF9D6  
0x004FF9D7

那我们如何能得到a的地址呢?

这里就得学习一个操作符(&)------取地址操作符

c 复制代码
#include<stdio.h>
int main ()
{
	int a = 10;
	printf("%p\n", &a);
	return 0;
}

注意:&a取出的是a所占4个字节中地址较小的字节的地址。

实际上,地址也是二进制的,只是为了在vs容易表示写成了16进制

虽然整型变量占用4个字节,我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。

2.2 指针变量和解引用操作符(*)

2.2.1 指针变量

在C语言中,指针变量是一种特殊的变量,它存储的是内存地址,而不是普通的数据值。

我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x004FF9D4,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。

c 复制代码
#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a; //取出a的地址并存储到指针变量pa中

	return 0
}

指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。

2.2.2 如何拆解指针类型

我们看到pa的类型是 int * ,我们该如何理解指针的类型呢?

c 复制代码
int a = 10;
int * pa = &a;

这里pa左边写的是 int* , * 是在说明pa是指针变量,而前面的 int 是在说明pa指向的是整型(int)类型的对象。

那如果有一个char类型的变量ch,ch的地址,要放在什么类型的指针变量中呢?

很显然,自然是放在char类型的指针变量中。

2.2.3 解引用操作符

在现实生活中,我们使用地址要找到一个房间,在房间里可以拿去或者存放物品。

C语言中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)

指向的对象,这里必须学习一个操作符叫解引用操作符(*)。
解引用操作符用于访问指针所指向的内存位置的值。也就是说,当你有一个指针变量,它存储了某个变量的内存地址,通过解引用操作符,你可以获取或修改该内存地址中存储的值。

举个例子:

c 复制代码
#include<stdio.h>
int main()
{
	int a = 100;
	int* pa = &a;
	*pa = 0;
	return 0;
}

第五行中定义了一个指向整型的指针变量 pa。int* 表示指针的类型,即该指针指向的是一个整型变量。& 是取地址运算符,&a 表示获取变量 a 的内存地址。因此,这行代码将变量 a 的地址赋值给指针 pa,使得 pa 指向了变量 a。

而第六行就有了解引用操作符的使用,*pa 表示访问 pa 所指向的变量,也就是变量 a。这行代码将 0 赋值给 *pa,实际上就是将 0 赋值给变量 a,从而修改了变量 a 的值。

有同学肯定在想,这里如果目的就是把a改成0的话,写成 a = 0;不就完了,为啥非要使用指针呢?

其实这里是把a的修改交给了pa来操作,这样对a的修改,就多了一种的途径,写代码就会更加灵活,

后期慢慢就能理解了。

2.3 指针变量的大小

前面的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后

是1或者0,那我们把32根地址线产生的2进制序列当做一个地址,那么一个地址就是32个bit位,需要4

个字节才能存储。

如果指针变量是用来存放地址的,那么指针变的大小就得是4个字节的空间才可以。

同理64位机器,假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来就需要

8个字节的空间,指针变量的大小就是8个字节。

c 复制代码
#include <stdio.h>
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(short*));
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(double*));
	return 0;
}

结论:

• 32位平台下地址是32个bit位,指针变量大小是4个字节

• 64位平台下地址是64个bit位,指针变量大小是8个字节

• 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的

三、指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在同⼀个平台下,大小都是⼀样的,为什么还要有各

种各样的指针类型呢?

其实指针类型是有特殊意义的,我们接下来继续学习。

3.1 指针的解引用

C 语言指针解引用是通过指针访问其指向内存地址中存储值的操作,使用星号 * 作为解引用操作符。它主要用于访问和修改数据,以及对动态分配的内存进行读写。 代码应用场景包括基本指针解引用、指针与数组结合的解引用和多级指针的解引用。使用时要注意进行空指针检查,避免对 NULL 指针解引用导致程序崩溃,同时防止使用未正确初始化或指向已释放内存的野指针,以免引发未定义行为。

对比下面两段代码:

c 复制代码
//代码1
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	return 0;
}
c 复制代码
//代码2
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	*pc = 0;
	return 0;
}

调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0。
结论:指针的类型决定了,对指针解引⽤的时候有多大的权限(一次能操作几个字节)。

比如: char* 的指针解引用就只能访问一个字节,而int* 的指针的解引用就能访问四个字节。

3.2 指针±整数

先看一段代码,调试观察地址的变化。

c 复制代码
#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;
}

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。

这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可

以-1。
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。

3.3 void指针

void 指针也被称为通用指针,其类型声明为 void *。它可以指向任意类型的数据,也就是说,void 指针可以存储任何类型变量的地址,但它本身并不明确所指向数据的具体类型。

这种类型的指针可以用来接受任意类型地址。但是也有局限性, void*类型的指针不能直接进行指针的±整数和解引用的运算。

举个例子:

c 复制代码
#include<stdio.h>
	int main()
	{
		int a = 10;
		int* pa = &a;
		char* pc = &a;
		return 0;
	}

在上面的代码中,将一个int类型的变量的地址赋值给一个char类型的指针变量。编译器给出了一个警
告(如下图),是因为类型不兼容。而使用void
类型就不会有这样的问题。

使用void*类型的指针接收地址:

c 复制代码
#include <stdio.h>
int main()
{
    int a = 10;
    void* pa = &a;
    void* pc = &a;
 
    *pa = 10;
    *pc = 0;
 return 0;
}

这里我们可以看到, void* 类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。

那么 void* 类型的指针到底有什么用呢?

一般 void * 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果,后面我们会继续讲解viod指针。

四、结尾

这一课的内容就到这里了,下节课继续学习操作符的其他一些知识
如果内容有什么问题的话欢迎指正,有什么问题也可以问我!

相关推荐
isyangli_blog2 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008112 小时前
FastAPI APIRouter
开发语言·python
Benszen2 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆2 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木2 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充3 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~3 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6163 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草3 小时前
反射、Tomcat执行
java·开发语言
雪的季节4 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt