C++11:列表初始化 初始化列表initializer_list

news2024/12/27 11:01:16

前言

        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)

补充:模板的概念是C++98引入的,不是C++11 

列表初始化

基本概念:C++98只能使用{}聚合类型进行聚合初始化(此时{}还不叫列表初始化),可以使用()内置类型进行直接初始化,对自定义类型的对象进行构造和初始化(构造 != 初始化,先调用构造函数,可以在调用构造函数的同时进行初始化,也可以在调用构造函数后进行初始化)

问题:什么是聚合类型?

解释:聚合类型是一种特殊的自定义类型,它具有以下四个特征:

  1. 没有用户定义的构造函数聚合类型不能有用户自定义的构造函数
  2. 所有成员都是公有的聚合类型的所有成员变量必须是公有的
  3. 没有基类聚合类型不能继承自其他类
  4. 没有虚函数聚合类型不能有虚函数

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

问题:为什么可以使用()对内置类型进行直接初始化,对自定义类型进行构造和初始化?

解释: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;
}

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

3、string s = "1111" 也是直接构造,但本质是隐式类型转换 + 构造 + 拷贝构造,只是编译器将这三个步骤优化为了直接构造(便于用户使用),我们称这样的优化为单参数的构造函数支持隐式类型转换(将const char[N]常量字符数组类型的字符串"1111"隐式类型转换为const char*,然后再通过支持const char *为参数的单参数构造函数构造一个 string 对象,最后再将该对象拷贝给s)

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

结论:使用()对自定义类型的对象构造或对内置类型进行直接初始化时,要注意()可能被解析为逗号表达式或函数声明的情况 

基本概念: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、所谓的单参数的构造函数支持隐式类型转换 多参数的构造函数支持隐式类型转换 指的是单参数和多参数的构造函数支持出现由于“传入的参数与规定的参数类型相似但不一致”导致的找不到合适的构造函数的问题时,可以将该参数转换为规定的参数然后再进行构造(支持隐式类型转换 != 一定发生)(还有可能会遇到编译器将构造和拷贝构造优化为直接构造的情况,具体内容可以查看:C++ | 探究拷贝对象时的一些编译器优化_gcc 优化 拷贝构造 问题-CSDN博客,本篇文章后续内容不再考虑编译器优化的问题)

#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}; // 直接调用 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;
}

2、列表初始化不支持窄化的隐式类型转换,窄化转换就是大范围转小范围,但是()支持

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

注意事项:

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类型的构造函数就会报错

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

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类型

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

~over~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1794853.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

扣子工作流实战案例教程,手把手教你搭建一个图书管理工作流

&#x1f9d9;‍♂️ 诸位好&#xff0c;吾乃斜杠君&#xff0c;编程界之翘楚&#xff0c;代码之大师。算法如流水&#xff0c;逻辑如棋局。 &#x1f4dc; 吾之教程&#xff0c;内含诸般技术之秘诀。吾欲以此笔记&#xff0c;传授编程之道&#xff0c;助汝解技术难题。 &#…

STM32 音乐播放器之音频入门实验(pwm、dac、.wav、.mp3)

1.pwm实现简易电子琴实验 1.改变PWM频率&#xff0c;输出不同音调 2.改变占空比&#xff0c;调节音量大小 3.按键弹奏&#xff0c;支持按按键录取弹奏音 4.播放:中高低音&#xff1b;录取音&#xff1b;指定歌曲 5.支持按上一首&#xff0c;下一首&#xff0c;调弹奏速度&#…

C++设计模式-外观模式,游戏引擎管理多个子系统,反汇编

运行在VS2022&#xff0c;x86&#xff0c;Debug下。 30. 外观模式 为子系统定义一组统一的接口&#xff0c;这个高级接口会让子系统更容易被使用。应用&#xff1a;如在游戏开发中&#xff0c;游戏引擎包含多个子系统&#xff0c;如物理、渲染、粒子、UI、音频等。可以使用外观…

数据分析每周挑战——心衰患者特征数据集

这是一篇关于医学数据的数据分析&#xff0c;但是这个数据集数据不是很多。 背景描述 本数据集包含了多个与心力衰竭相关的特征&#xff0c;用于分析和预测患者心力衰竭发作的风险。数据集涵盖了从40岁到95岁不等年龄的患者群体&#xff0c;提供了广泛的生理和生活方式指标&a…

Web3开发框架分析

Web3开发框架旨在简化区块链和去中心化应用&#xff08;DApp&#xff09;的开发过程&#xff0c;为开发者提供必要的工具和库。以下是一些主要的Web3开发框架。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.Truffle Suite Truffl…

GB28181安防视频融合汇聚平台EasyCVR如何实现视频画面自定义标签?

安防视频融合汇聚平台EasyCVR兼容性强&#xff0c;可支持Windows系统、Linux系统以及国产化操作系统等&#xff0c;平台既具备传统安防视频监控的能力&#xff0c;也具备接入AI智能分析的能力&#xff0c;可拓展性强、视频能力灵活&#xff0c;能对外分发RTMP、RTSP、HTTP-FLV、…

25 - 销售分析III(高频 SQL 50 题基础版)

25 - 销售分析III -- where 是分组之前筛选数据 -- having 是分组之后筛选数据selectp.product_id,p.product_name fromSales s left join Product p on s.product_idp.product_id group byproduct_id havingmin(sale_date) >"2019-01-01" and max(sale_date)&…

使python技能飙升的5个实用F-String技巧

前言 python中存在着一些奇奇怪怪的语法&#xff0c;了解这些语法可以使我们在编写程序时事半功倍&#xff0c;不了解的则会使我们产生疑惑。今天&#xff0c;我们来了解一下python中关于f-string的5个实用的小技巧。 技巧一:数字分隔 num 1000000000 print(-*20)print(f{n…

传统的老程序员转向人工智能需要准备好这三件事情,你知道吗?

前言 人类文明的进步有时候快的吓人&#xff0c;在我们父辈上街买菜还是以一毛为计量买菜的时代&#xff0c;其实过去了也没有多长时间。现在买菜接近10块为准了&#xff0c;正常在集市上喊着怎么卖&#xff1f;摊主喊着三斤&#xff0c;包含的意思是10块钱三斤。相隔这么多年…

翘首以盼的抗锯齿

Antialiasing 实际的图形学中是怎么实现反走样的呢&#xff1f; 我们不希望实际产出的图形有锯齿效果&#xff0c;那怎么办呢&#xff1f; 从采样的理论开始谈起吧 Simpling theory 照片也是一种采样&#xff0c;把景象打散成像素放到屏幕上的过程&#xff1a; 还可以在不…

统信UOS SSH服务升级(ubuntu20)内网

服务器配置 系统信息 SSH版本 目标版本 openssh-server_8.2p1-4_arm64.deb 因为不通互联网&#xff0c;所以所有deb包需要手动下载&#xff08;可以连接互联网的可以自动忽略手动下载deb步骤&#xff0c;直接apt-get install xxx 即可&#xff09; 升级步骤 !!!deb下载方式…

开源低代码平台技术为数字化转型赋能!

实现数字化转型升级是很多企业未来的发展趋势&#xff0c;也是企业获得更多发展商机的途径。如何进行数字化转型&#xff1f;如何实现流程化办公&#xff1f;这些都是摆在客户面前的实际问题&#xff0c;借助于开源低代码平台技术的优势特点&#xff0c;可以轻松助力企业降低开…

SpringFramework总结

一.SpringFramework介绍 (一)Spring 广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。 Spring 已经不再是一个单纯的应用框架&#xff0c;而是逐渐发展成为一个由多个不同子项目&#xff08;模块&#xff09;组成的成熟技术&#xff0c;例如 Spring Frame…

LeetCode790多米诺和托米诺平铺

题目描述 有两种形状的瓷砖&#xff1a;一种是 2 x 1 的多米诺形&#xff0c;另一种是形如 “L” 的托米诺形。两种形状都可以旋转。给定整数 n &#xff0c;返回可以平铺 2 x n 的面板的方法的数量。返回对 109 7 取模 的值。平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺…

iPhone的5G设置怎么更改吗?设置好这些能够优化电池的使用寿命

随着5G技术的普及&#xff0c;iPhone用户现在可以享受到更快的网络速度&#xff0c;但这同时也带来了一个问题&#xff1a;如何在使用5G和保持电池寿命之间找到平衡&#xff1f;苹果公司通过引入“5G Auto”设置&#xff0c;为用户提供了一个智能的解决方案&#xff0c;但用户也…

动态库加载【Linux】

从此往后&#xff0c;建立映射&#xff0c;我们执行的任何代码&#xff0c;都是在我们的进程地址空间中进行执行 例如&#xff1a;将动态库映射到共享区&#xff0c;进程在CPU调度时&#xff0c;是在进程的进程地址空间处运行 程序在编译好之后&#xff0c;在加载到内存之前&a…

设计循环队列---力扣622

1、题目 1.1基础设置与讲解 循环队列&#xff0c;即固定长度的队列&#xff0c;可以想象成一个环形队列 就类似于这种队列&#xff0c;队尾指针后会有一个空位&#xff0c;用于控制判断队列为空还是为满&#xff1b; typedef int MyDataType;typedef struct {MyDataType fron…

美团发布2024年一季度财报:营收733亿元,同比增长25%

6月6日&#xff0c;美团(股票代码:3690.HK)发布2024年第一季度业绩报告。受益于经济持续回暖和消费复苏&#xff0c;公司各项业务继续取得稳健增长&#xff0c;营收733亿元(人民币&#xff0c;下同)&#xff0c;同比增长25%。 财报显示&#xff0c;一季度&#xff0c;美团继续…

vue-cl-service不同环境运行/build配置

概述 在项目开发过程中&#xff0c;同一个项目在开发、测试、灰度、生产可能需要不同的配置信息&#xff0c;所以如果能根据环境的不同来设置参数很重要。 vue项目的vue-cl-service插件也支持不同环境的不同参数配置和打包。 实现 新建不同环境配置文件 vue项目中的配置文件以…

Spring 的自动装配方式你都答的出来吗?

引言&#xff1a;Sprin g框架作为 Java 企业级应用开发的主流选择&#xff0c;其自动装配功能大大简化了开发人员的工作。自动装配能够帮助开发者减少手动配置的繁琐过程&#xff0c;提高了代码的可维护性和灵活性。在本文中&#xff0c;我们将深入探讨 Spring 的自动装配方式&…