C++11初窥1:保证稳定性和兼容性

C++11初窥一:保证稳定性和兼容性


1.1开始支持宏_STDC_ 和 _func_

不得不吐槽,g++早就支持这俩宏了,第一个宏_STDC_还有几个衍生的宏,用来控制版本;_func_在函数定义体内使用返回函数名称,不要在形参列表中使用,原因你懂的,函数都还没声明完毕呢。

顺带提一下标准C支持的其他的宏:

     __LINE__                       在源代码中插入当前源代码行号   
     __FILE__                       在源代码中插入当前源代码文件名   
     __DATE__                       在源代码中插入当前编译日期〔注意和当前系统日期区别开来〕   
      __TIME__                       在源代码中插入当前编译时间〔注意和当前系统时间区别开来〕         
      __STDC__                       当要求程序严格遵循ANSIC标准时该标识符被赋值为1。   
      __cplusplus                    当用C++编译程序编译时,标识符__cplusplus就会被定义 


 1.2_Pragma操作符替代#Pragma宏定义

语法: _Pragma (字符常量)

好处:该操作符可以参与其他宏定义和操作符运算

实例:

#Pragma once    变为: _Pragma("once");

1.3 支持long long int

也是马后炮,一直在用了

C输出格式:long long int :%lld,unsigned long long int: %llu

1.4编译期断言:static_assert()

运行期断言函数:assert(逻辑表达式),表达式为假时调用abort()中断执行

static_assert(逻辑表达式,警告信息字符常量);

注意:逻辑表达式必须在编译期就可以断言真假!!!

请记住,static_asset 是在编译时执行的,不能用于检测运行时的值,向下面函数的参数。

void Divide(int a, int b)  
{   
    static_assert(b==0, “Bad arguments.....leading to division by zero”);  
    // sorry mate! the above check is not possible via static_assert...use some other means  
}  
static_assert 这个声明对于模板的调试非常有用,编译器快速执行这个常量表示式参数(不能依赖模板参数)。否则编译器当模板实例化时执行这个常量表达式的参数。

1.5 引入noexcept修饰符和noexcept操作符

从语法上讲,noexcept修饰符有两种形式,一种就是简单地在函数声明后加上noexcept关键字。比如:
void excpt_func() noexcept;

另外一种则可以接受一个常量表达式作为参数,如下所示:
void excpt_func() noexcept (常量表达式);

常量表达式的结果会被转换成一个bool类型的值。该值为true,表示函数不会抛出异常,反之,则有可能抛出异常。这里,不带常量表达式的noexcept相当于声明了noexcept(true),即不会抛出异常。

1.6支持快速初始化成员变量

#include <string>
using namespace std;

class Mem {
public:
    Mem(int i): m(i){}

private:
    int m;
};

class Group {
public:
    Group(){}                   // 这里就不需要初始化data、mem、name成员了
    Group(int a): data(a) {}    // 这里就不需要初始化mem、name成员了
    Group(Mem m) : mem(m) {}    // 这里就不需要初始化data、name成员了
    Group(int a, Mem m, string n): data(a), mem(m), name(n){}

private:
    int data = 1;
    Mem mem{0};
    string name{"Group"};
};
// 编译选项:g++ 2-7-4.cpp -std=c++11 -c

(1)支持在类声明时初始化,其优先级低于构造函数初始化列表,因为后者要晚些发生!!

(2)初始化有两种方式:=和{}

(3)静态常量 成员 和 非常量静态成员初始化方式不变:前者里外都可以初始化,后者只能在类声明外初始化

1.7非静态成员支持sizeof()

这个变化不大,增加了对非对象的非静态成员的sizeof()支持

#include <iostream> 
using namespace std;  
 
struct People {  
public:  
    int hand;  
    static People * all;  
};  
 
int main() {  
    People p;  
    cout << sizeof(p.hand) << endl;         // C++98 中通过, C++11 中通过  
    cout << sizeof(People::all) << endl;    // C++98 中通过, C++11 中通过  
    cout << sizeof(People::hand) << endl;   // C++98 中错误, C++11 中通过  
}  
// 编译选项:g++ 2-8-1.cpp 

1.8扩展友元friend语法

看到这个,只想说,C++从此又多了一个奇巧淫技!!
(1)语法声明友元类时,可以不加class

class Poly;  
typedef Poly P;  
 
class LiLei {  
    friend class Poly;  // C++98通过, C++11通过  
};  
 
class Jim {  
    friend Poly;        // C++98失败, C++11通过  
};  
 
class HanMeiMei {  
    friend P;           // C++98失败, C++11通过  
};  
// 编译选项:g++ -std=c++11 2-9-1.cpp 
(2)可以为类模板声明友元类!!

class P;  
 
template <typename T> class People {  
    friend T;  
};  
 
People<P> PP;   // 类型P在这里是People类型的友元  
People<int> Pi; // 对于int类型模板参数,友元声明被忽略  
// 编译选项:g++ -std=c++11 2-9-2.cpp 
对于People这个模板类,在使用类P为模板参数时,P是People<P>的一个friend类。而在使用内置类型int作为模板参数的时候,People<int>会被实例化为一个普通的没有友元定义的类型。这样一来,我们就可以在模板实例化时才确定一个模板类是否有友元,以及谁是这个模板类的友元。

1.9多态重载控制:final/override控制

(1)final拒绝重载

(2)override标识重载

struct Object{
    virtual void fun() = 0;
};

struct Base : public Object {
    void fun() final;   // 声明为final
};

struct Derived : public Base {
    void fun();     // 无法通过编译
};
// 编译选项:g++ -c -std=c++11 2-10-2.cpp

struct Base {
    virtual void Turing() = 0;
    virtual void Dijkstra() = 0;
    virtual void VNeumann(int g) = 0;
    virtual void DKnuth() const;
    void Print();
};

struct DerivedMid: public Base {
    // void VNeumann(double g);
    // 接口被隔离了,曾想多一个版本的VNeumann函数
};

struct DerivedTop : public DerivedMid {
    void Turing() override;
    void Dikjstra() override;           // 无法通过编译,拼写错误,并非重载
    void VNeumann(double g) override;   // 无法通过编译,参数不一致,并非重载
    void DKnuth() override;             // 无法通过编译,常量性不一致,并非重载
    void Print() override;              // 无法通过编译,非虚函数重载
};
// 编译选项:g++ -c -std=c++11 2-10-3.cpp

1.10模板函数支持默认参数

模板类也是支持的。

对于多参数的模板类和模板函数来说,要求不一样!!!

模板类的默认形参书写顺序必须是从右往左,为了方便模板类具现化时类型推导

模板函数随意

template<typename T1, typename T2 = int> class DefClass1;  
template<typename T1 = int, typename T2> class DefClass2;   // 无法通过编译  
 
template<typename T, int i = 0> class DefClass3;  
template<int i = 0, typename T> class DefClass4;            // 无法通过编译  
 
template<typename T1 = int, typename T2> void DefFunc1(T1 a, T2 b);  
template<int i = 0, typename T> void DefFunc2(T a);  
// 编译选项:g++ -c -std=c++11 2-11-2.cpp  
函数模板的参数推导规则也并不复杂。简单地讲,如果能够从函数实参中推导出类型的话,那么默认模板参数就不会被使用,反之,默认模板参数则可能会被使用。

template <class T, class U = double> 
void f(T t = 0, U u = 0);  
 
void g() {  
    f(1, 'c');      // f<int,char>(1,'c')  
    f(1);           // f<int,double>(1,0), 使用了默认模板参数double  
    f();            // 错误: T无法被推导出来  
    f<int>();       // f<int,double>(0,0), 使用了默认模板参数double  
    f<int,char>();  // f<int,char>(0,0)  
}  
// 编译选项:g++ -std=c++11 2-11-3.cpp