c++基础知识(6)

c++ primer plus 第八章,包括引用变量&、默认参数、函数重载、函数模板。

c++内联函数

内联函数是c++为提高程序运行速度所做的一项改进。常规函数和内联函数的主要区别不在于编写方式,而在于c++编译器如何将它们组合到程序中。

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

内联函数的编译代码与其他程序代码“内联”起来了。即编译器将使用相应的函数代码替代函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此内联函数的运行速度比常规函数更快,但代价是需要占用更多内存。

常规函数:跳转;内联函数:复制

代码执行时间>函数调用时间–>使用常规函数

代码执行时间<函数调用时间–>使用内联函数

要使用内联函数,必须采用以下措施之一

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

内联函数也是按值传递

引用变量

引用是已定义的变量的别名(另一个名称)(外号)

主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。

除了指针以外,引用也为函数处理大型结构提供了一种非常方便的途径。

创建引用变量

使用&符号来声明引用

1
2
3
int rats;
int & rodents = rats;
// 引用声明允许将rats和rodents互换-它们指向相同的值和内存单元

必须在声明引用时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。

将引用用作函数参数

引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。按引用传递允许被调用的函数能够方位调用函数中的变量。(操作形参=操作实参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// swapping with references
#include<iostream>
void swapr(int & a, int & b);
int main()
{
using namespace std;
int wallet1 = 300;
int wallet2 = 350;
cout << "wallet1 = $" << wallet1 << endl;
cout << "wallet2 = $" << wallet2 << endl;
swapr(wallet1, wallet2);
cout << "wallet1 = $" << wallet1 << endl;
cout << "wallet2 = $" << wallet2 << endl;
return 0;
}

void swapr(int & a, int & b)
{
int temp;
temp = a;
a = b;
b = temp;
}

引用的属性和特别之处

如果想要让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用(通过const从而无法修改引用的值)

例如 double refcube (const double &ra)

1
2
3
4
5
6
7
8
9
10
11
12
13
double cube (double a);
double refcube(double &ra);

double cube(double a)
{
a *= a * a;
return a;
}
double refcube(double &ra)
{
ra *= ra * ra;
return ra;
}

对于按值传递的函数,例如上述代码中的cube函数,可使用多种类型的实参,例如以下调用都是合法的

1
2
3
double z = cube(x + 2.0);
z = cube(8.0);
cube(x + 3.0);

但是将上述类似的参数传递给接收引用参数的函数,会发现出错,这是因为传递引用的限制更严格,

–>临时变量、引用参数和const

当参数为const引用时,如果实参与引用参数不匹配,c++将生成临时变量

将引用参数声明为常量数据的引用的理由有

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

将引用用于结构体

假设有如下结构体定义:

1
2
3
4
5
6
7
8
struct free_throws
{
string name;
int made;
int attempts;
float percent;
}
void set_pc (free_throws &ft)

为什么要返回引用?不需要将值复制到临时变量,效率更高

返回引用时,应避免返回函数终止时不再存在的内存单元引用(函数局部变量的引用)

何时使用引用参数

默认参数

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

必须通过函数原型来设置默认值,由于编译器通过查看函数原型来了解函数所使用的参数数目,因此函数原型也必须将可能得默认参数告知程序。方法是将值赋给原型中的参数。

1
2
3
4
5
6
7
//例如如下函数原型
char * left(const char * str, int n = 1);
//在函数定义时 不填写默认值
char * left(const char * str, int n )
{
body
}

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

1
2
3
4
5
6
int harpo(int n, int m = 4, int j = 5); //valid
int chico(int n, int m = 6, int j); //invalid

beeps = harpo(2);
beeps = harpo(1, 8);
beeps = harpo(1, 8, 5);

实参按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数,例如下面的调用是不允许的

1
beeps = harpo(1, , 8);

函数重载

函数多态(函数重载)指的是可以有多个同名的函数。可以通过函数重载来设计一系列函数,这些函数完成相似的工作,但是使用不同的参数列表

1
2
3
4
5
6
7
8
9
10
11
void printf(const char *str, int width);// #1
void printf(double d, int width);// #2
void printf(long l, int width);// #3
void printf(int i, int width);// #4
void printf(const char *str);// #5
//使用pritf()函数时,编译器将根据所采取的用法使用有相同参数列表的原型
printf("pancakes", 14);// use #1
printf("Syrup");// #2
printf(1999.0, 10);// #3
printf(1999, 12);// #4
printf(1999L, 15)// #5

仅当函数基本上执行相同的任务,但使用不同的数据时,才应采用函数重载。

函数模板

需要对多个不同类型的数据使用同一种算法时,可以使用模板。

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

例如已经定义了一个交换两个int值的函数,假设现在要交换两个double值,那么一种方法是复制原来的代码,用double替换所有的int。如果需要交换两个char值,可以再次使用同样的技术。然而进行这种修改会浪费时间且容易出错。

c++的函数模板能够自动完成这一功能,可以节省时间,而且更可靠

定义

1
2
3
4
5
template <typename AnyType>
void function(AnyType a)
{
body
}

例如,以下是一个交换函数模板

1
2
3
4
5
6
7
8
9
10
11
12
//函数原型
template <typename T>
void Swap(T &a, T &b)
//函数定义
template <typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}

模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int的函数时,编译器将按模板创建这样的函数,并用int代替AnyType。同样,需要交换double的函数时,编译器将按模板创建这样的函数,并用double代替AnyType。

模板的局限性

假设有如下模板

1
2
3
4
5
template <typename T>
void f(T a, T b)
{
...
}

假设function f中定义了复制a = b,但是如果ab是数组,那么这种运算将不成立

在使用函数模板时,编写的函数模板可能无法处理某些类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 假设定义如下结构体
struct job
{
char name[40];
double salary;
int floor;
}
// 希望使用函数模板交换两个这种结构的内容
template <typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}

假设只想交换salaryfloor成员,而不交换name成员,则无法通过模板重载来提供其他的代码。

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

显式具体化

  • 对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本
  • 显式具体化的原型和定义应以template<>打头,并通过名称来指出类型
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板
1
2
3
4
5
6
7
// 用于交换job结构体的非模板函数、模板函数和具体化的原型:
void Swap(job &, job &);

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

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

实例化和具体化

实例化:

  • 隐式实例化:编译器使用模板为特定类型生成函数定义时,得到的是模板实例

  • 显式实例化:直接命令编译器创建特定的实例

    Swap函数声明:template void Swap<int>(int, int),意思是使用Swap()模板生成int类型的函数定义。

显式具体化:template<> void Swap<int>(int &, int &);意思是不要使用通用Swap模板来生成函数定义,而应使用专门为int类型显式的定义的函数定义。(专门定制)

显示具体化声明在关键字template后包含<>,而显式实例化没有。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!