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

CPP引用

引用

一、C++ 引用

C++ 中的引用 (Reference) 是一种特殊的变量,本质上是一个已存在的变量的别名。它提供了一种间接访问其他变量的方式。

引用本身不占用额外的内存空间来存储数据,而是与一个已存在的变量绑定,通过引用访问或修改的就是该绑定的变量。因此可以说引用是一个已存在变量的另一种访问方式。

1、引用的使用

语法:

数据类型 &别名 = 变量名(已存在);

示例:

int main(){ int a = 10; int &b = a; //声明引用变量 只是绑定到已有的变量 a 上,成为它的另一个名字或者说别名。cout << "a = " << a << endl; //10cout << "b = " << b << endl; //10cout << "a地址 = " << &a << endl; //a地址和b地址是一样的cout << "b地址 = " << &b << endl; b = 20; cout << "a = " << a << endl; cout << "b = " << b << endl;
}

从上面示例代码中可以看到,修改了引用变量 b 的值,那么变量 a 的值也发生了变化,说明它们是指向同一块内存中的变量。引用即是一个变量的别名。

在这里插入图片描述

2、引用的注意事项

(1) 声明引用必须进行初始化:

在 C++ 中,引用在声明时必须初始化。因为引用是已存在变量的别名,它不可以“空引用”,必须与某个已有变量关联。

int x = 5;
int& ref = x;  // 正确,引用 ref 被初始化为 x 的别名int& ref2;      // 错误:引用必须在声明时初始化
(2) 引用初始化后,不可以改变:

一旦引用绑定到一个变量,就不能改变引用指向的对象,它始终引用最初绑定的变量。

int x = 10;
int y = 20;
int& ref = x;  // ref 是 x 的引用ref = y;        // 这是赋值操作,将 y 的值赋给 x,而不是改变引用的目标
cout << ref << endl;  // 输出 20,x 被修改为 20,但 ref 依然指向 x,而不是 y
(3) 引用不能为 NULL

引用不能是空引用,也不能指向 NULL。引用必须在创建时与一个有效的对象关联,不能指向一个“空”位置。

int x = 10;
int& ref = x;  // 正确:ref 引用 xint* ptr = nullptr;
int& ref2 = *ptr;  // 错误:不能为 NULL 引用

引用必须指向有效对象,这与指针不同,指针可以为 NULL

(4) 一个变量可以有多个引用:

一个变量可以有多个引用,它们都指向同一个内存地址,并且共享同一个值。

int x = 10;
int& ref1 = x;  // ref1 是 x 的引用
int& ref2 = x;  // ref2 也是 x 的引用ref1 = 20;       // 修改 ref1 所引用的值,也就是 x
cout << x << endl;  // 输出 20
cout << ref2 << endl;  // 输出 20
(5) 引用不能指向常量或临时变量:

普通引用不能绑定到常量或临时变量,因为它们无法修改常量的值或持有临时变量的地址。

int x = 10;
int& ref1 = x;    // ref1 是 x 的引用
// int& ref2 = 20;  // 错误:引用不能指向常量或临时变量,10 是一个临时变量

二、引用作为参数

由于引用提供了一种轻量级的、不增加额外存储开销的方式来操作已存在的变量,因此常用于函数参数传递、返回值优化等场景,以提高代码的效率和可读性。

1、避免复制成本

引用作为函数的参数,可以减少值传递带来的开销,尤其是在传递大型对象时,能避免昂贵的深拷贝。

2、修改原始数据

使用引用参数可以实现对实参的直接修改。

#include <iostream>
using namespace std;
void swap(int* a,int* b) {int temp = *a;*a = *b;*b = temp;
}
void swapWithXor(int &a, int &b) {if (a != b) {  // 避免当a和b是同一个变量时值被清零a = a ^ b;  // 第一步:a存储a和b的异或结果b = a ^ b;  // 第二步:b = (a^b)^b = a(恢复a的原始值)a = a ^ b;  // 第三步:a = (a^b)^a = b(恢复b的原始值)}
}
void swap(int& num1, int& num2){ int temp = num1; num1 = num2num2 = temp;
}
int main(){ int a = 10; int b = 20; swap(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl;
}

运行结果发现,a 和 b 的值发生了改变,也就是说通过引用传参相当与给变量起了一个别名,最终都是对同一个变量的操作。

3、对比指针

使用引用和指针作为函数参数的区别:

(1)直接使用引用变量进行赋值和修改,语法简洁,不需要解引用操作符 (*)。

(2)引用和指针都可以作为函数参数来实现数据的共享和修改,使用指针需要程序员自行管理内存和检查指针的有效性。而引用不能为 nullptr ,因为在定义时必须要进行初始化,总是指向某个有效的对象,所以这在一定程度上强制了函数调用时必须提供有效的对象。

void func(int& var){ var = 100; //不需要处理空值,因为引用在使用前必须初始化,保证值是有效的
}
int* func(int* var){ if (var != nullptr) { //需要检查指针的有效性return var;}
}
int main(){ int a = 10; func(a); int* result = func(&a); delete result; //手动释放内存result = nullptr;
}

(3)使用指针作为函数参数,可以在函数内部改变指针的指向,这是引用类型参数所不具备的。

void func1(int* var){ var = new int(10);//指向新的地址
}

三、引用作为函数的返回值

用引用作为函数的返回值,最大的好处是,在内存中不产生被返回值的副本。

1、引用作为函数返回值

我们来看一段代码:

//定义全局变量
int temp;
int fun1(){ temp = 10;return temp;
}
int& fun2(){ temp = 10;return temp;
}
int main() { // 1. 返回值类型int a = func1();  // 调用 func1 获取返回值a = 99;            // 修改 a,不影响 tempcout << temp << endl; // 输出 10,因为 func1 返回的是值// 2. 返回引用类型int& b = func2();  // 调用 func2 获取返回引用b = 88;            // 修改 b,同时修改 tempcout << temp << endl; // 输出 88,因为 func2 返回的是引用
}

上述代码中会出现两层拷贝,当执行语句 int a = fun1 (); 的时候会先创建一个临时变量,把返回值拷贝给隐藏的临时变量,然后再把临时变量的值再拷贝给 a,假设这个临时变量是 t,相当于做了这两个赋值的步骤:

t = temp;
a = t;

而返回引用在内存中不会产生副本,是原有变量的一个别名,这样就避免产生临时变量,相比返回普通类型的执行效率更高。

2、使用引用作为函数返回值注意事项

局部变量不要作为引用返回,函数执行完成后,局部变量会被销毁。

#include <iostream>
using namespace std;
int& test(){ int a = 1int& b = a; //局部变量不要作为引用返回,函数执行完成后,局部变量内存会被释放,导致返回的引用成为悬挂引用。return b;
}
int main(){ int& a = test(); cout << "返回结果:" << a << endl; //返回结果:-858993460
}

如果要返回局部变量,可以使用 static 修饰,静态变量存在于全局区,全局区上的数据在程序结束后释放。

int& test(){ static int a = 1; int& b = a; return b;
}

3、函数的调用可以作为左值

(1)左值
在 C++ 中,表达式分为左值和右值。

左值 (lvalue):指的是持久的对象,通常指代表达式结束后依然存在的对象。

特点:

  • 左值在内存中有明确的地址,可以取地址。
  • 左值可以被修改,即可以出现在赋值语句的左侧。
  • 左值可以出现在赋值表达式的左边或右边。
int a = 10; // 'a' 是左值,10 是右值
int* p = &a; // 正确,取变量地址,'a' 是左值
int b = a + 1; // 正确,使用左值

(2)右值
右值 (rvalue):指的是临时的对象,通常指表达式结束后不再存在的对象。

特点:

  • 右值不能取地址。
  • 右值不能出现在赋值语句的左侧。
int a = 10; 
int* p2 = &(a+1); // 错误,表达式(a+1)的结果是一个右值,右值不能取地址
int x = 10;
int y = 20;// 同样,下面的尝试也是无效的
(x + y) = 100; // 编译器错误:表达式 (x + y) 是一个右值,不能出现在赋值语句的左边

(3)函数调用作为左值
当函数返回引用时,函数的调用可以作为左值。

int a;
int& test(){ a = 10; return a;
}
int main(){ int& num1 = test(); //返回的是 a 的引用//num1是 a 的引用,因此输出10。cout << num1 << endl; //test()返回a的引用,所以可以将a的值改为 20test() = 20;cout << num1 << endl; //输出num1的值
}

四、常引用

1、常引用的定义

通过 const 关键字定义常量引用,必须在声明时初始化。

const int &a = 10;

上述代码中,10 是一个字面值常量,本身不是变量,不能直接被引用。但是使用 const 修饰后,编译器会创建一个临时变量,相当于下面的代码:

int temp = 10;
const int &a = temp;

此时,如果想要修改 a 的值,是不被允许的。会提示表达式必须是可修改的左值。

a=100;

常量引用主要用来修饰形参,提高安全性,防止形参改变实参。

如下代码,向 test () 函数传入实参变量 a,test () 函数执行之后,输出 a 的值是 100。也就是说函数执行完成后,会对外部的变量 a 产生影响。

int a = 10;
const int& ref = a;
  • 这里 ref 是对 a常量引用const int&),表示通过 ref 不能修改 a 的值(ref = 20; 是错误的)。

  • 但是 a 本身是非const变量,可以直接修改 aa = 20; 是合法的)

  • const int& 的重点是:引用本身是不能修改所引用对象的内容

2、指针常量和常量指针:

const 可以修饰指针的两部分:指针本身指针指向的内容。具体的含义如下:

  1. const int* pint const* p

    • p 可以改变指向不同的内存位置,但 不能修改指向的内容
    void func(const int* p) {// *p = 10;  // 错误:不能修改指针指向的内容int b = 20;p = &b;  // 正常:可以修改指针的指向
    }
    
  2. int* const p

    • p 是常量指针,指针本身不能修改,但 可以修改指向的内容
    void func(int* const p) {*p = 10;  // 正常:可以修改指针指向的内容int b = 20;// p = &b;  // 错误:不能修改指针的指向
    }
    
  3. const int* const p

    • p 是常量指针,指针本身不能修改指针指向的内容也不能修改
    void func(const int* const p) {// *p = 10;  // 错误:不能修改指针指向的内容// p = &b;  // 错误:不能修改指针的指向
    }
    
总结:
  • const 左边修饰指针的内容const int* p):指针指向的内容不可通过指针修改,但指针本身可以指向其他地址,指针指向的变量也可以直接被赋值修改。
  • const 右边修饰指针本身int* const p):指针本身指向的地址不可修改,但指针指向的变量的值可以被修改。
类型含义是否能修改值是否能修改指针
const int* p常量指针:不能通过 p 修改指向的值❌ 否✅ 是
int* const p指针常量:指针地址不可变,值可改✅ 是❌ 否
const int* const p指向常量的常量指针,两者都不可变❌ 否❌ 否

const 在 * 左边,指向内容不能变 → 常量指针
const 在 * 右边,指针地址不能变 → 指针常量

把const当作常量就行了!

http://www.dtcms.com/a/316143.html

相关文章:

  • 组织架构与软件架构协同演进实践指南
  • UE5 安装Visual Studio
  • Go语言实战案例:使用context控制协程取消
  • GB28181监控平台LiveGBS如何配置GB28181对接海康、大华解码器上墙,将GB28181平台是视频给硬件解码器解码上墙
  • 软件无线电 招标参数
  • ⭐CVPR2025 非均匀运动视频插帧新突破
  • 文献阅读 | Briefings in Bioinformatics | Hiplot:全面且易于使用的生物医学可视化分析平台
  • HarmonyOS 应用拉起系列(二):如何拉起微信小程序
  • 前端1.0
  • 查看 Redis 某个数据库的内存占用
  • python+MySQL组合实现生成销售财务报告
  • 站在前端的角度,看鸿蒙页面布局
  • MTK-Android 系统拷贝预置资源
  • 本地使用uv管理的python项目怎么部署到服务器?
  • Next.js 链接与导航:页面间无缝切换
  • 最新安卓原生对接苹果cms App后端+app(最新优化版)
  • Spring Cloud系列—简介
  • 从循环嵌套到拓扑编排:LangGraph如何重构Agent工作流
  • 网络 —— 笔记本(主机)、主机虚拟机(Windows、Ubuntu)、手机(笔记本热点),三者进行相互ping通
  • 企业AI转型之战:Coze、Dify与FastGPT的巅峰对决
  • css动态样式
  • Linux 内存管理之 Rmap 反向映射(二)
  • 去哪儿StarRocks实践
  • 以Linux为例补充内存管理基础知识
  • 【 IPMI 内核模块】重新加载
  • BeeWorks私有化即时通讯,局域网办公安全可控
  • 光伏电站环境监测系统:绿色能源的“智慧守护者”
  • 是的,或许这就是意识!
  • 政安晨【开源人工智能硬件】【ESP乐鑫篇】 —— 详细分享小智(78/xiaozhi-esp32)AI终端开源硬件的嵌入式开发经验笔记
  • C语言---文件操作