Spreading展头像
关注

《Python & C++ 》双修心法:函数篇

Python vs C++ 函数完整对照详解

🎯 核心总纲

在正式开始之前,我想先把这两门语言在"函数"这件事上的根本差异亮出来,因为理解了这层本质,后面所有细节都是顺理成章的。

  • C++ 函数:编译期确定签名;区分传值 / 传引用 / 传指针;栈上分配栈帧;支持重载;函数是独立代码块,不是对象
  • Python 函数:运行期解析;全部传对象引用;函数本身是一等堆对象;无重载;灵活可变参数

在我个人看来,这两者的差异本质上不是"语法不同",而是两种完全不同的语言哲学在函数这个切面上的投影。C++ 关心的是"编译期我能不能把一切都定死",Python 关心的是"运行期我怎么把一切变得足够灵活"。


目录


1. 函数定义语法

Python

def add(a: int, b: int) -> int:
    return a + b

Python 的定义有几个值得注意的地方:

  • def 是关键字,但它不像 C++ 那样在编译期生成代码,而是运行时才创建函数对象,函数本身存放在
  • 可以写类型注解 a: int-> int,但它仅起到提示作用,运行时并不校验,传一个字符串进去照样能跑(直到运算报错)
  • 如果没有写 return,函数默认返回 None
  • 函数可以嵌套定义,这点在后面的闭包章节还会展开

C++

int add(int a, int b) {
    return a + b;
}

C++ 的写法则是另一番气象:

  • 返回类型写在最前面,编译期就把函数编译成机器指令
  • 没有返回值时必须写 void,且不存在隐式返回——如果你漏了 return,编译器会给你警告甚至报错
  • C++ 的普通函数不能嵌套定义(这一点和 Python 差异很大),想要局部定义函数只能借助 lambda

💡 个人的看法:这里最值得玩味的是"类型注解"。很多初学者以为 Python 有了 -> int 就安全了,其实那只是"写给维护者看的说明书",编译器根本不看。这既是 Python 灵活性的来源,也是它容易在大型项目里埋雷的地方——而 C++ 恰恰相反,它把所有的约束都压在了编译期,宁可让你编译慢一点,也不让错误混到运行期。


2. 参数传递(最容易混淆,重中之重)

在我看来,这一节是整个"函数对照"里最值得花时间的部分,因为太多人在 Python 和 C++ 之间切换时,在这里栽过跟头。

Python:只有一种传递方式 —— 传对象引用(传递指针语义)

⚠️ 请先记住一句话:Python 没有真正的传值拷贝! 它不会拷贝对象本体,而是复制对象的引用(指针)。

理解了这一点,下面的两种现象就都说得通了。

① 可变对象(list、dict):函数内部修改对象内容,外部可见

def modify_list(data):
    data.append(100)

lst = [1, 2, 3]
modify_list(lst)
print(lst)  # [1,2,3] → [1,2,3,100],外部被改动

因为 datalst 指向的是同一个 list 对象,所以你在函数里 append,外部自然能看到变化。

② 不可变对象(int、str、tuple):函数内部重新赋值,外部不受影响

def modify_num(x):
    x = 999  # 只是把局部引用 x 指向新对象,外部变量不受影响

num = 10
modify_num(num)
print(num)  # 输出 10,不变

这里要特别澄清一个常见的误解:x = 999 并不是在修改原来的对象(int 本身就是不可变的,想改也改不了),而是把局部变量 x 这个引用重新绑定到了一个新的堆对象 999 上。外部的 num 还稳稳地指着原来的 10

💡 x = 999 不是修改旧对象,而是局部引用重新绑定新堆对象


C++:三种传参模式,编译期决定

C++ 这边就直白得多,它在编译期就把"你要怎么传"定死了,常见的有四种。

① 传值(拷贝)

void modify(int x) {
    x = 999;
}
int a = 10;
modify(a);  // 拷贝一份副本,外部 a 不变

传值时,参数 xa 的一份完整拷贝,你在函数里改的只是副本,外面的 a 纹丝不动。

② 左值引用 &,别名,直接操作原内存

void modify(int &x) {
    x = 999;
}
int a = 10;
modify(a);  // a 变成 999

引用本质上是原对象的别名xa 操作的是同一块内存,所以修改会直接作用于外部变量。

③ const 左值引用,只读,大对象优先使用,避免拷贝开销

void show(const std::vector<int>& v) {
    // v 不能修改
}

这是我在 C++ 里最推荐的一种传参方式:既避免了传值带来的拷贝开销,又通过 const 保证了只读,安全性最高。

④ 指针传参 *

void modify(int *p) {
    *p = 999;
}
int a = 10;
modify(&a);

指针和引用在效果上很接近,但指针可以指向 nullptr、可以被重新赋值,语义上比引用更"自由"也更危险,需要自己保证非空。

对比小结

维度PythonC++
传参本质永远复制引用,无拷贝对象本体传值会完整复制对象;引用 / 指针直接操作原内存
想要拷贝对象手动 copy / deepcopy传值即可
大对象处理无拷贝开销问题优先 const 引用 避免拷贝

📝 个人总结:用一句话概括——Python 里"传参"实际上传的是"对象引用",而 C++ 里你得自己决定"传值、传引用还是传指针"。Python 把这种选择替你包了起来,换来的是简单;C++ 把选择交还给你,换来的是精确的控制。这里没有谁绝对更好,关键看你追求什么。


3. 默认参数

Python —— 大坑:默认参数不能用可变对象

# ❌ 大坑:默认参数不能用可变对象
def bad_func(items=[]):
    items.append(1)
    return items

# ✅ 正确写法
def good_func(items=None):
    if items is None:
        items = []
    items.append(1)
    return items

⚠️ 坑点:默认参数对象在函数定义的时候就已经创建好了,而不是每次调用时重新创建。如果默认值是 [] 这样的可变对象,那么所有调用共享这同一个 list,状态会跨调用累积,这是无数 Python 新手(甚至老手)都踩过的经典陷阱。

在我看来,这个坑是 Python 设计里最"反直觉"的地方之一。解决方式也很简单:默认值一律写 None,在函数体内部再去创建新的可变对象。这不是什么高深技巧,但值得形成习惯。

C++ 默认参数

void func(int a, int b = 10) {

}
  • 默认参数在编译期确定;而且有个约定俗成的规矩——在声明处写默认值,实现处不要再重复写,否则容易产生歧义
  • C++ 不存在 Python 那个坑,因为每次调用都会按类型构造对应的临时对象

📝 个人看法:同样是"默认参数",Python 因"对象在定义时创建"而埋下坑,C++ 因"每次调用构造"而天然安全。这恰恰印证了我们在第 1 节说的那句话——Python 的运行时灵活性,往往要付出"隐式共享状态"的代价


4. 可变参数

Python

def demo(a, *args, **kwargs):
    print(a)
    print(args)    # 元组,接收多余位置参数
    print(kwargs)  # 字典,接收多余关键字参数

demo(1, 2, 3, name="abc")
  • *args 把多余的位置参数打包成一个元组**kwargs 把多余的关键字参数打包成一个字典
  • 这种写法在 Python 里几乎是开箱即用的,非常灵活

C++

C++ 没有 Python 这种开箱即用的可变参数,需要绕一下:

  1. C++11 之后可以使用可变模板参数(在编译期展开,类型安全)
  2. 也可以用 C 风格的 va_list,但它不安全、不推荐在业务代码中使用

💡 C++ 不能像 Python 那样随便接收任意多参数,这也是两者在设计理念上的一大差异。

📝 个人看法*args**kwargs 是 Python 里极其强大的工具,尤其在写框架、装饰器、接口封装时几乎离不开。但我想提醒的是,灵活是一把双刃剑——过度使用会让函数签名变得模糊、难以为调用者提供清晰的提示。建议是:能写明确参数就别滥用可变参数,它更适合做"透传"而非"日常"。C++ 的可变模板参数虽然写起来繁琐,但类型安全,这也再次体现了 C++"宁可麻烦也要安全"的哲学。


5. 函数重载

Python:没有函数重载

def calc(x):
    pass

def calc(x, y):
    pass
# 后面直接覆盖前面,只有第二个 calc 生效
  • 在 Python 里,后面定义的函数会直接覆盖前面同名函数,只有最后一个生效
  • 想要实现"多参数版本",只能用默认参数*args 在函数内部手动判断

C++:支持函数重载

int calc(int a);
int calc(int a, int b);
// 编译期根据实参匹配不同函数
  • C++ 允许函数名相同、参数列表不同,编译器会根据实参自动匹配最合适的那个
  • 注意:返回值不能用来区分重载,只看参数列表

📝 个人看法:很多人会感慨"Python 居然不支持重载"。但其实换个角度想,Python 的动态类型让"重载"变得没那么必要——因为参数本身可以是任何类型,一个函数就能覆盖很多情况,配合默认参数和 *args 已经够用了。而 C++ 是静态类型语言,没有运行时的灵活性,所以它把"多态"下沉到了编译期,用重载来补偿。语言不给你某样东西,往往是因为它用别的方式给了你等价的能力。


6. 返回值

Python

  • return 可以返回任意对象,几乎没有限制
  • 更妙的是,它支持一次性返回多个值,本质上是返回了一个元组
def get_pos():
    return 10, 20  # 等价 return (10, 20)

x, y = get_pos()
  • 这里 return 10, 20 实际返回的是元组 (10, 20),再通过解包 x, y = ... 分别接收

C++

  • 返回值可以传值、引用或指针,选择权在你自己
  • 但有一条铁律需要牢记:千万不要返回局部栈对象的引用或指针。因为局部变量在函数结束时就被销毁了,返回的引用会变成悬垂引用,访问它是未定义行为(UB)
// ⚠️ 危险!返回局部变量引用
int& bad() {
    int x = 10;
    return x;  // x 在栈上,函数结束销毁,悬垂引用 UB 未定义行为
}

📝 个人看法:多返回值这个特性,是 Python 日常开发里非常舒服的地方,省去了 C++ 里定义结构体或者传引用参数去"带出"结果的麻烦。而在 C++ 里,返回引用的诱惑(省一次拷贝)往往伴随着悬垂的风险,因此:没有十足把握,就老老实实按值返回,现代编译器有 RVO / 移动语义,性能损失远没有想象中那么大。安全永远排在"省一次拷贝"前面。


7. 函数是一等公民(巨大差异)

一等公民:函数可以赋值给变量,作为参数传入,作为返回值返回。

这一节的差异,实质上是两门语言"气质"上最大的分水岭之一。

Python

函数是堆上的普通对象,可以赋值、传参、返回,闭包正是基于这个特性才得以成立。

def hello():
    return "hi"

f = hello      # 把函数对象赋值给 f
print(f())     # hi

# 函数作为参数
def run(func):
    return func()

run(hello)

这种"把函数当数据传来传去"的能力,让 Python 在写回调、装饰器、策略模式时行云流水。

C++

C++ 的普通函数本身并不是对象,不能直接赋给变量,但可以用几种方式"曲线救国":

  1. 函数指针int (*fp)() = hello;
  2. std::function 包装器:可以包装任意可调用对象
  3. lambda:局部定义匿名函数对象
#include <functional>
#include <iostream>

int hello() {
    return 100;
}

int main() {
    // 函数指针
    int (*fp)() = hello;
    std::cout << fp();

    // std::function 包装器
    std::function<int()> f = hello;
    return 0;
}

📝 个人看法:Python 是一等公民的"原生"支持者,函数用起来就像变量一样自然;C++ 则把这个能力"外包"给了函数指针、std::function 和 lambda 这三驾马车,能用,但需要你多一层心智负担。从现代工程实践看,C++ 我更推荐优先用 std::function 或 lambda,函数指针的语法可读性确实差一些。这里没有对错,只是"语言给了你多直接的路径"的区别。


8. 嵌套函数 & 闭包

Python

Python 允许在函数内部再定义函数,从而形成闭包——内层函数可以捕获并使用外层函数的变量;如果要在内层修改外层变量,需要借助 nonlocal

def outer(x):
    def inner():
        return x + 1
    return inner

f = outer(10)
print(f())  # 11

这里 inner 捕获了 outer 的参数 x,即使 outer 已经返回,inner 依然能访问到 x——这正是闭包的精髓。

C++

C++ 的普通函数不能嵌套,闭包只能靠 lambda 实现。lambda 捕获外部变量有两种方式:值捕获 [=](捕获一份拷贝)和引用捕获 [&](捕获引用)。

#include <functional>
#include <iostream>

std::function<int()> outer(int x) {
    return [x]() {  // 值捕获 x
        return x + 1;
    };
}

int main() {
    auto f = outer(10);
    std::cout << f();
    return 0;
}

📝 个人看法:闭包是一个非常强大的抽象,但我想提醒的是——捕获要谨慎。在 C++ 里,如果 lambda 用 [&] 捕获了一个已经销毁的局部变量,同样会产生悬垂引用;而 Python 里如果用闭包捕获循环变量,也容易踩到"延迟绑定"的坑。闭包是把双刃剑,用好了是利器,用不好就是隐蔽的 bug 源头。个人习惯是:C++ 优先值捕获 [=],只在确有必要时才用引用捕获。


9. 作用域

Python —— LEGB 规则

Python 的变量查找遵循一个明确的顺序,用一个口诀记它:

Local 局部 → Enclosed 闭包外层 → Global 全局 → Builtin 内置

  • 修改全局变量:用 global 声明
  • 修改外层嵌套函数里的局部变量:用 nonlocal 声明

这套规则解释了为什么 Python 里"在函数里给全局变量赋值"会报错——因为赋值默认被当作创建了一个新的局部变量,除非你用 global 明确声明。

C++

  • 局部作用域:变量在函数(或 {} 块)内声明,离开作用域即销毁
  • 全局作用域:整个程序可见,但应尽量少用,容易造成耦合
  • 命名空间 namespace 用来隔离符号;C++ **没有"闭包作用域"**这一说

📝 个人看法:Python 的 LEGB 和 global/nonlocal 初看有点绕,但它换来的是"隐式的词法作用域",写起来很省心;C++ 则把一切规则都显式化、块级化,作用域清晰但需要你时时记着。我个人认为,作用域的本质都是"可见性和生命周期"的管理,理解了这个抽象,换哪种语言都不会慌。


10. 栈帧底层对比(结合之前内存模型)

这一节如果只看语法可能觉得抽象,但它是理解两门语言"性能气质"的钥匙。

① Python 函数调用

  • Python 在解释器层面创建"栈帧对象",这个栈帧对象其实是放在上的;真正的操作系统栈上只存了少量指针
  • 函数结束时,栈帧对象解除引用,回收时机要等 GC 决定,不确定

② C++ 函数调用

  • C++ 在操作系统原生栈上开辟栈帧;局部变量直接放在 CPU 栈上
  • 函数执行完毕,栈帧立即弹出销毁,生命周期确定

💡 C++ 局部对象的生命周期是确定性的、可预测的;而 Python 的栈帧、对象回收时机不确定,受 GC 支配。这也是为什么同样一个高频调用的函数,C++ 能跑得远比 Python 快的重要原因之一——它省去了对象管理和 GC 的额外开销。

📝 个人思考:理解了这一点,你就明白为什么"性能敏感"的场景几乎都选 C++,而"开发效率优先"的场景更适合 Python。这不是谁比谁"好",而是两者在"确定性"和"灵活性"上做了不同的取舍。C++ 把栈帧管理、对象生命周期全部握在自己手里,换来了可预测的性能;Python 把这一切交给解释器和 GC,换来了极高的开发效率。


极简核心总结表

把上面十节浓缩成一张表,方便你随时回顾:

特性Python 函数C++ 函数
定义def,运行期生成堆函数对象返回类型 + 函数名,编译成机器码
传参全部传对象引用;无拷贝,需手动 copy传值(拷贝)/ & 引用 / * 指针 / const 引用
默认参数⚠️ 可变对象陷阱,定义时实例化编译期确定,无此坑
可变参数*args **kwargs 原生支持模板可变参数 / va_list
重载不支持,后定义覆盖前面支持,参数列表区分
一等公民✅ 函数是对象,可赋值传参返回靠函数指针 / std::function / lambda
嵌套闭包def 嵌套 + nonlocal普通函数不能嵌套,lambda 实现闭包
栈帧解释器栈帧(堆),回收看 GC操作系统原生栈,调用完立刻弹出销毁

📌 一句话总结:Python 函数是运行时的一等堆对象,靠引用传递、灵活但开销大、回收不确定,适合追求开发效率的场景;C++ 函数是编译期的机器代码块,靠栈帧 / 拷贝 / 引用精确控制生命周期与内存,效率高但更繁琐,适合追求性能与确定的场景。没有哪门语言更好,只有哪门语言更适合你当下的问题——理解它们的差异,正是选择与驾驭它们的前提。


希望这篇对照能帮你建立起对两门语言函数的整体认知。如果哪里讲得不够清楚,欢迎留言交流。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/m0_54450246/article/details/163979407

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--