前面几章讲的都是非常琐碎的小事物,现在终于到了函数,所谓函数,就是一个命名的代码块,可以通过调用函数执行相应的代码。
1 函数基础
函数定义包括四部分:返回类型,函数名字,由0或多个形参组成的列表以及函数体。
通过调用运算符()来执行函数。函数调用的过程:首先是实参初始化函数对应的形参,接着将控制权转移给被调函数,执行被调函数,当遇到return 语句时函数结束执行过程,首先返回return 语句中的值,接着将控制权从被调函数转回主调函数。
返回类型。特殊的返回类型void ,不返回任何值。函数的返回类型不能是数组或函数类型,但可以是指向数组或函数的指针。
局部对象:形参和函数体内部定义的变量。
局部静态对象:static 类型对象,在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止才销毁。
函数声明:也称函数原型,函数声明与函数的定义非常类似,唯一区别是函数声明无须函数体,用一个分号代替。建议在头文件中声明,源文件中定义。
2 参数传递
参数的传递主要有两种:传值和传引用。传值就是将实参的值拷给形参,两者相互独立。传引用时,形参是它对应实参的别名,是同一个对象。
传值参数:函数对形参所做的所有操作都不会影响实参。对于指针形参,拷贝的是指针的值,此时两个指针是不同的指针,可以修改指针指向对象的值,但是实参指针不会改变。C++ 建议使用引用类型的形参代替指针。
传引用:对引用的操作实际是作用在引用所指的对象上。拷贝大的类类型对象或容器对象比较低效,所以应尽量避免直接拷贝他们,这时使用引用形参是好主意,如:
bool isShorter(const string &s1, const string &s2)
{
return si.size() < s2.size();
}
如果函数无须改变引用形参的值,最好将其声明为常量const 引用。
一个函数只能返回一个值,如果需要同时返回多个值,引用形参可以实现,就是传递参数时传入一个引用形参,不过它得在函数外部定义过。
当传入的const 形参是顶层const 时,使用实参初始化时会忽略掉顶层const,如:
void fcn(const int i)
void fcn(int i)
会出现错误,重定义函数。
数组形参。数组的两个性质:不允许拷贝数组,使用数组时会转换成指针,因此以下三个函数声明等价:
void print(const int *);
void print(const int []);
void print(const int [10]);
因为数组是以指针形式传给函数的,所以一开始函数不知道数组的确切尺寸,调用者需提供一些额外信息。管理指针形参的三种技术:
(1) 使用标记指定数组长度。C风格字符串最后跟一个空字符
void print(const char *cp)
{
if (cp)
while (*cp)
cout<<*cp++;
}
(2) 使用标准库规范,传递指向数组首元素和尾后元素的指针
void print(const int *beg, const int *end)
{
while (beg != end)
cout<<*beg++<<endl;
}
(3) 显式传递一个表示数组大小的形参
void print(const int ia[], size_t size)
{
for (size_t i = 0; i != size; ++i)
{
cout<<ia[i]<<endl;
}
}
数组引用形参,如:
void print(int (&arr)[10])
{
for (auto elem : arr)
cout<<elem<<endl;
}
传递多维数组,如:
void print(int (*matrix)[10],int rowsize)
{
}
main:处理命令行选项
int main(int argc, char *argv[])
第一个形参argc 表示数组中字符串的数量,第二个形参argv 是一个数组,它的元素是指向C风格字符串的指针。
使用argv中的实参时,要注意可选的实参从argv[1] 开始,第一个(argv[0])保存的是程序的名字。
有时我们无法预知应该向函数传递几个实参,这时就需要处理可变实参的函数,两种方法,如果所有实参类型相同,可以传递一个名为initializer_list的标准库类型,如果实参类型不同,需要编写一个可变参数模板。
3 返回类型与return 语句
return 语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。
没有返回值的return 语句只能出现在返回类型为void 的函数中。
有返回值的return 语句,返回值的类型必须与函数的返回类型相同,或者能隐式转换成函数的返回类型。
对于正常的非引用的值会返回一个对象的副本或者未命名的临时对象,而返回引用不会真正拷贝对象,所以返回引用效率更高一些。
函数终止意味着局部变量的引用或指针将指向不再有效的内存区域。因此不要返回局部对象的引用或指针。
4 函数重载
重载函数:同一作用域内的几个函数名字相同但形参列表不同。
因为顶层const 不会影响传入函数的对象,所以一个拥有顶层const 的形参无法和一个没有顶层const 的形参区分开来。而如果形参是某种类型的指针或引用,则根据是否是常量对象可以实现函数重载,如:
void lookup(phone);
void lookup(const phone);
// 重复声明lookup
void lookup(Account &);
void lookup(const Account &);
// 重载
const_cast 与重载
现有函数定义如下:
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
这个函数的参数和返回类型都是const string的引用,如果想要传入非常量可以这样:
string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string &>(s1), const_cast<const string &>(s2));
return const_cast<string &>(r);
}
默认实参如:
typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, sz backgrnd = ' ');
函数调用的缺点:函数调用一般比求等价表达式的值要慢一点。而内联函数可避免函数调用的开销。
内联机制用于优化规模较小,流程直接,频繁调用的函数。
6 函数指针
函数指针指向的是函数而非对象。
声明一个指向函数的指针,只要用指针替换函数名就好,如:
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);
当把函数名作为一个值使用时,该函数会自动的转换成指针。
pf = lengthCompare;