C++函数重载,函数模板,引用

第八章-深入函数

1. 内联函数

内联函数是C++为了提高程序运行速度所作的一项改进。当执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着使用函数时需要一定的开销。
内联函数的编译代码与其他程序代码连接起来了,编译器直接在对应的位置用函数体内部的代码替换,这样便没有调用函数时指令的跳转了。可以加快程序执行的时间,但是需要占用更多的内存。当函数调用花费的时间较多时,可以使用内联函数的方法。
使用inline关键字便可定义。

inline double square (double x) {
	return x * x;
}

内联代码的原始实现为# define square(x) x*x

2. 引用变量

引用是已定义变量的别名(另外一个名称)。使用& 进行定义,而且在定义时需要初始化。

# include<iostream>
int main(){
	using namespace std;
	int a = 101;
	int & b = a;  // 定义a的别名b
	b = b + 1;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	return 0;
}

在函数的参数传递中,如果使用引用变量,将可以允许函数使用原始数据。
声明时void sum(int &a, int &b)

void sum(int &a, int &b){
	return a + b;
}
int m = 26;
int n = 25;
sum(m,n);

在sum函数中,a,b是m,n的别名,对a,b 操作就是对m,n操作。
函数的返回值也可以是一个引用。

# include<iostream>
# include<string>

struct free_throws{
	std::string name;
	int made;
	int attempts;
	float percent;
};

void set_pc(free_throws & ft);
free_throws & acc(free_throws & target, const free_throws & source);

int main(){
	using namespace std;
	free_throws one = {"Ifelsa", 13, 14};
	free_throws two = {"Andor", 10, 16};
	free_throws three = {"Max", 7, 9};
	free_throws four = {"Whily", 5, 9};
	free_throws five = {"Long", 6, 14};
	free_throws team = {"Throwgoods", 0, 0};
	
	acc(team, one) = four;
	cout << team.percent;
	
	return 0;
}

void set_pc(free_throws & ft){
	if(ft.attempts !=0)
		ft.percent = 100.0f * float(ft.made) / float(ft.attempts);
	else
		ft.percent = 0;
}


free_throws & acc(free_throws & target, const free_throws & source){
	target.attempts += source.attempts;
	target.made += source.made;
	set_pc(target);
	return target;
} 

在上述代码中,acc(team, one) = four看上去很奇怪。一般来说,常规(非返回引用)函数得到的结果都会放到等号的右边。传统的返回机制与按值传递函数参数类似:将return后面的表示式结果计算出来,将结果复制到例外一个临时位置。返回引用时,acc(team, one) = four其实相当于

acc(team, one);
team = four;

在赋值表达式中,左边的子表达式必须标识一个可以修改的内存块。在这里函数返回team的引用,它指向了一个可以修改的内存块。
引用分为左值引用和右值引用,

  1. 可以取地址的,有名字的,非临时的就是左值;
  2. 不能取地址的,没有名字的,临时的就是右值;
    可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值。
类型 && 引用名 = 右值表达式;
int && a = 100;

3. 默认参数

在函数原型中进行定义,便可使用默认参数的特性。比如

char* left(const char *str, int n=1);

对于带参数列表的函数,必须从右向左添加默认值,也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。

int chico(int n, int m =6, int i = 2); // 合法
int chico(int m =6, int i = 2, int n ); // 不合法

4. 函数重载

函数重载是指可以有多个同名的函数。函数重载的关键是函数的参数列表,C++允许定义名称相同的函数,条件是它们的特征标不同。

// 合法的重载
void print(double a);
void print(int b);
void print(char *p);

// 不合法的重载
double cube(double x);
double cube(double &x); // 编译器在检查函数特征标时,把类型引用和类型本身视为同一个特征标。

是特征标志不同,而不是函数类型使得可以对函数进行重载,下面的重载是非法的

long gronk(int n, int m);
double gronk(int n, int m);

在类设计和STL经常使用引用参数,因此有必要知道不同引用类型的重载

void sink(double &a1);  // 与可修改的左值参数匹配
void sink(const double &a2);  // 与可修改的左值参数,const左值参数,右值参数匹配
void sink(double &&a3);  // 与右值参数匹配
sink(10.0 + 10.0);  // 与右值参数匹配

5. 函数模板

5.1 函数模板的基本概念

函数模板是通用的函数描述,使用泛型来定义函数,其中的泛型可以用具体的类型(int或者long)来替换。通过将类型作为参数传递给模板,可以使编译器生成该类型的函数。下面通过一个例子介绍

template <typename AnyType>
void swap(AnyType &a, AnyType &b){
	AnyType temp;
	temp = a;
	a = b;
	b = temp;
}

要建立模板,关键字templatetypename是必须的(typename 可以用class代替)。

5.2 重载的模板

并非所有的类型都使用相同的算法,为了满足这个需求,可以像重载常规函数一样重载模板定义,比如

template <typename AnyType>  
void swap(AnyType &a, AnyType &b);  // 原始的模板定义

template <typename AnyType>
void swap(AnyType *a, AnyType *b);  // 新的模板定义

有些时候,函数的参数保持不变,但是我们需要做出不同的操作,明显,使用模板的重载无法实现(因为参数没有改变)C++提供了显式具体化(explicit specialization)的方法。
编译器使用函数的优先级为:非模板函数> 显式具体化函数 > 模板函数

# include<iostream>
# include<string>
using namespace std;
struct job{
	char name[40];
	double salary;
	int floor;
};
//模板函数原型
template <typename T>
void print(T a);
// 具体化后的函数原型
template <> void print(job x);

int main(){
	job teacher = {"teacher", 10000, 4};
	print(9); 
	print('a');  // 使用模板函数
	print(teacher);  // 使用显式具体化函数 
	return 0;
}

// template prototype
template <typename T>
void print(T a){
	cout << a << endl;
} 
// specialization
template <> void print(job x){
	cout << x.name << endl;
	cout <<"salary is : "<< x.salary << endl;
}

编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation),为了更好地理解模板,下面来表明三个概念。

  1. 隐式实例化:通过实际使用的参数来得到模板实例,比如上面的print(9)print函数传入一个int类型的参数,因此经过编译器处理后得到参数为int类型的模板实例。
  2. 显式实例化:在函数后面指定参数类型,比如template void print<int>(int);不可以在函数中定义与原来模板函数不一样的操作
  3. 显式具体化:在template后面有<>,可以在函数中定义与原来模板函数不一样的操作,比如template <> void print(job x);
    在同一个文件中使用同一种类型的显式实例和显式具体化将报错

5.3 decltype关键字

但是,在编写函数时可能会出现无法确定变量的类型的情况,比如

template <class T1, class T2>
void add(T1 x, T2 y){
	type??? a = x + y;
	// 当要定义一个变量a来接收x + y的值,a应该什么类型的变量
}

C++11新增关键字decltype,使用方法如下所示

int a;
decltype(a) b;  // b与a的类型相同
// 上面的例子
decltype(x + y)  a = x+y;

5.4 后置返回

看下面一个例子

template <class T1, class T2>
type??? add(T1 x, T2 y){
	return x + y;
	// 如何确定函数的返回类型
}
decltype(x + y) add(T1 x, T2 y); // 不可行,在声明时还未定义x,y,

使用decltype关键字也无法确定返回值的类型,因为此时还未声明x,y,x,y不在作用域中。
可以使用下面的语法,使用auto关键字。

template<class T1, class T2>
auto add(T1 x, T2 y) -> decltype(x + y);

6. 编译器的重载解析

对于函数重载,函数模板和函数模板重载,C++需要有一个良好的策略,来决定使用哪一个函数定义,这一过程称为重载解析。简化的步骤有三步

  1. 创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数。
  2. 使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如,使用float参数的函数调用可以将该参数转换为double,从而与double形参匹配,而模板可以为float生成一个实例。
  3. 确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错。

参考资料

  1. C++ primer plus
  2. C++ 左值引用与右值引用
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值