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.rhkb.cn/news/292797.html

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

相关文章

web 技术中前端和后端交互过程

1、客户端服务器交互过程 客户端:上网过程中,负责浏览资源的电脑,叫客户端服务器:在因特网中,负责存放和对外提供资源的电脑叫服务器 服务器的本质: 就是一台电脑,只不过相比个人电脑它的性能高很多,个人电脑中可以通过安装浏览器的形式,访问服务器对外提供的各种资源。 个人…

Electron 读取本地配置 增加缩放功能(ctrl+scroll)

最近&#xff0c;一个之前做的electron桌面应用&#xff0c;需要增加两个功能&#xff1b;第一是读取本地的配置文件&#xff0c;然后记载配置文件中的ip地址&#xff1b;第二就是增加缩放功能&#xff1b; 第一&#xff0c;配置本地文件 首先需要在vue工程根目录中&#xff0…

切换ip地址的app,简单易用,保护隐私

在数字化时代&#xff0c;IP地址作为网络设备的标识&#xff0c;不仅承载着数据在网络间的传输任务&#xff0c;还在一定程度上关联着用户的隐私和安全。因此&#xff0c;切换IP地址的App应运而生&#xff0c;为用户提供了一种便捷的方式来改变其网络身份&#xff0c;实现匿名浏…

【Spring MVC】快速学习使用Spring MVC的注解及三层架构

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Spring MVC】快速学习使用Spring MVC的注解及三层架构 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 Spring Web MVC一: 什么是Spring Web MVC&#xff1…

【应用笔记】LAT1413+快速开关蓝牙导致设备无广播

1. 问题背景 客户使用 BlueNRG-345MC 开发了一个 BLE 外设&#xff0c;和手机连接。在测试中发现&#xff0c;手机连接上外设之后&#xff0c;不断地在手机上点击蓝牙的开关按钮&#xff0c;造成设备不断地断开、重连&#xff1b;少则几次&#xff0c;多则几十次。点击之后&am…

【Entity Framework】创建并配置模型

【Entity Framework】创建并配置模型 文章目录 【Entity Framework】创建并配置模型一、概述二、使用fluent API配置模型三、分组配置四、对实体类型使用EntityTypeConfigurationAttribute四、使用数据注释来配置模型五、实体类型5.1 在模型中包含类型5.2 从模型中排除类型5.3 …

loadbalancer 引入与使用

在消费中pom中引入 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> 请求调用加 LoadBalanced 注解 进行服务调用 默认负载均衡是轮训模式 想要切换…

【数据结构与算法】二叉树的遍历及还原

树形结构 - 有向无环图 树是图的一种。 树形结构有一个根节点树形结构没有回路根节点&#xff1a;A叶子节点&#xff1a;下边没有其他节点了节点:既不是根节点,又不是叶子节点的普通节点树的度:这棵树最多叉的节点有多少叉&#xff0c;这棵树的度就为多少树的深度&#xff1a…

实例、构造函数、原型、原型对象、prototype、__proto__、原型链……

学习原型链和原型对象&#xff0c;不需要说太多话&#xff0c;只需要给你看看几张图&#xff0c;你自然就懂了。 prototype 表示原型对象__proto__ 表示原型 实例、构造函数和原型对象 以 error 举例 图中的 error 表示 axios 抛出的一个错误对象&#xff08;实例&#xff0…

WiFiSpoof for Mac wifi地址修改工具

WiFiSpoof for Mac&#xff0c;一款专为Mac用户打造的网络隐私守护神器&#xff0c;让您在畅游互联网的同时&#xff0c;轻松保护个人信息安全。 软件下载&#xff1a;WiFiSpoof for Mac下载 在这个信息爆炸的时代&#xff0c;网络安全问题日益凸显。WiFiSpoof通过伪装MAC地址&…

C++入门知识详细讲解

C入门知识详细讲解 1. C简介1.1 什么是C1.2 C的发展史1.3. C的重要性1.3.1 语言的使用广泛度1.3.2 在工作领域 2. C基本语法知识2.1. C关键字(C98)2.2. 命名空间2.2 命名空间使用2.2 命名空间使用 2.3. C输入&输出2.4. 缺省参数2.4.1 缺省参数概念2.4.2 缺省参数分类 2.5. …

GRE和MGRE综合实验

实际网段划分 分配IP 1.IP划分 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 192.168.1.254 24 Mar 29 2024 16:42:44-08:00 r1 %%01IFNET/4/LINK_STATE(l)[3]:The line protocol IP on the interface GigabitEthernet0/0/0 has entered the UP state. [r1-Gigabi…

飞天使-k8s知识点28-kubernetes散装知识点5-helm安装ingress

文章目录 安装helm添加仓库下载包配置创建命名空间安装 安装helm https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gztar -xf helm-v3.2.3-linux-amd64.tar.gzcd linux-amd64mv helm /usr/local/bin修改/etc/profile 文件&#xff0c;修改里面内容,然后重新启用export PATH$P…

动态规划-----背包类问题(0-1背包与完全背包)详解

目录 什么是背包问题&#xff1f; 动态规划问题的一般解决办法&#xff1a; 0-1背包问题&#xff1a; 0 - 1背包类问题 分割等和子集&#xff1a; 完全背包问题&#xff1a; 完全背包类问题 零钱兑换II: 什么是背包问题&#xff1f; 背包问题(Knapsack problem)是一种…

Windows-安装infercnv包(自备)

目录 安装基础 ①安装JAGS a,找到适配版本 b&#xff0c;install for me only安装路径 ②安装"rjags"包 ③安装inferCNV 安装基础 版本&#xff1a; R version 4.2.2 (2022-10-31 ucrt) -- "Innocent and Trusting"安装的JAGS版本为JAGS 4.3.1 首…

GPT提示词分享 —— 智能域名生成器

提示词&#x1f447; 我希望你能充当一个聪明的域名生成器。我将告诉你我的公司或想法是什么&#xff0c;你将根据我的提示回复我一份域名备选清单。你只需回复域名列表&#xff0c;而不是其他。域名应该是最多 7-8 个字母&#xff0c;应该简短但独特&#xff0c;可以是朗朗上口…

C易错注意之分支循环,悬空else,短路表达式,函数static

接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 前言&#xff1a; c语言中一些关于分支循环中continue常混淆&#xff0c;悬空esle问题&#xff0c;短路表达式&#xff0c;static ,extern在使用时稍不注意就会出错的点,接下来我们将介绍…

Monkey工具之fastbot-iOS实践

Monkey工具之fastbot-iOS实践 背景 目前移动端App上线后 crash 率比较高&#xff0c; 尤其在iOS端。我们需要一款Monkey工具测试App的稳定性&#xff0c;更早的发现crash问题并修复。 去年移动开发者大会上有参加 fastbot 的分享&#xff0c;所以很自然的就想到Fastbot工具。…

Lilishop商城(windows)本地部署【docker版】

Lilishop商城&#xff08;windows&#xff09;本地部署【docker版】 部署官方文档&#xff1a;LILISHOP-开发者中心 https://gitee.com/beijing_hongye_huicheng/lilishop 本地安装docker https://docs.pickmall.cn/deploy/win/deploy.html 命令端页面 启动后docker界面 注…

java回溯算法笔记

回溯算法综述 回溯用于解决你层for循环嵌套问题&#xff0c;且不剪枝的回溯完全等于暴力搜索。 回溯算法模板https://blog.csdn.net/m0_73065928/article/details/137062099?spm1001.2014.3001.5501 组合问题&#xff08;startindex避免使用重复元素&#xff09; “不含重复…