• c++语法拾遗,一些细节与特性


    写了2年多的C+STL的acmer,在学习《C++ primer》时总结的一些少见的语法特性与细节。总体还是和题目说的一样这是一篇 c++ 拾遗。

    1 变量和基本类型

    1.1 基本类型

    1.1.1 字面常量

    0123 表示的不是带有前导0的数字123,而是代表8进制数字123。

    1.2 常量

    1.2.1 constexpr

    constexpr变量能自动判别赋值的表达式是否是常量表达式,若不是则会报错。

    constexpr int mf=20;        //20是常量表达式
    constexpr int limit=mf+1;   //mf+1是常量表达式
    constexpr int sz=size();    //当size是一个constexpr 函数的时候是是常量表达式
    int a=20;
    constexpr int b=a+1;        //报错
    

    1.2 处理类型

    1.2.1 decltype

    decltype能和auto类似的自动判别类型。

    decltype(f()) a=b;  //这里的a的类型是f()返回值的类型
    

    编译器并不实际调用函数f,只是将返回值作为a的类型。
    实际上decltype(exp) exp是一个表达式所以可以这么写

    int a = 0;
    decltype(a) b = 1;          //b 被推导成了 int
    decltype(10.8) x = 5.5;     //x 被推导成了 double
    decltype(x + 100) y;        //y 被推导成了 double
    
    • 如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致。
    • 如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
    • 如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用。

    2 表达式

    2.1 基础

    2.1.1 左值

    简单的一句话说,左值就是内存。

    2.1.2 右值

    简单的一句话说,右值就是数据。

    2.1.3 求值顺序

    c++ 中没有定义表达式的求值顺序,如:

    int cnt=0;
    int f(){
        return ++cnt;
    }
    int main(){
        int a=f()-f();      //a的结果可能会因为编译器不同而不同
                            //因为两个f函数的调用顺序没有定义
        return 0;
    }
    
    int a=1;
    int b=a+a++;            //同样,i的自增和i的调用顺序没定义
    int c=a++ + ++a;        //同样无定义
    int *d= new int[2];
    *d=f(*d++);                 //无定义
    

    另外java是有定义表达式的求值顺序的,从左到右。c++不定义是为了编译器启用更多优化。

    3 函数

    3.1 可变参数的函数

    3.1.1 initializer_list 形参

    int sum(initializer_list<int> li){
        int s=0;
        for(auto i : li)
            s+=i;
        return s;
    }
    
    // 调用方法 
    sum({1,2,3,4,5});
    sum({1,1,1,1});
    

    initializer_lsit 对象中的元素永远是常量类型。
    可使用begin、end、size方法进行操作。

    3.1.2 省略符形参

    void f(...);
    void f(cnt,...);
    

    不建议使用,仅为方便c++访问某些c代码设置。

    3.2 函数返回

    3.2.1 值如何被返回的

    值返回的时候回调用拷贝构造函数,创建一个临时对象。

    3.2.2 列表返回值

    C++11 新标准规定,函数可以返回花括号包围的值的列表。

    vector<int> f(){
        return {1,2,3,4};
    }
    

    实际上就是花括号赋值,因为值返回的时候回调用拷贝构造函数,如果拷贝构造函数支持花括号,那就可以作为返回值。

    3.2.3 返回数组指针

    int (*f)()[size];   //表示函数f返回长度为size的数组指针
                        //特别的是这里数组表示是后置的
    

    3.2.4 尾值返回类型

    在C++11标准中添加了一种尾置返回类型的定义。

    auto f()->int;          //同等于 int f();
    auto f()->int *[size];  //同等于 int (*f)()[size]; 
    

    3.3 重载

    3.3.1重载与const 形参

    顶层的const无法和另一个没有顶层的const的参数区分,但底层的const可以区分

    void f(int);
    void f(const int);      //重复声明,报错
    
    void f(int *)
    void f(int *const)      //重复声明
    
    void f1(int*);
    void f1(const int*);    //底层const,新函数。
    
    void f1(int&);
    void f1(const int&);    //底层const,新函数。
    
    

    3.3.2 重载与作用域

    当在作用域内重复定义标识符,作用域内的标识符将会直接覆盖外部的标识符,包括重载的。

    void f(int);        //函数1
    void f(char);       //函数2
    void f(double);     //函数3
    void foo(){
        void f(double); //函数4
        f(1.2);         //正确调用函数4
        f(1);           //正确调用函数4,传入值为1.0
        f('c')          //错误,没定义
    }
    
    

    3.4 特殊用途语言特性

    3.4.1 默认实参

    可给函数的参数设置默认实参,但缺省的调用时,会默认赋值。

    void f(int a,int b,int c=1,int d=2,int e=3);    //声明
    
    //调用
    f(1,2);         //实际为f(1,2,1,2,3);
    f(1,2,3);       //实际为f(1,2,3,2,3);
    f(1,2,3,4,5);   //实际为f(1,2,3,4,5);
    

    设置默认实参只能从右到左,并且不能留空。

    void f(int a=1,int b=2);        //正确
    void f(int a=1,int b);          //错误
    void f(int a,int b=1,int c);    //错误
    

    默认值可赋函数、变量或者表达式。

    int x=1;
    int v();
    void f(int a=x,int b=v());
    // 将在调用f时,调用v,为函数参数赋值
    
    

    3.4.2 内联函数 inline

    在函数的返回类型前面加上关键字inline,就可以将函数声明为内联函数。内联函数有点类似c的宏函数,它会在每一个调用的位置,内联的展开,从而节省函数调用和返回的开销。但内联函数声明只是向编译器发出的请求,编译器可以选择忽略。

    inline void f();
    

    内联函数定义通常放在头文件中

    3.4.3 constexpr 函数

    该函数相当于一个常量表达式。

    constexpr int f();
    

    constexpr 函数定义通常放在头文件中

    3.5 调试帮助

    3.5.1 assert 预处理宏

    assert(expr);
    

    当expr为假时输出信息并终止程序。

    3.5.2 NDEBUG 预处理变量

    当定义NDEBUG后,表示关闭调试。
    自定义调试

    #ifndef NDEBUG
        //调试代码
    #endif
    

    对调试有帮助的标识符

    • __func__ 当前函数名 字符串
    • __FILE__ 文件名 字符串
    • __LINE__ 当前行号 整型
    • __TIME__ 编译时间 字符串
    • __DATE__ 编译日期 字符串
  • 相关阅读:
    华为的底气:不造车,稳坐智能汽车C位
    Flutter开发指南
    代码随想录44——动态规划:完全背包理论基础、518零钱兑换II、377组合总和IV
    六大赛题,108万丰厚奖池!2023长三角(芜湖)人工智能视觉算法大赛等你来战!
    CentOS7管理防火墙及开放指定端口
    汇川触摸屏IT7000E和汇川中小型系列PLC连接
    内存还有几十G,为何jvm会oom crash
    王道书 P150 T12(打印x的所有祖先) + 拓展(打印从根节点到某个节点的路径、求根节点到某个节点的路径长度、求根节点的最大路径长度)
    RibbonControl
    【递归、搜索与回溯算法】第一节.初识递归、搜索与回溯算法
  • 原文地址:https://www.cnblogs.com/komet/p/16089478.html