详解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
    评论
### 回答1: 11-tie源码是一个用C语言实现的简单且高效的哈希表结构,可以用来实现键-值对的存储和查找。以下是对11-tie源码的详细解释。 11-tie源码主要由三个关键部分组成:哈希表结构、哈希函数和碰撞解决方法。 首先是哈希表结构。11-tie源码中使用了一个固定大小的数组作为哈希表来存储键-值对。数组的大小由用户在创建哈希表时指定,并具有较好的素数特性,以减少碰撞的发生。哈希表中的每个元素(bucket)是一个指向键-值对链表的指针。如果出现碰撞,新的键-值对将被添加到链表的头部。 然后是哈希函数。11-tie源码中使用了一个简单且高效的哈希函数,它会根据键的特征将其映射到数组的索引位置。哈希函数使得不同的键被均匀地分布在数组中,从而减少碰撞的发生。该哈希函数通常基于键的类型和特性,但也可以根据特定需求进行自定义。 最后是碰撞解决方法。当多个键映射到数组的同一个索引位置时,就会发生碰撞。11-tie源码中使用了链表来解决碰撞问题。当发生碰撞时,新的键-值对将被添加到链表的头部。这种解决方法简单且有效,但当哈希表中的元素数量较大时,链表的遍历会导致性能下降。 总结起来,11-tie源码是一个使用C语言实现的简单高效的哈希表结构。通过哈希函数将键映射到数组的索引位置,使用链表解决碰撞问题。这种结构可以用来存储和查找键-值对,适用于快速查询和插入数据的场景。 ### 回答2: c 11 tie 源码详解是指对 C++ 11 中的 `std::tie` 函数进行解析。`std::tie` 是一个模板函数,用于将多个值绑定到一个元组中。 `std::tie` 的源码实现如下: ```cpp namespace std { template <typename... Types> tuple<Types&...> tie(Types&... args) noexcept { return tuple<Types&...>(args...); } } ``` `std::tie` 函数是一个模板函数,接受任意数量的参数,并将这些参数作为引用传递给 `std::tuple`,然后返回这个 `std::tuple`。 `std::tuple` 是一个模板类,用于保存一组不同类型的值。`std::tuple<Types&...>` 的含义是保存参数 Types&... 的引用。 利用 `std::tie` 函数,可以将多个变量绑定到一个 `std::tuple` 中,并且可以通过解构绑定的方式获取这些变量。 例如,假设有两个变量 `int a` 和 `double b`,可以使用 `std::tie` 将它们绑定到一个元组中,并通过解构绑定方式获取它们的值: ```cpp int a = 1; double b = 2.0; std::tuple<int&, double&> t = std::tie(a, b); std::get<0>(t) = 10; std::get<1>(t) = 20.0; std::cout << a << ", " << b << std::endl; ``` 在上面的代码中,通过 `std::tie(a, b)` 将变量 `a` 和 `b` 绑定到一个元组 `t` 中,然后通过 `std::get<0>(t)` 和 `std::get<1>(t)` 获取元组中第一个和第二个值,并将它们分别赋值为 10 和 20.0。最后输出结果为 `10, 20`。 `std::tie` 的源码实现简单明了,通过将多个参数作为引用传递给 `std::tuple`,实现了将多个变量绑定到一个元组中的功能。这个功能在一些情况下非常方便,可以减少代码的复杂性和重复性。 ### 回答3: c 11 tie 是 C++ 11 标准中新增的一个标准库函数,用于将多个输出流(ostream)绑定到一个流对象上。通过将多个输出流绑定在一起,可以在输出时同时向多个流对象输出数据,提高代码的易读性和简洁性。 使用 c 11 tie 首先需要包含 `<tuple>` 头文件,并且可以接受任意个数的流对象作为参数。例如 `std::tie(stream1, stream2)` 表示将 stream1 和 stream2 绑定在一起。 在绑定之后,输出到绑定对象的数据会自动发送到所有绑定的流对象中。例如 `std::cout << "Hello World";`,如果之前使用 `std::tie(std::cout, fileStream)` 进行了绑定,那么输出的 "Hello World" 既会在控制台上显示,也会同时写入到文件流对象中,实现了同时输出到两个流对象的效果。 需要注意的是,绑定只在绑定操作发生时生效,之后对流对象的修改不会影响绑定。因此,如果在绑定之后修改了流对象,需要重新进行绑定操作。 c 11 tie 的使用可以简化代码,提高开发效率。通过同时输出到多个流对象,可以实现在不同目的地同时记录相同的输出信息,提供了一种方便的日志记录功能。此外,绑定的流对象可以是任意的输出流,不限于标准输出流和文件流,也可以是用户自定义的流对象。 总结来说,c 11 tie 是 C++ 11 标准中新增的一个标准库函数,用于将多个输出流绑定在一个流对象上,实现同时输出到多个流对象的功能。它提高了代码的可读性和简洁性,并且可以应用于日志记录等多种场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值