【C++指针】搭建起程序与内存深度交互的桥梁(下)



🔥🔥 个人主页 点击🔥🔥
每文一诗 💪🏼
往者不可谏,来者犹可追——《论语·微子篇》
译文:过去的事情已经无法挽回,未来的岁月还可以迎头赶上。
目录
C++内存模型
new与delete动态分配内存
动态分配单个变量(例如; int* ptr = new int(10))
动态分配数组(例如 int* arr = new int[5] )
分配内存失败的情况
一维数组与指针
使用数组名/和数组名加下标访问数组中元素及其地址
使用指针访问数组中元素及其地址
二维数组与指针
行指针
函数指针
指针用作函数参数
用函数指针来传递函数
C++内存模型
栈区:由编译器自动管理,用于存储局部变量、函数参数和返回地址。每当调用一个函数时,会在栈上分配一块新的栈帧,函数执行完毕后,栈帧自动释放。
堆区:也被叫做自由存储区,由程序员手动管理。通过new操作符在堆上分配内存,使用delete操作符释放内存。
数据存储区:用于存放全局变量和静态变量。在程序启动时分配内存,程序结束时释放内存。
代码区:用于存放程序的可执行代码。代码区是只读的,防止程序在运行过程中被意外修改。
栈区和堆区区别
- 管理方式不同:栈区是系统自动管理的,在离开作用域时,会自动释放
- 空间大小不同:栈区大小操作系统预先设定,一般只有8M。如果在栈上分配的内存超过了栈的最大容量,会导致栈溢出(Stack Overflow)错误;堆区空间仅受限与物理内存空间,所以相对较大。
- 效率不同:栈区内存的分配和释放速度非常快,因为它只需要移动栈指针;而堆区内存分配和释放需要操作系统复杂的操作。
new与delete动态分配内存
new:动态分配内存
delete: 释放分配的内存
在堆区动态分配内存的步骤:
- 声明一个指针
- 用new运算符向系统的堆区申请一块内存,并用指针指向这块内存
- 通过解引用的方式,来取出这块内存中的值
- 当这块内存不用时,需用delete来释放它
动态分配内存的两类:
动态分配单个变量(例如; int* ptr = new int(10))
int* p = new int(2);std::cout<<"*p的值:"<<*p<<std::endl;//打印内存中的值delete p;

解析:
- int* p = new int(2);
首先在堆区new申请了一块内存,这块内存是int型,接着初始化该内存的值为2,最后返回新分配内存的地址,用一个int类型的指针来指向他。
- delete p;
释放这块内存
动态分配数组(例如 int* arr = new int[5] )
int* arry = new int[8];for(int i=0;i<8;i++){*(arry+i) = i;std::cout<<"第"<<i<<"个值"<<*(arry+i)<<std::endl;}delete[] arry;

解析:
int* arry = new int[8];
首先new申请了一块内存,这块内存存储的是一个含有8位int型数据的数组,最后返回新分配内存的地址,用一个int类型的指针来指向他。这里的arry代表数组的首地址。
- *(arry+i) = i;
通过循环和解引用的方式为数组中每个数赋值。
delete[] arry;
释放这块内存
分配内存失败的情况
如果需要分配含有大量的数据的数组,那么栈空间上分配是远远不够的,需要在堆区分配。但是如果内存分配失败,则会导致程序崩溃,但是我们不希望这样,我们可以在内存分配失败的时候捕捉到这个错误。
使用std::nothrow
int main(int argc, char const *argv[])
{int* arry = new(std::nothrow)int[100000];if(arry == nullptr)std::cout<<"分配内存失败"<<std::endl;else{std::cout<<"分配内存成功"<<std::endl;arry[99999] = 0;delete[] arry;}return 0;
}
一维数组与指针
使用数组名/和数组名加下标访问数组中元素及其地址
int arry[3] = {2,4,6};
std::cout<<"数组"<<std::endl;
std::cout<<arry<<std::endl;
std::cout<<&arry[0]<<std::endl;
std::cout<<arry+1<<std::endl;
std::cout<<arry+2<<std::endl;
std::cout<<arry[0]<<std::endl;
std::cout<<arry[1]<<std::endl;
std::cout<<arry[2]<<std::endl;
解析:
- 数组的名称/数组第一个元素的地址 是同一个地址
- 数组名+n:数组第n个元素的地址
- 数组名[n]:数组第n个元素的内容
使用指针访问数组中元素及其地址
int* p = arry;
std::cout<<"指针"<<std::endl;
std::cout<<p<<std::endl;
std::cout<<p+1<<std::endl;
std::cout<<p+2<<std::endl;
std::cout<<*(p)<<std::endl;
std::cout<<*(p+1)<<std::endl;
std::cout<<*(p+2)<<std::endl;
解析:
如果将数组名称赋给一个指针变量,实际上是将数组的首地址赋给了指针。
- 指针+n:数组第n个元素的地址
- *(指针+n):数组第n个元素的内容
对于C++而言
数组名[下标] 解释为 *(数组名首地址+下标)
地址[下标] 解释为 *(地址+下标)
输出
两者是一样的

二维数组与指针
在讲二维数组之前,有必要去介绍对一个一维数组名取地址
void func2()
{int a[3] = {6,7,8};std::cout<<"数组第一个元素的地址:"<<a<<std::endl;std::cout<<"数组第一个元素的地址+1:"<<a+1<<std::endl;std::cout<<"数组的地址:"<<&a<<std::endl;//即为地址的地址,是一个行指针std::cout<<"数组的地址+1:"<<&a+1<<std::endl;int (*p)[3] = &a;//正确// int *p2 = &a;//错误S
}
解析:
我们都知道数组名a是代表数组第一个元素的地址,但是&a是数组的地址,虽然a和&a的地址是相同的,但是二者有着不同的类型。
a的类型是 int*
&a的类型是 int(*p)[],即行指针。
为了证明a和&a有着不同的含义,我们同时对两个地址加1测试
输出
- 可见数组第一个元素的地址+1后,实际上是加了4,对于16进制,c后是d,e,f,0然后进位a变为b,所以是ac变b0
- 而数组的地址+1后,发现并没有+4,而是+12,12是3*4得来的,因为数组有3个int型数据,每个数据占4个字节。
- 这也是行指针的作用,行指针+1后,实际上加上的是这一行数组组成的数组的总长度。
行指针
对于二维数组而言。
行指针格式: 数据类型 (*p)[列大小]
例如
int arry[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3] = arry;
#include<iostream>int main(int argc, char const *argv[])
{int arry[2][3] = {{1,2,3},{4,5,6}};int (*p)[3] = arry;// 这种方式是一个行指针,也就是说该指针p指向的是二维数组中第一个包含三个int型数据的数组的地址// 对该地址进行解引用,就会得到该数组的首地址,再次解引用就会得到数组中具体的值。std::cout<<**p<<std::endl;//p为二维数组中每一行数组的地址,*p得到数组第一个元素的地址,**p得到数组元素的值std::cout<<*(*(p+1))<<std::endl;//p为二维数组中第0行数组的地址,再加1得到第1行数组的地址,解引用为第一行数组的第一个元素的地址,再解引用位第一个元素的值。std::cout<<*(*(p+1)+1)<<std::endl;//*(p+1)为第一行数组的第一个元素的地址,*(p+1)+1:再加1为第一行数组的第二个元素的地址,再解引用为第二个元素的值。std::cout<<*(p[1]+1)<<std::endl;//p[1]在c++中被解释为*(p+1)// 个人理解:// 地址 + n:// 应看这个地址的类型,即这个指针的类型,如果这个指针是行指针,那么加1就是加上这一行数组的总共的字节数//例如p+1,p是行指针,存储的是每一行数组的地址,加1其实是加上了4*3=12个字节//如果这个指针是普通指针,那么加1就是加上这个数组的单个元素的字节数//例如*(p+1)+1,*(p+1)是普通指针,存储的是数组第一个元素的地址,加1其实是加上了4个字节return 0;
}
输出

函数详解:
int (*p)[3] = arry;
这种方式是一个行指针,也就是说该指针p指向的是二维数组中第一个包含三个int型数据的数组的地址
- std::cout<<**p<<std::endl;
p为二维数组中每一行数组的地址,*p得到数组第一个元素的地址,**p得到数组元素的值
- std::cout<<*(*(p+1))<<std::endl;
p为二维数组中第0行数组的地址,再加1得到第1行数组的地址,解引用为第一行数组的第一个元素的地址,再解引用位第一个元素的值。
- std::cout<<*(*(p+1)+1)<<std::endl;
*(p+1)为第一行数组的第一个元素的地址,*(p+1)+1:再加1为第一行数组的第二个元素的地址,再解引用为第二个元素的值。
- std::cout<<*(p[1]+1)<<std::endl;
p[1]在c++中被解释为*(p+1)
个人理解:
对于 地址 + n:
- 应看这个地址的类型,即这个指针的类型,如果这个指针是行指针,那么加1就是加上这一行数组的总共的字节数。例如p+1,p是行指针,存储的是每一行数组的地址,加1其实是加上了4*3=12个字节
- 如果这个指针是普通指针,那么加1就是加上这个数组的单个元素的字节数。例如*(p+1)+1,*(p+1)是普通指针,存储的是数组第一个元素的地址,加1其实是加上了4个字节
函数指针
指针用作函数参数
如果参数是一个 数组的话,必须传递数组的长度
下面用代码解释原因
#include<iostream>
void func(int* arr)
{std::cout<<"数组长度2="<<sizeof(arr)<<std::endl;for(int i =0;i<sizeof(arr)/sizeof(int);i++){std::cout<<*(arr+i)<<std::endl;}}
int main(int argc, char const *argv[])
{int arry[3] = {2,4,6};func(arry);std::cout<<"数组长度1="<<sizeof(arry)<<std::endl; return 0;
}
输出

在函数func中,参数是一个指针变量,使用sizeof运算符的时候,会返回这个指针的大小,而指针的大小是一个常数8(在64位操作系统);而在main函数中,arry是一个数组名,在使用sizeof运算符的时候,会返回这个数组的大小。
所以在func函数中,sizeof(arr)/sizeof(int)的值是8/4等于2,所以数组只打印了索引为0和1的值。
正确的做法是参数中加上数组长度
#include<iostream>
void func(int* arr,int len)
{std::cout<<"数组长度2="<<sizeof(arr)<<std::endl;for(int i =0;i<len;i++){std::cout<<*(arr+i)<<std::endl;}}
int main(int argc, char const *argv[])
{int arry[3] = {2,4,6};func(arry,sizeof(arry)/sizeof(int));std::cout<<"数组长度1="<<sizeof(arry)<<std::endl; return 0;
}
输出

用函数指针来传递函数
用途:可以用一个函数来调用别的函数.
做法:将该函数的参数设置为要调用函数的指针
声明一个函数指针:
格式:返回值类型 (*函数指针名)(参数1,参数2)
通过函数指针调用函数
函数指针名(参数1,2)
在C++中,函数的名称就是函数的地址
#include<iostream>int func2(int m)
{std::cout<<"函数2"<<std::endl;return m+1;
}
int func3(int m)
{std::cout<<"函数3"<<std::endl;return m-1;
}
void func(int(*pf)(int))
{std::cout<<"准备工作"<<std::endl;int n = pf(3);std::cout<<"返回值"<<n<<std::endl;std::cout<<"收尾工作"<<std::endl;
}
int main(int argc, char const *argv[])
{func(func2);func(func3);return 0;
}
函数解析:
这段代码实现了函数传递函数,通过修改参数可以让不同的函数被执行。
主要看的是void func(int(*pf)(int))
这个函数func的参数是一个函数指针
这个是将需要传递的函数的名称传递过去,函数的名称就是函数的地址
- 名称:pf
- 返回值:int
- 参数:int 可不加变量名
func(func2);func(func3);
输出

若本文对你有帮助,你的支持是我创作莫大的动力!
🔥🔥 个人主页 点击🔥🔥

相关文章:
【C++指针】搭建起程序与内存深度交互的桥梁(下)
🔥🔥 个人主页 点击🔥🔥 每文一诗 💪🏼 往者不可谏,来者犹可追——《论语微子篇》 译文:过去的事情已经无法挽回,未来的岁月还可以迎头赶上。 目录 C内存模型 new与…...
.NET开发基础知识1-10
1. 依赖注入(Dependency Injection) 技术知识:依赖注入是一种设计模式,它允许将对象的依赖关系从对象本身中分离出来,通过构造函数、属性或方法参数等方式注入到对象中。这样可以提高代码的可测试性、可维护性和可扩展…...
IEEE PDF Xpress校验出现 :字体无法嵌入问题以及pdf版本问题
文章目录 问题描述一、字体嵌入问题首先查看一下,哪些字体没有被嵌入查看window的font文件夹里的字体下载字体的网站修复字体嵌入问题 二、pdf版本不对 问题描述 在处理IEEE的camera ready的时候,提交到IEEE express的文件没有办法通过validate…...
cookie详解
一、cookie出现原因 http是无状态的,浏览器无法记录当前是哪个人浏览的,所以出现了cookie 作用:会话状态管理(用户登录状态、购物车、游戏分数)、个性化设置(主题、自定义设置)、浏览器行为跟…...
Mayo Clinic Platform在人工智能医疗领域的现状及启示意义研究
一、引言 1.1 研究背景与意义 在科技飞速发展的当下,人工智能(AI)已逐渐渗透至各个行业,医疗领域作为关乎人类生命健康的重要领域,也迎来了人工智能技术带来的深刻变革。人工智能医疗,作为人工智能与医疗行业深度融合的产物,正重塑着全球医疗的格局。 从全球范围来看,…...
Rust基础语法
以下是 Rust 语言基础语法的核心要点,结合与 JavaScript 的对比,帮助前端开发者快速掌握核心概念: 一、变量与常量 1. 变量声明 Rust:变量默认不可变,需用 mut 显式声明可变性。let x 5; // 不可变变量 le…...
如何将 Java 应用做成 EXE 的可执行软件
目录 前言一、情景介绍二、实现步骤1. 打 Jar 包2. 编写 bat 批处理文件3. bat 转 exe 前言 最近使用 GUI 帮朋友写了一个软件,为了方便他处理工作上的重复性且很麻烦的事情,程序是使用 Java 写的,就不得不面对一个问题:我必须将…...
第一篇:系统分析师首篇
目录 一、目标二、计划三、完成情况1.宏观思维导图2.过程中的团队管理和其它方面的思考 四、意外之喜(最少2点)1.计划内的明确认知和思想的提升标志2.计划外的具体事情提升内容和标志 一、目标 通过参加考试,训练学习能力,而非单纯以拿证为目的。 1.在复…...
自动关机监控器软件 - 您的电脑节能助手
## 自动关机监控器 - 您的电脑节能助手 自动关机监控器是一款基于Python开发的实用工具,旨在帮助用户节省电力资源并延长电脑使用寿命。该程序通过监控用户的鼠标和键盘活动,在设定的无活动时间后自动关闭计算机,特别适合需要长时间离开电脑但…...
线程概念与控制(中)
线程概念与控制(上)https://blog.csdn.net/Small_entreprene/article/details/146464905?sharetypeblogdetail&sharerId146464905&sharereferPC&sharesourceSmall_entreprene&sharefrommp_from_link我们经过上一篇的学习,接…...
K8S学习之基础六十二:helm部署memcached服务
helm部署memcached服务 #安装memcached的Chart docker load -i memcache_1_4_36.tar.gz #如果k8s用的是docker做容器运行时,用docker load -i导出镜像 ctr -nk8s.io images import memcache_1_4_36.tar.gz #如果k8s用的是containerd做容器运行时,用ctr…...
CPU 超线程技术以及如何关闭CPU超线程功能
CPU超线程技术介绍 CPU 超线程技术(Hyper-Threading Technology,HT)是英特尔提出的一种同时多线程(Simultaneous Multi-Threading, SMT)实现方式,其核心思想是通过逻辑层面的优化,让单个物理…...
Redis 源码硬核解析系列专题 - 第二篇:核心数据结构之SDS(Simple Dynamic String)
1. 引言 Redis没有直接使用C语言的标准字符串(以\0结尾的字符数组),而是自定义了SDS(Simple Dynamic String)。SDS是Redis的基础数据结构之一,广泛用于键值存储、命令参数等场景。本篇将深入剖析SDS的实现原理、优势以及源码细节。 2. 为什么不用C标准字符串? C字符串…...
1--当「穷举」成为艺术:CTF暴力破解漏洞技术从入门到入刑指南(知识点讲解版)
当「穷举」成为艺术:CTF暴力破解漏洞技术从入门到入刑指南 引言:论暴力破解的哲学意义 “世界上本没有漏洞,密码设得简单了,便成了漏洞。” —— 鲁迅(并没有说过) 想象你是个不会撬锁的小偷,面…...
DHCP报文的详细流程
在DHCP协议的工作流程中,Discover和Request报文使用广播MAC地址,而Offer和ACK报文通常使用单播MAC地址。这种差异源于DHCP协议的设计逻辑和网络通信的实际需求,具体原因如下: 1. DHCP报文交互流程 DHCP的完整流程分为四个阶段…...
通信协议和特征
文章目录 双工时钟电平串并行 双工 全双工:全双工通信允许同一时刻数据在两个方向上同时进行传输。一般来说,全双工的通信都有两根数据线,一根发送,一根接收,二者互不影响。半双工:允许数据在两个方向上传…...
Python 循环全解析:从语法到实战的进阶之路
一、问答题 (1)下面的循环体被重复了多少次?每次循环的输出结果是什么? i1 while i < 10:if i % 2 0:print(i)死循环,没有输出结果 i1 while i < 10:if i % 2 0:print(i)i l死循环,没有输出结果 i 1 while i< 10…...
The Rust Programming Language 学习 (七)
常见集合 使用 Vector 存储表 Vec<T>,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用 新建Vector 为了创建一…...
[GXYCTF2019]禁止套娃1 [GitHack] [无参数RCE]
Git基础 Git信息泄露原理解析及利用总结 - FreeBuf网络安全行业门户 CTF中的GIT泄露_ctf git泄露-CSDN博客 Git结构 dirsearch扫出来一大堆东西(然而这些并没有什么屁用) 但也算起码了解了git结构了吧 /.git/HEAD:表示当前HEAD指针的指…...
从ChatGPT到AutoGPT——AI Agent的范式迁移
一、AI Agent的范式迁移 1. ChatGPT的局限性与Agent化需求 单轮对话的“工具属性” vs. 多轮复杂任务的“自主性” ChatGPT 作为强大的生成式AI,虽然能够进行连贯对话,但本质上仍然是“工具型”AI,依赖用户提供明确的指令,而无法自主规划和执行任务。 人类介入成本过高:提…...
stock-pandas,一个易用的talib的替代开源库。
原创内容第841篇,专注智能量化投资、个人成长与财富自由。 介绍一个ta-lib的平替——我们来实现一下,最高价突破布林带上轨,和最低价突破布林带下轨的可视化效果: cross_up_upper stock[high].copy()# cross_up_upper 最高价突破…...
Spring Cloud Gateway详细介绍简单案例
文章目录 1、Spring Cloud Gateway 详细介绍1.1. 统一入口(Single Entry Point)1.2. 请求路由(Request Routing)1.3. 负载均衡(Load Balancing)1.4. 流量控制(Rate Limiting)1.5. 身…...
鸿蒙原生开发之状态管理V2
一、ArkTS状态变量的定义: State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。 在鸿蒙原生开发中,使用ArkTS开发UI的时候,我们可以…...
矩阵中对角线的遍历问题【C++】
1,按对角线进行矩阵排序 题目链接:3446. 按对角线进行矩阵排序 - 力扣(LeetCode) 【题目描述】 对于一个m*n的矩阵grid,要求对该矩阵进行 变换,使得变换后的矩阵满足: 主对角线右上的所有对角…...
Python小练习系列 Vol.4:迷宫寻路(回溯 + DFS)
🧠 Python小练习系列 Vol.4:迷宫寻路(回溯 DFS) 🚪 本期我们将探索一个二维世界,借助回溯算法帮助角色走出迷宫!这是学习路径搜索类题目的经典案例。 🧩 一、题目描述 给定一个二维…...
[Lc4_dfs] 解数独 | 单词搜索
目录 1.解数独 题解 2.单词搜索 题解 1.解数独 链接:37. 解数独 编写一个程序,通过填充空格来解决数独问题。 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线…...
day17 学习笔记
文章目录 前言一、数组的增删改查1.resize函数2.append函数3.insert函数4.delete函数5.argwhere函数6.unique函数 二、统计函数1.amax,amin函数2.ptp函数3.median函数4.mean函数5.average函数6.var,std函数 前言 通过今天的学习,我掌握了num…...
自动语音识别(ASR)技术详解
语音识别(Automatic Speech Recognition, ASR)是人工智能和自然语言处理领域的重要技术,旨在将人类的语音信号转换为对应的文本。近年来,深度学习的突破推动语音识别系统从实验室走入日常生活,为智能助手、实时翻译、医…...
git | 版本切换的相关指令
常见指令 git log --oneline #查看历史提交 git tag latest-backup # 对当前的提交进行标记,标记名为latest-backup git checkout -b old-version 55b16aa # 切换到[55b16aa]的提交中,并标记为[old-version]的分支 git checkout master …...
19.OpenCV图像二值化
OpenCV图像二值化 图像二值化(Binarization)是图像预处理中的一种常用技术,其目的是将图像中的像素值分为两个类别——通常是“前景”和“背景”或者说0和255。二值化能够简化图像信息,为后续的形态学处理、边缘检测、目标识别等…...

