1 引用概述
C++中新增了一种复合类型,引用变量。引用是已定义的变量的别名。通过这个别名和原来的名字都能够找到同一份数据。引用在定义时必须被初始化,并且一旦初始化后,就不能再指向其他变量。
引用的声明语法格式:
type &ref = var;
其中,
-
type
是变量的类型 -
ref
是引用的名称 -
var
是已经存在的变量
在这个声明中,ref
就是var
的一个引用,或者说别名,对ref
的任何操作都会反映到var
上。引用类似于Windows中的快捷方式,一个可执行程序可以有多个快捷方式,通过这些快捷方式和可执行程序本身都能够运行程序。
2 引用变量创建
在C++中,创建引用变量非常简单,只需在变量声明时加上一个&
符号即可。创建引用变量的基本语法:
type &reference_name = variable_name;
其中,
-
type
是变量的数据类型 -
reference_name
是引用名字 -
variable_name
是已经存在的变量
#include <iostream>
int main()
{
// 创建一个int类型的变量
int var = 10;
std::cout << "引用修改前var=" << var << std::endl;
// 创建一个引用变量,指向上面创建的变量
int &refVar = var;
// 通过引用变量修改原始变量的值
refVar = 20;
// 输出原始变量的值,由于引用和原始变量是同一个对象,所以它们的值相同
std::cout << "引用修改后var=" << var << std::endl;
// 输出引用变量的值,由于它引用的是原始变量,所以输出相同
std::cout << "引用变量refVar=" << refVar << std::endl;
return 0;
}
注意,引用必须在声明时初始化,并且一旦初始化后,就不能再指向其他变量。这是因为引用本质上是一个已存在变量的别名,而不是一个新的变量。
3 指针引用
在C++中,指针引用(即指针的引用)是一种特殊的引用,它绑定到一个指针而不是直接绑定到一个对象。指针引用允许你间接地修改指针本身的值(即它所指向的地址),而不是指针所指向的对象的值。
指针引用的语法格式:
Type*& refToPointer = pointer;
其中,
-
Type
是指针所指向的类型 -
pointer
是一个已经声明的指针变量 -
refToPointer
是一个引用,它引用了pointer
指针
#include <iostream>
int main()
{
// 定义另外一个变量
int var2 = 20;
// 定义指针,指向var2
int *ptr = &var2;
// 再定义指针引用
int *&refPtr = ptr;
std::cout << "修改前,*ptr=" << *ptr << std::endl;
std::cout << "修改前,*refPtr=" << *refPtr << std::endl;
std::cout << "修改前,ptr addr=" << ptr << std::endl;
std::cout << "修改前,refPtr addr=" << refPtr << std::endl;
// 再定义一个变量
int var3 = 30;
refPtr = &var3;
std::cout << "修改后,*ptr=" << *ptr << std::endl;
std::cout << "修改后,*refPtr=" << *refPtr << std::endl;
std::cout << "修改后,ptr addr=" << ptr << std::endl;
std::cout << "修改后,refPtr addr=" << refPtr << std::endl;
// 利用指针引用操作它所指向的对象var3
*refPtr = 50;
std::cout << "var3=" << var3 << std::endl;
std::cout << "再次修改后,*ptr=" << *ptr << std::endl;
std::cout << "再次修改后,*refPtr=" << *refPtr << std::endl;
std::cout << "再次修改后,ptr addr=" << ptr << std::endl;
std::cout << "再次修改后,refPtr addr=" << refPtr << std::endl;
return 0;
}
4 函数参数的引用
在C++中,函数参数可以通过值传递、指针传递或引用传递。当函数参数通过引用传递时,函数内部对引用的任何修改都会反映到原始数据上。这是因为引用只是原始数据的别名,它们共享同一块内存。
在C++中,函数参数通过引用传递的语法格式非常简单。在函数声明和定义中,只需在参数类型后面加上一个&
符号来表示该参数是通过引用传递的。函数参数引用的基本语法格式:
// 函数定义
void functionName(dataType& referenceName)
{
// 函数体
// 在这里可以通过 referenceName 访问和修改调用者传递的实际参数
}
例如,
#include <iostream>
// 两数交换
// 采用指针交换
void swap(int *num1, int *num2)
{
if ((num1 != nullptr) && (num2 != nullptr))
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
}
// 采用引用实现
void swap(int &num1, int &num2)
{
int tmp = num1;
num1 = num2;
num2 = tmp;
}
int main()
{
int num1 = 10;
int num2 = 20;
std::cout << "指针形式交换前,num1=" << num1 << ", num2=" << num2 << std::endl;
swap(&num1, &num2);
swap(nullptr, nullptr);
std::cout << "指针形式交换后,num1=" << num1 << ", num2=" << num2 << std::endl;
std::cout << "引用形式交换前,num1=" << num1 << ", num2=" << num2 << std::endl;
swap(num1, num2);
std::cout << "引用形式交换后,num1=" << num1 << ", num2=" << num2 << std::endl;
return 0;
}
使用引用作为函数参数时需要注意以下几点:
-
引用必须在定义时被初始化,且初始化后不能重新绑定到另一个对象。
-
传递引用给函数时,必须确保引用的对象在函数执行期间是有效的。如果传递的是局部变量的引用,并且函数在该局部变量生命周期结束后仍然使用这个引用,那么将会导致未定义行为。
-
传递引用可以避免数据的拷贝,这在处理大型对象时可以提高效率。
-
如果函数不需要修改传入的参数,通常推荐使用常量引用作为参数,以表明函数不会修改参数的值。例如,
void printValue(const int& ref)
。
5 常量引用
常量引用(const reference
)在C++中是一种特殊的引用类型,它指向一个常量对象,意味着通过这个引用不能修改所指向对象的值。
常量引用通常在以下几种情况中使用:
-
指向常量对象:当你要引用一个常量对象时,必须使用常量引用。因为常量对象的值不能被修改,所以非常量引用不能绑定到它上面。
-
防止意外修改:即使对象不是常量,你也可以使用常量引用来传递对象,从而确保函数不会意外地修改它。这有助于向调用者表明函数不会修改其参数。
-
作为函数参数:常量引用常常用作函数参数,以表明函数不会修改其输入参数。这避免了数据的拷贝,同时提供了类型安全。
-
返回局部变量的引用:由于局部变量的生命周期在函数结束时结束,因此通常不能返回局部变量的非常量引用。但是,可以返回局部变量的常量引用,因为返回的是对值的引用,而不是对存储位置的引用。
-
重载操作符:在重载操作符时,常量引用通常用于那些不会修改对象状态的成员函数。
5.1 指向常量对象的常量引用
指向常量对象的常量引用通常用于确保函数不会修改传递给它的参数值,特别是当参数是字面量、临时对象或者不希望函数修改其值的对象时。例如,
#include <iostream>
// 函数接受一个常量整数的引用,并打印它
void PrintValue(const int& value)
{
std::cout << "value=" << value << std::endl;
// 下面的代码是错误的,因为不能通过常量引用修改值
// value = 10; // 错误:不能修改通过常量引用绑定的对象
}
int main()
{
const int number = 42; // 声明一个常量整数
// 调用printValue函数,传递number的常量引用
PrintValue(number);
// 也可以传递字面量,因为它们本质上是常量
PrintValue(100);
return 0;
}
5.2 常量引用用作函数参数
在C++中,常量引用常常用作函数参数,以传递对象而避免拷贝,同时保证函数不会修改这个对象。这种用法特别适用于大型对象,因为使用常量引用可以避免复制对象带来的性能开销。例如,
#include <iostream>
#include <string>
// 函数接受一个常量字符串引用的参数
void PrintString(const std::string& str)
{
// 打印字符串,但不能修改它
std::cout << str << std::endl;
// 下面的代码是错误的,因为不能通过常量引用修改值
// 错误:不能修改通过常量引用绑定的对象
// str = "Hello, World!";
}
int main()
{
std::string myString = "Hello, C++!";
// 调用printString函数,传递myString的常量引用
PrintString(myString);
// 传递字面量字符串,它们本质上是常量
PrintString("This is a literal string.");
return 0;
}
5.3 返回局部变量的引用
在C++中,通常不建议返回局部变量的引用,因为局部变量在函数执行结束后会被销毁,其存储位置不再有效。如果试图返回局部变量的引用,将会导致悬挂引用(dangling reference),即引用指向的内存已经被释放,但引用仍然存在,这会导致未定义行为。
然而,有一个特例是当局部变量是静态的(static
)时。静态局部变量具有持久的生命周期,它们在程序的生命周期内只会被初始化一次,并且会在程序的整个执行期间保持存在。因此,返回静态局部变量的引用是安全的。
#include <iostream>
// 函数返回一个静态局部变量的引用
const int& GetValue()
{
// 静态局部变量
static int value = 42;
// 返回对静态局部变量的引用
return value;
}
int main()
{
// 获取引用并打印值
const int& ref = GetValue();
std::cout << "ref=" << ref << std::endl;
// 验证我们得到的是同一个引用
std::cout << "再次获取,值是" << GetValue() << std::endl;
return 0;
}
6 指针和引用的区别
指针和引用在C++中都是用于间接访问对象或函数的工具,但它们之间存在一些重要的区别。它们之间的一些主要差异:
-
本质和存储:
-
指针:是一个变量,存储的是内存地址,指向某个存储单元。指针变量本身占用内存空间,通常大小为4字节(在32位系统中)或8字节(在64位系统中)。
-
引用:是某个变量的别名,与原始变量共享相同的内存地址。引用本身不占用额外的内存空间,它的大小与被引用的变量类型相同。
-
-
初始化和赋值:
-
指针:可以在任何时候被初始化或赋值,可以为空(即不指向任何对象)。
-
引用:必须在声明时初始化,并且一旦初始化后就不能再改变引用的对象(即不能重新绑定)。引用不能为空。
-
-
空值:
-
指针:可以为空,即指向
nullptr
。 -
引用:不能为空,它总是指向某个有效的对象。
-
-
自增和自减:
-
指针:可以进行自增(
++
)和自减(--
)操作,以移动到内存中的下一个或前一个位置。 -
引用:不能进行自增或自减操作,因为它们总是引用同一个对象。
-
-
多级:
-
指针:可以有多级,例如
int** p
是一个指向指针的指针。 -
引用:只能有一级,没有多级引用的概念。
-
-
安全性:
-
指针:需要更多的关注,因为错误的使用(如野指针、悬挂指针等)可能导致未定义行为。
-
引用:通常被认为更安全,因为它们总是指向有效的对象,并且不能重新绑定。
-
-
与const的结合:
-
指针:可以有
const
指针和const
指针指向的常量值。 -
引用:不能有
const
引用,但可以有对常量对象的引用。
-
-
函数参数和返回值:
-
指针:通常用于传递大型对象或表示可选值。
-
引用:通常用于表示函数将修改其参数,并且通常用于返回大型对象,以避免复制的开销。
-
7 引用使用的时机
在C++中,使用引用的时机通常取决于你想要达到的目的和上下文。以下是一些常见的使用引用的场景:
-
函数参数传递:
-
当你想通过函数修改外部变量的值时,应该使用引用传递。这样函数内部对引用的修改会反映到原始变量上。
-
对于大型对象或类,使用引用可以避免数据的拷贝,提高效率。
-
当你想明确表示函数不会修改参数时,可以使用常量引用(
const reference
)。
-
-
函数返回值:
-
对于大型对象或类,使用引用返回可以避免复制的开销。
-
但要注意,返回局部变量的引用是不安全的,因为当函数返回时,局部变量会被销毁,引用会悬挂。如果你需要返回函数内局部变量的引用,那么应该返回静态局部变量或者确保引用的对象在函数外部是存在的。
-
-
实现操作符重载:
-
在重载操作符时,通常使用引用作为参数,以允许操作符对操作数进行修改(如果需要的话)。
-
-
栈空间敏感的情况:
-
如果你对栈空间的大小非常敏感(例如在递归函数中),使用引用而不是值传递可以减少不必要的栈空间使用,因为引用本身不占用额外的栈空间。
-
-
避免拷贝大型对象:
-
当需要避免拷贝大型对象或类时,可以使用引用。例如,在函数参数和返回值中使用引用可以避免不必要的对象拷贝。
-
-
需要返回多个值的情况:
-
虽然C++没有内置的多返回值机制,但可以通过引用或指针返回多个值。例如,你可以通过引用参数返回额外的输出值。
-
-
与
const
结合使用:-
当你想传递一个不应该被修改的对象到函数中时,可以使用常量引用。这提供了类型安全性,并向调用者明确表明该参数不会被修改。
-