C++内联函数、引用变量、函数重载、函数模板

内联函数

常规函数调用使程序跳到另一个地址(函数的地址),并在函数结束后返回。来回跳跃并记录跳跃位置意味着一定的开销。
内联函数使得编译器将相应的函数代码替换函数调用。程序无需跳到另一个位置处执行代码,再调回来。
因而,内联函数运行速度比常规函数快,但是代价是需要占用更多的内存。如果程序在10个不同的地方调用调用同一个内联函数,则该程序将包含该函数代码的10个副本。

使用内联函数:

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

通常的做法是省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。

示例代码:

inline.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;

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

int main() {
double a, b;

a = square(5.0);
b = square(4.5 + 7.5);

cout << "a = " << a << ", b = " << b <<endl;
return 0;
}

执行结果: a = 25, b = 144

NOTE:
C语言使用预处理语句**#define提供宏(内联代码的原始实现),比如:#define SQUARE(X) X*X
这是通过文本替换来实现的。


引用变量标识符&

引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是副本。这样除了指针以外,引用也为函数处理大型结构提供了方便的途径。

创建引用变量

1
2
int rats;
int & rodents = rats; //此处的&不是地址运算符,而是类型标识符的一部分。int&是指向int的引用。

上述的引用声明允许将rats和rodents互换,他们指向相同的值和内存单元
示例代码:

firstref.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>

using namespace std;

int main(){
int rats = 101;
int & rodents = rats; //必须在声明引用时初始化,不可以分成两句
//上一句等价于 int * const pr = &rats;

cout << "rats = " << rats << ", rodents = " << rodents <<endl;

rodents++;
cout << "After rodents++, rats = " << rats << ", rodents = " << rodents << endl;
cout << "Address &rats = " << &rats << ", &rodents = " << &rodents <<endl;
return 0;
}

执行结果:

1
2
3
rats = 101, rodents = 101
After rodents++, rats = 102, rodents = 102
Address &rats = 0x7ffe8c156f1c, &rodents = 0x7ffe8c156f1c //相同的地址和值

将引用用作函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//按值传递
void lazy(int x);
int main() {
int times = 20;
lazy(times);
return 0;
}

void lazy(int x) {
...
}

//按引用传递
void work(int &x);
int main() {
int times = 20;
work(times);
}

void work(int &x) {
...
}

如果让函数使用传递给她的信息,而不对信息进行修改,同时又箱使用引用,则应使用常量引用,即使用const。

double refcube(const double &ra);

将引用参数声明为常量数据的引用的理由三个:

  • 使用const可以避免无意中修改数据的编程错误
  • 使用const使函数能够处理const和非const实参,否则将只能接受非cosnt数据
  • 使用const引用使函数能够正确生成并使用临时变量

C++11新增了另一种引用 – 右值引用,这种引用可以指向右值,是使用&&声明的。目的用来实现移动语义。而’&’引用是左值引用。

1
2
3
double && rref = std::sqrt(36.00);  //6
double j = 15.0;
douvle && jref = 2.0 * j + 18.5; //48.5

合适使用引用参数

使用引用参数的主要原因有两个:

  • 能够修改调用函数中的数据对象
  • 通过传递引用而不是整个数据对象,提高程序运行速度

对于使用传递的值而不做修改的函数:

  • 如果数据对象很小,如内置数据类型或小型数据。则按值传递
  • 如果数据对象是数组,则使用指针,并将指针声明为指向const的指针
  • 如果数据对象是较大的结构,则使用const指针或const引用,提高效率,节省复制结构所需的时间和空间
  • 如果数据对象是类对象,则使用const引用,传递类对象参数的标准方式是按引用传递

对于修改函数中数据的函数:

  • 如果数据对象是内置数据类型,则使用指针。例如调用fixit(&x)这样的函数,则很明显要修改x
  • 如果数据对象是数组,则只能使用指针
  • 如果数据对象是结构,则使用引用或者指针
  • 如果数据对象是类对象,则使用引用

默认参数

默认参数指的是当函数调用中省略了实参时自动调用的一个值。例如将void wow(int n)设置成n有默认值是1,则函数调用wow()就等价于wow(1)

通过函数原型设置默认值:char * left(const char *str, int n = 1);


函数重载

同名的函数使用不同的参数列表。
因而关键是参数列表,也称作函数特征标。如果两个函数的参数数目和类型相同,同时参数的排列顺序相同,则他们的特征标相同
返回类型可以不同,但是特征标也必须不同

1
2
3
4
5
6
7
8
9
10
11
void print(const char *str, int width);
void print(double d, int width);
void print(long l, int width);
void print(int i, int width);
void print(const char *str);

//返回类型不同
long gronk(int n, float m);

double gronk(int n, float); //互斥,不允许重载
double gronk(float n, float m); //可以重载

因而在使用print()函数时,编译器根据采取的用法使用有相应特征标的函数原型。

1
2
3
print(1999.0, 10);
print(1999L, 15);
print("pen", 12);

当函数基本上执行相同的人物,但使用不同形式的数据时,才应采用函数重载。

名称修饰(或名称矫正)

根据函数原型中指定的形参类型对每个函数名进行加密。
long MyFun(int, float);
编译器将其转化为内部表示来描述接口:
?MyFun@@YAXH


函数模板

函数模板是通用的函数描述,使用泛型来定义函数,其中泛型可以用具体的类型(如int或double)替换。
通过将类型作为参数传递给模板,可以使编译器生成该类型的函数
由于模板允许以泛型的方式编写程序,因此也称作通用编程
由于类型是用参数表示的,因此模板特性有时也称作参数化类型

函数模板允许以任意类型的方式来定义函数。例如,可以建立一个交换模板:

1
2
3
4
5
6
7
8
9
template <typename AnyType>
//template <class T>

void swap(AnyType &a, AnyType &b) {
AnyType temp;
temp = a;
a = b;
b = temp;
}

第一行建立一个模板,将类型命名为AnyType,关键字templatetypename是必需的,除非使用另一个关键字class代替typename(这两个关键字等价的)。另外,必须使用尖括号。
类型名可以任意选择(此处是AnyType),常用T

如果需要多个将同一个算法用于不同类型的函数,请使用函数模板。如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数时,应使用关键字typename而不使用class。

Example:

funtemp.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>

using namespace std;

template <typename T> //or class T
void Swap(T &a, T &b); //函数原型、函数模板使用、引用变量

//函数模板定义
template <typename T> //or class T
void Swap(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp;
}

int main(){
int i = 10;
int j = 20;
cout << "Before swap, i = " << i << ",j = " << j <<endl;
Swap(i, j);
cout << "After swap, i = " << i << ",j = " << j <<endl;

double x = 24.45;
double y = 45.2;
cout << "Before swap, x = " << x << ",y = " << y <<endl;
Swap(x, y);
cout << "After swap, x = " << x << ",y = " << y <<endl;

return 0;
}

Result:

1
2
3
4
Before swap, i = 10,j = 20   //当接收两个int参数,则会用int代替T
After swap, i = 20,j = 10
Before swap, x = 24.45,y = 45.2
After swap, x = 45.2,y = 24.45

Note:
函数模板不能缩短可执行程序,最终仍将由独立的函数定义,最终的代码不包含任何函数模板,而只包含了为程序生成的实际函数。

使用函数模板的好处是使得多个函数定义更简单可靠。

重载的函数模板

可以像重载常规函数定义一样重载函数模板定义。保证被重载的函数模板特征标必须不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename T>
void Swap(T &a, T &b);

template <typename T>
void Swap(T *a, T *b, int n);

//实现第二个
template <typename T>
void Swap(T a[], T b[], int n) {
T temp;
for (int i = 0; i < n; i++) {
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}

局限性

模板函数可能无法处理某些类型。例如数组、指针、结构的某些运算。
一种解决方案是C++允许你重载运算符+,以便能够将其用于特定的结构或类;
另一种是为特定类型提供具体化的模板定义。

显示具体化

C++98标准选择了以下的方法实现第三代具体化:

  • 对于给定的函数名,可以有非模板函数、模板函数、显示具体化模板函数、以及他们的重载版本
  • 显示具体化的原型和定义应该以template <>开头,并通过名称指出类型
  • 优先级: 非模板函数 > 显示具体化模板函数 > 常规模板函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct job {
char name[40];
double salary;
int floor;
};

//非模板函数(首先调用)
void Swap(job &, job &);

//显示具体化模板函数(其次调用)
template <> void Swap<job>(job & ,job &);

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

Swap<job><job>是可选的,因为函数的参数类型声明,这是job的一个具体化。因此,该原型也可以写作:
template <> void Swap(job &, job &);

Example:

twoswap.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<iostream>
using namespace std;

template <typename T>
void Swap(T &a, T &b);

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

template <> void Swap<job>(job &j1, job &j2);

void Show(job &j);

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

template <> void Swap<job>(job &j1, job &j2) {
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;

t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}

void Show(job &j) {
cout << j.name << " : $" << j.salary << " on floor " << j.floor <<endl;
}

int main(){
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);

int i = 10, j = 20;
cout << "Before swap, i = " << i << ",j = " << j <<endl;
Swap(i, j);
cout << "After swap, i = " << i << ",j = " << j <<endl;

job sue = {"Susan", 7300.60, 7};
job sidney = {"Sidney Taffee", 78060.72, 9};
cout << "Before job swap,\n";
Show(sue);
Show(sidney);
Swap(sue, sidney);
cout << "After job swap,\n";
Show(sue);
Show(sidney);

return 0;
}

Results:

1
2
3
4
5
6
7
8
Before swap, i = 10,j = 20
After swap, i = 20,j = 10
Before job swap,
Susan : $7300.60 on floor 7
Sidney Taffee : $78060.72 on floor 9
After job swap,
Susan : $78060.72 on floor 9
Sidney Taffee : $7300.60 on floor 7

关键字decltype(c++11)

1
2
3
4
5
6
template <class T1, class T2>
void ft(T1 x, T2 y) {
...
?type? xpy = x + y;
...
}

此处的xpy不知道如何确定类型,在C++11中提供关键字decltype,使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int x;
decltype(x) y;

decltype(x+y) xpy;
xpy = x + y;

//或者合并成一句
decltype(x+y) xpy = x + y;

//举例
decltype(auto) a;

//完善上述的模板函数
template <class T1, class T2>
void ft(T1 x, T2 y) {
...
decltype(x+y) xpy = x + y;
...
}

C++11后置返回类型声明语法

1
2
3
4
template<class T1, class T2>
?type? gt(T1 x, T2 y) {
return x + y;
}

此处无法确定返回的类型,因为未声明参数x和y,所以他们不再作用域呢,无法使用decltype关键字(必须声明参数后使用)。C++11新增了一种语法:

double h(int x, float y);
使用新增的语法后可以这样编写:

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

这样将返回类型已到了参数声明之后,->double被称为后置返回类型

因而使用这种方法声明模板函数:

1
2
3
4
template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y) {
return x + y;
}
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2020 sunwengang
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信