003 C++基础篇
前言
大家好,本文将会向您介绍引用(定义、使用场景、引用与值分别作为返回值和参数时的性能比较、引用的权限)
引用
一、引用是什么
引用:定义一个变量的别名,不是新定义一个变量,而是给已经存在的变量取了一个别名,编译器不会为引用变量单独开辟一个内存空间,它和引用的变量共用同一块内存空间
我们使用以下方式定义变量的别名
类型& 引用变量名(对象名) = 引用实体
#include <iostream>
using namespace std;
int main()
{
int a = 1;
int& b = a;
cout << &a << endl;
cout << &b << endl;
return 0;
}
我们可以观察到变量a与它引用的变量所属的是同一块空间
一个变量可以有多个引用(b、c、d)
void Test()
{
int a = 0;
int& b = a;
int& c = a;
int& d = a;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
}
二 、引用的使用场景
1、引用作参数
void swap(int& x1, int& y1)
{
int tmp = x1;
x1 = y1;
y1 = tmp;
}
int main()
{
int x = 0, y = 1;
swap(x, y);
cout << x <<" " << y << endl;
return 0;
}
根据引用的特性——可知变量x,y与他们的别名是共用同一块空间的,因此对x1,y1的修改可以影响到实参x,y
2、引用作返回值
//定义一个结构体
struct SeqList
{
int a[10];
int size;
int capacity;
};
//出了函数作用域结构体PS、a[i]都还在
int& SeqListRef(struct SeqList& ps, int i)
{
assert(i < ps.size);
return ps.a[i]; //返回数组第i个整型的别名
}
int main()
{
struct SeqList s;
s.size = 3;
//修改返回对象(别名)
SeqListRef(s, 0) = 10;
SeqListRef(s, 1) = 20;
SeqListRef(s, 2) = 30;
//打印别名
cout << SeqListRef(s, 0) << endl;
cout << SeqListRef(s, 1) << endl;
cout << SeqListRef(s, 2) << endl;
}
在以上场景中需要注意如果函数返回时,出了作用域,如果返回对象还在,则可以引用返回,如果已经返回给系统后则必须使用传值返回
什么意思呢?我们可以看一个程序
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2); //Add这个函数的返回值的别名为ret
//Add(3, 4);
cout << "Add(1,2) is :" << ret << endl;
}
上述程序是引用作返回值的场景,可是得到的结果是随机值
原因是:返回了一个局部变量的引用,ret引用的是已经销毁的变量c的地址,打印ret的值会导致未定义的行为,表现为打印随机值
关于引用还应注意:
引用在定义时必须初始化
引用在初始化时引用一个实体后,就不能再引用其他实体(即一旦一个实体被赋值给一个引用,那么该引用就只能指向该实体,不能再指向其他实体)
引用与实体的类型必须一致
值和引用作为函数参数的性能比较
struct A
{
int arr[10000] = {};
};
void TestFunc1(A a)
{
;
}
void TestFunc2(A& a)
{
;
}
void parameRefAndValue()
{
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()
{
parameRefAndValue();
return 0;
}
值和引用作为返回值时的性能比较
struct A
{
int arr[10000] = {};
};
A a;
//返回值
A TestFunc1()
{
return a;
}
//返回引用
A& TestFunc2()
{
return a; //返回类型为A&
}
void ReturnRefAndValue()
{
A a;
size_t begin1 = clock(); //计时开始
for (size_t i = 0; i < 10000; ++i)
{
TestFunc1();
}
size_t end1 = clock(); //计时结束
size_t begin2 = clock(); //计时开始
for (size_t i = 0; i < 10000; ++i)
{
TestFunc2();
}
size_t end2 = clock(); //计时结束
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
ReturnRefAndValue();
return 0;
}
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常底下的,尤其是当参数或者返回值类型非常大的时,效率就更低
三、引用权限
在引用过程中
权限可以平移
权限可以缩小
权限不能放大
关于权限的相关场景可以看看以下程序
int func()
{
int a = 0;
return a;
}
int main()
{
const int a = 0;
//1、权限放大
//error:int& b = a;
//2、非权限放大而是赋值,引用、指针存在权限
// int b = a;
//3、权限平移
const int& c = a;
//4、权限缩小
int x = 0;
const int& y = x;
//5、当发生类型转换时,会产生一个带有常性的临时变量
int i = 0;
//先将i给临时变量
//常量是不具有别名的,常量在编译时就被确定了
//error:double& d = i;
const double& d = i;
//6.返回一个局部变量
//出了函数作用域,变量销毁,此时会将值传给一个带有常性的临时变量
//error:int& ret = func();
const int& ret = func();
return 0;
}
小结
本文介绍了引用的相关知识,如果本文存在疏漏或错误的地方,还请您指出,祝您天天开心啦!