1.初始化列表
2.1 C++98中{}的初始化问题
在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:
int array1[] = {1,2,3,4,5};
int array2[5] = {0};
对于一些自定义的类型,却无法使用这样的初始化。比如:
vector<int> v{1,2,3,4,5};
就无法通过编译,导致每次定义vector时,都需要先把vector定义出来,然后使用循环对其赋初始值,非常 不方便。C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定 义的类型,使用初始化列表时,可添加等号(=),也可不添加。
2.2内置类型的列表初始化
int main() {
// 内置类型变量
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<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};
return 0;
}
注意:列表初始化可以在{}之前使用等号,其效果与不使用=没有什么区别。
2.3 自定义类型的列表初始化
- 标准库支持单个对象的列表初始化
class Point
{
public:
Point(int x = 0,int y = 0)
:_x(x)
,_y(y)
{}
private:
int _x;
int _y;
} ;
int main()
{
Ponint p{1,2};
return 0;
}
- 多个对象的列表初始化
多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_list(初始值设定项列表)类型参数的构造函数即 可。注意:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器 以及获取区间中元素个数的方法size()。
#include<initializer_list>
template<class T>
class Vector
{
public:
//...
Vector(initializer_list<T> l)
:_capacity(l.size())
,size(0)
{
_array = new T[_capacity];
for(auto e:l)
_array[_size++] = e;
}
Vector<T>& operator=(initializer_list<T> l)
{
size_t i = 0;
for(auto e:l)
_array[i++] = e;
return *this;
}
//...
private:
T* _array;
size_t _capacity;
size_t _size;
};
2.变量类型推导
2.1 为什么需要变量类型推导
在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎 么给,或者类型写起来特别复杂,比如:
#include<map>
#include<string>
int main()
{
short a = 32670;
short b = 32670;
//c如果给成short,会造成数据丢失,如果能让编译器根据a+b的结果推导c实际的类型,就不会存在问题
short c = a + b;
std:: map<std::string,std::string> m{{"apple","苹果"},{"banana","香蕉"} };
//使用迭代器遍历容器,迭代器类型太繁琐
std::map<std::string,std::string>::iterator it = begin();
while(it != m.end())
{
cout<<it->first<<" "<<it->second<<endl;
++it;
}
return 0;
}
C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方 便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁。
2.2 decltype类型推导
2.2.1 为什么需要decltype
auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有 时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就 无能为力。
tempalte<class T1,class T2>
T1 Add(const T1& left,const T2& right)
{
return left+ right;
}
如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错,但这需要程序运行完才能知道结果的实际类型,即RTTI(Run-Time Type Identification 运行时类型识别)。
C++98中确实已经支持RTTI:
- typeid(以字符串形式返回类型)只能查看类型不能用其结果类定义类型
- dynamic_cast只能应用于含有虚函数的继承体系中
运行时类型识别的缺陷是降低程序运行的效率。
2.1.2 decltype
decltype是根据表达式的实际类型推演出定义变量时所用的类型,比如:
1. 推演表达式类型作为变量的定义类型
int main()
{
int a = 10;
int b = 20;
//用decltype推演a+b的实际类型,作为定义c的类型。
decltype(a+b) c ;
cout<<typid(c).name()<<endl;
return 0;
}
2. 推演函数返回值的类型
void* GetMemory(size_t size)
{
return malloc(size);
}
int main()
{
// 如果没有带参数,推导函数的类型
cout << typeid(decltype(GetMemory)).name() << endl;
// 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
cout << typeid(decltype(GetMemory(0))).name() <<endl;
return 0;
}
运行结果:
3.基于范围for的循环
采用c++11新特性中的基于范围for循环,不必去操心数组越界(边界)问题,因此非常的方便,特别是在项目开发中。
语法形式:
for(declaration:expression)
{
statement
}
其中:
expression部分表示一个对象,用于表示一个序列。
declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。通常该变量的类型由auto进行推导,更加的方便简洁。
每次迭代,declaration部分的变量会被初始化为expression部分的下一个
元素值。
例1:
int main()
{
int a[] = {1,2,3,4,5};
for(int e: a)
{
cout<<e<<endl;
}
return 0;
}
示例2:
若迭代器变量的值希望能够在for中被修改,可以采用引用&的方式;
int main()
{
int a[] = {1,2,3,4,5};
for(int& e: a)
{
e = 0;
cout<<e<<" ";
}
cout<<endl;
for(int i = 0;i<5;i++)
cout<<a[i]<<" ";
cout<<endl;
return 0;
}
结果:
实例3.对于STL标准模板库也同样适用,这里就不进行举例了。
3.2基于范围的for循环特点
(1)和普通循环一样,也可以采用continue跳出循环的本次迭代。
(2)用break来终止整个循环
3…3基于范围的for循环使用的要求及依赖条件
(1)for循环迭代的范围是可以确定的;如数组的第一个元素和最后一个元素便构成了for选好的迭代范围。
(2)对于用户自定义的类,若类中定义便实现的有begin、end函数,则这个begin、end便是for循环的迭代范围。
(3)基于范围的for循环要求迭代器的对象实现:++ ==等操作符。
(4)对于STL标准模板库中(如:vector,set,list,map,queue,deque,string等)的各种容器使用“基于范围的for循环”是不会有
任何问题的,因为这些容器中都定义了相关操作。
4.final与override
final 用于标志一个最终类,该类不能被继承。
override 标志一个函数,该函数必须重写父类的一个虚函数。
5.委派构造函数
5.1 构造函数冗余造成重复
委派构造函数也是C++11中对C++的构造函数的一项改进,其目的也是为了减少程序员书写构造函数的时 间。通过委派其他构造函数,多构造函数的类编写更加容易。
class Info
{
public:
Info()
:_type(0)
,name('a')
{
InitRSet();
}
Info(int type)
:_type(type)
,name('a')
{
InitRSet();
}
Info(char a)
:_type(0)
,name(a)
{
InitRSet();
}
private:
void InitRSet(){
//初始化其他变量
}
private:
int _type;
char _name;
//...
};
述构造函数除了初始化列表不同之外,其他部分都是类似的,代码重复。
初始化列表可以通过:类内部成员初始化进行优化,但是构造函数体的重复在C++98中无法解决。
能否将:构造函数体中重复的代码提出来,作为一个基础版本,在其他构造函数中调用呢?
5.2委派构造函数
所谓委派构造函数:就是指委派函数将构造的任务委派给目标构造函数来完成的一种类构造的方式
class Info
{
public:
//目标构造函数
Info()
:_type(0)
,name('a')
,InitRSet()
{
}
//委派构造函数
Info(int type)
:Info()
{
_type = type;
}
//委派构造函数
Info(char a)
:Info()
{
_char = a;
}
private:
void InitRSet(){
//初始化其他变量
}
private:
int _type;
char _name;
//...
};
在初始化列表中调用”基准版本”的构造函数称为委派构造函数,而被调用的”基准版本”则称为目标构造函数。
6.默认函数控制
在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构 函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生 成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程 序员可以控制是否需要编译器生成。
class A
{
public:
A(int _a)
:a(_a)
{}
private:
int a;
};
int main()
{
//调用有参数的构造函数
A a1(10);
//调用无参的构造函数,原本编译器自己默认可以生成,因为我们定义了构造函数,因此编译器不再生成默认构造函数,以至于编译出错。
A b2 ;
return 0;
}
编译结果:
6.1 显式缺省函数
在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版 本,用=default修饰的函数称为显式缺省函数。
class A
{
public:
A(int _a)
:a(_a)
{}
A() = default;
private:
int a;
};
int main()
{
//调用有参数的构造函数
A a1(10);
//调用无参的构造函数
A b2 ;
return 0;
}
6.2删除默认函数
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他 人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对 应函数的默认版本,称=delete修饰的函数为删除函数。
class A
{
public:
A(int a)
:_a(a)
{}
//进制编译器生成默认的拷贝构造函数以及赋值运算符的重载
A(const A& ) = delete;
A& operator=(const A&) = delete;
private:
int _a;
};
int main()
{
A a1(10);
//编译失败,因为该类没有拷贝构造函数
//A a2(a1);
//编译失败,因为该类没有赋值运算符的重载
A a3(20);
a3 = a2;
return 0;
}