内联函数
编译过程的目标是可执行程序(由一组机器语言指令组成)
。运行程序时,操作系统将指令载入到计算机内存中,则每条指令都有其特定的内存地址
。
内联函数的编译代码与其它程序代码内联,编译器就使用相应的函数代码替换函数调用。
1.1 常规函数和内联函数
常规函数:调用使得程序调到另一个地址(函数的地址),并在函数结束时返回。
将程序流程转到独立的函数。
内联函数:程序无需跳到另一个位置处执行代码,再跳回来。
如果编译器认定可以设置为内联函数,在编译之前,就会将主调函数中调用该内联函数的位置,直接替换为该函数体的内容,再进行编译,这样就省去了运行时,调用函数的CPU开销
1.2 内联函数的优缺点
优点:运行速度比常规函数快。
缺点:占用内存大
1.3 使用内联函数的要求
- 在函数
声明前
加上 关键字inline - 在函数
定义前
加上 关键字inline - 一般用法:省略原型,将整个定义(函数头和所有函数代码)放在本应提供原型的地方。
- ⚠️注意点:内联函数不能递归,末尾不加分号(;)
2. 引用变量
引用:已定义变量的别名。
主要用途 :用作 函数的形参
。通过引用变量用作参数,函数将使用原始数据,而不是使用副本。
2.1 创建引用变量
C和C++中使用 地址符(&)
来指示变量的地址。用来声明引用。 (符号重载)
cpp
int rats;
int & rodents = rats; // &不是地址运算符,是类型标识符的一部分。
引用 必须在声明引用时将其初始化,而不是类似指针,先声明,再赋值。
引用更接近 const指针,必须在创建时进行初始化,变量关联后,就无法更改。
2.2 引用用作函数参数
引用传递 :当引用被用作函数参数
时,使得函数中的变量名成为调用程序中的变量的别名
。
允许被调用的函数能够
访问调用函数
中的变量。
按值传递 :被调用函数使用调用程序的值的拷贝。
C语言中改用按指针传递的方式避开按值传递的限制。
使用和访问原始数据的方法:按 引用传递
和 传递指针
。
当左值引用参数是 const时,会生成临时变量的两种情况:
左值参数:可被引用的数据对象。
-
实参的类型正确,但不是左值。
-
实参的类型不正确,但可转换为正确的类型。
尽可能使用const
使用const可以避免无意中修改数据的编程错误
使用const使函数能够处理
const
和非const实参
,否则只能接受非const数据
。使用
const引用
使函数能够正确生成并使用临时变量
(如果实参
和引用参数
不匹配,c++将生成临时变量
)。
C++11 引入 右值引用
,可指向右值,使用 &&
来声明。
相关链接:C++引用_小梁今天敲代码了吗的博客-CSDN博客https://blog.csdn.net/weixin_43780415/article/details/128448823
2.3 结构引用
引用适合 结构和类(用户自定义类型,非基本的内置类型)。
引入引用的目的:用于用户自定义类型,而不是基本的内置类型。
使用 结构引用参数
的方式 与基本变量引用
相同,只需在声明结构参数时使用 引用运算符&
即可。
2.5 对象、继承和引用
继承 :将语言的特性从一个类
传递 给另一个类
。
继承的特征 :派生来继承了基类的方法,基类引用可以指向派生类对象,而无需进行强制类型转换
。
ostream
是基类
ofstream
是派生类
2.6 何时使用引用参数
使用引用参数的两个主要原因
- 程序员能修改调用函数中的数据对象。
- 通过传递引用而不是整个数据对象,提高程序的运行速度。(当数据对象(结构和类对象)较大时很重要)
对于 使用传递的值
而 不作修改
的函数
- 数据对象
很小
,如内置数据类型或小型结构
,按值传递
; - 数据对象是
数组
,则使用指针
,因为这是唯一的选择,并将指针声明为指向 const 的指针
; - 数据对象是
较大的结构
,则使用const 指针
或const 引用
,可以 节省复制结构所需的时间和空间; - 数据对象是
类对象
,则使用const 引用
。传递类对象参数的标准方式是按引用传递
。
对于修改调用函数中数据
的函数
-
数据对象是
内置数据类型
,则使用指针
; -
数据对象是
数组
,则只能
使用指针
; -
数据对象是
结构
,则使用引用或指针
; -
数据对象是
类对象
,则使用引用
。
3. 默认参数
定义:指当函数调用中省略了实参时自动使用的一个值。
设置默认值的方法:通过函数原型将值赋给原型中的参数。例left() 原型:
cpp
char *left(const char *str,int n = 1);
对于带参数列表
的函数,必须从右向左
添加默认值(要为某个参数设置默认值,必须为其右边的所有参数提供默认值)。
cpp
int harpo(int n,int m = 4 , int j = 5); //VALID
int chico(int n ,int m = 6,int j); // INVALID
实参按 从左向右
的顺序依次被赋给相应的形参,而不能跳过任何参数。
cpp
beeps = harpo(3, ,8); // 不允许
默认参数的好处 :减少要定义的析构函数、方法以及方法重载的数量*。
注意:只有原型指定了默认值,函数定义与没有默认参数时完全相同。
4. 函数重载(polymorphism)
默认参数
可以使用不同数目的参数调用同一个函数。- 术语
多态(polymorphism)
指多种形式,函数多态允许函数使用多种形式。 - 术语
函数重载
指可以有多个同名的函数,则对名称进行重载。
函数多态(函数重载)
可使用多个同名的函数。
函数重载的关键是函数的参数列表
---> 函数特征标
。
C++允许定义名称相同
的函数,条件是特征标不同
。
编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标。
匹配函数时,不区分
const
和非const变量
。
⚠️注意:真正让函数能够进行重载的是:特征标。
何时使用函数重载?
函数重载不可滥用。仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。
相关链接:C++ 函数提高_小梁今天敲代码了吗的博客-CSDN博客https://blog.csdn.net/weixin_43780415/article/details/128460372
5. 函数模版
5.1 重载的模板
函数模板
是通用的函数描述
,使用泛型(可用具体的类型替换)
来定义函数。所以也叫做通用编程
。
建立一个模板,关键字 template
和 typename
是必需,除非使用关键字class
代替typename,必须使用 尖括号<>
。
cpp
template <typename T> /*C++98 标准时添加关键字 typename*/
template <class T> /*C++98之前使用class*/
Tips:如果需要多个将同一种算法
用于不同类型的函数
,请使用模板
。如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数
时,应使用关键字typename
而不是class
。
⚠️注意:函数模板不能缩短可执行程序。
模板重载和函数重载类似。
5.2 模板的局限性
模板函数也会有一些无法处理的某些类型。
cpp
template <class T> /*等于 template <typename T>*/
void f(T a,T b)
{
a=b; /*如果T为数组时,假设不成立*/
if(a > b) /*如果T为结构时,假设不成立*/
}
5.3 显式具体化
当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
C++98标准使用的方法
- 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本。
- 显示具体化的原型和定义应以
template<>打头
,并通过名称
来指出类型。 具体化
优先于常规模板
,而非模板函数
优先于具体化
和常规模板
。
显式具体化的格式:
cpp
template <> void Swap<int>(int&,int&)
5.4 实例化和具体化
在代码中包含函数模板本身并不会生成函数定义,只是一个用于生成函数定义的方案。
隐式实例化:编译器在使用模板事会为特定类型生成函数定义时,即可实现模板实例。
显式实例化:直接告知编译器创建特定的实例。
cpp
/*语法:声明所需的类 ----> 用符号 <> 符号指示类型,并在声明前加上关键字template*/
template void Swap<int>(int,int); /*显式实例化*/
注意:显式具体化
声明在关键字template后包含<>
,而显式实例化没有。
不要试图在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化,否则会出错。
隐式实例化
、显式实例化
和显式具体化
统称为具体化
。
5.5 编译器选择使用哪个函数版本
重载解析:决定为函数调用使用哪一个函数定义的过程。
解析的过程
- 创建
候选函数列表
,包含被调用函数名称
相同的所有函数 - 使用候选函数列表创建可执行函数列表,这些都是参数数目正确的函数,为此有一个
隐式转换序列
,其中包括实参类型和相应的形参类型完全匹配的情况。 - 确定是否有最佳可执行函数,如果有则调用,没有则报错
在实际场景下,只考虑特征标,不考虑返回类型。
相关链接:C++模板_小梁今天敲代码了吗的博客-CSDN博客https://blog.csdn.net/weixin_43780415/article/details/130388222