您现在的位置是:首页 >学无止境 >【025】C++对C的扩展之引用(reference)详解网站首页学无止境
【025】C++对C的扩展之引用(reference)详解
C++对C的扩展
引言
? 作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。
?
?️ CSDN实力新星,社区专家博主
?
? 专栏介绍:从零到c++精通的学习之路。内容包括C++基础编程、中级编程、高级编程;掌握各个知识点。
?
? 专栏地址:C++从零开始到精通
?
? 博客主页:https://blog.csdn.net/Long_xu
一、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=#
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
可以看到,引用的语法更加清楚简单:
- 函数调用时传递的实参不必加
&
符号。 - 在被调函数中不必在参数前加“*”符号,引用作为变量的别名而存在,因此在一些场合中可以替代指针。
- 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 ID Scores
";
for (const auto& student : students) {
cout << student.name << " " << student.id << " ";
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函数等的引用。
总结
- 引用是一个别名,可以将一个变量与另一个变量关联起来。通过引用,我们可以使用两个不同的名称访问相同的数据。
- 引用必须在定义时初始化,一旦引用被初始化为一个对象,它就不能再绑定到另一个对象上了。
- 引用作为函数参数传递时,可以避免复制大型对象的开销,并且可以直接修改原始对象。
- 可以使用const修饰符使引用成为只读引用,这样就无法通过该引用修改原始数据。
- 引用还可以返回函数中的值或对象,在这种情况下,函数返回类型必须是引用类型。
- 指向常量的指针和常量引用之间有所不同:前者可重新赋值而后者不能重新赋值。