六、函数
一个命名了的代码块
1.函数包含:返回类型、函数名、形参列表、函数体;
形参:在函数参数列表中声明的局部变量,它们由每个函数调用中提供的参数初始化,作用是说明函数参数的类型。
实参:函数调用中提供的值,用于初始化函数的参数。
调用运算符,表达式是函数或者指向函数的指针,圆括号内是实参列表,用于初始化形参;
2.调用函数:实参相同或可以转化为形参的类型
3.形参列表:每个形参都含有一个声明符的声明,及时两个形参的类型一样
4.函数的返回类型不能是数组类型或函数类型
函数的类型必须与函数的返回类型相匹配!
6.1.1局部对象
名字的作用域,对象的生命周期;
1.自动对象(形参):块执行结束后,块中创建的自动对象的值就变成未定义的了。
2.局部静态对象:令局部变量的生命周期贯穿函数调用及之后的时间。(比如计数值使用)
局部变量:定义在一个块中的变量。
形参:在函数参数列表中声明的局部变量,函数终值,形参也被销毁。
局部静态变量:在第一次执行通过对象定义之前初始化局部静态变量(对象)。函数结束时不破坏局部静态;它们在程序终止时被销毁。
6.1.2函数声明
函数原型
1.声明中可以省略形参的名字,但是写上可以帮助理解
2.与变量一样,建议将函数的声明放在头文件,而在源文件中定义
6.1.3分离式编译
可以把程序分割到几个文件中,每个文件单独编译
6.2参数传递
形参是引用类型,则绑定到对应的实参上;否则,将实参的值拷贝后赋值给形参
引用传递--传引用调用;值传递--传值调用
例:引用传递交换值(C++最好使用引用类型的形参代替指针,修改实参的变量)
#include <iostream>
using namespace std;
int exchange(int &val1, int &val2)//(引用传递)
{
int a;
a = val1;
val1 = val2;
val2 = a;
return 0;
}
void main()
{
cout<<"请输入需要交换的两整数:";
int val1,val2;
cin>>val1>>val2;
cout<<"交换之前的两数:"<<val1<<" "<<val2<<endl;
exchange(val1,val2);
cout<<"交换之后的两数:"<<val1<<" "<<val2<<endl;
1.有些拷贝大的类类型对象或容器对象比较低效,可以直接用引用形参访问;如果也无须改变对象的内容,可以把形参定义为对常量的引用;
2.使用引用形参返回额外信息:给函数传入一个额外的引用实参;
3.const形参和实参:实参初始化形参时,会忽略掉顶层const。
当形参有顶层const时,传给它常量对象或者非常量对象都是可以。
复习:const引用可能引用一个非常量、字面值、一般表达式,可以通过其他途径改变值,但是普通的引用不能绑定到const引用上;(第二单元const限定符)
1.可以用非常量初始化一个底层const对象,二反过来不行;
2.同时一个普通的引用必须用同类型的对象初始化;
void str(int &i)
{
i=0;
}
int i=0;
const int ci=i;
string::size_type ctr=0;
reset(&i);//正确
reset(&ci);//错误,不能用指向const int的对象初始化指正int*
reset(i);
reset(ci);//错误,不能把普通引用绑定到const对象上
reset(45);//引用不能绑定字符值
const(ctr);//类型不匹配
4.尽量使用常量引用:
1.不改变的形参定义为普通引用时常见错误,带来对函数调用者的误导;
2.使用引用而非常引用也会极大限制函数所能接受的实参类型
练习6.17
#include <iostream>
#include <string>
using std::string;
bool hasUppercase(const string& str)
{
for (auto c : str)
if (isupper(c)) return true;
return false;
}
const string& makeLowercase(string& str)
{
for (auto& c : str)
if (isupper(c)) c = tolower(c);
return str;
}
int main()
{
string str("Hello World!");
std::cout << std::boolalpha << hasUppercase(str) << std::endl;
std::cout << makeLowercase(str) << std::endl;
}
5.数组形参:
数组特征:1.不允许拷贝数组;2.使用数组时会将其转化为指针
函数传递一个数组时,实际上传递的是指向数组首元素的指针(不能用值传递,用引用传递)!
管理指针形参的方法:
1.使用标记指定数组长度:C风格字符串存储式是,空字符(结束标记)
2.使用标准库规范:传递数组首元素和尾后元素的指针begin()和end()函数
3.显示传递一个表示数组大小的形参;
形参可以是数组的引用,绑定到数组上
-
void f(int &a[10]);//引用的数组
-
void f(int (&a)[10]);//数组的引用
void print(int(&arr)[10])//限制了维度
{
for (auto s : arr)
cout << s << endl;
}
当函数不需要对数组元素进行写操作时,使用const常量指针。
6.main:处理命令行选项
需要给main()函数传递实参,之前写的程序基本上main()函数都是空形参列表
特殊点:使用argv中的实参时,一定要记得可选参数从argv[1]开始,argv[0]保存的是程序的名字。
#include <iostream>
#include <string>
int main(int argc, char** argv)
//实参列表,其中第二个argv是指针数组,相当于char *argv[ ]
{
string str;
for (int i = 1; i != argc; ++i) {
str += argv[i];
str += " ";
}
cout << str <<endl;
return 0;
7.含有可变形参的函数
为了编写处理不同数量实参的函数
1.实参数量未知但是类型一致:
可以使用initializer_list类型的形参(标准库类型,定义在同名头文件)
表示某种特定类型的值的数组;与vector一样,一种模板类型;元素永远是常量值
void error_msg(initiallizer_list<string> il) { for (auto beg=il.begin();beg!=il.end();++beg) cout<<*beg<<" "<<endl; }
向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内
error_msg({ "functionX",expected,catual }); error_msg({ "functionX","Okay"});
2.省略符形参
便于c++访问某些特殊的c代码而设置的,使用名为varargs的c标准库功能。
两种形式:
void foo(parm_list,...); void foo(...);
6.3返回类型和return语句
1.void 最后有隐式执行return,可以不写;中间位置可用于提前退出
6.3.2有返回值函数
返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
1.不要返回局部变量的引用或指针(函数完成后,专用的存储空间释放):
返回的引用无效:局部临时变量或者局部对象的引用对于返回都是无效的,因为在函数终止之后,局部变量或者对象的引用不再指向有效的内存区域。若是常量在函数调用之前存在,引用即可用。
2.调用运算符的优先级与点运算符、箭头运算符相同,符合左结合律:
auto sz=ShortString(s1,s2).size();//先函数调用返回值,然后取对象中的size()成员
3.函数的返回类型决定函数是否是左值。调用一个返回引用的函数是左值(可以出现在赋值左侧),其他返回类型是右值。
4.列表初始化返回值
5.main函数可以省略return 0
6.递归:函数调用了她自己:求阶乘
int factorial(int val)
{
if (val > 1)
return factorial(val - 1) * val;
return 1;
}
输出vector对象内容:
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
using Iter = vector<int>::iterator;
void print(vector<int>::iterator beg, vector<int>::iterator end)
{
if (beg != end) {
cout << *beg << " ";
print(++beg, end);
}
else
{
cout << endl;
return;
}
}
int main()
{
vector<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print(vec.begin(), vec.end());
}
6.3.3返回数组指针
函数可以返回数组的指针或引用
声明一个返回数组指针的函数:int (*fun(int i))[10];
1.使用类型别名
2.任何函数都可以使用尾置返回类型:对于返回类型是数组的指针或数组的引用有效;
auto func(int i) ->int(*)[10];//返回一个指针,该指针指向含10个整型的数组
3.使用decltype,已知函数的返回值时,可以使用关键字decltype表示返回类型为指针
string (&func(string (&arrStr)[10]))[10] //返回数组的引用,数组包含10个string对象
using ArrT = string[10];
ArrT& func1(ArrT& arr);//使用类型别名auto func2(ArrT& arr) -> string(&)[10]; //使用尾置返回类型
string arrS[10];
decltype(arrS)& func3(ArrT& arr);//使用decltype关键字
6.4函数重载
同一作用域内几个函数名字相同但形参列表不同。
1.不允许两个函数除了返回类型外其他所有的要素相同;
2.顶层const不影响传入函数的对象,形参有无const是同一个函数;
3.底层const可以实现函数重载;
最好只重载那些确实非常相似的操作,给函数起不同的名字使函数更好理解!
4.可以用const_cast,非常量转换为常量
5.重载与作用域:如果在内层作用域声明名字,将隐藏外层作用域中声明的同名实体。在不同作用域中无法重载函数名
6.5特殊用途语言特性
6.5.1默认实参
含有默认实参的函数,默认实参负责填补函数调用缺少的尾部参数(靠右侧位置);
一旦函数的某个形参被赋予了默认值,他后面所有的参数都必须有默认值;(从右到左默认实参。从左到右赋值)
1.给定作用域中一个形参只能被赋予一次默认形参;后续声明只能对没有默认值的形参添加
2.局部变量不能作为默认变量
3.只要表达可以被转换成形参所需的类型,就能作为默认实参(如char变int)
6.5.2内联函数和constexpr函数
调用函数比一般求等价表达式慢一些。
内联函数:将它在每个调用点上“内联地”展开,在函数返回类型前加关键词inline
用于规模较小、流程直接、调用频繁的函数,有些编译器不支持。
constexpr函数
能用于常量表达式的函数。
要求:函数的返回类型及形参的类型都是字面值类型,而且函数体有且仅有1个return语句
通常将内联函数和constexpr函数定义在头文件中。
constexpr int new_sz() {return 42;}
6.5.3调试帮助
用于调试的代码,只在开发使用,发布时要屏蔽掉。
assert预处理宏:
一种预处理宏(预处理变量,行为与inline函数类似)
预处理宏assert(expr):包含一个表达式,expr为真时,assert什么也不做,为假时输出信息并终止程序
1.包含在cassert头文件中;
预处理名字由预处理器而非编译器管理,可以直接用预处理名字声明。
宏名字在程序内必须唯一
2.通常用于检查不能发生的条件
NDEBUG预处理变量:
定义后,避免检查各类条件所需的运行时开销;
2.可以使用NDEBUG编写自己的条件调试代码,如果NDEBUG未定义,将执行#ifndef到#endif之间的代码,如果定义了NDEBUG,这些代码将被忽略。
#ifndef NDEBUG
cerr<<"my name is:"<<__func__<<endl;
#endif
//其他代码
一些C++编译器定义的调试有用的名字:
_ _func_ _ :一个静态数组,存放函数的名字
_ _FILE_ _ :存放文件名的字符串字面值
_ _LINE_ _ :存放当前行号的整形字面值
_ _TIME_ _ :存放文件编译时间的字符串字面+值
_ _DATE_ _ :存放文件编译日期的字符串字面值
6.6函数匹配
候选函数:函数匹配的第一步是选定本次调用的重载函数集,集合中的函数被称为候选函数
可行函数:第二步,根据实参情况,从候选函数中挑选出能被这实参调用的函数,此次选出的函数被称为可行函数。
第三部,寻找最佳匹配:精度匹配要比类型转换好。
1.实参类型到形参转换(p219)
在匹配过程中,除了精确匹配还有几种转换类型的匹配方式。
引用类型的const,类型转换:判别方式是判断实参是否是常量来决定选择哪个函数。指针类型的形参判别也是类似的。(虽然非常量对象初始化常量引用可以,但需要类型转换)
类型提升:不是完全相同,皆会提升类型,char会提升为int。
算术类型转换:该转换的级别都相同,即3.14是double,转换成long或者float都是同样等级,会产生二义性。
这几种匹配是有顺序的。从上到下
6.7函数指针
1.指向函数,用指针替换函数名,括号必须写
bool lengthCompare (const string &, const string &);
bool (*p) (const string &, const string &);
2.函数名作为一个值使用,该函数自动转换为指针;
可以直接使用指向函数的指针调用函数,无须提前解引用指针:
bool b2= p( "hello" , "goodbye" );//也可以用(*p)或者 lengthCompare
3.不同函数类型指针不存在转换规则,可以将函数指针赋nullptr
4.重载函数的指针:指针类型与重载函数中某一个精确匹配;
5.指向函数的指针可以作为函数的形参:
void useBigger(const string &str1, const string &str2, bool pf(const string &,const string &)) ;//pf
void useBigger(const string &str1, const string &str2, bool *pf(const string &,const string &)) ;//*pf
void useBigger(const string &str1, const string &str2, lengthCompare) ;//lengthCompare
注意:
使用decltype返回的函数类型 decltype(Func2)
typdef bool Func(const string &, const string &) ;
加上*,返回指针 typdef bool (*Func) (const string &, const string &);
6.返回函数的指针:
类型别名的方法定义
using F= int (int*,int); F *f1(int);//函数别名定义,定义函数指针
using *F=int (int*,int); F f1(int);//函数指针别名定义
int (*f1(int))(int*,int);//形参表f1是函数,*f1指针,指针本身包含形参表函数指针
auto f1(int) -> int (*)(int*,int);//尾置定义返回