【025】C++对C的扩展之引用(reference)详解

news2024/11/18 3:24:17

C++对C的扩展

  • 引言
  • 一、struct类型增强
  • 二、bool类型关键字
  • 三、引用(reference)
    • 3.1、普通变量的引用
    • 3.2、数组的引用
    • 3.3、指针变量的引用
    • 3.4、函数的引用
    • 3.5、引用作为函数的参数
    • 3.6、引用作为函数的返回值类型
    • 3.7、常引用
  • 四、引用的典型应用场景
  • 五、引用在一个较为复杂的项目中的多种应用场景
  • 总结

引言


💡 作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。
👉
🎖️ CSDN实力新星,社区专家博主
👉
🔔 专栏介绍:从零到c++精通的学习之路。内容包括C++基础编程、中级编程、高级编程;掌握各个知识点。
👉
🔔 专栏地址:C++从零开始到精通
👉
🔔 博客主页:https://blog.csdn.net/Long_xu


🔔 上一篇:【024】C++对C的扩展之命名空间namespace详解

一、struct类型增强

  • C语言中定义结构体变量需要加上struct关键字,而C++可以不需要。
  • C语言中的结构体只能定义成员变量,不能定义成员函数;C++既可以定义成员变量,也可以定义成员函数。

(1)C++结构体中既可以定义成员变量,也可以定义成员函数。

struct Student{
	string name;
	int age;
	void setName(string str)
	{
		name=str;
	}
	void setAge(int num)
	{
		age=num;
	}
	void show()
	{
		cout<<"name : "<<name<<", age : "<<age<<endl;
	}
};

(2)C++中定义结构体变量不需要加struct关键字。

struct Student{
	string name;
	int age;
	void setName(string str)
	{
		name=str;
	}
	void setAge(int num)
	{
		age=num;
	}
	void show()
	{
		cout<<"name : "<<name<<", age : "<<age<<endl;
	}
};

void test()
{
	Student stu;
	stu.setName("Lion");
	stu.setAge("18");
	stu.show();
}

二、bool类型关键字

标准C++的bool类型有两种内建的常量:true(转换为整数1)和false(转换为整数0);这三个都是关键字。在C++中,bool类型关键字用于表示布尔值,即真或假。bool类型只有两个取值:true和false。

bool类型占1个字节大小,给bool类型赋值时,非0值会自动转换为true,0值会自动转换为false。

void test()
{
	cout<<sizeof(true)<<endl;
	bool flag=true;
	flag=100;//给bool类型赋值时,非0值会自动转换为true,0值会自动转换为false。
}

三、引用(reference)

在C/C++中指针的作用基本都是一样的,但是C++增加了另外一种给函数传递地址的途径,这就是按引用传递。变量名实际上是对一段连续内存空间的别名,是一个标号程序中通过变量来申请并命名内存空间的名字可以使用存储空间。
对一段连续的内存空间只能取一个别名吗?C++新增引用的概念,可以作为一个已定义变量的别名。

引用的本质是给变量取个别名;引用是在定义时使用&符号声明的,例如:

int x = 10;
int &ref = x;   // 引用x

上面这个例子中,ref是一个对x的引用,它相当于x的别名。因此,通过修改ref也会修改原始变量x的值。

引用通常与函数参数一起使用,以便将函数调用中的实际参数传递给函数并进行更改。这样可以避免复制大型数据结构和类对象所需的开销,并且可以使代码更加简洁易懂。

需要注意以下几点:

  • 引用必须在定义时初始化,且不能更改它所引用的变量。
  • 引用不是对象,只是一个别名。因此,对引用应用sizeof运算符将返回其所引用对象的大小。
  • 引用通常与指针混淆,但它们是不同的概念。引用可以看作是一种自动解引用的指针,而指针需要显式进行解引用操作。

3.1、普通变量的引用

引用必须初始化。

int main()
{
	int a = 100;
	// 系统不会为引用开辟空间,引用必须初始化
	int &b = a;
	// a,b 代表同一个空间内容
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "&a=" << &a << endl;
	cout << "&b=" << &b << endl;
	// 操作b等价于操作a
	b = 200;
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	return 0;
}

输出:

a=100
b=100
&a=0096F7F8
&b=0096F7F8
a=200
b=200

3.2、数组的引用

语法:

数据类型 (&引用别名)[数组元素个数]=数组名;
int arr[5]={1,2,3,4,5};
int (&myarr)[5]=arr;
for(int i=0;i<5;i++)
{
	cout<<myarr[i]<<" ";
}
cout<<endl;

输出:

1 2 3 4 5

3.3、指针变量的引用

语法:

数据类型* &引用别名=指针变量名;

示例:

int num=100;
int *p=&num;
int* &my=p;
cout<<"*p = "<<*p<<endl;
cout<<"*my="<<*my<<endl;

输出:

*p = 100
*my=100

3.4、函数的引用

语法:

返回类型 (&引用别名)(参数列表)=函数名;

示例:

void func(void)
{
	cout<<"func"<<endl;
}

int main()
{
	void (&myFunc)(void)=func;
	myFunc();
	return 0;
}

3.5、引用作为函数的参数

函数内部可以通过引用 操作外部变量。

#include <iostream>
using namespace std;
// 指针的方式
void swap01(int *x,int *y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
// 引用的方式
void swap01(int &x, int &y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;

	cout << "a = " << a << ", b = " << b << endl;
	swap01(&a, &b);//传地址
	cout << "a = " << a << ", b = " << b << endl;

	swap01(a, b);//传引用
	cout << "a = " << a << ", b = " << b << endl;

	return 0;
}

输出:

a = 10, b = 20
a = 20, b = 10
a = 10, b = 20

可以看到,引用的语法更加清楚简单:

  1. 函数调用时传递的实参不必加&符号。
  2. 在被调函数中不必在参数前加“*”符号,引用作为变量的别名而存在,因此在一些场合中可以替代指针。
  3. C++主张用引用传递取代地址传递的方式,因此引用语法容易且不易出错。

3.6、引用作为函数的返回值类型

(1)不要返回普通局部变量的引用。普通变量一旦函数结束就会释放,相当于给已经释放的内存空间取别名,这是不应该的。操作已经释放的内存空间可能会造成段错误。

int& gen()
{
	int num = 100;
	// 返回num的别名
	return num;
}

int main()
{
	int &num = gen();
	num = 200;//可能会出现段错误
	cout << num << endl;
	return 0;
}

(2)返回值类型为引用可以完成链式操作。

#include <iostream>
using namespace std;
struct Stu{
	Stu& printfStu(Stu &obj,int num)
	{
		cout<<num<<endl;
		return obj;
	}
};
int main()
{
	Stu ob;
	ob.printfStu(ob,100).printfStu(ob,200).printfStu(ob,300);
}

输出:

100
200
300

3.7、常引用

(1)给常量取别名,不能通过常引用修改内容。

//int &a=10;//error
const int &a=10;//正确
// a=20;//error,不允许修改
cout<<a<<endl;

(2)常引用作为函数的参数,防止函数内部修改外部的值。

#include <iostream>
using namespace std;
void printNum(const int &a)
{
	// a=20;//error
	cout<<a<<endl;
}

int main()
{
	int num=100;
	printNum(num);//输出100
	return 0;
}

四、引用的典型应用场景

引用(reference)是C++中非常有用的一个特性,它可以使代码更简洁、更易读。

(1)交换变量。使用引用可以使交换两个变量的值更加简洁:

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

(2)遍历容器(容器将在后面章节讲到,这里先抛出一个概念)。在遍历STL容器时,使用引用可以避免不必要的复制开销,例如:

vector<int> vec{1, 2, 3};
for (int& x : vec) {
    x *= 2; // 直接修改原始数据
}

(3)函数返回多个值。在函数中使用引用,可以方便地返回多个值。例如:

#include <iostream>
#include <vector>
using namespace std;
void getMinMax(const vector<int>& vec, int& minVal, int& maxVal) {
    minVal = INT_MAX;
    maxVal = INT_MIN;
    for (int x : vec) {
        if (x < minVal) minVal = x;
        if (x > maxVal) maxVal = x;
    }
}

int main() {
    vector<int> vec{1, 2, 3};
    int minValue, maxValue;
    getMinMax(vec, minValue, maxValue);
    cout << "minValue: " << minValue << endl; // 输出1
    cout << "maxValue: " << maxValue << endl; // 输出3
    return 0;
}

(4)实现链表。在实现链表时,我们经常需要操作节点的指针,使用引用可以使代码更加简洁。例如:

#include <iostream>
using namespace std;
struct Node {
    int data;
    Node* next;
};

void insert(Node*& head, int val) {
    Node* newNode = new Node{val, nullptr};
    if (head == nullptr) {
        head = newNode;
        return;
    }
    Node* cur = head;
    while (cur->next != nullptr) {
        cur = cur->next;
    }
    cur->next = newNode;
}

int main() {
    Node* head = nullptr;
    insert(head, 1);
    insert(head, 2);
    return 0;
}

这里insert函数中的Node*&表示对头指针的引用,可以直接修改头指针。

五、引用在一个较为复杂的项目中的多种应用场景

假设开发一个学生成绩管理系统,需要实现以下功能:

  • 计算学生总分、平均分和排名;
  • 查询某个学生的成绩,并可以修改该学生的成绩;
  • 根据成绩排序并输出所有学生的信息。

(1)为了方便起见,将每个学生的信息保存在如下结构体中:

struct Student {
    string name;
    int id;
    vector<int> scores;
};

(2)为了计算每个学生的总分和平均分,可以编写如下函数:

void calculateTotalAndAverageScore(Student& student, int& totalScore, double& averageScore) {
    totalScore = 0;
    for (int score : student.scores) {
        totalScore += score;
    }
    averageScore = static_cast<double>(totalScore) / student.scores.size();
}

注意这里使用了对Student对象和totalScore、averageScore变量的引用,以免不必要地复制数据。

(3)为了查询某个学生的成绩并进行修改,可以编写如下函数:

bool findStudentById(vector<Student>& students, int id, Student*& foundStudent) {
    for (auto& student : students) {
        if (student.id == id) {
            foundStudent = &student;
            return true;
        }
    }
    return false;
}

void modifyStudentScores(Student& student, const vector<int>& newScores) {
    student.scores = newScores;
}

这里使用了对students变量、id参数和foundStudent指针的引用,以避免不必要的复制。

(4)最后,为了根据成绩排序并输出所有学生信息,可以编写如下函数:

bool compareStudentsByTotalScore(const Student& s1, const Student& s2) {
    int totalScore1, totalScore2;
    double averageScore1, averageScore2;
    calculateTotalAndAverageScore(s1, totalScore1, averageScore1);
    calculateTotalAndAverageScore(s2, totalScore2, averageScore2);
    return totalScore1 > totalScore2;
}

void printAllStudents(const vector<Student>& students) {
    cout << "Name\tID\tScores\n";
    for (const auto& student : students) {
        cout << student.name << "\t" << student.id << "\t";
        for (int score : student.scores) {
            cout << score << " ";
        }
        cout << endl;
    }
}

void sortAndPrintAllStudents(vector<Student>& students) {
    sort(students.begin(), students.end(), compareStudentsByTotalScore);
    printAllStudents(students);
}

这里使用了对students变量、compareStudentsByTotalScore函数、printAllStudents函数等的引用。

总结

  1. 引用是一个别名,可以将一个变量与另一个变量关联起来。通过引用,我们可以使用两个不同的名称访问相同的数据。
  2. 引用必须在定义时初始化,一旦引用被初始化为一个对象,它就不能再绑定到另一个对象上了。
  3. 引用作为函数参数传递时,可以避免复制大型对象的开销,并且可以直接修改原始对象。
  4. 可以使用const修饰符使引用成为只读引用,这样就无法通过该引用修改原始数据。
  5. 引用还可以返回函数中的值或对象,在这种情况下,函数返回类型必须是引用类型。
  6. 指向常量的指针和常量引用之间有所不同:前者可重新赋值而后者不能重新赋值。

在这里插入图片描述

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

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

相关文章

3D绘制爱心(python)

目录 图像绘制代码结果显示参考 图像绘制代码 import time import numpy as np import matplotlib.pyplot as pltclass Guess:def __init__(self, bbox(-1.5, 1.5), resolution50, lines20, scale1.2) -> None:"""bbox: 控制画格的大小resolution: 控制爱心…

6. WebGPU 纹理(Textures )

在本文中&#xff0c;我们将介绍纹理的基础知识。在之前的文章中&#xff0c;我们介绍了 将数据传递到着色器的主要方法&#xff0c;它们是inter-stage variables, uniforms, storage-buffers, and vertex-buffers。将数据传递到着色器的最后一种主要方式是纹理。 纹理通常表示…

北宋文坛伯乐的传承关系

北宋的科学文化水平达到了古代最高峰&#xff0c;文化繁荣&#xff0c;名人辈出&#xff0c;涌现出了一大批大文豪&#xff0c;文坛领袖&#xff0c;词派宗祖等大师级的人物。例如&#xff0c; “宰相词人”晏殊、“两宋三百年来第一人”的范仲淹、“唐宋散文八大家”中的欧阳修…

java版本工程项目管理系统源码-简洁+好用+全面-工程项目管理

​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 ​系统定义 工程项目管理企业不直接与该工程项目的总承包企…

swagger实现在线接口文档

一、前言 之前换了新的单位后&#xff0c;单位的项目有使用到swagger&#xff0c;那个时候觉得这个好方便&#xff0c;后面是建立在他们搭建好的基础上使用一些swagger的注解&#xff0c;但一直想要自己去实现&#xff0c;奈何没有机会&#xff0c;这次机会终于来了&#xff0…

《楚天法治》期刊简介及投稿邮箱

《楚天法治》期刊简介及投稿邮箱 《楚天法治》杂志为半月刊&#xff0c;是一本以关注法制热点、推进法治建设、促进法治社会和谐发展为宗旨的法制类专业期刊。 主管单位&#xff1a;湖北日报传媒集团 主办单位&#xff1a;湖北日报楚天传媒&#xff08;集团&#xff09;有限…

解析kubernetes部署:微信配置文件部署

微信安全配置文件 以下两步二选一 一、暂时没有微信配置文件 1、创建configmap kubectlcreateconfigmapweixin-config--from-file/opt/kubernetes/weixin/weixin-mp.txt--namespacens-javashop 2、创建微信配置文件service(执行如下命令) kubectlcreate-f/opt/kubernetes/weix…

魅族20 INFINITY首销在即:比魅族20 Pro贵2200元,究竟有啥区别?

这两天又有一款高端手机要开售了&#xff0c;这款手机就是魅族20 INFINITY无界版&#xff0c;手机其实早早就已经发布&#xff0c;只不过一直没开售。从配置来说&#xff0c;这款手机也是采用了骁龙8 Gen2芯片&#xff0c;目前只有12GB256GB版一个规格&#xff0c;和魅族20 Pro…

WPF开发txt阅读器7:自定义文字和背景颜色

文章目录 添加控件具体实现代码说明 txt阅读器系列&#xff1a; 需求分析和文件读写目录提取类&#x1f48e;列表控件与目录字体控件绑定书籍管理系统&#x1f48e;用树形图管理书籍 添加控件 除了字体、字体大小之外&#xff0c;文字和背景颜色也会影响阅读观感&#xff0c…

k8s中的PVC为何需要延迟绑定?(WaitForFirstConsumer)

文章目录 背景为什么需要延迟绑定延迟绑定的原理storgeageClass yaml配置 背景 有一个pod, 使用的pvc叫pvc-1&#xff0c; 我们希望它只运行在node-2上&#xff0c;在当前的集群中存在两台主机符合pod的pvc的要求, 假如node-1上是pv-1&#xff0c; node-2上是pv-2&#xff0c;…

stm32读取DHT11温湿度传感器

stm32读取DHT11温湿度传感器 一.序言二.DHT11响应数据格式三.DHT11通讯过程3.1 产生起始信号3.2 读取数据03.3 读取数据1DHT11停止信号 四.代码实例4.1读取DHT11源文件4.2 读取DHT11头文件 五.结语5.1 总结整体思路5.2 对读者的期望 一.序言 我们知道DHT11是单总线协议&#x…

单体、SOA、微服务的介绍

本文涉及的内容以及知识点如下&#xff1a; 1、单体架构 2、单体架构的拆分 3、SOA与微服务的区别 4、微服务的优缺点 5、微服务的消息 6、服务集成 7、数据的去中心化 目录 单体架构单体架构的拆分 SOA与微服务 微服务的优缺点微服务消息服务集成数据去中心化 单体架构 Web应用…

springboot+vue.js汽车销售网站il05r

本靓车汽车销售网站管理员功能有个人中心&#xff0c;用户管理&#xff0c;车辆展示管理&#xff0c;车辆品牌管理&#xff0c;车辆型号管理&#xff0c;维修材料管理&#xff0c;材料分类管理&#xff0c;用户交流&#xff0c;留言板管理&#xff0c;系统管理&#xff0c;订单…

国产openeuler22.03容器环境下固定容器IP的实例

Docker 中默认的容器网络为名为bridge的桥接网络&#xff0c;使用DHCP协议&#xff0c;不能固定容器IP&#xff0c;每次重启&#xff0c;容器的IP是按其启动顺序来分配的&#xff0c;单宿主机多容器时&#xff0c;容器的IP就会发生变化&#xff0c;不利于程序 连接及安全加固配…

Ada Tutorial(1):Ada基础——wordcount程序

文章目录 Ada 常用的库和方法Ada.Characters.Handling字符类型函数转换函数 Ada 基础语法概览数据类型和子类型类型&#xff08;Type&#xff09;子类型&#xff08;Subtype&#xff09;类型和子类型的区别常用类型转换方法显示类型转换类型相关函数 循环语句无条件循环 (Loop)…

探索智慧档案的发展路径,开源网安受邀参加国际档案日专题讲座

近日&#xff0c;深圳市档案学会举办了“奋进新征程&#xff0c;兰台谱新篇”2023年国际档案日专题讲座。开源网安常务副总经理王颉博士受邀参加此次讲座&#xff0c;分享了《档案信息安全实务&#xff1a;时代与展望》&#xff0c;从软件供应链安全的维度为到场人员讲解了数字…

Vue3 ElementPlus Dialog封装 (一:使用props emit)

引言 多个页面中需要录入用户数据&#xff08;弹窗内容相同&#xff09;&#xff0c;重复写弹窗代码比较繁琐。因此封装一下组件&#xff0c;使用效果如下&#xff1a; 本例中模型较简单&#xff0c;记录下使用方法和原理 实现原理 参考VUE官方两个例子&#xff0c;基本父子件…

调试CAN过滤器功能使用笔记

一.关于CAN过滤器的配置及使用 提示&#xff1a;此处使用的是雅特力的芯片&#xff08;基本兼容stm32的芯片&#xff09; 这里只讲32位宽的过滤器&#xff0c;16位的用法基本相同&#xff0c;注意因为位数减少数据不一样。 1.1首先过滤器有两种工作模式&#xff1a; 1.标识符…

Rust每日一练(Leetday0025) 矩阵置零、搜索二维矩阵、颜色分类

目录 73. 矩阵置零 Set Matrix Zeroes &#x1f31f;&#x1f31f; 74. 搜索二维矩阵 Search A 2d-Matrix &#x1f31f;&#x1f31f; 75. 颜色分类 Sort Colors &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang…

逻辑回归与决策树回归

逻辑回归 逻辑回归函数: 逻辑回归分析属于概率型回归分析方法。 假设在自变量xi1、xi2…xip的作用下&#xff0c;因变量y取值为1和0的二值变量&#xff0c;其取值为1的概率为pi&#xff0c;则可以表示为&#xff1a; 相反&#xff0c;y取值为0的概率即&#xff1a; 对y取值为…