本系列 C++ 相关文章 仅为笔者学习笔记记录,用自己的理解记录学习!C++ 学习系列将分为三个阶段:基础篇、STL 篇、高阶数据结构与算法篇,相关重点内容如下:
- 基础篇:类与对象(涉及C++的三大特性等);
- STL 篇:学习使用 C++ 提供的 STL 相关库;
- 高阶数据结构与算法篇: 手动实现自己的 STL 库 及 设计实现高阶数据结构,如 B树、B+树、红黑树等。
本期内容:C++ 引用的概念、特性、常引用、使用方式(建议)及引用与指针
目录:
1. 概念、注意点及特性
- - 1.1 概念及注意点
- - 1.2 特性
- - 代码示例及运行结果
2. 一般引用使用场景 / 作用
- - 2.1 作参数(含代码示例)
- - 2.2 作返回值(含代码示例)
- - 2.3 注意点
3. 常引用 const 及权限
- - 3.1 const 的简单回顾
- - 3.2 常引用与权限
4. 关于调用函数引用传参的注意点
5. 引用和指针的区别
- - 5.1 联系
- - 5.2 引用和指针的不同点
【 合集链接 】
1. 概念、注意点及特性
1.1 概念及注意点
- 引用是给已存在变量取了一个别名;
- 引用不是新定义一个变量;
- 编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
示例:比如:水浒传中的李逵,在家称为"铁牛",江湖上人称"黑旋风"。“铁牛”、“黑旋风”都是李逵这个人别名(外号),即直白的说就是只同一个人。
- 基于语法的角度看,引用就是给变量取别名, 使用变量名和别名都是操作同一个“对象”。
1.2 特性
- 引用在定义是必须初始化;
- 一个变量可以有多个引用;
- 引用一旦引用一个实体,就不能在引用其他实体;【区别于指针,指针可以修改指向对象,但是引用不可以】
(实体即引用的对象)
引用的声明定义写法:
- 类型& 引用变量名(对象名) = 引用实体;【 引用类型必须和引用实体是同种类型的 】
代码示例
#include<iostream>
using std::cout;
using std::endl;
int main() {
int a = 100;
int& A = a; // 定义引用类型:类型& 引用变量名(对象名) = 引用实体;
/* 声明定义一个 a = 100,数据 100 存储在某块内存区域上,变量名 a 好比这块内存的门牌号名称,引用变量名 A 也是这块内存门牌号名称,无论使用 a / A 都是在使用这块内存区域上的数据! */
int& aa = a;
int& AA = A;
cout << a << ' ' << &aa<< endl;
cout << A << ' ' << &A<< endl;
cout << aa << ' ' << &aa<< endl;
cout << AA << ' ' << &AA<< endl;
/* 结果如下图所示:数据、地址 均相同!!! */
return 0;
}
运行结果
2. 一般引用使用场景 / 作用
2.1 作参数
- 输出型参数:即在函数参数列表中使用形参引用,在函数体内使用形参引用,等同于操作外部的实参!【如代码示例(实现值交换)说明】
- 大对象传参提高效率:直白解释:原来一般函数使用的参数返回是值传递的形式,过程中会产生拷贝本!即有一个申请空间并拷贝的小过程;若使用引用,就相当于直接操作原对象,无该过程!在大对象传参过程中有一定的效率提升!
代码示例
#include<iosteam>
/* 以往的指针形式实现! */
void _Swap(int* a, int* b){
int temp = *a;
*a = *b;
*b = temp;
}
/* 引用写法:输出型参数 */
/*
该函数中 a 与 (外部)aaaa 指的就是同一个内存块上的数据,操作 a 等价于操作 aaaa,该形式既是作输出型参数!对于其他数据类型同理。
*/
void Swap(int& a, int& b){ // 此处参数列表中使用引用!
int temp = a;
a = b;
b = temp;
}
int main(){
int aaaa = 10, bbbb = 20;
Swap(aaaa, bbbb);
return 0;
}
2.2 作返回值
- 修改返回对象:当引用作为返回值对象时,相当于返回了被操作对象内存块上的对象,你可以直接修改!【如下代码示例】
- 大对象传参提高效率。
- 注意点:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用
引用返回,如果已经还给系统了,则必须使用传值返回。
代码示例
#include<iostream>
using std::endl;
using std::cout;
struct A { int a[100] = { 0 }; }; // 声明一个结构体
int& test1(A& a) {
return a.a[0]; // 返回 a[0] 的引用
}
int test2(A& a) {
return a.a[0]; // 返回 a[0] 的引用
}
int main() {
A a;
// 此时 a[0] 应当是:0;
cout << "a[0]:" << a.a[0] << endl;
test1(a)++; // 引用作为返回值
// test2(a)++; // 值作为返回值:error:表达式必须是可修改的左值!
/* 值和引用的作为返回值:
1. 值作为返回值:返回的值是一个常属性的量,是不可修改的;
2. 引用作为返回值:个人理解就是返回 a[0] 所在内存块上数据本身,所以可以操作。
*/
cout << "a[0]:" << a.a[0] << endl;
return 0;
}
运行结果
关于注意点的说明:使用引用作为返回值的前提是:被掉函数销毁后,操作的对象依然存在【否则会出问题】:如上代码中,我们使用的引用作为参数,就是操作原结构体 A,调用函数后,函数栈帧销毁了,也不影响原结构体 A【该函数的功能就相当于给 a[0] 取一个别名】。
2.3 注意点
任何时候都可以用引用作参数,但是不是任何时候都可以用引用作返回值!!!
对比引用与指针:引用相当于对指针的"改进",弱化了对指针这个高深内容的操作难度!
3. 常引用 const 及权限
3.1 const 的简单回顾
const 的作用:声明被修饰对象为:只可读变量;
说白了:被const 修饰的变量不可以修改!
int a = 10; // 对于 变量 a 而言,我们可读可写
a = 20; // 可写:即修改
const int b = 100; // 对于 变量 b 而言,我们只可读不可写!
// b = 200; // 错误
3.2 常引用与权限
1. 变量的操作权限:只能平移或缩小,不能放大!!!
2. 权限的放大缩小限制只针对于引用!
代码示例说明
#include<iostream>
int main(){
int a = 10; // 操作权限:可读可写
const int b = 20; // 操作权限:只读
// 引入引用
int& aa = a; // 操作权限:可读可写【该情形是:权限平移】
// int& c = b; // 错误:声明的引用是 int 类型且可读可写
/* 此处权限:相当于放大了:只读 => 可读可写! 错误!!! */
/* 正确用法 */
const int& c = b;
/* b、c 均只可读! */
// b = 2000; 错误!
// c = 2000; 错误!
/* 注意! */
const int& d = a; // 正确!
/* a:可读可写;d:只读:该过程相当于权限减小,后续会有十分重要的作用! */
return 0;
}
4. 关于调用函数引用传参的注意点
使用建议:如果使用引用传参,若在函数内不进行值修改操作,建议使用 const 引用传参,如下 func2 和 func3
代码示例
#include<iostream>
using std::cout;
using std::endl;
void func1(int n){
cout << n << endl;
}
void func2(int& n){
cout << n << endl;
}
void func3(const int& n){ // 如函数内不修改值,推荐该形式写法
cout << n << endl;
}
int main(){
int aa = 10;
const int bb = 20;
/* 对于 func1 而言参数列表是简单的形参形式,就是简单的拷贝式值转递,故三种情形都能通过 */
func1(aa); // 实参 aa 可读可写
func1(bb); // 实参 bb 只读
func1(30); // 常量 30 只读
/* 对于 func2 而言参数列表是引用形式的形参,本身是 int 类型且可读可写 */
func2(aa); // 实参 aa 可读可写
// func2(bb); // 实参 bb 只读:传递失败!
/* 编译器提示:E0433:将 "int &" 类型的引用绑定到 "const int" 类型的初始值设定项时,限定符被丢弃 */
func2(30); // 常量 30 只读
/* 编译器提示:E0461:非常量引用的初始值必须为左值 */
return 0;
}
5. 引用和指针的区别
面试回答角度:使用场景、语法特性及底层原理
5.1 联系
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的(看汇编代码)。
用途基本相似。
5.2 引用和指针的不同点
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求。
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
4. 没有 NULL 引用,但有 NULL 指针。
5. 在 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)。6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7. 有多级指针,但是没有多级引用。
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
9. 引用比指针使用起来相对更安全。