局部对象
在c++语言中,名字有作用域,对象有声明周期(lifetime):
- 名字的作用域是程序文本的一部分,名字在其中可见。
- 对象的生命周期是程序执行过程中该对象存在的一段时间。
如我们所知,函数体是一个语句块。块构成一个新的作用域,我们可以在其中定义变量。形参和函数体内部定义的变量统称为局部变量(local variable)。他们对函数而言是局部的,仅在函数的作用域内可见,同时局部变量还会隐藏(hide)在外层作用域中同名的其他所有生命中。意思是,函数体内定义的变量和函数体外定义的变量,即使是同名,但在函数运行的时候,只对函数体内的变量操作(除非将函数体外同名变量作实参传入),请看以下实例代码:
void Test()
{
int Name1 = 2;
cout << Name1;
}
void main()
{
int Name1 = 1;
Test();//结果是2
system("pause");
}
上面讲的是在函数内定义的局部变量,只存在于函数的执行过程中;而在函数体之外定义的局部对象存在于整个程序的执行过程中。此类对象在程序启动时被创建,直到程序结束时才会销毁。
因此,局部对象的生命周期依赖于定义的方式。而局部对象又具体分为自动对象和局部静态对象
自动对象
对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为自动对象(automatic object)。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。
形参是一种自动对象。函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁。
静态局部对象
某些时候,有必要另局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。**局部静态对象(local static object)**在程序执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。意思是,如果执行完这个函数,下次再执行但注意,即使在函数体内声明了一个与函数体外同名的静态局部变量,函数执行完也不会对函数体外的变量改变,请看以下代码:
void Test()
{
static int Name1 = 2;
static int Name2 = 3;
cout << Name1;
}
void main()
{
int Name1 = 1;
Test();//结果是2
cout << Name1 << endl;//结果是1
cout<<Name2;//报错
system("pause");
}
请再看以下代码:
size_t count_calls()
{
static size_t ctr = 0;
return ++ctr;
}
size_t count_calls2()
{
size_t ctr = 0;
return ++ctr;
}
void main()
{
for (size_t i = 0;i < 10;i++)
{
cout << count_calls() << endl;
}//结果是1-10的数
for (size_t i = 0;i < 10;i++)
{
cout << count_calls2() << endl;
}//结果是111111(10个)
system("pause");
}
在控制流第一次经过count_calls的ctr的定义之前,ctr被创建并初始化为0。每次调用使得ctr+1。每次执行count_calls时,变量ctr的值都已经存在并且等于函数上一次退出时ctr的值,因此第二次调用时ctr的值是1,第三次是2,以此类推。
或许有人会有这样的理解:每次执行count_calls()时都对ctr重新初始化一次为0。这个想法看上去合理,但恰恰相反。诸君应该了解,在c++中,是禁止对一个变量进行两次初始化的,可以这样理解:调用count_calls()时,编译器发现ctr是静态变量,首先先去找了它是否被初始化,如果被初始化,则直接取出值,否则才执行初始化语句。但普通变量编译器则会直接再做一次初始化,这就是调用count_calls2()会是10个1的原因。
函数声明
和其他名字一样,函数的名字也必须在使用之前声明。类似于变量,函数只能定义一次,但可以多次声明,如果一个函数永远也不会被用到,那么它可以只有声明没有定义。
函数的生命和函数的定义非常类似,唯一的区别是函数声明无须函数体,用一个分号代替即可。
因为函数的生命不包含函数体,所以也就无须形参的名字(但最好加上,便于阅读)。声明方法请看以下代码:
void print(vector<int>::const_iterator beg,vector<int>::const_iteratir end);
函数的三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也被称为函数原型。
在头文件中进行函数声明
定义函数的源文件需要把含有函数声明的头文件包含进来,编译器负责验证
const形参和实参
和其他初始化一样,当用实参初始化形参时会忽略掉顶层const。也就是说,当形参有顶层const时,传给它常量对象或非常量对象都是可以的:
void fcn(const int i){/*fcn能读取i,但是不能向i写值*/}
调用fcn时,既可以传入const int 也可以传入int。但是忽略形参的顶层const会带来意想不到的结果:
void fcn(const int i){/*fcn能读取i,但是不能向i写值*/}
void fcn(int i){/*错误,重复定义了fcn(int i)*/}
在c++中,允许我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表有明显的区别。因为顶层const被忽视,所以以上两个函数本质上是相同的。