随笔 - 56, 文章 - 0, 评论 - 0, 引用 - 0
数据加载中……

第七章 函数


使用引用形参返回额外的信息
函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。例如:
 1vector<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{
 7    vector<int>::const_iterator res_iter=end;
 8    occur=0;
 9    for(;beg!=end;++beg)
10    {
11        if(*beg==value)
12        {
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
2bool isShorter(const string &s1,const string &s2)
3{
4    return s1.size()>s2.size();
5}


更灵活的指向const引用
如果函数具有普通的非const引用形参,则显然不能通过const对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的const特性。
但比较容易忽略的是,调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的:
int incr(int &val)
{
    
return ++val;
}

int main()
{
    
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
3string::size_type find_char(string &s,char c)
4{
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)
{
    
//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)
{
    
//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的函数的调用结果:
 1void do_swap(int &v1,int &v2)
 2{
 3
 4    int tmp=v2;
 5    v2=v1;
 6    v1=tmp;
 7}

 8void swap(int &v1,int &v2)
 9{
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)
{
    
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,' ')

注意第二个调用,只传递一个字符值,虽然这是合法的,但是并不是程序员的原意。

是定默认实参的约束
既可以在函数声明也可以再函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。下面的例子是错误的:
//ff.h
int ff(int =0);

//ff.cc
#include"ff.h"
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
 2Record lookup(const Account &acct);
 3Record lookup(const Account&);
 4
 5typedef Phone Telno;
 6Record lookup(const Phone&);
 7Record lookup(const Telno&);  //Telno and Phone are the same type
 8
 9Record lookup(const Phone&,const Name&);
10Record lookup(const Phone&,const Name& =" ");
11
12Record lookup(Phone);
13Record lookup(const Phone);
14
15f(int *);
16f(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
第四对不能基于指针本身是否为const来实现函数的重载,const用于修饰指针本身,而不是修饰指针所指向的类型。
当形参以副本传递时,不能基于形参是否为const来实现重载。

posted on 2009-08-12 15:51 八路 阅读(204) 评论(0)  编辑 收藏 引用 所属分类: C++primer 笔记


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理