6.1 函数基础


一个典型的函数 定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的 列表以及函数体。其中,形参以逗号隔开,形参的列表位于一对圆括号之内。函数执行的操作在语句块中说明,该语句块称为函数体。

我们通过调用运算符来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针:圆括号之内是一个用逗号隔开的实参列表,我们用实参初始化函数的形参。调用表达式的类型就是函数的返回类型。

这边给个案例下文进行分析:(计算阶乘)

#include<iostream>

using namespace std;

int function(int val)
{
	if (val == 1 || val == 0)
		return 1;
	else
		return val * function(val - 1);
}

int main()
{
	int num = function(5);
	cout << num;
	return 0;
}

编写函数

对于编写的函数也就是上文的:

int function(int val)
{
	if (val == 1 || val == 0)
		return 1;
	else
		return val * function(val - 1);
}

这个函数说明了:这个函数的名字是function,它作用于一个整型的参数,返回的值也是一个整型的数据,return语句负责结束function并且返回相对应的值。

调用函数

对于上述的函数要调用这个函数就必须传入一个整型值。并且它是有一个返回值的,所以也需要一个东西来承接住,文章中用

int num = function(5);

来进行调用函数。

函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数。此时,主调函数(main)的执行被暂时中断,被调函数(function)开始执行。

执行函数的第一步是(隐式地)定义并初始化它的形参。因此,当调用function函数时,首先创建一个名为val的int变量,然后将它初始化为调用时所用的实参5。

当遇到一条return语句时函数结束执行过程。和函数调用一样, return语句也完成两项工作:一是返回return语句中的值(如果有的话),二是将控制权从被调函数转移回主调函数。函数的返回值用于初始化调用表达式的结果,之后继续完成调用所在的表达式的剩余部分。

形参和实参

实参是形参的初始值。第一个实参初始化为第一个形参,第二个实参初始化第二个形参,以此类推。尽管实参与形参存在对应关系,但是并没有规定实参的求值顺序。编译器能以任意可行的顺序对实参求值。

实参的类型必须与对应的形参类型匹配,这一点与之前的规则是一致的,我们知道在初始化过程中初始值的类型也必须与初始化对象的类型匹配。函数有几个形参,我们就必须提供相同数量的实参(缺省函数除外)。因为函数的调用规定实参数量应与形参数量一致,所以形参一定会被初始化。

但是如果上文我们的函数调用使用:

int num = function(3,14);

这样也是对的,对于调用函数的时候,函数先去寻找有没有function(double)的函数,发现没有,就将double强转去寻找相类似的函数,发现找到了function(int)所以实际上这句话调用的是function(3),也就是进行了强行转化。

函数的形参列表

函数的形参列表可以为空,但是不能省略。要想定义一个不带形参的函数,最常用的办法是书写一个空的形参列表。不过为了与C语言兼容,也可以使用关键字void表示函数没有形参。

形参名是可选的,但是由于我们无法使用未命名的形参,所以形参一般 都应该有个名字。偶尔,==函数确实有个别形参不会被用到,则此类形参通常不命名以表示在函数体内不会使用它。==不管怎样,是否设置未命名的形参并不影响调用时提供的实参数量。即使某个形参不被函数使用,也必须为它提供一个实参。

函数的返回类型

大多数类型都能用作函数的返回类型。一种特殊的返回类型是void,它表示函数不返回任何值。函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。我们将在后文介绍如何定义一种特殊的函数,它的返回值是数组的指针(或引用),在之后将介绍如何返回指向函数的指针

局部对象

在C++语言中,名字有作用域,对象有生命周期(lifetime)。

  1. 名字的作用域是程序文本的一部分,名字在其中可见。
  2. 对象的生命周期是程序执行过程中该对象存在的一段时间。

如我们所知,函数体是一个语句块。块构成一个新的作用域,我们可以在其中定义变量。形参和函数体内部定义的变量统称为局部变量。它们对函数而言是“局部”的,仅在函数的作用域内可见,同时局部变量还会隐藏在外层作用域中同名的其他所有声明中。

在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,直到程序结束才会销毁。

局部变量的生命周期依赖于定义的方式。

自动对象

对于普通局部变量对应的对象来说,形参是一种自动对象。函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁。

我们用传递给函数的实参初始化形参对应的自动对象。对于局部变量对应的自动对象来说,则分为两种情况:如果变量定义本身含有初始值,就用这个初始值进行初始化;否则,如果变量定义本身不含初始值,执行默认初始化(也可能内部没有任何的值)。

局部静态对象

某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。

样例:

#include<iostream>

using namespace std;

int function()
{
	static int i = 0;
	return i++;
}

int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		cout << function()<<endl;
	}
	return 0;
}

输出结果:
在这里插入图片描述
在控制流第一次经过 i的定义之前,i被创建并初始化为0, 每次调用将i加1并返回新值。但是仅仅对于static来说,他仅仅只会定义一次,后续再次使用就相当于跳过此句话。

如果局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0(和放在所有函数之外的变量一样)。

最后稍微总结一下:

  1. 形参:函数括号内的数据。
  2. 局部变量:语句块{}内部定义的变量。
  3. 局部静态变量:使用static定义的局部变量。

函数声明

和其他名字一样,函数的名字也必须在使用之前声明。类似于变量,函数只能定义一次,但可以声明多次。唯一的例外是虚函数,如果一个函数永远也不会被我们用到,那么它可以只有声明没有定义。

函数的声明一般用在函数的定义在你所要写的位置之后,所有需要有声明,其实完全可以将声明提到最开始的时候进行书写,这样子就不需要函数的声明了,声明的语法实际上就是去掉{程序块}的函数加个;

比如:

int function();这就是一个函数的声明

样例:

#include<iostream>

using namespace std;


int main()
{
	int function();//函数的声明
	for (size_t i = 0; i < 5; i++)
	{
		cout << function()<<endl;
	}
	return 0;
}

int function()//函数的定义
{
	static int i = 0;
	return i++;
}

函数需要声明的情况是:

  1. 函数是外部定义的, 即调用者看不到函数的实现时. 比如函数A在a.cpp中实现的, 函数B在b.cpp中实现的. 函数B要调用A,则需要先声明, 同样, A调用B也需要先声明.(对应的是分离式编译)
  2. 调用处, 函数还未实现. 比如函数A在前面. 函数B在后面. 这时A调用B就需要先声明. 反过来B调用A的话, 因为A在B的前面已经实现, 所以就可以不用声明了.(对应的是上述例子)

在头文件中进行函数的声明

如果有需要的声明,可以在头文件中进行声明。

或者说为了完成分离式编程,你在可以写一个全是函数声明的头文件来避免重复。

分离式编译

随着程序越来越复杂,我们希望把程序的各个部分分别存储在不同文件中。为了允许编写程序时按照逻辑关系将其划分开来,C++语言支持所谓的分离式编译。分离式编译允许我们把程序分割到几个文件中去,每个文件独立编译。

编译和链接多个源文件

举个例子,假设fact函数的定义位于一个名为fact.h的文件中,它的声明位于名为Chapter6.h的头文件中。显然与其他所有用到fact函数的文件一样,fact.cc应该包含Chapter6.h头文件。另外,我们在名为factMain.cc 的文件中创建main函数,main函数将调用fact函数。要生成可执行文件,必须告诉编译器我们用到的代码在哪里。这边不详细介绍,相关的可以自行去了解。这源于每个编译器的底层。

这边以一个例子进行辅助理解:

Test.h:

//Test.h
#pragma once

#include<iostream>
using namespace std;
#include<stdlib.h>

void fun();           // 仅仅只声明//1

Test.cpp:

//Test.cpp
#include"Test.h"

void fun()                 // 对函数fun()进行定义
{
    cout << "fun" << endl;
}

main.cpp:

//main.cpp
#include"Test.h"

int main()
{
    fun();              //调用fun()函数
    return 0;
}

我们可以把1注释掉,可以简单的发现这个声明是必须的,因为这个声明表示了fun函数是一个外部函数(fun函数定义在Test.cpp中,而且main.cpp完全没有它的定义)。实际上这个过程就像extern int i;一样,声明i是外部来的变量,从别的cpp中拿值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值