%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# 缓冲区笔记
函数签名、函数原型、函数类型、函数指针
%%
# `main` 函数
`main` 函数是 C/C++程序的 **==标准入口点==**。
C++标准中规定的**两种 "合法且可移植"** 的 `main` 函数原型如下 [^1]:
- `int main();`
- `int main(int argc, char** argv);` 或 `int main(int argc, char* argv[]);`
> [!NOTE]
> 当需要**结束一个 C++程序**时,除了**从 `main()` 返回**以外,**其它时候应当调用 `exit()`、`quick_exit()` (since C++11) 或者 `terminate()` 来结束程序**。
> [!NOTE] Linux 中支持另一种扩展形式,可接收 `exec` 系列函数传入的 "**环境变量列表**"
>
> Linux 支持的扩展(非 C++标准):`int main(int argc, char** argv, char** envp);`
>
###### 形式一
```cpp
int main() {
// 程序代码
return 0; // 该语句可省略, 若省略则编译器会自动添加. (C中不能省略)
}
```
不接收任何参数,返回一个 `int` 值,表示**程序的退出状态**(给到操作系统):
- 正常退出时应**返回 `0`,表示成功**;
- **非零值** 通常表示**有错误发生**,不同的非零值表示不同类型的错误。
###### 形式二
```cpp
int main(int argc, char** argv) { //或 int main(int argc, char* argv[]);
// argc 表示命令行的"参数数量"
// argv 是一个指向"字符指针数组"的指针, 每个元素指向一个"参数字符串"
// argv[0] 通常是程序的名称或路径, 或者为一个空字符
// argv[1] 到 argv[argc-1] 是程序实际接收的命令行参数.
}
```
可接受**命令行参数**的 `main` 函数形式:具有**两个形参**。
<br><br>
# 函数参数
## 函数参数传递
**"实参" 是 "形参" 的初始值**。
函数调用时,根据**形参是否为"引用"类型**,分为两种参数传递方式[^2](P187):
- **值传递**(passed by value):形参为**值类型**,**形参为==对实参的值进行拷贝/移动构造==后得到的==副本**==;
- 也称 "**传值调用**"(called by value)
- **引用传递**(passed by reference):形参为**引用类型**,形参**作为实参的引用/别名**。
- 也称 "**传引用调用**"(called by reference)
> [!caution] 实参初始化形参时,**实参的顶层 const 会被忽略**。 [^2] (P191)
>
> - 值传递下,**形参为实参的拷贝副本,形参独立于实参,因此实参的顶层 const 属性不会影响形参**。
> - 引用传递下,没有顶层 "const" 的说法,**引用只有底层 const,且不可被忽略**。
>
> ```cpp
> // 下列两个函数会被认为 "重复定义"
> void fcn(const int i) {}
> // void fcn(int i) {} // error: 重复定义
>
> // `fcn(const int i)`与`fnc(int i)`二者都是既能接受常量, 也能接受非常量.
> // 假如编译器允许同时定义这两个函数版本, 则调用时会产生二义性冲突.
> // 因此, 编译器不允许同时定义上面两个版本
> ```
>
>
<br>
## 默认实参
在函数参数列表中,可以为形参指定默认值,但要求**该形参右侧所有形参也都具有默认值**——即**带有默认值的形参必须全部位于参数列表的后面**。
> [!NOTE] 通常,应当**在==函数声明==中指定默认实参**,并将该声明放在合适的头文件中。
>
> **给定作用域中的一个函数形参==只能被赋予一次默认实参==**,即**函数的后续声明**只能为**之前那些没有默认值的形参**添加默认实参,并且**该形参右侧的所有形参必须都已经具有默认值**。
```cpp
string MyFunc(int, int, char=' '); // 首次函数声明.
// string MyFunc(int, int, char='*'); // error:不能覆盖/修改一个已存在的默认值
string MyFunc(int, int = 80, char); // 允许添加新的默认实参, 只要该默认实参右侧的所有形参都已具有默认值.
```
<br>
## 可变形参 Variadic Parameters
"可变形参"(Varadic Parameters)支持函数接受 **==不定数量的参数==**。
C++提供了以下几种支持**可变形参**的机制:
- **C 风格的可变参数列表**
- 以**初始化列表** `std::initializer_list` 作为形参,支持同一类型的实参;
- **可变参数模版**:支持不同类型的实参;
此外,如果要处理
#### C 风格的可变参数列表
C 风格的可变参数列表**使用 `<cstdarg>` 头文件中定义的宏和函数**,如 `va_start`、`va_arg` 和 `va_end`,来处理可变数量的参数。这种方式继承自 C 语言。
在这一方式下,**函数形参**使用 "**省略符形参**`...`",该形参只能出现在**形参列表的最后一个位置**。
该方式**只应该用于 C 和 C++通用的类型**,因为大多数类型的对象在传递给省略符形参时可能都无法正确拷贝。
```cpp title:in_c_style.cpp
#include <cstdarg>
#include <iostream>
using namespace std;
// 使用`<cstdarg>`库提供的宏和函数处理可变参数. 这一方式继承自C语言.
void printNumbers(int count, ...) {
va_list args;
va_start(args, count); // 初始化args列表;count是最后一个固定参数, 表示参数数量.
for (int i = 0; i < count; ++i) {
int num = va_arg(args, int); //
cout << num << " ";
}
va_end(args); // 清理工作
}
int main() {
printNumbers(5, 91, 92, 93, 94, 95); // 需要通过第一个参数显式指定参数数量
}
```
#### 使用 `std::initializer_list`
```cpp title:use_std_initializer_lsit.cpp
#include <iostream>
#include <initializer_list>
using namespace std;
// 使用`std::initializer_list`处理同一类型的可变数量参数
void printNumbers(initializer_list<int> args) { // 形参为`std::initializer_list`类模版的实例
for (auto& num : args) {
cout << num << " ";
}
}
int main() {
// 实参通过`{}`花括号初始化列表的形式传递给`std::initializer_list`类型的形参
printNumbers({91, 92, 93, 94, 95}); // 不需要显式指定参数数量
}
```
#### 可变参数模版
```cpp
/** 通过"可变参数模版'来处理任意数量和类型的参数.
*
* 处理思路:
* 定义两个函数模版, 一个处理单个参数, 另一个处理多个参数.
* 当接收到多个参数时, 递归调用处理单个参数的函数模版实例.
*/
template <typename T>
void print(const T& value) {
cout << value << endl;
}
template <typename T, typename... Args> // 可变模版参数
void print(const T& firstArg, const Args&... args) {
cout << firstArg << endl;
print(args...); // 递归调用
}
```
<br><br><br>
# 函数返回类型
函数的返回类型**不能是"数组类型"或"函数类型"**,可以**是==指向数组或函数的指针==**,也可以返回 `lambda` 函数。
对于返回类型为 `void` 的函数,可以省略 `return;` 语句,编译器会自动隐式执行。
<br>
## 函数返回值
**函数返回值**将用于初始化一个 "**==临时对象==**",其即为**函数调用表达式**的结果。(如同**初始化一个变量或函数形参**)
如果返回的是**引用类型**(非局部变量的引用),则 **"函数调用表达式"的值类型是==左值==**,**否则为==右值**==。
> [!caution] 不要返回==**局部变量的引用或指针**==。
>
> 如果尝试返回 **局部变量的"引用"** 或 **指向局部变量的指针**,则函数返回后,**引用或指针将指向无效的内存区域**。
>
> 这**与返回一个"==值=="不同**,**返回一个值时会发生拷贝**。
> [!NOTE] 函数可返回一个 "花括号列表",语义是对返回值执行 "**列表初始化**"(since C++11)
>
> 当花括号为空时,意味着执行 "**值初始化**"。
<br><br>
## 尾置返回类型
尾置返回类型语法:**将==函数的返回类型==放在参数列表之后**,而**使用 `auto` 关键字作为函数返回类型的占位符** [^2] (P206)。
尾置返回类型语法:`auto func(params) -> ret-type;` ,这一语法形式 **允许返回类型依赖于==函数参数类型==**。
> [!example] 使用示例
>
> ```cpp
> // 尾置返回类型(C++11)
> auto func(int i) -> int(*)[10]; // 函数func的返回类型为一个指向含有10个整数的数组的指针.
> ```
>
> [!faq] 为什么需要尾置返回类型
>
> C++11 中引入尾置返回类型的主要目的在于:
>
> (1)解决**泛型编程中的==返回类型依赖==**:
>
> **编译器在==解析到函数的参数列表之==前,其中的==参数对编译器而言都是不可见==的**。
> 如果 **==返回类型本身依赖于函数参数的类型==** (例如 **需要由函数参数类型进行 `decltype ()` 推断**) ,则**必须使用尾置返回类型** 。
>
> (2)为 **==lambda 表达式==指定返回类型**
>
> lambda 表达式的返回类型只能通过 "尾置返回类型" 语法给出。
>
#### 解决返回类型依赖
使用场景:**函数返回类型依赖于==函数参数类型==** 的场景
示例一:
```cpp
template<typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x + y) {
return x + y;
}
```
示例二: 用以获取 STL 容器中**关联于元素类型的"迭代器类型"**,或是获取 "**迭代器中的元素类型**"
```cpp
template <typename T>
auto MyBegin(vector<T>& vec) -> decltype(vec.begin()) {
return vec.begin();
}
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg) {
...
return *beg; // 返回序列中一个元素的引用
}
```
#### 用于 lambda 表达式
lambda 表达式的返回类型只能通过尾置返回类型的语法给出。
```cpp
auto lambda = [](int x, int y) -> int { return x + y; };
```
<br><br><br>
# 函数原型/函数声明
> "**函数声明**" 也即 "**函数原型**" (function prototype)
函数声明指定了**函数的接口**——**函数名称**、**返回类型**以及**参数类型列表**。
函数声明为编译器提供了**关于如何调用该函数的必要信息**,但不包括函数的具体实现(即函数体)。
通过函数声明,**编译器能够在函数实际定义之前就进行函数调用的类型检查,确保调用者提供正确类型和数量的参数**。
<br>
# 函数类型
**函数类型**由函数的 **==返回类型==** 和 **==参数类型列表==(忽略顶层 const)** 确定。
**函数名称**、**函数形参的==顶层 const==**、**类成员函数的限定符** 不包括在函数类型中。
例如 `int(int, int)` 是一个函数类型,接受两个 int 参数,返回 int 类型。
```cpp
void func(int a); // 函数1
void func(const int a); // 函数2, 与1具有相同的函数类型,顶层const不影响函数类型
void func(int* a); // 函数3
void func(int* const a); // 函数4, 与3具有相同的函数类型,顶层const不影响函数类型
void func(const int* a); // 函数5, 与3和4具有不同的函数类型,底层const影响函数类型
```
**==可调用对象==都有其自己的类型**:
- 每个 lambda 有其**唯一的、未命名的类型**。
- 函数及函数指针的类型则由其**返回类型**和**参数类型列表**决定
<br><br>
# 函数签名
> 参见说明:[Is the return type part of the function signature?](https://stackoverflow.com/questions/290038/is-the-return-type-part-of-the-function-signature)
**函数签名**(Function Signature)是用于 **==唯一标识函数==的一组特征**。
**函数签名** 包括下列三项,**不包括==返回类型==**。
- **==函数名称==**
- **==参数类型列表==**:包括**参数类型**、**顺序**,不包括参数名称。
- 形参的 =="**顶层 `const`" 将被忽略**==,**不影响函数签名**。
- 形参的 ==**"底层 const" 属于函数签名的一部分**==。
- **==模版参数==**(仅对于**模版函数**)
在**函数重载解析**中,编译器主要通过 "**函数签名**" 来区分确定重载版本。
```c++
// 示例:
void foo(int) {} // signature: foo(int)
void foo(double) {} // signature: foo(double)
void foo(int, double) {} // signature: foo(int, double)
class MyClass {
void bar() const {} // signature for member function: bar() const
void bar() {} // signature for member function: bar()
};
```
> [!caution] **函数形参**中的**顶层 `const` 不影响函数签名**
>
> 仅仅 **在形参中添加或移除==顶层 const== 不足以区分两个函数**,将被视为**具有相同的函数签名**而报错 "==**重复定义**=="。
>
> (1)仅 "**==顶层 const==**" 不同的两个函数将被视为**具有相同的函数签名**
>
> ```cpp
> // 下列两个函数被视为具有相同的函数签名.
> void MyFunc(int);
> void MyFunc(const int); // 重复定义, 而非重载!形参中的顶层`const` 将被忽略。
>
> // 下列两个函数被视为具有相同的函数签名.
> void MyFunc(int*);
> void MyFunc(int* const); // 重复定义, 而非重载!形参中的顶层`const` 将被忽略。
> ```
>
> (2)"**==底层 const==**" 的有无会**影响函数签名**,会被视为**不同的重载版本**。
>
> ```cpp
> // 下列两个函数将被视为具有"不同"的函数签名, 因为受"底层 const"影响.
> void MyFunc(int&);
> void MyFunc(const int&);
>
> // 下列两个函数将被视为具有"不同"的函数签名, 因为受"底层 const"影响.
> void MyFunc(int*)
> void MyFunc(const int*);
> ```
>
<br>
%%
### 函数模版签名
函数模版签名(The signature of a function template)包含:
- 函数签名
- 返回类型
- 模版参数列表(template parameter list)
> 关于函数模版签名的说明:
>
> 
### 模版函数签名
模版函数签名(The signature of a function template specialization)包含:
- 函数模版签名
- 实际的模版参数(actual template arguments)
> 参考:For reference, here is what the most recent C++0x draft n3000 says about "signature" in `1.3.11`, which is much more complete in its coverage of the different type of entities:
>
> the **name** and the **parameter type list** (8.3.5) of a function, as well as the **class or namespace** of which it is a member. If a function or function template is a class member its signature additionally includes the **cv-qualifiers** (if any) and the **ref-qualifier** (if any) on the function or function template itself.
>
> The signature of a **function template** additionally includes its **return type** and its **template parameter list**.
>
> The signature of a **function template specialization** includes the **signature of the template** of which it is a specialization and its **template arguments** (whether explicitly specified or deduced). [ Note: Signatures are used as a basis for name mangling and linking. — end note ]
> A function template by itself is not a type, or a function, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated.
%%
<br><br>
# 函数指针
参见 [[02-开发笔记/01-cpp/类型相关/cpp-指针相关#函数指针|cpp-指针相关-函数指针]]
<br><br>
# 函数重载
> 函数重载是 C++ 提供的一种多态形式。
**==同一作用域==内**,**函数名相同**,但 **==形参列表不同==** 的多个函数,称之为 "**重载函数**"。
编译器将根据函数调用时提供的参数类型、数量以及顺序来选择**最匹配的函数**执行,这过程称之为 "**函数重载**"。
**函数重载版本**受两个因素影响:
1. **==函数签名==**
2. **类成员函数**的 **==函数限定符==**(`cv` 限定符、引用限定符)
- **`cv` 限定符**(**cv-qualifiers**, `const`,`volatile`,`const valatile`)
- **引用限定符**(**ref-qualifier**,`&` 与 `&&`)
具有 **==相同函数签名==** 但 **==限定符不同==的类成员函数**,将被**视为不同的==重载版本==**。
> [!caution] 函数重载只针对 "**==同一作用域==内**"
>
> **不同作用域下(命名空间 Or 类作用域)的函数不构成重载**(即使函数签名完全相同)。
>
> 例如,**==内层作用域==里定义或声明的==名称== (变量名或函数名)将"==隐藏=="外层作用域中的同名函数**。
>
> ```cpp
> void print (const string&);
> void print (double);
>
> int main () {
> void print (int); // 在该局部作用域内, `print` 隐藏了外部名称, 因此不会再构成重载.
> }
> ```
>
>
> [!caution] 仅返回类型不同**不构成函数重载**
> **返回类型**不是函数签名的一部分。
> C++中**不允许两个函数仅仅只有返回类型不同**,而其他部分完全相同,会导致编译错误。
<br><br>
## 函数重载解析(Overload Resolution)
### 函数重载的决策
当存在多个重载版本时,编译器通过以下规则决定调用哪个函数 [^2] (P128):
- **精确匹配**:编译器首先寻找**参数类型完全匹配的重载版本**。
- **提升和转换**:如果没有精确匹配,编译器会尝试**通过提升和标准类型转换**来找到匹配的重载。
- **最佳匹配**:如果有多个函数都可以通过提升和转换来匹配,编译器将尝试确定哪个重载提供了“**最佳匹配**”。如果无法确定最佳匹配,会导致编译错误。
- "精确匹配" 优于 "类型转换" 的匹配,
##### 确定最佳匹配
![[_attachment/02-开发笔记/01-cpp/函数相关/cpp-函数相关.assets/IMG-cpp-函数相关-CFB4912D1CE637495B1AAB4554C6B71A.png|672]]
### 函数重载的结果
重载解析时可能有三种结果:
- 编译器找到一个**与实参==最佳匹配==(best match)的函数**,并**生成调用该函数的代码**;
- 找不到**任何一个函数与调用的实参匹配**,编译器报出 **==无匹配==(no match)错误信息**;
- **有多于一个函数可以匹配**,此时报 **==二义性调用错误==**(ambiguous call)
<br><br>
# 内联函数与 constexpr 函数
参见 [[02-开发笔记/01-cpp/cpp 基本概念/cpp-inline 说明符|cpp-inline 说明符]] 、 [[02-开发笔记/01-cpp/cpp 基本概念/cpp-constexpr 说明符|cpp-constexpr 说明符]] 、
<br><br>
# `noexcept` 说明符
`noexcept` 用以**标识该函数 "一定不会抛出异常"**[^3] [^4] ,**必须同时在 "函数声明" 和 "函数定义" 处出现**。
> [!NOTE] `noexcept` 的位置:**位于尾置返回类型之前**,**成员函数的 `const`、引用限定符之后**。
> [!caution] 编译器并不在编译时检查 `noexcept`
>
> 标识了该说明符的函数,**其内部仍可==含有 `throw` 语句==或==调用可能抛出异常的函数==,将可编译通过**。
>
>
> [!caution] 若**一个标识为 `noexcept` 的函数在运行时==实际了抛出异常==**,则程序将**直接调用 `std::terminate()` ==终止程序==**。
> [!NOTE] 若函数指针声明为 `noexcept`,则其只能指向 `noexcept` 函数
>
> ```cpp
> void (*pf1)(int) noexcept; // noexcept函数指针, 只能指向noexcept函数.
> ```
>
> [!NOTE] 若一个基类的==**虚函数**==声明为 `noexcept`,则派生类中重写时也必须声明为 `noexcept`
### 使用说明
`noexcept` 说明符可用于两种场景:
- **明确函数不会抛出异常**
- **明确表示无法处理该函数异常**(因此抛出后,将直接导致**程序调用 `std::terminate()` 终止进程**)
`noexcept` 可接收一个 "**bool 类型**" 的 **==常量表达式==** 实参,其**值为 `true` 时表示函数不会抛出异常**。
使用示例:
```cpp
// 表示f()与g()的异常声明一致. 当`g()`不抛出异常时, `f()`的`except`声明为true, 否则为`false`
void f() noexcept(noexcept(g())); // 第一个`noexcept`是函数说明符, 其中的`noexcept`是返回"bool右值常量表达式"的运算符
```
### `noexcept` 运算符
存在一个**同名的 `noexcept` 运算符**,其返回值是一个 **`bool` 类型的==右值常量表达式==**(编译期求值),表示**给定的表达式是否会抛出异常**。
`noexcept(expr)` 返回 `true` 的情况:`expr` 中**调用的所有函数都做了 `noexcept` 声明**,且 **`expr` 本身不含有 `throw` 语句**时。
```cpp
int main() {
cout << noexcept(safeFunction()) << endl; // true
cout << noexcept(unsafeFunction()) << endl; // false
cout << noexcept(1 + 2) << endl; // true, 纯算术运算, 不会抛出异常.
cout << noexcept(sizeof(int)) << endl; // true, 编译期操作, 不会抛出异常
cout << noexcept(new int) << endl; // false, new可能抛出std::bad_alloc异常.
std::vector<int> v = {1, 2, 3};
std::cout << noexcept(v.at(5)) << "\n"; // false,std::vector::at 可能抛出 std::out_of_range
}
```
<br><br><br>
# 谓词 (Predicate)
谓词 (Predicate) 指代**返回布尔值**的、检查 **单参数或双参数** 特定属性的**函数对象**。
谓词用于表示某种属性或条件的成立,常用 STL 中的算法,例如排序、搜索、替换、删除等。
- ==**一元谓词**==(Unary Predicate):只接受单一参数,检查**单一实参**的某项特性。例如判断某个数是否是素数 `isPrime()` 的函数。
- ==**二元谓词**==(Binary Predicate):接受两个参数,检查、比较**两个实参**的特定属性或条件。
> [!NOTE] 谓词只是对符合某种形式的函数的称谓。
说明示例:
```cpp
// 一元谓词
// 下列普通函数或lambda函数对象都可用作一元谓词. 例如在find_if()中使用
bool isPositive(int num) {
return num > 0;
}
auto isPositive = [](int num) { return num > 0;}
// 二元谓词
// 下列普通函数或lambda函数对象都可用作二元谓词. 例如在sort()中使用.
bool ascending(int a, int b){
return a < b;
}
auto ascending = [](int a, int b) { return a < b; };
```
使用示例:
```c++
// 查找在x和y之间的首个元素
vector<int> coll = {1, 3, 19, 5, 13, 7, 11, 2, 17};
int x = 5, y = 12;
auto pos = find_if(coll.begin(), coll.end(), [=](int i) {
return i > x && i < y;
});
// 等价的基本实现
vector<int>::iterator pos;
for (pos = coll.begin(); pos != coll.end(); ++pos) {
if (*pos > x && *pos < y) break;
}
```
<br><br>
# 预定义标识符 `__func__`
`__func__` 是**编译器在==每个==函数内==预定义==的一个==局部变量==**,其值为**该函数的"函数名**"。
与宏的工作方式不同,宏在**预处理阶段**展开,`__func__` 的值 **==在编译时==** 确定。
```cpp
void exampleFunction() {
std::cout << "This function is: " << __func__ << std::endl;
}
```
%%
# 其它
## 函数返回类型为容器时,返回空容器的方式
- 直接返回默认构造的对象: `return std::vector<int>();`
- 利用花括号初始化与隐式转换: `retrun {};`

%%
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: 《C++ Primer》P197
[^2]: 《C++ Primer》
[^3]: 《Effective Modern C++》Item 14
[^4]: 《C++ Primer》P692