第8讲——函数探幽

前面的笔记都是行云流水,因为前面几章看过了,从这一讲开始,每一讲都是现学现做笔记的。

 

没人是天生的赢家,只有后天的loser。

 

由上一讲中的知识,我们了解到许多有关C++函数的知识,但需要学习的知识还很多。C++还提供许多新的函数特性,使之有别于C语言。

新特性包括内联函数、按引用传递变量、默认的参数值、函数重载(多态)以及模板函数

这一讲中,我们将介绍C++在C语言基础上新增的特性,比前面各讲都多,这是我们进入(++)领域的重要一步。

 

【内联函数】

这是为提高程序运行速度所做的一项改进。

内联函数与常规函数的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。

要了解内联函数与常规函数之间的区别,必须深入到程序内部。

我们知道编译过程的最终产品是可执行程序——由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时(如有循环或分支语句时),将跳过一些指令,向前或向后跳到特定地址。常规函数调用也使程序调到另一个地址(函数的地址),并在函数结束时返回。

下面更详细地介绍这一过程的典型实现。

执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值放入到寄存器中),然后跳回到地址被保存的指令处(这与阅读文章时停下来看脚注,并在阅读完脚注后返回到以前阅读的地方类似)。来回跳跃位置意味着以前使用函数时,需要一定的开销。

此时,内联函数出现了,它提供了另一种选择。内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。如果程序在10个不同的地方调用同一个内联函数,则该程序将包含该函数代码的10个副本。

当然,我们不能盲目地使用内联函数,相反,我们应有选择地使用内联函数。

如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程的很小一部分。如果代码执行时间很短,则内联调用就可以节省非内联调用使用的大部分时间。另一方面,由于这个过程相当快,因此尽管节省了该过程的大部分时间,但节省的时间绝对值并不大,除非该函数经常被调用。

 

上面说了一堆理论知识,那我们如何使用这项特性呢??

  • 在函数定义前加上关键字inline

除此之外,我们通常省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。

 

  • 知识点:内联函数不能递归

 

下面是一个计算参数的平方的程序:

#include<iostream>
//一个内联函数定义
inline double square(double x)
{
	return x*x;
}

int main()
{
	using namespace std;
	double a = square(5.0);
	cout<<"a = "<<a<<endl;
}

我们注意到上述程序中整个内联函数的定义只占用了几行,如果函数定义占用多行(假定没有使用冗长的标识符),则将其作为内联函数就不太合适。

上述程序亦表明,内联函数和常规函数一样,也是按值来传递参数的。

如果参数为表达式(如4.5+7.5),则函数将传递表达式的值(为12)。这使得C++的内联功能远远胜过C语言的宏定义。

 

 

【默认参数】

这将又是C++的另一项新内容,让我们慢慢欣赏吧。

默认参数——当函数调用中省略了实参时自动使用的一个值。

上面是书本的解释,我的看法如下:

假设我们创建一个函数如下:

void wow(int n)

如若我们要调用这个函数,我们应该在调用函数中这样调用:wow(2);或int a=2;wow(a);,调用语句的本质是实参不能为空。而如果我们设置成n有默认值为1:

void wow(int n = 1)

哈哈,这时我们可以在调用函数中这样调用:wow();,此时实参为空,但由于形参n有默认值为1,那么此调用语句等价于:wow(1); 。当然如果我们调用语句为:wow(2);,那么形参n就是2,而不是1。

好了,我的讲解完了,是不是仿佛知道了默认参数的用处啊。

  • 我们只能通过函数原型设置参数的默认值,方法是将值赋给原型中的参数。这是由于编译器通过查看原型来了解函数所使用的参数数目,因此函数原型也必须将可能的默认参数告知程序。同时,只需要函数原型指定默认值,而函数定义与没有默认参数时完全相同。

每每学习一个知识点,总有它的要求:对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。

下面我们举几个例子:

int chico(int n, int m = 6, int j);		//错误 
int harpo(int n, int m = 4, int j = 5);
//harpo()原型允许调用该函数时提供1个、2个或3个参数:
beeps = harpo(2);			//same as harpo(2,4,5); 
beeps = harpo(1,8);			//same as harpo(1,8,5);
beeps = harpo(8,7,6); 		//没有使用默认参数 

除此,我们还要知道实参按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数。下面的调用就是一个反例:

beeps = harpo(3, ,8);		//错误,doesn't set m to 4

看到这,你可能吐槽默认参数的作用甚微,是的,它并非编程方面的重大突破,它只是提供了一种便捷的方式。而且,在设计类时我们将发现,通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量。

 

 

【函数重载】

在了解函数重载之前,我们先看一下函数多态。

函数多态是C++在C语言的基础上新增的功能。

学完了默认参数,我们知道它让我们能够使用不同数目的参数调用同一个函数,而函数多态(函数重载)让我们能够使用多个同名的函数。

术语“多态”:指的是有多种形式,因此函数多态允许函数可以有多种形式。

类似地,术语“函数重载”:指的是可以有多个同名的函数,因此对名称进行了重载。

这两个术语指的是同一回事,但我们通常使用函数重载。

简而言之,上面一大堆废话就是说:我们可以通过函数重载设计一系列同名的函数。

 

有多个同名的函数又怎么样??有什么用吗?

当然有用啦!!

这些同名函数使用了不同的参数列表,但它们却完成相同的工作。

 

所以函数重载的关键是函数的参数列表——也称为函数特征标。

两个函数的特征标相同:参数数目相同、参数类型相同、参数的排列顺序相同。

C++允许定义名称相同的函数,条件是它们的特征标不同。

  • 请记住:是特征标,而不是函数类型使得可以对函数重载。其实,这个我们仔细想一想就明白了,试想,若两个同名函数的特征标相同,那么我们调用语句与这两个函数都匹配,编译器就不知道调用哪个了,所以特征标才是函数重载的关键。

 

关于重载的几个注意点。

double cube(double x);
double cube(double &x);

这两个函数的特征标看上去不同,然而它们不能共存。因为如果有调用语句:cube(a);,参数a与double x原型和double &a原型都匹配,因此为避免这种混乱,编译器将把类型引用和类型本身视为同一个特征标。

/*	匹配函数时,并不区分const和非const变量	*/ 
void dribble(char *bits);		//用于常规指针 
void dribble(const char *bits);	//用于const指针 
//下面两个函数不能重载 
void dabble(char *bits);
void drivel(const char *bits);
//下面给出了各种函数调用对应的原型:
const char p1[20] = "How's the weather?";
char p2[20] = "How's business?";
dribble(p1);	//dribble(const char *);
dribble(p2);	//dribble(char *);
dabble(p1);		//no match
dabble(p2);		//dabble(char *)
drivel(p1);		//drivel(const char *)
drivel(p2); 	//drivel(const char *)

dribble()函数有两个原型,一个用于const指针,另一个用于常规指针,编译器将根据实参是否为const来决定使用哪个类型。dabble()函数只与带非const参数的调用匹配,而drivel()函数可以与带const或非const参数的调用匹配,只因为将非const值赋给const变量是合法的,但反之则是非法的。

 

我们何时使用函数重载呢??

  • 仅当函数基本上执行相同的任务,但使用不同形式的数据时。

 

 

【函数模板】

这又是C++新增的一项特性。

函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int或double)替换。

通过将类型作为参数传递给模板,可使编译器生成该类型的函数。

由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。

由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。

我承认这些都是废话。。。

 

下面我们来学习为何需要函数模板以及其工作原理。

看下面这个交换两个int值的程序:

#include<iostream>
using namespace std;

void swap(int *a, int *b)
{
	int t;  t = *a;	 *a = *b;	*b = t;
}
int main()
{
	int n1 = 3, n2 = 4;
	swap(n1, n2);
}

那么,如果我们要交换两个double值,我们会想到复制swap函数的代码,并用double替换所有的int。同理,如果需要交换两个char值,我们可以再次使用同样的技术。显然,进行这种修改将浪费宝贵的时间,且容易出错。这时,我们在想有没有什么好的办法完成这项任务呢?

当然有咯!!C++的函数模板功能能自动完成这一过程,可以节省时间,而且更可靠。

 

函数模板允许以任意类型的方式来定义函数。

例如,我们可以建立一个交换模板:

template <typename T>	//将类型命名为T,关键字typename可以用class代替 
void swap(T *a, T *b)
{
	T t;  t = *a;	 *a = *b;	*b = t;
}

我们要知道,模板并不创建任何函数,而只是告诉编译器如何定义函数。

需要交换int的函数时,编译器将按模板模式创建这样的函数,并用int代替T。同样,需要交换double的函数时,编译器将按模板模式创建这样的函数,并用double代替T。

 

更常见的情形是,将模板放在头文件中,并在需要使用模板的文件中包含头文件。

 

当我们需要多个对不同类型使用同一种算法的函数时,可使用模板。

但并非所有的类型都使用相同的算法,这是,我们可以像重载常规函数定义那样重载模板定义。

和常规重载一样,被重载的模板的函数特征标必须不同。如下面的程序:

template <typename T>
void swap(T *a, T *b);

template <typename T>
void swap(T a[], T b[], int n); //并非所有的模板参数都必须是模板参数类型(T)

template <typename T>
void swap(T *a, T *b) 
{
	T t;  t = *a;	 *a = *b;	*b = t;
}

template <typename T>
void swap(T a[], T b[], int n) 
{
	T t;
	for(int i = 0; i < n; i++)
	{
		t = a[i];
		a[i] = b[i];
		b[i] = t;
	}  
}

上述程序中新增了一个交换模板,用于交换两个数组中的元素。原来的模板的特征标为(T &, T &),而新模板的特征标为(T[], T[], int)。

注意,在后一个模板中,最后一个参数的类型为具体类型(int),而不是泛型。并非所有的模板参数都必须是模板参数类型。

 

看到了模板函数的好处,它的局限性也比较显著。

假设有如下模板函数:

void f(T a, T b) 
{
	...
}

函数体内总会执行一些操作,这是不可否认的。假如,函数体内定义了赋值,但如果T为数组,这个模板函数就没用;再比如,函数体内定义了<,但如果T为结构,这个模板也没用。此外,还有好多好多。

总之,编写的模板函数很可能无法处理某些类型。

但我们可以想办法解决这个困难。例如,将两个包含位置坐标的结构相加是有意义的,虽然没有为结构定义运算符+。

此时,我们有两种解决方案:①C++允许我们重载运算符+,以便能够将其用于特定的结构或类,这样使用运算符+的模板便可处理重载了运算符+的结构;②为特定类型提供具体化的模板定义。

下面我们将来介绍第二种解决方案,因为第一种解决方案等我们学完运算符重载就会了啊!

 

我们来看这样一个结构:

struct job{
	char name[20];
	double salary;
	int floor;
};

我们要交换两个这种结构的内容可以使用上面的第一种模板,因为C++允许将一个结构赋给另一个结构。

然而,如果我们只想交换salary成员,其他成员不做交换,那么就需要使用不同的代码,但swap()的参数将保持不变(两个job结构的引用),因此无法使用模板重载来提供其他的代码。

然而,可以提供一个具体化函数定义(称为显示具体化),这个函数包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。

显然,具体化是需要一些特别说明的:

  •  对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版本;
  • 显示具体化的原型和定义应以template<>打头,并通过名称来指出类型;
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。

下面给出交换job结构的非模板函数、模板函数和具体化的原型:

//非模板函数
void swap(job &, job &);

//模板函数
template <typename T>
void swap(T &, T &);

//显示具体化
template <> void swap<job>(job &, job &);  //<job>可不写,因为参数列表已经表明这是job的一个具体化

上面总结了:如果有多个原型,则编译器在选择原型时,非模板版本优先于显示具体化和模板版本,而显示具体化优先于使用模板生成的版本。

例如,在下面的代码中,第一次调用swap时使用通用版本,而第二次调用使用基于job类型的显示具体化版本。

//模板函数
template <typename T>
void swap(T &, T &);

//显示具体化
template <> void swap<job>(job &, job &);

int main()
{
	double u, v;
	...
	swap(u,v);	//使用模板函数 
	job a, b;
	...
	swap(a,b);	//使用基于job类型的显示具体化 
} 

下面这个程序演示了显示具体化的工作方式:

 1 // twoswap.cpp -- specialization overrides a template
 2 #include <iostream>
 3 template <typename T>
 4 void Swap(T &a, T &b);
 5 
 6 struct job
 7 {
 8     char name[40];
 9     double salary;
10     int floor;
11 };
12 
13 // explicit specialization 
14 template <> void Swap<job>(job &j1, job &j2);
15 void Show(job &j);
16 
17 int main()
18 {
19     using namespace std;
20     cout.precision(2);
21     cout.setf(ios::fixed, ios::floatfield);
22     int i = 10, j = 20;
23     cout << "i, j = " << i << ", " << j << ".\n";
24     cout << "Using compiler-generated int swapper:\n";
25     Swap(i,j);    // generates void Swap(int &, int &)
26     cout << "Now i, j = " << i << ", " << j << ".\n";
27 
28     job sue = {"Susan Yaffee", 73000.60, 7};
29     job sidney = {"Sidney Taffee", 78060.72, 9};
30     cout << "Before job swapping:\n";
31     Show(sue);
32     Show(sidney);
33     Swap(sue, sidney); // uses void Swap(job &, job &)
34     cout << "After job swapping:\n";
35     Show(sue);
36     Show(sidney);
37     // cin.get();
38     return 0;
39 }
40 
41 template <typename T>
42 void Swap(T &a, T &b)    // general version
43 {
44     T temp;
45     temp = a;
46     a = b;
47     b = temp;
48 }
49 
50 // swaps just the salary and floor fields of a job structure
51 
52 template <> void Swap<job>(job &j1, job &j2)  // specialization
53 {
54     double t1;
55     int t2;
56     t1 = j1.salary;
57     j1.salary = j2.salary;
58     j2.salary = t1;
59     t2 = j1.floor;
60     j1.floor = j2.floor;
61     j2.floor = t2;
62 }
63 
64 void Show(job &j)
65 {
66     using namespace std;
67     cout << j.name << ": $" << j.salary
68          << " on floor " << j.floor << endl;
69 }
View Code

 

 

上面讲完了显示具体化,我们有了解决模板局限性的问题。

现在,我们需要掌握实例化和具体化。

我们知道,只有一个函数模板本身不会生成函数定义,因为它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例。例如,函数调用swap(i,j)导致编译器生成swap()的一个实例,该实例使用int类型。

模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化,因为编译器之所以知道需要进行定义,是由于程序调用swap()函数时提供了int参数。

也就是说,编译器能通过隐式实例化,来使用模板生成函数定义。

除了隐式实例化,还有显示实例化。这意味着可以直接命令编译器创建特定的实例,如swap<int>()。

这种语法是,声明所需的种类——用<>符号指示类型,并在声明前加上关键字template:

template void swap<int>(int, int); 	//显示实例化 

实现了这种特性的编译器看到上述声明后,将使用swap()模板生成一个试用int类型的实例。也就是说,该声明的意思是“使用swap()模板生成int类型的函数定义”。

显示具体化使用下面两个等价的声明之一:

template <> void swap<int>(int &, int &); 	//显示具体化 
template <> void swap(int &, int &);		//显示具体化

这与显示实例化的区别是,这些声明的意思是“不要使用swap()模板来生成函数定义,而应使用专门为int类型显示地定义的函数定义”。这些原型必须有自己的函数定义。显示具体化声明在关键字template后包含<>,而显示实例化没有。

提示:试图在同一个文件(或转换单元)中使用同一种类型的显示实例和显示具体化将出错。

还可通过在程序中使用函数来创建显示实例化:

template <typename T>
T Add(T a, T b)				//传值 
{
	return a + b;
} 
...
int m = 6;
double x = 10.2;
cout << Add<double>(x,m) <<endl;	//显示实例化

显然,这里的模板与函数调用Add(x,m)不匹配,因为该模板要求两个函数参数的类型相同。但通过使用Add<double>(x,m),可强制为double类型实例化,并将参数m强制转换为double类型,以便与函数Add<double>(double,double)的第二个参数匹配。

需要注意的是,若参数列表中是引用(即T add(T &a, T &b)),那么上述处理将不起作用,因为第二个形参的类型为double &,不能指向int变量m。

  • 隐式实例化、显示实例化和显示具体化统称为具体化。
  • 它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。

隐式实例化、显示实例化、显示具体化。。。怎么区分还是不清楚啊!!!!!下面,来一下小结。。。

template <typename T>
void swap(T &, T &);	//模板原型

template <> void swap<job>(job &, job &);	//基于job类型的显示具体化 
 
int main()
{
	template void swap<char>(char &, char &);	//基于job类型的显示实例化 
	short a, b;
	...
	swap(a,b);	//隐式实例化for short
	job m, n;
	...
	swap(m,n);	//显示具体化for job 
	char g, h;
	...
	swap(g, h);	//显示实例化for char 
} 

编译器看到char的显示实例化后,将使用模板定义来生成swap()的char版本。对于其他swap()调用,编译器根据函数调用中实际使用的参数,生成相应的版本。例如,当编译器看到函数调用swap(a,b)后,将生成swap()的short版本,因为两个参数的类型都是short。当编译器看到函数调用swap(m,n)后,将使用为job类型提供的独立定义(显示具体化)。当编译器看到swap(g,h)后,将使用处理显示实例化时生成的模板具体化。

 

呼!!终于学完了函数模板重载。。

但是,对于函数重载、函数模板和函数模板重载,C++需要(且有)一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析。

哦哦,这里挖个坑,以后回看再填坑。(p289)

 

C++发展早期,大多数人没想到模板函数和模板类会这么强大而有用,于是后来,C++98标准添加了标准模板库。从此,模板程序员在不断探索各种可能性,并消除模板的局限性。C++11标准又做出了相应的修改。下面我们来学习一些相关的问题及其解决方案。

问题:在C++98中,编写模板函数时,一个问题并非总能知道应在声明中使用哪种类型:

template <typename T1, typename T2>
void ft(T1 x, T2 y)
{
	...
	?type? xpy = x + y;
	...
}

 

xpy应为什么类型呢?由于不知道ft()将如何使用,因此无法预先知道这一点。正确的类型可能是T1、T2或其他类型。

例如,T1可能是double,T2可能是int,那么两个变量的和将为double类型。T1可能是short,而T2可能是int,那么两个变量的和为int类型。T1还可能是short,而T2可能是char,那么加法运算将导致自动整型提升,因此结果类型为int。另外,结构和类可能重载运算符+,这将导致问题更复杂。因此,在C++98中,没有办法声明xpy的类型。

解决方案:C++11新增的关键字decltype提供了解决方案。可这样使用关键字:

int x;
decltype(x) y;		//让y与x的类型相同 

给decltype提供的参数可以是表达式,因此在前面的模板函数ft()中,可使用下面的代码:

decltype(x + y) xpy;		//让xpy与x+y的类型相同 
xpy = x + y;
//或者这样写
decltype(x + y) xpy = x + y; 

于是上面问题的代码可以修改为:

template <typename T1, typename T2>
void ft(T1 x, T2 y)
{
	...
	decltype(x + y) xpy = x + y; 
	...
}

其实,decltype比这些实例演示的要复杂些,其具体实现过程可参考书p295,我们只要知道它的用法即可。

 

除了上面这一问题外,还有一个问题是decltype本身无法解决的,请看下面的代码:

template <typename T1, typename T2>
?type? gt(T1 x, T2 y)
{
	...
	return x + y;
}

同样,我们无法预知将x和y相加得到的类型。好像可以将返回类型设置为decltype(x+y),但不幸的是,此时还未声明x和y,它们不在作用域内(编译器看不到它们,也无法使用它们),即我们必须在声明参数后使用decltype。

为此,C++新增了一种声明和定义函数的语法。下面使用内置类型来说明这种语法的工作原理。

对于下面的原型:

double h(int x, float y);

使用新增的语法可编写成这样:

auto h(int x, float y) -> double;

这将返回类型移到了参数声明的后面。->double被称为后置返回类型。其中auto是一个占位符,表示后置返回类型提供的类型,这是C++11给auto新增的一种角色。

这种语法也可用于函数定义:

auto h(int x, float y) -> double;
{	/*	函数体	*/	}

通过结合使用这种语法和decltype,便可给gt指定返回类型:

template <typename T1, typename T2>
auto gt(T1 x, T2 y) -> decltype(x + y)  //decltype在参数声明之后,故x和y位于作用域内,可以使用它们
{
	...
	return x + y;
}

 

 

 

温故而知新

1.哪种函数适合定义为内联函数

 短小的,执行速度快的,占用内存小,非递归的。

 

2.假如song()函数的原型如下:

void song(const char* name, int times);

a.如何修改原型,使times的默认值为1?

b.函数定义需要做哪些修改?

c.能否为name提供默认值“O.My Papa”?

答:

a。 修改成:void song(const char*name,int times=1);

b。函数定义无需修改

c。可以,但前提是为times提供默认值,且在调用时,应符合带默认参数的函数的调用方式。函数原型如改成:

void song(const char*name="O.My Papa", int times =1);

答案没有加const,但我觉得应该加上吧。

 

3.编写iquote()的重载版本——显示其用双引号括起的参数。编写3个版本:一个用于int参数,一个用于double参数,一个用于string参数。

答:

void iquote(int a)
{
    cout<<"\""<<a<<"\'"<<endl;
}

void iquote(double a)
{
    cout<<"\""<<a<<"\""<<endl;
}

void iquote(string a)
{
    cout<<"\""<<a<<"\""<<endl;
}
View Code

 

4.下面是一个结构模板:

struct box{
	char maker[40];
	float height;
	float width;
	float length;
	float volume;
};

a.请编写一个函数,它将box结构的引用作为形参,并显示每个成员的值。

b.请编写一个函数,它将box结构的引用作为形参,并将volume成员的值设为其他3边的乘积。

答:

//a
void abc_1(const box& a)
{
    cout<<a.maker<<endl;
    cout<<a.height<<endl;
    cout<<a.width<<endl;
    cout<<a.length<<endl;
    cout<<a.volume<<endl;
}

//b
void abc_2(box& a)
{
    a.volume=a.height*a.width*a.length;
}
View Code

 

5.为让函数fill()和show()使用引用参数,需要对程序清单7.15做哪些修改?

答:

//原型修改为:
void fill(std::array<double,Seasons>&);
void show(std::array<double,Seasons>&);

//函数定义修改为:
void fill(std::array<double,Seasons>&pa)
{
    using namespace std;
    for (int i = 0; i < Seasons; i++){
        cout<< "Enter "<< Snames[i]<<" expenses: ";
        cin>>pa[i];
    }
}
void show(std::array<double, Seasons> &da)
{
    using namespace std;
    double total = 0.0;
    cout<<"\nEXPENSES\n";
    for (int i = 0; i < Seasons; i++){
        cout<<Snames[i]<<": $"<<da[i]<<endl;
        total += da[i];
    }
    cout<<"Total Expenses: $"<<total<<endl;
}

//函数调用改为:
fill(expenses);
show(expenses);
View Code

 

  

6.指出下面每个目标是否可以使用默认参数或函数重载完成,或者这两种方法都无法完成,并提供合适的原型。

a. mass(density,volume)返回密度为density、体积为volume的物体的质量,而mass(density)返回密度为density、体积为1.0立法米的物体的质量。这些值的类型都为double。

答:

可以用函数重载完成,也可以用默认函数完成。

默认函数如下:

double mass(double a, double b=1);

函数重载如下:

double mass(double a,double b);

double mass(double a);

 

b. repeat(10,"I'm OK")将指定的字符串显示10次,而repeat("But you're kind of stupid")将指定的字符串显示5次。

答:

只能用函数重载完成,不能用默认函数,原型为:

void repeat(int,string);

void repeat(string);

 

c. average(3,6)返回两个int参数的平均值(int类型),而average(3.0,6.0)返回两个double值的平均值(double类型)。

答:

只能用函数重载完成,原型为:

int average(int,int);

double average(double,double);

 

d. mangle("I'm glad to meet you")根据是将值赋给char变量还是char*变量,分别返回字符1和指向字符串“I'm glad to meet you”的指针。

答:

两种方法都无法完成。

 

7.编写返回两个参数中较大值的函数模板。

答:

template<class xx>xx bigger(xx a,xx b)
{
    xx c=(a>b)?a:b;
    return c;
}
View Code

 

 

8.给定复习题6的模板和复习题4的box结构,提供一个模板具体化,它接受两个box参数,并返回体积较大的一个。

答:

template<>box mass(box a,box b){
    return (a.volume>b.volume)?a:b;
}
View Code

 

 

9.在下述代码(假定这些代码是一个完整程序的一部分)中,v1、v2、v3、v4和v5分别是哪种类型?

int g(int x);
....
float m = 5.5f;
float &rm = m;
decltype(m)v1 = m;
decltype(rm)v2 = m;
decltype((m)) v3 = m;
decltype(g(100))v4;
decltype(2.0*m)v5;

答:v1为float、v2为float&,v3为float(错,为float&,核对表的第三步),v4为g(100)这个函数的返回值的类型int(做题的时候没有注意第一行)(核对表的第二步),v5为double(核对表的第一步)。

 

 

 

 

 

 

posted @ 2017-08-17 01:59  GGBeng  阅读(235)  评论(0编辑  收藏  举报