文章目录
前言
本人在阅读C++ primer plus(第六版)的过程中做的笔记,写这篇文章既是为了分享,也是为了以后查阅。以下是本篇文章正式内容。
一、内联函数
创建内联函数需要在函数声明前和函数定义前加上关键字inline,通常的做法是省略原型,即在本应提供原型的地方进行定义。
二、引用变量
1.创建引用变量
int rats;
int &rodents = rats;
引用是已定义变量的别名,上述引用声明允许rats和rodents互换,它们指向相同的值和内存单元。必须在声明引用变量时就对其初始化。
2.将引用用作函数参数
如果将引用(变量的别名)用作函数的形参,则实参应是该变量。例如下面这样的函数调用将不合理:
double cube(double& a);
double b = cube(c + 1.0);
如果实参和引用参数不匹配,C++将生成临时变量。当前,仅当参数是const引用时才允许这样做。如果引用参数是const,则编译器将在下面两种情况下生成临时变量:
- 实参的类型正确,但不是左值;
- 实参的类型不正确,但可以转换为正确的类型;
C++新增了另一种引用,右值引用,这种引用可以指向右值,使用&&声明:
double &&ref = 15.0;
double &&j = ref + 15.0;
3.设置cout的格式化状态
ios_base::fmtflags initial;
initial = cout.setf(ios_base::fixed, ios_base::showpoint);
cout.precision(2);
cout.width(12);
cout.setf(initial);
setf()方法可以设置各种格式化状态。setf(ios_base::fixed)方法将cout对象置于使用定点表示法的模式;setf(ios_base::showpoint)将对象置于显示小数点的模式;precision()方法指定显示多少位小数;width()方法设置下一次输出时使用的字段宽度,这个设置只在下一个值时有效,然后将恢复到默认设置。setf()方法返回调用它之前有效的所有格式化设置,ios_base::fmtflags是存储这种信息所需的数据类型名称,将返回值赋给initial可以存储修改cout对象格式化设置之前的格式化设置,然后将initial作为参数调用setf()就可以将格式化设置恢复到原来的值。
三、默认参数
默认参数指的是当函数调用中省略了实参时自动使用的一个值。默认值的设置必须通过函数原型,方法是将值赋给原型中的参数。只有原型指定了默认值,函数定义与参数没有默认值时完全相同,例如:
char* left(const char* str, int n = 1);
对于带参数列表的函数,必须从右向左添加默认值,因为实参是按从左到右的顺序依次赋给相应的形参的。
四、函数重载
没有匹配的原型并不会自动停止使用其中的某个函数,而会使用标准类型转换强制进行匹配,而如果有多个重载的函数都可以强制转换后使之匹配,则编译器会报错。
五、函数模板
函数模板允许以任意类型的方式来定义函数。例如:
template <typename AnyType>
void swap(AnyType& a, AnyType& b)
{
…
…
}
第一行(后面没有分号)指出,要建立一个模板,并将类型命名为AnyType。关键字template和typename是必需的,除非可以使用关键字class代替typename。另外,必须使用尖括号,类型名可以任意选择,只要遵守C++的命名规则(这里为AnyType)。
在文件的开始位置提供模板函数的原型,并在main()函数后面提供定义:
template <typename T>
void swap(T& a, T& b);
int main()
{
…
int i = 5;
int j = 10;
swap(i, j);
…
}
template <typename T>
void swap(T& a, T& b)
{
T temp;
temp = a;
a = b;
b = temp;
}
1.模板的重载
可以像重载常规函数那样重载模板定义,被重载的模板特征标必须不同,并非所有的模板参数都是模板参数类型。例如:
template <typename T>
void swap(T& a, T& b);
template <typename T>
void swap(T& a, T& b, int n);
2.显式具体化
显式具体化的方法:
- 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数版本以及它们的重载版本;
- 显式具体化的原型和定义应以template<>打头,并通过名称来指出具体类型;
- 优先顺序:非模板函数 > 显式具体化模板函数 > 模板函数;
template <> void swap<job>(job&, job&); //job是一个结构
swap<job>中的job是可选的,因为参数类型表明这是job的具体化,因此该函数也可以这样编写:
template <> void swap(job& ,job&);
下面是显式具体化的示例:
#include <iostream>
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);
int main()
{
using namespace std;
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int swapper:\n";
Swap(i, j); //隐式实例化
cout << "Now i, j = " << i << ", " << j << ".\n";
job sue = { "Susan Yaffee", 73000.60, 7 };
job sidney = { "Sidney Taffee", 78060.72, 9 };
cout << "Before job swapping:\n";
Show(sue);
Show(sidney);
Swap(sue, sidney);
cout << "After job swapping:\n";
Show(sue);
Show(sidney);
//cin.get();
return 0;
}
template<typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template<> void Swap(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)
{
using namespace std;
cout << j.name << ": $" << j.salary
<< " on floor " << j.floor << endl;
}
3.实例化和具体化
在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时得到的是模板实例。上述示例代码中,函数调用Swap(i, j)导致编译器生成Swap()的一个实例,该实例使用int类型。模板并非函数定义,但使用特定类型的模板实例是函数定义,这种实例化的方式被称为隐示实例化。但C++还允许显示实例化,这意味着可以直接命令编译器创建特定的实例,其语法是,声明所需的种类——用<>符号指示类型,并在声明前加上关键字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; //显式实例化
隐示实例化、显式实例化和显示具体化统称为具体化,它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。
引入显示实例化后,必须使用新的语法——在声明中使用前缀template和template<>来区分显式实例化和显式具体化。下面的片段总结了隐式实例化、显式实例化和显式具体化:
……
template <class T>
void Swap(T &, T &); //模板原型
template<> void Swap<job>(job &, job &); //显示具体化
int main(void)
{
template void Swap<char>(char &, char &); //显示实例化
short a, b;
……
Swap(a, b); //隐式实例化
job n, m;
……
Swap(n, m); //使用job的显式具体化
char g, h;
……
Swap(g, h); //使用char的显式实例化
}
编译器看到char的显式实例化后,将使用模板定义来生成Swap()的char版本。对于其他Swap()调用,编译器根据函数调用中实际使用的参数,生成相应的版本。例如,当编译器看到函数调用Swap(a, b)后,将生成Swap()的short版本,因为两个参数类型都是short。当编译器看到Swap(n, m)后,将使用为job类型提供的独立定义(显式具体化)。当编译器看到Swap(g, h)后,将使用处理显式实例化时生成的模板具体化。
4.自己选择要使用的函数版本
由于同一个函数名具有非函数模板、函数模板和函数模板显示具体化版本,在函数调用时这三个版本具有不同的优先级,但是也可以由自己来选择,例如:
template <typename T>
void lesser(T a, T b);
void lesser(int a, int b);
…
double a = 1.0;
double b = 2.0;
lesser<>(a, b); //其中的<>表明要使用模板函数版本
总结
以上就是本文的内容——内联函数、引用变量、函数的默认参数、函数重载和函数模板。