5、函数(上):函数定义与调用、参数传递、返回值、函数重载
好,咱们今天聊聊函数。说实话,函数是C++里最基础也最核心的概念之一。你想想看,如果没有函数,所有代码都堆在main里,那得多恐怖?我刚开始学的时候,就写过几百行的main函数,调试起来简直想哭。后来才明白,把功能拆成函数,才是正经的编程思维。
5.1 函数定义与调用——先学会怎么“造”和“用”
函数定义,说白了就是给一段代码起个名字,以后想用就喊这个名字。语法其实很简单:
返回值类型 函数名(参数列表) {
// 函数体
return 返回值;
}
举个例子,咱们写一个计算两个整数和的函数:
int add(int a, int b) {
int sum = a + b;
return sum;
}
调用的时候更简单:
int result = add(3, 5); // result 就是 8
嗯,这里要注意:C++要求函数要么在调用之前定义,要么提前声明。我见过不少新手把函数定义写在main后面,结果编译报错,一脸懵。其实加个声明就行:
// 声明
int add(int a, int b);
int main() {
int result = add(3, 5);
return 0;
}
// 定义
int add(int a, int b) {
return a + b;
}
5.2 参数传递——值传递 vs 引用传递
这是很多初学者容易搞混的地方。我当年也踩过坑,咱们一次说清楚。
5.2.1 值传递
值传递,就是把实参的值复制一份给形参。函数里改形参,不影响实参。就像你复印了一份文件,在复印件上涂改,原件还是好好的。
void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
// 这里改的是副本,外面的a和b不受影响
}
int main() {
int a = 10, b = 20;
swap(a, b);
// a还是10,b还是20
return 0;
}
为什么会这样?因为值传递时,函数内部操作的是实参的拷贝。我在项目中遇到过有人用值传递去交换两个大对象,结果性能惨不忍睹——每次调用都要复制整个对象,你说亏不亏?
5.2.2 引用传递
引用传递就不一样了。它传的是实参的别名,函数里改的就是实参本身。说白了,就是直接把原件的地址给你,你改的就是原件。
void swap(int &x, int &y) {
int temp = x;
x = y;
y = temp;
// 这里改的是实参本身
}
int main() {
int a = 10, b = 20;
swap(a, b);
// a变成20,b变成10
return 0;
}
我曾经在维护一个老项目时,发现有人用值传递传了一个巨大的结构体,每次调用都卡半天。改成const引用后,性能直接翻倍。你想想看,传一个指针大小的引用,和传几百字节的副本,能一样吗?
5.3 返回值——函数给你的“回信”
返回值就是函数执行完后,给调用者返回一个结果。可以是基本类型、对象、指针,甚至引用。
int max(int a, int b) {
return (a > b) ? a : b;
}
这里有个坑:不要返回局部变量的引用或指针。因为局部变量在函数结束后就被销毁了,返回它的引用或指针,拿到的是野指针,用起来就是未定义行为。
int& getValue() {
int x = 10;
return x; // 危险!x是局部变量
}
这种代码编译可能不报错,但运行时随时可能崩溃。记住:永远不要返回局部变量的引用或指针。
那什么时候返回引用呢?比如你想让函数返回一个容器里的元素,方便外部修改:
int& getElement(std::vector<int>& vec, int index) {
return vec[index]; // 返回引用,外部可以修改vec[index]
}
5.4 函数重载——同名不同“料”
函数重载,就是同一个函数名,可以有不同的参数列表。编译器会根据你传的参数类型和个数,自动选择调用哪个版本。
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
调用的时候:
add(1, 2); // 调用 int 版本
add(1.5, 2.3); // 调用 double 版本
add(1, 2, 3); // 调用三个参数的版本
嗯,这里要注意:重载只看参数列表,不看返回值类型。你不能写两个同名函数,参数一样,只是返回值不同——编译器会报错,因为它不知道你调的是哪个。
void print(int x);
void print(const char* str);
然后调用 print(nullptr),结果编译器报错,因为nullptr既可以匹配int(0),也可以匹配指针。这种二义性要尽量避免。我的建议是:重载时尽量让参数类型差异明显,别让编译器猜。
知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以对照着看,哪里还不清楚,就回头翻翻。
好了,函数的上半部分就讲到这里。记住:定义要清晰,传参要选对方式,返回值要小心局部变量,重载要避免二义性。这些基本功打扎实了,后面学函数指针、lambda表达式才会轻松。