8.1 内联函数
内联函数:只有一行代码的小型非递归函数
内联函数的运行速度比常规函数稍快,但代价是占用更多的内存。
内联函数的使用:
- 在函数声明前加上关键字inline 或者
- 在函数定义前加上关键字inline
内联函数按值传递参数
8.2 引用变量
引用是已定义的变量的别名
引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据。这样除指针之外,引用也为函数处理大型数据提供了一种非常方便的途径,同时对于设计类来说,引用也是必不可少的。
引用
int rats;
int& rodents = rats; //必须在声明引用变量时进行初始化
引用经常被用作函数参数,是得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。(按值传递导致被调用函数使用调用程序的值的拷贝)函数调用使用实参初始化形参
传递引用和传递指针的区别
- 声明函数参数的方式不同
指针与引用
void swapr(int& a, int& b)
void swapp(int* p, int* q)
- 指针版本需要在函数使用p, q的整个过程中使用解除引用运算符*
临时变量、引用参数和const
如果引用参数是const,则编译器将在下面两种情况下生成临时变量
- 实参的类型正确,但不是左值
- 实参的类型不正确,但可以转换为正确的类型
左值:可被引用的数据对象(变量、数组元素、结构成员、引用和解除引用的指针)
非左值: 字面常量(用引号括起来的字符串除外,它们由其地址表示)和包含多项的表达式
如果接受引用参数的函数的意图是修改作为参数传递的变量,则C++将禁止创建临时变量
如果函数的目的只是使用传递的值而不修改它们,C++将在必要时生成临时变量
将引用用于结构
程序清单8.6中,需要通过函数修改某一结构成员的值,这个时候按值传递显然不可行,所以要用按地址传递或者按引用传递
清单8.6
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& accumulate(free_throws& target, const free_throws& source)
如果返回类型被声明为free_throws而不是free_throws&,上述返回语句将会返回target的拷贝
free_throws dup;
dup = accumulate(team, five);
accumulate返回值为引用,将直接把team值赋给dup,否则如果返回的是结构,则会将结构复制到一个临时的位置,再将这个拷贝复制给dup
返回引用时需要注意的问题
应避免返回函数终止时不再存在的内存单元引用(同时也应避免返回指向临时变量的指针)
避免这种问题的方法:
- 返回一个作为参数传递给函数的引用
- 用new来分配新的存储空间
new
const free_throws& clone(free_throw& ft)
{
free_throws* pt;
*pt = ft;
return *pt;
}
该函数实际返回这个结构的引用
在不需要new分配的内存时,应使用delete来释放它们
将引用用于类对象
将类对象传递给函数时,C++的通常做法是使用引用(可以使用引用,让函数将类string、ostream、istream、ofstream、ifstream等类的对象作为参数)
示例1:
string version1(const string& s1, const string& s2)
{
string temp;
temp = s2 + s1 + s2;
return temp;
}
如果把参数换成string对象,结果将不变,但是使用引用效率更高
但如果返回的是引用,则不能使用函数中定义的临时变量,如下所示,是很糟糕的
返回引用
const string& version3(const string& s1, const string& s2)
{
string temp;
temp = s2 + s1 + s2;
return temp;
}
如上图示例1所示,实参(input和"*")的类型分别为string和const char
string类定义了一种char 到string的转换功能,这使得可以使用C风格字符串来初始化string对象
对象、继承和引用
能够将特性从一个类传递给另一个类的语言特性被称为继承
继承特征:基类引用可以指向派生类对象,可以定义一个接受基类引用作为参数的函数,调用函数时,可以将基类对象作为参数,也可以将派生类对象作为参数
何时使用引用参数
使用引用参数的原因:
-
程序员能够修改调用函数中的数据对象
-
通过传递引用而不是整个数据对象,可以提高程序的运行速度
使用引用、指针、按值传递的指导原则
对于使用传递的值而不做修改的函数: -
如果数据对象很小,如内置数据类型或小型结构,则按值传递。
-
如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
-
如果数据对象是较大的结构,则使用const指针或const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类参数的标准方式是按引用传递。
对于修改调用函数中数据的函数: -
如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中x是int),则很明显,该函数将修改x
-
如果数据对象是数组,则只能使用指针
-
如果数据对象是结构,则使用引用或指针
-
如果数据对象是类对象,则使用引用
8.3 默认参数
设置默认值必须通过函数原型
对于带参数列表的函数,必须从右向左添加默认值。
参数
int harpo(int n, int m = 4, int j = 5);
int groucho(int k = 1, int m = 2, int n = 3);
实参按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数。
用new创建一个新字符串,以存储被选择的字符
创建new
char* left(const char* str, int n)
{
if (n < 0)
n = 0;
char* p = new char[n+1];
int i;
for (int i = 0; i < n && str[i]; i++)
p[i] = str[i];
while (i <= n)
p[i++] = '\0';
return p;
}
如果输入的字符串长度小于n,显然浪费了空间,所以用下面两种方法去创建恰好够用的空间
法二
int len = strlen(str);
n = (n < len) ? n : len;
char* p = new char[n + 1];
但由于添加了一个函数调用(strlen()),使得程序更长,运行速度降低
或者可以考虑如下代码,可以省掉strlen()函数调用
法三
int m = 0;
while (m <= n && str[m] != '\0') //可替换为while (m <= n && str[m])
m++;
char* p = new char[m + 1];
8.4 函数重载
函数重载的关键是函数的参数列表,也称为函数特征标。特征标(参数数目和类型,参数排列顺序)
编译器在检查函数特征标时,将把类型引用和类型本身视为同一特征标。
void dribble(char* bits);
void dribble(const char* cbits); //重载
void dabble(char* bits); //只能匹配非const指针
void drivel(const char* bits); //既能匹配const指针,也能匹配非const指针
返回类型可以不同,但特征标也必须不同
double gronk(int n, float m);
long gronk(float n, float m);
重载引用参数
类设计和STL经常使用引用参数,会调用最匹配的版本
void stove(double& r1); //matches modifiable lvalue
void stove(const double& r2); //matches const lvalue,rvalue or modifiable
void stove(double&& r3); //matches rvalue
double x = 55.5;
const double y = 32.0;
stove(x); // calls stove(double&)
stove(y); //calls stove(const double&)
stove(x + y); //calls stove(double&&)
重载示例
要获得9位数字的前4位:
ct = digits - ct; //digits位数字总位数,ct位要获得的位数
while (ct--)
num /= 10;
return num;
何时使用函数重载
仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。
8.5 函数模板
如果需要多个将同一种算法用于不同类型的函数,请使用模板
void Show(int a[]); //其中 在函数头或函数原型中,int* a和int a[]含义相同
模板的局限性
编写的模板函数很可能无法处理某些类型
显式具体化
当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
- 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本。
- 显示具体化的原型和定义应以template<>打头,并通过名称来指出类型。
- 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。
template <> void Swap<job>(job&, job&); // 可以省略<job>
实例化和具体化
template void Swap<int>(int&, int&); //显示实例化
template <> void Swap<int>(int&, int&); //显示具体化
这些声明的意思是:
不要使用Swap()模板来生成函数定义,而应使用专门为int类型显式地定义的函数定义。
不能在同一文件(或转换单元)使用同一类型的显示实例和显式具体化。
隐式实例化、显式实例化和显式具体化统称为具体化。
编译器选择使用哪个函数版本
- 完全匹配,但常规函数优于模板
- 提升转换(例如,char和shorts自动转化为int,float自动转换为double)
- 标准转换(例如,int转换成char,long转换为double)
- 用户定义的转换,如类声明中定义的转换
完全匹配和最佳匹配
进行完全匹配时,C++允许某些“无关紧要的转换”
具体见书本P290
const和非const的区别仅适用于指针和引用指向的数据
非模板优先于模板函数
较具体模板优先于普通模板
具体指的是编译器腿短使用哪种类型时执行的转换最少
double* pd[3]; //pd为指针数组
template<class T>
T lesser(T a, T b)
{
return a < b ? a : b;
int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9;
lesser<>(m,n); //<>指出编译器选择模板函数
lesser<int>(x, y); //x, y强制转换为int