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

C++零基础到工程实战(5.2.1):指针和引用理论到实战

目录文章摘要一、为什么需要指针和引用1.1 函数参数传递默认会发生复制1.2 大数据复制会影响效率二、指针的基本概念2.1 地址是什么2.2 指针是什么2.3 p、p、*p 的区别1p指针变量中保存的值2p指针变量自己所在的地址3*p通过地址访问对应内存中的值三、32 位程序和 64 位程序中的指针大小3.1 32 位程序为什么指针通常是 4 字节3.2 为什么 32 位程序理论上可以表示 4GB 内存3.3 32 位 int 和 32 位地址不是一回事132位int232位地址3.4 64 位程序为什么指针通常是 8 字节3.5 指针大小和指针类型有什么关系3.6 64 位程序是不是就能真正使用 2^64 字节内存3.7 本节小结四、引用的基本概念4.1 引用就是变量的别名4.2 引用的地址4.3 指针和引用的区别五、指针和引用在函数中的作用5.1 值传递会复制数据5.2 指针传递传递地址可以修改原数据5.3 引用传递像普通变量一样使用也能修改原数据5.4 const 引用减少复制但不允许修改六、返回值安全、nullptr 和完整代码演示6.1 不能返回栈区局部变量的指针6.2 不能返回栈区局部变量的引用6.3 可以返回值、堆区、全局变量或静态变量6.4 nullptr 和 NULL 的区别七、总结文章摘要本节主要学习 C 中指针和引用的基本原理。指针本质上是保存内存地址的变量引用本质上是已有变量的别名。通过指针和引用可以减少函数参数传递时的数据复制提高程序运行效率。同时本节还结合 32 位和 64 位程序说明指针大小的区别分析p、p、*p的含义并重点讲解为什么不能返回栈区局部变量的指针或引用以及为什么现代 C 推荐使用nullptr而不是NULL。一、为什么需要指针和引用1.1 函数参数传递默认会发生复制在 C 中函数传参默认是值传递。值传递的意思是调用函数时会把实参的数据复制一份传给函数内部的形参。例如#include iostream using namespace std; void test(int x) { x; } int main() { int a{10}; test(a); cout a a endl; // 10 return 0; }运行结果a 10这里虽然在test函数中执行了x;但是a的值没有改变。原因是x 是 a 的副本函数内部修改的是副本不是原来的变量。1.2 大数据复制会影响效率如果传递的是int、char、double这类小数据复制一份影响不大。但是如果传递的是string vector 数组 结构体 类对象这些数据可能比较大每次函数调用都复制一份就会增加内存和时间开销。所以 C 中常用指针传参 引用传参它们的共同目的就是不复制整份数据而是直接访问原来的内存空间。二、指针的基本概念2.1 地址是什么程序运行时变量会被放到内存中。内存可以理解为一排连续的小格子每个格子都有编号这个编号就是内存地址。例如int x{10}; cout x x endl; cout x x endl;其中x表示变量中保存的值。x表示变量x的内存地址。可能输出x 10 x 000000B938CFF914这里的000000B938CFF914就是变量x在内存中的地址。注意地址不是固定的每次运行程序时系统分配的地址可能不同。2.2 指针是什么指针也是变量。只不过普通变量存放的是普通数据而指针变量存放的是内存地址。例如int x{10}; int* p{x};这句话可以理解为定义了一个 int 类型的指针变量 p p 中保存的是变量 x 的地址。也就是说p x如果x的地址是000000B938CFF914那么p中保存的值也是000000B938CFF914所以cout p endl; cout x endl;输出结果一般是一样的。2.3p、p、*p的区别这是初学指针时最容易混淆的地方。先看代码int x{10}; int* p{x}; cout *p *p endl; cout p p endl; cout p p endl;分别解释如下。1p指针变量中保存的值cout p endl;p表示指针变量中保存的内容。因为p保存的是x的地址所以p x例如p 000000B938CFF914说明p指向了地址为000000B938CFF914的内存空间。也就是指向了变量x。2p指针变量自己所在的地址指针变量p自己也是一个变量。既然是变量它自己也要存放在内存中所以它自己也有地址。cout p endl;这里输出的是指针变量 p 自己的地址例如p 000000B938CFF914 p 000000B938CFF8F8可以这样理解x 是普通变量 p 是保存 x 地址的指针变量 p 是指针变量 p 自己的地址所以p 里面保存的是 x 的地址 p 表示 p 自己在内存中的地址。3*p通过地址访问对应内存中的值cout *p endl;*p表示根据 p 保存的地址访问那块内存中的值。因为p保存的是x的地址所以*p x例如int x{10}; int* p{x}; cout *p endl; // 10如果执行(*p);就相当于x;因为p指向的是x所以通过*p修改的就是x本身。三、32 位程序和 64 位程序中的指针大小在学习指针时经常会看到一句话32 位程序中指针通常占 4 字节64 位程序中指针通常占 8 字节。这句话背后其实和地址编号方式有关。指针变量中保存的是内存地址而内存地址本质上可以理解为一串编号。程序是 32 位还是 64 位会影响这个地址编号最多能用多少个二进制位来表示。3.1 32 位程序为什么指针通常是 4 字节32 位程序中的“32 位”可以简单理解为内存地址通常使用 32 个二进制位来表示而计算机中1 字节 8 位所以32 位 32 ÷ 8 4 字节因此在常见的 32 位程序中一个指针变量通常占用4 字节例如int* p; cout sizeof(p) endl;如果程序按照 32 位方式编译通常输出4这里要注意sizeof(p)查看的是指针变量 p 自己占多少字节不是p指向的数据占多少字节。3.2 为什么 32 位程序理论上可以表示 4GB 内存32 位地址使用 32 个二进制位表示。每一个二进制位只有两种可能0 或 1所以 32 个二进制位一共可以组合出2 × 2 × 2 × ... × 2一共乘 32 次也就是2^32计算结果为2^32 4,294,967,296也就是大约42.9 亿个不同的地址编号。内存地址通常是以字节为单位编号的。也就是说一个地址编号对应一个字节空间所以如果有2^32 个地址编号那么理论上就可以表示2^32 个字节也就是2^32 Byte接下来再换算成 GB。计算机中常用的换算关系是1KB 1024 Byte 2^10 Byte1MB 1024 KB 2^20 Byte1GB 1024 MB 2^30 Byte所以1GB 2^30 Byte因此2^32 Byte ÷ 2^30 Byte 2^(32 - 30) 2^2 4所以2^32 Byte 4GB也就是说32 位程序理论上最多可以表示 4GB 的地址空间这里说的是理论地址空间不是说程序一定能完整使用 4GB 内存。因为实际运行时操作系统还会占用一部分地址空间例如操作系统内核空间 动态链接库 栈区 堆区 程序代码区 全局数据区所以实际可用空间通常会少于理论值。3.3 32 位 int 和 32 位地址不是一回事这里有一个非常容易混淆的问题32 位 int 32 位程序地址它们都和 32 位有关但含义完全不同。132位int如果int是 4 字节也就是 32 位那么一个有符号int的范围通常是-2^31 到 2^31 - 1大约是-2147483648 到 2147483647也就是大约-21 亿 到 21 亿这是int类型能够表示的整数值范围。232位地址但是 32 位地址表示的是2^32 个不同的内存地址编号它表示的是地址空间数量。所以不能把这两个概念混在一起。更准确地说32 位 int 表示一个整数值占 4 字节范围大约是 -21 亿到 21 亿。 32 位地址 表示一个内存地址占 4 字节理论上可以表示 4GB 地址空间。因此不能简单说int 最大是 2GB这个说法不准确。应该说int 的正数最大值大约是 21 亿 32 位程序理论地址空间是 4GB。3.4 64 位程序为什么指针通常是 8 字节64 位程序中地址通常使用 64 个二进制位来表示。因为1 字节 8 位所以64 位 64 ÷ 8 8 字节因此在常见的 64 位程序中指针变量通常占用8 字节例如#include iostream using namespace std; int main() { int* p1 nullptr; double* p2 nullptr; char* p3 nullptr; cout sizeof(p1) endl; cout sizeof(p2) endl; cout sizeof(p3) endl; return 0; }如果程序按照 64 位方式编译通常输出8 8 8可以发现int*、double*、char* 的大小都是 8 字节这是因为指针变量中保存的都是地址只要是在同一个 64 位程序中地址大小通常都是一样的所以不同类型的指针变量本身大小也通常一样。3.5 指针大小和指针类型有什么关系虽然下面几个指针大小可能都是 8 字节int* p1; double* p2; char* p3;但是它们的含义并不一样。区别在于int* 表示这个地址上的数据按照 int 类型解释double* 表示这个地址上的数据按照 double 类型解释char* 表示这个地址上的数据按照 char 类型解释例如int x{10}; int* p{x};这里p保存的是x的地址。当我们写*p程序会从p保存的地址开始按照int类型读取数据。如果int占 4 字节那么程序就会从该地址开始读取 4 个字节并把这 4 个字节解释成一个整数。所以指针变量本身的大小主要由程序位数决定指针指向数据的解释方式由指针类型决定。总结成一句话指针大小看平台解引用方式看类型。3.6 64 位程序是不是就能真正使用 2^64 字节内存从理论上说64 位地址可以表示2^64 个地址编号如果一个地址编号对应一个字节那么理论地址空间就是2^64 Byte这个数字非常大远远超过 4GB。但是实际计算机并不会真的给一个程序完整使用2^64 Byte的内存空间。原因包括硬件内存容量有限 CPU 实际支持的地址位数有限 操作系统会限制进程可用地址空间 程序本身也不会真的需要这么大的空间在学习阶段只需要先记住32 位程序中指针通常是 4 字节 64 位程序中指针通常是 8 字节。这和指针指向int、double、char没有直接关系。3.7 本节小结1. 指针变量保存的是内存地址。 2. 32 位程序中地址通常使用 32 个二进制位表示 所以指针变量通常占 4 字节。 3. 64 位程序中地址通常使用 64 个二进制位表示 所以指针变量通常占 8 字节。 4. 32 位地址理论上可以表示 2^32 个字节 也就是 4GB 地址空间。 5. int 的 32 位和地址的 32 位不是一回事。 int 讨论的是数值范围地址讨论的是内存编号范围。 6. 指针变量大小主要由程序位数决定 指针指向空间的解释方式由指针类型决定。用一句话总结指针变量保存的是地址32 位地址通常占 4 字节64 位地址通常占 8 字节int*、double*、char*的区别不是指针变量大小不同而是解引用时按照不同的数据类型解释内存。四、引用的基本概念4.1 引用就是变量的别名引用可以理解为给已经存在的变量起一个别名。例如int x{10}; int ref{x};这里ref就是x的引用。也就是说ref 和 x 表示的是同一块内存空间。代码示例#include iostream using namespace std; int main() { int x{10}; int ref{x}; cout x x endl; cout ref ref endl; ref; cout x x endl; return 0; }运行结果x 10 ref 10 x 11因为ref 修改的就是 x 本身。4.2 引用的地址代码int x{10}; int ref{x}; cout x x endl; cout ref ref endl;运行结果可能是x 000000B938CFF914 ref 000000B938CFF914可以发现ref x这说明ref 不是新变量而是 x 的别名。初学阶段可以先理解引用在使用层面不看作一个独立变量而是原变量的另一个名字。不过从底层实现角度看编译器有时可能会用类似指针的方式实现引用但语法层面我们重点掌握“别名”这个概念就可以。4.3 指针和引用的区别对比项指针引用本质保存地址的变量已有变量的别名是否必须初始化可以不初始化但非常危险必须初始化是否可以为空可以是nullptr正常情况下不存在空引用是否可以改变指向可以重新指向其他变量初始化后不能重新绑定使用方式需要*p解引用像普通变量一样使用取地址含义p是指针变量自己的地址ref是被引用变量的地址例如int a{10}; int b{20}; int* p{a}; p b; // 指针可以重新指向 b int ref{a}; // ref b; 不是让 ref 重新绑定 b // 而是把 b 的值赋给 a这里特别注意ref b;不是让ref改成引用b。而是相当于a b;引用一旦绑定到某个变量就不能再重新绑定到另一个变量。五、指针和引用在函数中的作用5.1 值传递会复制数据void testValue(int x) { x; } int main() { int a{10}; testValue(a); cout a endl; // 10 return 0; }这里x是a的副本。所以函数内部修改x不会影响外部变量a。5.2 指针传递传递地址可以修改原数据void testPointer(int* p) { (*p); } int main() { int a{10}; testPointer(a); cout a endl; // 11 return 0; }这里传递的是a 的地址函数内部通过*p找到原来的变量并进行修改。所以a的值会变成11。指针传参的特点是调用时需要传地址函数内部需要解引用可以传 nullptr使用前最好判断是否为空。更加安全的写法void testPointer(int* p) { if (p nullptr) { return; } (*p); }5.3 引用传递像普通变量一样使用也能修改原数据void testReference(int ref) { ref; } int main() { int a{10}; testReference(a); cout a endl; // 11 return 0; }引用传参的特点是调用时像普通变量一样 函数内部也像普通变量一样 不需要写 * 一般不会为空 适合表达“这个参数必须存在”。所以引用传参通常比指针传参写起来更简单。5.4 const 引用减少复制但不允许修改如果函数只是读取数据不想修改原数据推荐使用const 引用。例如#include iostream #include string using namespace std; void printString(const string str) { cout str endl; }这样写有两个好处1. 不会复制整个 string提高效率2. 函数内部不能修改 str更安全。如果写成值传递void printString(string str) { cout str endl; }每次调用函数时都会复制一份string。所以实际开发中经常看到void func(const vectorint data); void func(const string name); void func(const Student stu);这类写法非常常见。六、返回值安全、nullptr 和完整代码演示6.1 不能返回栈区局部变量的指针错误代码int* getPointer() { int x{10}; return x; }这个代码看起来好像返回了x的地址但实际上是错误的。原因是x 是函数内部的局部变量存放在栈区函数执行结束后x 会被销毁。虽然地址被返回出去了但是这个地址指向的空间已经失效。这种指针通常叫做野指针继续访问它可能出现打印乱码 结果不固定 程序崩溃 看似正常但存在隐藏错误所以不能因为某一次运行结果正常就认为代码没有问题。6.2 不能返回栈区局部变量的引用错误代码int getReference() { int x{10}; return x; }这个代码同样错误。因为x是函数内部的局部变量函数结束后就销毁了。返回它的引用相当于返回了一个已经失效变量的别名。这也是非常危险的。6.3 可以返回值、堆区、全局变量或静态变量更安全的写法是返回值int getValue() { int x{10}; return x; }这种方式没有问题。现代 C 编译器通常会进行优化很多情况下不用过度担心返回值复制的问题。如果返回堆区空间int* createValue() { int* p new int{10}; return p; }虽然函数结束后堆区数据不会销毁但是需要手动释放int* p createValue(); cout *p endl; delete p; p nullptr;如果忘记delete就会造成内存泄漏。如果返回静态变量引用int getStaticValue() { static int x{10}; return x; }static局部变量的生命周期是整个程序运行期间所以函数结束后不会销毁。但是它也有问题多次调用访问的是同一个变量 如果被修改后续调用也会受到影响 多线程情况下可能有安全问题。所以使用指针和引用时一定要思考当前访问的这块内存在程序运行期间是否还存在6.4 nullptr 和 NULL 的区别在现代 C 中推荐使用int* ptr nullptr;而不是int* ptr NULL;原因是nullptr 是 C11 引入的专门表示空指针的关键字。而NULL在 C 中很多时候本质上是0或0L容易和整数发生混淆。例如#include iostream using namespace std; void test(int) { cout int endl; } void test(int*) { cout int* endl; } int main() { test(NULL); // 可能调用 test(int) test(nullptr); // 调用 test(int*) return 0; }所以在现代 C 中建议统一写nullptr不要再习惯性使用NULL。七、总结本节主要围绕 C 中的指针和引用展开学习。指针和引用都是 C 中非常重要的基础知识它们经常用于函数参数传递、内存访问、数据修改以及后续面向对象编程中。1函数参数默认采用值传递也就是会把实参复制一份给形参。如果传递的是int、char这类小数据复制开销不大但如果传递的是string、vector、结构体或类对象每次复制都会带来额外的性能开销。因此指针和引用的一个重要作用就是减少数据复制让函数可以直接访问原来的数据空间。2指针的本质是保存地址的变量例如int x{10}; int* p{x};这里p保存的是变量x的地址*p表示通过这个地址访问x的值p表示指针变量p自己在内存中的地3引用的本质是变量的别名。例如int x{10}; int ref{x};这里ref就是x的另一个名字ref和x访问的是同一块内存空间。通过ref修改数据本质上就是修改x本身。引用必须初始化并且初始化之后不能再重新绑定到其他变量。4关于 32 位和 64 位程序中的指针大小需要记住指针变量中保存的是地址所以指针大小和程序的地址位数有关。常见情况下32 位程序中的指针通常占 4 字节64 位程序中的指针通常占 8 字节。不同类型的指针例如int*、double*、char*在同一个程序中指针变量本身的大小通常一样区别在于解引用时按照不同的数据类型解释内存。5函数传参方面值传递、指针传递和引用传递的区别可以总结如下传参方式是否复制数据是否能修改原变量使用特点值传递会复制不能修改原变量简单安全但大对象复制开销大指针传递复制地址可以修改原变量可以为空需要判断nullptr引用传递不复制完整数据可以修改原变量写法简单必须绑定有效对象const 引用不复制完整数据不能修改原变量适合只读大对象开发中常用6使用指针和引用时最重要的是关注内存生命周期。不能返回栈区局部变量的指针或引用因为函数结束后局部变量会被销毁此时返回出去的地址或引用已经失效继续访问就可能产生野指针、乱码、程序崩溃等问题。相对安全的方式是返回值或者在明确管理内存生命周期的情况下返回堆区、全局变量或静态变量。此外在现代 C 中空指针推荐使用nullptr而不是NULL因为nullptr是 C11 引入的专门表示空指针的关键字语义更加清晰也能避免和整数0混淆。本节需要重点掌握指针保存地址引用是变量别名使用指针和引用时必须时刻关注它们指向或绑定的内存是否仍然有效。
http://www.gsyq.cn/news/1335263.html

相关文章:

  • Linux驱动开发避坑:为什么你的GPIO申请总失败?从devm_gpio_request_one源码看设备资源管理
  • 初创团队如何利用Taotoken的Token Plan套餐有效控制AI开发成本
  • 异步复位、异步复位-同步释放
  • 告别WSL网络隔离:用桥接模式让Ubuntu 22.04和Windows 11共享同一个局域网IP段
  • 2026年靠谱阳台晾衣架TOP5品牌技术实力深度剖析:电动衣架/落地晾衣架/遥控晾衣机/遥控晾衣架/隐藏式晾衣架/选择指南 - 优质品牌商家
  • 实验二:防火墙路由通信与安全访问实验
  • 【养龙虾指南:把 AI 养成“一次构建、永久运行“的自我进化系统】
  • 量化感知训练中的权重震荡:成因、影响与抑制策略
  • 5分钟终极指南:Adobe-GenP通用激活工具快速上手
  • 嵌入式储能监控系统开发实战:从核心板选型到算法部署
  • GEFFEN格芬智能云控分布式电源管理系统GF-SPMS8
  • 别再到处找教程了!用Docker Compose一键部署RuoYi-Cloud微服务全家桶(含Nacos 2.x + Sentinel)
  • 论文查重,重复率太高怎么办?
  • 华为ENSP模拟器实战:手把手教你配置LACP链路聚合,实现带宽翻倍与链路备份
  • 2026年腾讯云OpenClaw/Hermes Agent配置Token Plan保姆式教学
  • 好用的合同管理系统怎么选?8个真实选型标准
  • 别再只改POI版本了!解决EasyExcel报错,你可能还漏了xmlbeans这个关键依赖
  • 从Hi-Fi耳机到5G基站:聊聊FIR和IIR滤波器那些意想不到的应用场景
  • 别再只用串口了!手把手教你用STM32CubeMX配置LIN总线(基于TJA1020收发器)
  • 把OpenWrt路由器变成轻量Web服务器:手把手教你配置NGINX并挂载外部存储
  • 合宙ESP32 S3接SD卡模块总失败?可能是HSPI和VSPI的坑(附完整引脚配置)
  • DistroAV:基于NDI技术的OBS Studio网络音视频传输解决方案
  • c语言之时间格式化之转换为yyyy-MM-dd‘T‘HH:mm:ss.SSSZ 例如“2026-12-17T17:26:40.979+0700”
  • Qt QAction的隐藏玩法:除了菜单,还能用在工具栏、快捷键和右键菜单?
  • 避坑指南:Docker Buildx多架构构建时,如何正确配置BuildKit和insecure-registry推送
  • STM32CubeMX安装后,HAL库到底怎么选?在线安装慢、离线包找不到的终极解决指南
  • Perplexity文化新闻搜索效率翻倍:从冷启动到高信噪比输出的7个被低估的底层参数配置
  • 长沙自动变速箱维修哪家强?这些公司口碑好
  • 别再纠结软件IIC了!用STM32硬件IIC驱动0.96寸OLED,实测代码稳定不掉线
  • 【软考高级架构】论文范文23——论分布式事务架构设计及应用