①
使用引用形参返回额外的信息
函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。例如:
1
vector<int>::const_iterator find_val(
2
vector<int>::const_iterator beg,
3
vector<int>::const_iterator end,
4
int value,
5
vector<int>::size_type &occure)
6![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
7
vector<int>::const_iterator res_iter=end;
8
occur=0;
9
for(;beg!=end;++beg)
10![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
11
if(*beg==value)
12![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
13
if(res_iter==end)
14
res_iter=begl; //remember first occurrence of value
15
++occure;
16
}
17
return res_iter;
18
}
19
}
调用find_val时,需传递四个实参:一对标志vector对象中要搜索的元素范围的迭代器,所查找的值,以及用于存储出现次数的size_type类型对象。
假设ivec是vector<int>类型的对象,it是一个适当类型的迭代器,而ctr则是size_type类型的变量,则可如此调用该函数:
it = find_val(ivec.begin(),ivec.end(),18,ctr);
调用后,ctr的值将是18出现的次数,如果18在ivec中出项了,则it将指向其第一次出现的位置;否则,it的值为ivec.end(),而ctr则为0。
②利用const引用避免复制
在向函数传递大型对象时,需要使用引用实参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型
对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率通常较低。此外,某些类类型是无法复制的。使用引用形参,函数可以
直接访问实参对象,而无法复制它。
编写一个比较两个string对象长度的函数作为例子。这个函数需要访问每个string对象的size,但不必修改这些对象。由于string对象可能相当长,所以
我们希望避免复制操作。使用const引用就可避免复制:
1
//compare the length of two strings
2
bool isShorter(const string &s1,const string &s2)
3![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
4
return s1.size()>s2.size();
5
}
③更灵活的指向const引用
如果函数具有普通的非const引用形参,则显然不能通过const对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的const特性。
但比较容易忽略的是,调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的:
int incr(int &val)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
return ++val;
}
int main()
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
short v1=0;
const int v2=42;
int v3=incr(v1); //error: v1 is not an int
v3=incr(v2); //error: v2 is const
v3=incr(0); //error literals are not lvalues
v3=incr(v1+v2); //error: addition doesn't yield an lvalue
int v4=incr(v3); //ok: v3 is a non const object type int
}
问题的关键是非const引用形参只能与完全同类型的非const对象关联。
如果把被调用函数形参的“&”去掉,通过编译。
换句话说,应该将不修改相应实参的形参定义为const引用。如果将这样的形参定义为非const引用,则毫无必要地限制了改函数的使用。例如,
可编写下面的程序在一个string对象中查找一个指定的字符:
1
//returns index of first occurrence of c in s or s.size() if c isn't in s
2
//Note:s doesn't change,so it should be a reference to const
3
string::size_type find_char(string &s,char c)
4![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
5
string::size_type i=0;
6
while(i!=s.size() && s[i]!=c)
7
++i;
8
return i;
9
}
这个函数将其string类型的实参当做普通(非const)的引用,尽管函数并没有修改这个形参的值。这样的定义带来的问题是不能通过字符串字面值
来调用这个函数:
if (find_char("Hello world",'o'))
虽然字符串字面值可以转换为string对象,但上述调用仍然会导致编译失败。
即使程序本身没有const对象,而且只使用string对象调用find_char函数,编译阶段的问题依然会出现。
例如,可能有另一个函数is_sentence调用find_char来判断一个string对象是否是句子:
bool is_sentence(const string s)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
//if there's a period and it's the last character in s
//then is a sentence
return(find_char(s,'.')==s.size()-1);
}
传递进is_sentence的形参是指向const string对象的引用,不能将这种类型的参数传递给find_char,因为后者期待得到一个指向非const string对象的引用。
④
return语句
没有返回值的函数
一般情况下,返回类型是void的函数使用return语句是为了引起函数的强制结束,这种return的用法类似于循环结构中的break语句的作用。例如
可如下写swap程序,使之在输入的两个数值相同时不执行任何工作。
void swap(int &v1,int &v2)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
//if values already the same,no need to swap,just return
if(v1==v2)
return ;
int tmp=v2;
v2=v1;
v1=tmp;
//no explicit return necessary
}
这个函数首先检查两个值是否相等,如果相等则退出函数,如果不相等,则交换这两个值,隐式的return发生在最后一个赋值语句后。
返回类型为void的函数通常不能使用第二种形式的return语句,但是,它可以返回另一个返回类型同样是void的函数的调用结果:
1
void do_swap(int &v1,int &v2)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
3![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
4
int tmp=v2;
5
v2=v1;
6
v1=tmp;
7
}
8
void swap(int &v1,int &v2)
9![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
10
if(v1==v2)
11
return false;
12
return do_swap(v1,v2);//ok:returns call to a void function
13
}
⑤
千万不要返回局部对象的引用
当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。考虑下面的程序:
const string &manip(const string&s)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
string ret=s;
return ret;
}
这个函数会在运行时出错,因为它返回了局部对象的引用。当函数执行完毕,字符串ret占用的储存空间被释放,函数返回值
指向了对于这个程序来说不再有效的内存空间。
⑥
默认实参
例如,下面的函数创建并初始化了一个string对象,那么,用于模拟窗口屏幕。此函数为窗口屏幕的高、宽和背景字符提供了默认实参:
string screenInit(string::size_type height=24,
string::size_type width=80,
char background=' ');
调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供实参,则它将覆盖默认的实参值;否则,函数
将使用默认实参值。下面的函数streenInit的调用都是正确的:
string screen;
screen=screenInit(); //equivalent to screenInit(24,80,' ')
screen=screenInit(66); //equivalent to screenInit(66,80,' ')
screen=screenInit(66,256); //screenInit(66,256,' ')
screen=screenInit(66,256,'#');
如果要给background提供实参,那么必须给height和width提供实参:
screen = screenInit(,,'?'); //error:can omit only trailing arguments
screen = screenInit('?'); //cakks screenInit('?',80,' ')
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
注意第二个调用,只传递一个字符值,虽然这是合法的,但是并不是程序员的原意。
是定默认实参的约束
既可以在函数声明也可以再函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。下面的例子是错误的:
//ff.h
int ff(int =0);
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
//ff.cc
#include"ff.h"
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
int ff(int i=0){
}//error
通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。
⑦
内联函数应该在头文件中定义,这一点不同于其他函数。
⑧
合成的默认构造函数
如果没有为一个类显式定义任何构造函数,编译器将自动为这个生成默认构造函数。
合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认
构造函数初始化这些成员。
⑨
重载函数
Record lookup(const Account&); //find by Account
Record lookup(const Phone&); //find by Phone
Record lookup(const Name&); //fing by Name
Record r1,r2;
r1=lookup(acct); //call version that takes an Account
r2=lookup(phone); //call version that takes a Phone
这里的三个函数共享一个函数名,但却是三个不同的函数。编译器将根据所传递的实参类型来判断调用的是哪个函数。
任何程序都仅有一个main函数的实例。main函数不能重载。
如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的,例如:
Record lookup(const Account&);
bool lookup(const Account&); //error:only return type is different
函数不能仅仅基于不同的返回类型而实现重载。
有些看起来不相同的形参表本质上是相同的:
1
//each pair declares the same function
2
Record lookup(const Account &acct);
3
Record lookup(const Account&);
4![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
5
typedef Phone Telno;
6
Record lookup(const Phone&);
7
Record lookup(const Telno&); //Telno and Phone are the same type
8![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
9
Record lookup(const Phone&,const Name&);
10
Record lookup(const Phone&,const Name& =" ");
11![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
12
Record lookup(Phone);
13
Record lookup(const Phone);
14![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
15
f(int *);
16
f(int *const); //redeclaration
在第一对函数声明中,第一个声明给它的形参命了名。形参名只是帮助文档,并没有修改形参表。
在第二对函数声明中,看似形参类型不同,但注意到Telno其实并不是新类型,只是Phone类型的同义词。
在第三对中,形参列表只有默认实参不同。默认实参并没有改变形参的个数。无论实参是由用户还是由编译器提供的,这个
函数都带有两个实参。
最后一对的区别仅在于是否将形参定义为const。这种差异并不影响传递至函数的对象;第二个函数声明被视为第一个的重复声明。
其原因在于实参传递的方式。复制形参时并不考虑形参是否为const—函数操作的知识副本。函数无法修改实参。结果,既可将
const对象传递给const形参,也可传递给非const形参,这两种形参并无本质区别。
值得注意的是,形参与const形参的等价性仅适用于非引用形参。有const引用形参的函数与有非const引用形参的函数是不同的。
类似地,如果函数带有指向const类型的指针形参,则与带有指向相同类型的非const对象的指针形参的函数是不同的。
如:
Record lookup(Account &acct);
Record lookup(const Account&); //new function
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
第四对不能基于指针本身是否为const来实现函数的重载,const用于修饰指针本身,而不是修饰指针所指向的类型。
当形参以副本传递时,不能基于形参是否为const来实现重载。