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)

补充:模板的概念是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 会被默认初始化为 0std::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" 也是直接构造,但本质是 构造 + 拷贝构造,只是编译器将它们优化为了直接构造(便于用户使用),我们将这样的优化称为单参数的构造函数支持隐式类型转换(所谓的隐式类型转换即将一个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;
}

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

基本概念: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;
}

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

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~ 

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

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

相关文章

认识和使用 Vite 环境变量配置,优化定制化开发体验

Vite 官方中文文档&#xff1a;https://cn.vitejs.dev/ 环境变量 Vite 内置的环境变量如下&#xff1a; {"MODE": "development", // 应用的运行环境"BASE_URL": "/", // 部署应用时使用的 URL 前缀"PROD": false, //应用…

国外媒体软文发稿-引时代潮流-助力跨国企业蓬勃发展

大舍传媒&#xff1a;开疆拓土&#xff0c;引领传媒新潮流 随着全球经济的一体化和信息技术的高速发展&#xff0c;跨国企业在国际市场上的竞争越来越激烈。这也给跨国企业带来了巨大的机遇和挑战。在这个时代背景下&#xff0c;大舍传媒凭借其独特的优势和创新的服务模式&…

市值超越苹果,英伟达的AI崛起与天润融通的数智化转型

Agent&#xff0c;开启客户服务新时代。 世界商业格局又迎来一个历史性时刻。 北京时间6月6日&#xff0c;人工智能芯片巨头英伟达&#xff08;NVDA&#xff09;收涨5.16%&#xff0c;总市值达到3.01万亿美元&#xff0c;正式超越苹果公司&#xff0c;成为仅次于微软&#xf…

IDEA启动项目报java.lang.OutOfMemoryError: GC overhead limit exceeded

idea编译项目时报j ava.lang.OutOfMemoryError: GC overhead limit exceeded错误&#xff0c;教你两步搞定&#xff01; 第一步&#xff1a;打开help -> Edit Custom VM Options ,修改xms和xmx的大小&#xff0c;如下图&#xff1a; 第二步&#xff1a;File -> Settings…

python tushare股票量化数据处理:笔记

1、安装python和tushare及相关库 matplotlib pyplot pandas pandas_datareader >>> import matplotlib.pyplot as plt >>> import pandas as pd >>> import datetime as dt >>> import pandas_datareader.data as web 失败的尝试yf…

C++入门 string(1)

目录 string类简介 string类的常用接口说明 string类对象的常见构造 string类对象的访问及遍历操作 operator[ ] begin end rbegin rend string类简介 string是表示字符串的字符串类该类的接口与常规容器的接口基本相同&#xff0c;再添加了一些专门用来操作string的…

打造你的博客帝国:DjangoBlog带你飞向国际舞台!

你的网站加载速度还在慢如蜗牛吗&#xff1f;将为你揭开网站速度提升的神秘面纱。从缓存策略到数据库优化&#xff0c;再到高效的代码实践&#xff0c;我们深入探讨了如何让DjangoBlog飞速运行。不仅如此&#xff0c;我们还提供了实用的监控和日志管理技巧&#xff0c;确保你的…

Nginx高级配置及重写功能

文章目录 一、高级配置网页的状态页Nginx第三方模块变量访问日志Nginx压缩功能https功能自定义小图标 二、重写功能&#xff08;rewrite&#xff09;if指令return指令set指令break指令rewrite指令防盗链 一、高级配置 网页的状态页 状态页显示的是整个服务器的状态而非虚拟主…

怎么取消Intellij IDEA中的项目和Git仓库的关联

这篇文章分享一种最简单的方法&#xff0c;取消已经开启代码控制的项目与git代码仓库的关联。 打开项目的位置&#xff0c;然后点击文件管理器上方的查看选项卡&#xff0c;勾选【隐藏的项目】。 删除.git文件夹 然后可以看到项目的文件夹下显示了一个隐藏的.git文件夹&#x…

【Python教程】3-控制流、循环结构与简单字符串操作

在整理自己的笔记的时候发现了当年学习python时候整理的笔记&#xff0c;稍微整理一下&#xff0c;分享出来&#xff0c;方便记录和查看吧。个人觉得如果想简单了解一名语言或者技术&#xff0c;最简单的方式就是通过菜鸟教程去学习一下。今后会从python开始重新更新&#xff0…

武忠祥17堂课没必要全听,这几个才是精华!

作者&#xff1a;Captain 链接&#xff1a;https://www.zhihu.com/question/381665751/answer/3197724055 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 17堂课类似于习题课&#xff0c;是专题训练 17堂课省略了…

SpringMVC[从零开始]

SpringMVC SpringMVC简介 1.1什么是MVC MVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分 M:Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据 JavaBean分为两类&#xff1a; 一类称为实体类Bean&#xff1a;专…

李廉洋:6.10黄金原油非农之后,今日行情走势分析策略。

黄金消息面分析&#xff1a;即美联储不会在短期内以降息的方式出手纾困。该报告确实将首次降息的可能性推迟到了12月&#xff0c;但股市的反应不像多数交易商预期的那样。股市非但没有崩盘&#xff0c;反而随着交易员逢低买入而大幅反弹。很明显&#xff0c;市场完全专注于盈利…

Java(十七)---ArrayList的使用

文章目录 前言1.ArrayList的简介2. ArrayList使用2.1.ArrayList的构造2.2.ArrayList的扩容机制(JDK17) 3.ArrayList的常见操作4. ArrayList的具体使用4.1.[杨辉三角](https://leetcode.cn/problems/pascals-triangle/description/)4.2.简单的洗牌游戏 5.ArrayList的问题及思考 …

【数据结构】AVL树(平衡二叉树)

目录 一、AVL树的概念二、AVL树的节点三、AVL树的插入四、AVL树的旋转1.插入在较高左子树的左侧&#xff0c;使用右单旋2.插入在较高右子树的右侧&#xff0c;使用左单旋3.插入较高左子树的右侧&#xff0c;先左单旋再右单旋4.插入较高右子树的左侧&#xff0c;先右单旋再左单旋…

【Python核心数据结构探秘】:元组与字典的完美协奏曲

文章目录 &#x1f680;一、元组⭐1. 元组查询的相关方法❤️2. 坑点&#x1f3ac;3. 修改元组 &#x1f308;二、集合⭐1. 集合踩坑❤️2. 集合特点&#x1f4a5;无序性&#x1f4a5;唯一性 ☔3. 集合&#xff08;交&#xff0c;并&#xff0c;补&#xff09;&#x1f3ac;4. …

如何为色盲适配图形用户界面

首发日期 2024-05-25, 以下为原文内容: 答案很简单: 把彩色去掉, 测试. 色盲, 正式名称 色觉异常. 众所周知, 色盲分不清颜色. 如果用户界面设计的不合理, 比如不同项目只使用颜色区分, 而没有形状区分, 那么色盲使用起来就会非常难受, 甚至无法使用. 色盲中最严重的情况称为…

电阻常见失效模式

电阻常见失效模式&#xff1a; 电阻器由于结构较为简单&#xff0c;工艺成熟&#xff0c;通常失效率相对较低。器失效主要表现为以下几种&#xff1a; 阻值漂移&#xff1a;老化后通常发生&#xff1b;&#xff08;通过老化试验进行筛选&#xff0c;规避该问题&#xff09; …

Golang Context详解

文章目录 基本介绍context源码剖析Context接口emptyCtxcancelCtxtimerCtxvalueCtx context使用案例协程取消超时控制数据共享 基本介绍 基本介绍 在Go 1.7版本中引入了上下文&#xff08;context&#xff09;包&#xff0c;用于在并发编程中管理请求范围的数据、控制生命周期、…