C++:引用的本质与应用

一、变量知识回顾

1、变量是一段连续存储空间的别名

2、程序通过变量来申请并命名存储空间;

3、可以通过变量的名字来使用存储空间;

那么问题来了,一段连续的存储空间是否可以有多个别名?

一个人可以有乳名,小名,正名,字,号,同理存储空间应该可以有多个别名,可以通过引用来解决。

二、引用----C++中新增加的概念

C++新增了一种复合类型----引用变量。引用是已经定义的变量的别名,例如将twain作为clement变量的引用,就可以交替使用twain和clement来表示这个变量,就像鲁迅和周树人就是同一个人。那么这种别名有什么用?引用变量的主要用途是作为函数的形参,通过将引用变量用作参数,函数将使用原始数据,而不是数据的副本。这样除指针外,引用也为函数处理大型结构提供了非常方便的途径,同时对于设计类来说,引用也是必不可少的。

创建引用变量

C和C++使用&符号来只是变量的地址。C++还给&符号赋予了另一个含义,用来申明引用。比如将rodents作为rats变量的别名,可以这样做

int rats;
int& rodents = rats;//将rodents作为rats的别名

其中&不是地址运算符,而是类型标识符的一部分,int&就是一个完整的类型。就像声明中的char*表示指向char的指针一样,int&指的是指向int的引用。上述引用声明允许将rats和rodents互换,因为它们指向相同的值,相同的内存单元。

程序实例1:引用的用法

#include <iostream>int main()
{using namespace std;int rats = 101;int& rodents = rats;//rodents是一个引用cout << "rats = " << rats << endl;cout << "rodents = " << rodents << endl;rodents++; //rodens自增,也会导致rats自增cout << "rats = " << rats << endl;cout << "rodents = " << rodents << endl;cout << "rats address = " << &rats << endl; //rats变量的地址与rodents的地址是一样的cout << "rodents address = " << &rodents << endl;return 0;
}

输出结果:

rats = 101
rodents = 101
rats = 102
rodents = 102
rats address = 0x61fe14
rodents address = 0x61fe14

结果分析:rats与rodents的值和地址完全相同的,将rodents加1,rats也加1,。

三、引用的本质

引用在声明的时候必须初始化,从这一点出发,引用更接近const指针,一旦与摸个变量相关联起来,就会一直效忠于它。实际上,int& rodents = rats是语句int* const pr = &rats的伪装表示;rodents扮演的角色与表达式*pr相同。

C++中的引质用本上是一个别名,它提供了一种间接访问已声明变量的方式,就像给一个变量取了一个新的名字。引用一旦初始化,就不能改变其绑定的目标,即不能再赋值为另一个变量。引用具有以下特性:

1、引用必须在声明时初始化,并且一旦初始化,就不能改变引用的对象,除非先解除引用再重新绑定;

2、引用不会占用额外的内存空间,因此可以减少内存开销。

3、引用提高程序的效率,因为通过引用可以直接操作目标对象,避免了复制数据的过程。

四、将引用作为函数参数

引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名,这种传递参数的方法称为引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。C语言只能按值传递,按值传递导致被调用函数使用调用程序的值拷贝,要想避开按值传递,就只能使用指针。

程序实例2:不同方式交换两个变量的值

#include <iostream>void swapr(int& a,int& b);
void swapp(int* p,int* q);
void swapv(int a,int b);int main()
{using namespace std;int wallet1 = 300;int wallet2 = 500;cout << "wallet1 = $" <<wallet1<< endl;cout << "wallet2 = $" <<wallet2<< endl;cout <<"\n";cout << "Using references to swap contents:\n";swapr(wallet1,wallet2);//能正常交换cout << "wallet1 = $" <<wallet1<< endl;cout << "wallet2 = $" <<wallet2<< endl;cout <<"\n";cout << "Using pointers to swap contents:\n";swapp(&wallet1,&wallet2);//能正常交换cout << "wallet1 = $" <<wallet1<< endl;cout << "wallet2 = $" <<wallet2<< endl;cout <<"\n";cout << "Using passing by value to swap contents:\n";swapv(wallet1,wallet2); //这种方法将会失败cout << "wallet1 = $" <<wallet1<< endl;cout << "wallet2 = $" <<wallet2<< endl;cout <<"\n";return 0;
}//引用的方式交换两个变量的值
void swapr(int& a,int& b)
{int temp;temp = a;a = b;b = temp;
}//指针的方式交换两个变量的值
void swapp(int* p,int* q)
{int temp;temp = *p;*p = *q;*q = temp;
}//按值传递的方式交换两个变量的值
void swapv(int a,int b)
{int temp;temp = a;a = b;b = temp;
}

输出结果

wallet1 = $300
wallet2 = $500Using references to swap contents:
wallet1 = $500
wallet2 = $300Using pointers to swap contents:
wallet1 = $300
wallet2 = $500Using passing by value to swap contents:
wallet1 = $300
wallet2 = $500

结果分析:引用和指针的方法都能正常交换wallet1与wallet2的值,按值传递不能正常交换。

按引用传递 swapr(wallet1,wallet2)和按值传递 swapv(wallet1,wallet2)看起来是一样的,只能通过函数原型或函数定义才知道它们之间的区别。

在函数swapr(int& a,int& b)中,变量a,b是wallet1与wallet2的别名,所以交换a,b的值相当于交换wallet1,wallet2;

在函数swapv(int a,int b)中,变量a,b是复制了wallet1与wallet2的值,所以交换a,b的值不影响wallet1,wallet2的值;

五、引用的属性和特别之处

先看程序实例

程序实例3:

#include <iostream>double cube(double a);
double refcube(double& ra);int main()
{using namespace std;double x = 3.0;cout << cube(x) << " cube of " << x << endl;cout << refcube(x) << " refcube of " << x << endl;return 0;
}double cube(double a)
{a *= a * a;return a;
}double refcube(double& ra)
{ra *= ra * ra;return ra;
}

输出结果

27 cube of 3
27 refcube of 27

 refcube()函数修改了x的值,而cube()函数没有修改x的值。这是提醒我们一般情况下就使用按值传递。变量a位于cube()中,它被初始化为x的值,本质上a和x是不同的东西,修改a,不影响x;但是refcube()函数使用引用参数,ra与x是同一个东西,修改ra就是修改x。如果函数既想使用传递的值,又不对其进行修改,那就得使用常量引用。

六、 常量引用const

程序中经常将引用参数声明位常量数据的引用,有如下理由:

1、使用const可以避免无意中修改数据;

2、使用const使函数能够处理const和非const实参,否则只能接受非const数据;

3、使用const引用使函数能够正确生成并并使用临时变量。

临时变量、引用参数与const

如果实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为const引用时,C++才允许这么操作。

编译器在什么条件下产生临时变量:

1、实参的类型正确,但不是左值;

2、实参的类型不正确,但是可以转换成正确的类型;

什么是左值

左值参数是可被引用的数据对象,如变量、数组元素、结构成员、引用、解除引用的指针等;

非左值包括字面常量,和包括多项的表达式;

const变量也可以视为左值,因为可以通过地址访问它们,只是变量的属性是不可修改。

程序实例4:const引用

#include<stdio.h>int main()
{int a = 4;const int& b = a; //通过const引用,使变量a拥有只读属性int* p = (int*)&b;*p = 5; //只读常量不能直接修改值,但是可以通过指针来修改值printf("a = %d\n", a);printf("b = %d\n", b);return 0;
}

输出结果

a = 5
b = 5

当使用常量对const引用进行初始化的时候,C++编译器会给常量只分配空间,并将引用名作为这段空间的别名。使用常量对const引用初始化之后,将生成一个只读变量。

七、引用的存储空间

我们知道引用是一个变量的别名,那它有自己的存储空间吗?

我们知道指针是有自己的存储空间的,引用又跟指针很相似,应该有自己的存储空间吧。

程序实例6:

#include<stdio.h>struct Test
{char& r;
};int main()
{char c = 'c';char& rc = c;Test ref = { c };printf("sizeof(char&) = %d\n", sizeof(char&)); //char类型的引用还是1个字节printf("sizeof(rc&) = %d\n", sizeof(rc)); //char引用的变量也是一个字节printf("sizeof(Test) = %d\n", sizeof(Test));//test结构体里面只有一个引用printf("sizeof(ref.r) = %d\n", sizeof(ref.r));//char类型的引用,是一个字节return 0;
}

输出结果:

sizeof(char&) = 1
sizeof(rc&) = 1
sizeof(Test) = 4
sizeof(ref.r) = 1

结果分析:

sizeof(Test) = 4占4个字节,指针也是4个字节。

八、将引用用于结构

实际上,C++引入引用参数,就是为结构体和类服务的,而不是为基本的int char float等内置类型服务。使用结构体引用参数的方式与使用基本变量引用是相同的。

假设有这么个结构体

struct free_throws
{string name;    //名字int made;       //罚球命中int attempts;   //罚球次数float percent;  //命中率
};

则可以这样编写函数原型,在函数中将指向该结构体的引用作为参数

void set_pc(free_throws& ft);

 如果不希望函数修改传入的结构体,可以这么操作

void display(const free_throws& ft);

程序实例7:结构体引用

#include <iostream>
#include <string>using namespace std;struct free_throws
{string name;    //名字int made;       //罚球命中int attempts;   //罚球次数float percent;  //命中率
};void display(const free_throws& ft);
void set_pc(free_throws& ft);
free_throws& accumulate(free_throws& target, const free_throws& source);int main()
{//部分初始化free_throws one = {"Yao Ming", 13, 14};free_throws two = {"Yi Jianlian", 10, 16};free_throws three = {"Sun Yue", 7, 9};free_throws four = {"Lao wang", 5, 9};free_throws five = {"Wu Yifan", 6, 14};free_throws team = {"China", 0, 0};//不初始化free_throws dup;set_pc(one); //计算1号球员的命中率display(one);//显示1号球员的参数cout << '\n';accumulate(team, one);//把1号球员的信息统计进球队display(team);//显示球队的信息cout << '\n';//use return value as argumentdisplay(accumulate(team, two)); //把2号球员的信息统计进球队,再显示球队的信息cout << '\n';accumulate(accumulate(team, three), four); //把3号球员的信息统计进球队之后,再把4号球员的信息统计进球队display(team);//显示球队信息cout << '\n';//use return value in assignmentdup = accumulate(team, five);//把5号球员的信息统计进球队之后,把结果给dupcout << "displaying team:\n";display(team); //显示球队的信息cout << '\n';cout << "displaying dup after assignment:\n";display(dup);//显示dup的信息cout << '\n';set_pc(four); //计算4号球员的命中率//乱操作,前面把第五个球员的信息录入dup之后,重新将4号球员的信息覆盖掉dupaccumulate(dup, five) = four; //cout << "displaying dup after ill-advised assignment:\n";display(dup);return 0;
}//显示球员信息
void display(const free_throws& ft)
{cout << "name: " << ft.name << '\n';cout << " made: " << ft.made << '\t';cout << " attempts: " << ft.attempts << '\t';cout << " percent: " << ft.percent << '\n';
}//计算球员命中率
void set_pc(free_throws& ft)
{if(ft.attempts != 0){ft.percent = 100.0f * float(ft.made)/float(ft.attempts);}else{ft.percent = 0;}
}//计算球队的信息
free_throws& accumulate(free_throws& target, const free_throws& source)
{target.attempts += source.attempts;target.made += source.made;set_pc(target);return target;
}

输出结果:

name: Yao Mingmade: 13        attempts: 14    percent: 92.8571name: Chinamade: 13        attempts: 14    percent: 92.8571name: Chinamade: 23        attempts: 30    percent: 76.6667name: Chinamade: 35        attempts: 48    percent: 72.9167displaying team:
name: Chinamade: 41        attempts: 62    percent: 66.129displaying dup after assignment:
name: Chinamade: 41        attempts: 62    percent: 66.129displaying dup after ill-advised assignment:
name: Lao wangmade: 5         attempts: 9     percent: 55.5556

结果分析:

起始代码里面的注释已经写得非常清楚了。

返回引用与传统返回机制的不同

函数 free_throws& accumulate(free_throws& target, const free_throws& source),返回的是结构体引用,当然返回结构体也是可以的,函数可以写成这样:
free_throws accumulate(free_throws& target, const free_throws& source);但是效率是不一样的。

传统返回机制与按值传递函数参数类似,计算关键字return后面的表达式,并将结果返回给调用函数。从概念上来讲,这个返回值是先被复制到一个临时的位置,调用函数再从这个临时位置去取这个值。分析下面的代码

double m = sqrt(16.0);cout << sqrt(25.0);

第一条语句,值4.0被复制到一个临时位置,然后再复制给m;第二条语句,值5.0被复制到一个临时位置,然后再传递给cout;

再回过头来看 dup = accumulate(team, five);如果accumulate()函数返回的是一个结构体而不是返回引用,那就要把整个结构体复制到一个临时位置,再将这个位置的值拷贝给dup,还好我们示例的结构体不大,如果结构体很大,内容很多,那不仅拷贝需要时间,还要占用一块额外的内存。

九、将引用用于类对象

将类对象传递给函数时,C++通常的做法是使用引用,例如可以通过引用,将类string、ostream、istream、ofstream、ifstream等类的对象作为参数。

下面看一个实例程序,使用string类,并演示不同的设计方案

程序实例8:创建一个函数,将指定的字符串加入到另一个字符串的前面和后面

#include <iostream>
#include <string>using namespace std;string version1(const string& s1, const string& s2);const string& version2(string& s1, const string& s2);const string& version3(string& s1, const string& s2);int main()
{string input;string copy;string result;cout << "enter a string: ";getline(cin, input);copy = input;cout <<"your string as entered: " << input << endl;result = version1(input, "***");cout << "your string enhanced: " << result << endl;cout <<"your original string as entered: " << input << endl;result = version2(input, "###");cout << "your string enhanced: " << result << endl;cout <<"your original string as entered: " << input << endl;cout << "resetting original string.\n";input = copy;result = version2(input, "@@@");cout << "your string enhanced: " << result << endl;cout <<"your original string as entered: " << input << endl;return 0;
}string version1(const string& s1, const string& s2)
{string  temp;temp = s2 + s1 + s2;return temp;
}//会有边际效应
const string& version2(string& s1, const string& s2)
{s1 = s2 + s1 + s2;return s1;
}//垃圾设计
const string& version3(string& s1, const string& s2)
{string temp;temp = s2 + s1 + s2;return temp; //返回局部变量的引用
}

返回结果

enter a string: hello world
your string as entered: hello world
your string enhanced: ***hello world***
your original string as entered: hello world
your string enhanced: ###hello world###
your original string as entered: ###hello world###
resetting original string.
your string enhanced: @@@hello world@@@
your original string as entered: @@@hello world@@@

结果分析:

程序中三个函数,version1()函数最简单,它接受两个string参数,并使用string类的相加功能,来创建一个满足要求的新字符串。这两个函数参数都是const引用,虽然不使用 引用,而是直接使用string参数也能满足要求,但是我们知道使用引用更高效,因为不需要创建新的string对象,不需要复制。引用参数用const,就是让函数不改变原有的参数。

const string& version2(string& s1, const string& s2) 函数不创建临时string对象temp,而是直接修改原来的string,该函数可以修改s1,不能修改s2,但是s1指向input的引用,该函数会修改input的原始值,这是一种错误的做法;

version3()返回一个指向函数内声明的变量的引用,函数内的变量,用完就不存在了,返回它的引用是一个危险的做法,大错特错,会导致程序崩溃。虽然有的强大的编译器没有报错,还输出的符合预期的结果。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

探索LlamaIndex:如何用Django打造高效知识库检索

简介 LlamaIndex&#xff08;前身为 GPT Index&#xff09;是一个数据框架&#xff0c;为了帮助我们去建基于大型语言模型&#xff08;LLM&#xff09;的应用程序。 主要用于处理、构建和查询自定义知识库。 它支持多种数据源格式 excel&#xff0c;txt&#xff0c;pdf&…

Matlab中collectPlaneWave函数的应用

查看文档如下&#xff1a; 可以看出最多5个参数&#xff0c;分别是阵列对象&#xff0c;信号幅度&#xff0c;入射角度&#xff0c;信号频率&#xff0c;光速。 在下面的代码中&#xff0c;我们先创建一个3阵元的阵列&#xff0c;位置为&#xff1a;&#xff08;-1,0,0&#x…

【linux进程】进程地址空间(什么是进程地址空间?为什么要有进程地址空间?)

目录 一、前言 二、 程序的地址空间是真实的 --- 物理空间吗&#xff1f; 三、进程地址空间 &#x1f525; 操作系统是如何建立起进程与物理内存之间的联系的呢&#xff1f; &#x1f525;什么是进程地址空间&#xff1f; &#x1f525;为什么不能直接去访问物理内存&a…

c小红的图上划分(牛客127)

题意&#xff1a; 有一个无向图&#xff0c;有 n 个点 m 条边&#xff0c;q 个询问&#xff0c;每次给出 L,R&#xff0c;求将图划分为至少 L 个连通块&#xff0c;最多 R个连通块的最大划分价值&#xff0c;若不可划分输出 "NO ANSWER"。 图的划分定义为将图划分为一…

Tabu Search — 温和介绍

Tabu Search — 温和介绍 目录 Tabu Search — 温和介绍 一、说明 二、什么是禁忌搜索以及我可以在哪里使用它&#xff1f; 三、禁忌搜索原则 四、短期记忆和积极搜索&#xff1a; 五、举例时间 六、结论&#xff1a; 七、参考&#xff1a; 一、说明 最近&#xff0c;我参加了…

机器学习筑基篇,​Ubuntu 24.04 编译安装 Python 及多版本切换

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] Ubuntu 24.04 编译安装最新Python及多版本切换 描述&#xff1a;说到机器学习&#xff0c;人工智能&#xff0c;深度学习不免会提到Python这一门编程语言&#xff08;人生苦短&#xff0c;及时Pyt…

蓝桥杯开发板STM32G431RBT6高阶HAL库学习FreeRtos——FreeRTOS任务调度方式

一、任务调度方式 1.1抢占式调度&#xff08;不同优先级&#xff09; 主要是针对优先级不同的任务&#xff0c;每个任务都有一个优先级&#xff0c; 优先级高的任务可以抢占优先级低的任务。1.2时间片调度&#xff08;同优先级&#xff09; 主要针对优先级相同的任务&#x…

三、虚拟机连接外网

来源网站&#xff1a;山海同行 来源地址&#xff1a;https://shanhaigo.cn 本篇资源&#xff1a;以整理分类并关联本篇地址 本篇地址&#xff1a;https://shanhaigo.cn/courseDetail/1805875642621952000 一、配置虚拟机 1. 选择NAT模式 编辑虚拟网络设置&#xff0c;选择NAT…

A Threat Actors 出售 18 万名 Shopify 用户信息

BreachForums 论坛成员最近发布了涉及 Shopify 的重大数据泄露事件。 据报道&#xff0c;属于近 180,000 名用户的敏感数据遭到泄露。 Shopify Inc. 是一家总部位于安大略省渥太华的加拿大公司。 开发和营销同名电子商务平台、Shopify POS 销售点系统以及专用于企业的营销工…

SQL脚本初始化数据

创建或选择某个数据库&#xff0c;运行窗口输入&#xff1a;source,再拖入文件&#xff0c;回车即可&#xff1b; 虽然也可以使用图形化工具初始化数据&#xff0c;但是他会有内存限制&#xff0c;也就是较大的sql文件不可以初始化&#xff0c;而运行窗口没有sql文件大小限制&…

纹波和噪声的介绍以及区别

纹波和噪声的介绍 纹波和噪声都是在电源输出中出现的信号波动&#xff0c;但两者存在明显的区别。   纹波&#xff1a;是附着于直流电平之上的包含周期性与随机性成分的杂波信号。在额定输出电压、电流的情况下&#xff0c;纹波指的是输出电压中的交流电压的峰值 。狭义上的纹…

[Godot3.3.3] – 人物死亡动画 part-2

前言 在上一个 part 中已经完成了大部分的逻辑&#xff0c;现在进行一些新的修改。 增加重力 首先将 PlayerDeath 中的 AnimationPlayer 设置为自动播放。 返回 PlayerDeath.gd 并增加一个重力 300&#xff0c;防止玩家的尸体腾空运动。 var gravity 1000 _process 函数中…

短信验证码实现

一、设置AccessKey 创建用户并配置使用权限&#xff0c;使我们拥有调用 aliyunAPI 的权限&#xff0c;之后会生成 AccessKeyID 和 AccessKey密码&#xff0c;后面我们会使用到。需要注意的是 AccessKeyID 和 AccessKey密码生成后我们需要将他保存起来&#xff0c;否则后期无法查…

基于Android Studio订餐管理项目

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 能够实现登录&#xff0c;注册、首页、订餐、购物车&#xff0c;我的。 用户注册后&#xff0c;登陆客户端即可完成订餐、浏览菜谱等功能&#xff0c;点餐&#xff0c;加入购物车&#xff0c;结算&#xff0c;以及删减…

非NI GPIB卡与LabVIEW兼容性分析

在许多测试和测量应用中&#xff0c;通用接口总线&#xff08;GPIB&#xff09;是一种广泛使用的标准。尽管国家仪器公司&#xff08;NI&#xff09;提供的GPIB硬件和LabVIEW软件的组合被广泛接受和使用&#xff0c;但成本可能较高。因此&#xff0c;一些用户会考虑使用其他厂商…

科研绘图系列:R语言两组数据散点分布图(scatter plot)

介绍 展示两组数据的散点分布图是一种图形化表示方法,用于显示两个变量之间的关系。在散点图中,每个点代表一个数据点,其x坐标对应于第一组数据的值,y坐标对应于第二组数据的值。以下是散点图可以展示的一些结果: 线性关系:如果两组数据之间存在线性关系,散点图将显示出…

基于机器学习(霍特林统计量,高斯混合模型,支持向量机)的工业数据异常检测(MATLAB R2021B)

近年来&#xff0c;隨着集散控制系统、工业物联网、智能仪表等信息技术在现代工业生产系统中的应用&#xff0c;生产过程的运行状态能够以大量数据的形式被感知和记录。基于数据的故障诊断方法以过程数据为基础&#xff0c;采用统计分析、统计学习、信号处理等方法&#xff0c;…

DNS正向解析与反向解析实验

正向解析 安装bind软件 [rootlocalhost ~]# dnf install bind bind-utils -y修改主配置文件/etc/named.conf [rootlocalhost ~]# vim /etc/named.conf重启DNS服务&#xff08;named&#xff09; [rootlocalhost ~]# systemctl restart named编辑数据配置文件。在/var/named…

从零开始读RocketMq源码(一)生产者启动

目录 前言 获取源码 总概论 生产者实例 源码 A-01:设置生产者组名称 A-02:生产者服务启动 B-01&#xff1a;初始化状态 B-02&#xff1a;该方法再次对生产者组名称进行校验 B-03&#xff1a;判断是否为默认生产者组名称 B-04: 该方法是为了实例化MQClientInstance对…

Matplotlib Artist Axes

在简介里介绍了很多了&#xff0c;这里补充一点 Axes包含一个属性patch&#xff0c;是Axes对应的方框&#xff0c;可以用来设置Axes的相关属性 ax fig.add_subplot() rect ax.patch # a Rectangle instance rect.set_facecolor(green) Axes有以下方法 Axes helper metho…