当前位置 : 主页 > 编程语言 > c语言 >

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

来源:互联网 收集:自由互联 发布时间:2023-08-25
C++11新特特性 统一初始化列表 这是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新特特性

统一初始化列表

这是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;
}

image-20230508203506938

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;
}

image-20230508205029577

image-20230508204930841

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

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

l

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

image-20230508205509410

我们可以这个容器也给我提供了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的构造函数==

image-20230508210149491

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

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

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

image-20230508211349827

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

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

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++;
    }
}

image-20230508212224384

==这样子就成功支持了!==

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

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提供了很多简化的声明方式

auto

在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;
}

image-20230512145654259

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;
}

image-20230512151506780

==如果是声明==

class A
{
    public:
       A* func()
       {
           return new A(*this);
       }
       A()
       {
       }
       ~A()
       {
           delete[] p;
       }
    private:
       A(const A& a);
    private:  
       int* p = new int[10];
};

image-20230512150938337

正常的拷贝

image-20230512151414978

调用函数进行拷贝

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

==我们可以发现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;
}

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

上一篇:模拟实现一个简单的string类
下一篇:没有了
网友评论