函数基础
- 我们用调用运算符来执行函数,它是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;括号之内是一个用逗号隔开的实参列表
(1)编写函数
int fact(int val){
int ret=1;
while (val>1)
ret*=val--;
return ret;
}
(2)调用函数
int main(){
int j=fact(5);
cout<<"5! is "<<j<<endl;
return 0;
}
函数的调用完成两项工作:一是实参初始化函数对应的形参,二是将控制权转移给被调用函数
(3)形参和实参
(4)函数的形参列表
(5)函数的返回类型
局部对象
在C++中,名字有作用域,对象有生命周期
- 名字的作用域是程序文本的一部分,名字在其中可见
- 对象的生命周期是程序执行中该对象存在的一段时间
(1)自动对象
(2)局部静态对象
// 统计函数它自己被调用了多少次
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;
retunr 0;
}
- 在控制流第一次经过ctr的定义之前,ctr被创建并初始化为0。
- 每次调用将ctr加1并返回新值
- 每次执行函数时,变量ctr的值都已经存在并且等于函数上一次退出时ctr的值
函数声明
void print(vector<int>::const_iterator beg,
vector<int>::const_iterator end);
- 函数的三要素(返回类型、函数名、形参列表)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也称作函数原型
(1)在头文件中进行函数声明
分离式编译
(1)编译和链接多个源文件
参数传递
- 但形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用
- 当实参被拷贝给形参时,形参和实参是两个独立的对象
传值参数
(1)指针形参
- 当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两者为不同的指针
- 因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值
int n=0,i=42;
int *p=&n,*q=&i; //p指向n;q指向i
*p=42; //n的值改变;p不变
p=q; //p现在指向了i;但是i和n的值不变
- 指针形参的行为与之相似
void reset(int *ip){
*ip=0; // 改变指针ip所指对象的值
ip=0; // 只改变了ip的局部拷贝,实参并未改变
}
- 调用该函数需要传递对象的地址
熟悉C的程序员经常使用指针类型的形参来访问函数外部的对象。在C++语言中,建议使用引用类型的形参替代指针
传引用参数
// 该函数接受一个int对象的引用,然后将对象的值置为0
void reset(int &i){ // i是传给reset函数的对象的另一个名字
i=0; // 改变了i所引对象的值
}
- 调用该函数时,我们直接传入对象,而无须传入对象地址
(1)使用引用避免拷贝
如果函数无须改变引用的形参的值,最好将其声明为常量引用
// 比较两个string对象的长度
bool isShorter(const string &s1,const string &s2){
return s1.size()<s2.size();
}
(2)使用引用形参返回额外信息
- 但我们需要获得两个或以上的返回数据时,函数只能有一个返回值,有两种办法
- 定义一个新的数据类型,让它包含不同的成员,作为返回值
- 给函数传入一个额外的应用实参,令其保存另外的返回数据
// 返回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; //记录第一次出现的位置
}
++occurs;
}
}
return ret;
}
const形参和实参
- 关于顶层const作用于对象本身:
const int ci=42; //不能改变ci,const时顶层的
int i=ci; //正确:当拷贝ci时,忽略了它的顶层const
int * const p=&i; //const是顶层的,不能给p赋值
*p=0; //正确:通过p改变对象的内容是允许的,现在i变成了0
- 当用实参初始化形参时会忽略掉顶层const
- 即当形参有顶层const时,传给它常量对象或者非常量对象都是可以的
void fcn(const int i){
// fcn能够读取i,但是不能向i写值
}
(1)指针或引用形参与const
- 形参的初始化方式和变量的初始化方式时一样的
- 我们可以使用非常量初始化一个底层const对象,当反过来不行
- 一个普通的引用必须用同类型对象来初始化
int i =42;
const int *cp=&i; //正确:但是cp不能改变i
const int &r=i; //正确:但是r不能改变i
const int &r2=42; //正确
int *p=cp; //错误:p的类型与c的类型不匹配
int &r3=r; //错误:r3的类型和r的类型不匹配
int &r4=r2; //错误:不能用字面值初始化一个非常量引用
(2)尽量使用常量引用
数组形参
- 数组的两个特殊性质对我们定义和使用作用在数组上的函数又影响,分别是:
- 不允许拷贝数组
- 使用数组会将其转换成指针
- 尽管不能以值传递的方式传递数组,当可以把形参写成类似数组的形式
void print(const int*);
void print(const int[]);
void print(const int[10]);
和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界
- 因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的尺寸,调用者应该为此提供额外信息。管理指针形参有三种常用技术
(1)使用标记指定数组长度
void print(const char *cp){
if(cp) //若cp不是一个空指针
while(*cp) //只要指针不是空字符
cout<<*cp++;
}
(2)使用标准库规范
- 传递指向数组首元素和尾后元素的指针
void print(const int *beg,consst int *end){
//输出beg到end之间全部元素
while(beg!=end)
cout<<*beg++<<endl;
}
上述函数的调用可以通过:
int j[2]={0,1};
print(begin(j),end(j));
(3)显式传递一个表示数组大小的形参
void print(cosnt int ia[],size_t size){
for(size_t i=0 ; i!=size() ; ++i){
cout<<ia[i]<<endl;
}
}
(4)数组形参和const
(5)数组引用形参
void print(int (&arr)[10]){
for(auto elem:arr)
cout<<elem<<endl;
}
(6)传递多维数组
void print(int (*matrix)[10] ,int rowSize){}
/*等价定义*/
void print(int matrix[][10],int rowSize){}
上述语句将matrix声明成指向含有10个整数的数组的指针
mian:处理命令行选项
含有可变形参的函数
(1)initializer_list形参
- 如果函数的实参数量未知但是全部实参的类型都相同,可以使用initializer_list类型的形参
操作 | 结果 |
---|---|
initializer_list lst; | 默认初始化:T类型元素的空列表 |
initializer_list lst{a,b,c…> | lst的元素数量和初始值一样多,lst是初始值的副本,列表种的元素是const |
lst2(lst) | 拷贝或赋值一个initalizer_list对象不会拷贝列表中的元素;拷贝后两列表共享与元素 |
lst2=lst | 同上 |
lst.size() | 列表中的元素数量 |
lst.begin() | 返回首元素指针 |
lst.end() | 返回尾元素下一位置指针 |
void error_msg(initalizer_list<string> il){
for(auto beg=il.begin() ; beg!=il.end() ; ++beg)
cout<<*beg<<"";
cout<<endl;
}
- 如果想向initialzer_list形参中传递一个值的序列,必须把序列放进花括号中
error_msg( {"functionX",expected,actual} );
(2)省略符形参
返回类型和return语句
无返回值函数
- 可以直接执行return语句处推出
void swap(int &v1,int &v2){
if(v1==v2)
return;
int tmp=v2;
v2=v1;
v1-tmp;
}
有返回值函数
(1)值是如何被返回的
string make_plural(size_t str,const string &word,const string &ending){
return (ctr>1)?word+ending:word;
}
(2)不要返回局部对象的引用或指针
- 函数完成后,它说占用的空间也随之被释放。因此,局部变量的引用不再有效
(3)返回类类型的函数和调用运算符
// shorterString函数返回istring,访问其size成员
auto sz=shorterString(s1,s2).size();
(4)引用返回左值
(5)列表初始化返回值
- 如果返回值是引用,它是左值;其它则是右值
- 如果返回值是常量引用,就不能给调用的结果赋值
(5)列表初始化返回值
vectot<sting> process(){
...
...
return {"funtionX","OKay"};
}
(6)主函数main的返回值
- return EXIT_FALLURE或者return EXIT_SUCCESS 这两个预处理变量定义再头文件cstdlib头文件中
(7)递归
返回数组指针
- 最直接的办法就是使用类型别名
typedef int arrT[10]; // arrT是一个类型别名,表示含有10个整数的数组
using arrT=int[10]; // arrT的等价声明
arrT* func(int i); // func返回一个指向含有10个整数的数组的指针
(1)声明一个返回数组指针的函数
- 要想再声明func时不适用类型别名
int arr[10];
int *p1[10];
int (*p2)[10]=&arr; //p2时一个指针,它指向含有10个整数的数组
- 返回数组指针的形式如下所示
Type (*function(parameter_list))[10];
// 例子
int (*func(int i))[10];
(2)使用尾置返回类型
// func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];
(3)使用decltype
int odd[]={1,3,5,7,9};
int even[]={0,2,4,6,8};
// 返回一个指针,该指针指向含有5个整数的数组
decltype(odd) *arrPtr(int i){
return (i%2)?&odd:&even; //返回一个指向数组的指针
}
- 其中,decltype表示它的返回类型是一个指针,并且所指对象类型与odd的类型一致
- decltype并不负责把数组转换成对应指针,所以decltype的结果是个数组,要想表示返回指针,还必须再函数声明时加一个符号
函数重载
(1)定义重载函数和
Record lookup(const Account&);
Record lookup(const Phone&);
(2)判断两个形参的类型是否相同
(3)重载和const形参
- 一个拥有顶层const的形参无法和另一个没有顶层const的形参区别开来
- 如果形参时某种类型的指针或引用,则区分其指向是否常量可以实现函数重载
(4)const_cast和重载
const string &shorterString(const string &s1,const string &s2){
return s1.size()<=s2.size()?s1:s2;
}
- 上述程序,当它的实参不是常量时,得到的结果是一个普通的引用
string &shorterString(string &s1,string &s2){
auto &r=shorterString(const_cast<const string&>(s1),const_cast<const string&>(s2));
return const_cast<string&>(r)
}
(5)调用重载的函数
重载与作用域
特殊用途语言特性
默认实参
typedef string::size_type sz;
string screen(sz ht=24,sz wid=80,char backgrnd='');
- 其中,为每个形参都提供了默认实参
- 注意:只能给全部形参同时提供默认值
(1)使用默认实参调用函数
- 直接再调用时省略该实参即可
- 注意:只能省略尾部的实参,或者省略全部实参
(2)默认实参声明
- 通常在头文件中,进行函数实参的声明
通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中
(3)默认实参初始值
- 除局部变量之外,表达式能实现对应转化则可以作为默认实参
内联函数和constexpr函数
(1)内联函数可避免函数调用的开销
- 在函数的返回类型前面加上关键字inline,就声明成为内联函数了
很多编译器都不支持递归内联函数
(2)constexpr函数
- constexpr函数的全部形参都必须时字面值类型,而且函数体中必须有且只有一条return
调试帮助
(1)assert预处理宏
- assert是一种预处理变量,它的行为类似于内联函数
assert(expr);
- 首先,对expr求值,如果为假(0),assert输出信息并终止程序的执行;如果为真,assert声明也不做
- 常常用来检查“不能发生的事”
(2)NDEBUG预处理变量
- assert的行为依赖于一个名为NDEBUG的预处理变量状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行检查
- 定义NDEBUG能避免检查各种条件所需的运行时开销
- 小例子:
void print(const int ia[],size_t size){
#ifndef NDEBUG
cerr<<__func__<<":array size is "<<size<<endl;
#endif
}
如果NDEBUG未定义,将执行 #ifndef 和 #endif之间代码,如果定义了NDEBUG,将被忽略
编译器定义的用于调试的名字
代码 | 结果 |
---|---|
_ _ func _ _ | 存放当前调试的函数的名字的const char静态数组 |
_ _ FILE _ _ | 存放文件名的字符串字面值 |
_ _ LINE _ _ | 存放当前行号的整型字面值 |
_ _ TIME _ _ | 存放文件编译时间的字符串字面值 |
_ _ DATE _ _ | 存放文件编译日期的字符串字面值 |
if(word.size() < threshold)
cerr<<"Error:"<<_ _FILE_ _
<<":in function"<<_ _func_ _
<<"at line"<<_ _LINE_ _<<endl
<<" Compiled on"<<_ _DATE_ _
<<"at"<<_ _TIME_ _<<endl
<<" Word read was \""<<word
<<"\":Length too short"<<endl;
函数匹配
(1)确定候选函数
(2)寻找最佳匹配(如果有的话)
(3)含有多个形参的函数匹配
实参类型转换
(1)需要类型提升和算术类型转换的匹配
(2)函数匹配和const实参
函数指针
bool (*pf)(const string &,const string &);
(1)使用函数指针
pf=lengthCompare; //pf指向名为lengthCompare的函数
pf=&lengthCompare; //等价的赋值语句
另外,还可以直接使用指向函数的指针条用函数
bool b1=pf("hello","goodbye");
bool b2=(*pf)("hello","goodbye"); //一个等价的调用
(2)重载函数指针
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int)=ff; //pf1指向ff(unsigned)
编译器通过指针类型决定选用那个函数,指针类型必须与重载函数中的某一个精确匹配
(3)函数指针参数
- 函数虽然不能定义数组、函数类型的形参,但是形参也可以是指向
void useBigger(const string &s1,const string &s2,bool pf(const string &,const &));
// 等价的声明
void useBigger(const string &s1,const string &s2,bool (*pf)(const string &,const string &));
(4)返回指向函数的指针
- 最简单的办法是声明一个类型别名作为返回类型
using F=int(int*,int); //F是函数类型,不是指针
using PF=int(*)(int*,int); //PF是指针类型
- 将F定义成函数类型,将PF定义成指向函数类型的指针
PF f1(int); //正确:PF是指向函数的指针,f1返回指向函数的指针
F=f1(int); //错误:F是函数类型,f1不能返回一个函数
F *f1(int); //正确:显式地指定返回类型是指向函数的指针
- 处于完整性考虑,可以使用尾置返回类型的方式
auto f1(int)->int(*)(int*,int);
(5)将auto和decltype用于函数指针类型
string::size_type sumLength(const string&,const string&);
string::size_type larger(const string&,const string&);
// 根据其形参的取值,getFcn函数返回指向sumLength或者largerLength的指针
decltype(sumLength) *getFcn(const string &);