函数是一个命名了的代码块,我们通过调用函数执行相应的代码;函数可以有0个或多个参数,而且会产生一个结果;可以重载函数,即同一个名字可以对应几个不同的函数。
一个典型的函数定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体;其中形参以逗号隔开。
通过调用运算符来执行函数;调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号之内是一个用逗号隔开的实参列表,我们用实参初始化函数的形参,调用表达式的类型就是函数的返回类型。
函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调函数。
实参是形参的初始值,实参的类型必须与对应的形参类型匹配。
函数的形参列表可以为空,但是不能省略。
c++中名字的作用域是程序文本的一部分,名字在其中可见;对象的生命周期是程序执行过程中该对象存在的一段时间。
形参和函数体内定义的变量统称为局部变量。
可以将局部变量定义成static类型从而获得生命周期贯穿函数调用及之后的时间,即局部静态对象;在程序执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
size_t count_calls()
{
static size_t ctr = 0;//调用结束时这个值仍然有效
return ++ctr;
}
int main()
{
for(siez_t i = 0; i != 10; ++i)
cout << count_calls() << endl;
return 0;
}
//输出1~10
函数的声明和函数的定义非常类似,唯一的区别是函数声明无须函数体,用一个分号替代即可。
因为函数的声明不包含函数体,所以也无须形参的名字,但写上名字可以更好的帮助理解函数。
函数的声明也称为函数原型。
函数应该是头文件中声明,在源文件中定义。
每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化;该机理与变量初始化一样。
形参的类型决定了形参和实参的交互的方式;如果形参是引用类型,它将绑定到对应的实参上;否则将实参的值拷贝后赋给形参。
引用传递:当形参是引用类型时,则它对应的实参被引用传递或者函数被传引用调用;和其它引用一样,引用形参也是它绑定的对象的别名,即是对应的实参的别名。
值传递:当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象,这样的实参被值传递或者函数被传值调用。
当初始化一个非引用类型的变量时,初始值被拷贝给变量;此时对变量的改动不会影响初始值。
int n = 0;//int类型的初始变量
int i = n;//i是n的值的副本
i = 42;//i的值改变,n的值不变
传值参数的机理完全一样,函数对形参做的所有操作都不会影响实参。
指针的行为和其它非引用类型一样,当执行指针拷贝操作时,拷贝的是指针的值;拷贝之后,两个指针是不同的指针;因为指针使我们可以间接地访问它所指向的对象,所以通过指针可以修改它所指对象的值。
int n = 0, i = 42;
int *p = &n, *q = &i;//p指向n,q指向i
*p = 42;//n的值改变,p不变
p = q;//p现在指向了i,但是i和n的值都不变
指针形参的行为与执行指针拷贝操作类似。
建议:c语言常常使用指针类型的形参访问函数外部的对象,在c++中建议使用引用类型的形参代替指针。
对于引用的操作实际上是作用在引用所引 的对象上。
int n = 0, i = 42;
int &r = n;//r绑定了n(即r是n的另一个名字)
r = 42;//现在n的值是42
r = i;//现在n的值和i相同
i = r;//i的值和n相同
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本不支持拷贝操作;当某种类型不支持拷贝操作时,函数只能通过引用形参该类型的对象。
//比较连个string对象的长度
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size();
}
一个函数只能返回一个值,但有时候需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效途径。
//返回s中c第一次出现的位置索引
//引用形参occurs负责统计c出现的总次数
string::size_type find_char(const string &s, char c, string::size_type &occurs){
auto ret = s.size(); //第一次出现的位置(如果有的话)
occurs = 0; //设置表示出现次数的形参的值
for(decltype(ret) i = 0; i != s.size(); ++i){
if(s[i] == c){
if(ret == s.size()){
ret = i;//记录c第一次出现的位置
}
++occurs; //将出现的次数加1
}
}
return ret; //出现次数通过occurs隐式的返回
}
此外要主要const类型修饰的形参和实参以及数组形参。
return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方;有两种形式:
return;
return expression;
没有返回值的return语句只能用在返回类型是void的函数中。
void swap(int &v1, int &v2)
{
if(v1 == v2)
return;
int tmp = v2;
v2 = v1;
v1 = tmp;
}
只要函数的返回类型不是void,则该函数内的每条return语句必须返回一个值,且返回类型必须与函数的的返回类型相同,或者能隐式的转换成函数的返回类型。
注意:不要返回局部对象的引用或指针,因为函数完成之后,意味着它所占用的存储空间也随着被释放掉;因此,函数终止意味着局部变量的引用将指向不再有效的内存区域。
如果同一作用域内的几个函数名字相同但形参列表不同,则称之为重载(overload)函数。
注意:main()函数不允许重载。
某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值,此时,把这个反复出现的值称为函数的默认实参。
调用函数一般比求等价表达式的值要慢一点,因此将函数指定为内联(inline),通常就是将它在每个调用点上“内联地”展开。
constexpr函数是指能用于常量表达式的函数,定义与其他函数类型,不过要遵守几项约定:函数的返回类型及所有形参的类型都的是字面值类型,而且函数体内必须有且只有一条return语句。
当然,constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。
constexpr int new_sz(){return 42;}
函数指针指向的是函数而非对象,形式:
type (*fun)(type, type)
*fun两端的括号必不可少,如果不写这对括号,则fun是一个返回为type指针的函数。
函数指针只是一个指针,函数指针的作用和指针相似,只是这种指针赋值的函数的地址,函数指针的作用有点像给函数换名字。
int (*fun)(int x,int y);
int add(int x,int y){
return x+y;
}
int sub(int x,int y){
return x-y;
}
//函数指针
int (*fun)(int x,int y);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//第一种写法
fun = add;
qDebug() << "(*fun)(1,2) = " << (*fun)(1,2) ;
//第二种写法,建议用第二种,区别普通函数
fun = ⊂
qDebug() << "(*fun)(5,3) = " << (*fun)(5,3) << fun(5,3);
return a.exec();
}
指针函数本质是函数,只是返回值是指针,原因是返回值的数据类型先和星号*结合,作为一种指针数据类型创建的函数,这类函数的特点就是,返回值只能是地址。
int *fun(int x,int y);
常见写法:
int *fun(int x,int y); int * fun(int x,int y); int* fun(int x,int y);
typedef struct _Data{
int a;
int b;
}Data;
//指针函数
Data* f(int a,int b){
Data * data = new Data;
data->a = a;
data->b = b;
return data;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//调用指针函数
Data * myData = f(4,5);
qDebug() << "f(4,5) = " << myData->a << myData->b;
return a.exec();
}