C++进阶:C++11

列表初始化

在C++98中,我们可以使用花括号对数组元素进行统一的列表初始值设定,例如:

int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[5] = { 0 };

对于一些自定义类型,无法使用花括号进行初始化,例如:

vector<int> v{ 1, 2, 3, 4 };

无法通过编译,导致每次定义vector时,都需要先把vector定义出来,然后使用循环对其赋初始值,非常不方便。C++11扩大了用花括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

  • 内置类型的列表初始化
    //内置类型列表初始化
	int x1 = { 10 };
	int x2{ 10 };
	int x3 = 1 + 2;
	int x4 = { 1 + 2 };
	int x5{ 1 + 2 };
	//数组
	int arr1[5]{1, 2, 3, 4, 5};
	int arr2[]{1, 2, 3, 4, 5};
	//动态数组,C++98中不支持
	int* arr3 = new int[5]{1, 2, 3, 4, 5};
	//标准容器
	vector<int> v{ 1, 2, 3, 4, 5 };
	map<string, string> m{ { "one", "1" }, { "two", "2" } };
  • 自定义类型列表初始化
    1、标准库支持单个对象的列表初始化
    例如:
class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d2{ 2019, 11, 22 };
	return 0;
}

2、多个对象的列表初始化
多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_list类型参数的构造函数即可。注意:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size();
例如:

#include <initializer_list>
namespace Daisy
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
	public:
		vector()
		{
			_start = _finish = endofstorage;
		}
		vector(const initializer_list<T>& il)
			:_start(new T[il.size()])//开辟空间
		{
			auto it = il.begin();
			_finish = _start;
			while (it != il.end())
			{
				*_finish++ = *it;//放元素
				++it;
			}
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		iterator _start;
		iterator _finish;
		iterator endofstorage;
	};
}
int main()
{
	Daisy::vector<int> v1;
	Daisy::vector<int> v2{ 1, 2, 3, 4, 5 };
	//范围for是为了用户使用方便
	//但是编译器最终会将范围for转换为迭代器形式,加上迭代器的操作
	for (auto e : v2)
		cout << e << " ";
	cout << endl;
	return 0;
}
变量类型推导
  • 为什么需要类型推导
    例如:
#include <string>
int main()
{
	short a = 32670;
	short b = 32670;
	short c = a + b;
	map<string, string> m{ { "apple", "苹果" }, { "banana", "香蕉" } };
	map<string, string>::iterator it = m.begin();
	while (it != m.end())
	{
		cout << it->first << " " << it->second;
		++it;
	}
	cout << endl;
	return 0;
}

其中c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存在问题;使用迭代器遍历容器, 迭代器类型太繁琐,可以使用auto关键字,C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁;但是在有些情况下,是不能使用auto的,例如:

template < class T1, class T2>
auto Add(const T1& left, const T2& right)//不知道该返回T1还是T2
{
	return left + right;
}
//使用auto报错,因为编译器在编译期间会进行替换,但是此时不知道替换成什么类型
int main()
{
	return 0;
}

此时我们就不知道该返回什么类型,因为auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。这时候需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力。
因此我们需要 decltype

  • decltype
    根据表达式的实际类型推演出定义变量时所用的类型,例如:
    1、推演表达式类型作为变量的定义类型
int main()
{
	short a = 32760;
	short b = 32760;
	decltype(a + b)c;
	cout << typeid(c).name()<< endl;
	return 0;
}

2、推演函数返回值的类型

void TestFunc(int)
{}
void(*set_malloc_handler(void(*f)()))()
{
	return nullptr;
}
typedef decltype(set_malloc_handler) SH;
int main()
{
	//没有带参数,推演函数类型
	cout << typeid(SH).name() << endl;
	//带参数,推演函数调用类型
	cout << typeid(decltype(set_malloc_handler(nullptr))).name() << endl;
	return 0;
}

结果是:
在这里插入图片描述
可以看出函数调用类型是一个函数指针

  • 返回值类型追踪
    例如:
template <class T1,class T2>
auto Add(const T1& left, const T2& right)->decltype(left + right)
{
	return left + right;
}
int main()
{
	cout << typeid(Add(1, 2.0)).name() << endl;
	return 0;
}

由于在编译期间,left和right的类型不知道,因此将类型推演放在后面,使用->来进行指向,然后函数返回值是auto(语法规定);

默认函数控制

在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成

  • 显式缺省函数
    在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。
    例如:
class Date
{
public:
	Date() = default;
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	return 0;
}
  • 删除默认函数
    如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
    总结:什么情况下编译器一定会生成默认的构造函数
    (1)如果类中定义了其他类类型对象,一定生成
    (2)在继承体系中,如果基类定义了无参的构造函数,派生类没有定义任何构造函数,编译器会给派生类生成一个无参的构造函数(作用:要在生成的无参构造函数初始化列表位置调用基类构造函数)
    (3)如果类中定义了虚函数,如果没有显式定义任何构造函数,由于要在构造函数中放虚表指针,要生成构造函数&#
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值