c++11 新特性

文章目录

    • 一、列表初始化
    • 1 C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。
    • 2 多个对象的列表初始化
    • 二、变量类型推导
    • 1 auto
    • 2 decltype 类型推导
    • 三、基于范围for的循环
    • 四、final和override
    • 五、委派构造函数
    • 六、默认函数控制
    • 1 显式缺省函数
    • 2 删除默认函数
    • 七、右值引用
    • 1 移动语义
    • 2 C++中的右值
    • 3 右值引用
    • 4 std::move()
    • 5 移动语义中要注意的问题

一、列表初始化

1 C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。

int array1[] = {1,2,3,4,5,6,7,8};
int array2[] = {0};

对对于一些自定义类型,却不行.

vector<int> v{1,2,3,4,5,6,7,8};//在C++98中这样无法通过编译,因此需要定义vector之后,在使用循环进行初始赋值。

C++11扩大了用初始化列表的使用范围,让其适用于所有的内置类型和自定义类型,而且使用时,=可以不写

// 内置类型
int x1 = {10};
int x2{10}
// 数组
int arr1[5] {1,2,3,4,5}
int arr2[]{1,2,3,4,5};
// 标准容器
vector<int> v{1,2,3}
map<int,int> m{{1,1},{2,2}}
// 自定义类型
class Point{
	int x;
	int y;
}
Point p{1,2};

2 多个对象的列表初始化

给类(模板类)添加一个带有initializer_list类型参数的构造函数即可支持多个对象的,列表初始化.

#include
template<class T>
class Vector{
public:
	Vecto(initializer_list<T> l)
		:_capacity(l.size())
		,_size(0){
			_array = new T[_capacity];
			for(auto e : l)
				_array[_size++] = 3;
		}
private;
	T* _array;
	size_t _capacity;
	size_t _size;
};

二、变量类型推导

1 auto

在C++中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,简化程序的书写

auto a; // 错误,auto是通过初始化表达式进行类型推导,假设没有初始化表达式,就无法确定a的类型
auto p = new foo() // 对自己定义类型进行类型推导

// 不使用auto需要写很长的迭代器的类型  类模板中使用
map<string,string> m;
map<string,string>::iterator it1 = m.begin();
// 使用auto就很简单
auto it2 = m.begin();

2 decltype 类型推导

从变量或者表达式中获得类型。但是有些场景可能需要根据表达式运行后的结果进行类型推导。

 	double tempA = 3.0;
    const double ctempA = 5.0;
    const double ctempB = 6.0const double *const cptrTempA = &ctempA; 
    //const xx *const代表xx类型常量指针,指向的内容也是xx类型常量,指针与内容皆为常量不可更改
    
    /*1.dclTempA推断为const double(保留顶层const,此处与auto不同)*/
    decltype(ctempA) dclTempA = 4.1;
    /*2.dclTempA为const double,不能对其赋值,编译不过*/
    dclTempA = 5;
    /*3.dclTempB推断为const double * const*/
    decltype(cptrTempA) dclTempB = &ctempA;
    /*4.输出为4(32位计算机)和5*/
    cout<<sizeof(dclTempB)<<"    "<<*dclTempB<<endl;
    /*5.保留顶层const,不能修改指针指向的对象,编译不过*/
    dclTempB = &ctempB;
    /*6.保留底层const,不能修改指针指向的对象的值,编译不过*/
    *dclTempB = 7.0;

三、基于范围for的循环

map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};
for (auto p : m){
    cout<<p.first<<" : "<<p.second<<endl;
}

通常的用法

vector<int> v{1,2,3,4,5};
for(const auto& e : v)
	cout<<e<<' ';
cout<<endl;

四、final和override

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

运行下面这段代码:会报错:“Car::Drive”: 声明为“final”的函数无法被“Benz::Drive”重写

//1、final修饰基类的虚函数不能被派生类重写
#include
using namespace std;

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

需要重写我们需要加override 来强制重写

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

五、委派构造函数

委派构造函数可以通过委派其它构造函数,使多构造函数的类编写更加容易
c++11 新特性_第1张图片

#include 
#include 
#include 
#include 


//委派构造函数的使用,类似与构造基类
class Info{
public:
	Info(){
		init();
	}
	//Info(int _age) : Info(), mName("qwe") //无法编译通过,不能同时委派和初始化成员,如果需要初始化成员,必须放在构造函数体中
	Info(int _age) : Info(){
		mAge = _age;
		printf("--- one param mAge:%d\n", mAge);
	}
	Info(int _age, std::string _name) : Info(_age){
		mName = _name;
		printf("--- two param mName:%s\n", mName.c_str());
	}
private:
	void init(){
		mHeight = 1.23f;
		printf("--- init mheight:%f\n", mHeight);
	}
	int mAge;
	std::string mName;
	float mHeight;
};

void testDelegatingConstructor(){
	Info info(77, "yang");
	/*
	--- init mheight:1.230000
	--- one param mAge:77
	--- two param mName:yang
	请按任意键继续. . .
	*/
}

int main(){
	testDelegatingConstructor();
	return 0;
}

六、默认函数控制

在C++中对于空类,编译器会生成一些默认的成员函数,如果在类中显式定义了,编译器就不会重新生成默认版本。但是如果在一个类中声明了带参的构造函数,如果又需要定义不带参的实例化无参的对象。这时候编译器是有时生成,有时不生成,就会造成混乱,C++11可以让程序员自己控制是否需要编译器生成。

1 显式缺省函数

在C++11中,可以在默认函数定义或声明时加上=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;

2 删除默认函数

要想限制一些默认函数的生成,在C++98中,可以把该函数设为私有,不定义,这样,如果有人调用就会报错。在C++11中,可以给该函数声明加上=delete就可以。

class A{
A(int a):_a(a){}
A(constA&) = delete;  // 禁止编译器生成默认的拷贝构造函数
private:
	int _a;
};

七、右值引用

1 移动语义

class String{
public:
	String(char* str = '"){
		if(str == nullptr)
			_str = "";
		_str = new char[strlen(str)+1];
		strcpy(_str,str);
	}
	String(const String& s):_str(new char[strlen(c._str)+1]){
		strcpy(_str,s._str);
	}
	~String(){
		if(_str)
			delete[] _str;
	}
private:
	char* _str;
};

String GetString(char* pStr){
	String strTemp(pStr);
	return strTemp;
}
	
int main(){
	String s1("hello");
	String s2(GetString("world"));
	return 0;
}

移动语义:将一个对象资源移动到另一个对象中的方式,在C++中要实现移动语义,必须使用右值引用

2 C++中的右值

右值引用,顾名思义就是对右值的引用。在C++中右值由纯右值和将亡值构成。

  • 纯右值:用于识别变量和一些不跟对象关联的值。比如:常量、运算符表达式等、
  • 将亡值:声明周期将要结束的对象。比如:在值返回时的临时对象

3 右值引用

格式:类型&& 应用变量名字 = 实体;
使用场景:

  • 1、与移动语义相结合,减少必须要的资源的开辟,提高运行效率
String&& GetString(char* pStr){
	String strTemp(pStr);
	return strTemp;
}
	
int main(){
	String s1("hello");
	String s2(GetString("world"));
	return 0;
}
  • 2、给一个匿名对象取别名,延长匿名对象的生命周期
String GetString(char* pStr) {
	return String(pStr);
}
int main(){
	String&& s = GetString("hello");
return 0; }

注意

  • 右值引用在定义时必须初始化
  • 右值引用不能引用左值
int a = 10;
int&& a1;  // 未初始化,编译失败
int&& a2 = a;   // 编译失败,a是一个左值
// 左值是可以改变的值

4 std::move()

C++11中,std::move()函数位于头文件中,它可以把一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。该转化不会对左值产生影响.
注意:其更多用在生命周期即将结束的对象上。

5 移动语义中要注意的问题

  • 1、在C++11中,无参构造函数/拷贝构造函数/移动构造函数实际上有三个版本
Object()
Object(const T&)
Object(T&&)
  • 2、如果将移动构造函数声明为常右值引用或者返回右值的函数声明为常量,都会导致移动语义无法实现
String(const String&&);
const String GetString();
  • 3、C++11默认成员函数,默认情况下,编译器会隐士生成一个移动构造函数,是按照位拷贝来进行。因此在涉及到资源管理时,最好自己定义移动构造函数。
class String
{
public:
	String(char* str = "")
	{
		if(str == nullptr)
			str = "";
		_str = new char[strlen(str)+1];
		strcpy(_str,str);
	}
	//  拷贝构造
	// String s(左值对象)
	String(const String& s)
		:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str,s_str);
		}
	//  移动构造
	// String s(将亡值对象)
	String(String&& s)
		:_str(nullptr)
		{
			swap(_str,s._str);
		}
	// 赋值
	String&  operator=(const String& s)
	{
		if(this != &s)
		{
			char* tmp = new char[strlen(s._str)+1];
			stcpy(tmp,s._str);
			delete[] _str;
			_str = tmp;
		}
		return *this;
	}
	// 移动赋值
	String& operator=(String&& s)
	{
		swap(_str,s._str);
		return *this;
	}
	~String()
	{
		if(_str)
			delete[] _str;
	}
	// s1 += s2  体现左值引用,传参和传值的位置减少拷贝
	String& operator+=(const String& s)
	{
		// this->Append(s.c_str());
		return *thisl
	}
	// s1 + s2
	String operator+(const String& s)
	{
		String tmp(*this);
		// tmp.Append(s.c_str());
		return tmp;
	}
	const char* c_str()
	{
		return _str;
	}
private:
	char* _str;
};
int main()
{
	String s1("hello");  // 实例化s1时会调用移动构造
	String s2("world");
	String ret 
	ret = s1 + s2   // +返回的是临时对象,这里会调用移动构造和移动赋值,减少拷贝
	
	vector<String> v;
	String str("world");
	v.push_back(str);  // 这里调用拷贝构造函数
	v.push_back(move(str));  // 这里调用移动构造,减少一次拷贝
	return 0;
}

c++11 新特性_第2张图片
总结:

  • 左值:可以改变的值;
  • 右值: 不可以改变的值(常量,表达式返回值,临时对象)
  • 左值引用: int& aa = a; 在传参和传值的位置使用,减少拷贝,提高效率
  • 右值引用: int&& bb = 10; 在传值返回和将亡值传参时,通过调用移动构造和移动赋值,减少拷贝,提高效率。
  • const 左值引用可以引用右值
  • 右值引用可以应用move后的左值

参考链接:https://blog.csdn.net/a15920804211/article/details/90691525
参考链接:https://blog.csdn.net/aixintianshideshouhu/article/details/94548940

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