关于标准库中的string类 - c++

news2024/11/15 17:34:29

目录

关于string类

string类的常用接口

string类常用接口的简单模拟实现


关于string类

string类在cplusplus.com的文档介绍

  • 1. string是表示字符串的字符串类
  • 2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  • 3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
  • 4. 不能操作多字节或者变长字符的序列。
在使用 string 类时,必须包含 #include<string> 头文件以及 using namespace std;

string类的常用接口

 string 类对象的常见构造/析构
void Teststring()
{
     string s1; // 构造空的string类对象s1

     string s2("hello world"); // 用C格式字符串构造string类对象s2

     string s3(s2); // 拷贝构造s3
}

string 类对象的容量操作
#include <iostream>
using namespace std;

#include <string>

//-----------------------------------------------------------------------
//测试string容量相关接口 
//size / capacity / clear / resize

void Teststring()
{
	//注意:string类对象支持直接用cin和cout进行输入和输出
	string s1("hello world");

	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	//将s1中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s1.clear();
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	//将s1中的有效字符个数增加到6个,并且使用'*'来进行填充
	s1.resize(6, '*');
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	// 将s1中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// 注意此时s中有效字符个数已经增加到15个
	s1.resize(15);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	//另外,resize也有删除有效字符的功能,但是不会改变底层空间的大小
	s1.resize(1);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;
}


//-----------------------------------------------------------------------
//测试reserve
//1.是否改变string类中间的有效元素个数
//2.当reserve参数小于string底层空间时,是否会将空间缩小

void TestReserve()
{
	string s1;
	s1.reserve(100);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	s1.reserve(10);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
}


int main()
{
	 Teststring();

	 TestReserve();

	 return 0;
}

注意:
  • 1. size()length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
  • 2. clear()只是将string中有效字符清空,不改变底层空间大小。
  • 3. resize(size_t n) resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  • 4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
string 类对象的访问及遍历操作
#include <iostream>
using namespace std;

#include <string>

//---------------------------------------------------------------------
// string的遍历及访问
// 1.迭代器:begin()+end() / rbegin()+ rend() ...
// 2.for+operator[]  
// 3.范围for:底层实现是使用迭代器,实际上是迭代器的封装 (范围forC++11后才支持)


void Teststring()
{
	string s1("hello world");
	
	//1.
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;

	//2.
	for (size_t i = 0; i < s1.size(); ++i)
	{
		cout << s1[i];
	}
	cout << endl;

	//3.
	for (auto ch : s1)
	{
		cout << ch;
	}
	cout << endl;
}

int main()
{
	Teststring();

	return 0;
}

string 类对象的修改操作
#include <iostream>
using namespace std;

#include <string>

//---------------------------------------------------------------------
//string的测试与修改
//1.插入(拼接)方式:push_back / append / operator+=
//2.任意位置插入:insert
//3.查找 / 反向查找:find / rfind
//4.替换字符:replace 
//5.截取子串 :substr
//6.读取字符:geline
//7.删除:erase
//8.返回c形式字符串 : c_str

void TestString()
{
	string s1("hello world");

	//插入一个字符
	s1.push_back(' ');
	s1.push_back('i');  
	cout << s1 << endl;

	//插入一串字符
	s1.append(" ");
	s1.append("love");
	cout << s1 << endl;

	//c++更喜欢使用+=
	s1 += ' ';
	s1 += "you";
	cout << s1 << endl;
}


void TestInsert()
{
	string s1("hello");

	//任意位置插入
	s1.insert(5, 1, ' ');
	s1.insert(6, "world");
	cout << s1 << endl;

	//使用迭代器
	s1.insert(s1.begin(), '!');
	s1.insert(s1.begin()+1, ' ');
	cout << s1 << endl;
}

void TestFind_Replace()
{
	//笔试题:将下列所有空格都改成 %20

	//思路1.
	string str("hello world i love you");

	size_t pos = str.find(' ');

	while (pos != string::npos)
	{
		str.replace(pos, 1, "%20");
		pos = str.find(' ');
	}
	cout << str << endl;

	//====================================
	//优化:

			//string str("hello world i love you");

			1.提前开空间,减少扩容消耗的效率
			//size_t num = 0;
			//for (auto ch : str)
			//{
			//	if (ch == ' ')
			//	{
			//		num++;
			//	}
			//}
			//str.reserve(str.size() + 2 * num);

			//size_t pos = str.find(' ');

			//while (pos != string::npos)
			//{
			//	str.replace(pos, 1, "%20");

			//	//2.避免重复访问数据,提高效率
			//	pos = str.find(' ', pos+3);
			//}
			//cout << str << endl;


	//==================================
	//思路2.

			//string str("hello world i love you");

			//string newStr;

			//size_t num = 0;
			//for (auto ch : str)
			//{
			//	if (ch == ' ')
			//	{
			//		num++;
			//	}
			//}
			//newStr.reserve(newStr.size() + 2 * num);

			//for (auto ch : str)
			//{
			//	if (ch != ' ')
			//	{
			//		newStr += ch;
			//	}
			//	else
			//	{
			//		newStr += "%20";
			//	}
			//}

			//cout << newStr << endl;

}

void TestFind_Substr()
{
	//笔试题:获取ulr中的域名

	string url("http://www.cplusplus.com/reference/string/");

	cout << url << endl;

	size_t start = url.find("://");

	if (start == string::npos)
	{
		cout << "invalid url" << endl;
		return;
	}

	start += 3;

	size_t finish = url.find('/', start);

	string address = url.substr(start, finish - start);

	cout << address << endl;

}

void TestRfind_Getline()
{
	//笔试题:求字符串最后出现单词的长度
	string str;

	//注意:为什么这里使用 getline 而不使用 cin 
	//因为 cin 类似于scanf ,读取字符遇到空格或者\0就会终止,无法读取后面的字符
	//使用c++提供了 getline :遇到换行符才会终止读取
	getline(cin, str);

	size_t pos = str.rfind(' ');

	if (pos != string::npos)
	{
		cout << str.size() - pos - 1 << endl;
	}
	else
	{
		//只有一个单词的情况
		cout << str.size() << endl;
	}

}


void TestErase()
{
	string str("hello world");

	str.erase(5, 1);
	cout << str << endl;

	str.erase(5, 10);
	cout << str << endl;

	str.erase(1);
	cout << str << endl;
}

void TestC_str()
{
	string str("hello world");

	cout << str << endl;
	cout << str.c_str() << endl;

	cout << str << endl;
	cout << (void*)str.c_str() << endl;

	str += ' ';
	str += '\0';
	str += "******";

	cout << str << endl;
	cout << str.c_str() << endl;
}


int main()
{
	TestString();

	TestInsert();

	TestFind_Replace();

	TestFind_Substr();

	TestRfind_Getline();

	TestErase();

	TestC_str();

	return 0;
}
注意:
  • 1. string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  • 2. string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

string类常用接口的简单模拟实现

#include <iostream>

using namespace std;

#include <assert.h>
#include<string>

class _string
{

public:
	//模拟实现常用接口
    //...

private:
	char* _str;
	size_t size;
	size_t capacity;

	static const size_t npos;
};

const size_t string::npos = -1;

注:npos

       关于npos ,这里建议类里面声明,类外面定义,不建议在类里面给缺省值。因为成员变量给缺省值是因为会在初始化列表进行初始化,但是strtic修饰的静态成员变量,不能给缺省值,因为静态成员变量存储位置在静态区,属于整个工程。

        另外,c++11有一个值得吐槽的地方,就是开了一个特例,如果加const ,那么整型静态成员变量可以给缺省值,如下图:

1.构造函数
	_string()
		:_str(new char[1])
		, _size(0)
		,_capacity(0)
	{
		_str[0] = '\0';
	}

	_string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}

问:为什么无参的字符串构造函数 _str(new char[1])  初始化不给 nullptr 要给一个空间且加上[ ]?

答:因为如果给nullptr的话,cout是会对_str进行解引用,这样会导致程序崩溃,所以才会给一个空间。而给[ ]是为了在析构的时候与delete[ ] 保持一致。

问:为什么 _str = new char[_capacity + 1] 中要+1?

答:因为_capacity是容量字符,指的是能够存取多少个有效字符,而vs认为 '\0'属于标识符,不属于有效字符的范畴,所以+1是为了给'\0'预留空间。

  • 优化:以上两个函数可以优化为缺省函数
	_string(const char* str = "\0")
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}

1.1拷贝构造
	_string(const _string& str)
		:_size(str._size)
		,_capacity(str._capacity)
	{
		_str = new char[_capacity + 1];
		strcpy(_str, str._str);
	}

2.赋值 =

	_string operator=(const _string& str)
	{
		delete[] _str;
		_str = new char[str._capacity + 1];
		strcpy(_str, str._str);
		_size = str._size;
		_size = str._capacity;

        return *this;
	}
  • 上述代码有一个问题,就是程序开始就将_str的空间进行释放,这样可能会导致数据的丢失,所以需要进行优化。
	_string operator=(const _string& str)
	{
		if (this != &str)
		{
			char* tmp = new char[str._capacity + 1];
			strcpy(tmp, str._str);
			delete[] _str;
			_str = tmp;
			_size = str._size;
			_capacity = str._capacity;
		}

		return *this;
	}

3.析构函数 ~

	~_string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}

4.成员访问

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

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

	size_t size() const
	{
		return _size;
	}

	size_t capacity() const
	{
		return _capacity;
	}

	const char* c_str()
	{
		return _str;
	}

5.简单迭代器 iterator
	typedef char* iterator;

	iterator begin()
	{
		return _str;
	}

	iterator end()
	{
		return _str + _size;
	}

6.关系运算符(relational operators)string

    bool operator>(const _string& str) const
	{
		return strcmp(_str, str._str) > 0;
	}

	bool operator==(const _string& str) const
	{
		return strcmp(_str, str._str) == 0;
	}

	bool operator>=(const _string& str) const
	{
		return *this > str || *this == str;
	}

	bool operator<(const _string& str) const
	{
		return !(*this >= str);
	}

	bool operator<=(const _string& str) const
	{
		return !(*this > str);
	}

	bool operator!=(const _string& str) const
	{
		return !(*this == str);
	}

7.reserve

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

问:这里为什么要加个 if 进行判断?

答:因为如果不进行判断,当 n < _capacity 的时候,会导致缩容的问题,对程序的安全造成隐患,因此要加个判断,避免出现缩容的情况。

8.push_back
	void push_back(char ch)
	{
		if (_size + 1 > _capacity)
		{
			reserve(_capacity * 2);
		}

		_str[_size] = ch;
		_size++;

		_str[_size] = '\0';
	}

       上述代码有一个隐藏的问题,那就是如果 _capacity 如果为 0 ,这个时候进行push_back,代码就会越界,从而崩溃。解决办法有两个,一是修改一下构造函数,而是进行判断,如果_capacity 为 0 就直接进行赋值。

优化:
	_string(const char* str = "\0")
		:_size(strlen(str))
	{
		_capacity = _size == 0 ? 3 : _size;
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}
9.append
	void append(const char* str)
	{
		if (_size + 1 > _capacity)
		{
			reserve(_capacity * 2);
		}

		size_t len = strlen(str);

		strcpy(_str + _size, str);
		_size += len;
	}

问:这里为什么不适用strcat?

答:因为 strcat 是自己去寻找 '\0' 的位置,而 _str + len 就是 '\0' 的位置 ,strcpy 函数会把需要拷贝的字符串最后的位置的 '\0'拷贝过来。

10.+=
	_string& operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

	_string& operator+=(const  char* str)
	{
		append(str);
		return *this;
	}

11.resize
void resize(size_t n, char ch = '\0')
{
	if (n <= _size)
	{
		//保留前n个 - 删除数据
		_size = n;
		_str[_size] = '\0';
	}
	else  //n > size
	{
		if (n > _capacity)
		{
			reserve(n);
		}

		size_t i = _size;
		while (i < n)
		{
			_str[i++] = ch;
		}

		_size = n;
		_str[_size] = '\0';
	}
}

12.insert
	void insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size + 1 > _capacity)
		{
			reserve(_capacity * 2);
		}

		size_t end = _size;
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			--end;
 		}

		_str[pos] = ch;
		++_size;
	}

        以上代码有个问题,那就是 end 属于无符号整型,而 pos 也属于无符号整型,这里会造成程序的死循环。即使把 end 置成 int 类型,但是也会发生隐式类型转换,有符号会转换成无符号。也不建议进行强转,所以解决办法的话,就是修改挪动数据的逻辑。

优化:

	void insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size + 1 > _capacity)
		{
			reserve(_capacity * 2);
		}

		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}

		_str[pos] = ch;
		++_size;
	}

当然,insert 还需要重载插入字符串

	void insert(size_t pos, const char* str)
	{
		assert(pos <= _size);

		size_t len = strlen(str);

		if (_size + len > _capacity)
		{
			reserve(_capacity * 2 + len);
		}

		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		strncpy(_str + pos, str, len);
		_size += len;
	}

这里之所以选择 strncpy 而不是 strcpy ,因为 strcpy 会拷贝字符串结尾的标识符 '\0'。

13.erase
	void erase(size_t pos, size_t len = npos)
	{
		//尾部直接删除
		if (pos + len > _size || len == npos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else //挪动数据
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	}

14.swap
	void swap(_string& str)
	{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
	}

15.find
	size_t find(char ch, size_t pos = 0)
	{
		for (size_t i = pos; i < _size; ++i)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}

		return npos;
	}

	size_t find(const char* str, size_t pos = 0)
	{
		assert(pos <= _size);

		char* p = strstr(_str + pos, str); //strstr : 返回第一次指针匹配的位置
		if (p == nullptr)
		{
			return npos;
		}
		else
		{
			return p - _str;
		}
	}

17.<<
       
ostream& operator<<(ostream& out, const string& str)
{
	for (auto ch : str)
	{
		out << str;
	}

	return out;
}

        注:因为在类域中存在this指针,而友元函数又会增加耦合度,破环封装,所以这里建议流插入或者流提取的实现不写在类域之中。

18.>>        
istream& operator>>(istream& in, string& str)
{
	char ch;
	in >> ch;
	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		in >> ch;
	}

	return in;
}

         问:为什么如果输入多组字符,中间用空格或者换行隔开的话,编译器只能拿到第一组字符,拿不到后面的字符。

答:因为cin或者sancf读取的时候会默认忽然空格或者换行,不进行识别,默认空格或者换行是多个值之间的间隔。流提取并未在输入中获取字符,而是在缓冲区获取字符,而空格或者换行未进入缓冲区,c++/c 规定,值与值之间的区分必须是空格或者换行,所以输入空格或者换行会被认为是多个字符之间的间隔,不会被cin 或者 scanf 拿到。

优化:

istream& operator>>(istream& in, string& str)
{
	str.clear();  //清除掉之前的字符

	char ch = in.get(); //get()函数不区分间隔

	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		ch = in.get();
	}

	return in;
}

上述版本只是一个简单的版本,实际实现可能有些复杂

istream& operator>>(istream& in, string& str)
{
	str.clear();  //清除掉之前的字符

	char ch = in.get(); //get()函数不区分间隔

	char buff[128];
	size_t i = 0;

	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[127] = '\0';
			str += buff;
			i = 0;
		}

		ch = in.get();
	}

	if (i != 0) //防止最后的数据没有添加进去
	{
		buff[i] = '\0';
		str += buff;
	}

	return in;
}

接口实现代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>

using namespace std;

#include <string>
#include <assert.h>

class _string
{

public:
	typedef char* iterator;

	iterator begin()
	{
		return _str;
	}

	iterator end()
	{
		return _str + _size;
	}

	//模拟实现 

		//_string()
		//	:_str(new char[1])
		//	, _size(0)
		//	,_capacity(0)
		//{
		//	_str[0] = '\0';
		//}

	_string(const char* str = "\0")
		:_size(strlen(str))
	{
		_capacity = _size == 0 ? 3 : _size;
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}

	_string(const _string& str)
		:_size(str._size)
		, _capacity(str._capacity)
	{
		_str = new char[_capacity + 1];
		strcpy(_str, str._str);
	}

	_string operator=(const _string& str)
	{
		if (this != &str)
		{
			char* tmp = new char[str._capacity + 1];
			strcpy(tmp, str._str);
			delete[] _str;
			_str = tmp;
			_size = str._size;
			_capacity = str._capacity;
		}

		return *this;
	}

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

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


	size_t size() const
	{
		return _size;
	}

	size_t capacity() const
	{
		return _capacity;
	}

	const char* c_str()
	{
		return _str;
	}

	bool operator>(const _string& str) const
	{
		return strcmp(_str, str._str) > 0;
	}

	bool operator==(const _string& str) const
	{
		return strcmp(_str, str._str) == 0;
	}

	bool operator>=(const _string& str) const
	{
		return *this > str || *this == str;
	}

	bool operator<(const _string& str) const
	{
		return !(*this >= str);
	}

	bool operator<=(const _string& str) const
	{
		return !(*this > str);
	}

	bool operator!=(const _string& str) const
	{
		return !(*this == str);
	}

	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 + 1 > _capacity)
		{
			reserve(_capacity * 2);
		}

		_str[_size] = ch;
		_size++;

		_str[_size] = '\0';
	}

	void append(const char* str)
	{
		if (_size + 1 > _capacity)
		{
			reserve(_capacity * 2);
		}

		size_t len = strlen(str);

		strcpy(_str + _size, str);
		_size += len;
	}

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

	_string& operator+=(const  char* str)
	{
		append(str);
		return *this;
	}

	void resize(size_t n, char ch = '\0')
	{
		if (n <= _size)
		{
			//保留前n个 - 删除数据
			_size = n;
			_str[_size] = '\0';
		}
		else  //n > size
		{
			if (n > _capacity)
			{
				reserve(n);
			}

			size_t i = _size;
			while (i < n)
			{
				_str[i++] = ch;
			}

			_size = n;
			_str[_size] = '\0';
		}
	}

	void insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size + 1 > _capacity)
		{
			reserve(_capacity * 2);
		}

		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}

		_str[pos] = ch;
		++_size;
	}


	void insert(size_t pos, const char* str)
	{
		assert(pos <= _size);

		size_t len = strlen(str);

		if (_size + len > _capacity)
		{
			reserve(_capacity * 2 + len);
		}

		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		strncpy(_str + pos, str, len);
		_size += len;
	}

	void erase(size_t pos, size_t len = npos)
	{
		//尾部直接删除
		if (pos + len > _size || len == npos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else //挪动数据
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	}

	void swap(_string& str)
	{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
	}

	size_t find(char ch, size_t pos = 0)
	{
		for (size_t i = pos; i < _size; ++i)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}

		return npos;
	}

	size_t find(const char* str, size_t pos = 0)
	{
		assert(pos <= _size);

		char* p = strstr(_str + pos, str); //strstr : 返回第一次指针匹配的位置
		if (p == nullptr)
		{
			return npos;
		}
		else
		{
			return p - _str;
		}
	}

	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}

	~_string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}


private:
	char* _str;
	size_t _size;
	size_t _capacity;

	//static const size_t npos;

	static const size_t npos = -1;
};

//const size_t string::npos = -1;

ostream& operator<<(ostream& out, const string& str)
{
	for (auto ch : str)
	{
		out << str;
	}

	return out;
}

istream& operator>>(istream& in, string& str)
{
	str.clear();  //清除掉之前的字符

	char ch = in.get(); //get()函数不区分间隔

	char buff[128];
	size_t i = 0;

	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[127] = '\0';
			str += buff;
			i = 0;
		}

		ch = in.get();
	}

	if (i != 0) //防止最后的数据没有添加进去
	{
		buff[i] = '\0';
		str += buff;
	}

	return in;
}


以上仅代表个人看法,欢迎讨论

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

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

相关文章

SEO与 SMO 的区别

SEO和SMO都是为网站带来流量的方法&#xff0c;但二者截然不同。搜索引擎优化&#xff08;SEO&#xff09;是一种在搜索引擎结果页面上提高网站流量的技术&#xff0c;而社交媒体优化&#xff08;SMO&#xff09;则是一种让更多用户看到社交媒体内容的技术。 阅读本文&#xf…

开源之夏2023 MatrixOne 项目结业啦

开源之夏是由中国科学院软件研究所与 OpenEuler 社区共同主办的一项面向高校学生的暑期在线活动&#xff0c;旨在鼓励在校学生积极参与开源软件的开发维护&#xff0c;促进优秀开源软件社区的蓬勃发展。 在开源之夏 2023 年中&#xff0c;MatrixOne 一共有 2 个任务项目&#…

智能座舱架构与芯片- (11) 软件篇 上

一、智能汽车基础软件平台分类 汽车软件主要分为应用软件和基础软件。应用软件和业务形态高度关联&#xff0c;不同控制器的应用软件之间差异较大。基础软件介于应用软件和硬件之间&#xff0c;用于屏蔽硬件特性、支撑应用软件。可有效地实现应用软件与硬件之间解耦&#xff0…

MySQL - 4种基本索引、聚簇索引和非聚索引、索引失效情况

目录 一、索引 1.1、简单介绍 1.2、索引的分类 1.2.1、主键索引 1.2.2、单值索引&#xff08;单列索引、普通索引&#xff09; 1.2.3、唯一索引 1.2.4、复合索引 1.2.5、复合索引经典问题 1.3、索引原理 1.3.1、主键自动排序 1.3.2、索引的底层原理 1.3.3、B 树和 B…

【Web】Ctfshow XSS刷题记录

目录 反射型XSS ①web316 ②web317-319 ③web320-322 ④web323-326 存储型XSS ①web327 ②web328 ③web329 ④web330 ⑤web331 ⑥web332-333 反射型XSS ①web316 直接输入<script>alert(1)</script>,能弹窗。xss题目一般会有个bot&#xff0c;可以触…

【精选】OpenCV多视角摄像头融合的目标检测系统:全面部署指南&源代码

1.研究背景与意义 随着计算机视觉和图像处理技术的快速发展&#xff0c;人们对于多摄像头拼接行人检测系统的需求日益增加。这种系统可以利用多个摄像头的视角&#xff0c;实时监测和跟踪行人的活动&#xff0c;为公共安全、交通管理、视频监控等领域提供重要的支持和帮助。 …

易航网址引导系统 v1.9 源码:去除弹窗功能的易航网址引导页管理系统

易航自主开发了一款极其优雅的易航网址引导页管理系统&#xff0c;后台采用全新的光年 v5 模板开发。该系统完全开源&#xff0c;摒弃了后门风险&#xff0c;可以管理无数个引导页主题。数据管理采用易航原创的JsonDb数据包&#xff0c;无需复杂的安装解压过程即可使用。目前系…

HTML5生成二维码

H5生成二维码 前言二维码实现过程页面实现关键点全部源码 前言 本文主要讲解如何通过原生HTML、CSS、Js中的qrcodejs二维码生成库&#xff0c;实现一个输入URL按下回车后输出URL。文章底部有全部源码&#xff0c;需要可以自取。 实现效果图&#xff1a; 上述实现效果为&#…

【机器学习】贝叶斯分类器

贝叶斯分类器是一种概率模型&#xff0c;利用贝叶斯公式来解决分类问题。假设样本的特征向量服从一定的概率分布&#xff0c;我们就可以计算出该特征向量属于各个类的条件概率。分类结果是条件概率最大的分类结果。如果假设特征向量的每个分量彼此独立&#xff0c;则它是朴素贝…

java.lang.UnsupportedOperationException 关于Arrays.asList问题解决

解析String 字符串为List集合ArrayList<String> itemsList Arrays.asList(items.split("\\|")List<String> itemsList Arrays.asList(items.split("\\|")final Iterator<String> iterator itemsList.iterator();while (iterator.hasNex…

【数据结构(四)】栈(1)

文章目录 1. 关于栈的一个实际应用2. 栈的介绍3. 栈的应用场景4. 栈的简单应用4.1. 思路分析4.2. 代码实现 5. 栈的进阶应用(实现综合计算器)5.1. 栈实现一位数计算(中缀表达式)5.1.1. 思路分析5.1.2. 代码实现 5.2. 栈实现多位数计算(中缀表达式)5.2.1. 解决思路5.2.2. 代码实…

【Java程序员面试专栏 专业技能篇】Java SE核心面试指引(二):面向对象思想

关于Java SE部分的核心知识进行一网打尽,包括四部分:基础知识考察、面向对象思想、核心机制策略、Java新特性,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 本篇Blog为第二部分:面向对象思想,子节点表示追问或同级提问 面向对象基…

《多GPU大模型训练与微调手册》

全参数微调 Lora微调 PTuning微调 多GPU微调预备知识 1. 参数数据类型 torch.dtype 1.1 半精度 half-precision torch.float16&#xff1a;fp16 就是 float16&#xff0c;1个 sign&#xff08;符号位&#xff09;&#xff0c;5个 exponent bits(指数位)&#xff0c;10个 ma…

[机缘参悟-119] :一个IT人的反思:反者道之动;弱者,道之用 VS 恒者恒强,弱者恒弱的马太效应

目录 前言&#xff1a; 一、道家的核心思想 二、恒者恒强&#xff0c;弱者恒弱的马太效应 三、马太效应与道家思想的统一 3.1 大多数的理解 3.2 个人的理解 四、矛盾的对立统一 前言&#xff1a; 马太效应和强弱互转的道家思想&#xff0c;都反应了自然规律和社会规律&…

【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏3(附项目源码)

文章目录 先本文看看最终效果前言二叉空间分割算法房间优先生成使用走廊连接各个房间BSP和随机游走源码完结 先本文看看最终效果 前言 前两期我们使用了随机游走算法已经实现了地牢的生成&#xff0c;本期再说另外一种生成地牢的方法&#xff0c;使用二叉空间分割算法&#xf…

【Python数据结构与算法】--- 递归算法应用-五行代码速解汉诺塔问题.

&#x1f308;个人主页: Aileen_0v0 &#x1f525;系列专栏:PYTHON数据结构与算法学习系列专栏&#x1f4ab;"没有罗马,那就自己创造罗马~" 汉诺塔 两层汉诺塔的演示 三层汉诺塔的走法演示 我不知道有没有朋友跟我一样有一个疑问,如果我们顶端的先放到中间柱子呢?…

【算法】二分查找-20231122

这里写目录标题 一、1089. 复写零二、917. 仅仅反转字母三、88. 合并两个有序数组四、283. 移动零 一、1089. 复写零 提示 简单 266 相关企业 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&a…

visionOS空间计算实战开发教程Day 2 使用RealityKit显示3D素材

我们在​​Day1​​中学习了如何创建一个visionOS应用&#xff0c;但在第一个Demo应用中我们的界面内容还是2D的&#xff0c;看起来和其它应用并没有什么区别。接下来我们先学习如何展示3D素材&#xff0c;苹果为方便开发人员&#xff0c;推出了RealityKit&#xff0c;接下来看…

Qml使用cpp文件的信号槽

文章目录 一、C文件Demo二、使用步骤1. 初始化C文件和QML文件&#xff0c;并建立信号槽2.在qml中调用 一、C文件Demo Q_INVOKABLE是一个Qt元对象系统中的宏&#xff0c;用于将C函数暴露给QML引擎。具体来说&#xff0c;它使得在QML代码中可以直接调用C类中被标记为Q_INVOKABLE的…

ventoy安装操作系统

下载ventoy https://github.com/ventoy/Ventoy/releases/download/v1.0.96/ventoy-1.0.96-windows.zip 解压后执行 Ventoy2Disk 2、安装后将ISO放入U盘大的分区&#xff0c;通过U盘启动就可以识别到ISO镜像开始装系统