1、return语句
1、没有返回值的函数
在返回值类型为void的函数中,return返回语句不是必需的,隐式的return发生在函数的最后一个语句完成时。
一般情况下,返回类型是void的函数使用return语句是为了引起函数的强制结束,这种return的用法类似于循环结构中的break语句的作用。
void do_swap(int &v1,int &v2)
{
int tmp = v1;
v1 = v2;
v2 = tmp;
}
void swap(int &v1,int &v2)
{
if (v1 == v2)
return;
return do_swap(v1,v2);
}
2、具有返回值的函数
bool str_subrange(const string &str1,const string &str2)
{
if (str1.size() == str2.size())
return str1 == str2;
string::size_type size = (str1.size() > str2.size() ? str1.size() : str2.size());
string::size_type i = 0;
while (i != size)
{
if (str1[i] != str2[i])
return; //Error return没有返回值
++i;<pre name="code" class="cpp" style="color: rgb(0, 0, 153);"> }
//此处应有return语句
}
千万不要返回:
1)局部对象的引用
2)指向局部对象的指针(悬垂指针!)
const string &manip(const string &s)
{
string ret = s;
return ret; //Warning
}
int factorial(int val)
{
if (val > 1)
return val * factorial(val - 1);
return 1;
}
递归函数必须定义一个终止条件:否则,函数就会“永远”递归下去,直到程序栈耗尽!
2、函数声明
void print(int *arr,size_t size);
应该在头文件中提供函数声明:
在源文件中声明函数的方法比较呆板而且容易出错。解决的方法是把函数声明放在头文件中,这样可以确保对于指定函数其所有声明保持一致。如果函数接口发
生变化,则只要修改其唯一的声明即可。
定义函数的源文件应该包含声明该函数的头文件。将提供函数声明头文件包含在定义该函数的源文件中,可使编译器能检查该函数的定义和声明时是否一致。
如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。因此,设计带有默认实参的函数,其中部分工作就是排列形参,应该使最少使用默认实参的形参排在最前面,最可能使用默认实参的形参排在最后。
//ff.h
int ff(int = 0);
//ff.cc
#include "ff.h"
int ff(int a = 0) //Error
{
//...
}
通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中。
3、局部对象
静态局部对象
一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为static(静态的)。
static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。
/*
*在第一次调用函数count_calls之前,cnt就已经创建并赋初值为0
*在执行函数 count_calls 时, 变量 ctr 就已经存在并且保留上次调用该函数时的值。
*/
size_t count_calls()
{
static size_t cnt = 0;
return ++cnt;
}
int main()
{
for (int i = 0; i != 10; ++i)
{
cout << count_calls() << endl;
}
return 0;
}
4、内联函数
使用短小函数的好处:
1)阅读和理解函数的调用,要比读一条用等价的表达式要容易得多。
2)如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多。
3)使用函数可以确保统一的行为,每个测试都保证以相同的方式实现。
4)函数可以重用,不必为其他应用重写代码。
但是这样的函数也存在者一些缺陷,调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作:
1)调用前先保存寄存器,并且要在返回时恢复
2)复制实参
3)程序必须转向一个新的位置执行
1、使用内联函数避免函数调用的开销
将函数指定为inline函数,就是将它在程序中每个调用点上“内联地”展开。从而消除了把表达式写成函数的额外执行开销。
inline const string &shorterString(const string &s1,const string &s2)
{
return (s1.size() < s2.size() ? s1 : s2);
}
内联说明(inline)对于编译器来说只是一个建议,编译器可以选择忽略这个建议;一般来说,内联机制使用与优化小的,只有几行而且经常被调用的函数。
2、把内联函数写入头文件
内联函数要在头文件中定义,这一点不同于其他函数!
在头文件中加入或修改内联函数时,使用了该头文件的所有源文件都必须重新编译!因为修改此处的头文件相当于修改了各个源文件!
5、类的成员函数函数原型必须在类中定义,而函数体既可以在类中定义,也可以在类外定义,一般比较短小的函数定义在类的内部。
编译器隐式的将在类内定义的成员函数当作内联函数。
类的成员函数可以访问该类的private成员。
class Sales_item
{
public:
double avg_price() const;
bool same_isbn(const Sales_item &rhs) const
{
return isbn == rhs.isbn;
}
private:
std::string isbn;
unsigned int units_sold;
double revenue;
};
每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象绑定在一起。而这个形参就是this指针!const成员函数的引入
bool Sales_item::same_isbn(const Sales_item *const this,
const Sales_item &rhs) const
{
return (this -> isbn == rhs.isbn);
}
使用这种方式的const函数称为常量成员函数。由于this是指向const对象的指针,const成员函数不能修改调用该函数的对象,因此,函数same_isbn只能读取而不能修改调用他们的对象的数据成员。const对象、指向const对象的指针或者引用只能用于调用其const成员函数,如果尝试用它们来调用非const成员函数则是错误的。
double Sales_item::avg_price() const
{
if(units_sold)
return revenue/units_sold;
else
return 0;
}
使用作用域操作符指明函数avg_price是在类Sales_itemd的作用域范围内定义的。
构造函数
Sales_item::Sales_item():units_sold(0),revenue(0){};
1)通常构造函数会作为类的接口的一部分,因此必须将构造函数定义为public的。
2)在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开头。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。
3)如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。由编译器创建的默认构造函数通常称为合成的默认构造函数。
对于具有类类型的成员,如isbn,则会调用该成员所属类自身的默认构造函数实现初始化。而内置类型成员的初值却要依赖于对象如何定义,如果对象定义为全局对象,或定义为静态局部对象,则将这些成员初始化为0,不然,则不提供这些成员的初始化工作,这些成员没有初始化!
合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认构造函数初始化这些成员。类代码文件的组织
通常情况下,将类的声明放置在头文件中,在类外定义的成员函数放置在源文件中,C++程序员习惯使用一些简单的规则给头文件及其关联的类定义代码命名。类定义应置于名为type.h或type.H的文件 中,type指在该文件中定义的类的名字。成员函数的定义则一般存储在与类同名的源文件中。依照这些规则,我们将类Sales_item放在名为Sales_item.h的文件中定义。任何需使用这个类的程序,都必须包含这个头文件。而Sales_item的成员函数的定义则应该放在名为Sales_item.cc的文件中。这个文件同样也必须包含Sales_item.h头文件。
6、重载函数Record lookup(const Account&);
bool lookup(const Account&); //error
函数不能仅仅基于不同的返回类型而实现重载。
<span style="font-family: Arial, Helvetica, sans-serif;"></span><pre code_snippet_id="545516" snippet_file_name="blog_20141208_15_6347098" name="code" class="cpp" style="color: rgb(85, 85, 85); line-height: 35px;"><span style="font-family: Arial, Helvetica, sans-serif;">const phone&,const Name&);</span>
Record lookup(const phone&,const Name&=""); //默认实参不同,并未改变形参的个数和类型。
Record lookup(phone);
Record lookup(const phone);
复制实参时并不考虑形参是否为const,函数操纵的只是实参的副本,函数无法修改实参。结果既可以将const对象传递给const形参,也可以传递给非const形参,这两种形式并无区别。
void print(const string &);
void print(double);
void fooBar(int ival)
{
void print(int);
print("Value: "); //error <span style="font-family: SimSun;">print(const string &)已经被隐藏</span>
print(ival); //OK 调用<span style="font-family: SimSun;">void print(int);</span>
print(3.14); //OK <span style="font-family: SimSun;">调用</span><span style="font-family: SimSun;">void print(int)</span>
}
重载确定的三个步骤1)候选函数
第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数,候选函数是与被调用函数同名的函数。
2)选择可行函数
选择可行函数必须满足两个条件:函数的形参个数与该调用的实参个数相同;每一个实参的类型必须与对引形参的类型匹配,或者可以被隐式转换成为对应的形参类型。
如果没有找到可行函数,则该调用错误。
3)寻找最佳匹配
实参类型与形参类型越接近则匹配越佳,因此,实参类型与形参类型之间的精确匹配比需要转换的匹配要好。
为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
1)精确匹配。实参与形参类型相同。
2)通过类型提升实现的匹配
3)通过标准转换实现的匹配
4)通过类类型转换实现的匹配
需要类型提升或转换的匹配
1)较小的整型提升为int型:对于任意整型的实参值,int型版本都是由于short版本的较佳匹配,即使从形式看short版本匹配较佳
void ff(int);
void ff(short);
ff('a'); //匹配ff(int);
2
)通过提升实现的转换要优于其他标准转换。如:对于char型实参来说,有int型形参的函数是优于有double型形参的函数较佳匹配。