C++11: nullptr、默认函数的控制、lambda函数、对齐方式

1 nullptr

       nullptr是nullptr_t类型的实例化,替代了传统的NULL,C++11的指针空值常量。

       nullptr_t是指针空值类型,nullptr仅仅是nullptr_t的一个实例,nullptr_t的使用规则:

1) 所有定义为nullptr_t类型的数据都是等价的,行为也完全一致

2) nullptr_t类型数据可以隐式转换成任意一个指针类型

3) nullptr_t类型类型不能转换为非指针类型,即使使用reinterpret_cast<nullptr_t>()的方式也是不可以的

4) nullptr_t类型数据不适用于算术表达式

5) nullptr_t类型数据可以用于关系运算表达式,但仅能于nullptr_t类型数据或者指针类型数据进行比较,当且仅当关系运算符为==、<=、>=等时返回true。

下面的例子演示了上面的规则:

#include <iostream>
#include <typeinfo>
using namespace std;

int main()
{
    // nullptr可以隐式转换为 char*
    char * cp = nullptr;        

    // 不可转换为整型,而任何类型也不能转换为nullptr_t,
    // 以下代码不能通过编译
    // int n1 = nullptr;     
    // int n2 = reinterpret_cast<int>(nullptr);
    
    // nullptr与nullptr_t类型变量可以作比较,
    // 当使用"==", "<=", ">="符号比较时返回true
    nullptr_t nptr;
    if (nptr == nullptr)
        cout << "nullptr_t nptr == nullptr" << endl;//输出
    else
        cout << "nullptr_t nptr != nullptr" << endl;

    if (nptr < nullptr)
        cout << "nullptr_t nptr < nullptr" << endl;
    else
        cout << "nullptr_t nptr !< nullptr" << endl;//输出
    
    // 不能转换为整型或bool类型, 以下代码不能通过编译
    // if (0 == nullptr); 
    // if (nullptr);

    // 不可进行算术运算, 以下代码不能通过编译
    // nullptr += 1;
    // nullprt * 5;
    
    // 以下操作均可以正常进行
    sizeof(nullptr);
    typeid(nullptr);
    throw(nullptr);
    
    return 0;
}
     虽然nullptr_t是个类型,但是在实例化模板的时候却不能实例化为任何指针:

#include <iostream>
using namespace std;

template<typename T> void g(T* t) {}
template<typename T> void h(T t) {}

int main()
{
   g(nullptr);            // 编译失败, nullptr的类型是nullptr_t,而不是指针
   g((float*) nullptr);   // 推导出T = float

   h(0);                  // 推导出T = int
   h(nullptr);            // 推导出T = nullptr_t
   h((float*)nullptr);    // 推导出T = float*
}
      nullptr和void*所占内存空间一样,但是nullptr是个编译期常量,而(void*)0是强制转换表达式,nullptr到任何指针的转换是隐式的,而(void*)0则必须经过类型转换后才能使用,nullptr可以完全替换null和(void*)0。

int* ptr=(void*)0;//错误
int* p=nullptr;//正确
     nullptr_t对象的地址可以被用户使用,但是用户确却不能获得nullptr的地址,因为nullptr是右值常量,但是C++11没有禁止声明一个nullptr的右值引用并打印该右值引用的地址。


2 默认函数的控制 =default、=delete

       自定义的类型一但显示定义了构造函数后将不再是POD,即使函数体内什么也没有定义,但是可以通过=default恢复编译器默认的构造版本。

#include <type_traits>
#include <iostream>
using namespace std;
class TwoCstor {
public:
    // 提供了带参数版本的构造函数,再指示编译器
    // 提供默认版本,则本class依然是POD类型
    TwoCstor() = default;//TwoCstor(){}函数体内什么也没定义也不是POD
    TwoCstor(int i): data(i) {}

private:
    int data;
};
int main(){
    cout << is_pod<TwoCstor>::value << endl;
}
         可以在函数后面加=delete禁止函数的生成,如下禁止拷贝构造函数:

#include <type_traits>
#include <iostream>
using namespace std;
class NoCopyCstor {
public:
    NoCopyCstor() = default;//编译器默认的构造函数

    // 使用 "= delete" 同样可以有效阻止用户
    // 错用拷贝构造函数
    NoCopyCstor(const NoCopyCstor &) = delete;//禁止拷贝构造
};
int main(){
    NoCopyCstor a;
    NoCopyCstor b(a);   // 无法通过编译
}
        =default和=delete可以在类外定义,这样就可以在不同的cpp文件中实现不同的版本

class DefaultedOptr{
public:
    // 使用"= default"来产生缺省版本
    DefaultedOptr() = default;   

    // 这里没使用"= default"
    DefaultedOptr & operator = (const DefaultedOptr & );//类内不需要=default
};

// 在类定义外用"= default"来指明使用缺省版本
inline DefaultedOptr & 
DefaultedOptr::operator =( const DefaultedOptr & ) = default; 
        =delete还可以删除部分重载函数:

void Func(int i){};
void Func(char c) = delete;  // 显式删除char版本

int main(){
    Func(3);
    Func('c');  // 本句无法通过编译
    return 1;
}


3 lambda函数,是闭包的类,每个lambda表达式则会产生一个闭包类型的临时对象(右值)

     语法:[capture](parameters) mutable->return_type{statement}

1) [capture] 捕捉列表,[]是编译器判断lambda函数的引导符,通过该列表函数可以捕捉前面中的变量供lambda使用

2) (parameters) 参数列表,如果不需要向lambda函数传任何参数则可以连()一起省略

3) mutable 默认情况下lambda函数是const函数,但是加上mutable后可以去掉const属性

4) ->return_type 追踪返回类型,如函数无返回值则可以联通->一起省略

5) {statement} 函数体

      最简单的lambda函数: [] {} 该函数什么也不做,毫无意义。

      [capture]有多个捕捉项组成,用逗号隔开,有如下几种形式:

1) [var] 表示值传递方式捕捉变量var

2) [=]表示值传递方式捕捉所有父作用域的变量,包括this

3) [&var]表示以引用方式传递var变量

4) [&]表示以引用传递所有父作用域的变量,包括this

5) [this]表示值传递方式捕捉当前的this指针

        可以通过组合使用上面的方式,但是不允许变量重复传递,如:

[=,this];//值传递重复传递this
[&,&this];//引用重复传递this
        实例如下:

#include<iostream>
using namespace std;
int x=100;
int main(){
    int a=1;
    {
        {
            int b=10;
            auto fun=[=]{
                cout<<a<<endl;//1
                cout<<b<<endl;//100
                //cout<<c<<endl;//不是父作用域,这里父作用域指包含lambda的函数块
            };
            fun();
        }
    }
    return 0;
}
   
       若lambda不在任何函数块内,也可以捕捉:

#include<iostream>
using namespace std;
int x=10;
auto fun=[=]{cout<<x<<endl;};//10
int main(){
    fun();
    return 0;
}

       lam bda与仿函数,lambda底层实现和仿函数差不多,和仿函数一样它们都有初始状态,仿函数通过对象实例化时初始化数据成员获得初始态,lambda通过获取父作用域中的变变量作为初始态。但是lambda正是受限于只能接收父作用域的变量,因此不像仿函数那样可以跨域使用,但是lambda函数本来的设计目的就是局部函数。

       对于按值传递的捕捉列表,其传递的值在lambda函数定义的时候就已经决定了,而按传值引用的捕捉列表其传递的值跟随绑定对象改变而改变,如下:

#include <iostream>
using namespace std;

int main() {
    int j = 12;
    auto by_val_lambda = [=] { return j + 1;};
    auto by_ref_lambda = [&] { return j + 1;};
    cout << "by_val_lambda: " << by_val_lambda() << endl;//13
    cout << "by_ref_lambda: " << by_ref_lambda() << endl;//13

    j++;
    cout << "by_val_lambda: " << by_val_lambda() << endl;//13
    cout << "by_ref_lambda: " << by_ref_lambda() << endl;//14
}
       C++11允许lambda表达式向函数指针转换,但前提是lambda没有捕捉任何变量,且函数指针的函数原型和lambda相同,如下:

int main() {
    int girls = 3, boys = 4;
    auto totalChild = [](int x, int y)->int{ return x + y; };
    typedef int (*allChild)(int x, int y);
    typedef int (*oneChild)(int x);
    allChild p;
    p = totalChild;//lambda表达式赋值给函数指针
    oneChild q;
    q = totalChild;     // 编译失败,参数必须一致
    decltype(totalChild) allPeople = totalChild;    // 需通过decltype获得lambdas的类型
    decltype(totalChild) totalPeople = p;           // 编译失败,指针无法转换为lambda
    return 0;
}
     lambda默认是const,可以通过mutable去掉const,如下:

int main(){
    int val;
    // 编译失败, 在const的lambda中修改常量
    auto const_val_lambda = [=]() { val = 3;};

    // 非const的lambda,可以修改常量数据
    auto mutable_val_lambda = [=]() mutable { val = 3;};  

    // 依然是const的lambda,不过没有改动引用本身
    auto const_ref_lambda = [&] { val = 3;};//引用本身并没有改变

    // 依然是const的lambda,通过参数传递val
    auto const_param_lambda = [&](int v) { v = 3;};
    const_param_lambda(val);

    return 0;
}
       按值传递的lambda函数相当于从父作用域复制了所需的捕获变量,这点和thread传参类似,如果担心拷贝开销的话可以参数引用传递父作用域的变量。

 
4 对齐方式

    alignof函数查看数据的内存对齐方式,alignas设定数据的内存对齐方式,如下:

#include <iostream>
using namespace std;

// 自定义的ColorVector,对齐到32byte的边界
struct alignas(32) ColorVector {
    double r;
    double g;
    double b;
    double a;
};

int main() {
    // 使用C++11中的alignof来查询ColorVector的对齐方式
    cout << "alignof(ColorVector): " << alignof(ColorVector) << endl;//alignof(ColorVector): 32
    return 1;
}








你可能感兴趣的:(C++11,nullptr默认函数的控制)