[C++ 11] 列表初始化:轻量级对象initializer_list

news2024/12/23 16:40:36

C++发展历史

C++11是C++语言的第二个主要版本,也是自C++98以来最重要的一次更新。它引入了大量的新特性,标准化了已有的实践,并极大地改进了C++程序员可用的抽象能力。在2011年8月12日被ISO正式采纳之前,人们一直使用“C++0x”这个名称,因为它原本预计会在2010年之前发布。然而,由于各种原因,直到2011年才最终确定。C++03与C++11之间间隔了8年,这是C++版本发布史上最长的一次。从那时起,C++社区每三年发布一次新标准,保持了更加稳定的更新节奏。

列表初始化

C++11引入了列表初始化(List Initialization),试图统一所有对象的初始化方式,使代码更加简洁和安全。然而,这也带来了一些细节和概念上的区别,可能会引起混淆。该章节将结合具体代码,深入讲解C++11中的列表初始化,与C++98进行对比,更清晰地理解这些概念。

C++98中的初始化方式

在C++98中,数组和聚合类型(如结构体)可以使用大括号{}进行初始化,但基本类型和自定义类对象通常不能直接使用{}初始化,需要使用构造函数或赋值操作。

数组和结构体的初始化

struct Point {
    int _x;
    int _y;
};

int main() {
    // 数组初始化
    int a1[] = {1, 2, 3, 4, 5};
    int a2[5] = {0}; // 所有元素初始化为0

    // 结构体初始化
    Point p = {1, 2};

    return 0;
}

上述代码中,数组a1a2,结构体p都使用{}进行初始化,这是C++98所支持的。

基本类型和自定义类的初始化

在C++98中,基本类型的初始化不能使用{},需要使用赋值或构造函数。

int x = 2; // 赋值初始化

对于自定义类对象,需要定义构造函数,然后使用括号()进行初始化。

class Date {
public:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {}

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d(2025, 1, 1); // 使用构造函数初始化
    return 0;
}

C++11中的列表初始化

C++11引入了列表初始化,使得几乎所有类型的对象都可以使用{}进行初始化,包括基本类型和自定义类对象。这种统一的初始化方式带来了代码简洁性和安全性的提升。

基本类型的列表初始化

int x1 = {2}; // 列表初始化
int x2 = 2;   // 传统赋值初始化
int x3{2};    // 省略等号的列表初始化
  • 区别x1x3使用了列表初始化,x2使用了传统的赋值初始化。
  • 优势:列表初始化可以防止窄化转换。例如,int x = {2.5};会编译错误,防止精度丢失。

自定义类型的列表初始化

Date d1 = {2025, 1, 1};
Date d20(2025, 1, 1);
const Date& d2 = {2024, 7, 25};
Date d3 = {2025};
Date d4 = 2025;
Date d6{2024, 7, 25};
const Date& d7{2024, 7, 25};
  • Date d1 = {2025, 1, 1};:使用列表初始化,按照语义应该是先构造一个临时的Date对象,然后调用拷贝构造函数复制给d1。但是,编译器会进行优化,直接构造d1,避免了拷贝构造。
  • const Date& d2 = {2024, 7, 25};:引用一个临时的Date对象,该对象由列表初始化创建。
  • Date d3 = {2025};:当只有一个参数时,列表初始化也可以使用。
  • Date d4 = 2025;:C++98中允许的隐式类型转换,调用Date(int, int, int)构造函数,剩余参数使用默认值。
  • 省略等号的初始化Date d6{2024, 7, 25};const Date& d7{2024, 7, 25};

结构体的列表初始化

struct Point {
    int _x;
    int _y;
};

int main() {
    Point p1 = {1, 2}; // C++98风格
    Point p2{3, 4};    // C++11,省略等号

    return 0;
}

容器的列表初始化

vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
v.push_back({2025, 1, 1}); // 使用列表初始化

vector<int> v1 = {1, 2, 3, 4};
vector<int> v2 = {10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1};
const vector<int>& v4 = {10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1};

vector<int> v3({10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1});
  • v.push_back({2025, 1, 1});:直接使用列表初始化创建一个Date对象,并插入到向量v中。
  • 容器的列表初始化v1v2v4v3都使用了列表初始化,其中v3显式地调用了构造函数。
std::initializer_list的使用
initializer_list<int> il1 = {10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1};
  • std::initializer_list是一个轻量级的只读容器,用于保存初始化列表中的元素。
  • 容器类(如vector)的构造函数和赋值运算符都增加了接受std::initializer_list的版本,因此可以直接使用{}进行初始化。
map的列表初始化和插入
map<string, string> dict;
dict.insert({"xxx", "yyyy"}); // 使用列表初始化的 pair

map<string, string> dict2 = {{"xxx", "yyyy"}, {"sort", "zzzz"}};
  • dict.insert({"xxx", "yyyy"});{"xxx", "yyyy"}会被隐式转换为std::pair<const string, string>,然后插入到dict中。
  • dict2的初始化:直接使用列表初始化,将多个键值对插入到map中。

std::initializer_list原理和作用

C++11引入了std::initializer_list,使得初始化容器和自定义类型的方式更加灵活和简洁。

背景

在C++98中,初始化数组和聚合类型(如结构体)可以使用大括号{},但对于容器和自定义类的初始化,尤其是当需要传入多个参数时,显得不够方便。例如,要初始化一个std::vector对象并赋予多个初始值,可能需要多次调用push_back,或者手动实现多个构造函数来支持不同数量的参数。

std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// 或者手动实现多个构造函数来支持不同数量的参数

为了解决这个问题,C++11引入了std::initializer_list,提供了一种统一的方式来初始化容器和自定义类型。

std::initializer_list的原理

std::initializer_list是C++11标准库中的一个模板类,用于表示由大括号{}括起来的一系列元素。它允许我们使用列表初始化的方式为对象赋值,从而简化代码书写,提高可读性。

#include <initializer_list>

std::initializer_list<int> il = {1, 2, 3};
内部实现

std::initializer_list内部包含了两个指针,分别指向初始化列表中第一个元素和最后一个元素的下一个位置。其实现通常如下:

template <class E>
class initializer_list {
public:
    // 类型定义
    typedef E        value_type;
    typedef const E& reference;
    typedef const E& const_reference;
    typedef size_t   size_type;
    typedef const E* iterator;
    typedef const E* const_iterator;

    // 构造函数
    initializer_list() noexcept : _array(0), _length(0) {}

    // 大小和开始、结束迭代器
    size_t size()  const noexcept { return _length; }
    const E* begin() const noexcept { return _array; }
    const E* end()   const noexcept { return _array + _length; }

private:
    // 私有成员
    const E* _array;
    size_t   _length;
};
  • _array:指向存储元素的数组的指针。
  • _length:表示元素的数量。
特性
  • 只读容器std::initializer_list是一个轻量级的只读容器,不能修改其中的元素。
  • 自动推导类型:可以通过auto关键字自动推导类型。
  • 范围for循环:支持使用范围for循环遍历元素。

std::initializer_list的作用

初始化容器

C++11中的标准容器(如std::vectorstd::liststd::map等)都增加了接受std::initializer_list的构造函数和赋值运算符,使得容器可以方便地使用列表初始化。

#include <iostream>
#include <vector>
#include <map>
#include <string>

int main() {
    // 初始化vector
    std::vector<int> v1 = {1, 2, 3, 4, 5};
    std::vector<int> v2{6, 7, 8, 9, 10}; // 省略等号

    // 初始化map
    std::map<std::string, std::string> dict = {
        {"apple", "苹果"},
        {"banana", "香蕉"}
    };

    // 输出vector内容
    for (auto val : v1) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // 输出map内容
    for (const auto& pair : dict) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

运行结果

1 2 3 4 5
apple: 苹果
banana: 香蕉
  • std::vector的列表初始化:通过{}直接传入初始值列表,调用了接受std::initializer_list的构造函数。然后作为initializer_list来构造容器。
  • std::map的列表初始化:使用{}传入键值对的列表,其中每个键值对也是使用{}初始化的std::pair对象,也就相当于initializer_list的嵌套构造。
自定义类型的初始化

除了标准容器,用户自定义的类也可以通过定义接受std::initializer_list的构造函数,来支持列表初始化。

#include <iostream>
#include <initializer_list>

class MyClass {
public:
    MyClass(std::initializer_list<int> il) {
        for (auto it = il.begin(); it != il.end(); ++it) {
            data.push_back(*it);
        }
    }

    void print() const {
        for (auto val : data) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }

private:
    std::vector<int> data;
};

int main() {
    MyClass obj = {1, 2, 3, 4, 5};
    obj.print(); // 输出:1 2 3 4 5

    return 0;
}
  • 接受std::initializer_list的构造函数:在自定义类MyClass中,定义了一个构造函数,接受std::initializer_list<int>类型的参数。
  • 使用列表初始化创建对象:在main函数中,直接使用{1, 2, 3, 4, 5}来初始化MyClass对象。
函数参数的初始化

std::initializer_list也可以作为函数的参数,方便地传递一组值。

#include <iostream>
#include <initializer_list>

void printValues(std::initializer_list<int> il) {
    for (auto val : il) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
}

int main() {
    printValues({10, 20, 30, 40, 50});
    return 0;
}
10 20 30 40 50
  • 函数接受std::initializer_list参数printValues函数接受一个std::initializer_list<int>类型的参数。
  • 调用函数时传入列表:在调用printValues时,直接传入一个初始化列表{10, 20, 30, 40, 50},也可以作为构造函数或拷贝构造函数等的实参进行传入。

容器对std::initializer_list的支持

  1. 构造函数

标准容器都增加了接受std::initializer_list的构造函数。例如:

// vector的initializer_list构造函数
std::vector<int> v = {1, 2, 3, 4, 5};

// map的initializer_list构造函数
std::map<std::string, int> m = {{"one", 1}, {"two", 2}};
  1. 赋值运算符

容器的赋值运算符也支持std::initializer_list,可以方便地重置容器的内容。

std::vector<int> v = {1, 2, 3};
v = {4, 5, 6}; // 重新赋值

示例代码:

#include <iostream>
#include <vector>
#include <map>

int main() {
    // 使用initializer_list初始化vector
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用initializer_list赋值vector
    vec = {6, 7, 8, 9, 10};

    // 输出vector内容
    for (auto val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // 使用initializer_list初始化map
    std::map<std::string, int> mp = {{"apple", 1}, {"banana", 2}};

    // 输出map内容
    for (const auto& pair : mp) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

运行结果:

6 7 8 9 10
apple: 1
banana: 2

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

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

相关文章

10-1.idea中的项目结构,辅助快捷键,模块的操作

idea中的项目结构和辅助快捷键 IDEA中项目结构 首先是创建项目&#xff0c;新建的项目中有子项目&#xff0c;我们可以创建模块 然后在模块中我们可以创建包&#xff0c;在包中的SRC中写我们的源代码&#xff0c;也就是类。 VScode写Java项目 如何你电脑比较卡的话&#…

基于GPT的智能客服落地实践

&#x1f4cd;前言 在日常生活中&#xff0c;「客服」这个角色几乎贯穿着我们生活的方方面面。比如&#xff0c;淘宝买东西时&#xff0c;需要客服帮你解答疑惑。快递丢失时&#xff0c;需要客服帮忙找回。报名参加培训课程时&#xff0c;需要客服帮忙解答更适合的课程…… 基…

RTE 2024 隐藏攻略

大家好&#xff01;想必今年 RTE 大会议程大家都了解得差不多了&#xff0c;这将是一场实时互动和多模态 AI builder 的年度大聚会。 大会开始前&#xff0c;我们邀请了参与大会策划的 RTE 开发者社区和超音速计划的成员们&#xff0c;分享了不同活动的亮点和隐藏攻略。 请收…

InnoDB 存储引擎<一>InnoDB简介与MySQL存储架构及相关数据结构

目录 回顾MySQL架构 InnoDB简介 ​MySQL存储结构 回顾MySQL架构 对MySQL架构图的总结: MySQL服务器是以网络服务的方式对外提供数据库服务的&#xff0c;我们使用的应用程序以及客户端统称为外部程序。 外部程序通过发送网络请求的方式来连接MySQL服务器&#xff0c;这时首先每…

SpringBoot启动报错java.nio.charset.MalformedInputException: Input length =1

启动springboot项目时&#xff0c;出现了以下报错&#xff1a; defaultPattern_IS_UNDEFINEDdefaultPattern_IS_UNDEFINEDdefaultPattern_IS_UNDEFINEDjava.lang.IllegalStateException: Failed to load property source from location classpath:/application-local.yamlat o…

使用pytest单元测试框架执行单元测试

Pytest 是一个功能强大且灵活的 Python 单元测试框架&#xff0c;它使编写、组织和运行测试变得更加简单。以下是 Pytest 的一些主要特点和优点&#xff1a; 简单易用&#xff1a;Pytest 提供了简洁而直观的语法&#xff0c;使编写测试用例变得非常容易。它支持使用 assert 语…

《皮革制作与环保科技》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《皮革制作与环保科技》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《皮革制作与环保科技》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国轻工业联合会 …

软考:CORBA架构

CORBA过时了吗 CORBA指南 个人小结&#xff1a; IPC&#xff0c;进程间通信&#xff0c;Socket应用在不同机器之间的通信 RPC是一种技术思想而非一种规范 但站在八九十年代的当口&#xff0c;简单来说&#xff0c;就是我在本地调用了一个函数&#xff0c;或者对象的方法&…

uvm_info、uvm_warning,uvm_error、uvm_fatal

1、warning/error/fatal调试语句 调试语句除了uvm_info&#xff0c;UVM内部根据问题的严重性&#xff08;severity&#xff09;由低到高&#xff0c;还引入了uvm_warning/uvm_error/uvm_fatal。 它们也是UVM预定义的宏&#xff0c;格式跟umv_info很像&#xff0c;只是不再需要…

int main(int argc,char* argv[])详解

#include <stdio.h> //argc 是指命令行输入参数的个数; //argv[]存储了所有的命令行参数, //arg[0]通常指向程序中的可执行文件的文件名。在有些版本的编译器中还包括程序文件所在的路径。 //如:"d:\Production\Software\VC_2005_Test\Win32控制台应用程序\Vc_T…

Kafka-Windows搭建全流程(环境,安装包,编译,消费案例,远程连接,服务自启,可视化工具)

目录 一. Kafka安装包获取 1. 官网地址 2. 百度网盘链接 二. 环境要求 1. Java 运行环境 (1) 对 java 环境变量进行配置 (2) 下载完毕之后进行解压 三. 启动Zookeeper 四. 启动Kafka (1) 修改Conf下的server.properties文件&#xff0c;修改kafka的日志文件路径 (2)…

软件分享丨Marktext 编辑器

Marktext是一款开源免费的Markdown编辑器&#xff0c;它具有简洁优雅的界面设计和强大的功能&#xff0c;支持多种Markdown语法&#xff0c;包括表格、流程图、甘特图、数学公式、代码高亮等。Marktext还支持导出HTML和PDF格式的文档&#xff0c;非常适合需要编写Markdown文档的…

sersync实时同步部署案例

目录 sersync介绍 案例信息 操作步骤 服务端部署 客户端部署 创建存储目录 安装sersync 修改配置文件 启动服务 停止服务 测试 sersync介绍 sersync是一个基于inotifyrsync的实时文件同步工具&#xff0c;通过监控目录的变动达到实时同步的目的。 案例信息 拓扑…

【微软商店平台】如何将exe打包上传微软商店

打开微软合作者中心&#xff1a;https://partner.microsoft.com/en-us/dashboard/home点击App and Games板块可以创建项目。 3. 重新生成包含私钥的自签名证书 运行以下命令&#xff0c;确保生成的证书包含私钥&#xff1a; New-SelfSignedCertificate -Type CodeSigning -Su…

Git的初次使用

一、下载git 找淘宝的镜像去下载比较快 点击这里 二、配置git 1.打开git命令框 2.设置配置 git config --global user.name "你的用名"git config --global user.email "你的邮箱qq.com" 3.制作本地仓库 新建一个文件夹即可&#xff0c;然后在文件夹…

从零开始:构建一个高效的开源管理系统——使用 React 和 Ruoyi-Vue-Plus 的实战指南

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

QT界面开发:图形化设计、资源文件添加

设计界面介绍 此时我们创建项目时就可以选择添加UI选项了。 添加完之后&#xff0c;我们可以看到&#xff0c;文件中多出了一个存放界面文件的目录&#xff0c;下面有个.ui的界面文件。甚至pro的项目文件中也会添加一项内容。 我们点击界面文件中的.ui文件&#xff0c;我们可以…

mono源码交叉编译 linux arm arm64全过程

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

有道在线翻译外,这三款翻译工具值得一试!

在众多翻译工具中&#xff0c;有道在线翻译是很多小伙伴都会用的。而市场上当然也有很多好用的翻译工具&#xff0c;这里就来给大家介绍几个&#xff01; 福昕在线翻译 直达链接&#xff1a; fanyi.pdf365.cn/ 操作教程&#xff1a;立即获取 这也是一款在线翻译工具。它以…

解决 VScode 每次打开都是上次打开的文件问题

每次使用 VScode 打开总是上次的文件&#xff0c;可以简单设置即可&#xff0c;记录一下。 VScode Visual Studio Code&#xff08;简称VSCode&#xff09;是一个由微软开发的免费、开源的代码编辑器。它支持多种编程语言&#xff0c;并提供了代码高亮、智能代码补全、代码重构…