What & How & Why

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录前一修订版
后一修订版
前一修订版
cs:programming:cpp:courses:cpp_basic_deep:chpt_6 [2024/10/03 14:30] – [返回值优化] codingharecs: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>
 +===函数的返回类型===
 +  * 返回类型在编译期会确定,表示函数的计算结果类型
 +  * 返回类型不能省略
 +==函数返回的方式==
 +<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; }
 +</code> 
 +如果存在多个 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>
 +==返回类型与结构化绑定==
 +<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;
 +}
 +</code>
 +==nodiscard 属性==
 +  * 防止函数的返回结果没有被正确的利用
 +  * 防止内存泄漏
 +<code cpp>
 +// 使用 warning 提醒该返回值非常重要,不能忽略
 +[[nodiscard]] int fun(int a, int b) 
 +{
 +    return a + b;
 +}
 +
 +int main()
 +{
 +    // 返回值没有用到
 +    // warning: ignoring return value
 +    fun(2, 3);
 +}
 </code> </code>
 ====函数重载和解析==== ====函数重载和解析====
 +===函数的重载===
 +  * 使用相同函数名定义多个不同参数列表的函数
 +    * 参数**数量**不同
 +    * 参数**类型**不同
 +    * 跟参数名和返回类型无关
 +==函数重载和 name mangling==
 +重载函数会被 mangle 为以下的格式:
 +<code bash>
 +# 除了函数名,还有参数的信息
 +000000014000145c T _Z3fund
 +0000000140001450 T _Z3funi
 +</code>
 +
 +<WRAP center round box 100%>
 +[[https://www.youtube.com/watch?v=GydNMuyQzWo|Calling Functions: A tutorial]]
 +</WRAP>
 +===name lookup===
 +  * 限定查找(只会在指定区域内查找)
 +  * 非限定查找
 +    * 优先查找所在域,没找到回去上级
 +    * 查找顺序从上到下
 +    * 如果希望优先本域的函数,需要做前置声明
 +      * 两个同名函数在同域时,在调用重载函数的位置之前声明
 +<code cpp>
 +void fun()
 +{
 +    std::cout << "global fun\n";
 +}
 +
 +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::fun();
 +
 +    // 非限定名称查找
 +    // 会从当前的域(myNS)开始查找
 +    // 一旦查找到名称,就会停止 name lookup
 +    // 调用 myNS::fun()
 +    myNS::g();
 +
 +    // 如果当前域不存在 fun() 则会去上级查找
 +    // 查找的顺序从上到下,如果域内的 myNS::fun() 的定义在 g() 之后
 +    // 那么会调用 ::fun()
 +    // 可以提前声明 myNS::fun() 来告诉编译器本域中存在 myNS::fun() 的定义
 +    return 0;
 +}
 +</code>
 +==名称隐藏==
 +  * 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& x){}
 +void fun(const int& x){}
 +
 +// int& 
 +fun(x);
 +// const int&
 +fun(3);
 +</code>
 +==多个形参的情况==
 +  * 多个参数时,最佳匹配函数中的**所有参数匹配都优先级**都要高于其他候选
 +
 ====其他内容==== ====其他内容====
 +==递归函数==
 +  * 通常用于描述复杂的迭代过程
 +===内联函数===
 +  * 普通函数会有额外的开销
 +    * 运行期会建立 stack frame 用于保护函数的掉用
 +  * 内联函数是一种优化机制:如果函数的内部逻辑较为简单,则将函数的内容直接在 ''main'' 中执行
 +  * 编译器替换的机制:
 +    * 取决于编译器的实现机制
 +    * 对于逻辑复杂的函数,overhead 的 cost 不是很明显
 +  * 展开不是简单的替换
 +    * 替换需要保证替换前和替换后执行效果是一样的,需要检测很多问题(比如命名冲突)
 +  * 定义在翻译单元外的函数无法内联:内联发生在编译期,而翻译单元以外的定义处理在链接期
 +==内联函数可以放到头文件中==
 +  * ''inline'' 关键字修饰的函数可以重复定义。
 +  * 实际上,''inline'' 函数保证同时只有一个定义用于链接期。这使得函数定义的范围从翻译单元变为了跨翻译单元。
 +  * ''inline'' 的定义应该与其声明处于同一位置
 +===constexpr 函数===
 +  * ''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);
 +</code>
 +===consteval 函数===
 +  * C++20 的新内容
 +  * 该函数只能在编译期求值(确保优化)
 +  * 避免在运行期的误调用
 +<WRAP center round box 100%>
 +  * ''inline'' 的出发点是让函数内容在 main 中的调用处展开,因此兼容性最强
 +  * ''constexpr'' / ''consteval'' 更强调在编译期计算
 +</WRAP>
 +===函数指针===
 +  * 函数类型:返回类型+参数类型,比如:''int(int)''
 +  * 只能为函数引入声明,不能直接定义
 +  * 其他使用场景:''std::function'' 可以接受函数类型的参数
 +==函数类型的定义==
 +<code cpp>
 +u,sing K = int(int);
 +// fun 被声明为 int(int) 类型的函数
 +K fun;
 +// 不能通过赋值的方式给出定义
 +K fun = { return 0; } // error
 +</code>
 +==函数指针==
 +<code cpp>
 +#include <iostream>
 +#include <vector>
 +#include <algorithm>
 +
 +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 <<(*fun)(100) << std :: endl;
 +    std::cout << twiceInc(fun, 100) << std::endl;
 +
 +    // 泛型算法实例
 +    std::vector<int> a{1,2,3,4,5};
 +    std::transform(a.begin(), a.end(), a.begin(), fun);
 +
 +}
 +</code>
 +==函数的退化==
 +  * 赋值时,函数类型会退化为函数指针类型
 +<code cpp>
 +// fun 是 int(*)(int)
 + auto fun = inc;
 +</code>
 +===函数指针与重载===
 +  * 重载时尽量显式的使用函数类型
 +<code cpp>
 +void fun(int) { std::cout << "single para;\n"; }
 +void fun(int, int) {std::cout << "double para;\n"; }
 +
 +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>
 +===函数指针作为返回值===
 +  * 可以使用函数作为返回值
 +  * 函数无法复制,返回的类型是函数指针
 +<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;
 +}
 +</code>