1 内联函数inline
- 目的:为提高程序的运行速度
- 与常规函数的区别:
1)被调用时的运行机制不一样
2)编译器使用函数代码替换来代替函数调用 - 如何选择内联函数和常规函数:当代码执行时间很短时,远小于函数调用机制所花费的时间时,就使用内联函数。否则,没有必要使用内联函数。
- 内联函数的使用:
1)一般将整个函数定义放于函数原型处,并加上inline关键词。如:
inline void add (int a, int b) { return a + b; }
2)即使你使用了inline,编译器也不一定会使用。当你定义的inline函数中代码过长,或者有递归时等情况,都不会使用定义的inline。 - C++中的inline就相当于C中的define宏。
2 引用变量&
- 目的:主要用途是用于函数的形参,通过将引用作为函数的参数,可以直接使用原始数据,而不是副本。对于类和结构等占据内存较大的变量,建议使用引用,这样可以节省时间和内存。
- 定义:引用是已定义的变量的别名,相当于一个变量,多个名称。引用的变量不能为表达式。
- 引用的创建与初始化:
- 引用必须在声明时进行初始化,不能先声明,再初始化。正确示例:
int & rodents = rats;
当然rats变量是之前就已经定义的。 - 不可以直接引用常量。错误示例:
double & d = 12.3; d = 110;
这点我不太明白 - 可以指向常量的引用。正确示例:
const double & d = 12.3;
- 可以通过初始化来设置引用,但不能通过赋值来设置。这点也不是很明白
- 引用必须在声明时进行初始化,不能先声明,再初始化。正确示例:
- 引用作为函数参数:按引用传递,允许被调用的函数能够访问调用函数中的变量。
1) 原因:
能够修改调用函数中的数据对象
可以提高程序的运行速度
2) 原则:
按值传递: 如果数据对象很小,如内置数据类型或小型结构
按指针传递:如果数据对象是数组,唯一选择
按引用传递:如果数据对象是较大的结构或类对象,以提高程序的效率,节省复制的时间和空间。
3)比较按引用传递和按值传递:
void Swapr (int &a, int &b);
void Swapv (int a, int b);
- 声明函数参数的方式不同
- 变量a和b一个是别名(原始数据),一个是副本
4) 比较按引用传递和按指针传递
void Swapr (int &a, int &b);
void Swapp (int *a, int *b);
- 声明函数参数的方式不同
- 按指针传递时,整个过程要使用解除引用运算符*
- 临时变量
1) 何时生成临时变量:
实参类型正确,但为字面常量或表达式。
实参类型不正确,但可以转换为正确的类型。
2) 生存周期: 只在函数调用期间,此后会被编译器删除
3) 函数参数应尽可能使用const引用:
使用const可以避免无意中修改数据
使用const是函数可以接受const和非const实参,否则只能接受非const数据
使用const引用使函数能够正确生成和使用临时变量 - 返回引用:效率更高
1)不要返回局部变量的引用
2) 返回引用时,要求函数参数中包含被返回的引用对象
3) 函数可以不返回值,默认返回传入的引用对象本身
4) 使用const用于返回引用时,是为了让函数返回的是不可修改的左值,不能被赋值。 - 继承在引用上的特性:基类引用可以指向派生类的对象,无需进行强制转换,即可以定义一个接受基类引用作为参数的函数,调用该函数时,既可以将基类对象作为参数,也可以将派生类对象作为参数。
3 默认参数
- 必须通过原型设置默认值
- 要为某个参数设置默认值,则必须为他右边的所有参数提供默认值
- 通过使用默认参数,可以减少要定义的析构函数/方法以及方法重载的数量
4 函数重载
- 目的:完成相同的工作,但使用不同的参数列表,即函数名相同,函数特征标不同(参数数目/参数类型不同),但与函数返回类型无关
- 编译器在检查函数特征标时,将按引用传递和按值传递视为同一个特征标。
- 重载引用参数
void sink(double &r1);
void sink(const double &r2);
void sink(double &&r3);
1) 左值引用参数r1与可修改的左值参数匹配
2) const左值引用参数r2与==可修改的左值参数、const左值参数和右值参数(如两个double值的和)==匹配
3) 左值引用参数r3与左值匹配
5 函数模板
- 目的:函数处理过程一样,传入参数不一样时,采用函数模板来作为通用的函数描述。它可以使生成多个函数定义更简单和可靠。
- 建立函数模板:
- 模板头与函数原型和函数定义是不可分割的一个整体:
模板函数原型:
- 模板头与函数原型和函数定义是不可分割的一个整体:
template <typename T>
void Swap (T &a, T &b);
模板函数定义:
template <typename T>
void Swap (T &a, T &b)
{
...
}
其中,T可以接受int/double/…参数,编译器会生成对应版本的函数。
- 模板函数重载:只要特征标不一样即可。例如:
输入参数为引用:
template <typename T>
void Swap (T &a, T &b);
输入参数为指针,且包含一个具体类型:
template <typename T>
void Swap (T *a, T *b, int n);
- 显式具体化:例如当你要把一个结构赋给一个结构时,只想将某些成员进行赋给,并不是所有时,无法使用模板重载来实现,即用结构来代替T。
1) 原型和定义要以template <>
开头,并通过名称指出类型:其中job
就是类型
template <> void Swap <job> (job &, job &);//第一种写法
template <> void Swap (job &, job &);//第二种写法
2) 编译器选择原型时,非模板函数>显示具体化>模板函数
- 实例化和具体化
1) 隐式实例化:编译器使用模板为特性类型生成函数定义时,得到的就是模板实例。例如:
Swap(i,j);//其中i,j为int型
2) 显示实例化:直接命令编译器创建特定的实例。声明语法为:template void Swap<int> (int, int);
调用时:Swap(i,j);
3) 隐式实例化、显示实例化和显示具体化统称为具体化。它们表示的都是使用具体类型的函数定义,而不是通用描述。
- decltype关键字
template<class T1, class T2>
void ft (T1 x, T2 y)
{
...
??? xpy = x + y;
...
}
这个时候xpy
为什么类型呢?使用关键字decltype就可以解决,decltype (expression) var
按顺序依次:
1) 如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符类型一样,例如:
const double * p;
decltype(p) v;//v与p的类型一样,均为 const double *
2) 如果expression是一个函数调用,则var的类型函数的返回类型一样,例如:
long indeed(int);
decltype (indeed(3)) m;//m的类型为long
3) 如果expression是一个左值,也就是用括号括起来的标识符,则var的类型为指向其类型的引用,例如:
double xx = 4.4;
decltype((xx)) r2 = xx;//r2的类型为double &
4) 如果前面的条件均不满足,则var的类型与expression的类型一样,例如:
int j = 3;
decltype(j+3) i;//i的类型为(j+3)的类型,也就是int