c++函数

160 阅读8分钟

上一篇:C++变量

下一篇:C++头文件

1. 概述

函数到底是什么?函数就是我们写的代码块,被设计用来执行特定的任务。以后,我们学习class类的时候,这些块被称为方法。但是当我们说到函数时,我明确说的是不属于C++类里面的东西。对我们来说,使用函数是很常见的,它可以避免代码重复。我们不想重复写相同的代码。

当然,我们也可以通过复制粘贴很多代码,但这会导致巨大的混乱。这也意味着如果我们决定改变一些代码,那所有这些地方复制粘贴的地方都需要改变,那维护这些代码将会很痛苦。

所以我们需要把我们要做的事情写成一个函数,如果需要的话,我们可以多次调用它。你可以认为函数有一个输入,一个输出。尽管它们并不需要这么做。我们可以为该函数提供一定的参数,函数可以为我们返回值。

2. 案例

1. 准备项目

回到案例

现有一个这样的项目,项目中只有一个Main.cpp文件,该文件有一个main函数,如下

image.png

2. 开始案例

1. 定义函数

假设我们想写出两个数相乘的函数。首先,我们要写的便是所谓的返回值,因为是两个整数相乘,所以返回值会得到一个整数,所以返回值类型写int。然后我们给函数命名为multiply,它有两个参数,int a和int b,也就是我们要相乘的数。然后给出函数体返回 a * b,如下

image.png

我们不一定要提供参数,例如,我们直接返回5 * 8;,这仍然是一个返回整数的函数。但是它没有任何参数。

image.png

我们也可以让函数不返回任何东西,我们通过void作为返回类型来实现。修改函数将5 * 8的结果打印到控制台。

image.png

让我们回到最初的两整数相乘的例子

image.png

2. 函数调用

那么如何调用这个multiply函数呢?调用一个函数非常简单,首先我们需要一个定义一个变量result用来存储我们multiply函数的计算结果。如下,就是调用了multiply函数,并传3和2两个参数。并将结果打印出来。

image.png

F5运行程序

image.png

在它编译之后,可以看到控制台打印6,也就是3 * 2的结果。

3. 复制粘贴代码问题

让我们把情况说的更详细些,假设我要做一堆的乘法。我想把他们都记录到控制台。如果没有函数的情况下,这样会看起来很乱。让我们来修改代码,将一段代码复制粘贴多次,修改传参的参数,如下

image.png

F5运行程序

image.png

看到都是相同的值6,为什么到处都是得到相同的值6。

仔细看代码,发现,当我们复制粘贴这段代码的时候,我们忘记改变被打印变量了。

image.png

你以为我是犯了错误,其实我是故意的。因为这种情况在实际编码中经常发生。

人们复制和粘贴代码块,然后忘记改变一些小的细节,在某些情况下,如果你的程序正常运行,你甚至都有可能注意不到这种细节。直到应用程序因为这块代码导致某个地方出错了,你才会注意到。

像例子中这样的情况很容易修正。只需要修改对应的变量即可,如下

image.png

F5运行程序

image.png

可以看到,得到了正确的结果。

4. 重复代码抽取成函数

然而,可以看到,我们有多次调用一段代码块,它只是有一点点的不同,一直复制粘贴,这很不好。std::cout << result << std::endl;只是用来打印记录,那么我们可以为这些重复出现的代码片段创建一个函数叫做MultiplyAndLog,然后我们来看看需要什么参数,从上面的相似的三个代码块中找到有什么不同的位置,可以看到是参数的不同,所以创建MultiplyAndLog函数如下。

void MultiplyAndLog(int a, int b)
{
	int result = multiply(a, b);
	std::cout << result << std::endl;
}

image.png

所以可以通过调用MultiplyAndLog函数的方式去掉那块复制粘贴的代码片段,修改如下,最后得到的是一个干净漂亮易于阅读的程序。

image.png

F5运行程序

image.png

可以看到结果是对的,规避了因复制粘贴而容易出现毛病的问题,也使代码更易于阅读。

5. 过于抽取代码封装成函数

上述例子,很有效的证明了函数是真的很重要。你的目标应该是将你的代码拆分成很多函数,然而,事无绝对,也不要把你的代码每一行都拆成函数,这样对任何人没有好处的代码,会导致代码很难维护,且会让你的程序变慢。

每次我们调用函数时(用 * 表示强调),编译器会生成一个call指令。这基本上意味着,在一个运行的程序中,为了调用一个函数,我们需要创建一个堆栈结构,这意味着我们必须把像参数这样的东西推入堆栈。我们还需要将一个叫做返回地址的东西,压入堆栈。然后,我们要做的是跳到二进制执行文件的不同部分,以便开始执行我们的函数指令。为了将推入(push)进去的结果返回,然后我们得回去到最初调用函数之前。就这样就像在内存中跳跃来执行函数。跳跃和执行这些都需要时间,所以他会减慢我们的程序。前面强调了*,之前是因为这都是编译器假设的,决定保持我们的函数作为一个实际的函数,并不做内联inline工作(后续章节中会继续深入讨论inline)。之所以说这些是因为不想你们对于每一行代码都去创建函数。这需要一定编程经验,哪里需要一个函数。但基本上如果你看到自己在做一项共同的任务,而且多次,那么这个时候就要想到应该为此创建一个函数了。函数的主要目的是防止代码重复。我们不希望到处复制粘贴代码。

6. 特殊的mian函数

现在回到我们的代码

你可能已经注意到了一些奇怪的事情,在main函数中,它的定义表示返回一个int类型的值,但是,可以看到main函数的函数体并没有return语句,显然我们没有返回任何东西。

image.png

所以,我们来看看一个返回值为int的函数真的可以不用返回值吗?让我们在multiply函数上试试,我们去掉return语句,如下

image.png

单文件编译Main.cpp文件。

image.png

可以看到编译失败了,我们得到了一个错误,“multiply”: 必须返回一个值,有返回类型的函数需要返回值。

主函数main实际上一个特殊的函数,可以不返回值,因为它会在你没有返回值时自动为你返回0,这是现代C和C++的一个特性。可以让你的代码保持的干净一点。其他指定了返回值的函数必须要有返回值

好了,我们已经知道multiple函数必须要返回一个值。但是刚才讲的东西实际上只适用于debug调试模式,如果我们在release模式下编译。

image.png

单文件编译ctrl + F7

image.png

可以看到编译通过了,我们没有得到错误。

这并不是说我们现在做的是正确的,因为如果我们真的要拿multiple函数的这个返回值做某些事情的话,我们会得到未定义的行为这样的错误信息。

只是release模式编译器不会给你这个“multiply”: 必须返回一个值,但是,在debug调试模式下,编译器就会出问题,会给你这个“multiply”: 必须返回一个值这个错误,这对我们很有帮助,因为在任何时候都不应该写出一个应该返回某个值的但实际没有返回值的函数,当然main函数除外。

函数真的很有用,每个程序都是由一系列函数组成的。我们通常还将函数分解为声明和定义,声明通常存储在头文件中,我们在翻译单元或cpp文件中编写函数定义。头文件的内容后续会继续更新,在头文件中,函数如何声明等等。

上一篇:C++变量

下一篇:C++头文件