C++11新特性

1. 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能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅 功能更强大,而且能提升程序员的开发效率。

2. 统一的初始化

2.1 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 自定义类型的列表初始化

 1. 标准库支持单个对象的列表初始化

class Point 
{ 
public:
	Point(int x = 0, int y = 0)
		: _x(x)
		, _y(y)  
	{}   
private:  
	int _x;   
	int _y; 
};

int main()
{
	Point p{ 1, 2 };
	return 0;
}

 2. 多个对象的列表初始化

    多个对象想要支持列表初始化,需给该类(模板类)添加一个带有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; 
};

3. 变量类型推导

3.1 为什么需要类型推导

在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要某些实际类型怎么给,或者类型写起来特别复杂,比如:

#include <map>
#include <string>
#include <iostream>
using namespace std;

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 = m.begin();
	while(it != m.end())    
	{       
		cout<<it->first<<" "<<it->second<<endl;   
		++it;   
	}      
	return 0; 
}

 C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁。

3.2.2 decltype

decltype是根据表

达式的实际类型推演出定义变量时所用的类型,比如:

 1. 推演表达式类型作为变量的定义类型

#include <iostream>
#include <typeinfo>
using namespace std;

int main() {
	int a = 10; 
	int b = 20;     
	// 用decltype推演a+b的实际类型,作为定义c的类型   
	decltype(a+b) c;  
	cout<<typeid(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; 
}

4. 范围for循环

4.1 范围for的语法

在C++98中如果要遍历一个数组,可以按照下面的方式进行:

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 }; 
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) 
		array[i] *= 2;      
	for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
		cout << *p << endl;
}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“:”分为两部分;第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

void TestFor() 
{ 
	int array[] = { 1, 2, 3, 4, 5 }; 
	for (auto& e : array)    
		e *= 2; 
	for (auto e : array)  
		cout << e << " ";       
	return 0;
}

 注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

4.2:范围for的使用条件

    1.for循环迭代的范围必须是确定的

     对于数组而言,就是数组中第一个元素和最后一个元素的范围,对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围

     注意:以下代码就有问题,因为for的范围不确定 

void TestFor(int array[]) 
{ 
	for (auto& e : array)
		cout << e << endl; 
}

     2.迭代的对象要实现++和==操作。

5. final与override

C++11提供override 和 final 来修饰虚函数。
实际中我们建议多使用纯虚函数+ overrid的方式来强制重写虚函数,因为虚函数的意义就是实现多态,如果 没有重写,虚函数就没有意义

// 1.final 修饰基类的虚函数不能被派生类重写
class Car
{ 
public: 
	virtual void Drive() final {}
};

class Benz :public Car 
{
public:
	virtual void Drive()
	{ 
		cout << "Benz-舒适" << endl;
	} 
};

 

class Car
{ 
public: 
	virtual void Drive(){} 
}; 
// 2.override 修饰派生类虚函数强制完成重写,如果没有重写会编译报错 
class Benz :public Car 
{ 
public: 
	virtual void Drive() override 
	{
		cout << "Benz-舒适" << endl;
	} 
};

6. 委派构造函数

6.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中无法解决。

6.2 委派构造函数

所谓委派构造函数:就是指委派函数将构造的任务委派给目标构造函数来完成的一种类构造方式。

class Info{
public:  
	// 目标构造函数   
	Info()   
		: _type(0)  
		, _a('a')
	{     
		InitRSet(); 
	}      
	// 委派构造函数   
	Info(int type)   
		: Info() 
	{       
		_type = type;    
	}     
	// 委派构造函数
	Info(char a)    
		: Info() 
	{       
		_a = a; 
	}    
private:
	void InitRSet()
	{      
		//初始化其他变量  
	} 
private:  
	int _type =  0; 
	char _a = 'a'; 
	//...
};

 在初始化列表中调用基本版本的构造函数称为委派构造函数,而调用的基本版本则称为目标构造函数

注意:构造函数不能同时委派和使用初始化列表。

7. 默认函数控制

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

7.1 显式缺省函数

在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; 
}

7.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; 
}

注意:避免删除函数和explicit一起使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值