零、前言
本章将开始学习C++11的新语法特性,主要是一些比较常用的语法
一、C++11简介
-
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名 -
不过由于TC1主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准 -
从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。 -
相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率
二、列表初始化
在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定
int array1[] = {1,2,3,4,5};
int array2[5] = {0};
注:对于一些自定义的类型,却无法使用这样的初始化
1、内置类型列表初始化
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};
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、自定义类型列表初始化
- 标准库支持单个对象的列表初始化
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 };
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()
三、变量类型推导
1、auto类型推导
在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂
void test1()
{
short a = 32670;
short b = 32670;
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;
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;
}
}
2、decltype类型推导
- auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型
- 但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
return left + right;
}
注:如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错,但这需要程序运行完才能知道结果的实际类型,即RTTI(Run-Time Type Identification 运行时类型识别)
- typeid只能查看类型不能用其结果类定义类型
- dynamic_cast只能应用于含有虚函数的继承体系中
注:运行时类型识别的缺陷是降低程序运行的效率
decltype是根据表达式的实际类型推演出定义变量时所用的类型
- 推演表达式类型作为变量的定义类型
int main()
{
int a = 10000000000000;
int b = 10000000000000;
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;
}
四、范围for循环
在 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 是要遍历的对象,它可以是 表达式 、容器 、数组 、初始化列表 等
五、final和override
1、final
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:
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';
}
};
2、override
override 关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数。这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,和 final 一样这个关键字要写到方法的后面
class Base
{
public:
void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() override
{
cout << "Child class...";
}
};
注:使用了 override 关键字之后,假设在重写过程中因为误操作,写错了函数名或者函数参数或者返回值编译器都会提示语法错误
六、默认成员函数控制
- 在C++中对于空类编译器会生成一些默认的成员函数,如果在类中显式定义了,编译器将不会重新生成默认版本
- 有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成
- 显式缺省函数
在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;
}
- 删除默认函数
- 如果能想要限制某些默认函数的生成,在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;
}
注:避免删除函数和explicit一起使用
能想要限制某些默认函数的生成,在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;
}
注:避免删除函数和explicit一起使用
|