如果你写过C++代码,一定遇到过这样的困惑:为什么有时候修改函数参数里的对象,外面的对象也跟着变了?为什么有时候明明传了一个很大的对象,程序性能却没什么影响?为什么面试官总爱问“值传递、引用传递、指针传递的区别”?
这些问题背后,其实隐藏着C++对象传递机制的核心秘密。很多开发者只是机械地记住了“传值会拷贝,传引用不会”,却不知道在实际项目中,这个简单的选择会直接影响程序的性能、内存安全和代码可维护性。
今天我们就来彻底拆解C++中向函数传递对象的四种方式:值传递、引用传递、指针传递,以及常被忽视的右值引用传递。我会用一个完整的Student类作为示例,带你从内存层面理解每种方式的底层机制,分析它们的性能差异,并给出实际项目中的选择建议。
读完本文,你将能够:
- 清晰理解四种传递方式的底层原理和内存行为
- 掌握在什么场景下应该选择哪种传递方式
- 避免常见的对象传递陷阱和性能问题
- 写出更高效、更安全的C++代码
1. 为什么对象传递方式如此重要?
在C++中,对象传递不仅仅是语法选择问题,它直接关系到程序的三个核心方面:
性能影响:一个包含大量数据的对象(比如包含字符串、向量、动态数组)如果被不必要地拷贝,可能会消耗大量CPU时间和内存。在性能敏感的应用中,这种开销可能是不可接受的。
内存安全:错误的传递方式可能导致悬空指针、内存泄漏或意外的数据修改。特别是在多线程环境下,对象传递方式的选择会影响数据竞争和线程安全。
代码语义:不同的传递方式表达了不同的设计意图。传值意味着“我需要一份独立的副本”,传引用意味着“我要修改原始对象”,传const引用意味着“我只读取不修改”。
让我们先看一个简单的例子,感受一下不同传递方式的差异:
#include <iostream> #include <string> #include <vector> class Student { private: std::string name; int age; std::vector<int> scores; // 可能包含大量数据 public: Student(const std::string& n, int a) : name(n), age(a) { scores.reserve(100); // 预分配空间 for (int i = 0; i < 100; i++) { scores.push_back(i); } } // 拷贝构造函数 Student(const Student& other) { std::cout << "拷贝构造函数被调用!" << std::endl; name = other.name; age = other.age; scores = other.scores; // 这里会发生深拷贝! } void display() const { std::cout << "姓名: " << name << ", 年龄: " << age << ", 分数数量: " << scores.size() << std::endl; } void setName(const std::string& newName) { name = newName; } const std::string& getName() const { return name; } };这个Student类包含一个可能很大的vector,当我们传递这个对象时,选择不同的传递方式会产生完全不同的效果。
2. 四种传递方式的底层原理
2.1 值传递(Pass by Value)
值传递是最直观的方式,但也是性能陷阱最多的地方。
// 值传递示例 void processStudentByValue(Student student) { student.setName("ModifiedName"); student.display(); } int main() { Student s1("张三", 20); std::cout << "调用前: "; s1.display(); processStudentByValue(s1); std::cout << "调用后: "; s1.display(); // s1的name没有被修改! return 0; }内存行为分析:
- 当调用
processStudentByValue(s1)时,会调用Student的拷贝构造函数 - 在栈上创建一个新的
Student对象(副本) - 原对象
s1的所有数据被深拷贝到新对象 - 函数内部修改的是副本,不影响原对象
- 函数结束时,副本被销毁,调用析构函数
关键特点:
- ✅ 安全:函数内的修改不会影响原对象
- ✅ 简单:不需要担心原对象被意外修改
- ❌ 性能差:如果对象很大,拷贝开销显著
- ❌ 可能不必要:如果函数不需要修改对象,拷贝是浪费的
2.2 引用传递(Pass by Reference)
引用传递避免了拷贝,但需要特别注意对象的生命周期。
// 引用传递示例 void processStudentByRef(Student& student) { student.setName("ModifiedName"); student.display(); } int main() { Student s1("张三", 20); std::cout << "调用前: "; s1.display(); processStudentByRef(s1); std::cout << "调用后: "; s1.display(); // s1的name被修改了! return 0; }内存行为分析:
- 引用本质上是原对象的别名(编译器通常实现为指针)
- 不创建新对象,不调用拷贝构造函数
- 函数内对引用的操作直接作用于原对象
- 没有额外的内存分配和释放
关键特点:
- ✅ 高效:没有拷贝开销
- ✅ 可以修改原对象
- ⚠️ 危险:函数可能意外修改原对象
- ⚠️ 需要确保原对象在函数调用期间有效
2.3 const引用传递(Pass by const Reference)
这是C++中最常用的对象传递方式,兼顾了效率和安全性。
// const引用传递示例 void displayStudent(const Student& student) { // student.setName("NewName"); // 错误!不能修改const引用 student.display(); // 只能调用const成员函数 } void processStudent(const Student& student) { // 读取student的数据,但不修改 std::cout << "处理学生: " << student.getName() << std::endl; // 这里可以安全地传递student给其他需要const引用的函数 } int main() { Student s1("张三", 20); displayStudent(s1); // 安全,不会拷贝 processStudent(s1); // 安全,不会拷贝 // 甚至可以传递临时对象 displayStudent(Student("李四", 21)); // 创建临时对象,也不会拷贝 return 0; }内存行为分析:
- 和普通引用一样,不创建副本
- 编译器保证不能通过const引用修改对象
- 可以绑定到临时对象(右值)
- 是最佳的"只读"参数传递方式
关键特点:
- ✅ 高效:没有拷贝开销
- ✅ 安全:编译器防止意外修改
- ✅ 灵活:可以接受左值和右值
- ⚠️ 只能调用对象的const成员函数
2.4 指针传递(Pass by Pointer)
指针传递在C语言中常见,在C++中通常有更好的替代方案。
// 指针传递示例 void processStudentByPtr(Student* student) { if (student != nullptr) { // 必须检查空指针! student->setName("ModifiedName"); student->display(); } } void processStudentByPtr2(Student* student) { // 危险!没有检查空指针 student->setName("ModifiedName"); // 如果student是nullptr,程序崩溃 } int main() { Student s1("张三", 20); Student* s2 = new Student("李四", 21); processStudentByPtr(&s1); // 传递地址 processStudentByPtr(s2); // 传递指针 std::cout << "s1: "; s1.display(); // 被修改了 // 必须手动释放内存 delete s2; s2 = nullptr; // 危险调用 // processStudentByPtr(nullptr); // 会触发空指针检查 // processStudentByPtr2(nullptr); // 直接崩溃! return 0; }内存行为分析:
- 传递的是对象地址(通常是4或8字节)
- 需要手动检查空指针
- 语法稍显繁琐(
->操作符) - 可以传递
nullptr表示"没有对象"
关键特点:
- ✅ 可以表示"可选"参数(通过
nullptr) - ✅ 明确表示可能修改原对象
- ❌ 必须检查空指针,否则不安全
- ❌ 语法不如引用简洁
- ❌ 需要手动管理内存(如果是堆对象)
3. 右值引用和移动语义(C++11及以上)
这是现代C++中最重要的特性之一,用于优化临时对象的传递。
class Student { // ... 其他成员同上 ... public: // 移动构造函数 Student(Student&& other) noexcept { std::cout << "移动构造函数被调用!" << std::endl; name = std::move(other.name); // 移动而非拷贝 age = other.age; scores = std::move(other.scores); // 移动而非深拷贝 // 将源对象置于有效但未定义的状态 other.age = 0; } // 移动赋值运算符 Student& operator=(Student&& other) noexcept { std::cout << "移动赋值运算符被调用!" << std::endl; if (this != &other) { name = std::move(other.name); age = other.age; scores = std::move(other.scores); other.age = 0; } return *this; } }; // 接受右值引用的函数 Student createStudent() { return Student("临时学生", 22); // 返回临时对象 } void processStudentRvalueRef(Student&& student) { std::cout << "处理右值引用: "; student.display(); // 可以安全地"窃取"student的资源,因为它即将被销毁 } int main() { // 场景1:函数返回临时对象 Student s1 = createStudent(); // 可能调用移动构造函数 // 场景2:显式传递右值 processStudentRvalueRef(Student("王五", 23)); // 场景3:使用std::move将左值转为右值 Student s2("赵六", 24); processStudentRvalueRef(std::move(s2)); // s2之后不应再使用 return 0; }移动语义的核心思想:
- 识别出那些"即将销毁"的对象(右值)
- "窃取"它们的资源而不是深拷贝
- 将源对象置于有效但可析构的状态
- 避免不必要的内存分配和数据复制
4. 完整示例:四种传递方式的对比测试
让我们通过一个完整的程序来直观感受不同传递方式的差异:
#include <iostream> #include <string> #include <vector> #include <chrono> class DataHolder { private: std::vector<int> data; int id; public: DataHolder(int size, int i) : id(i) { data.reserve(size); for (int i = 0; i < size; i++) { data.push_back(i * i); } std::cout << "DataHolder " << id << " 构造函数,数据大小: " << data.size() << std::endl; } // 拷贝构造函数 DataHolder(const DataHolder& other) : id(other.id + 1000) { data = other.data; // 深拷贝! std::cout << "DataHolder " << id << " 拷贝构造函数被调用" << std::endl; } // 移动构造函数 DataHolder(DataHolder&& other) noexcept : id(other.id + 2000) { data = std::move(other.data); // 移动! std::cout << "DataHolder " << id << " 移动构造函数被调用" << std::endl; } ~DataHolder() { std::cout << "DataHolder " << id << " 析构函数" << std::endl; } void process() { // 模拟一些处理 int sum = 0; for (int val : data) { sum += val; } } }; // 1. 值传递 void processByValue(DataHolder dh) { dh.process(); } // 2. 引用传递 void processByRef(DataHolder& dh) { dh.process(); } // 3. const引用传递 void processByConstRef(const DataHolder& dh) { // dh.process(); // 错误!不能调用非const成员函数 // 但可以读取数据 } // 4. 指针传递 void processByPtr(DataHolder* dh) { if (dh) { dh->process(); } } int main() { const int DATA_SIZE = 1000000; // 100万个整数 std::cout << "=== 测试值传递 ===" << std::endl; { DataHolder dh1(DATA_SIZE, 1); auto start = std::chrono::high_resolution_clock::now(); processByValue(dh1); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::cout << "值传递耗时: " << duration.count() << " 微秒" << std::endl; } std::cout << "\n=== 测试引用传递 ===" << std::endl; { DataHolder dh2(DATA_SIZE, 2); auto start = std::chrono::high_resolution_clock::now(); processByRef(dh2); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::cout << "引用传递耗时: " << duration.count() << " 微秒" << std::endl; } std::cout << "\n=== 测试const引用传递 ===" << std::endl; { DataHolder dh3(DATA_SIZE, 3); auto start = std::chrono::high_resolution_clock::now(); processByConstRef(dh3); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::cout << "const引用传递耗时: " << duration.count() << " 微秒" << std::endl; } std::cout << "\n=== 测试指针传递 ===" << std::endl; { DataHolder dh4(DATA_SIZE, 4); auto start = std::chrono::high_resolution_clock::now(); processByPtr(&dh4); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::cout << "指针传递耗时: " << duration.count() << " 微秒" << std::endl; } std::cout << "\n=== 测试移动语义 ===" << std::endl; { DataHolder dh5(DATA_SIZE, 5); auto start = std::chrono::high_resolution_clock::now(); DataHolder dh6 = std::move(dh5); // 移动构造 auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::cout << "移动构造耗时: " << duration.count() << " 微秒" << std::endl; // 注意:dh5现在处于有效但未定义的状态,不应再使用 } return 0; }运行这个程序,你会清楚地看到:
- 值传递会触发拷贝构造函数,对于大对象性能极差
- 引用传递和指针传递几乎没有开销
- const引用传递是最安全的只读访问方式
- 移动语义可以显著提升临时对象处理的性能
5. 实际项目中的选择策略
5.1 基本原则:优先使用const引用
在大多数情况下,const引用是最佳选择:
// 好:const引用传递 void printStudentInfo(const Student& student); void calculateAverageScore(const Student& student); void validateStudentData(const Student& student); // 不好:值传递(除非确实需要副本) void printStudentInfo(Student student); // 不必要的拷贝 // 不好:非const引用(除非确实需要修改) void printStudentInfo(Student& student); // 可能被误修改5.2 需要修改原对象时:使用非const引用
// 明确表示要修改对象 void updateStudentScore(Student& student, int newScore); void promoteStudent(Student& student); void transferStudent(Student& from, Student& to);5.3 需要表示"可选"参数时:使用指针
// 使用指针表示参数是可选的 bool enrollStudent(Student* student, const Course& course) { if (!student) { // 处理没有学生的情况 return false; } // 注册学生到课程 return true; } // 或者使用std::optional(C++17) #include <optional> bool enrollStudent(std::optional<Student&> student, const Course& course) { if (!student.has_value()) { return false; } // 使用student.value() return true; }5.4 需要转移所有权时:使用右值引用
class StudentManager { private: std::vector<Student> students; public: // 添加学生,可能从临时对象移动 void addStudent(Student&& student) { students.push_back(std::move(student)); } // 或者使用值传递+移动(更通用) void addStudent(Student student) { students.push_back(std::move(student)); // 移动而非拷贝 } }; // 使用示例 StudentManager manager; manager.addStudent(Student("张三", 20)); // 临时对象,触发移动 Student s("李四", 21); manager.addStudent(std::move(s)); // 显式移动5.5 小型对象和内置类型:值传递可能更好
对于小型对象(如int、double、Point2D等),值传递可能更高效:
// 对于小型结构体,值传递可能更好 struct Point2D { double x, y; }; // 值传递:适合小型对象 Point2D rotatePoint(Point2D p, double angle) { // 创建副本进行计算 double newX = p.x * cos(angle) - p.y * sin(angle); double newY = p.x * sin(angle) + p.y * cos(angle); return {newX, newY}; } // 引用传递:如果对象很小,可能不如值传递高效 Point2D rotatePointRef(const Point2D& p, double angle) { // 需要间接访问,可能不如直接操作栈上的副本快 double newX = p.x * cos(angle) - p.y * sin(angle); double newY = p.x * sin(angle) + p.y * cos(angle); return {newX, newY}; }6. 高级话题:完美转发和通用引用
在模板编程中,C++11引入了完美转发(Perfect Forwarding)的概念:
#include <utility> // 通用引用模板 template<typename T> void processAndForward(T&& param) { // std::forward保持值类别(左值/右值) someOtherFunction(std::forward<T>(param)); } // 实际应用:工厂函数 template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); } // 使用示例 class Student { public: Student(std::string name, int age) : name(std::move(name)), age(age) {} private: std::string name; int age; }; int main() { // 完美转发参数给构造函数 auto student = make_unique<Student>("张三", 20); std::string name = "李四"; auto student2 = make_unique<Student>(name, 21); // 传递左值 auto student3 = make_unique<Student>(std::move(name), 22); // 传递右值 return 0; }完美转发允许我们编写接受任意类型参数并保持其值类别的函数模板,这是现代C++泛型编程的重要特性。
7. 常见问题与解决方案
7.1 问题:对象切片(Object Slicing)
当通过值传递派生类对象到接受基类参数的函数时,会发生对象切片:
class Base { public: virtual void print() const { std::cout << "Base" << std::endl; } }; class Derived : public Base { public: void print() const override { std::cout << "Derived" << std::endl; } }; // 值传递:会发生切片! void printByValue(Base b) { b.print(); // 总是调用Base::print() } // 引用传递:保持多态性 void printByRef(const Base& b) { b.print(); // 根据实际类型调用 } int main() { Derived d; printByValue(d); // 输出: Base(切片了!) printByRef(d); // 输出: Derived(正确) return 0; }解决方案:对于多态对象,总是使用引用或指针传递。
7.2 问题:悬空引用(Dangling References)
// 危险:返回局部对象的引用 const std::string& getInvalidReference() { std::string local = "局部变量"; return local; // 错误!local在函数结束时被销毁 } // 安全:返回参数中对象的引用 const std::string& getName(const Student& student) { return student.getName(); // 安全,student在调用者作用域中 } // 安全:返回静态对象的引用 const std::string& getGlobalString() { static std::string global = "全局字符串"; return global; // 安全,静态对象生命周期是整个程序 }解决方案:确保引用指向的对象在引用被使用时仍然有效。
7.3 问题:不必要的拷贝
// 不好的写法:多次不必要的拷贝 void processStudents(std::vector<Student> students) { // 第一次拷贝 for (Student s : students) { // 第二次拷贝(每次迭代) s.process(); } } // 好的写法:避免不必要的拷贝 void processStudents(const std::vector<Student>& students) { // 无拷贝 for (const Student& s : students) { // 无拷贝 s.process(); } } // 如果需要修改副本 void processStudents(std::vector<Student> students) { // 一次拷贝 for (Student& s : students) { // 引用,无拷贝 s.modify(); } }8. 性能优化最佳实践
8.1 使用移动语义优化返回值
// 传统方式:可能产生拷贝 std::vector<int> getNumbers() { std::vector<int> numbers; // ... 填充数据 ... return numbers; // C++11前可能拷贝,C++11后可能RVO/NRVO } // 使用移动语义明确优化 std::vector<int> getNumbersOptimized() { std::vector<int> numbers; // ... 填充数据 ... return std::move(numbers); // 明确移动 } // 接受右值引用参数 void mergeVectors(std::vector<int>&& vec1, std::vector<int>& vec2) { // 可以安全地"窃取"vec1的资源 vec2.insert(vec2.end(), std::make_move_iterator(vec1.begin()), std::make_move_iterator(vec1.end())); }8.2 小对象传值,大对象传引用
经验法则:
- 小于等于指针大小的对象(通常8-16字节)考虑值传递
- 大于指针大小的对象使用const引用传递
- 需要修改时使用非const引用
// 小对象:值传递 double calculateDistance(Point2D p1, Point2D p2); // 大对象:const引用传递 double calculateAverage(const std::vector<double>& values); // 需要修改:非const引用 void normalizeVector(std::vector<double>& values);8.3 使用std::string_view避免字符串拷贝(C++17)
#include <string_view> // 传统方式:可能产生字符串拷贝 void processString(const std::string& str) { // 如果传递C字符串,会构造std::string } // 现代方式:使用string_view避免拷贝 void processStringView(std::string_view str_view) { // 不拷贝,只是视图 // 可以接受std::string、C字符串、子字符串等 } int main() { std::string s = "Hello World"; const char* cs = "C String"; processStringView(s); // 无拷贝 processStringView(cs); // 无拷贝 processStringView("Literal"); // 无拷贝 processStringView(s.substr(0, 5)); // 无拷贝 return 0; }9. 面试常见问题解析
9.1 值传递 vs 引用传递的区别
| 特性 | 值传递 | 引用传递 |
|---|---|---|
| 拷贝行为 | 创建完整副本 | 不创建副本 |
| 内存开销 | 可能很大(如果对象大) | 很小(通常指针大小) |
| 修改原对象 | 不能 | 可以 |
| 安全性 | 高(隔离修改) | 低(可能意外修改) |
| 适用场景 | 小型对象、需要副本时 | 大型对象、需要修改原对象时 |
9.2 什么情况下必须使用const引用?
- 函数不需要修改参数:这是最主要的使用场景
- 参数可能是临时对象:const引用可以绑定到右值
- 避免对象切片:对于多态基类参数
- 接口设计:明确表示"只读"访问
9.3 指针和引用的主要区别?
- 语法:指针使用
*和->,引用使用. - 可空性:指针可以为
nullptr,引用必须绑定到有效对象 - 重绑定:指针可以指向不同对象,引用不能重新绑定
- 内存级别:指针是显式的内存地址,引用是编译器实现的别名
9.4 移动语义解决了什么问题?
移动语义主要解决了两个问题:
- 临时对象优化:避免临时对象的深拷贝
- 资源转移:明确转移对象资源的所有权
关键函数:
- 移动构造函数:
T(T&& other) - 移动赋值运算符:
T& operator=(T&& other) std::move():将左值转为右值引用
10. 实际项目代码示例
让我们看一个完整的实际项目示例,展示如何在实际代码中应用这些原则:
// Student.h #ifndef STUDENT_H #define STUDENT_H #include <string> #include <vector> #include <memory> class Course; class Student { private: std::string name; int age; std::vector<std::shared_ptr<Course>> courses; public: // 构造函数 Student(std::string name, int age); // 拷贝构造函数(深拷贝) Student(const Student& other); // 移动构造函数 Student(Student&& other) noexcept; // 赋值运算符 Student& operator=(const Student& other); Student& operator=(Student&& other) noexcept; // 访问器 const std::string& getName() const { return name; } int getAge() const { return age; } // 修改器 void setName(const std::string& newName) { name = newName; } void setAge(int newAge) { age = newAge; } // 课程管理 void enrollCourse(const std::shared_ptr<Course>& course); void dropCourse(const std::shared_ptr<Course>& course); // 显示信息 void displayInfo() const; // 计算平均分 double calculateAverageScore() const; }; // 工具函数 namespace StudentUtils { // 按年龄过滤学生(const引用,不修改) std::vector<Student> filterByAge(const std::vector<Student>& students, int minAge); // 批量更新学生信息(非const引用,需要修改) void updateAllAges(std::vector<Student>& students, int ageIncrement); // 查找学生(返回指针,可能为空) Student* findStudentByName(std::vector<Student>& students, const std::string& name); // 创建学生(返回右值,适合移动) Student createStudent(const std::string& name, int age); } #endif // STUDENT_H// Student.cpp #include "Student.h" #include "Course.h" #include <algorithm> #include <iostream> // 构造函数 Student::Student(std::string name, int age) : name(std::move(name)), age(age) { std::cout << "构造学生: " << this->name << std::endl; } // 拷贝构造函数 Student::Student(const Student& other) : name(other.name), age(other.age) { // 深拷贝课程列表 courses.reserve(other.courses.size()); for (const auto& course : other.courses) { courses.push_back(std::make_shared<Course>(*course)); } std::cout << "拷贝学生: " << name << std::endl; } // 移动构造函数 Student::Student(Student&& other) noexcept : name(std::move(other.name)), age(other.age), courses(std::move(other.courses)) { other.age = 0; std::cout << "移动学生: " << name << std::endl; } // 拷贝赋值运算符 Student& Student::operator=(const Student& other) { if (this != &other) { name = other.name; age = other.age; // 深拷贝课程 courses.clear(); courses.reserve(other.courses.size()); for (const auto& course : other.courses) { courses.push_back(std::make_shared<Course>(*course)); } } return *this; } // 移动赋值运算符 Student& Student::operator=(Student&& other) noexcept { if (this != &other) { name = std::move(other.name); age = other.age; courses = std::move(other.courses); other.age = 0; } return *this; } // 工具函数实现 std::vector<Student> StudentUtils::filterByAge( const std::vector<Student>& students, int minAge) { std::vector<Student> result; for (const Student& student : students) { // const引用,无拷贝 if (student.getAge() >= minAge) { result.push_back(student); // 这里会调用拷贝构造函数 } } return result; // 可能触发RVO } void StudentUtils::updateAllAges( std::vector<Student>& students, int ageIncrement) { for (Student& student : students) { // 非const引用,可以修改 student.setAge(student.getAge() + ageIncrement); } } Student* StudentUtils::findStudentByName( std::vector<Student>& students, const std::string& name) { for (Student& student : students) { if (student.getName() == name) { return &student; // 返回指针 } } return nullptr; // 没找到 } Student StudentUtils::createStudent(const std::string& name, int age) { return Student(name, age); // 返回值优化 }这个示例展示了在实际项目中如何:
- 根据不同的需求选择合适的传递方式
- 实现拷贝和移动语义
- 设计清晰的接口
- 管理资源生命周期
选择正确的对象传递方式不是教条,而是基于对程序需求、性能要求和代码安全的综合考虑。对于C++开发者来说,理解这些传递方式的底层机制,能够根据具体场景做出明智选择,是写出高质量代码的关键。
记住这个简单的决策流程:
- 函数是否需要修改参数? → 是:非const引用;否:进入步骤2
- 参数是否很小(<= 16字节)? → 是:考虑值传递;否:const引用
- 参数是否可能是临时对象? → 是:考虑右值引用重载
- 参数是否可选? → 是:指针或
std::optional
通过本文的详细分析和示例,你应该已经掌握了C++对象传递的所有关键知识。在实际编码时,多思考、多测试,逐渐培养出对对象传递方式的直觉判断能力。