函数
一个典型的函数包含以下几个部分
- 返回类型
- 函数名称
- 一个或者多个形参
- 函数体
举例如下
int fact(int ival)
{int ret=1;while(val>1)ret*=val--;//这里先乘再减return ret;
}
函数名 | fact |
---|---|
形参 | ival |
实参 | ret |
返回类型 | int |
每当我们定义出一个函数,我们不能直接使用它,还得提供一个数值去调用它
int main()
{int j=fact(5);cout<<"5! is "<<j<<endl;return 0;
}
执行函数首先隐式初始化,首先创建为一个叫ival的int类型变量,然后将它初始化为调用时所用到的形式参数
对于基元类型,复制初始化与直接初始化完全相同。对于具有构造函数的更复杂类型,除非复制构造函数被标记为显式,否则复制初始化与调用值在=”之后的复制构造函数相同。我们一般可以认为拷贝初始化就是直接初始化。
形参与实参
实参是形参的初始值。正如上文所言,形参最开始是没有值的,只是一个符号,在初始化开始之后,形参依次被其对应的实参初始化,但是编译器却并没有这种对应关系,无需按照顺序,可以直接求值。
函数的形参列表
形参列表可以为空,但不可以省略:
void f1();//隐式定义
void f2(void)//显式定义
为空时用void表示函数形参列表为空,是为了与C语言兼容,可以表示多个形参,中间使用逗号隔开,就算两个形参的类型一样,也得把两个类型都写出来:
int f3(int v1,int v2)
函数的返回类型
大多数函数的返回类型都是各种类型值,也有少部分是返回void,或者返回函数或者数组的指针
关于局部对象
在C++中,名字有作用域,对象有生命周期
作用域:作用域(scope,或译作有效范围)是名字(name)与实体(entity)的绑定(binding)保持有效的那部分计算机程序。不同的编程语言可能有不同的作用域和名字解析。而同一语言内也可能存在多种作用域,随实体的类型变化而不同。作用域类别影响变量的绑定方式,根据语言使用静态作用域还是动态作用域变量的取值可能会有不同的结果。
生命周期:对象在程序执行过程中存在的时间
局部对象主要包含:
- 自动对象
- 局部静态对象
自动对象
一个函数在执行过程中,它的控制路径经过变量自定义语句时创建该对象,当到达定义所在的末尾时销毁该对象,整个过程期间存在的对象便称为自动对象。这就导致如果我们在用传递给函数的实参初始化的形参对应的自动对象,如果变量本来就有初始值,就是用这个初始值初始化,若无初始值,便开始默认初始化。
局部静态对象
有些时候这个这个变量的生命周期贯穿函数以及之后的调用时间,通俗来说就是这个变量作用很大,单单在自己的生命周期里面调用还不够,需要多次调用,这样我们就可以把它定义为static对象,它的销毁是在程序终止的时候发生的
#include<iostream>
using namespace std;
size_t count_calls()
{static size_t ctr=0;return ++ctr;
}
int main()
{for(size_t i=0;i!=10;++i){cout<<count_calls()<<endl;}return 0;
}
关于函数声明
函数在使用前必须要声明,它只能定义一次,却可以声明多次
void print(vector<int>::const_iterator beg,vector<int>::const_iterator end)
我们在编写程序的时候,一般会把声明放在主函数前面,函数的定义放在主函数后面,调用则在主函数里面进行,这样做的目的是为了让程序更加的规范化,否则你就得在一大堆函数定义里面去找函数声明了
更加复杂的情况
C++支持分离式编译,定义和声明可以不在同一个文件中,这种情况下如果我们要编译我们必须告诉编译器我们的文件在哪里
指针形参
指针执行拷贝操作的时候,拷贝的是指针的值,拷贝之后两个指针是不同的两个指针,指针可以使得我们间接的访问所指的对象,通过对指针值得修改可以间接的改变指针指向的对象的值
#include<iostream>
using namespace std;
int main()
{int n=0,i=42;int *p=&n,*q=&i;*p=42;p=q;cout<<"p is "<<p<<endl;cout<<"i is "<<i<<endl;cout<<"n is "<<n<<endl;
}
输出如下:
p is 0x61fe08
i is 42
n is 42
避免引用拷贝
当容器对象比较大或者拷贝大类型对象的时候,拷贝被认为是一种低效的操作,甚至IO类型等个别类型压根不支持拷贝操作。我们可以使用引用形参来来完成这个操作。
bool shorter (const string &s1,const string &s2)
{return s1.size()<s2.size()
}
const形参和实参
int main()
{const int ci=42;int i=ci;//拷贝的时候忽略了它是顶层ciint *const p=&i;*p=0;cout<<i<<endl;
}
使用实参初始化会忽略掉顶层const,这时把值传给它的常量对象或者非常量对象都可以
void fcn(const int i)//可以读取i,但是不能修改i
void fcn(int i)//错误,重复定义了fcn(int)
在参数传递的时候要注意:
- 不能使用指向const int的对象初始化int
- 不能把普通引用绑定到const对象上