本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:courses:cpp_basic_deep:chpt_6 [2024/10/03 14:30] – [返回值优化] codinghare | cs:programming:cpp:courses:cpp_basic_deep:chpt_6 [2024/10/05 03:17] (当前版本) – [函数指针作为返回值] codinghare | ||
---|---|---|---|
行 286: | 行 286: | ||
==返回值优化== | ==返回值优化== | ||
* 分为具名和非具名返回值 | * 分为具名和非具名返回值 | ||
- | * C++ 17 会进行**强制性**的临时对象返回优化 | + | * C++ 17 会进行**强制性**的临时对象(非具名)返回优化:返回临时对象不会调用拷贝构造函数 |
<code bash> | <code bash> | ||
# 初始化和返回默认情况下都会进行拷贝构造 | # 初始化和返回默认情况下都会进行拷贝构造 | ||
行 294: | 行 294: | ||
# not work on C++ 17 | # not work on C++ 17 | ||
fno-elide-constructors | fno-elide-constructors | ||
+ | </ | ||
+ | ===函数的返回类型=== | ||
+ | * 返回类型在编译期会确定,表示函数的计算结果类型 | ||
+ | * 返回类型不能省略 | ||
+ | ==函数返回的方式== | ||
+ | <code cpp> | ||
+ | // 经典返回 | ||
+ | int fun(int a, int b) {} | ||
+ | // 尾部返回 C++11 | ||
+ | // 使用泛型编程的时候,参数的类型由模板参数决定,在后面写返回类型更方便 | ||
+ | // 返回类的成员函数的自定义类型,在后面写更方便 | ||
+ | // 尾部返回可以自动查找域 | ||
+ | auto fun(int a, int b) -> int {} | ||
+ | |||
+ | // 自动推导返回 C++14 | ||
+ | // 基于 return 语句进行推导 | ||
+ | // 返回 int | ||
+ | auto fun(int a, int b) { return a + b; } | ||
+ | </ | ||
+ | 如果存在多个 return 语句,那么返回结果的类型必须一致,否则需要使用 constexpr if 来处理: | ||
+ | <code cpp> | ||
+ | // 与运行期条件不同,constexpr 在编译器生效 | ||
+ | // 通过 constexpr value 来舍弃某个分支的 return | ||
+ | // 实际上编译结果只存在一个 return 的类型 | ||
+ | constexpr bool value = true; | ||
+ | auto fun() | ||
+ | { | ||
+ | if constexpr (value) | ||
+ | { | ||
+ | return 1; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | return 3.14; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | ==返回类型与结构化绑定== | ||
+ | <code cpp> | ||
+ | struct Str | ||
+ | { | ||
+ | int x; | ||
+ | int y; | ||
+ | }; | ||
+ | |||
+ | Str fun() | ||
+ | { | ||
+ | return Str{}; | ||
+ | } | ||
+ | |||
+ | int main(int argc, char const *argv[]) | ||
+ | { | ||
+ | |||
+ | // C++ 17 | ||
+ | // v1 v2 实际上是引用 | ||
+ | // 使用 v1, v2 直接绑定了 x 和 y | ||
+ | auto [v1, v2] = fun(); | ||
+ | v1; | ||
+ | v2; | ||
+ | |||
+ | //Str res = fun(); | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | ==nodiscard 属性== | ||
+ | * 防止函数的返回结果没有被正确的利用 | ||
+ | * 防止内存泄漏 | ||
+ | <code cpp> | ||
+ | // 使用 warning 提醒该返回值非常重要,不能忽略 | ||
+ | [[nodiscard]] int fun(int a, int b) | ||
+ | { | ||
+ | return a + b; | ||
+ | } | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | // 返回值没有用到 | ||
+ | // warning: ignoring return value | ||
+ | fun(2, 3); | ||
+ | } | ||
</ | </ | ||
====函数重载和解析==== | ====函数重载和解析==== | ||
+ | ===函数的重载=== | ||
+ | * 使用相同函数名定义多个不同参数列表的函数 | ||
+ | * 参数**数量**不同 | ||
+ | * 参数**类型**不同 | ||
+ | * 跟参数名和返回类型无关 | ||
+ | ==函数重载和 name mangling== | ||
+ | 重载函数会被 mangle 为以下的格式: | ||
+ | <code bash> | ||
+ | # 除了函数名,还有参数的信息 | ||
+ | 000000014000145c T _Z3fund | ||
+ | 0000000140001450 T _Z3funi | ||
+ | </ | ||
+ | |||
+ | <WRAP center round box 100%> | ||
+ | [[https:// | ||
+ | </ | ||
+ | ===name lookup=== | ||
+ | * 限定查找(只会在指定区域内查找) | ||
+ | * 非限定查找 | ||
+ | * 优先查找所在域,没找到回去上级 | ||
+ | * 查找顺序从上到下 | ||
+ | * 如果希望优先本域的函数,需要做前置声明 | ||
+ | * 两个同名函数在同域时,在调用重载函数的位置之前声明 | ||
+ | <code cpp> | ||
+ | void fun() | ||
+ | { | ||
+ | std::cout << " | ||
+ | } | ||
+ | |||
+ | namespace myNS | ||
+ | { | ||
+ | void fun() | ||
+ | { | ||
+ | std::cout << "NS fun\n"; | ||
+ | } | ||
+ | void g() | ||
+ | { | ||
+ | fun(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int main(int argc, char const *argv[]) | ||
+ | { | ||
+ | // 限定名称查找 | ||
+ | // 指定了查找的区域 | ||
+ | // 域可以由 Namespace,class(sub class) 形成 | ||
+ | ::fun(); | ||
+ | myNS:: | ||
+ | |||
+ | // 非限定名称查找 | ||
+ | // 会从当前的域(myNS)开始查找 | ||
+ | // 一旦查找到名称,就会停止 name lookup | ||
+ | // 调用 myNS::fun() | ||
+ | myNS::g(); | ||
+ | |||
+ | // 如果当前域不存在 fun() 则会去上级查找 | ||
+ | // 查找的顺序从上到下,如果域内的 myNS::fun() 的定义在 g() 之后 | ||
+ | // 那么会调用 ::fun() | ||
+ | // 可以提前声明 myNS::fun() 来告诉编译器本域中存在 myNS::fun() 的定义 | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | ==名称隐藏== | ||
+ | * name lookup 只按照名字来,不区分实体(比如变量与函数同名时,优先选择变量) | ||
+ | * 也是逐级往上查找 | ||
+ | ==argument dependent lookup== | ||
+ | * 如果是结构体的对象作为参数,结构体的内部会被纳入域 | ||
+ | * 只对自定义类型生效 | ||
+ | * 函数模板会进行实例化,实例化的过程中会导致查找范围扩大 | ||
+ | |||
+ | ===重载解析=== | ||
+ | - 过滤不能被调用的版本(non-viable candidate) | ||
+ | - 参数数量不匹配 | ||
+ | - 实参无法被转变为形参 | ||
+ | - 实参不满足形参的限制条件 | ||
+ | - Best match | ||
+ | - 单个参数 | ||
+ | - 选择匹配等级高的版本 | ||
+ | ==匹配等级== | ||
+ | 级别越低,匹配程度越高 | ||
+ | * rank 1:完美匹配或平凡转换(加一个 const) | ||
+ | * rank 2:提升(小尺寸到大尺寸)或者提升加平凡转换 | ||
+ | * rank 3:标准转换 / 标准转换加平凡转换 | ||
+ | * rank 4:自定义转换:类的类型转换 | ||
+ | * rank 5:调用形参为省略号的版本 | ||
+ | ==涉及 low const 的情况== | ||
+ | <code cpp> | ||
+ | // 左值会优先选择非 const 版本 | ||
+ | // 编译器会倾向选择保证对应变量的访问权限的函数 | ||
+ | void fun(int& | ||
+ | void fun(const int& x){} | ||
+ | |||
+ | // int& | ||
+ | fun(x); | ||
+ | // const int& | ||
+ | fun(3); | ||
+ | </ | ||
+ | ==多个形参的情况== | ||
+ | * 多个参数时,最佳匹配函数中的**所有参数匹配都优先级**都要高于其他候选 | ||
+ | |||
====其他内容==== | ====其他内容==== | ||
+ | ==递归函数== | ||
+ | * 通常用于描述复杂的迭代过程 | ||
+ | ===内联函数=== | ||
+ | * 普通函数会有额外的开销 | ||
+ | * 运行期会建立 stack frame 用于保护函数的掉用 | ||
+ | * 内联函数是一种优化机制:如果函数的内部逻辑较为简单,则将函数的内容直接在 '' | ||
+ | * 编译器替换的机制: | ||
+ | * 取决于编译器的实现机制 | ||
+ | * 对于逻辑复杂的函数,overhead 的 cost 不是很明显 | ||
+ | * 展开不是简单的替换 | ||
+ | * 替换需要保证替换前和替换后执行效果是一样的,需要检测很多问题(比如命名冲突) | ||
+ | * 定义在翻译单元外的函数无法内联:内联发生在编译期,而翻译单元以外的定义处理在链接期 | ||
+ | ==内联函数可以放到头文件中== | ||
+ | * '' | ||
+ | * 实际上,'' | ||
+ | * '' | ||
+ | ===constexpr 函数=== | ||
+ | * '' | ||
+ | * constexpr 函数不能调用 non-constexpr 函数(const 的一致性) | ||
+ | <code cpp> | ||
+ | // 在编译期和运行期均可调用 | ||
+ | constexpr int fun(int x ){ return x}; | ||
+ | // 编译器调用 | ||
+ | constexpr int x = fun(3); | ||
+ | // 运行期调用 | ||
+ | // 由于参数只能在运行期确定,因此是运行期调用 | ||
+ | int y; | ||
+ | std::cin >> y; | ||
+ | fun(y); | ||
+ | </ | ||
+ | ===consteval 函数=== | ||
+ | * C++20 的新内容 | ||
+ | * 该函数只能在编译期求值(确保优化) | ||
+ | * 避免在运行期的误调用 | ||
+ | <WRAP center round box 100%> | ||
+ | * '' | ||
+ | * '' | ||
+ | </ | ||
+ | ===函数指针=== | ||
+ | * 函数类型:返回类型+参数类型,比如:'' | ||
+ | * 只能为函数引入声明,不能直接定义 | ||
+ | * 其他使用场景:'' | ||
+ | ==函数类型的定义== | ||
+ | <code cpp> | ||
+ | u,sing K = int(int); | ||
+ | // fun 被声明为 int(int) 类型的函数 | ||
+ | K fun; | ||
+ | // 不能通过赋值的方式给出定义 | ||
+ | K fun = { return 0; } // error | ||
+ | </ | ||
+ | ==函数指针== | ||
+ | <code cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | using K = int(int); | ||
+ | |||
+ | int inc(int x) | ||
+ | { | ||
+ | return x + 1; | ||
+ | } | ||
+ | |||
+ | // 将指向 inc 的函数指针作为参数 | ||
+ | // 类型为 K*,也就是 int(*)(int) | ||
+ | // 好处:高阶函数逻辑不变,通过调用的函数来实现不同的功能 | ||
+ | // 实例:泛型算法,很多泛型算法接收函数指针作为参数 | ||
+ | int twiceInc(K* inc, int x) | ||
+ | { | ||
+ | int temp = (*inc)(x); | ||
+ | return temp * 2; | ||
+ | } | ||
+ | |||
+ | |||
+ | int main(int argc, char const *argv[]) | ||
+ | { | ||
+ | // 不是函数声明,是指针 | ||
+ | // 可接收 int(int) 类型的函数 | ||
+ | K* fun = &inc; | ||
+ | |||
+ | // 通过指针调用函数 | ||
+ | // 解引用-> | ||
+ | std::cout << | ||
+ | std::cout << twiceInc(fun, | ||
+ | |||
+ | // 泛型算法实例 | ||
+ | std:: | ||
+ | std:: | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | ==函数的退化== | ||
+ | * 赋值时,函数类型会退化为函数指针类型 | ||
+ | <code cpp> | ||
+ | // fun 是 int(*)(int) | ||
+ | auto fun = inc; | ||
+ | </ | ||
+ | ===函数指针与重载=== | ||
+ | * 重载时尽量显式的使用函数类型 | ||
+ | <code cpp> | ||
+ | void fun(int) { std::cout << " | ||
+ | void fun(int, int) {std::cout << " | ||
+ | |||
+ | int main(int argc, char const *argv[]) | ||
+ | { | ||
+ | // 无法通过 auto 来推断 fun 的类型 | ||
+ | // fun 在此处包含了两个不同类型的函数 | ||
+ | // auto 无法确定选择哪一个 fun | ||
+ | auto x = fun; | ||
+ | |||
+ | // 需要显式的指定类型 | ||
+ | using K = void(int); | ||
+ | K* x = fun; //int(int) type | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | ===函数指针作为返回值=== | ||
+ | * 可以使用函数作为返回值 | ||
+ | * 函数无法复制,返回的类型是函数指针 | ||
+ | <code cpp> | ||
+ | int inc(int x) { return x + 1; } | ||
+ | int dec(int x) { return x - 1; } | ||
+ | |||
+ | auto fun(bool condition, int x) | ||
+ | { | ||
+ | // 返回的实际上是函数的指针 | ||
+ | return condition ? inc(x) : dec(x); | ||
+ | } | ||
+ | |||
+ | int main(int argc, char const *argv[]) | ||
+ | { | ||
+ | |||
+ | std::cout << fun(true,1) << std::endl; | ||
+ | return 0; | ||
+ | } | ||
+ | </ |