盛世宏图 发表于 2024-6-11 08:48:00

C++11:列表初始化 && 初始化列表initializer_list && decltype关键字

目录
前言
列表初始化
初始化列表initializer_list
decltype关键字
左值和右值
move
前言

        2003年C++标准委员会曾经提交了一份技能勘误表(简称TC1),使得C++03这个名字代替了C++98成为了C++11前最新的C++标准名称。不过由于C++03主要是对C++98标准中的毛病进行修复,语言的核心部门则没有改动,因此人们风俗性的把两个标准归并成为C++98/03标准,从C++0x到C++11到,C++委员会十年磨一剑,第二个真正意义上的C++新标准C++11在2011年姗姗来迟。
        C++11则带来了数量可观的变化,其中包罗了约140个新特性,以及对C++03标准中约600个缺陷的修正,别的C++11能更好的用于系统开辟和库开辟、语言更加泛化和简单化、步伐更加稳定和安全,不但功能强盛,而且还能提拔步伐员的开辟效率,在公司现实项目开辟中也用的较多。
   官网检察各编译器对C++11标准的接收环境:C++11 - 维基百科,自由的百科全书 (wikipedia.org)
https://img-blog.csdnimg.cn/direct/a0cc545340a948ca845a9222a05d795f.png
   补充:模板的概念是C++98引入的,不是C++11 
列表初始化

根本概念:C++98只能使用{}对聚合范例进行聚合初始化(此时{}还不叫列表初始化),可以使用()对内置范例进行直接初始化,对自定义范例的对象进行构造和初始化(构造 != 初始化,先调用构造函数,可以在调用构造函数的同时进行初始化,也可以在调用构造函数后进行初始化)
   标题:什么是聚合范例?
解释:聚合范例是一种特别的自定义范例,它具有以下四个特征:

[*]没有用户定义的构造函数:聚合范例不能有用户自定义的构造函数
[*]所有成员都是公有的:聚合范例的所有成员变量必须是公有的
[*]没有基类:聚合范例不能继承自其他类
[*]没有虚函数:聚合范例不能有虚函数
    C++98中{}的留意事项:
1、对聚合范例进行的初始化叫做聚合初始化,聚合初始化与构造无关,不会调用构造函数
//聚合类型
struct Point {
    int x;
    int y;
};
Point p = {1, 2};2、{} 可对聚合范例进行部门初始化,未显式初始化的成员会被默认初始化为零
#include <iostream>
struct Point {
    int x;
    int y;
    int z;
};

int main() {
    Point p = { 1, 2 }; // 只初始化了 x 和 y,z 会被默认初始化为 0
    std::cout << "Point: (" << p.x << ", " << p.y << ", " << p.z << ")" << std::endl; // 输出:Point: (1, 2, 0)
    return 0;
}https://img-blog.csdnimg.cn/direct/01402c76cda2471381bd4397105824dd.png
    标题:为什么可以使用()对内置范例进行直接初始化,对自定义范例进行构造和初始化?
解释:C++98引入了模板的概念,使用 () 时,编译器会将其解释为调用相应范例的构造函数(()对内置范例int i(5)直接初始化的本质是调用int 范例的构造函数来将整数值 5 转换为 int 范例并初始化变量 i,但不能使用int i(); i = 5的情势,由于前者会被视为一个函数声明,赋值时会被视为向一个名为i函数进行赋值)
    C++98中()的留意事项:
1、对自定义范例的对象进行构造时,没有()时叫做默认构造,有()时依据()内参数的多少分为单参数和多参数构造,传入的参数叫做对该对象的初始化;一样平常不会使用()对内置范例进行像int i(5)这样的直接初始化,但要了解为什么可以这样(本质还是调用了构造)
2、使用() 对自定义范例的对象进行构造时必须要有得当的构造函数(传递单个参数对构造对象进行初始化时,对象中要有单参数的构造函数,传递多个参数对构造对象进行初始化时,对象中要有多参数的构造函数)构造时的情势为类名 对象名()或  类名 对象名
#include <iostream>
using namespace std;

class Date
{
public:
        Date()
        {
                cout << "Date()" << endl;
        }

        Date(int year)
                :_year(year)
        {
                cout << "Date(int year)" << endl;
        }

        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 d0;//默认构造
        Date d2(2024);//单参数构造
        Date d3(2024,5,26);//多参数构造
   
    int i(5);//单参数构造
    double f(3.14);//单参数构造
    return 0;
}

https://img-blog.csdnimg.cn/direct/9dd06da9b2f249b4bceb5596513a8c77.png
2、不建议以Date d =()的情势对自定义范例的对象进行构造,由于此时()会被编译器视为逗号表达式,()内为空时会报错,()有参数时,参数个数无论为多少都会去调用单参数构造函数,纵然是内置范例也是一样的
#include <iostream>
using namespace std;

class Date
{
public:
        Date()
        {
                cout << "Date()" << endl;
        }

        Date(int year)
                :_year(year)
        {
                cout << "Date(int year)" << endl;
        }

        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 = (2024);
        Date d1 = (2024, 5, 26);

        int z = (5, 6);
        cout << "z = " << z << endl;
        return 0;
};
https://img-blog.csdnimg.cn/direct/349cf3b3bee6485b920be527cb3d7cae.png3、string s = "1111" 也是直接构造,但本质是 构造 + 拷贝构造,只是编译器将它们优化为了直接构造(便于用户使用),我们将这样的优化称为单参数的构造函数支持隐式范例转换(所谓的隐式范例转换即将一个cosnt char*范例的对象转为string范例的对象)
4、进行默认构造时,要以Date d情势进行,不能以Data d()的情势,由于后者在编译器看来不是构造而是一个函数声明(这也被称为C++最烦人的解析)
#include <iostream>
using namespace std;

class Date
{
public:
        Date()
        {
                cout << "Date()" << endl;//最后打印的Date()应该只有一行
        }
};

int main()
{
        Date d1();//函数声明,不是初始化对象
        cout << endl;
        cout << "上面有Date()吗?" << endl;
        Date d1;//默认构造
        cout << "上面有Date()吗?" << endl;
        return 0;
}
https://img-blog.csdnimg.cn/direct/ef5284f0c79a4168bdcae0fd52fc1f80.png
    结论:使用()对自定义范例的对象构造或对内置范例进行直接初始化时,要留意()可能被解析为逗号表达式或函数声明的环境 
根本概念:C++11中扩大了{}的使用范围,使其可以对所有范例进行初始化(且=可以省略)此时我们将使用{}进行初始化的行为叫做列表初始化,()的用法稳定
   1、此时{}对聚合范例的初始化仍叫做聚合初始化,而不是列表初始化,且初始化规则稳定
//对聚合类型进行聚合初始化
struct Point {
    int x;
    int y;
};

Point P = {1};    // C++98支持使用{}进行部分聚合初始化
Point p = {1, 2}; // C++98支持使用{}进行完全聚合初始化
Point p{3, 4};    // C++11及以后版本均支持使用{}进行部分聚合初始化,且=可省略
Point p{3, 4};    // C++11及以后版本均支持使用{}进行聚合初始化,且=可省略

int arr[]{1,2,3,4,5}//对数组进行部分聚合化    2、使用()对内置范例进行初始化时仍叫直接初始化,而使用{}对内置范例进行初始化时叫做列表初始化
//对内置类型进行初始化的多种方式
int x = 1;               //每个C++版本一定支持的
int y(5) 或 y = (5);   //C++98后开始支持的
int z{3} 或 z = {3};   //C++11后开始支持的    3、此时{}和()均可以对自定义范例的对象进行构造和初始化
class Date
{
public:
    Date(int year)
                :_year(year)
        {
                cout << "Date(int year)" << endl;
        }

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

Date d1;//C++11和C++98均支持这样做

Date d2(2024);//C++98支持的使用()进行单参数构造
Date d3(2024,5,26);//C++98支持的使用()进行多参数构造

//省略=
Date d4{2024};//C++11支持的使用{}进行单参数构造
Date d5{2024,5,26};//C++11支持的使用{}进行多参数构造

//不省略=
Date d6 = {2024};//C++11支持的使用{}进行单参数构造
Date d6 = {2024,5,6};//C++11支持的使用{}进行多参数构造

[*]对自定义范例的对象进行构造时仍建议使用()进行,{}华而不实,且使用{}时也不建议省略=,由于会导致代码可读性降低
留意事项:
1、列表初始化是一种直接调用构造函数的方式,C++11及以后版本使用列表初始化时,{}会去寻找最为得当的构造函数,假如找不到最合适的,会实行将{}中的内容进行隐式范例转换,从而找到一个较为得当的构造函数,假如还找不到就会报错
2、单参数和多参数的构造函数支持隐式范例转换 中的隐式范例转换指的是它们支持将其它范例转换为它们自己的自定义范例(比如:{int,int} -> B)而在这一隐式范例转换的过程中会进行构造 + 拷贝构造(编译器会进行优化),同时它们还支持传入{1,21.1}但是构造函数只有(double,double)时会将{1,21.1}转换为{1.0,21.1}再去调用构造函数的这种情势的隐式范例转换
#include <iostream>
#include <string>

class Person {
public:
    std::string name;
    int age;
    int height;

    // 单参数构造函数
    Person(std::string n) : name(n), age(0) {
      std::cout << "Person(std::string n)" << std::endl;
    }

    Person(int a) : name("Unknown"), age(a) {
      std::cout << "Person(int a)" << std::endl;
    }

    Person(std::string n, int a) : name(n), age(a) {
      std::cout << "Person(std::string n, int a)" << std::endl;
    }

    Person(double n, double a) : height(n), age(a) {
      std::cout << "Person(double n, double a)" << std::endl;
    }

    Person(const char* s) : name(s) {
      std::cout << "Person(const char * s)" << std::endl;
    }
};

int main()
{
    Person a = { "Alice", 30}; // const char* -> string, 直接调用 Person(std::string, int) 构造函数
    Person b = { "fwqfq" };    // 直接调用 Person(const char * s) 构造函数
    Person c = { 5 , 6 };      // 隐式类型转换: int -> double,然后直接调用 构造Person(double n, double a) 函数
    Person d = (52.5);         // 隐式类型转换:double -> int,然后直接调用Person(int a) 构造函数
    Person e = "fewfew";       // 直接调用Person(const char* s)构造函数,如果只有Person(string s),就会先进行隐式类型转换然后再调用该构造函数
    Person f = 40;             // 直接调用 Person(int a) 构造函数
    return 0;
}
https://img-blog.csdnimg.cn/direct/806567d4ce1b41ffa26ee707b9ee7462.png
2、列表初始化不支持窄化的隐式范例转换,窄化转换就是大范围转小范围,但是()支持
https://img-blog.csdnimg.cn/direct/86b78bb234ce480e936a01d63abc8d36.png
https://img-blog.csdnimg.cn/direct/560025d3436640cf8a2bc2c24e3133d6.png
   关于C++的隐式范例转换的其它文章:彻底理解c++的隐式范例转换 - apocelipes - 博客园
初始化列表initializer_list

根本概念:是一个模板类,用于向自定义范例或函数(条件是得有支持该范例的构造函数或者参数)传递一组同范例的参数,它通常与列表初始化{}配合使用(初始化列表 != 列表初始化)
template<class T> class initializer_list; #include <iostream>
#include <vector>

//printList函数有initializer_list类型的参数
void printList(std::initializer_list<int> list) {
    for (auto elem : list) {
      std::cout << elem << " ";
    }
    std::cout << std::endl;
}

class MyClass {
public:
    //MyClass有支持initializer_list类型的构造函数
    MyClass(std::initializer_list<int> list) {
      for (auto elem : list) {
            std::cout << elem << " ";
      }
      std::cout << std::endl;
    }
};

int main() {
    printList({10, 20, 30, 40, 50});
    MyClass obj = {1, 2, 3, 4, 5};   
    return 0;
}
https://img-blog.csdnimg.cn/direct/2b0403fce8b449e4973419fa7c35da46.png
留意事项:
1、{10, 20, 30, 40, 50}在正常环境下还是列表初始化,但是当{10, 20, 30, 40, 50}要作为函数参数或者要赋值给一个自定义范例时,编译器会将{10, 20, 30, 40, 50}识别为initializer_list范例(不会构造initializer_list范例的匿名对象),然后直接向某个有initializer_list范例形参的函数进行传参,或者调用某个支持自定义范例对象的支持initializer_list范例的构造函数


[*]MyClass obj = {1, 2, 3, 4, 5}:将{1, 2, 3, 4, 5}解析为initializer_list范例—>直接调用MyClass类中支持initializer_list范例的构造函数
[*]Myclass obj({10, 20, 30, 40, 50}):将{10, 20, 30, 40, 50}解析为initializer_list范例—>直接调用Myclass 类中支持initializer_list范例的构造函数构造
[*]printList({10, 20, 30, 40, 50}):将{10, 20, 30, 40, 50}解析为initializer_list范例—>直接向printList函数传参
2、对于C++库中提供的各种容器,它们都有支持initializer_list范例的构造函数,不用担心直接使用即可,但是对于自定义范例假如没有支持initializer_list范例的构造函数就不能使用,上面的MyClass obj = {1, 2, 3, 4, 5};假如没有支持initializer_list范例的构造函数就会报错
https://img-blog.csdnimg.cn/direct/789f5a06a9eb496691f8d229bea6e4d0.png
3、编译器会优先调用支持initializer_list范例的构造函数,而不是写死参数个数的构造函数
#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass(int a,int b,int c,int d,int e)
    {
      std::cout << "MyClass(int a,int b,int c,int d,int e)" << std::endl;
    }

    MyClass(std::initializer_list<int> list)
    {
      for (auto elem : list) {
            std::cout << elem << " ";
      }
      std::cout << std::endl;
    }

};

int main()
{
    MyClass obj = { 1, 2, 3, 4, 5 };
    return 0;
} https://img-blog.csdnimg.cn/direct/32e3dc893aab4e4b8e5196ff8e4a3ba9.png
4、initializer_list模板类的引入,使得我们在向容器中写入数据时更加的简单
//原来
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

std::set<int> st;
v.insert(1);
v.insert(2);
v.insert(3);
v.insert(4);

std::map<string,string> mt;
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("right", "右边"));

//现在
std::vector<int> v = {1,2,3,4};//直接调用支持 initializer_list 类型的构造函数
std::set<int> st = {1,2,3,4,5};//直接调用支持 initializer_list 类型的构造函数
std::map<string,string> mt = {{"right", "右边"},{"right", "右边"}};

   对于std::set<int> st = {1,2,3,4,5}:{1,2,3,4,5}是使用{}的列表初始化,又由于左边是vector容器范例,因此编译器会解析为一个initializer_list范例,然后直接调用vector中支持initializer_list范例的构造函数
对于std::map<string,string> mt = {{"right", "右边"},{"right", "右边"}}:编译器会先识别出{{"right", "右边"}, {"right", "右边"}}是一个用于初始化map范例对象的initializer_list,接着调用 pair 的构造函数生成两个 pair<const std::string, std::string> 对象,然后将生成的 两个 pair 对象组合成一个 initializer_list<std::pair<const std::string, std::string>> ,末了直接调用map支持initializer_list范例的构造函数
   补充:
 1、pair范例不支持initializer_list 范例的构造函数,因此pair<?> p = {}是列表初始化而不是initializer_list范例
https://img-blog.csdnimg.cn/direct/fff13e2b46e64698b3df4248cebc7635.png
2、map 有一个担当initializer_list 的构造函数,其定义如下(set也类似):
map(std::initializer_list<std::pair<const Key, T>> init);3、pair差别范例间的pair可以进行拷贝构造,是由于pair的拷贝构造是一个函数模板
template<class U, class V>
pair(const pair<U, V>& pr);

pair<const char*,char*> kv3 = {"sort","排序"};
pair<const string,string> kv4(kv3);decltype关键字

根本概念:是 C++11 引入的一个关键字,用于查询表达式的范例,它办理了typeid只能进行打印变量范例但是不能作为一个范例的使用
常见使用方式:
1、 获取变量范例
int x = 0;
decltype(x) y = 5;//y的类型是int
2、获取表达式范例
int a = 5;
double b = 3.14;
decltype(a + b) c = a + b; //因为a + b的类型是double,所以c的类型是 double  3、decltype关键字通常会与获取lambda表达式配合使用
~over~ 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: C++11:列表初始化 && 初始化列表initializer_list && decltype关键字