【C++】C++11-新的类功能和可变参数模板

news2024/9/24 0:58:44

1、新的类功能

1.1 默认成员函数

原来C++类中,有6个默认成员函数:
构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址重载、const取地址重载

C++11增加了两个:移动构造函数、移动赋值运算符重载

自己实现这两个函数在上一篇文章中已经写过,现在来了解一下编译器默认生成的这两个函数是怎么样的,以及在上面条件之下编译器会自动生成

1. 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝(值拷贝),自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
2. 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
3. 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

为了演示,需要使用自己实现的string

string.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace cxf
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		string(const char* str = "");
		~string();
		string(const string& s);
		string(string&& s);
		string& operator=(const string& s);//传统写法的赋值运算符重载
		//string& operator=(string s);//现代写法的赋值运算符重载
		string& operator=(string&& s);

		//迭代器
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//插入删除函数
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos, size_t len = npos);

		//relational operators
		bool operator<(const string& s) const;
		bool operator==(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>(const string& s) const;
		bool operator>=(const string& s) const;
		bool operator!=(const string& s) const;

		//其他函数
		void swap(string& s);
		const char* c_str() const;
		int size() const;
		int capacity() const;
		char& operator[](size_t i);
		const char& operator[](size_t i) const;
		size_t find(char ch, size_t pos = 0) const;
		size_t find(const char* str, size_t pos = 0) const;
		void clear();
	private:
		char* _str = nullptr;
		int _size;
		int _capacity;
		const static size_t npos;
	};
	//输入输出函数
	std::ostream& operator<<(std::ostream& out, const string& s);
	std::istream& operator>>(std::istream& in, string& s);
	std::istream& getline(std::istream& in, string& s);
	// 将整型转为string
	string to_string(int value);
}

string.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace cxf
{
	const size_t string::npos = -1;
	void string::swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}
	string::string(const char* str)
		:_size(strlen(str))
	{
		cout << "构造函数" << endl;
		_str = new char[_size + 1];
		strcpy(_str, str);
		_capacity = _size;
	}
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
	//传统写法的拷贝构造
	string::string(const string& s)
	{
		cout << "拷贝构造" << endl;
		_size = s._size;
		_capacity = s._capacity;
		_str = new char[_capacity + 1];
		strcpy(_str, s._str);
	}
	//现代写法的拷贝构造
	//string::string(const string& s)
	//	:_str(nullptr)// 若_str为随机值析构时可能会报错
	//{
	//	string tmp(s._str);
	//	swap(tmp);
	//}
	//移动拷贝
	string::string(string&& s)
	{
		cout << "移动构造" << endl;
		swap(s);
	}
	//传统写法的赋值运算符重载
	string& string::operator=(const string& s)
	{
		cout << "赋值运算符重载" << endl;
		delete[] _str;
		_size = s._size;
		_capacity = s._capacity;
		_str = new char[_capacity + 1];
		strcpy(_str, s._str);
		return *this;
	}
	//现代写法的赋值运算符重载
	/*string& string::operator=(string s)
	{
		swap(s);
		return *this;
	}*/
	// 移动赋值
	string& string::operator=(string&& s)
	{
		cout << "移动赋值运算符重载" << endl;
		swap(s);
		return *this;
	}
	const char* string::c_str() const
	{
		return _str;
	}
	string::iterator string::begin()
	{
		return _str;
	}
	string::iterator string::end()
	{
		return _str + _size;
	}
	string::const_iterator string::begin() const
	{
		return _str;
	}
	string::const_iterator string::end() const
	{
		return _str + _size;
	}
	int string::size() const
	{
		return _size;
	}
	int string::capacity() const
	{
		return _capacity;
	}
	char& string::operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}
	const char& string::operator[](size_t i) const
	{
		assert(i < _size);
		return _str[i];
	}
	void string::reserve(size_t n)
	{
		assert(n > _capacity);
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		_capacity = n;
		delete[] _str;
		_str = tmp;
	}
	void string::resize(size_t n, char ch)
	{
		if (n < _size)
		{
			_str[n] = '\0';
		}
		else
		{
			if (n > _capacity)
			{
				reserve(n);
			}
			for (int i = _size; i < n; i++)
			{
				_str[i] = ch;
			}
			_str[n] = '\0';
		}
		_size = n;
	}
	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}
	void string::append(const char* str)
	{
		int len = strlen(str);
		if (_capacity < _size + len)
		{
			reserve(_size + len + 1);
		}
		strcpy(_str + _size, str);
		_size += len;
		_capacity = _size;
	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newcapacity);
		}
		int i = _size;
		while (i >= (int)pos)
		{
			_str[i + 1] = _str[i];
			i--;
		}
		_str[pos] = ch;
		_size++;
		return *this;
	}
	string& string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		int len = strlen(str);
		if (_capacity < _size + len)
		{
			reserve(_size + len + 1);
		}
		int i = _size;
		while (i >= (int)pos)
		{
			_str[i + len] = _str[i];
			i--;
		}
		strncpy(_str + pos, str, len);
		_size += len;
		return *this;
	}
	string& string::erase(size_t pos, size_t len)
	{
		assert(pos <= _size);
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
		}
		else
		{
			int i = pos + len;
			while (i <= _size)
			{
				_str[i - pos] = _str[i];
				i++;
			}
		}
		_size -= len;
		return *this;
	}
	size_t string::find(char ch, size_t pos) const
	{
		assert(pos < _size);
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}
	size_t string::find(const char* str, size_t pos) const
	{
		char* p = strstr(_str, str);
		if (p == nullptr) return npos;
		else return p - _str;
	}
	bool string::operator<(const string& s) const
	{
		int ret = strcmp(_str, s._str);
		return ret < 0;
	}
	bool string::operator==(const string& s) const
	{
		int ret = strcmp(_str, s._str);
		return ret == 0;
	}
	bool string::operator<=(const string& s) const
	{
		return *this < s || *this == s;
	}
	bool string::operator>(const string& s) const
	{
		return !(*this <= s);
	}
	bool string::operator>=(const string& s) const
	{
		return !(*this < s);
	}
	bool string::operator!=(const string& s) const
	{
		return !(*this == s);
	}
	void string::clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
			out << s[i];
		return out;
	}
	//std::istream& operator>>(std::istream& in, string& s)
	//{
	//  s.clear();
	//	while (1)
	//	{
	//		char ch;
	//		ch = in.get();
	//		if (ch == ' ' || ch == '\n')
	//			break;
	//		s += ch;//这样子容易造成频繁的扩容
	//	}
	//}
	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();//需要先将string原先的内容清空
		char buff[128];
		int i = 0;
		while (1)
		{
			char ch;
			ch = in.get();
			if (ch == ' ' || ch == '\n')
				break;
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				i = 0;
				s += buff;
			}
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	std::istream& getline(std::istream& in, string& s)
	{
		s.clear();
		char buff[128];
		int i = 0;
		while (1)
		{
			char ch;
			ch = in.get();
			if (ch == '\n')
				break;
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				i = 0;
				s += buff;
			}
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	cxf::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		cxf::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;
	}
}
class Person
{
public:
	Person(const char* name = "77", int age = 0)
		:_name(name)
		, _age(age)
	{}
	// 生成移动构造和移动赋值运算符重载
private:
	cxf::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

结果是,s1调用构造函数,s2调用拷贝构造,s3调用移动拷贝(注意,s3调用完移动拷贝后,s1就变成了原先s3的内容),s4先调用构造函数,再调用移动赋值运算符重载

当我们自己写了析构函数后,就不会生成默认的移动构造和移动赋值运算符重载

class Person
{
public:
	Person(const char* name = "77", int age = 0)
		:_name(name)
		, _age(age)
	{}
	~Person()
	{

	}
private:
	cxf::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

结果是

实际上,需要显示写析构,说明有资源需要释放,也就说明需要写拷贝构造、移动构造、赋值运算符重载、移动赋值运算符重载

自动生成的移动构造,对于Date这样的类是没有什么意义的,和拷贝构造的功能是一样的

但是像上面的Person类就是有意义的,因为Person类是右值时,内部的string也是右值,string就能走移动构造,提高效率

1.2 类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化(这里的初始化指是在初始化列表初始化)

1.3 default

强制生成默认函数的关键字default。C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了析构函数,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

class Person
{
public:
	Person(const char* name = "77", int age = 0)
		:_name(name)
		, _age(age)
	{}
	Person(const Person& p) = default;
	Person(Person&& p) = default;
	Person& operator=(const Person& p) = default;
	Person& operator=(Person&& p) = default;

	~Person()
	{}
private:
	cxf::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

这里写了1个就需要写另外3个,否则会互相影响导致程序无法运行

1.4 delete

禁止生成默认函数的关键字delete。如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

C++98若不想要一个默认成员函数,是只声明不实现,并将声明放在private中,因为若不放在private中,在类外面是可以实现的

class Person
{
public:
	Person(const char* name = "77", int age = 0)
		:_name(name)
		, _age(age)
	{}
private:
	Person(const Person& p);
	Person(Person&& p);
	Person& operator=(const Person& p);
	Person& operator=(Person&& p);
private:
	cxf::string _name;
	int _age;
};

C++11后,直接使用delete

class Person
{
public:
	Person(const char* name = "77", int age = 0)
		:_name(name)
		, _age(age)
	{}

	Person(const Person& p) = delete;
	Person(Person&& p) = delete;
	Person& operator=(const Person& p) = delete;
	Person& operator=(Person&& p) = delete;
private:
	cxf::string _name;
	int _age;
};

应用场景如有些类不想被拷贝,如IO流,有缓冲区,若拷贝会将缓冲区也一并拷贝 

1.5 final和override

继承和多态中的final与override关键字。在前面已经介绍过了,这里就不作介绍了。

2、可变参数模板

2.1 可变参数模板的概念

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。在C++11之前其实也有可变参数的概念,比如printf函数就能够接收任意多个参数,但这是函数参数的可变参数,并不是模板的可变参数。

本篇文章只介绍函数模板的可变参数

2.2 可变参数模板的定义

函数的可变参数模板的定义如下:

template <class ...Args>
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。

Args是一个模板参数包,args是一个函数形参参数包,声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。

可以使用sizeof来获取这个函数模板实例化出的函数有几个形参

template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << "  ";
}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1,"xxxxxxx");
	ShowList(1,"xxxxxxx",2.2);
	return 0;
}

结果是

2.3 展开参数包

若想获取参数包args中的每一个参数,也就是打印参数包的内容,很容易想到的方法是

template<class ...Args>
void ShowList(Args... args)
{
	//错误示例:
	for (int i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << " "; //打印参数包中的每个参数
	}
	cout << endl;
}

这样是不行的。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

展开参数包的方法有多种,本篇文章主要介绍两种,递归函数和逗号表达式

2.3.1 递归函数
template<class T, class ...Args>
void Print(T&& x, Args&&... args)
{
	if (sizeof...(args) == 0) return;
	cout << x << " ";
	Print(args...);
}
template<class ...Args>
void ShowList(Args&&... args)
{
	Print(args...);
}

将参数包传递给Print,参数包第一个参数就会传递给T,剩下的参数就会传递给args

但是像上面这样实现Print是有错误的,不能使用if来作为递归的结束条件,因为判断是在运行时,而模板实例化是在编译时。结束条件应该写一个无参的Print,当参数包中无参时就会自动匹配到

void Print()
{}
template<class T, class ...Args>
void Print(T&& x, Args&&... args)
{
	cout << x << " ";
	Print(args...);
}
template<class ...Args>
void ShowList(Args&&... args)
{
	Print(args...);
	cout << endl;
}

以传3个参数的为例,看看模板是如何实例化的

void Print(double x)
{
	cout << x << " ";
	Print();
}
void Print(const char* x, double y)
{
	cout << x << " ";
	Print(y);
}
void Print(int x, const char* y, double z)
{
	cout << x << " ";
	Print(y, z);
}
void ShowList(int x, const char* y, double z)
{
	Print(x, y, z);
}
int main()
{
	ShowList(1,"xxxxxxx",2.2);
	cout << endl;
	return 0;
}

可变参数模板节省了很多个模板

2.3.2 逗号表达式
template<class T>
void PrintArg(T&& t)
{
	cout << t << " ";
}
template<class ...Args>
void ShowList(Args&&... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}

这是利用arr[]没有指定大小,所以会去后面推导数组有多大,所以会先执行{}中的内容。用逗号表达式是因为PrintArg没有返回值,而arr是int数组,利用逗号表达式的结果是最后面的数,也就是在arr的相应位置填上0

也可以去掉逗号表达式,此时将PrintArg修改成有返回值即可

template<class T>
int PrintArg(T&& t)
{
	cout << t << " ";
	return 0;
}
template<class ...Args>
void ShowList(Args&&... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}

实例化出来是这样的,以3个参数的为例

int PrintArg(int x)
{
	cout << x << " ";
	return 0;
}
int PrintArg(char x)
{
	cout << x << " ";
	return 0;
}
int PrintArg(std::string x)
{
	cout << x << " ";
	return 0;
}
void ShowList(int x, char y, std::string z)
{
	int arr[] = { PrintArg(x),PrintArg(y),PrintArg(z) };
	cout << endl;
}
int main()
{
	ShowList(1, 'A', "xxxxxxx");
	return 0;
}

结果是

也可以将PrintArg和ShowList合二为一

template<class ...Args>
void ShowList(Args&&... args)
{
	int arr[] = { (cout << (args) << " ", 0)... };
	cout << endl;
}

此时就必须使用逗号表达式了,因为cout返回的是ostream对象,而数组是int数组。注意,不能使用ostream数组,因为ostream数组不允许拷贝

3、emplace_back

在C++11中,大部分容器都增加了empalce类接口,本篇文章以list的emplace_back来介绍

3.1 emplace_back的使用

emplace_back是既可接收左值,也可接收右值(注意这是万能引用,不是右值引用),且参数个数不确定的函数

int main()
{
	list<cxf::string> lt;
	// 左值
	cxf::string s1("1111111");
	lt.emplace_back(s1);
	// 右值
	lt.emplace_back(move(s1));
	return 0;
}

结果是

上面的用法是类似于push_back的

int main()
{
	list<pair<cxf::string, int>> lt;
	pair<cxf::string, int> kv("苹果", 1);
	lt.emplace_back(kv);
	lt.emplace_back(move(kv));
	cout << endl;
	// 上面是类似于push_back的写法

	// emplace_back是可变参数模板,所以可以直接构造pair的参数包往下传,直接用pair参数包构造pair
	lt.emplace_back("苹果", 1); // 此时只有一次构造
	return 0;
}

结果是​​​​​​​​​​​​​​​​​​​​​​​​​​​​

list<类型> lt;
lt.emplace_back(传类型对应的构造函数的参数)

这样就可以直接构造这个类型的参数包往下传,直接用这个类型的参数包区调用这个类型的构造函数。注意,emplace_back是可变参数模板,不是说一次可以插入多个对象,而是可以根据存储在容器中的数据类型的构造函数来传参

现在我们对自己实现的string增加一个构造函数,其中reserve和成员变量的默认初始值都有所变化

string.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace cxf
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		string(const char* str = "");
		string(size_t n, char ch = '\0');
		~string();
		string(const string& s);
		string(string&& s);
		string& operator=(const string& s);//传统写法的赋值运算符重载
		//string& operator=(string s);//现代写法的赋值运算符重载
		string& operator=(string&& s);

		//迭代器
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//插入删除函数
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos, size_t len = npos);

		//relational operators
		bool operator<(const string& s) const;
		bool operator==(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>(const string& s) const;
		bool operator>=(const string& s) const;
		bool operator!=(const string& s) const;

		//其他函数
		void swap(string& s);
		const char* c_str() const;
		int size() const;
		int capacity() const;
		char& operator[](size_t i);
		const char& operator[](size_t i) const;
		size_t find(char ch, size_t pos = 0) const;
		size_t find(const char* str, size_t pos = 0) const;
		void clear();
	private:
		char* _str = nullptr;
		int _size = 0;
		int _capacity = 0;
		const static size_t npos;
	};
	//输入输出函数
	std::ostream& operator<<(std::ostream& out, const string& s);
	std::istream& operator>>(std::istream& in, string& s);
	std::istream& getline(std::istream& in, string& s);
	// 将整型转为string
	string to_string(int value);
}

string.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace cxf
{
	const size_t string::npos = -1;
	void string::swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}
	string::string(const char* str)
		:_size(strlen(str))
	{
		cout << "构造函数" << endl;
		_str = new char[_size + 1];
		strcpy(_str, str);
		_capacity = _size;
	}
	string::string(size_t n, char ch)
	{
		cout << "string(size_t n, char ch)" << endl;
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			_str[i] = ch;
		}
		_size = n;
		_str[_size] = '\0';
	}
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
	//传统写法的拷贝构造
	string::string(const string& s)
	{
		cout << "拷贝构造" << endl;
		_size = s._size;
		_capacity = s._capacity;
		_str = new char[_capacity + 1];
		strcpy(_str, s._str);
	}
	//现代写法的拷贝构造
	//string::string(const string& s)
	//	:_str(nullptr)// 若_str为随机值析构时可能会报错
	//{
	//	string tmp(s._str);
	//	swap(tmp);
	//}
	//移动拷贝
	string::string(string&& s)
	{
		cout << "移动构造" << endl;
		swap(s);
	}
	//传统写法的赋值运算符重载
	string& string::operator=(const string& s)
	{
		cout << "赋值运算符重载" << endl;
		delete[] _str;
		_size = s._size;
		_capacity = s._capacity;
		_str = new char[_capacity + 1];
		strcpy(_str, s._str);
		return *this;
	}
	//现代写法的赋值运算符重载
	/*string& string::operator=(string s)
	{
		swap(s);
		return *this;
	}*/
	// 移动赋值
	string& string::operator=(string&& s)
	{
		cout << "移动赋值运算符重载" << endl;
		swap(s);
		return *this;
	}
	const char* string::c_str() const
	{
		return _str;
	}
	string::iterator string::begin()
	{
		return _str;
	}
	string::iterator string::end()
	{
		return _str + _size;
	}
	string::const_iterator string::begin() const
	{
		return _str;
	}
	string::const_iterator string::end() const
	{
		return _str + _size;
	}
	int string::size() const
	{
		return _size;
	}
	int string::capacity() const
	{
		return _capacity;
	}
	char& string::operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}
	const char& string::operator[](size_t i) const
	{
		assert(i < _size);
		return _str[i];
	}
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			if (_str)
			{
				strcpy(tmp, _str);
				delete[] _str;
			}
			_str = tmp;
			_capacity = n;
		}
	}
	void string::resize(size_t n, char ch)
	{
		if (n < _size)
		{
			_str[n] = '\0';
		}
		else
		{
			if (n > _capacity)
			{
				reserve(n);
			}
			for (int i = _size; i < n; i++)
			{
				_str[i] = ch;
			}
			_str[n] = '\0';
		}
		_size = n;
	}
	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}
	void string::append(const char* str)
	{
		int len = strlen(str);
		if (_capacity < _size + len)
		{
			reserve(_size + len + 1);
		}
		strcpy(_str + _size, str);
		_size += len;
		_capacity = _size;
	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newcapacity);
		}
		int i = _size;
		while (i >= (int)pos)
		{
			_str[i + 1] = _str[i];
			i--;
		}
		_str[pos] = ch;
		_size++;
		return *this;
	}
	string& string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		int len = strlen(str);
		if (_capacity < _size + len)
		{
			reserve(_size + len + 1);
		}
		int i = _size;
		while (i >= (int)pos)
		{
			_str[i + len] = _str[i];
			i--;
		}
		strncpy(_str + pos, str, len);
		_size += len;
		return *this;
	}
	string& string::erase(size_t pos, size_t len)
	{
		assert(pos <= _size);
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
		}
		else
		{
			int i = pos + len;
			while (i <= _size)
			{
				_str[i - pos] = _str[i];
				i++;
			}
		}
		_size -= len;
		return *this;
	}
	size_t string::find(char ch, size_t pos) const
	{
		assert(pos < _size);
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}
	size_t string::find(const char* str, size_t pos) const
	{
		char* p = strstr(_str, str);
		if (p == nullptr) return npos;
		else return p - _str;
	}
	bool string::operator<(const string& s) const
	{
		int ret = strcmp(_str, s._str);
		return ret < 0;
	}
	bool string::operator==(const string& s) const
	{
		int ret = strcmp(_str, s._str);
		return ret == 0;
	}
	bool string::operator<=(const string& s) const
	{
		return *this < s || *this == s;
	}
	bool string::operator>(const string& s) const
	{
		return !(*this <= s);
	}
	bool string::operator>=(const string& s) const
	{
		return !(*this < s);
	}
	bool string::operator!=(const string& s) const
	{
		return !(*this == s);
	}
	void string::clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
			out << s[i];
		return out;
	}
	//std::istream& operator>>(std::istream& in, string& s)
	//{
	//  s.clear();
	//	while (1)
	//	{
	//		char ch;
	//		ch = in.get();
	//		if (ch == ' ' || ch == '\n')
	//			break;
	//		s += ch;//这样子容易造成频繁的扩容
	//	}
	//}
	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();//需要先将string原先的内容清空
		char buff[128];
		int i = 0;
		while (1)
		{
			char ch;
			ch = in.get();
			if (ch == ' ' || ch == '\n')
				break;
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				i = 0;
				s += buff;
			}
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	std::istream& getline(std::istream& in, string& s)
	{
		s.clear();
		char buff[128];
		int i = 0;
		while (1)
		{
			char ch;
			ch = in.get();
			if (ch == '\n')
				break;
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				i = 0;
				s += buff;
			}
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	cxf::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		cxf::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()
{
	list<cxf::string> lt;
	lt.emplace_back("111111111");
	lt.emplace_back(10, 'x');
	return 0;
}

结果是 

像string可以使用上面两种方式构造,emplace_back就会直接把构造string的参数包往下传,直接用string参数包构造string

3.2 emplace_back的模拟实现

我们现在来模拟实现list中的emplace_back

引入我们之前实现的list

#pragma once
namespace cxf
{
	template<class T>
	struct __List_node//创建一个T类型的链表结点
	{
		__List_node(const T& data = T())//构造函数
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{}
		__List_node(T&& data)//构造函数
			:_data(move(data))
			, _next(nullptr)
			, _prev(nullptr)
		{}

		__List_node<T>* _next;
		__List_node<T>* _prev;
		T _data;
	};
	template<class T, class Ref, class Ptr>
	struct __List_iterator//封装链表的迭代器
	{
		typedef __List_node<T> Node;
		typedef __List_iterator<T, Ref, Ptr> Self;
		Node* _node;//成员变量
		__List_iterator(Node* node)//构造函数。将迭代器中的结点初始化成传过来的结点
			:_node(node)
		{}
		// *it
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		// ++it
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		// it++
		Self operator++(int)
		{
			Self tmp(*this);//调用默认的拷贝构造,因为是指针类型所以直接用默认的
			//_node = _node->_next;
			++(*this);
			return tmp;
		}
		// --it
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		// it--
		Self operator--(int)
		{
			Self tmp(*this);
			//_node = _node->_prev;
			--(*this);
			return tmp;
		}
		// it != end()
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
	};
	template<class T>
	class List//真正的链表
	{
	public:
		typedef __List_node<T> Node;//将链表结点的名称重命名为Node
		typedef __List_iterator<T, T&, T*> iterator;
		typedef __List_iterator<T, const T&, const T*> const_iterator;
		//带头双向循环链表
		List()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		List(std::initializer_list<T> il)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			for (auto e : il)
				push_back(e);
		}
		~List()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		List(const List<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			//const_iterator it = lt.begin();//这里迭代器不需要指定是那个类域,因为就是在这个类中使用
			//while (it != lt.end())
			//{
			//	push_back(*it);
			//	++it;
			//}
			for (const auto& e : lt)//这里与上面用迭代器一样,因为最终也会被替换成迭代器
				push_back(e);
		}
		/*List<T>& operator=(const List<T>& lt)
		{
			if (this != &lt)
			{
				clear();
				for (const auto& e : lt)
					push_back(e);
			}
			return *this;
		}*/
		List<T>& operator=(List<T> lt)
		{
			swap(_head, lt._head);//原来的空间给这个临时变量,因为这个临时变量是自定义类型,出了作用域后会自动调用析构函数
			return *this;
		}
		void clear()//clear是清除除了头节点意外的所以结点
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
			}
		}
		void push_back(const T& x)//一定要用引用,因为T不一定是内置类型
		{
			/*Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;*/
			insert(end(),x);
		}
		void push_back(T&& x)
		{
			insert(end(), move(x));
		}
		void pop_back()
		{
			/*Node* tail = _head->_prev;
			Node* prev = tail->_prev;
			delete tail;
			_head->_prev = prev;
			prev->_next = _head;*/
			erase(--end());
		}
		void push_front(const T& x)
		{
			/*Node* first = _head->_next;
			Node* newnode = new Node(x);
			_head->_next = newnode;
			newnode->_prev = _head;
			newnode->_next = first;
			first->_prev = newnode;*/
			insert(begin(), x);
		}
		void pop_front()
		{
			/*Node* first = _head->_next;
			Node* second = first->_next;
			delete first;
			_head->_next = second;
			second->_prev = _head;*/
			erase(begin());
		}
		void insert(iterator pos, const T& x)
		{
			Node* newnode = new Node(x);
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}
		void insert(iterator pos, T&& x)
		{
			Node* newnode = new Node(move(x));
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}
		void erase(iterator pos)
		{
			assert(pos != end());//不能删除头节点
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			prev->_next = next;
			next->_prev = prev;
			delete cur;
			cur = nullptr;
		}
		iterator begin()
		{
			return iterator(_head->_next);//使用这个结点去构造一个迭代器,并将这个迭代器返回
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator begin() const
		{
			return const_iterator(_head->_next);//使用这个结点去构造一个迭代器,并将这个迭代器返回
		}
		const_iterator end() const
		{
			return const_iterator(_head);
		}
	private:
		Node* _head;
	};
}

实现emplace_back与push_back类似,就是x变成了一个参数包

template<class... Args>
void emplace_back(Args&&... args)
{
	insert(end(), args...);
}
template<class... Args>
void insert(iterator pos, Args&&... args)
{
	Node* newnode = new Node(args...);
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
}

结点的构造函数

template<class... Args>
__List_node(Args&&... args)//构造函数
	: _data(args...)
	, _next(nullptr)
	, _prev(nullptr)
{}

运行下面代码

int main()
{
	cxf::List<cxf::string> lt;
	cxf::string s1("7777777");
	lt.emplace_back(s1);
	lt.emplace_back("111111111");
	lt.emplace_back(10, 'x');
	return 0;
}

会发现无论是左值还是右值,都是使用深拷贝

因为存在退化,此时就需要用到完美转发了

template<class... Args>
void emplace_back(Args&&... args)
{
	insert(end(), std::forward<Args>(args)...);
}
template<class... Args>
void insert(iterator pos, Args&&... args)
{
	Node* newnode = new Node(std::forward<Args>(args)...);
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
}
template<class... Args>
__List_node(Args&&... args)//构造函数
	: _data(std::forward<Args>(args)...)
	, _next(nullptr)
	, _prev(nullptr)
{}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2158934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Shelly实测天工的音乐创作功能,写了一首歌,来听听效果

​ 大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 在数字时代的洪流中&#xff0c;我始终…

16、斑马设备的ppocer-4进行文字识别,和opencv-mobile中文显示

基本思想:手上有个斑马设备,是客户的,简单记录一下开发过程和工程项目,同时记录跟着android小哥学习了很多anroid的知识,转ppocr-4参考之前的ppocr-3转换即可,整个框架仍然使用c++ ncnn jni框架推理和现实,图像库使用opencv-mobile 一、首先转paddle-cor-4 到ncnn的框架…

计算机复试相关问题

泰勒展开式 泰勒展开的目的是用多项式拟合一般函数。 拟合方法&#xff1a;保证多项式与原函数在x0处0到∞阶导都相同。 泊松分布 博客详解推导过程&#xff1a;概率论–泊松分布 IPv4和IPv6 IP 协议&#xff08;Internet Protocol&#xff09;是网络层的核心协议&#xf…

简单的spring缓存 Cacheable学习

简单的spring缓存 Cacheable学习 1.需求 项目中有很多的方法查询的数据其实是不会经常变的&#xff0c;但是其整体的查询sql以及调用第三方数据获取数据花费的时间很长&#xff0c;现在考虑对此类型的接口进行优化&#xff0c;首先想到的是对其进行缓存操作&#xff0c;所以简…

【数据结构与算法】十大经典排序算法深度解析:冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序、计数排序、桶排序、基数排序

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 引言 一、排序算法概述 排序算法简介 排序算法的分类 性能指标 二、十大排序算法…

小新 Pro13 + windows 11 家庭中文版(网络适配器及地址配置)

网络适配器位置及地址配置 网络适配器简介 计算机系统&#xff1a;网络适配器详解&#xff0c;全面剖析 网络适配器位置 不同于win11之前的版本&#xff0c;win11的网络适配器的位置如下&#xff1a; 1、右键 右下角的网络图标-》网络和internet设置-》高级网络设置-》可以…

Apache CVE-2021-41773 漏洞复现

1.打开环境 docker pull blueteamsteve/cve-2021-41773:no-cgid docker run -d -p 8080:80 97308de4753d 2.访问靶场 3.使用poc curl http://47.121.191.208:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd 4.工具验证

颍川陈氏——平民崛起的典范

园子说颍川 广州有一处老建筑“陈家祠”&#xff0c;豪华精美堪比皇宫&#xff0c;誉为“岭南建筑艺术明珠”、“新世纪羊城八景”之一&#xff0c;是全国文保单位&#xff0c;4A 级景区。主体建筑以中轴线三座厅堂为中心&#xff0c;由大小十九座单体建筑组成&#xff0c;占地…

通信工程学习:什么是VM虚拟机

VM&#xff1a;虚拟机 VM虚拟机&#xff08;Virtual Machine&#xff09;是一种通过软件模拟的计算机系统&#xff0c;它能够在物理计算机上模拟并运行多个独立的虚拟计算机系统。以下是关于VM虚拟机的详细解释&#xff1a; 一、VM虚拟机的定义与原理 定义&#xff1a; VM虚拟…

认知杂谈77《简单:通往高手的技巧》

内容摘要&#xff1a;          在信息爆炸、关系复杂的时代&#xff0c;简单是复杂背后的真谛。简单如“112”&#xff0c;是智慧的朴素呈现。简单有强大力量&#xff0c;像清泉般纯净&#xff0c;如“我爱你”简单却有力&#xff0c;基础财务知识也体现其在理财中的作…

鸿蒙开发(NEXT/API 12)【基础功能(使用剪贴板进行复制粘贴)】剪贴板服务

场景介绍 [剪贴板]为开发者提供数据的复制粘贴能力。 当需要使用复制粘贴等功能时&#xff0c;例如&#xff1a;复制文字内容到备忘录中粘贴&#xff0c;复制图库照片到文件管理粘贴&#xff0c;就可以通过剪贴板来完成。 约束限制 剪贴板内容大小<128MB。为保证剪贴板数…

拓维思注册机Tovos PowerLine4.0.19树障分析 Tovos SmartPlan2.0.0航线规划软件

Tovos PowerLine是功能强大的输电线路智能巡检系统&#xff01;这是一个专业且智能的软件&#xff0c;能够更准确的进行巡检和对线路设备进行精确的测量&#xff0c;通过获取高精度的点云来获取精准的三维路线的地形地貌、设备设施、途径的各种物体等来精确您的三维空间信息和三…

PostgreSQL的学习心得和知识总结(一百五十一)|[performance] PostgreSQL列对齐

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

828华为云征文|华为云Flexus云服务器X实例部署Xnote笔记应用

828华为云征文&#xff5c;华为云Flexus云服务器X实例部署Xnote笔记应用 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、Note Mark 介绍2.1 Xnote简介2.2 Xnote特点2.3 主要使用场景 三、本次实…

2024 硬盘格式恢复软件大盘点:功能、特点与适用场景

在当今社会设计师们依赖硬盘存储海量的创意素材&#xff0c;而学生群体则借助硬盘来管理繁重的作业与数据&#xff0c;这已成为一种普遍现象。在数据迁移或整理过程中&#xff0c;我们可能会选择格式化硬盘以获取一个干净的新空间。如果操作不当硬盘格式化后能恢复数据吗&#…

Vue开发前端图片上传给java后端

前端效果图 图片上传演示 1 前端代码 <template><div><!-- 页面标题 --><h1 class"page-title">图片上传演示</h1><div class"upload-container"><!-- 使用 van-uploader 组件进行文件上传&#xff0c;v-model 绑…

Qt 状态机编程,双层状态机,实现暂停恢复

流程设计状态图 #ifndef WORKMACHINE_H #define WORKMACHINE_H#include <QObject> #include <QStateMachine> #include <QHistoryState> #include <QFinalState>#include "WorkThread.h"class WorkMachine : public QObject {Q_OBJECT publ…

手写Spring第三篇,原来Spring容器是使用反射来初始化对象的

上次是不是你小子和大家说你拿来做登记的样品被我收了&#xff0c;然后取豆子的时候就是这个样品的&#xff1f; 今天我来辟一下谣&#xff0c;真的是这样的。这小子的样品确实被我收了&#xff0c;不过这小子没给真东西给我&#xff0c;只给了一个指针&#xff0c;害我宝贝得存…

【深度】为GPT-5而生的「草莓」模型!从快思考—慢思考到Self-play RL的强化学习框架

原创 超 超的闲思世界 2024年09月11日 19:17 北京 9月11日消息&#xff0c;据外媒The Information昨晚报道&#xff0c;OpenAI的新模型「草莓」&#xff08;Strawberry&#xff09;&#xff0c;将在未来两周内作为ChatGPT服务的一部分发布。 「草莓」项目是OpenAI盛传已久的…

QT中添加资源文件(一看就会)

QT中添加资源文件 什么是资源文件如何使用创建资源文件编辑资源文件代码中引用资源什么是资源文件 项目中经常需要添加图片、‌音频、‌视频、翻译文件等文件,在QT中,这些文件会放在 .qrc 文件中来被使用。 .qrc 文件是一个XML格式的资源集合描述文件,是Qt中用于定义和管理…