Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
Shujia Huang authored and Shujia Huang committed Feb 9, 2022
1 parent bbcda5e commit d0bf152
Showing 1 changed file with 46 additions and 32 deletions.
78 changes: 46 additions & 32 deletions booknotes/chapter08.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

## 8.1 C++ 内联函数

内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将它 们组合到程序中。 常规函数调用也使程序跳到另一个地址(函数的地址),并在函数结束时返回。
内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。 常规函数调用也使程序跳到另一个地址(函数的地址),并在函数结束时返回。

C++内联函数提供了另一种选择。内联函数的编译代码与其他程序 代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数 调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
C++内联函数提供了另一种选择。内联函数的编译代码与其他程序代码“内联”起来了。也就是说,**编译器将使用相应的函数代码替换函数调用**。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。

![image-20210805155012304](https://static.fungenomics.com/images/2021/08/image-20210805155012304.png)

Expand All @@ -27,11 +27,11 @@ C++内联函数提供了另一种选择。内联函数的编译代码与其他

## 8.2 引用变量

C++新增了一种复合类型——引用变量。引用是已定义的变量的别 名(另一个名称)。
C++新增了一种复合类型——引用变量。**引用是已定义的变量的别名(另一个名称)**

### 8.2.1 创建引用变量

C++给`&`符号赋 予了另一个含义,将其用来声明引用。例如,要将 `rodents`作为`rats`变量的别名,可以这样做:
C++给`&`符号赋予另一个含义,将其用来声明引用。例如,要将 `rodents`作为`rats`变量的别名,可以这样做:

```Cpp
int rats;
Expand All @@ -40,40 +40,44 @@ int &rodents = rats; // makes rodents an alias for rats

其中,**`&` 不是地址运算符,而是类型标识符的一部分**。就像声明中的 `char*` 指的是指向 `char` 的指针一样,`int &` 指的是指向 `int` 的引用。

`rodents` 加1将影响这两个变量。更准确地说, `rodents++` 操作将一个有两个名称的变量加1
`rodents` 加1将影响这两个变量。更准确地说, `rodents++` 操作将一共有两个名称的变量加1

引用看上去很像伪装表示的指针(其中,`*` 解除引用运算符被隐式理解)。实际上,引用还是不同 于指针的。除了表示法不同外,还有其他的差别。例如,差别之一是, 必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值。
引用看上去很像伪装表示的指针(其中,`*` 解除引用运算符被隐式理解)。实际上,引用还是不同于指针的。除了表示法不同外,还有其他的差别。例如,差别之一是, 必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值。

![image-20210805160617225](https://static.fungenomics.com/images/2021/08/image-20210805160617225.png)

引用更接近const指针,必须在创建时进行初始化,一旦与某个变量 关联起来,就将一直效忠于它。也就是说:某个变量的引用是不可更改的。
引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。也就是说:**某个变量的引用是不可更改的**

**引用是别名**
> **引用是别名**
### 8.2.2 将引用用作函数参数

引用经常被用作函数参数,使得函数中的变量名成为调用程序中的 变量的别名。这种传递参数的方法称为按引用传递。按引用传递允许被 调用的函数能够访问调用函数中的变量。C++新增的这项特性是对C语 言的超越,C语言只能按值传递。按值传递导致被调用函数使用调用程 序的值的拷贝(参见图8.2)。
**引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递**。按引用传递允许被调用的函数能够访问调用函数中的变量。**C++新增的这项特性是对C语言的超越,C语言只能按值传递**。按值传递导致被调用函数使用调用程序的值的拷贝(参见图8.2)。

![image-20210805161500948](https://static.fungenomics.com/images/2021/08/image-20210805161500948.png)

交换函数必须能够修改调用程序中的 变量的值。这意味着按值传递变量将不管用,因为函数将交换原始变量 副本的内容,而不是变量本身的内容。但传递引用时,函数将可以使用 原始数据。另一种方法是,传递指针来访问原始数据。
交换函数必须能够修改调用程序中的变量的值。这意味着按值传递变量将不管用,因为函数将交换原始变量副本的内容,而不是变量本身的内容。但传递引用时,函数将可以使用原始数据。另一种方法是,传递指针来访问原始数据。

![image-20210805162214166](https://static.fungenomics.com/images/2021/08/image-20210805162214166.png)

![image-20210805162432252](https://static.fungenomics.com/images/2021/08/image-20210805162432252.png)

### 8.2.3 引用的属性和特别之处
`refcube()` 函数修改了 `main()` 中的 `x` 值,而 `cube()` 没有,这提醒我们为何通常按值传递。变量 `a` 位于 `cube()` 中,它被初始化为 `x` 的值,但修改 `a` 并不会影响 `x`。但由于 `refcube()` 使用了引用参数,因此修改 `ra` 实际上就是修改 `x`。如果只是让函数使用传递给它的信息,而不对这些信 息进行修改,同时又想使用引用,则应使用常量引用。
`refcube()` 函数修改了 `main()` 中的 `x` 值,而 `cube()` 没有,这提醒我们为何通常按值传递。变量 `a` 位于 `cube()` 中,它被初始化为 `x` 的值,但修改 `a` 并不会影响 `x`。但由于 `refcube()` 使用了引用参数,因此修改 `ra` 实际上就是修改 `x`。如果只是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用。

例如,在这个例子中,应在函数原型和函数头中使用`const`

```Cpp
double refcube(const double &ra);
```
如果要编写类似于上述示例的函数(即使用基本数值 类型),应采用按值传递的方式,而不要采用按引用传递的方式。当数 据比较大(如结构和类)时,引用参数将很有用。
如果要编写类似于上述示例的函数(即使用基本数值 类型),应采用按值传递的方式,而不要采用按引用传递的方式。当数据比较大(如结构和类)时,引用参数将很有用。
![image-20210805163836749](https://static.fungenomics.com/images/2021/08/image-20210805163836749.png)
> 函数中应尽可能将引用形参声明为 const,这样好处有三个:
>
> - const 可以避免无意中修改数据,从而导致编程错误;
> - const 使函数能够处理 const 和非 const 实参,否则只能接受非 const 数据;
> - const 引用使函数能够正确生成并使用临时变量。
### 8.2.4 将引用用于结构体
Expand Down Expand Up @@ -112,21 +116,31 @@ double refcube(const double &ra);
- 能够修改调用函数中的数据对象;
- 通过传递引用而不是整个数据对象,可以提高程序的运行速度。
当数据对象较大时(如结构和类对象),第二个原因最重要。这些也是使用指针参数的原因。这是有道理的,因为引用参数实际上是基于 指针的代码的另一个接口
当数据对象较大时(如结构和类对象),第二个原因最重要。这些也是使用指针参数的原因。这是有道理的,因为引用参数实际上是基于指针的代码的另一个接口
使用引用的原则
**以下总结使用引用的原则**
![image-20210805171441141](https://static.fungenomics.com/images/2021/08/image-20210805171441141.png)
- 如果数据对象很小。如内置数据类型或者小型数据结构,则按值传递;
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向 const 的指针;
- 如果数据对象是较大的结构体,则使用 const 指针或者 const 引用,以便提升程序的效率。这样可以节省复制结构体所需的时间和空间;
- 如果数据对象是类对象,则使用 const 引用。类设计的语义常常要求使用引用,**这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递**。
对于修改调用函数中数据的函数:
- 如果数据对象是内置数据类型,则使用指针(不使用引用)。看到诸如:`fixit(&x)` 这样的代码(x是int),则很明显,该函数将要修改x;
- 如果数据对象是数组,则只能使用指针;
- 如果数据对象是结构体,则可以使用引用或者指针;
- 如果数据对象是类对象,则(首选)使用引用。
## 8.3 默认参数
默认参数指的是当函 数调用中省略了实参时自动使用的一个值。例如:
默认参数指的是当函数调用中省略了实参时自动使用的一个值。例如:
```Cpp
char * left(const char *str, int n=1);
```

对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值:
**对于带参数列表的函数,必须从右向左添加默认值**。也就是说,要为某个参数设置默认值,**则必须为它右边的所有参数提供默认值**

```Cpp
int harpo(int n, int m=4, int j=5); // Valid
Expand All @@ -135,17 +149,17 @@ int groucho(int k=1, int m=2, int n=3); // Valid
```
实参按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数。
**默认参数只在声明函数的时候给出,定义函数时,则不需要给出**。
***默认参数只在声明函数的时候给出,定义函数时,则不需要给出***。
![image-20210807220518596](https://static.fungenomics.com/images/2021/08/image-20210807220518596.png)
![image-20210807220537984](https://static.fungenomics.com/images/2021/08/image-20210807220537984.png)
## 8.4 函数重载
函数多态是C++在C语言的基础上新增的功能。默认参数让您能够 使用不同数目的参数调用同一个函数,而函数多态(函数重载)让您能够使用多个同名的函数,这称为函数重载,它们 完成相同的工作,但使用不同的参数列表。
函数多态是C++在C语言的基础上新增的功能。默认参数让我们能够使用不同数目的参数调用同一个函数,而函数多态(函数重载)让我们能够使用多个同名的函数,这称为函数重载,它们完成相同的工作,但使用不同的参数列表。
函数重载的关键是函数的参数列表——也称为函数特征标 (function signature)。
**函数重载的关键是函数的参数列表——也称为函数特征标 (function signature)**
如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。 C++允许定义名称相同的函数,条件是它们的特征标不同。
Expand All @@ -157,24 +171,24 @@ int groucho(int k=1, int m=2, int n=3); // Valid
double cube(double x);
double cube(double &x);
```
可能认为可以在此处使用函数重载,因为它们的特征标看起来不 同。然而,请从编译器的角度来考虑这个问题。假设有下面这样的代码:
可能认为可以在此处使用函数重载,因为它们的特征标看起来不同。然而,请从编译器的角度来考虑这个问题。假设有下面这样的代码:

```Cpp
cout << cube(x);
```

参数 `x``double x` 原型和 `double &x` 原型都匹配,因此编译器无法确 定究竟应使用哪个原型。为避免这种混乱,**编译器在检查函数特征标 时,将把类型引用和类型本身视为同一个特征标**
参数 `x``double x` 原型和 `double &x` 原型都匹配,因此编译器无法确定究竟应使用哪个原型。为避免这种混乱,**编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标**

请记住,**是特征标(即,函数特征列表),而不是函数类型使得可以对函数进行重载**。 例 如,下面的两个声明是互斥的:
请记住,**是特征标(即,函数特征列表),而不是函数返回类型使得可以对函数进行重载**。 例 如,下面的两个声明是互斥的:

```Cpp
long gronk(int n, float m); // same signatures,
double gronk(int n, float m); // hence not allowed
```
因此,C++不允许以这种方式重载gronk( )。返回类型可以不同,但 特征标也必须不同
因此,C++不允许以这种方式重载gronk( )。**返回类型可以不同,但特征标也必须不同**
匹配函数时,并不区分`const`和非`const`变量。看下面的原型:
匹配函数时,并不区分`const`和非`const`变量(这就要小心了)。看下面的原型:
```Cpp
void dribble(char *bits); // overloaded
Expand All @@ -185,17 +199,17 @@ void drivel(const char *bits); // not overloaded
### 8.4.1 函数重载示例
### 8.4.2 何时使用函数重载

虽然函数重载很吸引人,但也不要滥用。仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。
**虽然函数重载很吸引人,但也不要滥用。仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载**

## 8.5 函数模板

现在的C++编译器实现了C++新增的一项特性——函数模板。函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如`int``double`)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型(parameterized types)。
现在的C++编译器实现了C++的另一个新增特性——函数模板。函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如`int``double`)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型(parameterized types)。

![image-20210807224625424](https://static.fungenomics.com/images/2021/08/image-20210807224625424.png)

第一行指出,要建立一个模板,并将类型命名为 `AnyType`。关键字 `template``typename` 是必需的,除非可以使用关键字 `class` 代替 `typename`。 另外,**必须使用尖括号**。类型名可以任意选择(这里为 `AnyType`),只要遵守C++命名规则即可;许多程序员都使用简单的名称,如 `T`

模板并不创建任何函数,而 只是告诉编译器如何定义函数。需要交换`int`的函数时,编译器将按模板 模式创建这样的函数,并用`int`代替`AnyType`。同样,需要交换`double`的函数时,编译器将按模板模式创建这样的函数,并用`double`代替 `AnyType`
模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换`int`的函数时,编译器将按模板 模式创建这样的函数,并用`int`代替`AnyType`。同样,需要交换`double`的函数时,编译器将按模板模式创建这样的函数,并用`double`代替 `AnyType`

最终 的代码不包含任何模板,而只包含了为程序生成的实际函数。使用模板 的好处是,它使生成多个函数定义更简单、更可靠。

Expand Down Expand Up @@ -273,10 +287,10 @@ template <> void Swap(int &, int &);
## 8.6 总结
C++扩展了C语言的函数功能。通过将inline关键字用于函数定义, 并在首次调用该函数前提供其函数定义,可以使得C++编译器将该函数 视为内联函数。也就是说,编译器不是让程序跳到独立的代码段,以执行函数,而是用相应的代码替换函数调用。只有在函数很短时才能采用内联方式。
C++扩展了C语言的函数功能。通过将 inline 关键字用于函数定义, 并在首次调用该函数前提供其函数定义,可以使得C++编译器将该函数视为内联函数。也就是说,编译器不是让程序跳到独立的代码段,以执行函数,而是用相应的代码替换函数调用(相当于复制进去)。只有在函数很短时才能采用内联方式。
引用变量是一种伪装指针,它允许为变量创建别名(另一个名称)。引用变量主要被用作处理结构和类对象的函数的参数。
C++原型让您能够定义参数的默认值。如果函数调用省略了相应的 参数,则程序将使用默认值;如果函数调用提供了参数值,则程序将使 用这个值(而不是默认值)。只能在参数列表中从右到左提供默认参数。
C++原型让您能够定义参数的默认值。如果函数调用省略了相应的参数,则程序将使用默认值;如果函数调用提供了参数值,则程序将使用这个值(而不是默认值)。只能在参数列表中从右到左提供默认参数。
函数的特征标是其参数列表。程序员可以定义两个同名函数,只要 其特征标不同。这被称为函数多态或函数重载。
函数的特征标是其参数列表。程序员可以定义两个同名函数,只要其特征标不同。这被称为函数多态或函数重载。

0 comments on commit d0bf152

Please sign in to comment.