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

相关文章

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性: