前言:在C++函数编写过程中,你是否曾经考虑过函数参数应该用引用好还是指针好呢?这两种参数传递有和区别呢?该如何正确选择?
目录
一、区别对比
二、何时用引用
三、何时用指针
一、区别对比
首先,我们先分析一下引用与指针之间的主要区别。
| 特性 | 引用 (Reference,&) | 指针 (Pointer,*) |
|---|---|---|
| 本质 | 是一个别名 (Alias)。它就是变量本身,只是换了个名字。 | 是一个地址 (Address)。它存储了另一个变量的内存地址。 |
| 可空性 | 不能为空。必须在声明时初始化,且不能再指向其他变量。 | 可以为空(nullptr)。表示它不指向任何有效的对象。 |
| 操作语法 | 可以像普通变量一样直接使用。如ref.member | 需要解引用 (*或->) 访问其指向的对象。如ptr->member |
| 安全性 | 更安全。因为它保证有效(不为空),所以不需要进行空检查。 | 风险更高。必须在使用前检查是否为nullptr,否则可能导致程序崩溃。 |
| 内存占用 | 不占用内存空间(与原变量共享空间) | 占用4字节或8字节空间 |
| 用途语义 | 需要一个实际的对象,并且可能会修改它,或者只是想避免拷贝。 | 可能会给一个对象(也可能不给),不一定。 |
下图,我们从内存占用、语法复杂度、安全性及灵活性四个维度进行评估:
从以上分析不难看出,总体来说,引用是首选,指针是次选。
二、何时用引用
引用传递因其简洁和安全的特性,在C++中被广泛作为传递大型对象或需要修改外部变量的首选方式。
以下情况应优先考虑使用引用:
1) 大型对象,避免拷贝
当你需要传递大型对象(包含大量数据的类或结构体)时,使用引用传递可以避免调用拷贝构造函数,从而显著减少性能开销。
例如,当传递一个std::string或std::vector时,使用引用可以避免其内部数据的深拷贝。同时,在引用参数前加上const关键字,表明函数不会修改该参数,这样既能避免拷贝又能保证数据的安全性。
例如:
// 低效方式:每次调用都会拷贝整个字符串 void printStringByValue(std::string s) { std::cout << s << std::endl; } // 高效方式:传递引用,没有拷贝发生 void printStringByReference(const std::string& s) { // s = "oops"; // 编译错误!因为 s 是 const std::cout << s << std::endl; }2) 需要修改传入的参数
如果函数的目的是修改调用者传入的变量,那么引用是理想的选择。引用参数充当实参的别名,函数内部对引用参数的任何修改都会直接作用于原变量,非常方便。
例如:
void swap(int& a, int& b) { int temp = a; a = b; b = temp; } int main() { int x = 5, y = 10; swap(x, y); // x 现在是 10, y 现在是 5 }这里为什么不用指针?这里swap函数必须接收两个有效的int,不存在"可选"的情况发生。
三、何时用指针
虽然引用在多数情况下更为安全和方便,但在某些特定场景下,指针仍然是不可或缺的。当需要表达"可能为空"或"需要重新绑定"的语义时,指针会是更合适的选择。
以下情况应考虑使用指针:
1) 参数是可选的(可能为空)
如果函数参数可能没有值(参数是可选的),或者需要将"无值"作为一个特殊状态传递,那么使用指针比引用更直观。指针可以为空(nullptr),这明确表示该参数未被提供。
例如:
// 假设一个函数,它会查找用户,如果找到了,就通过输出参数返回用户信息 // 如果没找到,就不会修改输出参数。 // 使用指针作为可选的输出参数 bool findUser(int userId, User* outUser) { // outUser 是可选的 // ... 查找逻辑 ... if (user_found) { if (outUser != nullptr) { // 必须检查指针是否为空! *outUser = found_user_data; } return true; } return false; } int main() { User u; // 我需要用户信息 if (findUser(101, &u)) { // 这里使用 u } // 我只关心用户是否存在,不需要具体信息 if (findUser(102, nullptr)) { std::cout << "User 102 exists." << std::endl; } }【注:现代 C++ 替代方案:对于可选参数,C++17 引入了std::optional,它在类型层面就表达了“可选”的语义,比指针更安全。】
2) 需要重新绑定目标
如果函数内部需要改变指针本身的指向(例如 让指针指向另一个对象,或者让指针变为空),那么必须使用指针作为参数。(引用一旦绑定就无法更改指向)
例如:
void allocateInt(int** ptr) { *ptr = new int(42); // 让传入的指针指向新分配的内存 }3) 需要与C库进行交互
C 语言没有引用,所有通过地址传递的参数都使用指针。当你需要调用一个 C 函数或者一个遵循 C 风格的 C++ 库时,你必须使用指针。
例如:
// C 语言库中的一个函数 void process_data(const double* data, int size); // 在 C++ 中调用它 std::vector<double> my_data = {1.0, 2.0, 3.0}; // vector::data() 返回一个指向其内部数组的指针 // process_data(my_data.data(), my_data.size());