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

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] << " ";

相关文章:

  • C语言基础知识04---指针
  • docker搭建elk
  • MySQL-存储过程和自定义函数
  • Typora自带的picgo图床上传功能 异常修复
  • JVM常用概念之锁省略
  • Windows编译Flash-attention模块
  • 【医学影像 AI】大型语言模型生成 ROP 患者信息材料的能力
  • python实现接口自动化
  • 【NeurIPS 2024】LLM-ESR:用大语言模型破解序列推荐的长尾难题
  • 从Instagram到画廊:社交平台如何改变艺术家的展示方式
  • LLM(3): Transformer 架构
  • 自探索大语言模型微调(一)
  • Designing Dashboards with SAP Analytics Cloud
  • Centos离线安装openssl
  • 错误记录: git 无法连接到github
  • 【恒流源cc与恒压源cv典型电路解析】
  • CVE-2018-2628(使用 docker 搭建)
  • RUOYI框架在实际项目中的应用一:ruoyi简介
  • 用vue3显示websocket的状态
  • # RAG 框架 # 一文入门 全链路RAG系统构建与优化 —— 架构、策略与实践
  • 无锡网站建设技术/购物网站网页设计
  • 荆门网站建设服务/营销课程培训
  • 做网站平面模板是啥意思/免费推广途径与原因
  • 永久免费使用云服务器666777/seo怎么推广
  • 苏州建站公司优搜苏州聚尚网络/株洲seo排名
  • 网站开发方案报价/推广策划