概念
C++中引入了一个新的概念:引用
是一种特殊的数据类型,用于给变量起一个别名。通过引用,我们可以使用一个变量来访问另一个已存在的变量,从而实现对同一块内存空间的多个名称的访问。
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
声明方式
数据类型 &引用变量名 = 原变量名;
代码举例:
void TestRef1()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
输出结果:
000000771F79FBD4
000000771F79FBD4
需要注意的是:引用类型必须和引用实体是同种类型的。
引用特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
代码举例:
void TestRef2()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
输出结果:
000000A00F4FF8E4 000000A00F4FF8E4 000000A00F4FF8E4
常引用
常引用(常量引用)是指在声明引用时使用const关键字来限制被引用对象的修改。通过常引用,我们可以确保在引用的生命周期内,被引用的对象不会被修改。
声明方式:
常引用的声明方式为:const 数据类型 &引用变量名 = 原变量名;
概念 && 特性
- 常引用绑定到了一个常量或者表达式上,它们不能被修改,因为常引用指向的是一个只读的对象。
- 常引用可以绑定到非常量对象、常量对象以及表达式上,但不能通过常引用来修改所引用的对象。
- 常引用可以用于函数参数,从而避免了函数内部对被引用对象的不必要拷贝。
代码举例:
#include <iostream>
void print(const int& num) {
std::cout << "Value: " << num << std::endl;
}
int main() {
int x = 5;
const int& ref = x; // 声明一个常引用ref绑定到变量x
std::cout << "x = " << x << std::endl; // 输出:x = 5
std::cout << "ref = " << ref << std::endl; // 输出:ref = 5
// ref = 10; // 错误!常引用无法修改被引用的对象
x = 10; // 修改原始变量x的值
std::cout << "x = " << x << std::endl; // 输出:x = 10
std::cout << "ref = " << ref << std::endl; // 输出:ref = 10
int y = 20;
print(y); // 传递y的常引用给函数print
return 0;
}
应用场景
- 函数参数传递:通过引用作为函数参数,可以避免不必要的对象拷贝,提高性能,并且可以修改原始数据。特别是对于大型对象或容器,使用引用作为函数参数通常更高效。
代码举例:
void modifyValue(int& num) {
num += 10;
}
int main() {
int x = 5;
modifyValue(x); // 通过引用修改原始变量x的值
return 0;
}
- 做返回值:通过将函数返回类型声明为引用,可以避免产生临时对象,并且可以直接操作原始数据。
代码举例:
const string& getFullName(const string& firstName, const string& lastName) {
static string fullName;
fullName = firstName + " " + lastName;
return fullName;
}
int main() {
const string& name = getFullName("Jonathan", "Jostar"); // 引用函数返回值,避免临时对象的创建
return 0;
}
- 循环遍历容器:使用引用可以避免在循环遍历容器时进行对象的拷贝,提高性能。
代码举例:
std::vector<int> numbers = {1, 2, 3, 4, 5};`在这里插入代码片`
for (const auto& num : numbers) {
std::cout << num << " "; // 使用引用遍历容器,避免拷贝
}
- 类成员变量:引用可以作为类成员变量的别名,方便访问和修改成员变量。
代码举例:
class Circle {
public:
Circle(float& r) : radius(r) {}
void doubleRadius() {
radius *= 2;
}
private:
float& radius;
};
int main() {
float r = 5.0;
Circle circle(r); // 引用作为类成员变量的别名,方便访问和修改
circle.doubleRadius(); // 直接修改原始数据r
return 0;
}
传值、传引用的差别
传值是指将变量的值复制一份,然后将复制的值传递给函数或方法。在函数或方法内对参数的修改不会影响原始变量的值。
传引用是指将变量的引用(内存地址)传递给函数或方法。在函数或方法内对参数的修改会直接影响原始变量的值。
主要区别:
内存使用:传值需要额外的内存空间来存储复制的参数值,而传引用不需要额外的内存空间。
参数的修改:传值不会修改原始变量的值,而传引用可以修改原始变量的值。
效率:传值涉及数据的复制,可能会产生额外的性能开销,特别是当传递大型对象时。传引用不需要进行数据复制,因此更高效。
引用 与 指针 的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
代码举例:
void increment(int& ref) {
ref++;
}
int main() {
int num = 5;
int& ref = num; // 引用ref和变量num共享同一块内存空间
std::cout << "num: " << num << std::endl; // 输出 num 的初始值
increment(ref); // 通过引用修改变量num的值
std::cout << "num after increment: " << num << std::endl; // 输出增加后的num的值
return 0;
}
输出结果:
num: 5
num after increment: 6
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
引用与指针的不同点:
- 引用 概念上定义一个变量的别名,指针 存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而 指针 可以在任何时候指向任何一个同类型实体
- 没有空引用,但 有空指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
总的来说,引用提供了一种更安全、更简洁的方式来访问对象,它是指针的一种高级封装。指针则提供了更灵活的内存管理和操作方式。在选择使用引用还是指针时,需要根据具体的需求和语言特性进行选择。