详解C++11特性(上)

1. 简介

C++11是C++语言的一个重要版本,于2011年发布。它引入了许多新的特性和改进,使得C++语言更加现代化和强大。所以属性C++11的新特性是很有必要去熟悉的。

2. 列表初始化

2.1 { }初始化

相信大家在初始化数组和结构体的时候,都使用过{}进行初始化

struct Date
{
	int y;
	int m;
	int d;
};
int main()
{
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = { 0 };
	Date d = { 2024, 3,31 };
	return 0;
}

到了C++11,{}初始化的范围变大了,使其可用于所有的内置类型和用户自定义的类型。

class Person
{
public:
	Person(string name,string gender,int age)
		:_name(name)
		,_gender(gender)
		,_age(age)
	{}
private:
	string _name;
	string _gender;
	int _age;
};
int main()
{
	//不推荐
	int a = { 1 };

	vector<Person> v = { {"张三","男",20},{"李四","女",20}};
	map<string, int> m = { {"苹果",1},{"香蕉",2} };
	return 0;
}

2.2 initializer_list

其实支持这种操作的原因就是initializer_list的引入,可以把initializer_list看出一个模版类,{}就可以看作是initializer_list类,所以C++11的STL容器的构造都加了以initializer_list为参数的构造函数。
在这里插入图片描述

3. 类型推导

3.1 decltype

decltype 是 C++ 中的一个关键字,用于在编译时获取表达式的类型,而不会实际评估该表达式。它通常用于模板元编程中,在表达式的类型复杂或未知的情况下非常有用。

int main() {
    int x = 5;
    decltype(x) y = 10;  // y 的类型与 x 相同,即 int
    std::cout << "y 的类型: " << typeid(y).name() << std::endl;

    double z = 3.14;
    decltype(z) w = 2.71;  // w 的类型与 z 相同,即 double
    std::cout << "w 的类型: " << typeid(w).name() << std::endl;

    return 0;
}

在这里插入图片描述

4. 一些新增的容器

在这里插入图片描述

5. final和override

  1. final修饰的类不能被继承。
  2. final修饰的虚函数不能被重写。
  3. override会检测派生类虚函数是否重写了基类的虚函数,如果没有重写就报错。

6. 右值引用

6.1 什么是左值、左值引用?

左值是一个表示数据的表达式,出现在“=”的左边,可以对非const的左值赋值,对左值还可以取地址,左值引用就是左值的别名。

int main()
{
	//左值
	int a = 10;
	int* p = &a;
	const int b = 20;
	//左值引用
	int& a1 = a;
	int*& p1 = p;
	const int& b1 = b;

	return 0;
}

6.2 什么是右值、右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引
用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址
。右值引用就是对右值的引用,给右值取别名。

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = 1;
	int b = 2;
	//右值
	10;
	a + b;
	add(a , b);
	//右值引用
	int&& r1 = 10;
	int&& r2 = a + b;
	int&& r3 = add(a, b);

	//右值不能取地址
	&10;
	&(a + b);
	&add(a, b);
	return 0;
}

在这里插入图片描述

6.3 左值引用和右值引用

左值引用总结:

  1. 左值引用一般不能引用右值
  2. const 左值引用可以引用右值

右值引用总结:

  1. 右值引用一般不能引用左值
  2. 右值引用可以引用move之后的左值

在这里插入图片描述

6.4 右值引用的场景和意义

我们先来回忆一下左值引用的作用,作为函数的参数时,可以减少拷贝,对于一些自定义的对象来说可以有效的提高程序效率,作为函数的返回值,如果返回值是自己开辟的空间,也能减少一次拷贝。
但是左值引用有个短板,就是如果返回值是个局部变量,出了作用域系统就自动释放,那么我们只能传值返回了。所以右值引用的出现可以弥补这个问题。我们通过代码来看看是如何解决的。

没使用右值引用的情况:

namespace lbs
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

		// 拷贝构造 -- 左值
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

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

		// 拷贝赋值
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		 移动赋值
		//string& operator=(string&& s)
		//{
		//	cout << "string& operator=(string&& s) -- 移动拷贝" << endl;
		//	swap(s);

		//	return *this;
		//}

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

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};
}
lbs::string to_string(int value)
{
	bool flag = true;
	if (value < 0)
	{
		flag = false;
		value = 0 - value;
	}

	lbs::string str;
	while (value > 0)
	{
		int x = value % 10;
		value /= 10;

		str += ('0' + x);
	}

	if (flag == false)
	{
		str += '-';
	}


	std::reverse(str.begin(), str.end());
	return str;
}

int main()
{
	lbs::string s = to_string(1234);
	return 0;
}

在这里插入图片描述
这就是我们上面说的左值引用的缺陷,现在我们加上移动构造来看看右值引用是怎么解决这个问题的。

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

// 移动赋值
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动拷贝" << endl;
	swap(s);

	return *this;
}

在这里插入图片描述
在这里插入图片描述
可以看到移动构造只是把将亡值str的资源使用swap函数交换给了s,所以效率是很高的。完美解决了左值引用的缺陷。

6.5 完美转发

6.5.1 模版中的万能引用

我们先来看一段代码

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);
}
int main()
{
    PerfectForward(10);           // 右值
    int a;
    PerfectForward(a);            // 左值
    PerfectForward(std::move(a)); // 右值
    const int b = 8;
    PerfectForward(b);      // const 左值
    PerfectForward(std::move(b)); // const 右值

    return 0;
}

大家可以先预想一下结果。
在这里插入图片描述
是不是很奇怪,为什么全是左值引用?我们先来解释一下万能引用模版

  1. 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
  2. 你传左值,参数就是左值,你传右值就是右值
  3. 因为传参的时候发生了引用折叠 (& &&-> &) (&& && -> &&)

既然这样,那为什么右值变成了左值,这是因为,当右值引用了右值以后就变成了一个左值
在这里插入图片描述
为什么要有这个特性?因为根据右值的特性,右值是不能改变的,但我们的移动构造需要交换资源,这不就矛盾了,所以有了这个特性。那么该如何解决上面代码的问题呢?这时候完美转发就登场了。

6.5.2 完美转发的使用

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。

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(std::forward<T>(t));
}
int main()
{
    PerfectForward(10);           // 右值
    int a;
    PerfectForward(a);            // 左值
    PerfectForward(std::move(a)); // 右值
    const int b = 8;
    PerfectForward(b);      // const 左值
    PerfectForward(std::move(b)); // const 右值

    return 0;
}

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

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值