C++函数初相识
函数是C++ 编程的基础模块,它可以将一段独立的功能代码封装起来,通过定义好的接口进行调用,提高了代码的复用性和可维护性 。
函数能将复杂的任务分解成一个个独立的模块,每个模块负责完成特定的功能,使得程序结构清晰,便于管理和维护。例如在一个大型游戏开发项目中,角色的移动、攻击、防御等功能都可以封装成不同的函数。当需要实现角色的某个行为时,直接调用相应的函数即可,而不需要每次都重复编写实现该行为的代码。
函数定义
一个完整的C++函数定义包括以下几个部分:返回类型、函数名、参数列表和函数体。下面来看一个简单的加法函数示例:
// 返回类型为int,表示函数返回一个整数
// 函数名为add
// 参数列表包含两个int类型的参数a和b
int add(int a, int b) {
// 函数体:执行加法操作并返回结果
return a + b;
}
在这个例子中,int是返回类型,它告诉编译器这个函数最终会返回一个整数。add是函数名,这是调用这个函数时使用的标识符,(int a, int b)是参数列表,该函数定义了两个int类型的参数a和b,它们是函数执行加法运算所需要的操作数。函数体{ return a + b; }实现加法功能的具体代码,并通过return语句返回计算结果 。
函数参数传递
值传递
值传递就像是你有一份重要的文件(实参),当你要交给别人(函数)处理时,你并不直接把原件给他,而是先复印一份(副本),然后把复印件交给对方。对方在复印件上进行任何修改(修改形参),都不会影响到你手中的原件(实参)。
下面来看一个交换两个整数的函数示例:
void swapValues(int a, int b) {
int temp = a;
a = b;
b = temp;
}
在这个函数中,a和b是通过值传递接收实参值的副本。当外部调用swapValues函数时,例如swapValues(x, y),x和y的值会被复制给a和b,在函数内部交换a和b的值,并不会影响到x和y在函数外部的实际值。
引用传递
引用传递则像是你给别人一个指向你文件的快捷方式(引用),别人通过这个快捷方式直接访问和修改你的文件(实参)。也就是说,引用传递时,函数接收的是实参的别名,对形参的任何修改都会直接反映在实参上。
还是以上面的交换函数为例,使用引用传递来实现:
void swapReferences(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
这里的int& a和int& b表示a和b是引用类型的参数,它们分别是实参的别名。当调用swapReferences(x, y)时,a和b就分别成为了x和y的别名,在函数内部对a和b的操作,实际上就是对x和y的操作,所以能够真正交换x和y的值。
指针传递
指针传递就像是你把文件的存放地址(指针)告诉别人,别人可以通过这个地址找到并访问你的文件(实参)。在指针传递中,函数接收的是一个指针,该指针指向实参的内存地址,通过操作指针,可以间接修改实参的值。
下面是使用指针传递实现交换函数的代码:
void swapPointers(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
在这个函数中,int* a和int* b表示a和b是指针类型的参数,它们存储的是实参的内存地址。当调用swapPointers(&x, &y)时(这里的&是取地址运算符,用于获取x和y的地址),a和b分别指向x和y的内存地址,通过解引用指针(*a和*b),可以访问并修改x和y的值,从而实现交换功能 。
函数声明与调用
函数声明让编译器知道这个函数的基本信息,包括函数名、返回类型和参数列表。当编译器在后续代码中遇到函数调用时,能够顺利地进行编译工作。函数声明的语法和函数定义的头部类似,只是不需要包含函数体,例如:
// 函数声明
int add(int a, int b);
当程序执行到函数调用语句时,会按照函数定义的逻辑执行相应的代码。调用函数时,需要提供与函数参数列表匹配的实参,实参的数量、类型和顺序都要与形参一致。例如,对于前面定义的add函数,可以这样调用:
int result = add(3, 5);
在这个例子中,add(3, 5)就是函数调用表达式,3和5是实参,它们会按照顺序分别传递给add函数的形参a和b。函数执行完毕后,返回的结果会赋值给result变量。
函数的重载
函数重载允许在同一作用域内定义多个同名的函数,不过这些函数的参数列表(包括参数的数量、类型或顺序)必须有所不同。
例如,在开发一个数学计算库时,经常会遇到需要实现不同类型数据相加的功能。如果没有函数重载,可能需要为每种数据类型分别定义不同的函数名,像addInt用于整数相加,addDouble用于双精度浮点数相加等等,这样不仅函数名繁多,而且代码的可读性和维护性较差。通过函数重载,可以定义多个名为add的函数,分别处理不同类型的数据。
// 处理两个整数相加
int add(int a, int b) {
return a + b;
}
// 处理两个双精度浮点数相加
double add(double a, double b) {
return a + b;
}
// 处理一个整数和一个双精度浮点数相加
double add(int a, double b) {
return a + b;
}
在调用上述函数时,编译器会根据实参的类型和数量,自动匹配最合适的函数版本。例如:
int result1 = add(3, 5); // 调用int add(int a, int b)
double result2 = add(3.5, 5.5); // 调用double add(double a, double b)
double result3 = add(3, 5.5); // 调用double add(int a, double b)
注意:函数的返回类型不能作为函数重载的依据。也就是说,仅仅返回类型不同,而参数列表相同的两个函数,是不能构成函数重载的。下面函数重载的代码是错误的:
// 错误示例:仅返回类型不同,不能构成函数重载
int add(int a, int b) {
return a + b;
}
double add(int a, int b) {
return a + b;
}
编译器在编译时,无法根据函数调用语句add(3, 5)来确定到底应该调用哪个函数,这就会导致编译错误。因此在使用函数重载时,一定要确保参数列表有明显的区别,这样才能让编译器匹配到正确的函数版本。
默认参数
默认参数允许在定义函数时,为参数指定一个默认值。这样,在调用函数时,如果没有为该参数传递具体的值,编译器就会自动使用默认值 。
假设你正在编写一个发送邮件的函数,其中有一个参数是邮件的优先级。在大多数情况下,我们可能希望邮件以普通优先级发送,只有在特殊情况下才需要指定更高或更低的优先级。这时,就可以使用默认参数来简化函数调用。
// 定义发送邮件的函数,priority参数有默认值1(表示普通优先级)
void sendEmail(const std::string& recipient, const std::string& content, int priority = 1) {
// 函数体:实现发送邮件的逻辑
std::cout << "Sending email to " << recipient << " with content: " << content;
if (priority != 1) {
std::cout << " and priority " << priority;
}
std::cout << std::endl;
}
在调用这个函数时,可以根据实际需求选择是否传递priority参数:
// 调用函数,使用默认优先级
sendEmail("example@example.com", "Hello, this is a test email.");
// 调用函数,指定优先级为3
sendEmail("another@example.com", "Important notice!", 3);
使用默认参数时,也有一些规则需要遵循。首先,有默认值的参数必须从右至左连续排列,也就是说,一旦某个参数有了默认值,它右边的所有参数都必须有默认值。例如:
// 正确示例
void func(int a, int b = 2, int c = 3) {
// 函数体
}
// 错误示例
void wrongFunc(int a = 1, int b, int c = 3) {
// 函数体,b没有默认值,不符合规则
}
默认参数只能出现在函数声明中,不能同时出现在声明和定义中。
// 函数声明,指定默认参数
void printInfo(const std::string& name, int age = 18);
// 函数定义,不能再指定默认参数
void printInfo(const std::string& name, int age) {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
简单计算器
这个计算器将具备加、减、乘、除四种基本运算功能,通过用户输入选择运算类型,并输入相应的操作数来进行计算。
首先定义四个函数分别实现加、减、乘、除运算:
// 加法函数
double add(double a, double b) {
return a + b;
}
// 减法函数
double subtract(double a, double b) {
return a - b;
}
// 乘法函数
double multiply(double a, double b) {
return a * b;
}
// 除法函数
double divide(double a, double b) {
if (b == 0) {
// 处理除零错误
std::cerr << "Error: Division by zero!" << std::endl;
return 0;
} return a / b;
}
然后,在main函数中,获取用户的输入,并根据用户选择的运算符调用相应的函数进行计算:
#include <iostream>
int main() {
double num1, num2;
char operator;
std::cout << "请输入第一个数字: ";
std::cin >> num1;
std::cout << "请输入运算符 (+, -, *, /): ";
std::cin >> operator;
std::cout << "请输入第二个数字: ";
std::cin >> num2;
double result;
switch (operator) {
case '+':
result = add(num1, num2);
break;
case '-':
result = subtract(num1, num2);
break;
case '*':
result = multiply(num1, num2);
break;
case '/':
result = divide(num1, num2);
break;
default:
std::cerr << "错误: 无效的运算符" << std::endl;
return 1;
}
std::cout << "计算结果是: " << result << std::endl;
return 0;
}
在这段代码中,add、subtract、multiply和divide函数分别实现了加法、减法、乘法和除法运算。在main函数中,首先提示用户输入两个数字和一个运算符,然后根据运算符使用switch语句调用相应的函数进行计算,并输出结果。如果用户输入的运算符无效,程序会输出错误信息并终止;如果用户尝试除以零,除法函数会输出错误提示并返回 0 。通过这个简单的计算器案例,可以看到函数是如何将复杂的计算任务分解为独立的模块,使代码结构更加清晰,易于理解和维护 。