c++11学习笔记2——稳定性和兼容性

一、与C99保持兼容

1、预定义宏

c++11学习笔记2——稳定性和兼容性_第1张图片


2、__func__宏

     __func__宏的功能是返回所在函数的名字。事实上,按照标准定义,编译器会隐式地在函数的定义之后定义__func__标识符。例如

const char* hello()
{
     return __func__;
}
其实际的含义等同于如下实现

const char*hello()
{
       static const char* __func__ = "hello";
       return __func__;
}
使用时注意:__func__允许使用在类或者结构体中,但是不能作为函数参数的默认值(因为在参数声明时,__func__还未被定义)。

注意:vs实现中并没有__func__宏,取而代之的是__FUNCTION__宏。


3、_Pragma操作符

       c++11中定义了_Pragma操作符,功能与#pragma功能相同。区别在于#pragma是一条预处理指令,是用来向编译器传达语言标准以外的一些信息;而_Pragma是操作符,其使用方法跟sizeof等操作符一样,将字符串字面量作为参数写在括号内即可:_Pragma(字符串字面量)。由于_Pragma是一个操作符,因此可以用在一些宏中。

       #pragma once  也可以通过_Pragma("once")达到相同效果。

4、变长参数的宏定义以及__VA_ARGS__

      变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS__则可以在宏定义的实现部分替换省略号所代表的字符串。比如:

#define PR(...) pringf( __VA_ARGS__ )

这样可以定义一个printf的别名PR。


5、宽窄字符串的连接


6、long long 整型

        long long整型有两种:long long和unsigned long long. 在c++11中,标准要求long long整型可以在不同平台上有不同的长度,但至少有64位。写常数字面量时,使用LL后缀(或者ll)标识一个long long类型的字面量,而ULL(或者ull、Ull、uLL)标识一个unsigned long long 类型的字面量。

        与long long等价的类型:long long 、signed long long 、long long int 、signed long long int ;

        与unsigned long long 等价的类型 :unsigned long long int 。

        与long long整型相关的宏:LLONG_MIN、LLONG_MAX、ULLONG_MIN,它们分别代表了平台上最小的long long值、最大的long long 值,以及最大的unsigned long long值。

       用printf打印时,long long类型变量可以用:%lld, unsigned long long可以用:%llu 。


7、扩展的整型

      c++11 规定,扩展的整型必须和标准类型一样,有符号类型和无符号类型占用同样大小的内存空间。当运算、传参等类型不匹配时,整型间会发生隐式地转换,该过程称为整型的提升(Integral promotion)。


8、__cplusplus宏

在c与c++混合编写的代码中,为了抑制c++的名字改编,需要如下声明:

#ifdef __cplusplus
extern "C"{
#endif

//一些代码

#ifdef __cplusplus
}
#endif
注意:在C++ 03标准中, __cplusplus的值被预定义为199711L, 在c++ 11标准中,宏__cplusplus被预定义为201103L。可以使用一下代码检测代码是使用支持c++11编译器进行编译的:

#if __cplusplus < 201103L
       #error “should use C++11 implementation”
#endif

9、静态断言

      在c++中,标准在<cassert>或<assert.h>头文件中提供的assert宏,用于在运行时进行断言。使用NDEBUG宏可以禁用assert宏。

      在预处理阶段进行断言,可以通过#if和#error的配合。

      在编译时进行断言,就要使用“静态断言”。

     Boost内置的BOOST_STATIC_ASSERT断言机制,利用“除0”会导致编译器报错这个特性来实现静态断言:

#define assert_static(e) \
       do{ \
              enum { assert_static__ *1/(e) };\
} while(0)

        C++11标准中,引入了static_assert断言来解决这个问题,static_assert接收两个参数,一个是断言表达式,这个表达式通常需要返回一个bool值,另一个是警告信息,它通常也就是一段字符串。例如:

        

static_assert( sizeof(b) == sizeof(a), "the parameters of bit_copy must have same width .");
注意:由于static_assert是编译时期的断言,其使用范围比assert灵活,可以用于任何名字空间。

           将static_assert写在函数体外面通常是较好的选择,这让代码阅读者很容易发现static_assert为断言而非用户定义的函数。static_assert断言表达式的结果必须是在编译时期可以计算的表达式,即必须是常量表达式。


10、noexcept修饰符与noexcept操作符

        通常,在C++11中使用noexcept可以有效地阻止异常的传播与扩散。

        noexcept作为修饰符,表示其修饰的函数不会抛出异常。在C++11中如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行,这比给予异常机制的throw()在效率上会高一些。

        noexcept修饰符有两种形式:

        ** 简单地在函数声明后加上noexcept关键字,例如:void except_func()  noexcept;  //相当于 void except_func()  noexcept( true );

        ** 接受一个常量表达式作为参数,      例如: void except_func() noexcept(常量表达式); //常量表达式的值被转换为一个bool类型的值,该值为true,表示函数不会抛出异常,反之,则有可能抛出异常。

        noexcept作为操作符,通常用于模板。例如:

template <class T>
    void func() noexcept( noexcept(T()) ){}
        第一个noexcept是一个修饰符(同上),第二个noexcept是一个操作符,当T()是一个有可能抛出异常的表达式时,noexcept( T() )返回false, 反之返回true。
       所以,上述模板中,如果T()可能会抛出异常,那么函数func就是一个可能会抛出异常的函数,如果T()不会抛出异常,那么func就是一个不会抛出异常的函数。


注意:1>、指出函数可能抛出的异常类型的“throw(int,double)”这样的动态异常声明,在c++11中被弃用了;表示函数不会抛出异常的动态异常声明throw()也被noexcept异常声明所取代;

           2>、noexcept更大的作用是保证应用程序的安全,比如类的析构函数不应该抛出异常,所以C++11标准中让类的析构函数默认为noexcept( true)的,除非程序员显式地为析构函数指定了noexcept,或者类的基类或者成员有noexcept(false)的析构函数,这时析构函数将不会保持为默认值。此外,c++11默认将delete函数设置为noexcept来提高程序的安全性。


11、快速初始化成员变量

        C++11中,允许非静态成员变量的初始化有多种形式,既可以通过初始化列表,也可以进行就地初始化(通过等号=或者花括号{} )。就地初始化和初始化列表并不冲突,初始化列表的效果总是优先于就地初始化的。

        对于非常量的静态成员变量的初始化,C++11与C++98保持了一致,程序员需要在头文件以外去定义它。这会保证编译时,类的静态成员的定义最后只存在于一个目标文件中。


12、非静态成员的sizeof

        在C++98中,只有静态成员,或者对象的实例才能对其成员进行sizeof操作,在C++11中,对非静态成员变量使用sizeof操作时合法的。

struct People
{
 public:
       int hand;
       static People *all;
}

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通过
注意:
        如果环境只支持C++98的编译器,在没有定义类实例的时候,要活的类成员的大小,可以通过以下技巧:

sizeof(  ((People*)0)->hand );

13、扩展的friend语法

       C++11对friend关键字进行了一些改进,声明一个类为另外一个类的友元时,不再需要使用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通过
}

14、final/override控制

        final关键字的作用是使派生类不能覆盖它所修饰的虚函数。例如:

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

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

struct Derived : public Base
{
      void func();   //无法通过编译
};
        override修饰符,描述该虚函数必须是重载其基类的同名函数,否则将无法通过编译。可以帮助程序员更方便地确定问题,例如

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;              //无法通过编译,非虚函数重载
};

15、模板函数的默认模板参数

        函数模板和类模板都是在C++98中一起被引入的,不过在模板类声明时,标准允许其有默认模板参数,单数C++98标准却不支持函数模板的默认模板参数。但是在C++11中,同时支持类模板和函数模板的默认模板参数,不过在语法上函数模板与类模板有一些差别,类模板要求在有多个默认模板参数声明指定默认值时,程序员必须遵照“从右到左”的规则进行指定,而函数模板,默认模板参数的位置则比较随意。

       代码示例:

void DefParm( int m = 3) {}     //C++98编译通过,C++11编译通过

template <typename T = int >
      class DefClass{};         //C++98编译通过, C++11编译通过

template <typename T = int >
      void DefTempParm() {};    //C++98编译失败,C++11编译通过

template<typename T1, typename T2 = int> class DefClass1;   //OK
template<typename T1 = int, typename T2> class DefClass2;   //无法通过编译

template<typename T, int i=0> class DefClass3;              //OK
template<int i=0, typename T> class DefClass4;              //无法通过编译

template<typename T1= int, typename T2> void DefFunc1( T1 a, T2 b); //OK
template<int i=0, typename T> void DefFunc2( T a);                  //OK

注意:模板函数的默认形参不是模板参数推导的依据,函数模板参数的旋转,总是由函数的实参推导而来的。

16、外部模板

       “外部模板”是C++11中一个关于模板(编译)性能上的改进。类似于外部变量的声明。声明外部模板,可以在不同编译单元中只保留一份实例化代码,减少了链接时,链接器移除重复的实例化代码的工作量。提高了链接速度。

       显式的实例化与外部模板的声明

       如果某个编译单元定义了如下模板:

template <typename T> void fun(T) {}
       在本编译单元中,通过如下显式的实例化声明出一个fun<int>(int)版本的函数(这种做法也被称为强制实例化)

template void fun<int>( int );

在其他编译单元中只需要如下声明,就完成了一个外部模板的声明

extern template void fun<int>(int);

      注意:外部模板定义更应该算作一种针对编译器的编译时间与空间的优化手段。模板实例化展开带来的冗余开销不会太严重,通常只有在项目比较大的情况下,才建议用户进行外部模板声明这样的优化。


17、局部和匿名类型作模板实参

      C++98不允许局部的类型和匿名的类型做模板类的实参,c++11中允许这样做。例如:

template <typename T> class X{};
template <typename T> void TempFun( T t){};
struct A{} a;
struct {int i;} b;  //b是匿名类型变量
typedef struct {int i;} B; //B是匿名类型

void Fun()
{
      struct C{} c; //c是局部类型

      X<A> x1;      //C++98通过, C++11通过
      X<B> x2;      //C++98失败, C++11通过
      X<C> x3;      //C++98失败,C++11通过
      TempFun(a) ;  //C++98通过,C++11通过
      TempFun(b);   //C++98失败,C++11通过
      TempFun(c);   //C++98失败,C++11通过 
}

注意:虽然C++11中匿名类型可以被模板参数所接受,但是匿名类型的声明要是独立的表达式,不能声明在模板实参的位置。例如

template <typename T> struct MyTemplate{};

int main()
{
      MyTemplate< struct{int a;}> t; //无法编译通过,匿名类型的声明不能在模板实参位置
     return 0;
}



你可能感兴趣的:(c++11学习笔记2——稳定性和兼容性)