![e15650e1-6213-eb11-8da9-e4434bdf6706.png](http://p05.5ceimg.com/content/e15650e1-6213-eb11-8da9-e4434bdf6706.png)
第六章 函数
6.1 函数基础
局部静态对象
局部静态对象(local static object)在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被摧毁,在此期间对象所在的函数结束执行也不会对它有影响
size_t count_calls(){
static size_t ctr = 0;
return ++ctrj;
}
int main(){
for (size_t i = 0; i != 10; ++i)
cout << count_calls() << endl;
return 0;
}
6.2 参数传递
6.2.1 传值参数
当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时,对变量的改动不会影响初始值
指针形参
当执行指针拷贝操作是,拷贝的是指针的值,拷贝之后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值
#include<iostream>
using namespace std;
void reset(int *ip){
*ip = 0; // 改变了指针ip所指对象的值
}
int main(){
int i = 42;
reset(&i);
cout << "i = " << i << endl;
return 0;
}
6.2.2 传引用参数
引用形参绑定初始化它的对象,在函数调用过程中,形参仅仅是实参的另一个名字,在函数内部改变形参,也会改变实参
void reset(int &i) {
i = 0;
}
int main(){
int j = 42;
reset(j);
cout << j << endl;
return 0;
}
使用引用避免拷贝
拷贝大的类类型对象或者容器对象比较低效,有的类类型(包括IO类型在内)根本不支持拷贝操作,当各种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象
例如对string
对象的访问
bool isShorter(const string &s1, const string &s2) {
return s1.size() < s2.size();
}
使用引用形参返回额外信息
一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径
6.3.3 返回数组指针
数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用。
直接的方法是使用类型别名
typedef int arrT[10]; // arrT是一个类型别名,它表示的是含有10个整数的数组
using arrT = int[10]; // arrT的等价声明
arrT* func(int i); // func 返回一个指向含有10个整数的指针
声明一个返回数组指针的函数
在声明 func
时不使用类型别名,我们必须牢记被定义的名字后面数组的维度
int arr[10]; // arr是一个含有10个整数的数组
int *p1[10]; // p1是一个含有10个指针的数组
int (*p2)[10] = &arr; // p2是一个指针,它指向含有10个整数的数组
int (*func(int i))[10];
func(int i)
表示调用func
函数需要一个int
类型的实参(*func(int i))
意味着我们可以对函数调用的结果执行解引用操作(*func(int i))[10]
表示解引用将得到一个大小是10的数组int (*func(int i))[10]
表示数组中的元素是int
类型
使用尾置返回类型
尾置类型跟在形参后面并以一个 ->
符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个 auto
auto func(int i) -> int(*)[10];
使用 decltype
如果知道函数返回的指针指向那个数组,可以使用 decltype
关键字声明返回类型。
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
// 返回一个指针,该指针指向含有5个整数的数组
decltype(odd) *arrPrt(int i)
{
return (i % 2) ? &odd : &even;
}
有一个地方需要注意的是:decltype
并不负责吧数组转换为对应的指针,所以decltype
的结果是个数组,想要表示arrPtr
返回指针还必须在函数声明时加一个*
符号
6.4 函数重载
- 编译器根据实参的类型确定调用哪一个函数
- 对于重载的函数来说,它们应该在形参数量或形参类型上有所不同
- 不允许两个函数除了返回类型外其他要素都相同
- 顶层
const
不影响传入函数的对象,一个拥有顶层const
的形参无法和另一个没有顶层const
的形参区分开来
Record lookup(Phone); Record lookup(const Phone); // 重复定义了 Record lookup(Phone)
Record lookup(Phone); Record lookup(Phone const); // 重复定义了 Record lookup(Phone*)
如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const
是底层的
Record lookup(Account&); // 函数作用于Acount的引用 Record lookup(const Account&); // 新函数,作用于常量引用
Record lookup(Account); // 函数作用于Acount的指针 Record lookup(const Account); // 新函数,作用于常量的指针
6.5 特殊用途语言特性
6.5.1 默认实参
默认实参:调用含有默认实参的函数时,可以包含该实参,也可以省略该实参
typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
使用默认实参调用函数: 只要在调用函数的时候省略该实参就可以了
函数调用时按其位置解析,默认实参负责填补函数调用缺少的尾部实参
默认实参声明: 函数的声明通常是放在头文件中,并且一个函数只声明一次,但是多次声明同一个函数也是合法的。不过有一点需要注意,在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,并且该形参右侧的所以形参必须都有默认值。
// 表示高度和宽度的形参没有默认值
string screen(sz, sz, char = ' ');
// 不能修改一个已经存在的默认值
string screen(sz, sz, char = '*'); // 错误:重复声明
string screen(sz = 24, sz =80, char); // 正确:添加默认实参
课后练习
pratice 6.4
编写一个与用户交互的函数,要求用户输入一个数字,计算生成该数字的阶乘。在
main
函数中调用该函数
#include<iostream>
using namespace std;
/*
* 阶乘函数
*/
int fact (int val){
int ret = 1;s
while(val > 1)
ret *= val--;
return ret;
}
/*
* 绝对值函数
*/
int abs(int val){
return (val>=0?val:-val);
}
int main(){
int val;
cin >> val;
cout << val << "! is " << fact(val) << endl;
return 0;
}
pratice 6.10
编写一个函数,使用指针形参交互两个整数的值。
指针形参参考
#include<iostream>
using namespace std;
void swap(int *p1, int *p2){
int temp = *p1;
*p1 = *p2;
*p2 = *p1;
}
int main(){
int a=23, b=32;
swap(a, b);
cout << a << ' ' << b << endl;
return 0;
}
pratice 6.11
改写并验证你自己的
reset
函数,使其作用于引用类型的参数
#include<iostream>
using namespace std;
void reset(int &i) {
i = 0;
}
int main() {
int j = 42;
reset(j);
cout << j << endl;
return 0;
}
pratice 6.25
编写一个main
函数,令其接受两个实参。把实参的内容连接成一个string
对象并输出出来
#include<iostream>
using namespace std;
int main(int argc, char **argv) {
string s;
for(int i=0;i<argc;i++){
s.append(argv[i]);
s.append(" ");
}
cout << s << endl;
cout << argc << endl;
}
pratice 6.36
编写一个函数声明,使其返回数组的引用并且该数组包含10个string
对象。不要使用尾置返回类型、decltype
或者类型别名
#include<iostream>
#include<string>
using namespace std;
string odd[10] = {"life", "is", "like", "a", "box", "of", "chocolate"};
string even[10] = {"Do", "not", "ANSWER", "!", "Do", "not", "ANSWER", "!"};
int main(){
string (*func(int i))[10];
int i;
cin >> i;
string (*a)[10] = func(i);
for(int j=0;j<10;j++)
cout << (*a)[j] << endl;
cout << endl;
return 0;
}
string (*func(int i))[10]{
return (i % 2) ? &odd : &even;
}
输入数字i
,i
为奇数则输出第一个字符串,否则输出第二个字符串
pratice 6.37
为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用decltype
关键字
#include<iostream>
#include<string>
using namespace std;
typedef string arrStr[10];
string odd[10] = {"life", "is", "like", "a", "box", "of", "chocolate"};
string even[10] = {"Do", "not", "ANSWER", "!", "Do", "not", "ANSWER", "!"};
int main(){
string (*func(int i))[10]; // 直接声明
// arrStr* func1(int i); // 类型别名声明
// decltype(odd) *func2(int i); // decltype 声明
// auto func3(int i) -> string(*)[10];
int i;
cin >> i;
string (*a)[10] = func(i);
for(int j=0;j<10;j++)
cout << (*a)[j] << endl;
cout << endl;
return 0;
}
// 直接声明
string (*func(int i))[10]{
return (i % 2) ? &odd : &even;
}
// 类型别名声明
arrStr* func1(int i){
return (i % 2) ? &odd : &even;
}
// decltype 声明
decltype(odd) *func2(int i){
return (i % 2) ? &odd : &even;
}
// 尾置返回类型
auto func3(int i) -> string(*)[10]
{
return (i % 2) ? &odd : &even;
}
pratice 6.38
修改arrPtr
函数,使其返回数组的引用
直接在前两题的基础上进行修改,主要是将*
修改为&
,return
语句中的&
去掉,调用后的数组使用不要*
解引用
#include<iostream>
#include<string>
using namespace std;
typedef string arrStr[10];
string odd[10] = {"life", "is", "like", "a", "box", "of", "chocolate"};
string even[10] = {"Do", "not", "ANSWER", "!", "Do", "not", "ANSWER", "!"};
int main(){
string (&func(int i))[10]; // 直接声明
// arrStr& func1(int i); // 类型别名声明
// decltype(odd) &func2(int i); // decltype 声明
// auto func3(int i) -> string(&)[10];
int i;
cin >> i;
string (&a)[10] = func(i);
for(int j=0;j<10;j++)
cout << a[j] << endl;
cout << endl;
return 0;
}
// 直接声明
string (&func(int i))[10]{
return (i % 2) ? odd : even;
}
// 类型别名声明
arrStr& func1(int i){
return (i % 2) ? odd : even;
}
// decltype 声明
decltype(odd) &func2(int i){
return (i % 2) ? odd : even;
}
// 尾置返回类型
auto func3(int i) -> string(&)[10]
{
return (i % 2) ? odd : even;
}
![e45650e1-6213-eb11-8da9-e4434bdf6706.png](http://p03.5ceimg.com/content/e45650e1-6213-eb11-8da9-e4434bdf6706.png)