C++——const、指针和引用,深度理解

2023-11-06

const修饰符

const修饰符可以定义常量,相比define,const修饰的常量的类型更为确定,而不是文本替换。在 C++ 中,const 也可以修饰对象。且一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数),因为const对象的非const成员可能修改对象的数据,这是不安全的,我们希望const对象的数据无论如何都不被修改,因此会被限定访问权限。

const和指针

const int类型的指针

	int num1 = 10;
	int num2 = 20;

	int* pNum1 = &num1;
	// 这是一个指针,指向的数据类型是int
	// 这个的权限没有变化,因此可以正常读写和修改指向。
	*pNum1 = 20;
	pNum1 = &num2;

	const int* pNum2 = &num1;
	// 这是一个指针,指向的数据类型是const int。
	// 把数据的int权限限定为const。
	// 因此无法修改指针的内容,但是可以修改指向。
	*pNum2 = 20;//error
	pNum2 = &num2;

常量指针

	const int num1 = 10;
	const int num2 = 20;
	int num3 = 30;

	const int* pNum1 = &num1;
	// 这是一个指针,指向的数据类型是const int。
	// 数据类型没有变化,因此访问权限跟num1本身一样。
	// 无法修改值,但是可以修改指针的指向。
	pNum1 = &num2;
	pNum1 = &num3;// 这里涉及权限的缩小,把原来num3的权限缩小为const。
	num1 = 20;//error
	*pNum1 = 20;//error

	int* pNum2 = &num1;//error
	// 这是一个指向int的指针,但是指向了const int类型的num1
	// 涉及权限的放大,这是不允许的。

指针常量

	int num1 = 10;
	int num2 = 20;
	int* const pNum1 = &num1;
	// 这是一个const指针pNum1,指向的数据类型是int*。
	// 因此这个指针的内容因为是int所以可以修改,
	// 但是指向不能修改,因为这是一个指针常量。
	pNum1 = &num2;//error
	*pNum1 = 30;

这块内容比较晦涩,需要反复记忆和理解,不然及其容易遗忘。写这篇文章的原因就是因为这个知识点遗忘太多次了,以我为戒

引用

对于像 char、bool、int、float 等基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。而数组、结构体、对象是一系列数据的集合,数据的数量可能成千上万,频繁的内存拷贝会消耗很多时间,降低程序的效率。

C和C++ 禁止在函数调用时直接传递数组的内容,而是强制传递数组指针。而对于结构体和对象没有这种限制,调用函数时既可以传递指针,也可以直接传递内容;为了提高效率,传递指针少去了结构体和对象内容拷贝的时间。

但在 C++ 中,我们有了一种比指针更加便捷的传递聚合类型数据的方式,那就是引用(Reference)。引用是C++对C语言的重要扩充。引用就是某一变量(目标)的别名,对引用的操作与对变量直接操作完全一样。通过这个别名和原来的名字都能够找到这份数据。引用类似于 Windows 中的快捷方式和Linux中的软链接,一个可执行程序可以有多个快捷方式,但是一个快捷方式不能改变指向的可执行程序。

我们知道,参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。

引用必须在定义的同时初始化,并且以后不能再引用其它对象,这有点类似于常量(const 变量)。

	int num1 = 10;
	
	int& ref0; //error
	int& ref1 = 20;//error
	int&& ref2 = 20;// 右值引用
	int& ref3 = num1;// 左值引用
	// num1 和 ref3 地址相同,是同一个对象
	int num2 = 30;
	ref3 = num2; // 等价于 num1 = 30;
	// 实际是修改了ref3引用的num1的值,而不是把num3重定向为num4的引用
	string str1 = "Hello";
	string& str2 = str1;
	// str1和str2地址相同

指针和引用

指针是一个变量,能够在监视窗口中获取到指针的地址。而获取引用的地址实际是获取被引用对象的地址。
在这里插入图片描述
这样看起来好像引用并不占内存空间,实际上,在编译时,编译器会把引用视作一个const指针,即是一个指向不可变的指针。引用实际上与指针同样占用内存空间!这点可以在程序的汇编代码中看出。
在这里插入图片描述

指针数组和引用数组

当我们拥有一批指针,可以用一个这种指针类型的数组来存储这批指针。

int num1 = 10;
int* p1 = &num1;
int* p2 = &num1;
int* p3 = &num1;
int* p4 = &num1;
int* parr[4] = { p1,p2,p3,p4 };

但是如果引用按照同样的方式会报错

int& r1 = num1;
int& r2 = num1;
int& r3 = num1;
int& r4 = num1;
int& ref[4] = { r1,r2,r3,r4 };//error

因为数组是一个由若干个元素所组成的集合,所以无法建立一个由引用组成的集合。但是可以建立数组的引用

const int (&ref)[4] = { r1,r2,r3,r4 };

为什么要加上const ?因为 { r1, r2 , r3 , r4 } 此时是个字面值数组,是保存在代码段里的只读内容,如果不加,编译会报错。而且也不能修改数组的内容。

string str1 = "Hello";
string& sr1 = str1;
string& sr2 = str1;
string& sr3 = str1;
const string(&ref1)[] = { sr1,sr2,sr3 };

在这里插入图片描述

指针和引用的区别

  1. 引用定义时必须初始化,指针不需要。没有空引用,但有空指针。
  2. 引用指定一个对象后,不能再引用其他对象;指针可以指向任意同类型对象。
  3. sizeof:引用的结果为被引用类型的大小,而不是对象的大小;指针始终是地址空间所占字节个数(32位平台下占4个字节)。
  4. 引用自增相当于被引用对象执行加一操作;指针自增是向后偏移一个该类型大小的字节。
  5. 有多级指针,但是没有多级引用。
  6. 访问数据时指针要解引用,引用不需要。
  7. 引用更为安全,不存在野引用。
    在这里插入图片描述
    对于第三点的补充。

引用的使用场景

普通值传递参数和返回值时,不会传递实参本身,而是传递实参的一份内存中的临时拷贝,所以当遇到数据量非常大的结构时,效率会很低下,引用作为函数参数和返回值会有很大的改善(假设返回的是堆上的对象,如果是栈上的对象涉及右值引用)。

引用作函数参数

在定义或声明函数时,我们可以将函数的形参指定为引用,这样在调用函数时就会将实参和形参绑定在一起,让它们都指代同一份数据。如果修改了形参的数据,那么实参的数据也会被修改。从而和指针传参一样实现在函数内部修改函数和外部参数的效果。

template<typename T>
void swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

引用作返回值

引用除了可以作为函数形参,还可以作为函数返回值,在将引用作为函数返回值时应该注意不能返回局部数据的引用(例如局部变量、局部对象、局部数组等),因为当函数调用完成后局部数据就会被销毁,有可能在下次使用时数据就不存在或被覆盖了。返回的数据必须是被static修饰的静态变量、或全局变量、或动态开辟的内存数据等不会随着函数调用的结束而被销毁的数据。

int& test(int num) 
{
	int newNum = num + num;
	return newNum;  //返回局部数据的引用
}

这样的结果在不同的编译器下会不可预料。

int& add(int a, int b)
{
	int c = 0;
	c = a + b;
	return c;
}

int main()
{
	int& ref = add(10, 20);
	cout << ref << endl;
	add(20, 30);
	cout << ref << endl;
	cout << ref << endl;
	cout << ref << endl;

在这里插入图片描述
因为调用函数会创建函数栈帧,之后再调用函数,创建的栈帧可能将之前的覆盖了,但是ref还是之前内存内容的引用,因此可能会被覆盖和修改!

const和引用

const和引用的情况相比const和指针简单很多,因为少去了指向修改的问题。

int num1 = 10;
const int& ref1 = num1;
ref1 = 20;//error 常引用不可修改

const int num2 = 20;
const int& ref2 = 20;
const int& ref3 = num2;
int& ref4 = num2;//error 涉及权限放大

右值引用

右值引用是C++11引入的用于补齐C++在内存效率上的短板的,在某些场景,例如STL中string的operator + 操作,push_back操作如果没有右值引用,都会产生深拷贝,避免不了效率的问题。
我将会写一篇文章详细讲解C++11的新特性,如右值引用,移动语义等!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++——const、指针和引用,深度理解 的相关文章

  • 调用 McAfee 病毒扫描引擎

    我收到客户的请求 要求使用他们服务器上的 McAfee 病毒扫描将病毒扫描集成到应用程序中 我做了一些调查 发现 McScan32 dll 是主要的扫描引擎 它导出各种看起来有用的函数 我还发现提到了 McAfee Scan Engine
  • 没有特殊字符的密码验证器

    我是 RegEx 的新手 已经进行了大量搜索 但没有找到任何具体内容 我正在编写一个验证密码字符串的正则表达式 可接受的字符串必须至少具有 4 种字符类型中的 3 种 数字 小写字母 大写字母 特殊字符 我对包含有一个想法 也就是说 如果这
  • 通过引用传递 [C++]、[Qt]

    我写了这样的东西 class Storage public Storage QString key const int value const void add item QString int private QMap
  • 机器Epsilon精度差异

    我正在尝试计算 C 中双精度数和浮点数的机器 epsilon 值 作为学校作业的一部分 我在 Windows 7 64 位中使用 Cygwin 代码如下 include
  • free 和 malloc 在 C 中如何工作?

    我试图弄清楚如果我尝试 从中间 释放指针会发生什么 例如 看下面的代码 char ptr char malloc 10 sizeof char for char i 0 i lt 10 i ptr i i 10 ptr ptr ptr pt
  • 如何在 C++ 中标记字符串?

    Java有一个方便的分割方法 String str The quick brown fox String results str split 在 C 中是否有一种简单的方法可以做到这一点 The 增强分词器 http www boost o
  • C++ 多行字符串原始文字[重复]

    这个问题在这里已经有答案了 我们可以像这样定义一个多行字符串 const char text1 part 1 part 2 part 3 part 4 const char text2 part 1 part 2 part 3 part 4
  • 需要帮助优化算法 - 两百万以下所有素数的总和

    我正在尝试做一个欧拉计划 http projecteuler net问题 我正在寻找 2 000 000 以下所有素数的总和 这就是我所拥有的 int main int argc char argv unsigned long int su
  • C# 列表通用扩展方法与非通用扩展方法

    这是一个简单的问题 我希望 集合类中有通用和非通用方法 例如List
  • C# - 当代表执行异步任务时,我仍然需要 System.Threading 吗?

    由于我可以使用委托执行异步操作 我怀疑在我的应用程序中使用 System Threading 的机会很小 是否存在我无法避免 System Threading 的基本情况 只是我正处于学习阶段 例子 class Program public
  • 如何在当前 Visual Studio 主机内的 Visual Studio 扩展中调试使用 Roslyn 编译的代码?

    我有一个 Visual Studio 扩展 它使用 Roslyn 获取当前打开的解决方案中的项目 编译它并从中运行方法 程序员可以修改该项目 我已从当前 VisualStudioWorkspace 成功编译了 Visual Studio 扩
  • 复制目录下所有文件

    如何将一个目录中的所有内容复制到另一个目录而不循环遍历每个文件 你不能 两者都不Directory http msdn microsoft com en us library system io directory aspx nor Dir
  • 如何实例化 ODataQueryOptions

    我有一个工作 简化 ODataController用下面的方法 public class MyTypeController ODataController HttpGet EnableQuery ODataRoute myTypes pub
  • 为什么 isnormal() 说一个值是正常的,而实际上不是?

    include
  • C++ 继承的内存布局

    如果我有两个类 一个类继承另一个类 并且子类仅包含函数 那么这两个类的内存布局是否相同 e g class Base int a b c class Derived public Base only functions 我读过编译器无法对数
  • 对于某些 PDF 文件,LoadIFilter() 返回 -2147467259

    我正在尝试使用 Adob e IFilter 搜索 PDF 文件 我的代码是用 C 编写的 我使用 p invoke 来获取 IFilter 的实例 DllImport query dll SetLastError true CharSet
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • C++ 中的 include 和 using 命名空间

    用于使用cout 我需要指定两者 include
  • C++ 中的参考文献

    我偶尔会在 StackOverflow 上看到代码 询问一些涉及函数的重载歧义 例如 void foo int param 我的问题是 为什么会出现这种情况 或者更确切地说 你什么时候会有 对参考的参考 这与普通的旧参考有何不同 我从未在现
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么

随机推荐

  • C语言快速排序,以及注意点。

    快速排序尤其适用于对大数据的排序 它的高速和高效无愧于 快速 两个字 虽然说它是 最常用 的 可对于初学者而言 用它的人却非常少 因为虽然很快 但它也是逻辑最复杂 最难理解的算法 因为快速排序要用到递归和函数调用 快速排序所采用的思想是分治
  • SpringBoot2.0.5.RELEASE 整合Activiti7启动后不创建表

    环境描述 SpringBoot版本 2 0 5 Activiti版本 7 0 0 Beta3 MySQL版本 8 011 解决办法 在数据库访问的地址上添加配置 nullCatalogMeansCurrent true 问题原因 从mysq
  • java输出相对路径

    在 Java 中 您可以使用 java io File 类来获取文件或目录的相对路径 下面是一个简单的示例 展示了如何使用 File 类获取文件或目录的相对路径 import java io File public class Main p
  • QProgressDialog进度条类

    QProgressDialog 常用API 简单的使用 QProgressDialog类是QDialog的子类 通过这个类我们可以得到一个带进度条的对话框窗口 常用API 构造函数 参数 labelText 对话框中显示的提示信息 canc
  • Centos8安装英伟达显卡驱动并通过docker部署深度学习环境

    20201107 每个人的机器和实际需要的环境都不一样 本文只是提供了在自己实验室centos8上的部署过程 部署过程中 没有什么问题 请谨慎参考本篇文章 以免浪费宝贵时间 0 引言 之前的时候 在实验室的深度学习服务器上安装深度学习的环境
  • NStepSCAN和FSCAN

    NStepSCAN 在最短寻道时间优先 SSTF 扫描算法 SCAN 和循环扫描算法 CSCAN 中 都可能出现磁臂停留在某处不动的情况 例如 有一个或几个进程对某一磁道有较高的访问频率 这些进程反复请求对某一磁道的I O操作 从而垄断了整
  • Git将其他分支合并至主分支

    主要思想 把分支代码合并到master 合给谁 就先切换到谁的分支 1 当前分支是dev 开发完成后 需要合并到master分支 先把该提交的提交 需要push的push完成后 再切换分支 否则也会告诉你要提交本地代码才可以切换分支 2 本
  • 6. JVM调优工具详解及调优实战

    JVM性能调优 1 前置启动程序 1 1 Jmap 1 1 1 Jmap查询内存信息 1 1 2 Jmap查询堆信息 1 1 3 jmap查询堆内存dump 1 2 Jstack 1 3 远程连接jvisualvm 1 4 jstack找出
  • Mongo 数据导出、导入

    1 下载 mongodb database tools windows 2 解压 打开window Powershell 或 doc命令窗口 使用命令进入解压后的文件夹 3 导出数据命令 mongodump exe h 127 0 0 1
  • 2021-06-23 各种经典卷积神经网络总结

    各种经典卷积神经网络总结 1 原始卷积 Vanilla Convolution 2 分组卷积 Group convolution 2 1 组卷积案例1 2 2 组卷积案例2 Resnext 2 3 ShuffleNet 3 转置卷积 Tra
  • 遥感变化检测综述 Change Detection Based on Artificial Intelligence:State-of-the-Art and Change

    遥感变化检测综述 Change Detection Based on Artificial Intelligence State of the Art and Change 现存的变化检测综述主要专注于在多时态高光谱图像 HSIs 和高空间
  • 终极秘密---------windows里藏着9.11的惊天大密码

    终极秘密 windows里藏着9 11的惊天大密码 神秘连锁 密码 泄漏恐怖分子袭美玄机 方法 用WORD 编辑文档输入Q33NY 必须大写 这是9 11撞击世界贸易中心的沙特勇士们乘坐的航班号 第三 将字体大小改到72 最后 将字体转成
  • JS实现简单的购物车

    以下是一个基本的 JS 购物车实现 由于是实现基本的功能 就不弄得多复杂了 代码可以直接Ctrl c v 大家可以试一试 HTML div h2 产品列表 h2 ul li h3 商品1 h3 p 价格 10元 p li ul div
  • SVN 报错:does not support the HTTP/DAV protocol

    原因 我是直接粘贴了上面的网址 而正确做法应该是 点击checkout 复制这个里面的url
  • 图像色彩编码YUV(YCbCr)的基本知识

    参考地址 https www cnblogs com lifan3a articles 4930182 html YUV与YCbCr的定义 YCbCr是DVD 摄像机 数字电视等消费类视频产品中 常用的色彩编码方案 YCbCr 有时会称为
  • No Such Property: Scope For Class: Com.android.build.gradle.internal.variant.ApplicationVariantData

    No Such Property Scope For Class Com android build gradle internal variant ApplicationVariantData 集成360开源的Replugin出现了这个问
  • 软件测试-测试用例的经典例子

    一 等价类划分问 某程序规定 输入三个整数 a b c分别作为三边的边长构成三角形 通过程序判定所构成的三角形的类型 当此三角形为一般三角形 等腰三角形及等边三角形时 分别作计算 用等价类划分方法为该程序进行测试用例设计 三角形问题的复杂之
  • python os模块示例讲解

    os模块包含普遍的操作系统功能 提供了丰富的方法用来处理文件和目录以及一些系统相关的信息的获取 利用这个模块可以写出与平台无关的程序 比如就是使用os sep可以取代操作系统特定的路径分割符 本模块提供一种可移植的方式来使用依赖于操作系统的
  • Ubuntu上安装Boost C++以及Boost.Python的过程和经验

    由于实验的需要 想运行一下这个项目 https github com luckiezhou DynamicTriad 和所有科研相关类的repo一样 要真正用起来还得填很多坑 不得不说 这个repo的作者已经足够认真负责 但是要跑起来还是不
  • C++——const、指针和引用,深度理解

    const修饰符 const修饰符可以定义常量 相比define const修饰的常量的类型更为确定 而不是文本替换 在 C 中 const 也可以修饰对象 且一旦将对象定义为常对象之后 就只能调用类的 const 成员 包括 const