C++ 中函数参数传递是一个非常核心但也容易忽视的知识点,合理使用可以显著提高程序的性能和安全性。下面我会给出详尽的讲解、注意事项和丰富的示例。
C++ 参数传递方式详解
方式 |
语法 |
是否复制 |
可修改原对象 |
典型用途 |
值传递 |
void foo(Type x) |
✅ 是 |
❌ 否 |
小型对象、基本类型 |
指针传递 |
void foo(Type* x) |
❌ 否 |
✅ 是(非 const) |
允许修改;可空 |
引用传递 |
void foo(Type& x) |
❌ 否 |
✅ 是(非 const) |
修改原对象,传大型对象 |
const 引用传递 |
void foo(const Type& x) |
❌ 否 |
❌ 否 |
高效读取大型对象 |
右值引用传递 |
void foo(Type&& x) |
❌ 否 |
✅ 是 |
接受临时对象、支持移动语义 |
各种方式适用场景和注意事项
1. 值传递(by value)
void increment(int x) {
x += 1;
}
- 安全,函数内部修改不会影响调用者
- 对大型对象(如 std::string、std::vector)会有复制开销
2. 指针传递(by pointer)
void setZero(int* ptr) {
if (ptr) *ptr = 0;
}
3. 引用传递(by reference)
void swap(int& a, int& b) {
int tmp = a; a = b; b = tmp;
}
- 高效(不复制),代码清晰
- 不可传 null,不可看到是否被修改
4. 常量引用传递(by const reference)
void printString(const std::string& s) {
std::cout << s << "\n";
}
- 推荐用于大型对象的只读传参
- 不可修改,但比值传递高效
5. 右值引用传递(by rvalue reference) — C++11+
void takeOwnership(std::string&& s) {
std::cout << "Moved: " << s << "\n";
}
- 实现移动语义,避免拷贝(
std::move
)
- 接收临时变量/转移资源所有权
示例:函数参数传递效果对比
#include
#include
void byValue(std::string s) {
s += " world";
}
void byPointer(std::string* s) {
if (s) *s += " world";
}
void byReference(std::string& s) {
s += " world";
}
void byConstRef(const std::string& s) {
std::cout << "Only viewing: " << s << "\n";
}
void byRValue(std::string&& s) {
std::cout << "Moved: " << s << "\n";
}
int main() {
std::string str = "hello";
byValue(str);
std::cout << "After byValue: " << str << "\n";
byPointer(&str);
std::cout << "After byPointer: " << str << "\n";
byReference(str);
std::cout << "After byReference: " << str << "\n";
byConstRef(str);
byRValue(std::move(str));
std::cout << "After move: " << str << "\n";
return 0;
}
总结:使用建议
用途 |
推荐方式 |
小型类型(int, double) |
值传递 |
只读大型对象(std::vector 等) |
const 引用传递 |
可修改对象 |
引用或指针传递(引用更安全) |
条件修改或可能为 null |
指针传递 |
移动资源所有权 |
右值引用(+ std::move ) |
综合例子
下面通过模拟一个 “字符串处理器”系统,通过交互演示:
- 值传递:函数收到的是副本,外部不变
- 指针传递:修改原数据,需要检查 null
- 引用传递:高效修改原数据
- 常量引用:只读但不复制大型对象
- 右值引用:移动语义,偷资源
string_processor.h
的内容:
#pragma once
#include
class StringProcessor {
public:
static void toUpperCopy(std::string str);
static void toUpperPtr(std::string* str);
static void toUpperRef(std::string& str);
static void printLength(const std::string& str);
static void takeOwnership(std::string&& str);
};
string_processor.cpp
的内容:
#include "string_processor.h"
#include
#include
void StringProcessor::toUpperCopy(std::string str) {
for (char& c : str) c = std::toupper(c);
std::cout << "[toUpperCopy] result (local only): " << str << "\n";
}
void StringProcessor::toUpperPtr(std::string* str) {
if (str) {
for (char& c : *str) c = std::toupper(c);
}
}
void StringProcessor::toUpperRef(std::string& str) {
for (char& c : str) c = std::toupper(c);
}
void StringProcessor::printLength(const std::string& str) {
std::cout << "[printLength] Length: " << str.length() << "\n";
}
void StringProcessor::takeOwnership(std::string&& str) {
std::cout << "[takeOwnership] Moved string: " << str << "\n";
}
main.cpp
主函数使用的内容:
#include "string_processor.h"
#include
#include
void menu() {
std::cout << "\n==== 字符串处理器 - 类型传递演示 ====\n";
std::cout << "1. 值传递 (toUpperCopy)\n";
std::cout << "2. 指针传递 (toUpperPtr)\n";
std::cout << "3. 引用传递 (toUpperRef)\n";
std::cout << "4. 常量引用 (printLength)\n";
std::cout << "5. 右值引用 (takeOwnership)\n";
std::cout << "0. 退出\n";
std::cout << "请选择操作编号: ";
}
int main() {
std::string str;
std::cout << "请输入初始字符串:";
std::getline(std::cin, str);
int choice;
do {
menu();
std::cin >> choice;
std::cin.ignore();
switch (choice) {
case 1:
StringProcessor::toUpperCopy(str);
std::cout << "原始字符串仍是: " << str << "\n";
break;
case 2:
StringProcessor::toUpperPtr(&str);
std::cout << "修改后字符串: " << str << "\n";
break;
case 3:
StringProcessor::toUpperRef(str);
std::cout << "修改后字符串: " << str << "\n";
break;
case 4:
StringProcessor::printLength(str);
break;
case 5:
StringProcessor::takeOwnership(std::move(str));
std::cout << "原始字符串现在是: " << str << "\n";
break;
case 0:
std::cout << "程序结束。\n";
break;
default:
std::cout << "无效选择,请重试。\n";
}
} while (choice != 0);
return 0;
}
示例小结说明
菜单选项 |
类型传递 |
说明 |
1 |
值传递 |
toUpperCopy :不修改原始字符串 |
2 |
指针传递 |
toUpperPtr :通过指针修改 |
3 |
引用传递 |
toUpperRef :直接修改字符串内容 |
4 |
常量引用 |
printLength :只读且高效 |
5 |
右值引用 |
takeOwnership :偷走数据、原字符串置空 |