细嚼慢咽C++primer(3)——引用形参,内联函数,重载函数,指向函数的指针
1 引用形参
1)使用引用形参返回额外的信息
如何定义既返回一个迭代器又返回出现次数的函数?
向函数传递一个额外的引用实参,在函数内部对引用实参进行修改的同时,可以认为该引用实参返回了一个信息。
2) 在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。使用引用形参,函数可以直接访问实参对象,而无须复制它。
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
因为形参是引用,所以不复制实参,又因为形参是const引用,所以函数不能使用该引用来修改实参。
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const引用。
3)如果函数具有普通的非const引用形参,则显然不能通过const对象进行调用。因为此时函数可以修改传递进来的对象,这样就违背了实参的const特性。
容易忽略的是,调用这样的函数,传递一个右值或具有需要转换的类型的对象同样是不允许的。
问题的关键是非const引用形参只能与完全同类型的非const对象关联。
应该将不修改实参的形参定义为const引用。否则带来的问题是,普通的非const引用不能通过字符串字面值来调用这个函数。
应该将不需要修改的引用形参定义为const引用,普通的非const引用形参在使用时不太灵活,这样的形参既不能用const对象初始化,也不能用字面值或产生右值的表达式实参初始化。
4)传递指向指针的引用
int *&v1;
从右向左理解:v1是一个引用,与指向int型对象的指针相关联。
关键点:指针值的改变。
2 数组形参
1)通过引用传递数组
这种情况下,数组大小成为形参和实参类型的一部分,编译器检查数组实参的大小与形参的大小是否匹配。
2)多维数组的传递
多位数组的元素本身就是数组,除第一维数组以外的所有维的长度都是元素类型的一部分,必须明确指定。
void printValue(int (*matrix)[10], int rowSize);
3)非引用数组形参的类型检查只是确保实参是和数组元素具有同样类型的指针,而不会检查实参实际上是否指向指定大小的数组。
任何处理数组的程序都要确保程序停留咋数组的边界内。
3 main函数的参数
int main(int argc, char *argv[]){...}
int main(int argc, char **arg){...}
当将实参传递给主函数main是,argv中的第一个字符串通常是程序的名字,接下来的元素将额外的可选字符串传递给主函数main,。
4 静态局部对象
一个变量如果位于函数的作用域内,但生命期却跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为static(静态的)。
static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会被撤销,当定义静态局部对象的函数结束时,静态局部对象不会被撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。
5 内联函数
内联函数避免函数调用的开销。将函数指定为内联函数,就是将它在程序中每个调用点上”内联地“展开。
inline const string & shorterString(const string &s1, const string &s2);
内联函数的定义应该放在头文件中。
const引用:不可以通过这个变量的别名修改变量的值。
6 类的成员函数
编译器隐式地将在类内定义的成员函数当作内联函数。
类的成员函数可以访问该类的private成员。
调用成员函数时,实际上是使用对象来调用的,每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。
this指针:形参this初始化为调用函数的对象的地址。
total.same_isbn(trans);
编译器编译后:
Sales_item::same_isbn(&total, tran);
double Sales_item::avg_price() const
{
......
}
这行代码说明现在正在定义类Sales_item的函数avg_price,而且这是一个const成员函数,这个函数没有显式的形参,返回double类型的值。
构造函数:是特殊的成员函数,与类同名,而且没有返回类型。构造函数通常应确保其每个数据成员都完成了初始化。
class Sales_item {
public:
.......
Sales_item(): units_sold(0), revenue(0.0) { }
}
在冒号和花括号之间的代码称为构造函数的初始化列表。初始化列表显式地将units_sold和revenue初始化为0,并隐式地将isbn初始化为空串。
如果没有为一个类显式地定义任何构造函数,编译器将自动为这个类生成默认构造函数,又称为合成的默认构造函数。
类代码文件的组织:通常将类的声明放置在头文件中,在类外定义的成员函数则置于源文件中,类定义应置于type.h的文件中,type指在该文件中定义的类的名字,成员函数的定义则一般存储在与类同名的源文件中。
7 重载函数
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
要理解重载函数,必须理解如何定义一组重载函数和编译器如何决定对某一调用使用哪个函数。
Record lookup(Phone);
Record lookup(const Phone);
区别仅在于是否将形参定义为const。这种差异并不影响传递至函数的对象;第二个函数声明被视为第一个的重复声明,其原因在于实参传递的方式。复制形参时并不考虑形参是否为const——函数操纵的只是副本。函数无法修改实参,结果既可将const对象传递给const形参,也可传递非const形参,这两种形参并无本质区别。
值得注意的是,有const引用形参的函数与有非const引用形参的函数是不同的。类似地,如果函数带有指向const类型的指针形参,则与带有指向相同类型的非const对象的指针形参的函数不同。
重载确定的三个步骤:
1 候选函数
函数重载的第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。
2 选择可行函数
从候选函数中选择一个或多个函数,他们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行函数必须满足两个条件:1)函数的形参个数与该调用的实参个数相同;2)每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。调用带有默认实参的函数时可忽略这个实参,编译器自动将默认实参的值提供给被忽略的实参。因此某个调用拥有的实参可能比显式给出的多。
3 寻找最佳匹配
原则是实参类型与形参类型越接近则匹配越佳。
重载和const形参:
8 指向函数的指针(P237)仅当形参是引用或指针时,形参是否为const才有影响。
Record lookup(Account&);
Record lookup(const Account&);
const Account a(0);
lookup(a);
lookup(b);
如果形参是普通的引用,则不能将const对象传递给这个形参。如果传递了const对象,则只有带const引用形参的版本才是该调用的可行函数。
函数指针是指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型,函数类型由其返回类型以及形参表确定,而与函数名无关。
bool (*p)(const string &, const string &);
用typedef简化函数指针的定义:
typedef bool (*comFcn)(const string &, const string &);
该定义表示cmpFcn市一中指向函数的指针类型的名字。该指针类型为“指向返回bool类型并带有两个const string引用形参的函数的指针”。
直接引用函数名等效于在函数名上应用取地址操作符:
cmpFcn pf1 = lengthCompare;
comFcn pf2 = &lengthCompare;
将函数指针初始化为0,表示该指针不指向任何函数。
指向不同函数类型的指针之间不存在转换。
通过指针调用函数:指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数。