C++从入门到精通——引用()

C++的引用

  • 前言
  • 一、C++引用概念
  • 二、引用特性
    • 交换
      • 指针
      • 引用
  • 三、常引用
    • 保证值不变
    • 权限的方法
      • 权限的放大
      • 权限的缩小
      • 权限的平移
      • 类型转换
        • 临时变量
  • 四、引用的使用场景
    • 1. 做参数
    • 2. 做返回值
  • 五、传值、传引用效率比较
    • 值和引用的作为返回值类型的性能比较
  • 六、引用和指针的区别
    • 引用和指针的注意点
    • 引用和指针的不同点
  • 七、测试代码展示
    • &.cpp


前言

C++的引用是别名,它为已存在的对象提供了另一个名称。一旦引用被初始化指向一个对象,它就不能再指向其他对象。引用必须在声明时初始化,并且必须初始化为有效的对象或字面量。引用通常用于函数参数和返回值,以实现按引用传递和返回。此外,它们也常用于大型对象和数组,以避免复制的开销。C++11引入了右值引用和移动语义,允许更高效的资源管理和性能优化。总的来说,C++的引用是一种强大的工具,能够增强代码的可读性和性能。


一、C++引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

在这里插入图片描述
在C++中,引用是一个别名,用于已经存在的变量或对象。引用提供了对变量的间接访问,通过引用,可以通过不同的名称来访问同一变量。

类型& 引用变量名(对象名) = 引用实体;

void TestRef()
{int a = 10;int& ra = a;//<====定义引用类型printf("%p\n", &a);printf("%p\n", &ra);
}

在这里插入图片描述

注意:引用类型必须和引用实体是同种类型的

引用的定义使用&符号,如下所示:

int x = 10;
int &ref = x;

在这个例子中,refx的引用,它是x的别名。现在,refx可以互换使用,任何对ref的更改将反映在x上,反之亦然。

二、引用特性

void TestRef()
{int a = 10;// int& ra;   // 该条语句编译时会出错int& ra = a;int& rra = a;printf("%p %p %p\n", &a, &ra, &rra);  
}

在这里插入图片描述

引用有以下几个特点:

  1. 引用必须在定义时进行初始化,一旦初始化完成后,就无法更改引用的绑定。

  2. 引用必须与其所引用的对象具有相同的类型。

  3. 引用可以作为函数的参数和返回值,通过引用参数传递参数可以避免复制大型对象的开销。

  4. 一个变量可以有多个引用

引用与指针不同,指针是一个对象,可以指向任何其他对象,而引用始终指向同一个对象。另外,引用在使用时不需要解引用操作符(*),因为它本身就是对象的别名。

引用的使用可以简化代码并提高可读性,它常用于函数参数传递、函数返回值、以及在循环中使用。

void increment(int& i) {i++;
}int main() {int x = 10;increment(x);cout << x; // 输出11return 0;
}

在这里插入图片描述

在上面的例子中,increment函数接受一个引用参数i,对该引用进行递增操作。在main函数中,将变量x传递给increment函数后,x的值被递增为11。因为参数是引用类型,所以对i的修改会直接影响到x

交换

指针

void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}
int main{int x = 0, y = 10;cout << x << "  " << y << endl;Swap(&x, &y);cout << x << "  " << y << endl;return 0;}

在这里插入图片描述

引用

void _Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}
int main{int x = 0, y = 10;cout << x << "  " << y << endl;_Swap(x, y);cout << x << "  " << y << endl;return 0;}

在这里插入图片描述

三、常引用

保证值不变

C++中的常引用是指使用const关键字修饰的引用,即被引用的对象不能被修改。常引用和普通引用的主要区别在于,常引用所引用的对象在引用过程中不能被修改。

常引用的语法形式如下:

const T& ref;

其中,T是被引用对象的类型。常引用可以指向任何类型的对象,包括基本类型、自定义类型、指针等。

常引用在函数参数传递中很常用,可以用于避免拷贝大对象,同时又不希望对对象进行修改。在函数定义时,使用常引用作为参数,可以防止函数对参数进行修改。

需要注意的是,引用作为函数参数时,函数内部对引用的修改也会反映到函数外部的变量上。

void print(const int& i) {cout << i;
}int main() {int x = 10;print(x);return 0;
}

在这里插入图片描述

在上述例子中,print函数接受一个const引用参数i,这意味着i不可修改。在main函数中,将变量x传递给print函数后,print函数无法修改x的值。这样做可以确保函数不会意外地修改传递给它的参数。

权限的方法

void TestConstRef()
{const int a = 10;//int& ra = a;   // 该语句编译时会出错,a为常量const int& ra = a;// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;double d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;
}

权限的放大

const int a = 10;
int& ra = a;   // 该语句编译时会出错,a为常量
int p = a;

这样是可取的,因为这是赋值语句

如上const int a = 10;按照语法来理解是可读但不可写,我们使用一个可读可写的int类型来引用就会报错,所以这种方法是不可取的

const int* a = &m;
int* ra = a;   

这也是权限的放大,不可以

权限的缩小

int a = 10;
const int& ra = a;   

如上 int a = 10;按照语法来理解是可读可写,我们使用一个可读的const int类型来引用是没有问题的,所以这种方法是可取的

int p = a;//p = ra;

这样是可取的,因为这是赋值语句

注意a++ra也会++,因为raa的别名,但是ra不能++,因为ra是常量,总结来说就是不能通过ra来修改

 int* a = &m;
const int* ra = a;   

这也是权限的缩小,可以

权限的平移

 	int a = 10;int& ra = a;int& rra = a;
const int b = 10;
const int& rb = b;

如上 int a = 10;按照语法来理解是可读可写,我们使用一个可读可写的int类型来引用是没有问题的,所以这种方法是可取的,同理const的修饰也是一样的

类型转换

 double d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;

如上为什么int& rd = d;不行,而 const int& rd = d;确可以,是因为类型转换会生成临时变量,类型转换是将一个数据类型的值转换为另一个数据类型的值,而不是直接修改原始值。因此,在执行类型转换时,会创建一个新的变量来存储转换后的值,并且可以在需要的地方使用。这时的临时变量是具有常性的,使用int& rd = d; 就相当于权限的放大,会报错,需要使用const修饰

临时变量
int x = 0,y = 1;
//int& p = x + y;是不可以的
const int& p = x + y;//是可以的,和上面一样是临时变量的原因

除了类型转换之外,还有以下几种情况会生成临时变量:

  1. 函数返回值:当一个函数返回一个临时变量时,编译器会在函数结束时生成一个临时变量,并将其复制到函数返回的地方。

  2. 表达式计算:在进行表达式计算时,如果表达式中包含临时变量的创建和销毁,编译器会在需要的地方生成临时变量。

  3. 函数调用:当调用函数时,会将实参传递给形参。如果实参的类型与形参的类型不匹配,编译器可能会生成临时变量来进行类型转换。

  4. 对象初始化:当创建对象时,如果使用了拷贝构造函数,编译器会生成一个临时变量来初始化新对象。

  5. 运算符重载:当重载一个运算符时,可能会生成临时变量来进行操作。

需要注意的是,编译器为了优化性能可能会对临时变量进行优化,比如使用编译器自动生成的构造函数、析构函数等。因此,生成临时变量并不一定会带来显著的性能损耗。

四、引用的使用场景

1. 做参数

void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}

2. 做返回值

int& Count()
{static int n = 0;n++;// ...return n;
}

下面代码输出什么结果?为什么?

int& Add(int a, int b)
{int c = a + b;return c;
}
int main()
{int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;
}

在这里插入图片描述

在这里插入图片描述

注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

五、传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{A a;// 以值作为函数参数size_t begin1 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作为函数参数size_t begin2 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc2(a);size_t end2 = clock();// 分别计算两个函数运行结束后的时间cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{TestRefAndValue();return 0;
}

在这里插入图片描述

值和引用的作为返回值类型的性能比较

#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{// 以值作为函数的返回值类型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1();size_t end1 = clock();// 以引用作为函数的返回值类型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2();size_t end2 = clock();// 计算两个函数运算完成之后的时间cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{//TestRefAndValue();TestReturnByRefOrValue();return 0;
}

在这里插入图片描述

通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。

六、引用和指针的区别

引用和指针的注意点

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

int main()
{int a = 10;int& ra = a;cout<<"&a = "<<&a<<endl;cout<<"&ra = "<<&ra<<endl;return 0;
}

在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

int main()
{int a = 10;int& ra = a;ra = 20;int* pa = &a;*pa = 20;return 0;
}

我们来看下引用和指针的汇编代码对比:
在这里插入图片描述

引用和指针的不同点

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

  2. 引用在定义时必须初始化,指针没有要求

  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

  4. 没有NULL引用,但有NULL指针

  5. sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

  7. 有多级指针,但是没有多级引用

  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

  9. 引用比指针使用起来相对更安全

注意:

int* ptr = NULL;
int& r = ptr;

这样编译器是不会报错的
在这里插入图片描述

int main()
{int* ptr = NULL;int& r = *ptr;cout << r << endl;return 0;
}

在这里插入图片描述
这样才会报错,因为指针的解引用是在使用的阶段,在底层是r存放的是ptr的地址

七、测试代码展示

&.cpp

#include <iostream>
using namespace std;
//void TestRef()
//{
//    int a = 10;
//    int& ra = a;//<====定义引用类型
//    printf("%p\n", &a);
//    printf("%p\n", &ra);
//}
void TestRef()
{int a = 10;// int& ra;   // 该条语句编译时会出错int& ra = a;int& rra = a;printf("%p %p %p\n", &a, &ra, &rra);
}
void increment(int& i) {i++;
}
void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}
void _Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}
//int main()
//{
//    //TestRef();
//    //int x = 10;
//    //increment(x);
//    //cout << x; // 输出11
//    int x = 0, y = 100;
//    cout << x << "  " << y << endl;
//    //Swap(&x, &y);
//    _Swap(x, y);
//    cout << x << "  " << y << endl;
//    return 0;
//}
//void print(const int& i) {
//    cout << i;
//}
//
//int main() {
//    int x = 10;
//    /*int a = 10;
//    const int& ra = a;
//    int p = a;*/
//    const int a = 10;
//    //int& ra = a;   // 该语句编译时会出错,a为常量
//    int p = a;
//    print(p);
//    return 0;
//}
//int& Add(int a, int b)
//{
//    int c = a + b;
//    return c;
//}
//int main()
//{
//    int& ret = Add(1, 2);
//    Add(3, 4);
//    cout << "Add(1, 2) is :" << ret << endl;
//    return 0;
//}
#include <time.h>
//struct A { int a[10000]; };
//void TestFunc1(A a) {}
//void TestFunc2(A& a) {}
//void TestRefAndValue()
//{
//    A a;
//    // 以值作为函数参数
//    size_t begin1 = clock();
//    for (size_t i = 0; i < 10000; ++i)
//        TestFunc1(a);
//    size_t end1 = clock();
//    // 以引用作为函数参数
//    size_t begin2 = clock();
//    for (size_t i = 0; i < 10000; ++i)
//        TestFunc2(a);
//    size_t end2 = clock();
//    // 分别计算两个函数运行结束后的时间
//    cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
//    cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
//}
//struct A { int a[10000]; };
//A a;
 值返回
//A TestFunc1() { return a; }
 引用返回
//A& TestFunc2() { return a; }
//void TestReturnByRefOrValue()
//{
//    // 以值作为函数的返回值类型
//    size_t begin1 = clock();
//    for (size_t i = 0; i < 100000; ++i)
//        TestFunc1();
//    size_t end1 = clock();
//    // 以引用作为函数的返回值类型
//    size_t begin2 = clock();
//    for (size_t i = 0; i < 100000; ++i)
//        TestFunc2();
//    size_t end2 = clock();
//    // 计算两个函数运算完成之后的时间
//    cout << "TestFunc1 time:" << end1 - begin1 << endl;
//    cout << "TestFunc2 time:" << end2 - begin2 << endl;
//}
//int main()
//{
//    //TestRefAndValue();
//    TestReturnByRefOrValue();
//    return 0;
//}
int main()
{int* ptr = NULL;int& r = *ptr;cout << r << endl;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/783926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI算法中的关键先生 - 反向转播与戴维莱姆哈特

0. 引言 机器学习的自动推导过程中有一个关键步骤&#xff0c;就是自动求解过程的参数反向传播过程&#xff0c;这个工作据说是这个人做的&#xff1a; Remembering David E. Rumelhart (1942-2011) – Association for Psychological Science – APSAPS Fellow and Charter …

CDR2024软件免费版 CDR绘制矢量图形插画 平面设计CDR教程 cdr2024注册机 cdr2024安装包百度云 cdr快捷键

CDR2024软件免费版是由加拿大Corel公司研发的平面设计类软件。CDR2024软件免费版应用的行业非常广泛&#xff0c;影视动画、网页设等行业都可以应用此软件。CDR2024软件免费版为专业工作者提供了更加精细的操作功能&#xff0c;用户可以更加精确的对图片进行修改&#xff0c;降…

【MySQL】DQL-查询语句全解 [ 基础/条件/分组/排序/分页查询 ](附带代码演示&案例练习)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

代码随想录算法训练营第40天| 62.不同路径、63. 不同路径 II

62.不同路径 题目链接&#xff1a;不同路径 题目描述&#xff1a;一个机器人位于一个 m x n **网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish…

解决一个有意思的抛硬币问题,计算连续两次正面所需次数的数学期望

文章目录 一、问题与分析二、基本的数学推导三、代码示例 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、问题与分析 问题&#xff1a;对于质地均匀的硬币&#xff0c;连续两次得到正面所需的次数数学期望是多少&#xff1f; 关键词&#xff1a;…

Oracle批量插入

注意&#xff1a;与MySQL不同&#xff01; 方式一 insert all into table(...) values(...) into table(...) values(...) select * from dual; <insert id"addList" parameterType"java.util.List" useGeneratedKeys"false">INSERT A…

10天学会kotlin DAY6 继承、类、重载

kotlin 继承与重载 前言 1、open 关键字 2、类型转换 3、Any 超类 4、对象声明 5、对象表达式 6、伴生对象 7、嵌套类和内部类 8、数据类 9、copy 函数 10、运算符重载 11、枚举类定义函数 12、代数数据类型 13、密封类 14、数据类的小结 总结 前言 使用纯代码…

「MySQL」索引事务

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;数据库 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 索引&事务 &#x1f349;索引&#x1f34c;特点&#x1f34c;通过 SQL 操作索引&#x1f34c;底层数据结构 &#x1f349;事务&…

Nginx的反向代理

Nginx的反向代理 location ^~ /aaa {proxy_pass http://192.168.15.78/; } 1. 跨域 2.Nginx 代理服务器缓存 3.Nginx 负载均衡 4. 动静分离 Nginx的跨域 跨源资源共享 (CORS) 是一种机制&#xff0c;它使用额外的 HTTP 标头让用户代理获得访问来自不同来域的服务器上选定资…

Linux————计划任务

计划任务 计划任务&#xff0c;就是有计划的任务&#xff0c;就是按照我们的的计划自动执行任务 计划任务有一次性计划任务和周期性计划任务 一次性计划任务 所谓一次性计划任务&#xff0c;就是按照计划只执行一次的任务。 无论是windows还是Linux都具备一次性计划任务的…

交换奇偶位

题目描述&#xff1a; 写一个宏&#xff0c;可以将一个整数的二进制位的奇数位和偶数位交换。 题目分析&#xff1a; 相信大家都可以想到位运算如&#xff1a;按位异或&#xff0c;按位取反以及<<和>>等操作符&#xff0c;可是想到这些又能怎么样&#xff1f;还是…

ASC+Base64加密算法 根据用户id生成秘钥

需要是要根据用户的id生成密钥 通过密钥生成免登录链接 实现用户根据链接直接访问系统 标准Base64编码包含特殊字符,会被转义 从而登录失败要使生成的密文不包含特殊字符&#xff0c;您可以使用Base64URL编码代替标准的Base64编码。Base64URL编码使用-和_替换了标准Base64编码…

Python从原Excel表中抽出数据存入同一文件的新的Sheet(附源码)

python读取excel数据。Python在从原Excel表中抽出数据并存储到同一文件的新的Sheet中的功能&#xff0c;充分展示了其在数据处理和自动化操作方面的强大能力。这一功能不仅简化了数据迁移的过程&#xff0c;还提高了数据处理的效率&#xff0c;为数据分析和管理工作带来了极大的…

图像识别应用

一、图像识别应用 图像识别&#xff0c;是利用机器视觉对图像进行处理、分析和理解&#xff0c;以识别各种不同模式的目标和对象。图像识别在机器视觉工业领域中最典型的应用就是二维码的识别了&#xff0c;二维码就是我们平时常见的条形码中最为普遍的一种。将大量的数据信息存…

Nginx 配置 WebSocket 代理

Nginx 配置 WebSocket 代理 文章目录 Nginx 配置 WebSocket 代理官方文档代理样例 Linux 查看安装文件命令手册Nginx 日志配置方案成功解决问题--使用 Nginx 代理 WebSocket可能出现的问题 Nginx 官方文档网址 nginx documentation ... http:{...server{...# WebSocket代理loc…

诊断慢SQL根源

诊断慢SQL的根源通常涉及一系列步骤和方法&#xff0c;包括但不限于以下几个方面&#xff1a; 1. **收集慢SQL日志**&#xff1a; - 设置数据库系统的慢查询日志阈值&#xff08;如MySQL的long_query_time&#xff09;&#xff0c;记录执行时间超过指定阈值的SQL语句。 …

3.31学习总结

算法 解题思路 使用dfs,对蛋糕每层可能的高度和半径进行穷举.通过观察我们可以知道第一层的圆面积是它上面所有蛋糕层的圆面积之和,所以我们只要去求每层的侧面积就行了. 因为题目要求Ri > Ri1且Hi > Hi1,所以我们可以求出每层的最小体积和侧面积,用两个数组分别储存起来…

C语言实现猜数字游戏(有提示,限制次数版)

这次的猜数字游戏我添加了新的功能&#xff1a;为玩家添加了提示&#xff0c;以及输入数字的限制次数。 首先&#xff0c;我们的猜数字游戏需要一个菜单&#xff0c;来让玩家可以选择玩游戏还是退出游戏&#xff0c;所以我们需要开始就打印一个菜单&#xff1a; int main() {…

Mac air 个人免费版VMWare Fusion安装及配置教程

Mac air 安装免费版VMWare Fusion教程及问题解决 1、下载VMWare Fusion2、下载wins镜像文件3、开始配置4、出现的问题及解决方法4.1 如何跳过启动时的网络连接4.2 启动后&#xff0c;无法连接网络怎么办4.3 怎么实现将文件拖拽到虚拟机中 当你手上是一台Mac电脑&#xff0c;却需…

el-upload上传图片给SpringBoot后端

需求 我的需求是&#xff0c;将上传文件的el-upload放到一个el-form里面&#xff0c;点击保存才提交&#xff0c;所以不能直接用action的方式&#xff0c;下面采用自定义上传的方式来完成Vue前端 主要是使用editForm.imgList来保存图片的信息&#xff0c;注意这里是一个对象&am…