C++11(一)

一、简介

  • C++11是C++的第二个主要版本,也是自C++98以来最重要的更新。其中引入了大量的更改来标准化现有实践并改进C++程序员可用的抽象。
  • C++11在2011年8月12日最终获得ISO批准之前,使用了C++0x这个名称,因为它预计在2010年之前发布。从C++03到C++11花了8年时间。所以,它已经成为迄今为止版本之间最长的间隔。从那时起,目前C++每3年定期更新一次。
  • C++11的出现如下,在2003年,C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。
  • 相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛化和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多。
  • C++11增加的语法特性篇幅非常多,本文主要讲解实际中比较实用的语法。

二、统一的列表初始化

1、概念

  • 在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
  • C++11则扩大了用花括号{}括起的列表(列表初始化)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用列表初始化时,可添加等号(=),也可不添加。
  • 创建对象时可以使用列表初始化方式调用构造函数初始化。

2、示例代码

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	int arr1[]{ 1,2,3,4 };
	int arr2[2]{ 0 };
	Test test{ 5,6 };

	int i = 0;
	int j = { 0 };
	int k{ 0 };

	Date d1(2024, 8, 3);
	Date d2 = { 2024,8,3 };
	Date d3{ 2024,8,3 };

	const Date& d4 = { 2024,8,3 };

	Date* p1 = new Date[3]{ d1,d2,d3 };
	Date* p2 = new Date[3]{ {2024,8,1}, {2024,8,2},{2024,8,3} };
	return 0;
}

三、initializer_list

1、概念

  • 此类型用于访问C++初始化列表中的值,该列表是const T类型的元素列表。这种类型的对象由编译器从初始化列表声明中自动构造,初始化列表声明是用大括号括起来的,逗号分隔的元素列表。
  • initializer_list对象会自动构造,就像分配了一个T类型的元素数组一样,使用任何必要的非缩窄隐式转换,将列表中的每个元素复制初始化为数组中的相应元素。
  • initializer_list对象引用此数组的元素,但不包含它们。即复制initializer_liist对象会产生另一个引用相同底层元素的对象,而不是引用它们的新副本。
  • 只接受一个此类参数的构造函数是一种特殊的构造函数,称为initializer_list构造函数。当使用initializer_list构造函数语法时,initializer_list构造器优先于其他构造函数。
  • initializer_list一般是作为构造函数的参数,C++11中的STL有许多容器有用initializer_list作为参数的构造函数,这样初始化容器对象就很方便。
  • initializer_list可以作为赋值运算符重载函数的参数,这样就可以用大括号对对象进行赋值。

2、示意图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、示例代码

int main()
{
	vector<int> v1 = { 1,2,3,4 };
	
	auto il1 = { 17,18,19,20 };
	cout << typeid(il1).name() << endl;

	map<string, string> dict = { {"snow", "雪"}, {"dragon", "龙"}, {"snow dragon", "雪龙"} };
	for (auto& e : dict)
	{
		cout << e.first << ":" << e.second << endl;
	}
	return 0;
}

4、运行结果

在这里插入图片描述

四、声明

1、auto

(1)概念

  • 在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto没什么价值。
  • 在C++11中,废弃了auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

(2)示例代码

int main()
{
	int i = 0;
	auto p = &i;
	map<string, string> dict = { {"snow", "雪"}, {"dragon", "龙"} };
	auto it = dict.begin();

	cout << typeid(p).name() << endl;
	cout << typeid(it).name() << endl;
	return 0;
}

2、decltype

(1)作用

  • 将变量的类型声明为表达式指定的类型。

(2)示例代码

int main()
{
	int i = 1;
	double d = 2.3;
	auto ret = i * d;
	cout << typeid(ret).name() << endl;
	decltype(ret) k;
	cout << typeid(k).name() << endl;
	
	return 0;
}

(3)运行结果

在这里插入图片描述

五、左值引用与右值引用

1、左值与左值引用

  • 左值是一个表示数据的表达式,如变量名或解引用的指针,可以获取它的地址和对它赋值。又如在左值定义时用const修饰符修饰,虽然不能给他赋值,但是可以获取它的地址。另外,左值可以出现在赋值符号的两边。
  • 左值引用是对左值的引用,可以将左值绑定到左值引用上。左值引用通过使用 & 符号声明,并表示对某个具名对象的别名。

2、示例代码

//左值
int* p = new int(1);
int b = 1;
const int c = 2;

//对上面的左值进行引用的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

3、右值与右值引用

  • 右值是一个表示数据的表达式,如字面常量、表达式返回值,函数返回值(不能是左值引用返回)等等。
  • 右值只能出现在赋值符号的右边,不能出现在赋值符号的左边,且不能获取右值的地址。
  • 内置类型的右值称为纯右值,自定义类型的右值称为将亡值。
  • 右值引用是对右值的引用,可以将右值绑定到右值引用上。右值引用是不具名(匿名)变量的别名。
  • 右值引用给右值取别名后,会导致右值被存储到特定位置,即可以取到该位置(右值引用)的地址。如果不想该右值引用被修改,可以使用const修饰它。

4、示例代码

double x = 1.2, y = 3.4;
int&& rr1 = 10;
const double&& rr2 = x + y;

rr1 = 20;
//rr2 = 5.5;		//加const修饰不能修改

int z = 5;
//int&& rr3 = z;
int&& rr4 = move(z);

5、move

(1)函数

在这里插入图片描述

(2)作用

  • 这是一个辅助函数,用于强制对值执行移动语义,即使它们有名称,直接使用返回的值会导致arg被视为右值。
  • 标准库的许多组件实现了移动语义,允许在参数为右值时直接转移对象的资产和属性的所有权,而无需复制它们。
  • 在标准库中,移动意味着被移出的对象处于有效但未指定的状态。这意味着,在这样的操作之后,从对象中移动的值只应被销毁或分配一个新值;否则访问它会产生一个未指定的值。
  • move函数并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。但这个左值需要是将亡值,即它不会再被使用,因为它之前拥有的资源会被转移走。

(3)示例代码

string s1("snowdragon");
string s2("dragonsnow");

s2 = move(s1);
cout << s1 << "\n" << s2 << endl;

(4)运行结果

在这里插入图片描述

6、总结

  • 左值引用只能引用左值,不能引用右值。但用const修饰后的左值引用既可引用左值,也可引用右值。
  • 右值引用只能右值,不能引用左值。但右值引用可以引用move以后的左值。
  • 左值引用和右值引用都是属于引用类型,所以,无论是声明左值引用还是右值引用,都必须立即进行初始化。

六、右值引用和移动语义

1、左值引用的短板

  • 虽然左值引用做参数和返回值都可以提高效率,但是当函数的返回对象是一个局部变量时,这个局部变量出了该函数作用域就会被销毁,不能使用左值引用返回,只能传值返回。
  • 传值返回会导致多次拷贝构造,如果这个传值返回的数据是一个很大的自定义类型,就会导致资源的浪费。

2、移动语义

  • 移动语义是C++中的一个概念,允许程序员显式地指示编译器进行资源的移动而非复制。它通过利用临时对象的右值引用来实现,将对象的状态或所有权从一个对象转移到另一个对象,而不进行内存拷贝。

3、示例代码

namespace snowdragon
{
	class string
	{
	public:
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			cout << "string(char* str) -- 构造" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);

				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s)-- 移动赋值" << endl;
			swap(s);
			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};

	snowdragon::string to_string(int x)
	{
		snowdragon::string ret;
		while (x)
		{
			int val = x % 10;
			x /= 10;
			ret += ('0' + val);
		}

		reverse(ret.begin(), ret.end());

		return ret;
	}
}

void TestMobCon()
{
	snowdragon::string s2;
	s2 = snowdragon::to_string(123456789);
}

4、示意图

在这里插入图片描述

七、万能引用与完美转发

1、万能引用

(1)基本概念

  • 概念:万能引用主要用于模板编程中,它允许函数模板的参数既能接受左值引用,也能接受右值引用。
  • 定义:在模板函数中,当参数类型被声明为 T&& 时,这个参数就被称为万能引用。这里的T是一个模板类型参数,而&&并不直接表示右值引用,而是与T结合形成万能引用的特性。
  • 特性:万能引用可以接受左值或右值作为实参。当传入左值时,它会被视为左值引用;当传入右值时,它会被视为右值引用。这种灵活性使得万能引用在模板编程中非常有用。

(2)用途

  • 实现完美转发:万能引用常与forward一起使用,以实现完美转发。完美转发允许函数模板将其参数以原来的值类别(左值或右值)转发给另一个函数。这在编写泛型代码时非常有用,因为它可以确保参数的类型和值类别在传递过程中保持不变。
  • 提升代码灵活性:通过使用万能引用,模板函数可以更加灵活地处理不同类型的参数,包括左值和右值。这有助于编写更加通用和高效的代码。

(3)万能引用与右值引用的区别

  • 定义不同:万能引用是模板参数的一种特殊用法(T&&),而右值引用是变量类型的一种(X&&,其中X是确定的类型)。
  • 用途不同:万能引用主要用于模板编程中,以实现完美转发等高级功能;而右值引用则主要用于实现移动语义,以提高资源管理的效率。

(4)参数转发属性变化

  • 万能引用只是提供了能够同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都会退化成左值。即传递一个右值作为实参,但在万能引用中用这个右值作为实参转发(调用其他函数)时,它的属性就会变成左值。
  • 万能引用的右值引用参数属性改变是因为右值不能被修改,但是右值被右值引用后,如果需要实现移动构造和移动赋值,被转移资源的对象又是自定义类型的,此时就需要转发的右值引用的属性是左值。

2、完美转发

(1)概念

  • 完美转发(Perfect Forwarding)是C++11中引入的一种重要的编程技术和概念,主要用于在泛型编程中确保参数能够以最准确、最高效的方式传递给其他函数。其核心思想是在传递参数时,不仅要准确地转发参数的值,还要保持参数的左值(lvalue)或右值(rvalue)属性不变。

(2)函数

在这里插入图片描述

(3)目的

  • 在C++中,传统的函数参数传递方式在处理右值(如临时对象)时往往会导致不必要的拷贝操作,降低了程序的性能。完美转发的目的是通过优化参数传递机制,避免这种不必要的拷贝,从而提升程序的执行效率。

3、示例代码

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t)
{
	//Fun(t);
	//Fun(move(t));
	Fun(forward<T>(t));
}

int main()
{
	int a;
	const int b = 8;

	PerfectForward(10);           // 右值

	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

4、运行结果

  • 以下为依次放开PerfectForward中注释后的运行结果。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

八、后续文章

本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请点赞、收藏加关注支持一下💕💕💕

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值