当前位置: 首页 > article >正文

NO.40十六届蓝桥杯备战|指针和动态内存管理|取地址操作符|解引用操作符|指针+-整数|void*|new|delete(C++)

内存和地址

计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是 8GB/16GB/32GB 等,那这些内存空间如何⾼效的管理

把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩为1个字节
每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到编号对应的内存单元。
⽣活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫:指针

![[Pasted image 20250314200127.png]]

计算机中常⻅的单位(补充):
⼀个⽐特位可以存储⼀个2进制的位1或者0

bit - ⽐特位  
Byte - 字节  
KB  
MB  
GB  
TB  
PB
1Byte = 8bit  
1KB = 1024Byte  
1MB = 1024KB  
1GB = 1024MB  
1TB = 1024GB  
1PB = 1024TB

指针变量

取地址操作符(&)

C++中创建变量的本质是向内存申请空间

#include <iostream>  
int main()  
{  
	int a = 10;  
	return 0;  
}

![[Pasted image 20250314203536.png]]

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

0x0117FAD8  
0x0117FAD9  
0x0117FADA  
0x0117FADB

操作符(&)-取地址操作符

#include <iostream>  
using namespace std;  
int main()  
{  
	int a = 10;  
	&a;//取出a的地址  
	cout << &a << endl;  
	return 0;  
}

会打印处理: 0117FAD8 &a取出的是a所占4个字节中地址较⼩的字节的地址。
虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可⾏的。

指针变量

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

#include <iostream>  
using namespace std;  
int main()  
{  
	int a = 10;  
	int * pa = &a;//取出a的地址并存储到指针变量pa中  
	
	return 0;  
}

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。
指针变量中存放谁的地址,我们就说这个指针变量指向了谁;上⾯代码中 pa 就是存放 a 的地址,我们就是 pa 指向了 a 变量。
但是有时候⼀个指针变量创建的时候,还不知道存储谁的地址,那怎么办呢?在C++中这时候,我们会给指针变量赋值为 NULL , NULL 的值其实是 0 ,表⽰空指针,意思是没有指向任何有效的变量。
当然 0 也是作为地址编号的,这个地址是⽆法使⽤的,读写该地址会报错

int *p = NULL;

在C++11后,使⽤ nullptr 来代替了 NULL ,我们在代码中也可以直接使⽤ nullptr

如何拆解指针类型
int a = 10;  
int * pa = &a;

这⾥pa左边写的是 int* , * 是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int) 类型的对象。
![[Pasted image 20250314210605.png]]

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

char ch = 'w';  
pc = &ch; //pc 的类型怎么写呢
解引⽤操作符

C++语⾔中,只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符( * )。

#include <iostream>  
using namespace std;  
int main()  
{  
	int a = 100;  
	int* pa = &a;  
	*pa = 0;  
	cout << a << endl;  
	
	return 0;  
}

上⾯代码中第7⾏就使⽤了解引⽤操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa 其实就是 a 变量了;所以 *pa = 0 ,这个操作符是把 a 改成了 0 .
这⾥是把a的修改交给了pa来操作,这样对a的修改,就多了⼀种的途径,写代码就会更加灵活
注意:如果⼀个指针变量的值是 NULL 时,表⽰这个指针变量没有指向有效的空间,所以⼀个指针变量的值是 NULL 的时候,是不能解引⽤操作的

指针类型的意义

指针类型的意义体现在两个⽅⾯:

  • 指针的解引⽤
  • 指针±整数
指针的解引⽤
//代码1  
#include <iostream>  
using namespace std;  
int main()  
{  
	int n = 0x11223344;  
	int *pi = &n;  
	*pi = 0;
	  
	return 0;  
}

//代码2  
#include <iostream>  
using namespace std;  
int main()  
{  
	int n = 0x11223344;  
	char *pc = (char *)&n;  
	*pc = 0;  
	
	return 0;  
}

调试我们可以看到,代码1会将 n 的 4 个字节全部改为 0 ,但是代码2只是将 n 的第1个字节改为0 。
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。
![[Pasted image 20250314210947.png]]

指针±整数
#include <cstdio>  
int main()  
{  
	int n = 10;  
	char* pc = (char*)&n;  
	int * pi = &n;  
	printf("&n = %p\n", &n);  
	printf("pc = %p\n", pc); 
	//字符的地址使⽤cout打印会以为是字符串,所以这⾥使⽤printf来打印  
	printf("pc+1 = %p\n", pc + 1);  
	printf("pi = %p\n", pi);  
	printf("pi+1 = %p\n", pi + 1);  
	return 0;  
}

![[Pasted image 20250314211030.png]]

char* 类型的指针变量 +1 跳过 1 个字节, int* 类型的指针变量 +1 跳过了 4 个字节。这就是指针变量的类型差异带来的变化。指针 +1 ,其实跳过 1 个指针指向的元素。指针可以 +1 ,那也可以 -1
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)

void*指针

在指针类型中有⼀种特殊的类型是 void * 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的±整数和解引⽤的运算。

#include <cstdio>  
int main()  
{  
	int a = 10;  
	int* pa = &a;
	char* pc = &a;  
	return 0;  
}

在上⾯的代码中,将⼀个int类型的变量的地址赋值给⼀个 char* 类型的指针变量。编译器给出了⼀个报错(如下图),是因为类型不兼容。⽽使⽤ void* 类型就不会有这样的问题
![[Pasted image 20250314211357.png]]

#include <cstdio>  
int main()  
{  
	int a = 10;  
	void* pa = &a;  
	void* pc = &a;  
	*pa = 10;  
	*pc = 0;  
	return 0;  
}

![[Pasted image 20250314211425.png]]

void* 类型的指针可以接收不同类型的地址,但是⽆法直接进⾏指针运算

⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。

void test(void* p)  
{  
	//....  
}  

int main()  
{  
	int a = 10;  
	test(&a);  
	double d = 3.14;  
	test(&d);  
	return 0;  
}

指针访问数组

有⼀个整型数组,10个元素,默认初始化为0,现在要将数组的内容设置为1~10,然后打印数组的内容。
我们可以使⽤数组的形式,来完成任务,也可以使⽤指针的形式来完成。
我们知道数组在内存中是连续存放的,那是只要给定⼀个起始位置,顺藤摸⽠就能到后边的其他元素了。下⾯我们来使⽤指针,给⼀个整型数组做⼀个初始化,再将数组的内容打印出来。
![[Pasted image 20250314211542.png]]

#include <iostream>  
using namespace std;  
int main()  
{  
	int arr[10] = {0};  
	int *p = &arr[0];  
	int i = 0;  
	for(i = 0; i < 10; i++)  
	{  
		*(p + i) = i + 1;  
	}  
	for(i = 0; i < 10; i++)  
	{  
		cout << *p << " ";  
		p++;  
	}  
	return 0;  
}

![[Pasted image 20250314211643.png]]

  1. 这⾥操作的是整型数组,我们写代码时期望以⼀个整型为单位的访问,解引⽤时访问⼀个整型,+1 跳过⼀个整型,所以我们使⽤了 int* 类型的指针,如果是其他类型的数据要选择最恰当的指针类型。
  2. 代码中第⼀个 for 循环,我们选择使⽤ p+i 的⽅式, p 不变, i 在不断地变化,找到数组的每个元素,第⼆个 for 循环,我们选择 p++ 的效果,让 p 不断地往后寻找元素,要体会这两种的差异。
  3. 我们在代码中使⽤ for 循环,通过元素个数控制循环的次数。其实指针就是地址,是⼀串编号,这个编号是有⼤⼩的,那就可以⽐较⼤⼩,这就是指针的关系运算。使⽤指针关系运算,也能完成上⾯代码。请看下⽅代码:
#include <iostream>  
using namespace std;  
int main()  
{  
	int arr[10] = {0};  
	int *p = &arr[0];  
	int i = 0;  
	for(i = 0; i < 10; i++)  
	{  
		*(p + i) = i + 1;  
	}  
	while(p < &arr[10])  
	{  
		cout << *p << " ";  
		p++;  
	}  
	return 0;  
}

![[Pasted image 20250314211808.png]]

动态内存管理

变量的创建会为变量申请⼀块内存空间,数组的创建其实也向内存申请⼀块连续的内存空间。

int n = 10; //向内存申请4个字节的空间  
char arr1[5]; //向内存申请5个字节的空间  
int arr2[5]; //向内存申请20个字节的空间

这两种⽅式,如果创建是全局的变量和数组,是在内存的静态区(数据段)申请的,如果是局部的变量和数组,是在内存的栈区申请的。不管是全局变量还是局部变量,申请和回收都是系统⾃动完成的,不需要程序员⾃⼰处理。

#include <iostream>  
using namespace std;  
//全局变量 - 存放在内存的静态区(数据段)  
int n2 = 10;  
int arr2[5];  
int main()  
{  
	//局部变量 - 存放在内存的栈区  
	int n1 = 10;  
	int arr1[5];  
	return 0;  
}

其实C++还提供了另外⼀种⽅式,就是:动态内存管理,允许程序员在适当的时候,⾃⼰申请空间,⾃⼰释放空间,⾃主维护这块空间的⽣命周期。动态内存管理所开辟到的空间是在内存的堆区。

new/delete

C++中通过 new 和 delete 操作符进⾏动态内存管理。

  • new 负责申请内存, new 操作符返回的是申请到的内存空间的起始地址,需要指针存放。
    • new申请⼀个变量的空间, new[] 申请⼀个数组的空间
  • delete 负责释放(回收)内存
    • delete 负责释放⼀个变量的空间, delete[] 释放⼀个数组的空间
  • new 和 delete 配对, new[]delete[] 配对使⽤
// 动态申请⼀个int类型的空间  
int* ptr1 = new int;  
// 动态申请⼀个int类型的空间并初始化为10  
int* ptr2 = new int(10);  
// 动态申请10个int类型的空间  
int* ptr3 = new int[10];  
//释放内存空间  
delete ptr1;  
delete ptr2;  
delete[] ptr3;

![[Pasted image 20250314212407.png]]

new 不是只能给内置类型开辟空间,也可以给⾃定义类型开辟空间

#include <iostream>  
using namespace std;  
int main()  
{  
	int*p = new int;  
	*p = 20;  
	cout << *p << endl;  
	delete p;  
	int *ptr = new int[10];  
	for(int i = 0; i < 10; i++)  
	{  
		*(ptr + i) = i;  
	}  
	for(int i = 0; i < 10; i++)  
	{  
		cout << *(ptr + i) << " ";  
	}
	delete[] ptr;  
	return 0;  
}

其实数组是连续的空间, new[]申请到的空间也是连续的,那上述代码中 ptr 指向的空间能不能使⽤数组的形式访问呢?答案是可以的,上⾯代码中第18⾏代码可以换成:

cout << ptr[i] << " ";

http://www.kler.cn/a/588711.html

相关文章:

  • 7-14 利用正则表达式得到一段HTML文本中所有超链接对应的网址
  • vue echarts封装使用
  • 车载以太网测试-14【交换机以及MAC地址表】
  • MongoDB 和 Elasticsearch的区别、优缺点对比,以及选型建议
  • Chrome/Edge浏览器离线安装插件全攻略:CRX/ZIP文件手动安装教程
  • std::list的模拟实现
  • 零成本本地化搭建开源AI神器LocalAI支持CPU推理运行部署方案
  • 【WEB APIs】DOM-节点操作
  • 钉钉项目报销与金蝶系统高效集成技术解析
  • 人工智能中的线性代数基础详解
  • RabbitMQ实现定时/延迟任务
  • ES6(2) 函数详解
  • 【RNN神经网络】序列模型与RNN神经网络
  • 易境通集运系统:拼团模式重构社交化集运新生态
  • MobileNet家族:从v1到v4的架构演进与发展历程
  • Vue源码深度解析:从2.x到3.x的架构演进与核心原理剖析
  • pycharm配置镜像源【pycharm最新版(23.2.5及以上)方法】
  • 记第一次跟踪seatunnel的任务运行过程三——解析配置的具体方法getLogicalDag
  • 【vue3学习笔记】(第147-149节)vue3响应式原理_Reflect;reactive对比ref;setup的两个注意点
  • Solana