- 新增现代 C++ 教程的 Preface 章节,包括英文和中文版本 - 添加 C++ Primer 练习代码 - 新增 Learn C++ 教程的 C++ 开发简介章节 - 添加头文件解析文档 - 更新 mkdocs.yml,包含新教程的目录结构 - 修改项目设置,使用 Python 3.10环境
184 lines
8.4 KiB
Markdown
184 lines
8.4 KiB
Markdown
---
|
||
sort: 6
|
||
---
|
||
|
||
# 函数
|
||
|
||
## 函数基础
|
||
|
||
- **函数定义**:包括返回类型、函数名字和0个或者多个**形参**(parameter)组成的列表和函数体。
|
||
- **调用运算符**:调用运算符的形式是一对圆括号 `()`,作用于一个表达式,该表达式是函数或者指向函数的指针。
|
||
- 圆括号内是用逗号隔开的**实参**(argument)列表。
|
||
- 函数调用过程:
|
||
- 1.主调函数(calling function)的执行被中断。
|
||
- 2.被调函数(called function)开始执行。
|
||
- **形参和实参**:形参和实参的**个数**和**类型**必须匹配上。
|
||
- **返回类型**: `void`表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针。
|
||
|
||
|
||
|
||
### 局部对象
|
||
|
||
- **名字**:名字的作用于是程序文本的一部分,名字在其中可见。
|
||
- **生命周期**:对象的生命周期是程序执行过程中该对象存在的一段时间。
|
||
- **局部变量**(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是**隐藏**的。
|
||
- **自动对象**:只存在于块执行期间的对象。当块的执行结束后,它的值就变成**未定义**的了。
|
||
- **局部静态对象**: `static`类型的局部变量,生命周期贯穿函数调用前后。
|
||
|
||
### 函数声明
|
||
|
||
- **函数声明**:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称**函数原型**。
|
||
- **在头文件中进行函数声明**:建议变量在头文件中声明;在源文件中定义。
|
||
- **分离编译**: `CC a.cc b.cc`直接编译生成可执行文件;`CC -c a.cc b.cc`编译生成对象代码`a.o b.o`; `CC a.o b.o`编译生成可执行文件。
|
||
|
||
## 参数传递
|
||
|
||
- 形参初始化的机理和变量初始化一样。
|
||
- **引用传递**(passed by reference):又称传引用调用(called by reference),指**形参是引用类型**,引用形参是它对应的实参的别名。
|
||
- **值传递**(passed by value):又称传值调用(called by value),指实参的值是通过**拷贝**传递给形参。
|
||
|
||
### 传值参数
|
||
|
||
- 当初始化一个非引用类型的变量时,初始值被拷贝给变量。
|
||
- 函数对形参做的所有操作都不会影响实参。
|
||
- **指针形参**:常用在C中,`C++`建议使用引用类型的形参代替指针。
|
||
|
||
### 传引用参数
|
||
|
||
- 通过使用引用形参,允许函数改变一个或多个实参的值。
|
||
- 引用形参直接关联到绑定的对象,而非对象的副本。
|
||
- 使用引用形参可以用于**返回额外的信息**。
|
||
- 经常用引用形参来避免不必要的复制。
|
||
- `void swap(int &v1, int &v2)`
|
||
- 如果无需改变引用形参的值,最好将其声明为常量引用。
|
||
|
||
### const形参和实参
|
||
|
||
- 形参的顶层`const`被忽略。`void func(const int i);`调用时既可以传入`const int`也可以传入`int`。
|
||
- 我们可以使用非常量初始化一个底层`const`对象,但是反过来不行。
|
||
- 在函数中,不能改变实参的**局部副本**。
|
||
- 尽量使用常量引用。
|
||
|
||
### 数组形参
|
||
|
||
- 当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
|
||
- 要注意数组的实际长度,不能越界。
|
||
|
||
### main处理命令行选项
|
||
|
||
- `int main(int argc, char *argv[]){...}`
|
||
- 第一个形参代表参数的个数;第二个形参是参数C风格字符串数组。
|
||
|
||
### 可变形参
|
||
|
||
`initializer_list`提供的操作(`C++11`):
|
||
|
||
| 操作 | 解释 |
|
||
|-----|-----|
|
||
| `initializer_list<T> lst;` | 默认初始化;`T`类型元素的空列表 |
|
||
| `initializer_list<T> lst{a,b,c...};` | `lst`的元素数量和初始值一样多;`lst`的元素是对应初始值的副本;列表中的元素是`const`。 |
|
||
| `lst2(lst)` | 拷贝或赋值一个`initializer_list`对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。 |
|
||
| `lst2 = lst` | 同上 |
|
||
| `lst.size()` | 列表中的元素数量 |
|
||
| `lst.begin()` | 返回指向`lst`中首元素的指针 |
|
||
| `lst.end()` | 返回指向`lst`中微元素下一位置的指针 |
|
||
|
||
`initializer_list`使用demo:
|
||
|
||
```cpp
|
||
void err_msg(ErrCode e, initializer_list<string> il){
|
||
cout << e.msg << endl;
|
||
for (auto bed = il.begin(); beg != il.end(); ++ beg)
|
||
cout << *beg << " ";
|
||
cout << endl;
|
||
}
|
||
|
||
err_msg(ErrCode(0), {"functionX", "okay});
|
||
```
|
||
|
||
- 所有实参类型相同,可以使用 `initializer_list`的标准库类型。
|
||
- 实参类型不同,可以使用`可变参数模板`。
|
||
- 省略形参符: `...`,便于`C++`访问某些C代码,这些C代码使用了 `varargs`的C标准功能。
|
||
|
||
## 返回类型和return语句
|
||
|
||
### 无返回值函数
|
||
|
||
没有返回值的 `return`语句只能用在返回类型是 `void`的函数中,返回 `void`的函数不要求非得有 `return`语句。
|
||
|
||
### 有返回值函数
|
||
|
||
- `return`语句的返回值的类型必须和函数的返回类型相同,或者能够**隐式地**转换成函数的返回类型。
|
||
- 值的返回:返回的值用于初始化调用点的一个**临时量**,该临时量就是函数调用的结果。
|
||
- **不要返回局部对象的引用或指针**。
|
||
- **引用返回左值**:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型得到右值。
|
||
- **列表初始化返回值**:函数可以返回花括号包围的值的列表。(`C++11`)
|
||
- **主函数main的返回值**:如果结尾没有`return`,编译器将隐式地插入一条返回0的`return`语句。返回0代表执行成功。
|
||
|
||
### 返回数组指针
|
||
|
||
- `Type (*function (parameter_list))[dimension]`
|
||
- 使用类型别名: `typedef int arrT[10];` 或者 `using arrT = int[10;]`,然后 `arrT* func() {...}`
|
||
- 使用 `decltype`: `decltype(odd) *arrPtr(int i) {...}`
|
||
- **尾置返回类型**: 在形参列表后面以一个`->`开始:`auto func(int i) -> int(*)[10]`(`C++11`)
|
||
|
||
## 函数重载
|
||
|
||
- **重载**:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。
|
||
- `main`函数不能重载。
|
||
- **重载和const形参**:
|
||
- 一个有顶层const的形参和没有它的函数无法区分。 `Record lookup(Phone* const)`和 `Record lookup(Phone*)`无法区分。
|
||
- 相反,是否有某个底层const形参可以区分。 `Record lookup(Account*)`和 `Record lookup(const Account*)`可以区分。
|
||
- **重载和作用域**:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。
|
||
|
||
## 特殊用途语言特性
|
||
|
||
### 默认实参
|
||
|
||
- `string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');`
|
||
- 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。
|
||
|
||
### 内联(inline)函数
|
||
|
||
- 普通函数的缺点:调用函数比求解等价表达式要慢得多。
|
||
- `inline`函数可以避免函数调用的开销,可以让编译器在编译时**内联地展开**该函数。
|
||
- `inline`函数应该在头文件中定义。
|
||
|
||
### constexpr函数
|
||
|
||
- 指能用于常量表达式的函数。
|
||
- `constexpr int new_sz() {return 42;}`
|
||
- 函数的返回类型及所有形参类型都要是字面值类型。
|
||
- `constexpr`函数应该在头文件中定义。
|
||
|
||
### 调试帮助
|
||
|
||
- `assert`预处理宏(preprocessor macro):`assert(expr);`
|
||
|
||
开关调试状态:
|
||
|
||
`CC -D NDEBUG main.c`可以定义这个变量`NDEBUG`。
|
||
|
||
```cpp
|
||
void print(){
|
||
#ifndef NDEBUG
|
||
cerr << __func__ << "..." << endl;
|
||
#endif
|
||
}
|
||
```
|
||
|
||
## 函数匹配
|
||
|
||
- 重载函数匹配的**三个步骤**:1.候选函数;2.可行函数;3.寻找最佳匹配。
|
||
- **候选函数**:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
|
||
- **可行函数**:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function)。
|
||
- **寻找最佳匹配**:基本思想:实参类型和形参类型越接近,它们匹配地越好。
|
||
|
||
## 函数指针
|
||
|
||
- **函数指针**:是指向函数的指针。
|
||
- `bool (*pf)(const string &, const string &);` 注:两端的括号不可少。
|
||
- **函数指针形参**:
|
||
- 形参中使用函数定义或者函数指针定义效果一样。
|
||
- 使用类型别名或者`decltype`。
|
||
- **返回指向函数的指针**:1.类型别名;2.尾置返回类型。 |