本章将开始学习C++11的新语法特性,主要是一些比较常用的语法
在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定
int array1[] = {1,2,3,4,5};
int array2[5] = {0};
注:对于一些自定义的类型,却无法使用这样的初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加
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;
}
注:列表初始化可以在{}之前使用等号,其效果与不使用=没有什么区别
class Pointer
{
public:
Pointer(int x = 0, int y = 0) : _x(x), _y(y)
{}
private:
int _x;
int _y;
};
int main()
{
Pointer p{ 1, 2 };
//等同于与调用构造函数
//Pointer p( 1, 2 );
return 0;
}
多个对象想要支持列表初始化,需要实现initializer_list类型参数的构造函数
#include <initializer_list>
template<class T>
class Vector {
public:
// ...
Vector() : _capacity(0), _size(0){}
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) {
delete[] _array;
size_t i = 0;
for (auto e : l)
_array[i++] = e;
return *this;
}
// ...
private:
T* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Vector<int> v1{ 1,2,3,4 };
Vector<int> v2;
v2 = { 1,2,3,4,5 };
return 0;
}
注:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()**、**end()迭代器以及获取区间中元素个数的方法size()
在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂
void test1()
{
short a = 32670;
short b = 32670;
// c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存在问题
short c = a + b;
cout<<c<<endl;
}
void test2()
{
std::map<std::string, std::string> m{
{"apple", "苹果"}, {"banana","香蕉"}
};
// 使用迭代器遍历容器, 迭代器类型太繁琐
std::map<std::string, std::string>::iterator it = m.begin();
while(it != m.end())
{
cout<<it->first<<" "<<it->second<<endl;
++it;
}
}
C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁
void test3()
{
short a = 32670;
short b = 32670;
// c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存在问题
auto c = a + b;
cout<<c<<endl;
std::map<std::string, std::string> m{
{"apple", "苹果"}, {"banana","香蕉"}
};
// 使用迭代器遍历容器, 迭代器类型太繁琐
auto it = m.begin();
while(it != m.end())
{
cout<<it->first<<" "<<it->second<<endl;
++it;
}
}
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
return left + right;
}
注:如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错,但这需要程序运行完才能知道结果的实际类型,即RTTI(Run-Time Type Identification 运行时类型识别)
注:运行时类型识别的缺陷是降低程序运行的效率
decltype是根据表达式的实际类型推演出定义变量时所用的类型
int main()
{
int a = 10000000000000;
int b = 10000000000000;
// 用decltype推演a+b的实际类型,作为定义c的类型
decltype(a + b) c;
cout<<typeid(c).name()<<endl;
return 0;
}
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;
}
在 C++98/03 中,不同的容器和数组遍历的方式不尽相同,写法不统一,也不够简洁,而 C++11 基于范围的 for 循环可以以简洁、统一的方式来遍历容器和数组,用起来也更方便了
int main(void)
{
vector<int> v = { 1, 2, 3, 4, 5, 6 };
for (auto it = v.begin(); it != v.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
for (auto& value : v)
{
cout << value << " ";
}
cout << endl;
return 0;
}
for (declaration : expression)
{
// 循环体
}
declaration 表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression 是要遍历的对象,它可以是
表达式
、容器
、数组
、初始化列表
等
C++ 中增加了 final 关键字来限制某个类不能被继承,或者某个虚函数不能被重写。如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面
如果使用 final 修饰函数,只能修饰虚函数,这样就能阻止子类重写父类的这个函数了:
class Base
{
public:
virtual void test() final
{
cout << "Base class..."<<'\n';
}
virtual void test2()
{
cout << "Base class test2..."<<'\n';
}
};
class Child : public Base
{
public:
//带有final, 无法重写
void test()
{
cout << "Child class..." << '\n';
}
void test2()
{
cout << "Child class test2..." << '\n';
}
};
使用 final 关键字修饰过的类是不允许被继承的,也就是说这个类不能有派生类
class Base final
{
public:
virtual void test()
{
cout << "Base class..." << '\n';
}
virtual void test2()
{
cout << "Base class test2..." << '\n';
}
};
class Child : public Base
{
public:
void test()
{
cout << "Child class..." << '\n';
}
void test2()
{
cout << "Child class test2..." << '\n';
}
};
override 关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数。这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,和 final 一样这个关键字要写到方法的后面
class Base
{
public:
void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() override
{
cout << "Child class...";
}
};
注:使用了 override 关键字之后,假设在重写过程中因为误操作,写错了函数名或者函数参数或者返回值编译器都会提示语法错误
在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数
class A
{
public:
A(int a) : _a(a)
{}
// 显式缺省构造函数,由编译器生成
A() = default;
// 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
A& operator=(const A& a);
private:
int _a;
};
A& A::operator=(const A& a) = default;
int main()
{
A a1(10);
A a2;
a2 = a1;
return 0;
}
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;
}