列表初始化
在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)如果类中定义了虚函数,如果没有显式定义任何构造函数,由于要在构造函数中放虚表指针,要生成构造函数&#