在C++中,函数是具有名称的代码块,用于完成特定的功能。当程序运行到函数调用处时,会跳转到对应的函数体内执行预先编写的逻辑代码。 函数可以接收输入参数,也可以通过返回值将结果反馈给调用者。

函数其实就是一段有名字的代码块。你可以把它想象成厨房里的“做菜流程”:你给它起个名字,比如“做蛋炒饭”,然后规定好需要哪些原料(参数),最后告诉别人怎么做(函数体),做完后还可以把成品端出来(返回值)。
比如,我们来写一个计算某人每天喝水量的函数。假设你想根据体重和天气温度来推荐每天喝多少毫升水:
|int recommendWater(int weight, int temperature) { int base = weight * 30; // 每公斤体重推荐30毫升 if (temperature > 30) { base += 500; // 天气热多喝点 } return base; }
这里,recommendWater就是函数名,int weight, int temperature是参数,int是返回值类型,函数体用大括号括起来。你可以在函数体里写任意多的语句,最后用return把结果返回。
当你需要用到这个“技能”时,只要像喊口令一样写上函数名和参数,程序就会自动跳到函数里执行,然后把结果带回来。例如:
|int main() { int myWeight = 60; int todayTemp = 35; int water = recommendWater(myWeight, todayTemp); std::cout << "今天建议你喝 " << water << " 毫升水。" << std::endl; return 0; }
当程序执行到recommendWater(myWeight, todayTemp)时,会把myWeight和todayTemp的值传给函数,函数内部用这两个值计算结果,然后把结果返回给water变量。
每次你调用函数,参数就像“快递包裹”一样被送进函数内部,函数会用这些参数做事。参数的类型和数量必须和函数定义时保持一致,否则编译器会报错。
比如,如果你写了recommendWater(int, int),那你必须传两个能转换成int的值。如果你多传、少传或者类型不对,编译器都会提醒你。
返回值就像是函数“交作业”的方式。你可以让函数返回一个数字、一个字符串,甚至什么都不返回(用void)。如果你写了int作为返回类型,函数体里必须有return语句返回一个int类型的值。
在函数内部,你可以定义自己的变量,这些变量只在函数体里有效。比如:
|int calculateCalories(int steps) { int calories = steps * 0.04; // 每步消耗0.04千卡 return calories; }
这里的calories变量,只在calculateCalories函数里存在。函数执行完,这个变量就消失了。你可以在不同的函数里用同样的变量名,它们互不影响。
有时候,你希望某个变量在函数多次调用之间“记住”上一次的值。比如你想统计某个按钮被点击了多少次,可以用static关键字:
|int countButtonClick() { static int count = 0; // 只初始化一次 count++; return count; }
每次调用countButtonClick,count都会加1,而且不会因为函数结束而消失。下次再调用时,它会记得上一次的值。
在大型项目中,你可能会把函数的定义和调用分在不同的文件里。为了让编译器知道某个函数的存在,你需要提前“声明”它。声明就像是给编译器打个招呼:“这个函数我后面会实现,你先记住它的名字和参数。”
比如:
|// 在头文件 water.h 里 int recommendWater(int weight, int temperature); // 在源文件 water.cpp 里 int recommendWater(int weight, int temperature) { // ...实现... }
这样,其他文件只要包含water.h,就能调用recommendWater函数了。
分文件编译时,每个源文件可以单独编译成目标文件,最后再链接成一个完整的程序。这就像是大家分头写作业,最后交到老师那里统一装订成册。
每当你调用一个函数时,参数就像“快递包裹”一样被送进函数内部。函数会用这些参数做事,而这些参数的创建和初始化方式,直接影响了你能不能在函数里“改变”外面的东西。
当你写下greet("小明"),程序会把字符串"小明"作为参数传递给greet函数。函数内部会用一个叫name的变量来接收这个参数。这个变量只在函数体里有效,函数执行完就消失了。
|void greet(std::string name) { std::cout << "你好," << name << "!" << std::endl; } int main() { greet("小明"); // 输出:你好,小明! return 0; }
这里的name就是参数,它在greet函数被调用时被创建,并用传进来的"小明"初始化。
默认情况下,C++的参数传递是“值传递”。这就像你给快递员一份文件,他会先复印一份,送到收件人手里。收件人怎么改都不会影响你手里的原件。
比如:
|void addOne(int n) { n = n + 1; std::cout << "函数里n=" << n << std::endl; } int main() { int score = 80; addOne(score); std::cout << "main里的score=" << score << std::endl;
运行结果:
|函数里n=81 main里的score=80
你会发现,虽然在函数里把n加1了,但main函数里的score并没有变。这就是因为n只是score的“复印件”。
有时候你希望函数能直接修改外面的变量,这时可以用“引用传递”。这就像你把原件直接交给快递员,收件人怎么改你手里的也会变。
|void addOne(int& n) { n = n + 1; } int main() { int age = 18; addOne(age); std::cout << "现在年龄:" << age << std::endl; return 0; }
这次,age会真的变成19,因为addOne拿到的是age的“本尊”。
引用传递时,我们需要用到&符号。我们使用引用传递的目的是为了减小拷贝的开销。试想一下,如果一个函数需要传递一个很大的对象,每次调用函数时,都会拷贝这个对象,这样会消耗大量的时间和内存。
而使用引用传递,则不会拷贝这个对象,而是直接传递这个对象的引用,这样会节省大量的时间和内存。
我们知道使用引用传递时,函数可以修改传递进来的对象,这有时候并不是我们想要的,甚至会带来一些意想不到的后果。
因此,有时候你想让函数高效地读取一些比较大对象,但又不希望它被修改,可以用const引用。这就像你把原件借给别人看,但声明“只能看,不能改”。
|void printBook(const std::string& title) { std::cout << "正在阅读:《" << title << "》" << std::endl; }
这样既避免了复制大对象的开销,又保证了其原始数据的安全。
调用函数时,参数的数量和类型必须和定义时一致。比如你定义了void sayHello(std::string name, int times),那调用时必须传一个字符串和一个整数。
如果类型不完全一样,C++会尝试自动转换(比如double转int),但有些类型是不能自动转换的,
例如从std::string转int,这个时候编译器就会报错,所以我们在定义和使用函数时,应该尽量使用精确的类型。
在C++中,函数不仅可以像工厂一样“加工”输入的原料(参数),还可以把“成品”通过return语句送回给调用者。 这个“成品”就是函数的返回值。理解返回值的类型、生命周期和使用方式,是写好函数的关键。
你可以让函数返回各种类型的数据,比如整数、浮点数、字符串、对象,甚至什么都不返回(用void)。但是返回值的类型需要在函数定义时就要写清楚。
比如,写一个函数帮你计算两地之间的平均温度:
|float averageTemperature(float t1, float t2) { return (t1 + t2) / 2.0f; }
这里,averageTemperature返回一个float类型的结果。
return语句的作用有两个:
比如:
|int max(int a, int b) { if (a > b) return a; return b; }
只要遇到return,函数就会立刻“打包”结果返回,后面的代码不会再执行。因此你可以在函数里根据不同情况多次使用return,比如:
|std::string checkScore(int score) { if (score >= 90) return "优秀"; if (score >= 60) return "及格"; return "不及格"; }
有些函数只是做点事情,比如打印一句话、修改全局变量,不需要返回任何结果。这时可以用void作为返回类型,表示这个函数没有返回值:
|void sayHello(const std::string& name) { std::cout << "你好," << name << "!" << std::endl; }
调用sayHello("小红"),它只会输出内容,不会返回任何值。
有时候你希望函数返回的不是一个“快递包裹”,而是“原件”本身,这时可以让函数返回引用或指针。比如你有一个数组,想让函数返回最大值的那个元素的引用:
|int& findMax(int& a, int& b) { return (a > b) ? a : b; } int main() { int x = 5, y = 8; findMax(x, y) = 100; // 直接修改最大值 std::cout << x << ", " <<
注意:只有当返回的引用或指针指向的对象在函数外部依然有效时,这样做才安全。
如果你在函数里定义了一个局部变量,然后把它的引用或指针返回,调用者拿到的其实是“已经失效的地址”,后果很严重。
|int& wrongFunc() { int temp = 42; return temp; // 错误!temp在函数结束后就消失了 }
这样做会导致未定义行为,程序可能崩溃或输出莫名其妙的结果。
当你让函数返回一个对象时,C++会自动把这个对象“拷贝”或“移动”到外部变量里。比如:
|std::string makeGreeting(const std::string& name) { return "Hello, " + name + "!"; } int main() { std::string msg = makeGreeting("小明"); std::cout << msg << std::endl; return 0; }
这里,makeGreeting返回一个临时的字符串对象,C++会自动把它转交给msg变量,临时对象的生命周期会自动延长到msg初始化完成。
在C++中,你可以给不同的函数起同样的名字,只要它们的参数类型、数量或顺序不同。这种能力叫做“函数重载”。 就像你在家里可以有两个叫“小明”的人,一个是哥哥,一个是弟弟,只要你喊“小明,吃饭啦!”时能根据年龄、身高等特征区分他们,大家就不会搞混。
函数重载就是允许你用同一个名字,定义多个功能相似但细节不同的函数。编译器会根据你调用时传入的参数,自动选择最合适的那个。
比如,你想写一个函数,既能打印整数,也能打印小数,还能打印字符串:
|void print(int n) { std::cout << "整数:" << n << std::endl; } void print(double d) { std::cout << "小数:" << d << std::endl; } void print(const std::string& s
编译器会根据你传的参数类型,自动匹配到正确的print函数。 上述例子中我们发现编译器会自动区分哪个是我们需要的函数。 其实重载函数靠的是参数的类型、数量和顺序。只要这三点有区别,编译器就能分辨。
比如:
|void show(int a, double b) { std::cout << "int, double: " << a << ", " << b << std::endl; } void show(double a, int b) { std::cout << "double, int: " << a << ", " << b
这两个show虽然名字一样,但参数顺序不同,编译器能区分。
注意,光靠返回值类型不同,不能构成重载。比如:
|int getValue(); double getValue(); // 错误!仅返回类型不同不能重载
编译器看到你调用getValue()时,根本无法判断你想要哪个版本。
有时候你传入的参数类型和重载函数的参数类型不完全一致,编译器会尝试做自动类型转换。但如果有多个重载都能匹配,可能会报“二义性”错误。比如:
|void greet(int n) { std::cout << "你好,编号为" << n << "的同学!" << std::endl; } void greet(double d) { std::cout << "你好,编号为" << d << "的同学!" << std::endl; } int main() { greet(
如果你传了一个字符'A',编译器会报错,因为它既能转成int,也能转成double,不知道选哪个。
如果你在不同的作用域(比如不同的命名空间、类里)定义了同名函数,它们不会互相影响。只有在同一个作用域下,参数不同的同名函数才构成重载。
在C++中,有时候你会写一些很短小、经常被调用的函数,比如单位换算、简单的数学计算等。 如果每次调用都要像“打电话”一样跳到另一个地方执行,效率可能会受到影响。为了解决这个问题,C++提供了“内联函数”机制。
内联函数就像是你在写作业时遇到常用公式,直接在本子上抄一遍,而不是每次都翻书查。编译器在遇到内联函数调用时,会把函数的代码“原地展开”,就像把公式直接写进了作业里,而不是跳到别的地方再回来。
比如,你经常需要把摄氏度转换成华氏度,可以写一个内联函数:
|inline double celsiusToFahrenheit(double c) { return c * 1.8 + 32; } int main() { double tempC = 25; std::cout << "今天的温度是" << celsiusToFahrenheit(tempC) << "华氏度。" << std::endl; return 0; }
这里,inline关键字告诉编译器“有空的话请把这个函数原地展开”。
内联函数的最大优点是省去了函数调用的开销,尤其是那些很短、很频繁的小函数。这样可以让程序运行得更快。 但内联函数也有缺点:如果你把很大的函数声明为内联,编译器每次遇到调用都会把整段代码复制一遍,导致生成的程序体积变大,反而可能影响性能。
在C++中,有时候你写函数时会发现,某些参数大多数情况下都用同一个值,只有少数时候才需要特别指定。为了让函数调用更简洁,C++允许你为参数设置“默认值”,这就是默认实参。 默认实参就像是点外卖时,商家默认给你加了餐具和酱料,除非你特别说明不要,否则都会自动送上。你在定义函数时,可以为参数指定一个默认值,调用时如果不传这个参数,编译器就会自动用默认值。
比如,你想写一个发通知的函数,大多数时候通知都是“普通”级别,只有特殊情况才需要“紧急”级别:
|void sendNotice(const std::string& message, const std::string& level = "普通") { std::cout << "【" << level << "通知】" << message << std::endl; } int main() { sendNotice("明天早上8点开会"); // 用默认级别 sendNotice
第一个调用只传了消息内容,level自动用"普通";第二个调用则指定了"紧急"。
默认参数必须从右往左连续设置。也就是说,如果你给某个参数设置了默认值,后面的参数都必须有默认值。
|void bookTicket(const std::string& name, int count = 1, std::string seat = "A区") { std::cout << name << " 订了 " << count << " 张 " << seat << " 的票。" << std::endl; } int main() {
你不能只给中间的参数设置默认值,必须保证参数的顺序不会让编译器产生歧义。
如果你在头文件里声明了函数的默认参数,源文件定义时就不要再写一遍默认值,否则会报错或产生二义性。
|// ticket.h void bookTicket(const std::string& name, int count = 1, std::string seat = "A区"); // ticket.cpp void bookTicket(const std::string& name, int count, std::string seat) {

在C++里,指针不仅可以指向变量、数组,还能指向函数。你可以把函数指针想象成“遥控器”,它能远程“操控”某个函数,让你在需要的时候随时调用它。 函数指针其实就是一个变量,只不过它存储的不是普通数据,而是某个函数的“地址”。比如说,你有一个判断两个数字大小的函数:
|bool isGreater(int a, int b) { return a > b; }
如果你想用指针来“指向”这个函数,可以这样写:
|bool (*funcPtr)(int, int); // 声明一个指向函数的指针 funcPtr = isGreater; // 让它指向isGreater函数
这里的funcPtr就是一个函数指针,它能指向任何返回bool、参数是两个int的函数。注意,*funcPtr外面要加括号,否则就变成了“返回bool指针的函数”了。
有了函数指针,你就可以像用普通函数一样,通过指针来调用它:
|bool result = funcPtr(10, 5); // 相当于调用isGreater(10, 5)
你也可以写成(*funcPtr)(10, 5),效果完全一样。其实,函数名本身在需要的时候会自动转换成指针,所以你可以直接赋值,不用加&。
如果你把一个类型不匹配的函数赋值给指针,编译器会报错。比如:
|int add(int, int); funcPtr = add; // 错误!返回类型不对
只有参数和返回值类型都完全一致,才能赋值。
如果你有多个同名但参数不同的函数(重载),用指针指向时,编译器会根据指针的类型自动选择合适的那个。例如:
|void show(int n) { std::cout << "整数:" << n << std::endl; } void show(double d) { std::cout << "小数:" << d << std::endl; } void (*showPtr)(double) = show; // 指向double版本 showPtr(3.14); // 输出:小数:3.14
有时候你希望把“做什么事”这个选择权交给别人,比如写一个“比较两个数”的函数,但具体怎么比由调用者决定。这时可以把函数指针作为参数传进去:
|void compareAndPrint(int a, int b, bool (*cmp)(int, int)) { if (cmp(a, b)) { std::cout << a << " 比 " << b << " 更大" << std::endl; } else { std::cout << b << " 比 "
如果你发现函数指针的写法太长,可以用typedef或者using给它起个别名,让代码更清爽:
|using CompareFunc = bool(*)(int, int); void compareAndPrint(int a, int b, CompareFunc cmp) { // ...和上面一样... }
这样以后用CompareFunc就行了,不用每次都写一大串。
有时候你希望函数的返回值是一个“函数指针”,比如根据用户的选择返回不同的操作方式。写法稍微复杂一点,但也不难:
|bool addOne(int x, int y) { return x + 1 > y; } bool minusOne(int x, int y) { return x - 1 > y; } using OpFunc = bool(*)(int, int); OpFunc chooseOp
注意,返回类型要写成指针类型,比如OpFunc,不能直接写函数类型。
你也可以用auto和“尾置返回类型”来简化声明:
|auto chooseOp(bool useAdd) -> bool(*)(int, int) { // ... }
如果你已经有了某个函数,可以用decltype来自动推导类型,避免手写复杂的声明:
|decltype(isGreater) *getFunc(bool flag) { return flag ? isGreater : isSmaller; }
这里decltype(isGreater)得到的是函数类型,加个*就变成了指针类型。
现在让我们通过一些简单的练习题来巩固本章学到的知识。每道题都涵盖了函数的核心概念,请先尝试独立完成,然后再查看答案。
编写一个函数 int square(int n),返回参数n的平方。在 main 函数中调用它,输出5的平方。
|#include <iostream> using namespace std; // 函数定义:计算平方 int square(int n) { return n * n; } int main() { int result = square(5); // 调用函数 cout << "5的平方是:" << result << endl; return 0
编写一个函数 void swap(int &a, int &b),交换两个整数的值。在 main 函数中演示交换两个变量的效果。
|#include <iostream> using namespace std; // 使用引用传递,可以修改实参的值 void swap(int &a, int &b) { int temp = a; a = b; b = temp; } int main() { int x = 10, y = 20;
编写三个名为 print 的函数,分别能打印整数、浮点数和字符串。在 main 中分别调用它们。
|#include <iostream> #include <string> using namespace std; // 打印整数 void print(int n) { cout << "整数:" << n << endl; } // 打印浮点数 void print(double d) { cout << "浮点数:" << d << endl; }
编写一个函数 void showInfo(const string &name, int age = 18, const string &city = "北京"),输出个人信息。在 main 中用不同参数个数调用它。
|#include <iostream> #include <string> using namespace std; // 默认参数必须从右到左连续设置 void showInfo(const string &name, int age = 18, const string &city = "北京") { cout << "姓名:" << name << endl; cout << "年龄:" << age
编写一个函数 string greet(const string &name),返回 "Hello, name!" 格式的字符串。在 main 中调用并输出结果。
|#include <iostream> #include <string> using namespace std; // 函数返回一个字符串 string greet(const string &name) { return "Hello, " + name + "!"; } int main() { string message = greet("C++"); // 接收返回值 cout << message <<
函数定义包括返回类型、函数名、参数列表和函数体。调用函数时,传入的参数会传递给函数,函数执行后返回结果。这个例子展示了函数的基本用法:定义、调用和返回值的使用。
引用传递(使用 &)允许函数修改实参的值。如果使用值传递(void swap(int a, int b)),函数内部交换的是副本,不会影响原来的变量。引用传递避免了复制,提高了效率,并且可以直接修改实参。
函数重载允许我们定义多个同名函数,只要它们的参数列表不同(参数类型或个数不同)。编译器会根据传入的参数类型自动选择匹配的函数版本。这让我们可以用同一个函数名处理不同类型的数据。
默认参数让我们在调用函数时可以省略某些参数。默认参数必须从右到左连续设置,也就是说,如果某个参数有默认值,它右边的所有参数也必须有默认值。这样可以让函数调用更灵活,同时保持代码简洁。
函数可以返回各种类型的值,包括基本类型、字符串、对象等。返回值会被复制给调用者。在这个例子中,函数返回一个字符串,我们可以用变量接收它,也可以直接使用。注意,不能返回局部变量的引用,因为局部变量在函数结束后会被销毁。