深入理解string:通过模拟实现探讨其内部机制

一、auto和范围for

. auto

在早期C++中auto的含义是使用auto修饰的变量,是具有自动存储器的局部变量后来C++11中,为auto赋予了全新的含义auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

用auto声明指针类型时,用auto和auto*没有任何区别但用auto声明引用类型时则必须加&

需要注意的是,用auto声明指针类型时,右边可以给指针类型,也可以给其它类型,但如果用auto*,就只能给指针类型


当在同一行声明多个变量时,这些变量必须是相同的类型否则编译器会报错因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其它变量


auto不能用来定义数组类型


从C++14,auto可以作为函数返回类型,C++20开始,auto可以作为函数参数类型

. 范围for

C++11中引入了基于范围的 for 循环,for循环后的括号由冒号分为两部分第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围(自动迭代,自动取数据,自动判断结束)

范围 for 可以作用到数组和容器对象上进行遍历

范围 for 的底层其实就是替换为迭代器

二、String的模拟实现

1. string的成员变量

cpp 复制代码
class string
{
private:
	//声明
	char* _str;
	size_t _size;//字符串存储字符的个数
	size_t _capacity;//字符串的容量
	static size_t npos;
};

2. 构造函数

cpp 复制代码
size_t string::npos = -1;//后面要用到
string::string()
	:_str(new char[1])
	, _size(0)
	, _capacity(0)
{
	_str[0] = '\0';//初始化
}

3. reserve

cpp 复制代码
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		cout << "reserve()" << n << endl;
		char* tmp = new char[n + 1];//开辟新空间
		strcpy(tmp, _str);//拷贝数据
		delete[] _str;//释放旧空间
		_str = tmp;//指向新空间
		_capacity = n;//更新容量
	}
}

异地扩容,开辟新空间,将旧空间的内容拷贝到新空间里,释放旧空间,让指针指向新空间,更新容量的大小

也有可能是原地扩容,但大概率都是异地扩容,这里不考虑原地扩容。

4. push_back

cpp 复制代码
void string::push_back(char c)
{
	if (_size == _capacity)
	{
		//扩容
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	_str[_size] = c;
	_size++;
	_str[_size] = '\0';
}

首先检查是否还有容量存储数据,若没有就需要扩容,由于构造时初始容量为0,所以,需要判断,如果容量为0,我们就开4个空间,否则,开2倍的空间。此时添加数据,但是数组空间最后一个元素是\0,所以添加完数据之后,先更新_size,再在数组最后添加\0字符

5. append

cpp 复制代码
string& string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//扩容
		if (_size + len > 2 * _capacity)
		{
			reserve(_size + len);
		}
		else
		{
			reserve(2 * _capacity);
		}
	}
	strcpy(_str + _size, str);
	_size += len;
	return *this;
}

append函数添加一个字符串,如果还有存储空间,就直接存储,若存储空间不够,就需要开辟空间 。那么,问题来了,我们需要开辟多大的空间呢?直接开2倍的空间,字符串太长怎么办,难道需要不断开辟新空间吗?那直接开辟一块很大的空间呢字符串太短怎么办,浪费了许多空间

所以,我们可以先求出字符串的长度,如果存储空间足够,直接存储就可以了,否则,先判断开2倍的空间是否足够,不够就直接开辟_size + len 个字符空间,反之,开辟2倍的空间就足够了

最后更新_size的值。

6. insert

cpp 复制代码
string& string::insert(size_t pos, size_t n, char c)
{
	assert(pos >= 0 && pos <= _size);
	assert(n > 0);
	if (_size + n > _capacity)
	{
		//扩容
		if (_size + n > 2 * _capacity)
		{
			reserve(_size + n);
		}
		else
		{
			reserve(2 * _capacity);
		}
	}
	size_t end = _size + n;
	while (end > pos + n - 1)
	{
		_str[end] = _str[end - n];
		--end;
	}
	for (size_t i = 0; i < n; ++i)
	{
		_str[pos + i] = c;
	}
	_size += n;
	return *this;
}

首先判断要插入的字符的个数是否大于0,字符要插入的位置是否合法

接着判断存储空间是否足够存储 n 个字符,不够就考虑开2倍的空间还是直接开 _size + n 个字符的空间原因同append函数,反之足够的话就开始插入字符

紧接着就挪动数据,为插入数据预留位置空间

cpp 复制代码
string& string::insert(size_t pos, const char* str)
{
	assert(pos >= 0 && pos <= _size);
	size_t len = strlen(str);
	insert(pos, len, 'x');
	for (size_t i = 0; i < len; ++i)
	{
		_str[pos + i] = str[i];
	}
	return *this;
}

插入一个字符串,可以复用插入 n 个字符的操作,先随便插入一个指定的字符,然后在从 pos 位置开始覆盖

7. erase

cpp 复制代码
string& string::erase(size_t pos, size_t len)
{
	assert(pos >= 0 && pos <= _size);
	assert(len > 0);
	//剩余字符的个数
	size_t leftSize = _size - pos;
	//pos位置及之后的数据删完了
	if (len >= leftSize)
	{
		len = leftSize;
		_str[pos] = '\0';
	}
	else
	{
		size_t start = pos + len;
		while (start <= _size)
		{
			_str[pos++] = _str[start++];
		}
	}
	_size -= len;
	return *this;
}

分为两种情况第一种删除字符的个数大于等于 pos 位置开始剩余的字符个数,从 pos 位置全部删除,有一种简便操作就是把 pos 位置的字符设置为 \0 字符

否则,就要挪动数据,更新 _size的值

8. find

cpp 复制代码
size_t string::find(const char* s, size_t pos)
{
	assert(pos >= 0 && pos < _size);
	char* ptr = strstr(this->_str + pos, s);
	if (ptr == nullptr)
	{
		return npos;
	}
	return ptr - _str;
}

查找一个字符串,我们可以使用C语言库的 strstr 库函数查找,成功的话,会返回字符串在string对象中第一次匹配成功位置的指针。要得到匹配成功的下标,只需要让 ptr - _str就可以了

cpp 复制代码
size_t string::find(char c, size_t pos)
{
	assert(pos < _size);
	for (int i = pos; i < _size; ++i)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	return npos;
}

查找一个字符,只需要从 pos 位置开始遍历数组,判断是否有与字符 c 相等的字符就可以了

9. substr

cpp 复制代码
string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	size_t leftSize = _size - pos;
	if (len >= leftSize)
	{
		len = leftSize;
	}
	string tmp;
	tmp.reserve(len);
	for (size_t i = 0; i < len; ++i)
	{
		tmp += _str[pos + i];
	}
	//tmp是一个局部变量,出了作用域便会销毁
	//这里需要一个拷贝构造函数(深拷贝)
	//浅拷贝会存在多次析构的问题以及间接修改对象
	return tmp;
}

如果从pos位置开始提取的 len 个字符大于剩余字符的个数,那么就将剩余字符全部提取。我们可以调用reserve函数,提前开辟好空间,这样就不用频繁申请空间

但是,这里需要注意的一个点是 tmp 是一个局部对象,出了作用域便会销毁,构建一个临时对象去拷贝构造另一个 string 对象由于未显示实现拷贝构造函数所以拷贝构造函数的默认行为是浅拷贝

这就会导致出现问题,两个string对象的_str指向了同一块空间,那么在析构时,就会将同一块空间进行多次释放,这不就造成野指针问题了吗!所以,我们需要显示实现拷贝构造函数

10. 拷贝构造函数

cpp 复制代码
//拷贝构造函数
string::string(const string& s)
{
	clear();
	_str = new char[s.capacity() + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

开辟一个和拷贝对象一样大小的空间,将拷贝对象的内容拷贝进新开辟的空间内,更新_size和_capacity

11. clear

cpp 复制代码
void clear()
{
	_str[0] = '\0';
	_size = 0;
}

清空数据,直接修改0号下标字符为 \0字符,更新_size。

12. 赋值运算符重载

cpp 复制代码
//s2=s1;
string& string::operator=(const string& s)
{
	if (this != &s)
	{
		delete[] _str;
		_str = new char[s.capacity() + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

赋值运算符默认也是浅拷贝的行为,所以,对于有指向资源的成员是非常危险的行为,因此,我们需要实现一个深拷贝的赋值运算符重载

为了避免自己给自己赋值,从而导致自己的空间被释放,所以需要进行判断。如果不是自己给自己赋值,那就先释放原来的空间,在开辟一个新的空间,拷贝数据,更新_size和_capacity

13. operator[]运算符重载

cpp 复制代码
const char& string::operator[](size_t pos)const
{
	assert(pos >= 0 && pos < _size);
	return _str[pos];
}

底层是数组,直接利用下标访问即可

14. operator+=运算符重载

cpp 复制代码
string& string::operator+=(char c)
{
	push_back(c);
	return *this;
}

直接复用push_back操作即可

cpp 复制代码
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}

+=一个字符串,复用append函数

15. 判断两个string对象之间的大小关系

cpp 复制代码
bool string::operator==(const string& s) const
{
	return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
	return !(_str == s._str);
}
bool string::operator<(const string& s) const
{
	return strcmp(_str, s._str) < 0;
}
bool string::operator<=(const string& s) const
{
	return !(_str > s._str);
}
bool string::operator>(const string& s) const
{
	return strcmp(_str, s._str) > 0;
}
bool string::operator>=(const string& s) const
{
	return !(_str < s._str);
}

直接调用C语言中的strcmp函数比较两个string对象中的字符串即可

16. getline函数

cpp 复制代码
istream& string::getline(istream& in, string& s, char delim)
{
	s.clear();
	char ch;
	//阻塞
	//in >> ch;
	ch = in.get();
	const int N = 1024;
	int i = 0;
	//避免空间给大,造成浪费
	//空间给小,频繁扩容
	char buff[N];
	while (ch != delim)
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

delim是分割符,get是获取字符的库函数,只要输入的字符串中不包含delim分割符,就要一直输入为了避免频繁扩容,我们创建了一个char buff 的数组,获取到的字符先存储在字符数组里,如果数组满了,就存储到 string 对象中,将 i 置为0,从头开始继续存储。如果字符串全部读取到数组里,数组未满,就需要判断数组里是否还有内容,有的话就添加到 string 对象中

17. operator << 运算符重载

cpp 复制代码
ostream& operator<<(ostream& out, const string& str)
{
	for (auto e : str)
	{
		out << e;
	}
	return out;
}

out是ostream类的对象,直接遍历string对象输出即可

18. operator >> 运算符重载

cpp 复制代码
istream& operator>>(istream& in, string& s)
{
	s.clear();
	char ch;
	//阻塞
	//in >> ch;
	ch = in.get();
	const int N = 1024;
	int i = 0;
	//避免空间给大,造成浪费
	//空间给小,频繁扩容
	char buff[N];
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

原理和上面的getline函数是一样的,只不过换了一个分割符而已

19. Iterator

cpp 复制代码
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}
const_iterator begin()const
{
	return _str;
}
const_iterator end()const
{
	return _str + _size;
}

这里的 iterator 和 const_iterator 是被 typedef 命名过的实际是 char* 和 const char*

20. size、capacity、c_str、clear、~string

cpp 复制代码
size_t size()const
{
	return _size;
}
size_t capacity()const
{
	return _capacity;
}
char* c_str()const
{
	return _str;
}
void clear()
{
	_str[0] = '\0';
	_size = 0;
}
//析构函数
string::~string()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

三、完整代码

. string.h

cpp 复制代码
#pragma once

#include<iostream>
#include<assert.h>
#include<string.h>
#include<string>
using namespace std;

namespace LC
{
	class string
	{
		typedef char* iterator;
		typedef const char* const_iterator;
	public:
		//默认构造函数
		string();
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}
		size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _capacity;
		}
		char* c_str()const
		{
			return _str;
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		//拷贝构造函数
		string(const string& s);
		string& insert(size_t pos, const char* str);
		string& insert(size_t pos, size_t n, char c);
		void reserve(size_t n);
		void push_back(char c);
		string& append(const char* str);
		string& erase(size_t pos, size_t len = npos);
		const char& operator[](size_t pos)const;
		string& operator+=(char c);
		string& operator+=(const char* str);
		size_t find(const char* s, size_t pos = 0);
		size_t find(char c, size_t pos = 0);
		string substr(size_t pos, size_t len = npos);

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		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;
		string& operator=(const string& s);
		istream& getline(istream& is, string& str, char delim);
		//析构函数
		~string();
	private:
		//声明
		char* _str;
		size_t _size;//字符串存储字符的个数
		size_t _capacity;//字符串的容量
		static size_t npos;
	};
	ostream& operator<<(ostream& out, const string& str);
	istream& operator>>(istream& in, string& str);
}

. string.cc

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include"string.h"
namespace LC
{
	size_t string::npos = -1;
	string::string()
		:_str(new char[1])
		, _size(0)
		, _capacity(0)
	{
		_str[0] = '\0';
	}
	//拷贝构造函数
	string::string(const string& s)
	{
		clear();
		_str = new char[s.capacity() + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			cout << "reserve()" << n << endl;
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	void string::push_back(char c)
	{
		if (_size == _capacity)
		{
			//扩容
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		_str[_size] = c;
		_size++;
		_str[_size] = '\0';
	}
	string& string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			//扩容
			if (_size + len > 2 * _capacity)
			{
				reserve(_size + len);
			}
			else
			{
				reserve(2 * _capacity);
			}
		}
		strcpy(_str + _size, str);
		_size += len;
		return *this;
	}
	string& string::insert(size_t pos, const char* str)
	{
		assert(pos >= 0 && pos <= _size);
		size_t len = strlen(str);
		insert(pos, len, 'x');
		for (size_t i = 0; i < len; ++i)
		{
			_str[pos + i] = str[i];
		}
		return *this;
	}
	string& string::insert(size_t pos, size_t n, char c)
	{
		assert(pos >= 0 && pos <= _size);
		assert(n > 0);
		if (_size + n > _capacity)
		{
			//扩容
			if (_size + n > 2 * _capacity)
			{
				reserve(_size + n);
			}
			else
			{
				reserve(2 * _capacity);
			}
		}
		size_t end = _size + n;
		while (end > pos + n - 1)
		{
			_str[end] = _str[end - n];
			--end;
		}
		for (size_t i = 0; i < n; ++i)
		{
			_str[pos + i] = c;
		}
		_size += n;
		return *this;
	}
	ostream& operator<<(ostream& out, const string& str)
	{
		for (auto e : str)
		{
			out << e;
		}
		return out;
	}
	string& string::erase(size_t pos, size_t len)
	{
		assert(pos >= 0 && pos <= _size);
		assert(len > 0);
		//剩余字符的个数
		size_t leftSize = _size - pos;
		//pos位置及之后的数据删完了
		if (len >= leftSize)
		{
			len = leftSize;
			_str[pos] = '\0';
		}
		else
		{
			size_t start = pos + len;
			while (start <= _size)
			{
				_str[pos++] = _str[start++];
			}
		}
		_size -= len;
		return *this;
	}
	const char& string::operator[](size_t pos)const
	{
		assert(pos >= 0 && pos < _size);
		return _str[pos];
	}
	string& string::operator+=(char c)
	{
		push_back(c);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	size_t string::find(const char* s, size_t pos)
	{
		assert(pos >= 0 && pos < _size);
		char* ptr = strstr(this->_str + pos, s);
		if (ptr == nullptr)
		{
			return npos;
		}
		return ptr - _str;
	}
	size_t string::find(char c, size_t pos)
	{
		assert(pos < _size);
		for (int i = pos; i < _size; ++i)
		{
			if (_str[i] == c)
			{
				return i;
			}
		}
		return npos;
	}
	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		size_t leftSize = _size - pos;
		if (len >= leftSize)
		{
			len = leftSize;
		}
		string tmp;
		tmp.reserve(len);
		for (size_t i = 0; i < len; ++i)
		{
			tmp += _str[pos + i];
		}
		//tmp是一个局部变量,出了作用域便会销毁
		//这里需要一个拷贝构造函数(深拷贝)
		//浅拷贝会存在多次析构的问题以及间接修改对象
		return tmp;
	}

	bool string::operator==(const string& s) const
	{
		return strcmp(_str, s._str) == 0;
	}
	bool string::operator!=(const string& s) const
	{
		return !(_str == s._str);
	}
	bool string::operator<(const string& s) const
	{
		return strcmp(_str, s._str) < 0;
	}
	bool string::operator<=(const string& s) const
	{
		return !(_str > s._str);
	}
	bool string::operator>(const string& s) const
	{
		return strcmp(_str, s._str) > 0;
	}
	bool string::operator>=(const string& s) const
	{
		return !(_str < s._str);
	}
	//s2=s1;
	string& string::operator=(const string& s)
	{
		if (this != &s)
		{
			delete[] _str;
			_str = new char[s.capacity() + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		return *this;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch;
		//阻塞
		//in >> ch;
		ch = in.get();
		const int N = 1024;
		int i = 0;
		//避免空间给大,造成浪费
		//空间给小,频繁扩容
		char buff[N];
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	istream& string::getline(istream& in, string& s, char delim)
	{
		s.clear();
		char ch;
		//阻塞
		//in >> ch;
		ch = in.get();
		const int N = 1024;
		int i = 0;
		//避免空间给大,造成浪费
		//空间给小,频繁扩容
		char buff[N];
		while (ch != delim)
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	//析构函数
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
}

. test.cc

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include"string.h"

void TestString1()
{
	LC::string s1;
	s1.push_back('h');
	s1.push_back('e');
	s1.push_back('l');
	s1.push_back('l');
	s1.push_back('o');
	s1.push_back(' ');

	s1.append("world");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	s1.insert(11, 5, 'z');
	cout << s1 << endl;
	s1.insert(4, 3, 'y');
	cout << s1 << endl;
	s1.insert(0, 5, 'x');
	cout << s1 << endl;

	s1.insert(s1.size(), "welcome");
	cout << s1 << endl;
	s1.insert(10, "china");
	cout << s1 << endl;
	s1.insert(0, "to");
	cout << s1 << endl;
}

void TestString2()
{
	LC::string s1;
	s1.push_back('h');
	s1.push_back('e');
	s1.push_back('l');
	s1.push_back('l');
	s1.push_back('o');
	s1.push_back(' ');

	s1.append("world");
	s1.erase(11, 5);
	cout << s1 << endl;
	s1.erase(8, 5);
	cout << s1 << endl;
	s1.erase(3, 5);
	cout << s1 << endl;
	s1.erase(0, 2);
	cout << s1 << endl;
}
void TestString3()
{
	LC::string s1;
	s1.push_back('h');
	s1.push_back('e');
	s1.push_back('l');
	s1.push_back('l');
	s1.push_back('o');
	s1.push_back(' ');

	s1.append("world");
	cout << s1[3] << endl;
	cout << s1[0] << endl;
	s1 += ' ';
	s1 += "welcome";
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	LC::string s2;
	s2 += "china";
	cout << s2.c_str() << endl;
	cout << s1.find("come", 3) << endl;
	cout << s1.find("hello", 2) << endl;
	cout << s1.find("hello") << endl;
	cout << s1.find("to", 5) << endl;

	cout << s1.find('w', 10) << endl;
	cout << s1.find('o') << endl;
	cout << s1.find('z') << endl;
	//编译器进行了优化,隐藏有BUG
	//拷贝构造
	/*LC::string s3 = s1.substr(3);
	cout << s3 << endl;*/



	//2022编译器会进行优化,这种写法会打破编译器的优化
	LC::string s3;
	//赋值重载
	s3 = s1.substr(3);
	cout << s3 << endl;
}

void TestString4()
{
	LC::string s1;
	s1.push_back('h');
	s1.push_back('e');
	s1.push_back('l');
	s1.push_back('l');
	s1.push_back('o');

	LC::string s2;
	s2.push_back('w');
	s2.push_back('e');
	s2.push_back('l');
	s2.push_back('c');
	s2.push_back('o');
	cout << (s1 == s2) << endl;
	cout << (s1 != s2) << endl;
	cout << (s1 < s2) << endl;
	cout << (s1 <= s2) << endl;
	cout << (s1 > s2) << endl;
	cout << (s1 >= s2) << endl;
	LC::string s3;
	cin >> s3;
	cout << s3 << endl;

	LC::string s4;
	s4.getline(cin, s4, '#');
	cout << s4 << endl;
}
int main()
{
	TestString1();
	//TestString2();
	//TestString3();
	//TestString4();
	return 0;
}
相关推荐
A星空1231 小时前
二、交叉编译工具链(arm-linux-gnueabihf-gcc)安装与验证,搭建 TFTP+NFS 服务,调试开发板网络连通性;
linux·c++·驱动开发·单片机·嵌入式硬件
郝学胜-神的一滴2 小时前
Effective Modern C++ 条款40:深入理解 Atomic 与 Volatile 的多线程语义
开发语言·c++·学习·算法·设计模式·架构
骇城迷影2 小时前
代码随想录:二叉树篇(中)
数据结构·c++·算法·leetcode
czxyvX2 小时前
011-C++之异常
c++
tod1132 小时前
C++核心知识点全解析(四)
开发语言·c++·面试经验
闻缺陷则喜何志丹2 小时前
【计算几何 CAD】三点画弧、三点画圆是否是三角形的外接圆
c++·计算几何·cad··外接圆·
今儿敲了吗2 小时前
28| A-B数对
数据结构·c++·笔记·学习·算法
希望之晨3 小时前
c++ 11 学习 函数模板
linux·开发语言·c++
今儿敲了吗3 小时前
27| 魔法封印
数据结构·c++·笔记·学习·算法