头文件中函数声明、默认实参与局部对象

正如变量必须先声明后使用一样,函数也必须在被调用之前先声明。与变量的定义类似,函数的声明也可以和函数的定义分离;一个函数只能定义一次,但是可声明多次。

函数声明由函数返回类型、函数名和形参列表组成。形参列表必须包括形参类型,但是不必对形参命名。这三个元素被称为函数原型, 函数原型描述了函数的接口。

函数声明中的形参名会被忽略,如果在声明中给出了形参的名字,它应该用作辅助文档:

void print(int *array, int size);

在头文件中提供函数声明

函数应当在头文件中声明,并在源文件中定义。

把函数声明放在头文件中,这样可以确保对于指定函数其所有声明保持一致。如果函数接口发生变化,则只要修改其唯一的声明即可。

定义函数的源文件应包含声明该函数的头文件。

将提供函数声明头文件包含在定义该函数的源文件中, 可使编译器能检查该函数的定义和声明时是否一致。特别地,如果函数定义和函数声明的形参列表一致,但返回类型不一致,编译器会发出警告或出错信息来指出这种差异。

默认实参

默认实参是一种虽然并不普遍、但在多数情况下仍然适用的实参值。调用函数时,可以省略有默认值的实参。编译器会为我们省略的实参提供默认值。

默认实参是通过给形参表中的形参提供明确的初始值来指定的。可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。

string screenInit(string::size_type height = 24,
string::size_type width = 80,
char background = ' ' );

调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供了实参,则它将覆盖默认的实参值;否则,函数将使用默认实参值。下面的函数screenInit 的调用都是正确的:

string screen;
screen = screenInit(); // equivalent to screenInit (24,80,'')
screen = screenInit(66); // equivalent to screenInit (66,80,'')
screen = screenInit(66, 256); // screenInit(66,256,' ')
screen = screenInit(66, 256, '#');

函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参。

例如,如果要给 background 提供实参,那么也必须给 height 和 width 提供实参:

screen = screenInit(, , '?'); // error, can omit only trailing arguments
screen = screenInit( '?'); // calls screenInit('?',80,' ')

设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。

默认实参的初始化式

默认实参可以是任何适当类型的表达式:

string::size_type screenHeight();
string::size_type screenWidth(string::size_type);
char screenDefault(char = ' ');
string screenInit(
string::size_type height = screenHeight(),
string::size_type width = screenWidth(screenHeight()),
char background = screenDefault());

如果默认实参是一个表达式,而且默认值用作实参,则在调用函数时求解该表达式。

指定默认实参的约束

既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。下面的例子是错误的:

// ff.h
int ff(int = 0);
// ff.cc
#include "ff.h"
int ff(int i = 0) { /* ... */ } // error

通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。

如果在函数定义的形参表中提供默认实参, 那么只有在包含该函数定义的源文件中调用该函数时,默认实参才是有效的。

局部对象

在 C++ 语言中,每个名字都有作用域,而每个对象都有生命期。

名字的作用域指的是知道该名字的程序文本区。对象的生命期则是在程序执行过程中对象存在的时间。

在函数中定义的形参和变量的名字只位于函数的作用域中:这些名字只在函数体中可见。通常,变量名从声明或定义的地方开始到包围它的作用域结束处都是可用的。

自动对象

默认情况下,局部变量的生命期局限于所在函数的每次执行期间。只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。

局部变量所对应的自动对象在函数控制经过变量定义语句时创建。如果在定义时提供了初始化式,那么每次创建对象时,对象都会被赋予指定的初值。对于未初始化的内置类型局部变量,其初值不确定。当函数调用结束时,自动对象就会撤销。

形参也是自动对象。形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。

自动对象,包括形参,都在定义它们的块语句结束时撤销。形参在函数块中定义, 因此当函数的执行结束时撤销。 当函数结束时, 会释放它的局部存储空间。在函数结束后,自动对象和形参的值都不能再访问了。

静态局部对象

一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为 static(静态的)。

static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。

size_t count_calls()
{
static size_t ctr = 0; // value will persist across calls
return ++ctr;
}

int main()
{
for (size_t i = 0; i != 10; ++i)
cout << count_calls() << endl;
return 0;
}

这个程序会依次输出 1 到 10(包含 10)的整数。 

posted @ 2018-05-02 13:54  刘-皇叔  阅读(1083)  评论(0编辑  收藏  举报