C++11实用新特性

文章目录

  • 列表初始化
  • 变量类型推导
    • auto
    • decltype
  • for循环
  • final和override
  • 委派构造
  • 默认函数控制

列表初始化

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

如果不太理解,直接看代码:

#include
#include
#include
#include
#include
using namespace std;
class Person{
public:
	Person(string name,int age)
		:name_(name)
		, age_(age)
	{
	}
private:
	string name_;
	int age_;
};

int main()
{
	Person p{ "张威", 20 };
	vector<int> vec{ 1, 2, 3, 4, 5 };
	map<string, string> dict{ { "insert", "插入" }, { "left", "左边" } };
	set<int> se{ 5, 6, 7, 3, 8 };
	return 0;
}
  • C++98只支持单参数隐式转化,C++11支持多参数隐式转化。Person p的构造就是这一性质的体现。
  • C++11的支持列表初始化的模板类都存在使用initializer_list类型参数的构造函数。

使用Vector模拟实现C++11的initializer_list类型参数的构造函数。

#include 
#include
using namespace std;
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;
};
int main()
{
	Vector<int> v{ 1, 2, 3, 4 };
}

{}实际构造了一个initializer_list类型的临时对象,而C++容器中存在initializer_list类型参数的构造函数。

使用列表初始化优点

  1. 简单方便
  2. 放置类型变窄
int main()
{
	const int x = 10;
	const int y = 1024;
	char c1{ x }; // 可以通过编译
	//char c2{ y }; // 无法通过编译,y的高字节丢失
	return 0;
}

变量类型推导

参见:https://blog.csdn.net/y1196645376/article/details/51441503
有时候我们并不确定一个对象的类型,有时候类型过于复杂繁琐,写起来比较冗长。

#include 
#include 
#include
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;
}

auto

auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。

  • auto的作用
#include
#include
using namespace std;

int main()
{
	int i = 3;
	auto a = i, &b = i, *c = &i;//正确: a初始化为i的备份,b初始化为i的引用,c为i的指针。
	//auto sz = 0, pi = 3.14;//错误,两个变量的类型不一样。
	cout << typeid(a).name()<<endl << typeid(b).name()<<endl << typeid(c).name() << endl;
	system("pause");
}
int
int
int *
请按任意键继续. . .

auto一般会忽略掉顶层const,但底层const会被保留下来。
所谓顶层和底层实际就是值和地址的区别,如果auto是通过取地址推导,那么新对象仍然有const属性,如果auto通过对象推导,那么新对象丢失const属性。

#include
#include
using namespace std;

int main()
{
	int i = 0;
	const int ci = i, &cr = ci;  //ci 为整数常量,cr 为整数常量引用 
	auto a = ci;     // a 为一个整数, 顶层const被忽略
	auto b = cr;     // b 为一个整数,顶层const被忽略
	auto c = &ci;    // c 为一个整数指针.
	auto d = &cr;    // d 为一个指向整数常量的指针(对常量对象区地址是那么const会变成底层const)
	cout << typeid(a).name() << endl << typeid(b).name() << endl;
	cout << typeid(c).name() << endl << typeid(d).name() << endl;
	system("pause");
}

C++11实用新特性_第1张图片

decltype

1、 有的时候我们还会遇到这种情况,我们希望从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。decltype是根据表达式的实际类型推演出定义变量时所用的类型。

decltype(f()) sum = x;// sum的类型就是函数f的返回值类型。

在这里编译器并不实际调用f函数,而是分析f函数的返回值作为sum的定义类型。如果不给sum初始值,sum就是未初始化的变量。
2、decltype保持引用属性,auto丢失引用属性。

	int i = 42, *p = &i, &r = i;
	decltype(r) y1 = i;       //因为 r 为 int& ,所以 y1 为int&
	auto y2 = r;              //因为 r 为 int& ,但auto会忽略引用,所以 y2 为int
 
	decltype(*p) h1 = i;      //这里 h1 是int&, 原因后面讲
	auto h2 = *p;             // h2 为 int.

3.、如果e是一个左值,则decltype(e)为T的引用,否则decltype(e)为T

#include
#include
using namespace std;

int main()
{
	int j = 20;
	decltype(j=30) x = j;
	j++;
	cout << j <<endl<< x << endl;
	system("pause");
}

C++11实用新特性_第2张图片
auto通过变量的初始值来推断变量的类型,auto会忽略掉顶层const, 保留底层const,如果变量初始值是引用对象,auto会忽略引用作用。
decltype 用于从表达式的类型推断出要定义的变量的类型 不论是顶层const还是底层const, decltype都会保留。

auto decltype
原对象对新对象初始化 只是通过表达式推导类型,不实际计算表达式,需要手动初始化
原对象的引用属性auto会忽略引用属性 不会丢失,原对象是引用类型,新对象就是引用类型
丢失顶层const属性 不会丢失const属性

for循环

#include
#include
using namespace std;
int main()
{
	vector<int> ret{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	for (auto& e : ret)
		cout << e << endl;
	system("pause");
	return 0;
}
#include
#include
using namespace std;
int main()
{
	vector<int> ret{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	for (size_t i = 0; i < ret.size(); i++)
		cout << ret[i] << endl;
	system("pause");
	return 0;
}

临时变量e和i都只在for循环的{}内是有效的,方便使用。

final和override

类被final修饰,该类为最终类,无法被继承

class A1 final { };
class B1 : A1 { }; // “B1”: 无法从“A1”继承,因为它已被声明为“final”

虚函数被final修饰,该虚函数为最终函数,不能被重写。

class A1
{
	virtual void func() final {}
};

class B1 : A1
{
	virtual void func() {} //“A1::func”: 声明为“final”的函数无法被“B1::func”重写
};

override放在子类虚函数之后,检查重写

struct A1
{
	virtual void func1(int) {}
};

struct B1 : A1
{
	virtual void func1(int) override {} // OK
};

委派构造

所谓委派构造函数:就是指委派函数将构造的任务委派给目标构造函数来完成的一种类构造的方式。
注意:构造函数不能同时”委派”和使用初始化列表。
注意:如果有多个委派构造函数,不能形成环状委托

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

默认函数控制

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

你可能感兴趣的:(C++)