1

C++11新特特性(四千字长文详解)

 1 year ago
source link: https://blog.51cto.com/u_15835985/7109716
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

C++11新特特性

[TOC]

统一初始化列表

这是C++98中花括号可以给结构体或者数组进行初始化!

struct Point
{
       int _x;
       int _y;
};
int main()
{
       int array1[] = { 1, 2, 3, 4, 5 };
       int array2[5] = { 0 };
       Point p = { 1, 2 };
       return 0;
}

而C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型

使用初始化列表时,可添加等号(=),也可不添加。

struct Point
{
       int _x;
       int _y;
};
int main()
{
       int array1[] = { 1, 2, 3, 4, 5 };
       int array2[]{ 1, 2, 3, 4, 5 };//C++11可以省略等号

       Point p1 = { 1, 2 };
       Point p2{1,2};//C++11可以省略等号


       int x1 = 1;
       int x2 = {1};//变量也可以用花括号初始化
       int x3 {1};//也可以省略等号


       //前面的建议不要用花括号初始化变量,还是使用传统的初始化这样可读性更好
       //但是其实这种初始化方式更主要是为了支持下面的
       int* p = new int[10]{1,2,3,4,5,5};
       //C++11之前new出来的数组都是未初始化的,C++11之后可以用花括号初始化

       Point* pp = new Point[2]{{1,2},{3,4}};
       //我们甚至可以使用这样子来进行初始化!
       //这不是一个二维数组!这是一个一维数组
       //里面的的花括号是为了初始化数组里面的Point类型的对象
       //因为我们上面也演示过了,Point类型的对象可以用花括号初始化

       return 0;
}
C++11新特特性(四千字长文详解)_数组
class Date
{
       public:
       Date(int year, int month, int day)
           :_year(year)
               ,_month(month)
               ,_day(day)
           {
               cout << "Date(int year, int month, int day)" << endl;
           }
       private:
       int _year;
       int _month;
       int _day;
};

int main()
{
       Date d{1,1,1};//这里的花括号严格来说是调用了date的构造函数来进行初始化的!
       Date* ds = new Date[2]{{1,1,1},{2,2,2}};//这里调用量两次构造函数!
       return 0;
}

std::initializer_list

class Date
{
       public:
       Date(int year, int month, int day)
           :_year(year)
               ,_month(month)
               ,_day(day)
           {
               cout << "Date(int year, int month, int day)" << endl;
           }
       private:
       int _year;
       int _month;
       int _day;
};
int main()
{
       Date d1{1,1,1};//这样子写是没有问题的!
       //因为本质是调用了构造函数的参数列
       //但是如果下面这样写就会出现问题!
       //Date d2{1,1,1,1};

       //但是为什么vector,list就可以一直往里面插入元素呢?
       //vector,list其实本质也是一个自定义类型!
       vector<int> v ={1,1,1,1,1};
       list<int> ls ={1,2,3,4,55};
       //这是怎么做到的?
}

因为C++11中的{}出了可以匹配类的构造函数之外还能去匹配另一个类型——initializer_list

int main()
{
    auto il = {1,2,3,4,5,6,7};
    cout << typeid(il).name()<<endl;
}

C++11新特特性(四千字长文详解)_初始化_02
C++11新特特性(四千字长文详解)_数组_03

可以认为initializer_list底层就是一个常量数组!存在常量区

initializer_list这个容器自己不开辟空间,里面有两个指针!指向一个常量数组的开始和结束

C++11新特特性(四千字长文详解)_数组_04

里面还支持了一个类似迭代器的东西——那不是迭代器是类似迭代器

C++11新特特性(四千字长文详解)_初始化_05

我们可以这个容器也给我提供了begin与end接口!

int main()
{
    //所以我们要区分虽然都是使用花括号!但是调用的却是不一样的!
    Date d1{1,1,1};//这是调用构造函数!

    vector<int> v ={1,1,1,1,1};
    list<int> ls ={1,2,3,4,55};
    //这两个是调用initializer_list
}

而list和vector都是有支持了initializer_list的构造函数

C++11新特特性(四千字长文详解)_构造函数_06

所以无论多少个参数的都能支持!因为本质都是传给了initializer_list,然后再遍历initializer_list,push_back进vector或者list里面!

我们也可以自己写一个支持initializer_list的vector容器——具体情读者自行实现,这里我们只实现关于initializer_list的构造函数

当我们不进行实现,且使用自己的写的vector的试试

C++11新特特性(四千字长文详解)_数组_07

其实就是识别成了一个类型转换!

当我们实现了这个构造函数

vector(std::initializer_list<T> il)//一般都是传值!因为initializer_list里面有只两个指针所以直接传值就可以	
    :_start(nullptr),
	_finish(nullptr),
	_endofstorage(nullptr)
{
    typename std::initializer_list<T>::iterator it = il.begin();
    while(it != il.end())
    {
        push_back(*it);
        it++;
    }
}

C++11新特特性(四千字长文详解)_数组_08

这样子就成功支持了!

几乎所有的容器都支持这个构造函数!

int main()
{
    vector<Date> v1{{1,1,1},{2,2,2},{3,3,3}};
    //这样子vector就可以怎么写!
    //里面的花括号是调用了date的构造函数!
    //外面的那个花括号本质是先去调用了initializer_list
    //然后才去调用了vector的构造函数!
}

有了花括号我们还可以做一些很方便的事情!

int main()
{
    map<string,string> mp;
    mp.insert(make_pair("1","1"));//我们以前使用map都是这样子插入的!
    mp.insert(std::pair<string,string>("1","1"));//或者是这样子插入的!
    
    //但是有了花括号初始化之后,我们可以这样子插入
    mp.insert({"1","1"});//因为花括号会自动的去调用pair的构造函数去构造一个pair出来!
    
    //因为库里面的map也是支持initializer_list的构造函数!本质也是遍历initializer_list然后插入!
    map<int,int> mp2{{1,1},{2,2},{3,3}};//这样子也是可以的!
    //里面的花括号是调用用pair的构造函数!
    //外面的花括号是调用initializer_list然后遍历initializer_list进行插入!
    
}

C++11提供了很多简化的声明方式

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局 部的变量默认就是自动存储类型,所以auto就没什么价值了。

C++11中废弃auto原来的用法,将 其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。

int main()
{
       int i = 10;
       auto p = &i;
       auto pf = strcpy;
       cout << typeid(p).name() << endl;
       cout << typeid(pf).name() << endl;
    
       map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
       //map<string, string>::iterator it = dict.begin();
       //等价于上面的
       auto it = dict.begin();
       return 0;
}

decltype

可以通过表达式来推导类型!然后还可以用这个类型来定义变量!

decltype的功能和typeid.name有点类似!——只不过typeid.name得到是字符串!不能用于定义变量

int main()
{
       int x =0;
       cout << typeid(x).name() << endl;
       //tyepid(x).name y;//这样子是不行的!因为tyepid(x).name是一个字符串!

       decltype(x) y;//这样子是可以的!因为decltype(x)可以推导x的类型!用这样子的方式来定义变量!
}

不过更经常的是下面的用法

template<class T1,class T2>
       void F(T1 t1,T2 t2)
{
       decltype(t1 * t2) ret = t1*t2;//通过表达式来推导ret的类型!
       //然后用这个类型来定义对象
       //这样子我们可以保证精度不会丢失!
       //如果固定某个类型就可能丢失精度
       //主要还是因为不知道T1与T2的类型
       cout << typeid(ret).name() << endl;
}
//当我们在不知道t1与t2两个不同类型的相乘后的结果是什么的时候!我们可以用这个来进行推导!
int main()
{ 
       F(1,2);
       F(1.0,2);
}

nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示 整形常量。

所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

default——强制生成默认函数

假如我们真的遇到那非要写析构函数 、拷贝构造、拷贝赋值重载呢?——但是其实默认生成的移动构造就已经够用了!那么我们要怎么办呢?——我们可以使用defualt来强制生成!

class Person
{
       public:
       Person(const char *name = "", int age = 0)
            : _name(name), _age(age)
            {
            }
       Person(const Person &p)
            : _name(p._name), _age(p._age)
            {
            }

       //我们可以看到这样子写其实很麻烦!
       /*Person(Person &&p)
      : _name(forward<MySTL::string>(p._name))//要使用move或者forward,否则p._name是左值,不能调用右值引用的构造函数
      , _age(p._age)
{
}*/

       //有了default我们就可以怎么写
       Person(Person &&p) = default;
       //这样子就可以生成一个默认移动构造!
       ~Person() 
       {
       }
       private:
       MySTL::string _name;
       int _age;
};
int main()
{
       Person s1;
       Person s2 = s1;//拷贝构造
       Person s3 = move(s1);//移动构造
       return 0;
}

C++11新特特性(四千字长文详解)_数组_09

delete

假如我们想要写一个类!这个类禁止被拷贝!——那么我们应该怎么办?

在C++98的时候我们想要做到这件事——我们就要在私有对拷贝构造进行声明(而不是进行实现!)

为什么不要进行实现呢?因为如果实现了那么在A类里面就可以调用拷贝构造进行拷贝A类了!

//如果实现了拷贝构造!
class A
{
    public:
       A* func()
       {
           return new A(*this);
       }
       //就可以怎么玩来完成拷贝!
       A()
       {
       }
       ~A()
       {
           delete[] p;
       }
    private:
       A(const A& a)
       {
           //.....
       }//实现而不是声明
       int* p = new int[10];
};
int main()
{
       A a;
       A* b = a.func();
       return 0;
}
C++11新特特性(四千字长文详解)_初始化_10

如果是声明

class A
{
    public:
       A* func()
       {
           return new A(*this);
       }
       A()
       {
       }
       ~A()
       {
           delete[] p;
       }
    private:
       A(const A& a);
    private:  
       int* p = new int[10];
};
C++11新特特性(四千字长文详解)_数组_11

正常的拷贝

C++11新特特性(四千字长文详解)_构造函数_12

调用函数进行拷贝

我们发现这样子都不可以!——就可以防止拷贝了!

我们可以发现C++98的方式很绕!

但是C++11的方式就十分的直接!——使用delete!

class A
{
       public:
       A* func()
       {
           return new A(*this);
       }
       A()
       {}
    
       A (A& a) = delete;//直接这样子就可以!
       ~A()
       {
           delete[] p;
       }
       private:
       int* p = new int[10];
};
int main()
{
       A a;
       A* b = a.func();
       return 0;
}
C++11新特特性(四千字长文详解)_初始化_13

这样子无论如何都无法调用拷贝构造了!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK