ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

modern_cpp_4-C++ Functions

2022-01-23 17:30:59  阅读:211  来源: 互联网

标签:std Functions return 函数 int modern C++ cpp include


文章目录

函数命名建议

Google-Style使用驼峰法命名函数,例如CamelCase,并且更加倾向于将单个函数做的更小一些。

函数返回值

关于返回值,If has a return value, must return a value. If returns void, must NOT return any value.

返回类型自动推导和返回多个值

但是在C++14中,支持返回类型自动推导(automatic return type deduction),例如

std::map<char,int> GetDictionary(){
    return std::map<char,int>{{'a',27},{'b',3}};
}

可以被表达成

auto GetDictionary(){
    return std::map<char,int>{{'a',27},{'b',3}};
}

但还是不能和Python那样返回多个值灵活:

#!/usr/bin/env python3
def foo():
    return "Super Variable",5
name,value = foo()
print(name+"has value: "+str(value))

我们尝试使用C++17中的新特性(Structured binding,元组)模仿

#include<iostream>
#include<tuple>
auto Foo(){
    return std::make_tuple("Super Variable",5);
}
int main(){
    auto [name,value] = Foo();
    std::cout<<name<<"has value: "<<value<<std::endl;
    return 0;
}

但是千万要注意,不要返回局部变量的引用,因为局部变量会随着被调用函数结束而被清理,下面的这种做法是错误的:

#include <iostream>
using namespace std;

int & MultiplyBy10(int num){
    int retval = 0;
    cout<< "retval is "<<retval <<endl;
    return retval;
}// retval is destroyed, it's not accesisble anymore

int main(){
    int out = MultiplyBy10(10);
    cout<< "out is "<<out<<endl; // not 0
    return 0;
}

RVO(Return Value Optimization)

C++针对Return Value还会进行优化,可以参考Copy elision - Wikipedia

Copy Elision指一个编译器优化技巧,即消除不必要的对象的复制,其中一个重要体现就是RVO(Return Value Optimization)。

C++中有一个假设原则(as-if rule),即C++标准允许编译器执行任何程度的优化,只要生成的可执行文件表现出相同的可观察行为。而RVO指的是C++中的一个特殊语句,比假设原则更进一步,即使拷贝复制函数有作用,实际操作中也可以忽略由return语句产生的复制操作。

比如下面的程序:

#include <iostream>

struct C {
  C() = default;
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

这里按理说,应该会执行两次拷贝复制函数,即输出两次A copy was made.,第一次是f()内部将一个未命名的临时C复制拷贝给Return Value,第二次是main内部的fobj,但是结果有点出乎意料:

在这里插入图片描述

实际中,根据编译器的优化不同,下面三种情况都有可能会发生

Hello World!
A copy was made.
A copy was made.
Hello World!
A copy was made.
Hello World!

我们的编译器在这里什么也没做,直接在main函数中创建新的C,确实是最简洁的实现,这种方法是被Walter Bright第一次在他的Zortech C++ compiler中实现的,而目前的绝大多数的C++编译器都支持了RVO。

局部变量和静态变量

除非声明全局静态变量,否则每次触发局部变量的定义,都会创建一份局部变量:

void f(){
    float var1 = 5.5F;
    float var2 = 1.5F;
}
f(); // First call,var1,var2 are created
f(); // Second call,NEW var 1,var2 are created

NACHO-STYLE: 如果可能的话,请避免使用静态变量,下面的例子会介绍一种微妙的使程序崩溃的方法,即”initialization order ’fiasco‘ “(静态变量的初始化顺序),这种错误很难觉察到,而且发生在main函数开始之前。可以参考这篇文章:https://isocpp.org/wiki/faq/ctors#static-init-order

在C++标准 ‘ISOIEC14882-1998 3.6.2 Initialization of nonlocal objects’ 里明确规定,在同一个编译单元内,静态变量初始化的顺序就是定义的顺序,而跨编译单元的静态变量的初始化顺序未定义,具体的初始化顺序取决于编译器的实现。所以如果跨编译单元的定义使用静态变量,有可能所调用的静态变量还没有初始化,这是十分危险的。

当然静态变量的初始化分为静态初始化和动态初始化,前者可以认为是同步完成的,后者就会因为跨编译单元而导致初始化顺序不确定。

防范措施:尽量避免跨编译单元的初始化依赖,常用construct on first use idiom,即将静态变量定义在函数内,直到函数被调用才会被初始化,这就消除了跨编译单元的静态变量的初始化问题。

默认参数

例子:

#include<iostream>

using namespace std;

string SayHello(const string& to_whom = "world"){
        return "Hello"+ to_whom +"!";
}

int main(){
        // Will call SayHello using default arguments
        cout<<SayHello()<<endl;
        // This will override the default argument
        cout<<SayHello("students")<<endl;
        return 0;
}

但是默认参数只能够在定义中设置,不能在声明中设置,一方面简化了程序,另一方面会造成使用者在调用时,忽略已经设置的默认参数,当在同一个作用域内,再次设置默认参数时会造成意料之外的行为

// 下面的代码在编译时会出错
#include <iostream>
using namespace std;
void func(int a, int b = 10, int c = 36);
int main(){
    func(99);
    return 0;
}
void func(int a, int b = 10, int c = 36){
    cout<<a<<", "<<b<<", "<<c<<endl;
}

在这里插入图片描述

如果把当前函数的定义和声明分开,仍然设置两次默认参数,就不会出现这个问题了(但是我还是出现了问题,这好像应该就是一个作用域吧,日后可以再琢磨琢磨: C++函数的默认参数详解)。

总结:

在给定的作用域中,一个形参只能被赋予一次默认实参,换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须都有默认值。通常应该在函数声明中指定默认实参,并将该声明放在合适的头文件中。

GOOGLE-STYLE规定,只有当可读性更好的时候才能使用默认参数

NACHO-STYLE规定,不应该使用默认参数

更多参考:到底在声明中还是定义中指定默认参数 (biancheng.net)

传递较大的参数应使用Const Reference

当传递的参数较大时,函数直接拷贝可能会比较耗时,此时应该传递引用。

而且如果可以的话,应该尽量使用常量引用(尽管再C++11之前,非常量引用是很常见的),GOOGLE-STYLE中提出了避免使用non-const refs.

实验:Cost of passing by value

这里针对值传递和引用传递进行了性能的对比,后者要比前者快五倍。

inline

内联函数是用来提醒编译器,针对该部分 should attempt to generate code for a call rather than a function call,当然编译器会进行决断,如果这些代码块足够小,编译器会进行优化。

例如下面的阶乘:

inline int fac(int n){
    if(n<2)
        return 2;
    return n*fac(n-1);
}

Overloading

Naive overloading

在标准库中的数学计算库cmath中,简单的余弦和正切都会按照输入参数的类型而在命名上产生差异:

#include<cmath>
double cos(double x);
double cosf(float x);
long double cosl(long double x);

但是在实际中我们只用使用cos就可以了,这里就是实现了重载:

#include<cmath>
double cos(double x);
double cos(float x);
long double cos(long double x);

编译器会根据输入的参数类型推断应该采用哪个函数(注意,不是根据返回值的类型推断!)

GOOGLE-STYLE:避免不明显的重载。

Good Practices & Bad Practices

  1. 将复杂的计算拆解成模块更小,意义更加紧凑的子模块
  2. 函数的长度应该越小越好
  3. 避免无意义的注释
  4. 一个函数应该只承担一项任务
  5. 如果你不能去一个简单的名字,那就用它的功能命名它

实践

// Good Practice
#include<vector>
#include<iostream>

using namespace std;

vector<int> CreateVectorOfZeros(int size);

int main(){
        vector<int> zeros = CreateVectorOfZeros(10);
        return 0;
}

Namespaces

命名空间能够防止命名上的冲突和从逻辑上可以更加容易管理程序,下面的例子简单展示了如何使用Namespace:

#include<iostream>

namespace fun{
        int GetMeaningOfLife(void){
                return 42;
        }
}

namespace boring{
        int GetMeaningOfLife(void){
                return 0;
        }
}

int main(){
        std::cout<<boring::GetMeaningOfLife()<<std::endl;
        std::cout<<fun::GetMeaningOfLife()<<std::endl;
}

有时候,我们可以利用{}来隔绝定义域,在小范围内使用某种命名空间:

#include<cmath>
#include"my_pow.hpp"

int main(){
    int std_result = std::pow(2,3);
    int my_result = my_pow::pow(2,3);
    {
        using std::pow;
        result = pow(2,3); // same as std::pow
    }
    {
        using my_pow::pow;
        result = pow(2,3); //same as my_pow::pow
    }
}

记住:永远不要在*.hpp中使用using namespace name

GOOGLE_STYLE: 如果你发现自己依赖于某些内容,而且这些内容不应该在其他文件中被看到,你就要在当前文件的开头把它们放进namespace

更多参考 https://google.github.io/styleguide/cppguide.html#Namespaces

标签:std,Functions,return,函数,int,modern,C++,cpp,include
来源: https://blog.csdn.net/weixin_43721070/article/details/122654128

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有