在简单的程序中所有代码都包含在main函数中,但是当程序的代码量多了以后,main函数内容会非常长,这时候就需要用到一些函数来解决这个问题。函数提供了一种划分和组织程序逻辑的方法,方便我们将程序内容拆分成实现不同功能的逻辑块。
函数可以接收参数,并返回一个值。
一、函数的作用
一个使用函数的例子:
#include <iostream>
using namespace std;
const double Pi = 3.14159265;
// Function Declarations(Prototypes)
double Area(double radious);
double Circumference(double radius);
int main()
{
cout << "Enter radius: ";
double radius = 0;
cin >> radius;
// Call function "Area"
cout << "Area is: " << Area(radius) << endl;
// Call function "Circumference"
cout << "Circumference is: " << Circumference(radius) << endl;
return 0;
}
// Function definitions (implementations)
double Area(double radius)
{
return Pi * radius * radius;
}
double Circumference(double radius)
{
return 2 * Pi * radius;
}
输出:
Enter radius: 6.5
Area is: 132.732
Circumference is: 40.8407
1、函数的声明
函数的声明如下所示:
如果函数不需要返回任何值,那么函数类型可以表示为void。
2、函数定义
函数的定义部分也就是函数实际上实现的内容。如上面代码所示:
double Area(double radius)
{
return Pi * radius * radius;
}
函数定义由语句块组成,包含函数实际功能以及返回值,当函数类型为void时没有返回值。
3、函数调用和参数
函数的调用需要指明函数名,以及函数的参数。
cout << "Area is: " << Area(radius) << endl;
4、多参数函数
代码示例:
#include <iostream>
using namespace std;
const double Pi = 3.14159265;
// Declaration contains two parameters
double SurfaceArea(double radius, double height);
int main()
{
cout << "Enter the radius of the cylinder: ";
double radius = 0;
cin >> radius;
cout << "Enter the height of the cylinder: ";
double height = 0;
cin >> height;
cout << "Surface area: " << SurfaceArea(radius, height) << endl;
return 0;
}
double SurfaceArea(double radius, double height)
{
double area = 2 * Pi * radius * radius + 2 * Pi * radius * height;
return area;
}
输出:
Enter the radius of the cylinder: 3
Enter the height of the cylinder: 6.5
Surface area: 179.071
需要注意的是,函数参数类似于局部变量,仅在函数范围内有效。
5、无参数或无返回值的函数
代码示例:
#include <iostream>
using namespace std;
const double Pi = 3.14159265;
void SayHello();
int main()
{
SayHello();
return 0;
}
void SayHello()
{
cout << "Hello World" << endl;
}
输出:
Hello World
6、具有默认值的函数参数
代码示例:
#include <iostream>
using namespace std;
// Function Declarations(Prototypes)
double Area(double radius, double pi = 3.14);
int main()
{
cout << "Enter radius: ";
double radius = 0;
cin >> radius;
cout << "pi is 3.14, do you wish to change this (y/n)? ";
char changePi = 'n';
cin >> changePi;
double circleArea = 0;
if (changePi == 'y')
{
cout << "Enter new pi: ";
double newPi = 3.14;
cin >> newPi;
circleArea = Area(radius, newPi);
}
else
circleArea = Area(radius); // Ignore 2nd param, use default value
// Call function "Area"
cout << "Area is: " << circleArea << endl;
return 0;
}
// Function definitions(implementions)
double Area(double radius, double pi)
{
return pi * radius * radius;
}
输出:
Enter radius: 1
pi is 3.14, do you wish to change this (y/n)? n
Area is: 3.14
/
Enter radius: 1
pi is 3.14, do you wish to change this (y/n)? y
Enter new pi: 3.1416
Area is: 3.1416
需要注意的是,具有默认值的参数一般放在参数列表尾端。
7、递归
在某些情况下,函数可以在内部调用自己,这样的函数称为递归函数。需要注意的是,递归函数应该具有明确定义的退出条件,它返回时不会再次调用自身。在没有退出条件的情况下或者在出现错误的情况下,程序执行会停留在不停调用自身的递归函数中,并且当“堆栈溢出”时导致应用程序崩溃,最终会停止。
下列程序示例使用递归函数计算斐波那契数列:
#include <iostream>
using namespace std;
int GetFibNumber(int fibIndex)
{
if (fibIndex < 2)
return fibIndex;
else // recursion if fibIndex >= 2
return GetFibNumber(fibIndex - 1) + GetFibNumber(fibIndex - 2);
}
int main()
{
cout << "Enter 0-based index of desired Fibonacci Number: ";
int index = 0;
cin >> index;
cout << "Fibnacci number is : " << GetFibNumber(index) << endl;
return 0;
}
输出:
Enter 0-based index of desired Fibonacci Number: 6
Fibnacci number is : 8
上述代码中,函数GetFibNumber()是递归函数,它调用自身,退出条件是如果fibIndex小于2,函数将返回而不递归。因此GetFibNumber()以递减的方式调用自身,并且不断减少fibIndex的值。它最终到达满足退出条件的状态,递归结束,确定斐波那契值并返回到main()。
8、具有多个返回语句的函数
不止可以在函数末尾返回语句,也可以在函数中的任何点返回,如果需要,也可以多次返回。示例代码如下所示:
#include <iostream>
using namespace std;
const double Pi = 3.14159265;
void QueryAndCalculate()
{
cout << "Enter radius: ";
double radius = 0;
cin >> radius;
cout << "Area: " << Pi * radius * radius << endl;
cout << "Do you wish to calculate circumference(y/n)?";
char calcCircum = 'n';
cin >> calcCircum;
if (calcCircum == 'n')
return;
cout << "Circumference: " << 2 * Pi * radius << endl;
return;
}
int main()
{
QueryAndCalculate();
return 0;
}
输出:
Enter radius: 1
Area: 3.14159
D0 you wish to calculate circumference(y/n)?n
/
Enter radius: 1
Area: 3.14159
D0 you wish to calculate circumference(y/n)?y
Circumference: 6.28319
二、使用函数处理不同形式的数据
在调用函数的时候,也可以将数组传递给函数。可以创建两个具有相同名称和返回值但不同参数的函数。可以写一个函数,使其参数不会在函数调用中创建和销毁,相反,即使在函数退出后也使用有效的引用,以便允许我们在函数调用中操作更多数据或参数。
在本节中,我们将学习如何将数组传递给函数、函数重载和通过引用函数传递参数。
1、重载函数
具有相同名称和返回类型,但具有不同参数的函数被称为重载函数。重载函数在应用程序中非常有用,假设现在需要编写一个计算圆和圆柱面积的程序。计算圆面积的函数需要一个参数:半径。计算圆柱面积的另一个函数除了圆柱体的半径外还需要圆柱体的高度。这两个函数都需要返回表示面积的相同类型的数据。因此,C ++允许定义两个重载函数,两个都称为Area,两者都返回double,但是一个只将radius作为输入,另一个将height和radius作为输入参数,代码示例如下所示:
#include <iostream>
using namespace std;
const double Pi = 3.14159265;
double Area(double radius); // for circle
double Area(double radius, double height); // for cylinder
int main()
{
cout << "Enter z for Cylinder, c for Circle: ";
char userSelection = 'z';
cin >> userSelection;
cout << "Enter radius: ";
double radius = 0;
cin >> radius;
if (userSelection == 'z')
{
cout << "Enter height: ";
double height = 0;
cin >> height;
// Invoke overloaded variant of Area for Cyclinder
cout << "Area of cylinder is: " << Area(radius, height) << endl;
}
else
cout << "Area of cylinder is: " << Area(radius) << endl;
return 0;
}
// for circle
double Area(double radius)
{
return Pi * radius * radius;
}
// overloaded for cylinder
double Area(double radius, double height)
{
// reuse the area of circle
return 2 * Area(radius) + 2 * Pi * radius * height;
}
输出:
Enter z for Cylinder, c for Circle: z
Enter radius: 2
Enter height: 5
Area of cylinder is: 87.9646
/
Enter z for Cylinder, c for Circle: c
Enter radius: 1
Area of cylinder is: 3.14159
2、将数组传递给函数
代码示例:
#include <iostream>
using namespace std;
void DisplayArray(int numbers[], int length)
{
for (int index = 0; index < length; ++index)
cout << numbers[index] << " ";
cout << endl;
}
void DisplayArray(char characters[], int length)
{
for (int index = 0; index < length; ++index)
cout << characters[index] << " ";
cout << endl;
}
int main()
{
int myNums[4] = { 24, 58, -1, 245 };
DisplayArray(myNums, 4);
char myStatement[7] = { 'H', 'e', 'l', 'l', 'o', '!', '\0' };
DisplayArray(myStatement, 7);
return 0;
}
输出:
24 58 -1 245
H e l l o !
3、通过引用传递参数
前面调用函数时,传入的参数都是从函数外部的变量复制过去的,参数值在函数内部的改变不影响函数外部原变量的值的大小。
如果需要在函数内部更改函数外部变量的值,可以通过引用的方法传递参数。代码示例如下:
#include <iostream>
using namespace std;
const double Pi = 3.1416;
// output parameter result by reference
void Area(double radius, double& result)
{
result = Pi * radius * radius;
}
int main()
{
cout << "Enter radius: ";
double radius = 0;
cin >> radius;
double areaFetched = 0;
Area(radius, areaFetched);
cout << "The area is: " << areaFetched << endl;
return 0;
}
输出:
Enter radius: 2
The area is: 12.5664
函数只能使用return语句返回一个值,如果函数需要改变多个值,通过引用的方式传递参数是一种解决方式。
三、微处理器(Microprocessor)如何处理函数调用
函数调用时,微处理器跳转到在被调用函数的下一条指令。执行完函数中的指令后,它将返回到它停止的位置。要实现此逻辑,编译器会将函数调用转换为微处理器的CALL指令,该指令中带有下一条指令的地址,当微处理器执行CALL指令时,它会在函数调用堆栈之后保存要执行的指令的位置,并跳转到CALL指令中包含的内存位置。
关于堆栈:
堆栈是一个Last-In-First-Out的存储器结构,非常像一堆板块,可以在顶部取板块。将数据放入堆栈称为push操作,从堆栈中获取数据称为pop操作。随着堆栈向上增长,堆栈指针总是随着它的增长而递增,始终指向堆栈的顶部。
堆栈的性质使其成为处理函数调用的最佳选择。调用函数时,所有局部变量都在堆栈上实例化,即push操作。当函数结束时,做pop操作,堆栈指针返回原来的位置。
1、inline函数
常规函数调用被转换为CALL指令,这导致堆栈操作和微处理器执行转移到函数。对于大多数情况来说,这一过程是很快的。但是对于如下比较简单的情况,这种操作可能显得相对比较复杂。
double GetPi()
{
return 3.14159;
}
对此执行实际函数调用的开销可能对于实际执行GetPi()所花费的时间量而言非常高。 这就是C ++编译器使程序员能够将这些函数声明为内联函数的原因。关键字inline表示要求这些函数在被调用的位置内联扩展。
inline double GetPi()
{
return 3.14159;
}
执行简单操作(如加倍数)的函数也是内联的良好候选者。代码示例如下所示:
#include <iostream>
using namespace std;
// define an inline function that doubles
inline long DoubleNum(int inputNum)
{
return inputNum * 2;
}
int main()
{
cout << "Enter an integer: ";
int inputNum = 0;
cin >> inputNum;
// Call inline function
cout << "Double is: " << DoubleNum(inputNum) << endl;
return 0;
}
输出:
Enter an integer: 3
Double is: 6
关键字inline的作用是告诉编译器,直接将DoubleNum()函数的内容放在调用该函数的地方。
将函数表示为inline的形式也可能导致大量代码膨胀,尤其是在内联函数执行大量复杂处理时。使用inline关键字应该仅为那些代码很少并且需要以最小开销执行它的函数保留。
2、自动推导函数返回类型
第3章中了解了关键字auto,它允许根据分配给变量的初始化值将变量类型推导留给编译器,从而避免声明变量类型。 从C ++ 14开始,这同样适用于函数。我们可以使用auto而不是指定具体的返回类型,让编译器根据返回值推导出返回类型。
代码示例:
#include <iostream>
using namespace std;
const double Pi = 3.14159265;
auto Area(double radius)
{
return Pi * radius * radius;
}
int main()
{
cout << "Enter radius: ";
double radius = 0;
cin >> radius;
// Call function "Area"
cout << "Area is: " << Area(radius) << endl;
return 0;
}
输出:
Enter radius: 2
Area is: 12.5664
3、Lambda函数
Lambda函数的作用是比较两个函数的返回值,如果一个小于另一个则返回true,否则返回false。
代码示例:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
void DisplayNums(vector<int>& dynArray)
{
for_each(dynArray.begin(), dynArray.end(), [](int Element) {cout << Element << " "; });
cout << endl;
}
int main()
{
vector<int> myNums;
myNums.push_back(501);
myNums.push_back(-1);
myNums.push_back(25);
myNums.push_back(-35);
DisplayNums(myNums);
cout << "Sorting them in descending order" << endl;
sort(myNums.begin(), myNums.end(), [](int Num1, int Num2) {return (Num1 < Num2); });
DisplayNums(myNums);
return 0;
}
输出:
501 -1 25 -35
Sorting them in descending order
-35 -1 25 501
上述代码中,函数DisplayNums()中使用标准库函数for_each来迭代数组中的每个数,并将其输出到控制台,在函数中使用了一个lambda函数实现该功能。在第24行中,在sort函数中使用lambda函数对数组排序。
lambda函数的形式如下所示:
[optional parameters](parameter list){ statements; }
总结
本章讲了使用函数进行模块化编程的基础知识,了解了函数如何帮助我们更好地构建代码,并帮助我们重用编写的算法。了解到函数可以获取参数和返回值,参数可以具有默认值(这些默认值也可以在调用时重新指定),参数也可以通过引用传递参数(这种情况下在函数中修改参数值的同时也会修改掉参数原来的值)。学习了如何传递数组给函数,并且还学习了如何编写具有相同名称和返回类型但不同参数列表的重载函数。
最后大致学习了inline函数、自动推导函数返回值类型和lambda函数的相关内容。