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 = $500

Using references to swap contents:
wallet1 = $500
wallet2 = $300

Using pointers to swap contents:
wallet1 = $300
wallet2 = $500

Using 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 argument
    display(accumulate(team, two)); //把2号球员的信息统计进球队,再显示球队的信息
    cout << '\n';
    
    accumulate(accumulate(team, three), four); //把3号球员的信息统计进球队之后,再把4号球员的信息统计进球队
    display(team);//显示球队信息
    cout << '\n';

    //use return value in assignment
    dup = accumulate(team, five);//把5号球员的信息统计进球队之后,把结果给dup

    cout << "displaying team:\n";
    display(team); //显示球队的信息
    cout << '\n';

    cout << "displaying dup after assignment:\n";
    display(dup);//显示dup的信息
    cout << '\n';
    set_pc(four); //计算4号球员的命中率


    //乱操作,前面把第五个球员的信息录入dup之后,重新将4号球员的信息覆盖掉dup
    accumulate(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 Ming
 made: 13        attempts: 14    percent: 92.8571

name: China
 made: 13        attempts: 14    percent: 92.8571

name: China
 made: 23        attempts: 30    percent: 76.6667

name: China
 made: 35        attempts: 48    percent: 72.9167

displaying team:
name: China
 made: 41        attempts: 62    percent: 66.129

displaying dup after assignment:
name: China
 made: 41        attempts: 62    percent: 66.129

displaying dup after ill-advised assignment:
name: Lao wang
 made: 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.mfbz.cn/a/779576.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【扩散模型】LCM LoRA:一个通用的Stable Diffusion加速模块

潜在一致性模型&#xff1a;[2310.04378] Latent Consistency Models: Synthesizing High-Resolution Images with Few-Step Inference (arxiv.org) 原文&#xff1a;Paper page - Latent Consistency Models: Synthesizing High-Resolution Images with Few-Step Inference (…

Java常见面试题汇总带答案

本文分为十九个模块,分别是: Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网 络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、 Kafka、Zookeeper、MySQL、Redis、JVM 等等… JDK 和 JRE 有什么区别? JDK:Jav…

《基于 defineProperty 实现前端运行时变量检测》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;欢迎多多交流~ &am…

Threejs环境、透视相机、坐标系、光源

文章目录 如何引入threejsnpm方式script方式script module方式 基本流程与坐标摄像机Geometry(几何体)和Material(材质)光源 如何引入threejs 对于很多刚刚上手threejs的朋友&#xff0c;可能第一步引入threejs就出问题了&#xff0c; 明明已经导入了&#xff0c;就是这样问题…

scala基础

scala基础&#xff1a; hello world: 写scala可运行文件的注意事项1、如果一个scala文件要运行&#xff0c;class要改成object2、如果是class&#xff0c;就仅单纯代表一个类&#xff0c;如果是object代表的是单例对象3、scala语法中&#xff0c;一句话结束不需要加分号4、scal…

Linux——进程间通信一(共享内存、管道、systrem V)

一、进程间通信介绍 1.1、进程间通信的概念和意义 进程间通信(IPC interprocess communication)是一组编程接口&#xff0c;让不同进程之间相互传递、交换信息(让不同的进程看到同一份资源) 数据传输:一个进程需要将它的数据发送给另外一个进程 资源共享:多个进程之间共享同样…

Hadoop-16-Hive HiveServer2 HS2 允许客户端远程执行HiveHQL HCatalog 集群规划 实机配置运行

章节内容 上一节我们完成了&#xff1a; Metastore的基础概念配置模式&#xff1a;内嵌模式、本地模式、远程模式实机配置远程模式 并测试 背景介绍 这里是三台公网云服务器&#xff0c;每台 2C4G&#xff0c;搭建一个Hadoop的学习环境&#xff0c;供我学习。 之前已经在 V…

Hadoop-YARN-Tutorial

Hadoop-YARN-Tutorial 1 What is YARN? Yarn is the acronym for yet another resource negotiator. Yarn是yet another resource negotiator的缩写。 Yarn is a resource manager created by separating the processing engine and the management function of mapreduce. …

YOLOv8_obb数据集可视化[旋转目标检测实践篇]

先贴代码,周末再补充解析。 这个篇章主要是对标注好的标签进行可视化,虽然比较简单,但是可以从可视化代码中学习到YOLOv8是如何对标签进行解析的。 import cv2 import numpy as np import os import randomdef read_obb_labels(label_file_path):with open(label_file_path,…

ViewController 生命周期

ViewController 生命周期 ViewController 生命周期测试程序&#xff1a;ViewControllerLifeCircle ViewController 生命周期 ViewController 是 iOS 开发中 MVC 框架中的 C&#xff0c;ViewColllecter 是 View&#xff08;视图&#xff09;的 Collecter&#xff08;控制器&…

Vim编辑器与Shell命令脚本

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、Vim文本编辑器 二、编写Shell脚本 三、流程控制语句 四、计划任务服务程序 致谢 一、Vim文本编辑器 “在Linux系统中一切都是文件&am…

TQ15EG开发板教程:MPSOC创建fmcomms8工程

链接&#xff1a;https://pan.baidu.com/s/1jbuYs9alP2SaqnV5fpNgyg 提取码&#xff1a;r00c 本例程需要实现在hdl加no-OS系统中&#xff0c;通过修改fmcomms8/zcu102项目&#xff0c;实现在MPSOC两个fmc口上运行fmcomms8项目。 目录 1 下载文件与切换版本 2 编译fmcomms8项…

【SpringCloud】概述 -- 微服务入门

在Java的整个学习过程中&#xff0c;大家势必会听见一些什么分布式-微服务、高并发、高可用这些专业术语&#xff0c;给人的感觉很高级&#xff0c;有一种高深莫测的感觉。可以看一下这篇博客对这些技术架构的演变有一个初步的认识: 服务端⾼并发分布式结构演进之路-CSDN博客文…

Java开源ERP系统Axelor汉化方法初探

Axelor简介 汉化过程介绍 定义语言和本地化 导出多语言记录 导入翻译 验证翻译 调整翻译 Axelor简介 2024年6月份Axelor ERP发布了8.1版本&#xff0c;适配JDK11及PostgreSQL12及以上版本&#xff08;7及以前版本适配JDK8及PostgreSQL10&#xff09;数据库。v8版本较之前…

kubernetes集群部署:node节点部署和cri-docker运行时安装(四)

安装前准备 同《kubernetes集群部署&#xff1a;环境准备及master节点部署&#xff08;二&#xff09;》 安装cri-docker 在 Kubernetes 1.20 版本之前&#xff0c;Docker 是 Kubernetes 默认的容器运行时。然而&#xff0c;Kubernetes 社区决定在 Kubernetes 1.20 及以后的…

昇思MindSpore学习入门-评价指标

当训练任务结束&#xff0c;常常需要评价函数&#xff08;Metrics&#xff09;来评估模型的好坏。不同的训练任务往往需要不同的Metrics函数。例如&#xff0c;对于二分类问题&#xff0c;常用的评价指标有precision&#xff08;准确率&#xff09;、recall&#xff08;召回率&…

代码随想录算法训练Day58|LeetCode417-太平洋大西洋水流问题、LeetCode827-最大人工岛

太平洋大西洋水流问题 力扣417-太平洋大西洋水流问题 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格组成的网格。给定一个…

调度系统揭秘(下):调度算法与架构设计

文章目录 一、调度算法1.1、广度优先:1.2、深度优先1.3、总结广度优先搜索&#xff08;BFS&#xff09;深度优先搜索&#xff08;DFS&#xff09; 二、架构设计2.1、Master/Slave架构优劣分析 2.2、Leader架构优劣分析 2.3、总结 一、调度算法 在调度系统中&#xff0c;调度算…

【】AI八股-神经网络相关

Deep-Learning-Interview-Book/docs/深度学习.md at master amusi/Deep-Learning-Interview-Book GitHub 网上相关总结&#xff1a; 小菜鸡写一写基础深度学习的问题&#xff08;复制大佬的&#xff0c;自己复习用&#xff09; - 知乎 (zhihu.com) CV面试问题准备持续更新贴 …

本安防爆手机:危险环境下的安全通信解决方案

在石油化工、煤矿、天然气等危险环境中&#xff0c;通信安全是保障工作人员生命安全和生产顺利进行的关键。防爆智能手机作为专为这些环境设计的通信工具&#xff0c;提供了全方位的安全通信解决方案。 防爆设计与材料&#xff1a; 防爆智能手机采用特殊的防爆结构和材料&…