1. 小型数据(如基本类型)优先传值

对于intchardoublebool等基本类型,以及std::pairstd::tuple等小型聚合类型,传值(pass-by-value)是最自然的选择
原因:这些类型的复制成本极低(通常 1~8 字节),传值不会造成性能负担,且语义清晰(函数内修改参数不会影响外部)。

// 推荐:基本类型传值
void printInt(int num) {  // 复制int成本低,语义清晰
    cout << num << endl;
}

// 不推荐:对int用引用,画蛇添足
void printInt(const int& num) {  // 没必要,引用的底层实现和指针类似,反而可能更复杂
    cout << num << endl;
}

2. 大型对象(如自定义类、容器)优先传const引用

  对于std::stringstd::vector、自定义的LargeClass等占用内存较大的类型,const T&(常量引用) 是最优选择:

  • 避免对象的拷贝(节省内存和时间,尤其对容器或深拷贝类);
  • const保证函数内不会修改原对象(输入型参数的 “只读” 语义)。
// 推荐:大型对象传const引用
void printVector(const vector<int>& vec) {  // 避免vector的拷贝,且保证不修改原容器
    for (int x : vec) cout << x << " ";
}

// 不推荐:传值会导致整个vector拷贝,效率低
void printVector(vector<int> vec) {  // 错误!对大容器来说拷贝成本极高
    // ...
}

3. 需要修改外部变量时,用非const引用(输出型参数)

如果函数需要修改外部变量(输出参数),优先用T&(非 const 引用),而非指针:

  • 引用必须绑定到有效对象(避免空指针问题,更安全);
  • 语法更简洁(无需解引用*,可读性更好)。
// 推荐:用非const引用修改外部变量
void addOne(int& num) {  // 直接修改外部num
    num += 1;
}

// 不推荐:用指针(除非必要,如允许nullptr)
void addOne(int* num) {  // 需要检查nullptr,否则可能崩溃
    if (num) *num += 1;
}

4. 指针的使用场景:允许空值或需要重新指向

指针(T*const T*)的核心优势是可以为nullptr,或可以在函数内修改指针本身的指向。常见场景:

  • 表示 “可选参数”(允许调用时传nullptr);
  • 需要在函数内修改指针的指向(如链表的节点重定向)。

// 示例1:可选参数(允许nullptr)
void logMessage(const char* msg) {  // 传nullptr表示“无消息”
    if (msg) cout << "Log: " << msg << endl;
}

// 示例2:修改指针指向(链表操作)
void resetNode(Node*& ptr) {  // 引用指针,允许修改指针本身
    ptr = new Node(0);  // 让外部指针指向新节点
}

注意:指针需要手动检查nullptr,否则可能导致崩溃,因此非必要时优先用引用。

5. 输入 + 输出参数:用非const引用,且放在参数列表末尾

如果一个参数既是输入(函数读取其值)又是输出(函数修改其值),用T&,且按 “输入参数在前,输出参数在后” 的顺序排列,符合阅读习惯。

// 推荐:输入在前,输出在后
bool calculateSumAndProduct(int a, int b, int& sum, int& product) {
    sum = a + b;
    product = a * b;
    return true;  // 假设返回计算是否成功
}

6. 避免传递 “引用的引用” 或 “指针的引用”(除非特殊需求)

  T&&(右值引用)是 C++11 引入的,用于移动语义(转移资源所有权),但普通场景下应避免复杂的引用嵌套(如const T*&T&&&):

  • 这类写法语义晦涩,可读性差;
  • 大多数场景下,简单的const T&T*已经足够。

比如 中序遍历场景,const Node* root(传值)完全足够,const Node*& 属于过度复杂。

  (我就是这里受到启发!!!)

7. 对 “禁止修改” 的参数,务必加const

只要参数是 “输入型”(仅读取,不修改),无论传值、传引用还是传指针,都应加上const

  • 编译器会帮你检查误修改(如const int& x不能做x++);
  • 明确告诉其他开发者:“这个参数是只读的”(自文档化)。

// 推荐:输入参数加const
void printString(const string& s) {  // 明确s是只读的
    // s += "x";  // 编译错误,避免误修改
    cout << s << endl;
}

8. 容器迭代器传参:优先传值

迭代器本质是 “指针的封装”,体积通常和指针一样小(4~8 字节),传值比传引用更高效且语义清晰

// 推荐:迭代器传值
void printRange(vector<int>::iterator begin, vector<int>::iterator end) {
    for (auto it = begin; it != end; ++it) {
        cout << *it << " ";
    }
}

总结:传参的核心原则

  1. 效率:小对象传值,大对象传const引用;
  2. 安全:能用引用就不用指针(避免空指针),必要时用const保护;
  3. 清晰:参数语义明确(输入 / 输出),避免过度复杂的类型(如const T*&)。

遵循这些习惯,能让你的代码更高效、更易维护,也能减少很多因传参导致的 bug。

补充: 关于const 指针引用的问题---困扰我的问题

   

int a = 1;
	int* pa = &a;
	//const int*& rpa = pa; // 不允许
//	const int*& const crpa = pa;// 这也是不被允许的
const int* const& crpa = pa; // 这被允许了!!!!!!
   int* const& cpa = pa; // 这被允许了!!!!!!

   是不是很震惊,我也是超级震惊的很,其实我的思路来源是这样的,我正在写二叉树的中序遍历,Inorder(const Node*& root)  我传递一个Node* _root的参数但是无情的编译器不允许,我的想法是这是一个输入型参数啊只读啊,那我把它设置为const 引用好了,我才不要传值。 

   我思考良久,查阅资料,抓耳挠腮非const为啥不能传递给const啊!!!!!

 几乎疯掉了---- 当我没说, 

  直入主题:

   这跟引用绑定跟传值绑定有关,倘若我们设置的指针是const类型的指针,本质上呢传参的指针跟我的实参是两个不同类型的指针,只不过指向了相同的地址值。 

   理解上述代码中   //const int*& rpa = pa; // 不允许

    pa是一个int*类型的指针,  rpa是一个指向const int* 的指针注意这里的const的理解,超出我原脑子了, (const修饰的情况,优先修饰的是左对象,如果左边没有就修饰右侧对象) 所以const int* const 修饰的是 int这个家伙,意思是 const int* 这个指针 指向了一块const int类型的内存,该内存只读,但是你的这个指针不是const的 所以指针可以修改指向的!

   其次就是我们再谈 rpa引用是绑定const int*的引用,但是pa类型是int*类型的是,所以本质上是要创建一个临时对象 让pa->转换为 const int*的临时对象。 这是一个右值。

    文献:

  • rpa 是 “引用非 const 类型”(非 const 指针),而 C++ 不允许 “非 const 引用” 绑定到临时对象(担心引用可能修改已销毁的临时对象)

简单来说就是: T& 不能绑定一个右值  const T&能绑定一个右值 比如

string &str = string()  这是不被允许的  所以你可以理解为 现在const int* 引用它不是const引用,不能指向临时变量。

接下来你可以思考下面几个情况咯: 试试吧

//    const int*& const crpa = pa;// 这也是不被允许的
const int* const& crpa = pa; // 这被允许了!!!!!!
   int* const& cpa = pa; // 这被允许了!!!!!!

更多推荐