C++函数参数传递方式详解:string、string&、const string、const string&该怎么选?

写C++函数的时候,一旦参数类型是string这种"重量级"对象,怎么传参就成了一个绕不开的问题。很多人一开始都是"用起来能跑就行",但传参方式其实直接影响程序的性能和安全性。这篇文章把stringstring&const stringconst string&这四种写法掰开揉碎讲清楚。

先看一个例子

csharp 复制代码
void func(string s);          // 值传递

void func(string& s);         // 引用传递

void func(const string s);    // const值传递

void func(const string& s);   // const引用传递

  

int main() {

    string name = "hello";

    func(name);

}

调用func(name)时,这四种写法在底层做的事情完全不一样,接下来一个一个拆开看。

一、string s:值传递,拷贝一份新的

typescript 复制代码
void func(string s) {

    s += " world";  // 修改的是拷贝,不影响外面的name

}//这里的拷贝就是新创建了一个变量赋上s的值,这新的变量就叫拷贝

这种写法在调用时,会重新开辟一块内存空间 ,把传进来的name完整拷贝一份给形参s。函数内部对s的任何修改,都只是改了这个"复制品",函数结束后s被销毁,外面的name完全不受影响。

优点:

  • 安全,函数内部怎么折腾都不会影响外面的变量,不用担心"意外修改了别人的东西"。
  • 语义清晰,调用者一看就知道这个函数不会改自己传进去的变量。

缺点:

  • 性能开销大。string内部管理的是一块堆内存,拷贝意味着要重新申请内存、把字符内容整个复制一遍。如果字符串很长,或者这个函数被频繁调用(比如在循环里调用几万次),这个开销会非常明显。

适用场景:

  • 字符串很短(比如短单词、短标识符),拷贝开销可以忽略。
  • 你确实需要在函数内部得到一份独立的、可以随意修改的副本,且不想影响原变量。
  • 追求接口的"绝对安全",宁可牺牲一点性能。

实际情况: 这种写法在生产代码里其实不算常用,因为大多数场景下没必要付出拷贝的代价,除非你明确需要一份独立副本。

二、string& s:引用传递,直接操作原变量

typescript 复制代码
void func(string& s) {

    s += " world";  // 直接修改了外面的name!

}//直接修改了调用函数时传进去的这个参数

这种写法不会开辟新的空间s只是name的一个"别名",它们指向同一块内存。函数内部对s做任何修改,实际上就是在修改外面的name本身。函数调用结束后,外面的name已经被改变了。

优点:

  • 没有拷贝开销,性能好。
  • 如果你的目的就是要修改传进来的变量(比如一个trim(string& s)去掉字符串首尾空格的函数),引用传递是最直接、最高效的方式。

缺点:

  • 危险。调用者传进去一个变量,函数内部却可能悄悄把它改了,如果调用者不知道这个函数会修改参数,很容易踩坑,导致意料之外的bug。
  • 不能传常量或临时对象。比如func("hello")这种字面量、或者func(a + b)这种临时结果,是不能绑定到非const引用上的,编译器会报错。

适用场景:

  • 函数的目的本来就是要修改 传入的变量,比如swap(string& a, string& b)toUpper(string& s)这类"就地修改"的函数。
  • 需要在函数内部返回多个"输出结果",用引用参数当作输出参数。

实际情况: 常用,但只在"确实需要修改原变量"的场景下用,用的时候最好在函数名或文档里明确说明会修改参数,避免调用者踩坑。

三、const string s:const值传递,拷贝一份不能改的常量

typescript 复制代码
void func(const string s) {

    // s += " world";  // 编译报错!s是const的,不能修改

}

这种写法同样会开辟新的内存空间 ,把传进来的变量拷贝一份,但拷贝出来的这份被标记成了const,也就是说函数内部不能修改这个拷贝。

优点:

  • 既有值传递的安全性(不影响外部变量),又多了一层保护------防止你自己在函数内部不小心改了这个变量(比如写了s = s + "x"却忘了这行代码本来不该出现)。

缺点:

  • 该有的拷贝开销一分不少,性能上跟普通值传递一样差。
  • 而且这个"不能修改"的限制只在函数内部实现中有意义,对调用者毫无影响------调用者根本感知不到形参是不是const的,因为函数声明和定义中的顶层const在函数签名里其实是没有区分作用的(这也是为什么很多编译器/规范建议不要在函数声明里写顶层const形参)。

适用场景:

  • 说实话,这种写法非常不常用 。因为它既没有省掉拷贝的开销,又没有给调用者带来任何额外的接口保证,唯一的作用只是给函数内部实现者提个醒:"这个变量在函数体内不该被改"。
  • 如果确实想要这种"防止自己手滑改错"的保护,通常直接在函数体内部用const局部变量重新声明会更清晰,而不是写在参数列表里。

四、const string& s:const引用传递,既高效又安全

c 复制代码
void func(const string& s) {

    cout << s << endl;

    // s += " world";  // 编译报错,不能修改

}

这是最值得展开讲的一种。const string& s传进去的本质上是原变量的地址 (引用的底层实现通常就是指针),没有开辟任何新的空间 去存字符串内容。函数内部用到的s,实际上操作的还是外面那个原始变量,只是加了一层"只读"的限制------你可以读它、用它,但不能通过s去修改它。

这也是它和string&最大的区别: string&是"直接修改了传进去的变量",而const string&是"用的还是原来的变量,但不让你改它"------既拿到了引用传递不拷贝的性能优势,又靠const守住了值传递的安全性。可以说是把前面三种写法的优点捏在了一起。

优点:

  • 零拷贝开销,无论字符串多长,传参的成本都只是传一个引用(本质上类似传一个指针),跟传int差不多快。
  • 安全,函数内部不可能修改传进来的原变量,调用者可以放心传参,不用担心变量被"偷偷改掉"。
  • 可以接收临时对象和常量字符串 ,比如func("hello")func(a + b)这种字面量和临时结果都可以传给const string&(因为const引用可以绑定到临时对象上,这是C++语言规则里的一个特殊许可),而普通的string&是做不到这一点的。

缺点:

  • 几乎没有明显缺点,唯一要注意的是:如果函数确实需要修改参数或者需要一份独立副本,这种写法就不适用了,硬套上去反而受限。

适用场景:

  • 只读、不修改参数的函数,这是C++里最常见的场景,比如打印、比较、计算长度、查找子串、拼接生成新字符串(返回值是新的,参数本身不变)等。
  • 几乎所有"传入一个字符串,函数只是使用它而不修改它"的场景,都应该优先用这个。

实际情况: 这是最常用 的写法,可以说是C++里传递string(以及绝大多数非基本类型对象,比如vector、自定义类)的默认首选方案

总结对比

| 写法 | 是否拷贝 | 能否修改原变量 | 能否接收临时对象 | 常用程度 |

| string s | 是,开辟新空间 | 不能 | 能 | 一般,仅在需要独立副本时用 |

| string& s | 否 | 能,直接修改 | 不能 | 常用,仅在需要修改原变量时用 |

| const string s | 是,开辟新空间 | 不能(对内部也限制) | 能 | 很少用,收益不明显 |

| const string& s | 否 | 不能 | 能 | 最常用,默认首选 |

一句话选择原则

  • 需要修改 传进来的变量 → 用 string&
  • 只需要读取 传进来的变量,不修改 → 用 const string&(默认首选)
  • 需要在函数内部得到一份完全独立、可以随意改造的副本 → 用 string(按值传递,甚至可以考虑传入后用std::move
  • const string(值传递)基本可以从你的工具箱里划掉,几乎没有它明显优于其他三种的场景

理解了这四种传参方式背后"到底有没有拷贝、拷贝的是谁、能不能改"的本质区别,以后写函数签名的时候就不会再纠结了------大部分时候直接甩一个const string&就是最优解,只有在明确需要修改原变量或者需要独立副本时,才切换成string&string

相关推荐
cpp_25011 小时前
P1540 [NOIP 2010 提高组] 机器翻译
数据结构·c++·算法·队列·noip·洛谷题解
晚笙coding1 小时前
LeetCode 98:验证二叉搜索树 —— 从局部判断到全局范围约束的递归思想
算法·leetcode·职场和发展
学计算机的计算基2 小时前
回溯算法下篇:四道经典题讲透约束剪枝、原地标记、预计算与状态压缩
java·笔记·算法
霸道流氓气质2 小时前
SpringBoot中实现告警判定算法-位报警与模拟量阈值-技术详解
spring boot·算法·jquery
行者全栈架构师2 小时前
混元 Hy3 Agent 实战:季度报告 3 小时变 40 分钟
算法·架构·代码规范
兰令水2 小时前
hot100【acm版】【2026.7.21打卡-java版本】
java·开发语言·算法·leetcode·面试
好好沉淀3 小时前
主键选择(自增 vs UUID vs 雪花算法)
java·算法
退休倒计时3 小时前
【每日一题】LeetCode 131. 分割回文串 TypeScript
算法·leetcode·typescript
小白说大模型3 小时前
AI Agent 调试实战:链路追踪、Prompt 可视化与异常定位的系统方法
java·人工智能·python·算法·prompt