本章内容包括:
- 内联函数
- 引用变量
- 按引用传递
- 默认参数
- 函数重载
- 函数模板
- 函数模板具体化
8.1 内联函数
内联函数是C++为提高程序运行速度所做的改进。
内联函数使用相应的代码替换函数调用。
内联函数运行速度比常规代码稍快,代价是占用更多内存。
执行函数代码时间 ——>吃饭时间
函数调用机制时间——>路程时间
随便吃点(吃饭时间<路程时间)——>内联函数
吃个大餐(吃饭时间>路程时间)——>常规函数
函数声明、函数定义前加上关键字inline
内联函数不能递归
//程序清单8.1
//内联函数
#include <iostream>
using namespace std;
inline double square(double x) { return x * x; } //定义置前,声明省略
int main(void)
{
double a, b;
double c = 13.0;
a = square(5.0);
b = square(4.5 + 7.5); //内联函数可以传递表达式
cout << "a = " << a << ", b = " << b << endl;
cout << "c = " << c;
cout << ", c square = " << square(c++) << endl; //不要和上面放一行,否则c变14
cout << "Now c = " << c << endl;
return 0;
}
内联按值传递,宏不能按值传递,宏通过文本替换
#define SQUARE(X) X + X
a = SQUARE(5.0); //a = 5.0 * 5.0;
b = SQUARE(4.5 + 7.5); //b = 4.5 + 7.5 * 4.5 + 7.5; 宏隐患
d = SQUARE(c++); //d = c++ * c++;
//除a以外都不能正常工作
//可以通过括号改进
#define SQUARE(X) ((X) + (X))
8.2 引用变量
引用是已定义变量的别名。
引用的主要用途是作为函数的形参。函数将使用原始数据,而不是其副本。
8.2.1 创建引用变量
int rats;
int& rodents = rats;
- &不是地址运算符,指示变量的地址,而是声明引用,是类型标识符的一部分。
- 就像char*是指向char的指针,int&是指向int的引用。
- rats和rodents指向相同的值和内存单元。
int rats = 101;
int& rodents = rats;
int* prats = &rats;
- 表达式rats == rodents == *prats; 表达式&rats == &rodents == prats
- 引用和指针的差别之一是:引用同时进行初始化,不能像指针,先声明再幅值
- 引用不能更换指向对象,更接近const指针:int& rodents = rats; int* const pr = &rats;
8.2.2 将引用作为函数参数
- 按引用传递:允许被调用的函数能够访问调用函数中的变量
- 按值传递:导致被调用函数使用调用程序值的拷贝
//程序清单 8.4
#include <iostream>
using namespace std;
void swapr(int& a, int& b);
void swapp(int* p, int* q);
void swapv(int a, int b);
int main(void)
{
int wallet1 = 300;
int wallet2 = 350;
cout << "调换前:\n";
cout << "wallet1 = $" << wallet1 << ", wallet2 = $" << wallet2 << endl;
cout << "引用传递:\n";
swapr(wallet1, wallet2);
cout << "wallet1 = $" << wallet1 << ", wallet2 = $" << wallet2 << endl;
cout << "指针传递:\n";
swapp(&wallet1, &wallet2);
cout << "wallet1 = $" << wallet1 << ", wallet2 = $" << wallet2 << endl;
cout << "按值传递:\n"; //按值传递不能调换,因为拷贝副本
swapv(wallet1, wallet2);
cout << "wallet1 = $" << wallet1 << ", wallet2 = $" << wallet2 << endl;
return 0;
}
void swapr(int& a, int& b)
{
int temp;
temp = a;
a = b;
b = temp;
}
void swapp(int* p, int* q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
8.2.3 引用的属性和特别之处
//程序清单 8.5
#include <iostream>
using namespace std;
double cube(double a);
//double refcube(double& ra);
double refcube(const double& ra);
int main(void)
{
double x = 3.0;
cout << cube(x);
cout << " = cube of " << x << endl;
cout << refcube(x);
cout << " = cube of " << x << endl; //不要和上面放一行,refcube(x)之后x改变
double side = 3.0;
double* pd = &side;
double& rd = side;
long edge = 5L;
double lens[4] = { 2.0, 5.0, 10.0, 12.0 };
cout << refcube(side) << endl;
cout << refcube(*pd) << endl;
cout << refcube(rd) << endl;
cout << refcube(lens[2]) << endl;
//生成临时变量
cout << refcube(edge) << endl;
cout << refcube(7.0) << endl;
cout << refcube(side + 10.0) << endl;
return 0;
}
double cube(double a)
{
a *= a * a;
return a;
}
//double refcube(double& ra)
double refcube(const double & ra)
{
//ra *= ra * ra;
//return ra;
return ra * ra * ra;
}
- 引用不能用于常量,引用不能用于表达式,除非const引用,如double refcube(const double& ra);
- 仅当参数为const引用时,如果实参与引用参数不匹配,C++生成临时变量
- 应尽可能使用const
8.2.4 引用用于结构
引入引用主要是为了用于结构和类
//程序清单 8.6
#include <iostream>
#include <string>
using namespace std;
struct free_throws
{
string name;
int made; //成功次数
int attempts; //尝试次数
float percent; //成功百分百
};
void display(const free_throws& ft); //不希望函数修改传入的结构
void set_pc(free_throws& ft);
free_throws& accumulate(free_throws& target, const free_throws& source);
int main(void)
{
free_throws one = { "AAA", 13, 14 };
free_throws two = { "BBB", 10, 16 };
free_throws three = { "CCC", 7, 9 };
free_throws four = { "DDD", 5, 9 };
free_throws five = { "EEE", 6, 14 };
free_throws team = { "Throwgoods", 0, 0 };
set_pc(one);
display(one);
accumulate(team, one);
display(team);
display(accumulate(team, two));
accumulate(accumulate(team, three), four);
display(team);
free_throws dup = accumulate(team, five);
display(dup);
return 0;
}
void display(const free_throws& ft)
{
cout << "Name: " << ft.name << endl;
cout << "Made: " << ft.made << '\t';
cout << "Attempts: " << ft.attempts << '\t';
cout << "Percent: " << ft.percent << endl;
}
//free_throws& ft结构体引用,此处暂不加const,需要修改结构成员percent
void set_pc(free_throws& ft)
{
if (ft.attempts != 0)
ft.percent = 100.0f * (float)ft.made / (float)ft.attempts; //强制类型转换
else
ft.percent = 0.0;
}
//返回结构体引用,第一个形参和返回值类型相同
free_throws& accumulate(free_throws& target, const free_throws& source)
{
target.attempts += source.attempts;
target.made += source.made;
set_pc(target);
return target;
}
返回引用时最重要的是避免返回函数终止时不再存在的内存单元引用。
const free_throws& clone2(free_throws& ft)
{
free_throws newguy;
newguy = ft;
return newguy; //返回临时结构体
}
- 该函数返回一个指向临时变量(newguy)的引用,函数运行完毕后newguy将不存在,引无可引
- 同时,也应避免返回指向临时变量的指针
const free_throws& clone(free_throws& ft)
{
free_throws *pt;
*pt = ft;
return *pt; //返回临时结构体
}
- 临时指针使用完后不存在,但指针内容*pt == ft,一直存在
8.2.5 引用用于类对象
将类对象传递给函数时,C++通常的做法是使用引用
//程序清单 8.7
#include <iostream>
#include <string>
using namespace std;
string version1(const string& s1, const string& s2);
const string& version2(string& s1, const string& s2);
const string& version3(string& s1, const string& s2);
int main(void)
{
string input, copy, result;
cout << "Enter a string: ";
getline(cin, input);
copy = input; //按值传递,拷贝副本
cout << "Your string as entered: " << input << endl;
result = version1(input, "***"); //input是string类型,***是char*类型
//version1中const类型不匹配时生成临时变量
cout << "Your string enhanced: " << result << endl << endl;
cout << "Your original string: " << input << endl;
result = version2(input, "###");
cout << "Your string enhanced: " << result << endl << endl;
cout << "Your original string: " << input << endl;
input = copy; //恢复原始的input
cout << "Your original string: " << input << endl;
result = version3(input, "@@@");
cout << "Your string enhanced: " << result << endl << endl;
return 0;
}
string version1(const string& s1, const string& s2)
{
string temp; //临时变量
temp = s2 + s1 + s2;
return temp; //不能返回string&
}
const string& version2(string& s1, const string& s2)
{
s1 = s2 + s1 + s2;
//s1被修改了
return s1;
}
const string& version3(string& s1, const string& s2)
{
string temp; //临时变量
temp = s2 + s1 + s2;
return temp; //引无可引
}
8.2.6 对象、继承和应用
- 基类、派生类和继承,将在13章详细讨论
- 继承:将特性从一个类传递给另一个类的语言特性
- ostream是基类,ofstream是派生类
- 派生类继承了基类的方法,派生类可以使用基类的特性
- 基类引用可以指向派生类对象
//程序清单 8.8
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
const int LIMIT = 5;
void file_it(ostream& os, double fo, const double fe[], int n);
int main(void)
{
ofstream fout;
const char* fn = "ep-data.txt";
fout.open(fn);
if (!fout.is_open())
{
cout << "Can't open " << fn << ". bye.\n";
exit(EXIT_FAILURE);
}
double objective;
cout << "Enter the focal length of your telescope objective in mm: ";
cin >> objective;
double eps[LIMIT];
cout << "Enter the focal lengths, in mm, of " << LIMIT << " eyepieices:\n";
for (int i = 0; i < LIMIT; i++)
{
cout << "Eyepieice #" << i + 1 << ": ";
cin >> eps[i];
}
file_it(fout, objective, eps, LIMIT);
file_it(cout, objective, eps, LIMIT);
cout << "done.\n";
return 0;
}
void file_it(ostream& os, double fo, const double fe[], int n)
{
os << "Focal length of objective: " << fo << " mm.\n";
os << "f.1. eyepieice ";
os << " magnification" << endl;
for (int i = 0; i < n; i++)
{
os << "\t" << fe[i] << "\t\t" << int(fo/fe[i] + 0.5) << endl;
}
}
8.2.7 何时使用引用参数
使用引用参数原因:
- 修改数据对象
- 提高运行速度
什么时候使用引用、指针、按值传递?
使用传递的值不做修改:
- 数据对象很小——按值传递
- 数组——指针(唯一选择)
- 结构——const引用/const指针
- 类对象——const引用
修改数据:
- 内置数据类型——指针
- 数组——指针
- 结构——引用/指针
- 类对象——引用
8.3 默认参数
- 默认参数是当函数调用中省略实参时自动使用的值
- 必须通过函数原型,设置默认值,将值赋给原型中的参数
- 函数定义不需要指定默认值
char* left(const char* str, int n = 1);
- 必须从右向左添加默认值,实参从左向右赋给相应的形参,不能跳过
int harpo(int n, int m = 4, int j = 5); //有效
int chico(int n, int m = 6, int j); //无效
int groucho(int k = 1, int m = 2, int n = 3); //有效
beeps = harpo(2); //==harpo(2, 4, 5)
beeps = harpo(1, 8); //==harpo(1, 8, 5)
beeps = harpo(8, 7, 6); //==harpo(8, 7, 6)
8.4 函数重载
- 默认参数能够使用不同数目的参数调用同一个函数
- 函数多态(重载)能够使用多个同名不同参的函数
- 函数重载的关键是函数特征标:形参的数目、类型和排列顺序
- 类型引用和类型本身视为同一个特征标:double cube(double x) == double cube(double& x)
- 匹配函数时,并不区分const和非const变量
void dribble(char* bits); //重载
void dribble(const char* cbits); //重载
void dabble(char* bits); //非重载
void drivel(const char* bits); //非重载
const char p1[20] = "How's the weather?"; //const char*
char p2[20] = "How's business?"; //char*
dribble(p1); //dribble(const char*)
dribble(p2); //dribble(char*)
dabble(p1); //const变量不能进非const形参
drivel(p1); //drivel(const char*)
drivel(p2); //非const变量可以进const形参
- 是否重载,不看返回值,只看特征标
//非重载
long gronk(int n, float m);
double gronk(int n, float m);
//重载
long gronk(int n, float m);
double gronk(float n, float m);
左值、右值和左值引用、右值引用
8.4.1 重载示例
//程序清单 8.10
#include <iostream>
using namespace std;
const int ArSize = 80;
char* left(const char* str, int n = 1);
unsigned long left(unsigned long num, unsigned ct);
int main(void)
{
const char* trip = "Hawaii!!";
unsigned long n = 12345678;
int i;
char* temp;
for ( i = 0; i < 10; i++)
{
cout << left(n, i) << endl;
temp = left(trip, i);
cout << temp << endl;
delete[] temp;
}
return 0;
}
//返回字符串前n个字符
char* left(const char* str, int n)
{
if (n < 0)
n = 0;
int len = strlen(str);
n = (n < len) ? n : len;
char* p = new char[n + 1];
int i;
for ( i = 0; i < n; i++)
p[i] = str[i];
p[i] = '\0';
// p[n] = '\0';
return p;
}
//返回整数的前n位
unsigned long left(unsigned long num, unsigned ct)
{
unsigned digits = 1; //位数
unsigned long n = num; //原始数据不被更改
if (0 == ct || 0 == num)
return 0;
while (n/=10)
digits++;
if (digits > ct)
{
ct = digits - ct;
while (ct--)
num /= 10;
return num;
}
else
return num;
}
8.4.2 何时使用函数重载
仅当函数执行相同的任务,但使用不同形式的数据时,才应采用函数重载
8.5 函数模板
如果需要多个将同一种算法用于不同类型的函数,请使用函数模板
//程序清单 8.11
//相同功能,不同形参
#include <iostream>
using namespace std;
template <typename T>
void Swap(T& a, T& b);
int main(void)
{
int i = 10;
int j = 20;
cout << "i, j = " << i << ", " << j << endl;
Swap(i, j);
cout << "Now i, j = " << i << ", " << j << endl;
double x = 24.8;
double y = 81.7;
cout << "x, y = " << x << ", " << y << endl;
Swap(x, y);
cout << "Now x, y = " << x << ", " << y << endl;
return 0;
}
template <typename T>
void Swap(T& a, T& b) //按引用/指针传递
{
T temp;
temp = a;
a = b;
b = temp;
}
8.5.1 重载的模板
//程序清单 8.12
#include <iostream>
using namespace std;
const int Lim = 8;
void show(const int arr[]);
template <typename T>
void Swap(T& a, T& b);
template <typename T>
void Swap(T a[], T b[], int n);
int main(void)
{
int i = 10;
int j = 20;
cout << "i, j = " << i << ", " << j << endl;
Swap(i, j);
cout << "Now i, j = " << i << ", " << j << endl;
int d1[Lim] = { 0, 7, 0, 4, 1, 7, 7, 6 };
int d2[Lim] = { 0, 7, 2, 0, 1, 9, 6, 9 };
show(d1);
show(d2);
Swap(d1, d2, Lim);
cout << "After swap:\n";
show(d1);
show(d2);
return 0;
}
template <typename T>
void Swap(T& a, T& b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template <typename T>
void Swap(T a[], T b[], int n) //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;
}
}
void show(const int arr[])
{
cout << arr[0] << arr[1] << "/";
cout << arr[2] << arr[3] << "/";
for (int i = 4; i < Lim; i++)
cout << arr[i];
cout << endl;
}
8.5.2 模板的局限性
template <class T>
void f(T a, T b)
{...}
- 如果T为数组,a = b将不成立;
- 如果T为结构,if(a > b)将不成立;
- 如果T为数组、指针或结构,T c = a * b将不成立;
- 总之,模板函数可以无法处理某些类型。一种解决方法是重载运算符(第11章);另一种方法是为特定类型提供具体化的模板定义。
8.5.3 显式具体化
//程序清单 8.13
#include <iostream>
using namespace std;
const int Lim = 8;
struct job
{
char name[40];
double salary;
int floor;
};
void show(const int arr[]);
void show(const job& j);
template <typename T>
void Swap(T& a, T& b);
template <typename T>
void Swap(T a[], T b[], int n);
template <> void Swap<job>(job& j1, job& j2);
//template <> void Swap<>(job& j1, job& j2);
int main(void)
{
int i = 10;
int j = 20;
cout << "Before swap, i, j = " << i << ", " << j << endl;
Swap(i, j);
cout << "After swap, i, j = " << i << ", " << j << endl << endl;
int d1[Lim] = { 0, 7, 0, 4, 1, 7, 7, 6 };
int d2[Lim] = { 0, 7, 2, 0, 1, 9, 6, 9 };
cout << "Before swap:\n";
show(d1);
show(d2);
Swap(d1, d2, Lim);
cout << "After swap:\n";
show(d1);
show(d2);
cout << endl;
job sue = { "Susan Yaffee", 73000.60, 7 };
job sidney = { "Sidney Taffee", 78060.72, 9 };
cout << "Before swap:\n";
show(sue);
show(sidney);
Swap(sue, sidney);
cout << "After swap:\n";
show(sue);
show(sidney);
return 0;
}
template <typename T>
void Swap(T& a, T& b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template <typename T> //模板重载
void Swap(T a[], T b[], int n) //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;
}
}
template <> void Swap<job>(job& j1, job& j2) //显式具体化 template <> void
//template <> void Swap<>(job& j1, job& j2); //<>中job可选
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(const int arr[])
{
cout << arr[0] << arr[1] << "/";
cout << arr[2] << arr[3] << "/";
for (int i = 4; i < Lim; i++)
cout << arr[i];
cout << endl;
}
void show(const job& j) //函数重载
{
cout << j.name << ": $" << j.salary << " on floor " << j.floor << endl;
}
- 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本
- 显式具体化的原型和定义以template<>打头,通过名称来指出类型
- 非模板 > 具体化模板 > 常规模板
- 使用显式具体化模板的函数不再适用通用模板
- 显式具体化对某种特定类型,作单独处理,其特征标与常规模板一样
8.5.4 实例化和具体化
- 实例化:模板并非函数定义,只有传递具体参数,实现函数调用时,函数模板转变为函数定义,这个过程称为实例化。
- 通过调用实现称为隐式实例化。
- 显式实例化:template void Swap<int>(int, int); 使用Swap()模板生成一个使用int类型的函数调定义。(显式实例化template后不加<>尖括号)
- 显式具体化:template <> void Swap<int>(int&, int&)或template <> void Swap<>(int&, int&),(显式具体化template后加<>尖括号)
- 不要在同一个文件或转换单元中同时使用显式实例化和显式具体化
- 隐式实例化、显式实例化和显式具体化统称为具体化
template <class T>
T Add(T a, T b)
{
return a + b;
}
...
int m = 6;
double x = 10.2;
cout << Add<double>(x, m) << endl;
//强制为double类型实例化,将参数m强制转换为double类型
//以便于函数Add<double>(double, double)的第二个参数匹配
//如果是T Add(T& a, T& b),则不能强制转换Add<double>(x, m),double&不能指向int类型
8.5.5 编译器选择使用哪个函数版本
从最佳到最差:
- 完全匹配:常规>模板
- 提升转换:char和short自动转换为int,float自动转换为double
- 标准转换:int转换为char,long转换为double
- 用户定义的转换:如类声明中定义的转换
完全匹配和最佳匹配:
假设有下列代码:
struct blot { int a, char b[10]; };
blot ink = { 25, "sports" };
...
recyle(ink);
在这种情况下,下面原型都是完全匹配的:
void recyle(blot);
void recyle(const blot);
void recyle(blot &);
void recyle(const blot &);
- 指向非const数据的指针和引用 > const数据的指针和引用。#3和#4将选择#3。
- const和非const之间的区别只适用于指针和引用。#1和#2将出现二义性错误。
- 非模板 > 具体化 > 模板
template <class Type> void recyle( Type t); //Type转换成结构体指针blot*
template <class Type> void recyle( Type* t); //Type转换成结构体blot,最佳匹配
struct blot { int a, char b[10]; };
blot ink = { 25, "sports" };
...
recyle(&ink); //结构体地址
//程序清单 8.14
#include <iostream>
using namespace std;
struct debts
{
char name[50];
double amout;
};
template <typename T>
void ShowArray(T arr[], int n);
template <typename T>
void ShowArray(T* arr[], int n);
int main(void)
{
int things[6] = { 13, 31, 103, 301, 310, 130 };
debts mr_E[3] =
{
{"AAA", 2400.0},
{"BBB", 1300.0},
{"CCC", 1800.0}
};
double* pd[3];
for (int i = 0; i < 3; i++)
pd[i] = &mr_E[i].amout;
ShowArray(things, 6);
ShowArray(pd, 3);
return 0;
}
template <typename T>
void ShowArray(T arr[], int n)
{
cout << "template A\n";
for (int i = 0; i < n; i++)
cout << arr[i] << ' ';
cout << endl;
}
template <typename T>
void ShowArray(T* arr[], int n)
{
cout << "template B\n";
for (int i = 0; i < n; i++)
cout << *arr[i] << ' ';
cout << endl;
}
//程序清单 8.15
#include <iostream>
using namespace std;
template <class T>
T lesser(T a, T b)
{
return a < b ? a : b;
}
int lesser(int a, int b)
{
a = a < 0 ? -a : a;
b = b < 0 ? -b : b;
return a < b ? a : b;
}
int main(void)
{
int m = 20, n = -30;
double x = 15.5, y = 25.9;
cout << lesser(m, n) << endl; //非模板
cout << lesser(x, y) << endl; //常规模板
cout << lesser<>(m, n) << endl; //指定模板
cout << lesser<int>(x, y) << endl; //指定模板,强制转换
return 0;
}
8.5.6 模板函数的发展
1.xpy应为什么类型?
template <class T1, class T2>
void ft (T1 x, T2 y)
{
...
?type? xpy = x + y;
...
}
2. 关键字decltype
int x;
decltype(x) y; //y和x同一类型
decltype的参数可以是表达式,上述代码改进为:
template <class T1, class T2>
void ft (T1 x, T2 y)
{
...
decltype (x + y) xpy = x + y;
...
}
假设有如下声明:
decltype (expression) var;
第一步:如果expression是一个没有用括号括起的标识符,则var的类型与该标识符的类型相同,包括const等限定符:
double x = 5.5;
double y = 7.9;
double &rx = x;
const double* pd;
decltype(x) w; //w is type double;
decltype(rx) u = y; //u is type double&
decltype(pd) v; //v is type const double*
第二步:如果expression是一个函数调用,则var的类型与函数返回类型相同:
long indeed(int);
decltype (indeed(3)) m; //m is type long
第三步:如果expression是一个左值,则var为指向其类型的引用。要进入第三步,expression是用括号括起的标识符:
double xx = 4.4;
decltype((xx)) r2 = xx; //r2 is double &
decltype(xx) w = xx; //w is type double(第一步)
第四步:如果前面条件都不满足,则var类型与expression类型相同:
int j = 3;
int& k = j;
int& n = j;
decltype(j+6) i1; //i1 is type int
decltype(100L) i2; //i2 is type long
decltype(k + n) i3; //i3 is type int;
虽然k和n都是引用,但表达式k+n不是引用,是两个int的和。
3.另一种函数声明语法(C++11后置返回类型)
无法预知x + y 的返回类型,从左向右编译,现在暂时还不认识x,y
template <class T1, class T2>
?type? gt (T1 x, T2 y)
{
...
return x + y;
...
}
double h (int x, float y); ——用C++11后置返回类型写成——>auto h (int x, float y) -> double;
template <class T1, class T2>
auto gt (T1 x, T2 y) -> decltype(x + y)
{
...
return x + y;
...
}