C++11,{}初始化,initializer_list,decltype,右值引用,类和对象的补充

2024-05-07 02:12

本文主要是介绍C++11,{}初始化,initializer_list,decltype,右值引用,类和对象的补充,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

c++98是C++标准委员会成立第一年的C++标准,C++的第一次更新是C++03,但由于C++03基本上是对C++98缺陷的修正,所以一般把C++98与C++03合并起来,叫做C++98/03;

后来原本C++委员会更新的速度预计是5年更新一次,但由于C++标准委员会的进度,吃吃未更新,直到2011年C++11的出现使得C++的功能出现了大的提升,所以我们需要进行学习,提高我们的编码效率;

{}初始化

在c++11中,我们的初始化有更多的方式,{}这个符号也可以进行数据的初始化,这是在C++98中没有的;

//										测试C++11{}符号初始化
int main()
{int a = 10;int b = { 10 };int c{ 10 };vector<int> v{ 1,2,3,4,5 };int* pa = { &a };int arr[]{ 1,2,3,4 };return 0;
}

这些都是可以使用{}花括号来进行初始化的;

我们自己实现的类使用{}也会自动调用构造函数来进行初始化:

class A {
public:A(string str)//单参数构造参数可以自动进行类型转化:_a(str){cout << "A() 构造函数" << endl;}
private:string _a;
};
int main()
{A a{ "123" };return 0;
}

 看自动调用了构造函数:

我们使用的时候用起来非常方便但是出现这样的使用情况的时候是不是会有点奇怪,这是怎么构造的:

list<int> l{1,2,3,4,5,6,7}; 

奇怪了,为什么这样也可以进行构造,如果是调用普通的构造函数,我们传递这么多参数,难道构造函数有这么多参数吗,而且参数的个数还是不确定的;

所以为了解决这个疑惑引入一个新的概念:

initializer_list 模板类

这是一个C++的模板类,这个类可以用来接收{}中的数据,构造出一个装载了{}中类型数据的list;所以说我们在使用{}进行初始化容器时,如果容器无法直接调用构造函数初始化,就会尝试先将数据转换为initializer_list然后再将initializer_list中的数据交给容器的构造函数来进行构造;

我们看容器的构造函数就可以看到容器是适配了一个参数是initializer_list构造函数的

基本上在C++11中所有的容器都是实现了这样的构造来适配{}进行初始化的;

map<int,string> m{ { 123 , "abc" } , { 456 , "def" } };

它们都是适配了initializer_list的;

我们自己实现的list也需要再多实现一个构造这样我们也能支持{}的初始化了:

		list(initializer_list<int> lt){list_emptyinit();for (auto& e: lt){push_back(e);}}list<int> l{ 1,2,3,4,5,6 };

decltype 

 这个关键字可以用来获取我们的表达式结果类型并返回此类型:

自动推导出了类型vector中的数据类型为double;

C++11增加的容器:

unordered_map   ,unordered_set    ,array,   forward_list; (记之以忘)

右值引用

左右值简介

在c++中有右值与左值之分,一开始就是是用在赋值符号的作用来作为区分的;但是现在对于左右值不能这么区分;现在应该如此区分:可以取地址的就是左值,不可取地址的为右值;

右值一般为将亡值,就是即将被释放的数据,(但有时候我们用户强制在转换的右值可能不一定是将亡值)

左右值可以相互转换(但有条件)

//												右值引用
int main()
{string a = "hello";string b = " world!";//左值引用string& refa = a;const string& refab = a + b;//可以使用const来接收右值(因为临时对象具有常性)//右值引用string&& rrefb = move(b);//可以使用move函数将左值转换为右值来接收接收string&& rrefab = a + b;list<int> l{ 1,2,3,4,5,6 };list<int> l1 = move(l);//拷贝构造时右值的数据会被转移走return 0;
}

这里的l不但不是将亡值而且还是左值,但是因为move将它转换为了右值,使得他的数据被转移走了;

所以右值的转换要慎用; 

左值:就是我们常见的普通数据(不过多讲解)

右值:

1.  将亡值    

2.(我们不需要再使用的数据)可以将它转换为右值提供给某个接口或者资源,进行资源转移;

移动构造函数:

在容器中,为了减少拷贝带来的消耗,如果传递给容器的值是右值则会调用移动构造来进行拷贝,依此来提高效率

//								我发现编译器对于拷贝构造进行了很多的优化,有时候并不会调用拷贝构造
struct A
{string _str;A(const string& str):_str(str){cout << "构造函数A(const string& str)" << endl;}A():_str(""){cout << "构造函数A()" << endl;}A(const A& a):_str(a._str){cout << "深拷贝A(const A& a)" << endl;}A(A&& a){swap(_str, a._str);cout << "移动构造A(A&& a)" << endl;(void)a;}~A(){}
};int main()
{string s("hello");A a(s);//普通构造函数A c(a);//深拷贝
//A b(A());//这样会被编译器翻译为一个函数的声明,声明了一个叫b的函数,返回值类型为A,参数为返回值为A的无参函数
//A b(A{});//本来要调用移动构造的但是,由于编译器优化,直接进行了一次构造A b(move(a));//移动构造return 0;
}

这里由于a是右值,所以a的资源会被转移给,b使得b不用进行深拷贝(再开辟一片空间出来复制一份a的数据);

这样可以大大的提高拷贝的效率;这就是右值的运用;

运用场景

编译器未优化前:

左值拷贝时的场景:

看这就是深拷贝的场景,因为这样的情况,为了解决这样低效的拷贝,右值引用应运而生;

右值拷贝的场景:

 如果我们使用右值引用来接收这个返回值v

编译器优化后:

左值引用:

右值引用:

 移动语义

为什么说这里的&和&&标志可以省略呢,因为,前面我们说了拷贝构造和移动构造的出现使得我们的拷贝有了两种方式,1.是进行深拷贝,2.是进行移动构造;而这两种构造的方式是通过不同的参数来调用的,编译器会匹配最适合的函数让参数进入;而我们将&和&&标志省略后,编译器判断出这个返回值是局部变量他是右值,匹配最合适的移动构造来进行拷贝,这就是移动语义;当然如果没有移动构造,那么就只能匹配普通的构造函数来来构造了,移动语义也没办法去调用不存在的移动构造函数;

下面是我将移动构造加入我自己实现的strin类中,从而实现移动构造的场景的代码:

模拟实现stl容器/模拟实现string类 · future/my_road - 码云 - 开源中国 (gitee.com)

 完美转发

有的时候,我们的右值因为多次的传递它不再是右值了,这个时候我们的移动语义就无法被触发,从而无法调用移动构造;

//												完美转发void func(const string& a)
{cout << "左值" << endl;
}void func(string && a)
{cout<<"右值" << endl;
}template<class T>
void test1(T&&a)
{//func(a);//a引用接收右值之后,他自己便成为了左值func(forward<T>(a));//完美转发,保持a右值的状态
}void test()
{string a;test1(a);test1(move(a));
}int main()
{test();return 0;
}

 在模板中肯定会有这种多层传递的出现,一个右值传递给了右值引用,此时的右值引用是左值,因为它将右值存储器起来了,这个右值引用可以取地址了;

所以为了避免多次传递时右值被转换为了左值,我们需要使用forward<T>()这个函数来保持原有类型状态,保持它的右值的状态;我们需要在每一层都使用forward<T>来保证右值的传递,直到最后一层的右值传递给移动构造即成功完成传递;

模板中的&&

在模板中&&可以即代表左值又代表右值叫做(万能引用),编译器会根据传递给接口的参数类型来进行推到的;

push_back(T&&data)

data为左值时->T&

data为右值时->T&&

对于匿名对象的说明 

//									测试临时对象返回
struct A
{int _a ;int _b ;A(const A& a){_a = a._a;_b = a._b;//noting}//A &()(int a,int b)A(int a,int b):_a(a),_b(b){	}
};
A getA()
{A a(10, 20);return a;
}
int main()
{const A& b = getA();string("11111111111");
}

我们可以使用引用来接收匿名对象/临时对象时,匿名/临时对象会延长生命周期至b的生命周期,直到b死亡这份空间中的资源才会被释放;

如果我们修改返回值为引用:

A &()(int a,int b)

{

        return A;

那么这个时候返回的值指向的时被释放的内存的局部变量A;如果我们在继续增加栈帧就会覆盖掉原来的数据从而会影响b引用的数据:

 类和对象的补充

在C++98中有6个默认函数,到了c++11后增加移动构造和移动赋值两个默认成员函数;

默认生成的移动构造和移动赋值:

当我们没有实现析构,拷贝构造,赋值构造三个中的任意一个默认函数时,编译器会自动生成一个移动构造函数,这个移动构造函数会自动的调用成员的移动构造,如果成员没有移动构造则调用拷贝构造函数(移动赋值构造是同样的原理);

我们可以看下面的代码:

//					证明移动构造的默认生成
class B {
public:B(){}B(const char* str):_str(str){}B(const B& b):_str(b._str){cout << "(const B& b)深拷贝" << endl;}B(B&& b){_str.swap(b._str);cout << "B(B&& b)移动构造" << endl;}
private:string _str;
};class A {
public:A(const B& data):_data(data){}//~A()//只要写了一个除构造外的默认函数就会导致默认移动构造的删除//{}private:B _data;
};int main()
{B b("hello");A a(b);A a1(a);//深拷贝A a2(move(a));//移动构造我们并没有实现,但是还是调用了B的移动构造,说明默认生成了A a3 = move(a2);//移动赋值也是默认生成了的(b没有实现赋值调用了)return 0;
}

现象: 

 

 当写了析构函数的时候:

 

内置类型的初始化(缺省参数)

在C++11中补齐了前面C++98中,只有内置类型会自动调用自己的构造函数缺陷,在下面函数定义的位置可以设置缺省参数来初始化成员变量;

default关键字 

这个关键字可以生成默认的拷贝构造,赋值构造,移动构造,移动赋值函数(暂时我只知道这四个函数默认生成的情况,其他如果还有我们再慢慢补充)

生成场景,就拿上面的默认移动构造的生成来举例,假设我们自己实现了一个析构函数(一般自己实现说明是深拷贝,但我们这里只是为了产生现象所以写一个,但是内部不写任何代码)

class B {
public:B(){}B(const char* str):_str(str){}B(const B& b):_str(b._str){cout << "(const B& b)深拷贝" << endl;}B(B&& b){_str.swap(b._str); cout << "B(B&& b)移动构造" << endl;}
private:string _str = "hello";
};class A {
public:A(const B& data):_data(data){}~A()//只要写了一个除构造外的默认函数就会导致默认移动构造的删除{}A(const A& a) = default;//让编译器默认生成A(A&& a) = default;//让编译器默认生成private:B _data;
};int main()
{B b("hello");A a(b);A a1(a);//深拷贝A a2(move(a));//移动构造我们并没有实现,但是还是调用了B的移动构造,说明默认生成了A a3 = move(a2);//移动赋值也是默认生成了的(b没有实现赋值调用了)return 0;
}

看编译器删除了默认构造后又生成了,这就是default的作用;

delete关键字 

这是用来禁止生成默认函数的,C++98中是将那个不许生成的默认函数作为private成员这样一旦调用此函数就会报错,但是在C++11中,直接使用=delete,即可完成禁止生成的操作;

class B {
public:B(){}B(const char* str):_str(str){}B(const B& b):_str(b._str){cout << "(const B& b)深拷贝" << endl;}B(B&& b){_str.swap(b._str); cout << "B(B&& b)移动构造" << endl;}
private:string _str = "hello";
};class A {
public:A(const B& data):_data(data){}//~A()//只要写了一个除构造外的默认函数就会导致默认移动构造的删除//{}//A(const A& a) = default;//让编译器默认生成//A(A&& a) = default;//让编译器默认生成A(const A& a) = delete;//阻止编译器默认生成A(A&& a) = delete;//阻止编译器默认生成
private:B _data;
};int main()
{B b("hello");A a(b);A a1(a);//深拷贝A a2(move(a));//移动构造我们并没有实现,但是还是调用了B的移动构造,说明默认生成了A a3 = move(a2);//移动赋值也是默认生成了的(b没有实现赋值调用了)return 0;
}

委托构造 

在C++11中,我们还可以在一个构造函数中去调用另一个构造函数;这之前只有在继承的时候,子类需要显示的调用父类的带参构造去初始化父类的变量;而C++11可以通过在初始化列表中显示的委托构造调用自己的构造函数;

class A {
public:A(){cout << "A()" << endl;}A(int a):A()//委托构造{cout << "A(int a)"<<endl;_a = a;}
private:int _a;
};
int main()
{A a(10);return 0;
}

 现象:

 

这篇关于C++11,{}初始化,initializer_list,decltype,右值引用,类和对象的补充的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/966036

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给