C/C++编写规范之-----传参
1. 小型数据(如基本类型)优先传值
对于int、char、double、bool等基本类型,以及std::pair、std::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::string、std::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 << " ";
}
}
总结:传参的核心原则
- 效率:小对象传值,大对象传
const引用; - 安全:能用引用就不用指针(避免空指针),必要时用
const保护; - 清晰:参数语义明确(输入 / 输出),避免过度复杂的类型(如
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; // 这被允许了!!!!!!
更多推荐
所有评论(0)