跳到主要内容

C++ 引用声明

引用的基本概念

在C++中,引用是一个变量的别名。引用一旦初始化后,就永久绑定到了其关联的变量,不能再绑定到其他变量。与指针不同,引用必须在声明时进行初始化,且没有空引用的概念。

引用提供了一种间接访问对象的方式,但使用起来比指针更安全、更简单,不需要解引用操作符(*)。

引用声明的基本语法

引用声明的基本语法如下:

cpp
类型& 引用名 = 变量名;

这里的&符号表示我们声明的是一个引用,而不是普通变量。

基本示例

cpp
#include <iostream>
using namespace std;

int main() {
int original = 10; // 原始变量
int& ref = original; // ref是original的引用

cout << "原始值: " << original << endl;
cout << "引用值: " << ref << endl;

// 通过引用修改原始值
ref = 20;

cout << "修改后的原始值: " << original << endl;
cout << "修改后的引用值: " << ref << endl;

// 通过原始变量修改值
original = 30;

cout << "再次修改后的原始值: " << original << endl;
cout << "再次修改后的引用值: " << ref << endl;

return 0;
}

输出结果:

原始值: 10
引用值: 10
修改后的原始值: 20
修改后的引用值: 20
再次修改后的原始值: 30
再次修改后的引用值: 30
备注

在此示例中,我们可以看到无论是通过原始变量original还是其引用ref修改值,两者始终保持同步,因为它们实际上指向同一块内存。

引用的特性

  1. 必须初始化:引用必须在声明时初始化,不能像指针那样先声明后赋值。

    cpp
    int& invalidRef;  // 错误:引用必须初始化
    int x = 10;
    int& validRef = x; // 正确:引用在声明时初始化
  2. 不能重新绑定:一旦引用被初始化,它就不能再引用其他对象。

    cpp
    int x = 10;
    int y = 20;
    int& ref = x; // ref引用x
    ref = y; // 这不是重新绑定,而是将y的值赋给x
  3. 没有空引用:不存在空引用的概念,引用必须引用一个已存在的对象。

    cpp
    int& ref = nullptr;  // 错误:不能创建空引用
  4. 引用的地址就是被引用对象的地址

    cpp
    int x = 10;
    int& ref = x;
    cout << &x << " " << &ref << endl; // 两者输出相同的地址

常量引用

常量引用是指向常量的引用,通过常量引用不能修改被引用的变量。

cpp
#include <iostream>
using namespace std;

int main() {
int x = 10;
const int& ref = x; // 常量引用

cout << "x = " << x << ", ref = " << ref << endl;

// ref = 20; // 错误:通过常量引用不能修改值
x = 20; // 正确:可以通过原始变量修改值

cout << "修改后: x = " << x << ", ref = " << ref << endl;

return 0;
}

输出结果:

x = 10, ref = 10
修改后: x = 20, ref = 20

常量引用有一个特殊的性质:它可以绑定到右值(如字面量)。

cpp
// 正确:常量引用可以绑定到右值
const int& ref = 42;

// 错误:非常量引用不能绑定到右值
// int& badRef = 42;
提示

常量引用常用于函数参数中,可以避免拷贝大型对象的开销,同时保证函数不会修改传入的参数。

引用作为函数参数

引用作为函数参数有两个主要优点:

  1. 避免了值传递时的拷贝开销
  2. 允许函数修改传入的参数(非常量引用)

非常量引用参数

cpp
#include <iostream>
using namespace std;

// 使用引用参数交换两个值
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}

int main() {
int x = 5, y = 10;

cout << "交换前: x = " << x << ", y = " << y << endl;

swap(x, y);

cout << "交换后: x = " << x << ", y = " << y << endl;

return 0;
}

输出结果:

交换前: x = 5, y = 10
交换后: x = 10, y = 5

常量引用参数

当你不需要修改参数,但又想避免拷贝开销时,常量引用是最佳选择:

cpp
#include <iostream>
#include <string>
using namespace std;

// 使用常量引用参数计算字符串长度
void printLength(const string& str) {
cout << "字符串 \"" << str << "\" 的长度是: " << str.length() << endl;
// str = "修改"; // 错误:不能修改常量引用
}

int main() {
string name = "Hello, C++";

printLength(name);
printLength("直接传递字面量"); // 可以直接传递字面量,因为是常量引用

return 0;
}

输出结果:

字符串 "Hello, C++" 的长度是: 9
字符串 "直接传递字面量" 的长度是: 18

引用作为函数返回值

函数可以返回引用,这在某些情况下非常有用,特别是当你希望函数调用可以出现在赋值语句的左侧时。

cpp
#include <iostream>
using namespace std;

// 返回数组元素的引用
int& getElement(int arr[], int index) {
return arr[index];
}

int main() {
int numbers[5] = {10, 20, 30, 40, 50};

cout << "修改前的数组: ";
for (int i = 0; i < 5; i++) {
cout << numbers[i] << " ";
}
cout << endl;

// 函数返回引用可以作为左值使用
getElement(numbers, 2) = 100;

cout << "修改后的数组: ";
for (int i = 0; i < 5; i++) {
cout << numbers[i] << " ";
}
cout << endl;

return 0;
}

输出结果:

修改前的数组: 10 20 30 40 50 
修改后的数组: 10 20 100 40 50
注意

返回局部变量的引用是危险的,因为局部变量在函数结束时会被销毁,从而导致悬空引用!

cpp
// 危险:返回局部变量的引用
int& getDangerous() {
int local = 10;
return local; // 错误:返回局部变量的引用
}

实际应用场景

场景1:操作符重载

引用在操作符重载中非常常见,特别是流操作符:

cpp
#include <iostream>
using namespace std;

class Complex {
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}

friend ostream& operator<<(ostream& out, const Complex& c) {
out << c.real;
if (c.imag >= 0) out << "+";
out << c.imag << "i";
return out;
}

private:
double real;
double imag;
};

int main() {
Complex c1(3.0, 4.0);
Complex c2(2.5, -1.5);

cout << "c1 = " << c1 << endl;
cout << "c2 = " << c2 << endl;

return 0;
}

输出结果:

c1 = 3+4i
c2 = 2.5-1.5i

场景2:避免大型对象的拷贝

当处理大型对象(如向量或矩阵)时,使用引用可以避免不必要的拷贝:

cpp
#include <iostream>
#include <vector>
using namespace std;

// 使用常量引用避免大型向量的拷贝
double calculateAverage(const vector<double>& values) {
double sum = 0;
for (const auto& val : values) {
sum += val;
}
return values.empty() ? 0 : sum / values.size();
}

int main() {
vector<double> temperatures = {22.5, 23.2, 21.8, 24.0, 20.5};

cout << "平均温度: " << calculateAverage(temperatures) << " 度" << endl;

return 0;
}

输出结果:

平均温度: 22.4 度

场景3:实现链式调用

引用返回值可以用来实现链式方法调用,这在构建API时非常有用:

cpp
#include <iostream>
#include <string>
using namespace std;

class StringBuilder {
public:
StringBuilder& append(const string& str) {
data += str;
return *this; // 返回自身的引用
}

StringBuilder& appendLine(const string& str) {
data += str + "\n";
return *this;
}

string toString() const {
return data;
}

private:
string data;
};

int main() {
StringBuilder builder;

// 链式调用
string message = builder.append("Hello, ")
.append("C++ ")
.appendLine("References!")
.append("They are ")
.append("really ")
.append("useful.")
.toString();

cout << message << endl;

return 0;
}

输出结果:

Hello, C++ References!
They are really useful.

总结

C++引用声明是C++语言的一个强大特性,它提供了一种安全、方便的间接访问对象的方式:

  1. 基本特性

    • 引用是变量的别名
    • 必须在声明时初始化
    • 一旦绑定,不能重新绑定到其他对象
    • 没有空引用的概念
  2. 常见用途

    • 函数参数(避免拷贝,允许修改)
    • 常量引用(避免拷贝,防止修改)
    • 函数返回值(允许函数调用作为左值)
    • 实现链式调用
    • 操作符重载
  3. 注意事项

    • 不要返回局部变量的引用
    • 使用常量引用来传递不需修改的大型对象
    • 引用比指针更安全,但灵活性较低

熟练掌握引用的使用可以让你的C++代码更加简洁、高效,并能减少很多常见的错误。

练习

  1. 编写一个函数,使用引用参数查找并返回数组中的最大值和最小值。
  2. 实现一个简单的字符串类,使用引用返回操作符重载来支持类似str[i] = 'c'的操作。
  3. 使用常量引用参数编写一个函数,计算一个向量的平均值、中位数和标准差。
  4. 尝试解释以下代码的行为,特别注意引用的绑定:
    cpp
    int x = 10;
    int y = 20;
    int& ref = x;
    ref = y;
    y = 30;
    cout << x << " " << ref << " " << y << endl;
进一步学习

要深入理解C++引用,建议研究右值引用和移动语义,这是C++11引入的高级特性,可以显著提高代码性能,特别是在处理大型对象时。