函数
最近白天一直在修改小论文,马上放暑假了,希望在放假前把小论文投出去,这样这个暑假可以好好敲代码了。还是先赶紧把昨晚看的第六章做一个总结先。
6.1 函数基础
一个典型的函数有返回类型、函数名字、0个或多个形参列表以及函数体组成。函数的声明必须在使用之前。
int func(int i,double d){
函数体
}
形参
1、执行函数的第一步就是隐式的定义并初始化形参。实参类型必须与形参类型匹配,或者实参能够隐式转换为形参类型。列如一个实参为double类型可以初始化形参类型为int型的函数。
2、任意两个形参都不能同名,函数最外层作用域中的局部变量不能使用与形参相同的名字。
3、形参是一种自动对象,函数开始时为形参申请内存空间,一旦函数终止形参也就被销毁了。
4、当形参为const时,用实参初始化形参时会忽略掉形参的顶层const,此时可以传递常量对象或者非常量对象都行,不会构成函数重载。
void func(const int i) //即可以传递常量,也可以传递非常量,因为const为顶层const会被忽略掉
{}
如果此时再声明一个函数为:
void func(int i) //发生错误,因为重复定义了函数func(int i)
{}
5、应该尽可能使用常量引用,这样会提高函数所能接受的实参类型,因为非常量引用只能接受非常量对象,而常量引用可以接受常量对象、非常量对象以及字面值。
6、由于数组不能拷贝,并且使用数组时会将其转化为指针,因此当向一个函数传递一个数组名时,实际上传递的是一个指向数组首元素的指针。另外为了防止在函数内部使用形参指针发生数组越界问题,可以在传递一个表示数组大小的数,或者传递指向数组首元素的指针(begin(a))和尾后元素的指针(end(a),指向数组最后一个元素的下一个位置)。
int a[5]={1,2,3,4,5};
void func(const int *p)
{}
func(a);//正确,数组自动转化成指针
7、另外形参也可以是数组的引用,此时数组的维度也类型声明的部分,注意数组引用的声明形式:
void func(int (&a)[10]) //注意小括号不能省略,维度不能省略
void func(int &a[10]) //错误,
8、一般形参个数是固定的,传递的实参个数也是固定的。但是如果传递可变数量的实参,且这些实参类型一致,可以使用c++11新标准中的initializer_list类。initializer_list与vector一样也是模板类型,不同之处在于initializer_list中的元素永远是常量值,因此无法改变对象中的元素值。initializer_list类含有size(),begin(),end()等操作,详见第198页。
#include<initializer_list> //包含头文件
void func(initializer_list<int> l) //类模板
{
for (auto it = l.begin(); it != l.end(); it++)
cout << *it << endl; //注:不能修改l对象中的值
}
int main()
{
func({ 1,2,3 });
cout << "****" << endl;
func({ 1,2,3,4,5 });
return 0;
}
6.2 参数传递
函数调用时,都会创建其形参,并且使用实参对形参进行初始化。根据形参类型不同,可以将参数传递分为值传递和引用传递。
①值传递:当形参为非引用类型时,实参将会拷贝给形参,执行值传递,此时形参和实参是两个相互独立的变量。当形参为指针时,同样执行对指针的拷贝操作,此时拷贝的是指针的值,拷贝之后两个指针是不同的指针,但是指向同一个对象。
int a=10;
int *q=&a;
void func(int* p)
{
*p=20; //尽管p与q是两个不同的指针,但是他们指向同一个对象,因此此时*q也等于20
}
//
func(q);
②引用传递:当形参为引用类型,此时实参向形参进行传递不会发生拷贝,而是将实参绑定到形参上,此时对形参的改变会影响到实参。
int i=10;
void func(int &j)
{
j=20; //即i=20;
}
func(i);//相当于int &j=i,将实参i绑定到j 上。
通过引用传递可以避免对一些大的类型对象进行拷贝,另外如果函数无须改变引用形参的值,最好把形参声明为常量引用。
6.3 函数返回类型
1、函数的返回类型不能是数组或者函数类型,但是可以返回指向数组或函数的指针。
2、由于函数完成后,它所占用的储存空间会被释放,局部对象的引用或者指向局部变量的指针将不再指向有效的内存区域,因此不要返回局部对象的引用或指针。
3、当函数的返回类型为引用时,返回一个左值,而其他返回类型得到右值。
char &func(string &str, int i)
{
return str[i];
}
string s="hello";
func(s,2)='L'; //正确,函数返回类型为引用,得到一个左值,可以放在赋值运算符左边
4、可以使用花括号列表初始化返回值
vector<int> func()
{
//
return {1,2,3}; //列表初始化返回值
//
}
5、由于数组不能拷贝,因此函数不能返回一个数组,但是可以返回一个数组引用或者指向数组的指针。声明一个返回数组指针的函数有以下几种:
//第一种
int (*func(int i))[10]; //函数返回一个指向维度为10 的数组指针
//第二种,使用类型别名
using arr=int[10]; //arr为类型别名,表示一个维度为10 的数组
arr *func(int i);
//第三种,使用c++11新标准中的尾置返回类型
auto func(int i)->int(*)[10];
6.4 函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为函数重载。当形参含有const时,如何区分是否是函数重载?
个人总结的方法:
1、首先判断形参中的const是否是顶层const,由于在传参时会自动忽略顶层const,因此顶层const不能作为函数重载的依据。
1、
int func(int i);
int func(const int i); //顶层const会被自动忽略,因此不属于函数重载,会发生错误,func函数重复定义
2、
int func(int* p);
int func(int* const p); //顶层const,同样不属于函数重载,发生函数重复定义的产物
3、
int func(int* p);
int func(const int* p); //底层const不会被忽略,因此构成函数重载
4、
int func(int &i);
int func(const int &i);//底层const,构成函数重载
对于上述的第3种,尽管非常量对象既能初始化指向非常量的指针,也能初始化指向常量的指针,但是编译器会优先选择非常量版本的函数,因此构成函数重载。
6.5 特殊用途语言特性
1、默认实参,即函数形参含有初始值,含有默认实参的形参放在形参列表后面。
int func(int i, int j, int a=10);//可以传入两个或者三个实参
在给定的作用域中一个形参只能被赋予与默认实参,不能对同一个形参添加两次实参,函数的后续声明只能为哪些没有默认实参的形参添加默认实参,并且该形参右侧的所有形参都必须有默认实参。
int func(int i,int j,int a=10);
int func(int i,int j,int a=20);//错误
2、由于函数的调用较复杂,频繁的调用函数会影响程序性能,因此对于那些简单的函数可以声明成内联函数可以避免函数调用的开销。在函数返回类型前加上关键字inline即声明为内联函数。
inline int func(int i){return i+1;}//简单函数可以声明成内联函数
注意:内联函数只是先编译器发出内联的建议,编译器可能会忽略这个请求。
6.7 函数指针
函数指针指向的是函数而不是对象,函数类型由它的返回类型和形参类型组成,与函数名无关,如:
int func(int i,string s);
该函数的类型为:int(int ,string);
函数指针的声明如下:
int (*p)(int ,string);//p是一个指针,指向类型为int(int ,string)的函数。
注意上述声明中,指针的括号不能省略
如同数组一样,当我们把函数名当作一个值使用时,该函数会自动转化为指针。
p=func;//p指向名为func的函数
p=&func;//等价的赋值语句,取地址符可以省略
int a=p(10,"hello");//可以将函数指针调用对应的函数
和数组一样,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针:
void A(string s, int func(int i,double d));//函数A的第二个参数是函数类型,会自动转化为指向函数的指针,
void A(string s, int (*func)(int i,double d));//也可以显示将形参定义为指向函数的指针
尽管不能直接返回函数类型,但是可以返回指向函数的指针。可以使用类型别名达到返回指向函数指针的目的;
using p=int (*)(int ,string);//p为指向函数的指针
p func(int i);//func函数返回类型为p,即返回指向函数的指针。
另外值得注意的是,同数组类似,当使用decltype作用于函数时,不会将函数转换为指针。
先总结这些吧,还有很多零零散散的知识点这里没有列出来,有时间还是看看原书。