<C++> 模拟实现string

目录

前言

一、模拟实现string

1. 成员变量

2. 构造函数

2.1 构造函数

2.2 重载默认构造 

2.3 合并

3. 析构函数 

4. 拷贝构造函数 

5. c_str

6. size

7. operator[ ] 

7.1 普通版

7.2 const版本 

8. 迭代器—iterator 

8.1 普通版iterator

8.2 const版本iterator

9. 尾插

10. operator+=

11. insert

11.1 插入n个字符 

11.2 插入字符串 

12. erase 

12.1 从pos位置开始删除

13. find

13.1 查找字符

13.2 查找字符串

14. substr 

15. reseve && resize

16. operator<< 流插入

17. operator>> 流提取

18. string比较

19. operator= 赋值运算符重载

 二、整体代码

总结


前言

        学习了string的用法,为了提升自身能力,我们来模拟实现string的部分功能


一、模拟实现string

初始化列表初始化顺序是声明顺序!

我们将自己实现的string放在自己的命名空间内,防止与std中的string冲突

namespace my_string
{class string{};
};

1. 成员变量

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

2. 构造函数

 2.1 构造函数

构造函数功能:为字符串开空间,并根据传入的参数进行初始化操作。

        此处会出现一些问题,当我们使用初始化列表时,每个人的初始化顺序可能不同,并且成员变量的声明顺序也可能不同,编写完代码后,发现出错,代码不能跑,这就是初始化列表的知识点——初始化列表的初始化顺序由成员变量的声明顺序决定。

        并且为了少次使用strlen这一O(N)级函数,我们可以只将一个变量在初始化列表初始化,其他成员在函数内赋值(以达到少次使用strlen函数),这是因为这些成员变量是内置类型(内置类型在列表或构造函数体内初始化区别不大),如果是自定义类型则不能在内部赋值,因为即使不写,它们仍然会被自动调用各自的构造函数。

        这里我们将全部的成员变量都放在函数体内赋值,以便代码清晰。

namespace my_string
{class string{public:string(const char* str){_size = strlen(str);_capacity = _size;//加1,存放\0_str = new char[_capacity + 1];strcpy(_str, str);}private:size_t _size;size_t _capacity;char* _str;};
};

2.2 重载默认构造 

        那么,当我们不传参构造,只实例化时,编译器找不到默认构造函数,所以我们要重载一个默认构造函数

        根据监视,可以看到C++的string在无参构造时,它是有一个‘\0'的,所以我们在初始化_str不能置空要至少开一个空间,来存储 '\0'

string(const char* str):_size(0),_capacity(0),_str(new char[1])
{_str[0] = '\0';
}

 2.3 合并

我们可以使用缺省值,将两个构造函数合并

这里的问题在于,如何设置缺省值?是 '\0' ,还是 nullptr, 还是 "\0"

  1. '\0' 不可以,问题在于左边类型为char*,而'\0'是字符,两者类型不同
  2. nullptr 也不可以,问题在于后面的strlen直接失效,并且没有 '\0'
  3. "\0" 不太合适,因为 “” 内默认有\0,所以缺省值应为“”
string(const char* str = "")
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);
}

注意:该构造函数代码之后会在operator<<处再次修改 

3. 析构函数 

  • 释放new出的空间,并将_size与_capacity置空即可
~string()
{delete[] _str;_str = nullptr;_size = _capacity = 0;
}

4. 拷贝构造函数 

//拷贝构造
string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}

注意:该构造函数代码之后会在operator<<处再次修改  

5. c_str

  •  模拟string中的c_str函数功能,返回c形式的字符串,即返回 _str
  • 在函数后面加上const,可以使被const修饰的对象也可调用该函数
const char* c_str() const
{return _str;
}

6. size

  •  返回_size即可
size_t size() const
{return _size;
}

7. operator[ ] 

7.1 普通版

  • 为了可读可写,我们使用引用返回,因为返回的对象是数组内的元素,生命周期不在operator[ ]函数内,所以可以使用引用返回
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}

7.2 const版本 

  • const版本只能读,与普通版本形成函数重载,调用函数时,编译器会调用最匹配的函数
//const版本 只读
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

8. 迭代器—iterator 

8.1 普通版iterator

  • 对于string类,迭代器可以使用指针实现,即迭代器就是指针(针对string类)
//迭代器,在string这里就是指针
typedef char* iterator;iterator begin()
{return _str;
}iterator end()
{return _str + _size;
}

 测试:

#include<iostream>
#include"string模拟.h"
using namespace my_string;
using std::cout;
using std::endl;void test()
{my_string::string s1("hello world");cout << s1.c_str() << endl;string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;for (auto ch : s1){cout << ch << "";}cout << endl;
}
hello world
h e l l o  w o r l d
hello world

可以看到,我们在写完迭代器之后,范围for就可以直接使用了,因为它就是利用迭代器实现的,底层与使用迭代器遍历几乎一样。 

如果我们把end(),改为End(),那么范围for就不可用了。所以,范围for是傻瓜式替换,找到类中的begin(),end(),字母错一个它都实现不了。

8.2 const版本iterator

  • 当对象被const修饰,那么迭代器就要加上const,保证值不被修改
typedef const char* const_iterator;
const_iterator begin() const
{return _str;
}const_iterator end() const
{return _str + _size;
}
string::const_iterator cit = s1.begin();
while (cit != s1.end())
{cout << *cit << endl;++cit;
}
cout << endl;

 9. 尾插

  1. 判断是否要扩容对于push_back() 函数可以进行2倍扩容对于append() 函数,要进行大小判断,如果_size + strlen(str) > _ capacity 那么至少扩容到_size + len 的容量大小。
  2. 对于扩容操作,我们实现reserve() 函数,进行扩容处理
void reserve(size_t n)
{if (n > _capacity){//多开一个空间留给 '\0'char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}void push_back(char ch)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';
}void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;
}

 10. operator+=

  • 实现尾插之后,operator+=的实现就非常简单了,+=是有返回值的,返回的是string类型
  • 可以返回*this,因为*this是对象,对象的生命周期不在operator+=中,所以可以返回*this
string& operator+=(char ch)
{push_back(ch);return *this;
}string& operator+=(const char* str)
{append(str);return *this;
}

 11. insert

  1. 先判断下标是否合法
  2. 再判断是否要扩容
  3. 移位,移位时有“坑”,因为是size_t 类型,大于等于0,-1是最大正数,如果end改为int类型也不行,因为会发生隐式类型转换。这里有多种解决方案:可以强制类型转换、可以将end从end+n开始,进行end - n 赋给 end ,这样就不会到下标为0的地方、用npos判断(npos使用静态成员函数,赋值为-1,表示最大值)
  4. 插入数据
  5. 更新_size

11.1 插入n个字符 

void insert(size_t pos, size_t n, char ch)
{//size_t大于0,不用判断assert(pos <= _size);if (_size + n > _capacity){//至少扩容到 _size + nreserve(_size + n);}size_t end = _size;//size_t因为不小于0,当pos为0时,会陷入死循环//这里有多种方案,可以强制类型转换、可以将end从end+n开始、用npos判断while (end >= pos && end != npos){_str[end + n] = _str[end];--end;}for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;
}

11.2 插入字符串 

  • 同理,将n替换为len即可
void insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size;while (end >= pos && end != npos){_str[end + len] = _str[end];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;
}

12. erase 

12.1 从pos位置开始删除

  1. 先判断下标合法
  2. 再判断是不是要删到尾部,如果是,直接把pos处改为 '\0' ,并修改_size值即可
  3. 如果不是,就是在内部删除,和insert操作类似,移动
void erase(size_t pos, size_t len = npos)
{assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;_str[_size] = '\0';}else{size_t begin = pos + len;//等于size,把\0也移动过去while (begin <= _size){_str[pos++] = _str[begin++];}_size -= len;}
}

 13. find

13.1 查找字符

size_t find(char ch, size_t pos = 0)
{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch)return i;}//找不到,返回nposreturn npos;
}

 13.2 查找字符串

size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);const char* ptr = strstr(_str, str);if (ptr){return ptr - _str;}else{return npos;}
}

14. substr 

  • 注意for循环中 i < pos + n,别误写为 i < n
string substr(size_t pos, size_t len = npos)
{assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){n = _size - pos;}string tmp;tmp.reserve(n);//注意i < pos + nfor (size_t i = pos; i < pos + n; i++){tmp += _str[i];}return tmp;
}

15. reseve && resize

		void reserve(size_t n){if (n > _capacity){//多开一个放'\0'char* tmp = new char[n + 1];//strcpy(tmp, _str);memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}

        void resize(size_t n, char ch = '\0'){if (n < _size){_size = n;_str[_size] = '\0';}else{//利用reserve进行扩容判断reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;//由于ch不一定是\0,而且i < n没到最后的\0位置_str[_size] = '\0';}}

16. operator<< 流插入

流插入,有多少字符就输出多少字符,中间有'\0'也不终止

注意将该函数放在命名空间域中,还是全局函数

//流插入,有多少字符就输出多少字符,中间有'\0'也不终止std::ostream& operator<<(std::ostream& out, const string& s){//要访问私有变量才用友元for (auto i : s){out << s[i];}return out;}

        直接使用 c_str 和 << 在一般情况是相同的,但是c_str是c字符串,遇到\0自动终止而string则是依靠size来判断,size有多大,operator<<就打印多长 

例如 :

void test()
{//与c的字符串不同,c的字符串遇到\0终止,而string是直到size终止string ss("hello world");ss += '\0';ss += "LLLLLLLLLL";cout << ss.c_str() << endl;cout << ss << endl;
}int main()
{test();return 0;
}
hello world
hello wordl LLLLLLLLLL

        可以看到第二个打印有空格,空格表示了 '\0'

        所以,有了\0,之前用的str系列的函数就需要修改了,因为str类型的函数遇到\0自动结束,而string是遇到size才停止,将之前的str函数改为memcpy函数

  • 对于构造函数,可以不修改,因为本身构造时,用的就是c字符串,strlen以及strcpy都不需要修改,这里修改strcpy是为了与下面一致
        //构造string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];memcpy(_str, str, _size + 1);}//拷贝构造string(const string& s){_str = new char[s._capacity + 1];memcpy(_str, s._str, s._size + 1);_size = s._size;_capacity = s._capacity;}
  • 对于reserve也需要更改
  • 对于append可以不更改,因为追加的是c字符串,为了一致性,改为memcpy,拷贝到时候都默认加一个1,因为strcpy默认拷贝\0
		void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//strcpy(_str + _size, str);memcpy(_str + _size, str, len + 1);

17. operator>> 流提取

	//流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据std::istream& operator>>(std::istream& in, string& s){//不能写成 in >> s.str,因为s可能没空间来存放数据char ch;in >> ch;while (ch != ' ' && ch != '\n'){s += ch;in >> ch;}return in;}

        上面的代码有错误,当我们输入连续的字符时,不能读到空格和换行,就会陷入死循环,这是in本身的设定,以空格和换行符来区分多个数据,所以我们要使用 get 来提取终端数据 ——in.get(),可以读空格和换行

        当我们不想以空格为分隔符时,可以将 ch != '  ' 判断条件去掉

	//流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据std::istream& operator>>(std::istream& in, string& s){//不能写成 in >> s.str,因为s可能没空间来存放数据char ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}

对于cin操作,如果多次cin,应该会覆盖原先数据 ,即我们需要实现清理工作clear()

	void clear(){_str[0] = '\0';_size = 0;}//流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据std::istream& operator>>(std::istream& in, string& s){s.clear();//不能写成 in >> s.str,因为s可能没空间来存放数据char ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}

        优化:循环里的+=效率不高,因为如果字符串过长可能经历多次扩容。我们当然可以提前reserve开一定空间,但是当字符串过短,那么如果空间开的过大就浪费了。

        在这里,我们开一个128的char数组,暂存从终端提取的数据,积累一些数据,当数据超过数组大小后,尾插 \0 后将该数组+=上去,并将下标置为0,再次提取数据。

        当数据不够128时,跳出循环,所以要再循环外判断数组内是否还存有数据,再进行+=

	std::istream& operator>>(std::istream& in, string& s){s.clear();//不能写成 in >> s.str,因为s可能没空间来存放数据char ch = in.get();char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}

        但是对于前有空格和换行,后面输入的数据就进不去,所以再进行优化,加一个while循环,将前空格和换行去除。

	//流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据std::istream& operator>>(std::istream& in, string& s){s.clear();//不能写成 in >> s.str,因为s可能没空间来存放数据char ch = in.get();//处理前缓冲区-流-前面的空格或者换行while (ch == ' ' || ch == '\n'){ch = in.get();}char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}

18. string比较

  1. string按照ASCII进行比较大小
  2. 不能用strcmp,因为当遇到 例如:hello\0xxxxx 和 hello\0yyyyy 时,比较遇到 \0 就结束了,所以我们可以使用memcmp或手写
  3. 加const防止改变this值,以及可以使const对象调用该函数
        //方法一:bool operator<(const string& s) const{int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);return ret == 0 ? _size < s._size : ret < 0;}方法二://bool operator<(const string& s)//{//	//return strcmp(_str, s._str) < 0;由于\0,所以不能用strcmp//	size_t i1 = 0;//	size_t i2 = 0;//	while (i1 < _size && i2 < s._size)//	{//		if (_str[i1] < s._str[i2])//			return true;//		else if (_str[i1] > s._str[i2])//			return false;//		else//		{//			++i1;//			++i2;//		}//	}//	//在循环之外,只有一种情况是true//	//return i1 == _size && i2 != s._size;//	//或者判断size大小//	return _size < s._size;//}

 其他比较复用即可

        bool operator==(const string& s) const{//先比较size过滤各种情况return _size == s._size&& memcmp(_str, s._str, _size < s._size) == 0;}bool operator<=(const string& s) const{return (*this < s || *this == s);}bool operator>(const string& s) const{return !(*this <= s);}bool operator>=(const string& s) const{return !(*this < s);}bool operator!=(const string& s) const{return !(*this == s);}

 19. operator= 赋值运算符重载

 对于赋值,为深拷贝,则需要释放原空间,再开空间进行赋值

方法一: 开空间,赋值,再释放,赋值

		//赋值运算符重载——深拷贝string& operator=(const string& s){if (this != &s){//先开空间,赋值,再释放,赋值char* tmp = new char[s._capacity];memcpy(tmp, s._str, s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}

方法二:交换

        先根据s创建一个临时对象,再将临时对象和this的各个数据进行交换,这时this就指向了拷贝对象,临时对象tmp就指向了原空间,tmp出了作用域自动销毁,完美解决 

		//方法二:swap交换void swap(string& s){std::swap(s._str, _str);std::swap(s._size, _size);std::swap(s._capacity, _capacity);}string& operator=(const string& s){if (this != &s){//不能直接交换两个对象,会发生栈溢出、无穷递归//std::swap(tmp, *this);  一直调用赋值运算符重载string tmp(s);//this->swap(tmp);swap(tmp);}//tmp是局部对象,出函数自动销毁return *this;}

 方法三:再次优化,将形参改为string tmp,直接拷贝构造tmp,出函数作用域直接析构

		void swap(string& s){std::swap(s._str, _str);std::swap(s._size, _size);std::swap(s._capacity, _capacity);}//方法三:改变行参,直接拷贝构造tmpstring& operator=(string tmp){swap(tmp);return *this;}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 二、整体代码

my_string.h

#pragma once
#include<cstring>
#include<assert.h>
#include<iostream>namespace my_string
{class string{public://迭代器,在string这里就是指针typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//构造string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];memcpy(_str, str, _size + 1);}//拷贝构造string(const string& s){_str = new char[s._capacity + 1];memcpy(_str, s._str, s._size + 1);_size = s._size;_capacity = s._capacity;}赋值运算符重载——深拷贝//string& operator=(const string& s)//{//	if (this != &s)//	{//		char* tmp = new char[s._capacity];//		memcpy(tmp, s._str, s._size + 1);//		delete[] _str;//		_str = tmp;//		_size = s._size;//		_capacity = s._capacity;//	}//	return *this;//}//方法二:swap交换void swap(string& s){std::swap(s._str, _str);std::swap(s._size, _size);std::swap(s._capacity, _capacity);}//string& operator=(const string& s)//{//	if (this != &s)//	{//		//不能直接交换两个对象,会发生栈溢出、无穷递归//		//std::swap(tmp, *this);  一直调用赋值运算符重载//		string tmp(s);//		//this->swap(tmp);新手才写this o.0//		swap(tmp);//	}//	//tmp是局部对象,出函数自动销毁//	return *this;//}//方法三:改变行参,直接拷贝构造tmpstring& operator=(string tmp){swap(tmp);return *this;}//析构~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str() const{return _str;}size_t size() const{return _size;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//const版本 只读const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){//多开一个放'\0'char* tmp = new char[n + 1];//strcpy(tmp, _str);memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}void resize(size_t n, char ch = '\0'){if (n < _size){_size = n;_str[_size] = '\0';}else{//利用reserve进行扩容判断reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;//由于ch不一定是\0,而且i < n没到最后的\0位置_str[_size] = '\0';}}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//strcpy(_str + _size, str);memcpy(_str + _size, str, len);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}void insert(size_t pos, size_t n, char ch){//size_t大于0,不用判断assert(pos <= _size);if (_size + n > _capacity){//至少扩容到 _size + nreserve(_size + n);}size_t end = _size;//size_t因为不小于0,当pos为0时,会陷入死循环//这里有多种方案,可以强制类型转换、可以将end从end+n开始、用npos判断while (end >= pos && end != npos){_str[end + n] = _str[end];--end;}for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;}void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size;while (end >= pos && end != npos){_str[end + len] = _str[end];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;_str[_size] = '\0';}else{size_t begin = pos + len;//等于size,把\0也移动过去while (begin <= _size){_str[pos++] = _str[begin++];}_size -= len;}}string substr(size_t pos, size_t len = npos){assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){n = _size - pos;}string tmp;tmp.reserve(n);//注意i < pos + nfor (size_t i = pos; i < pos + n; i++){tmp += _str[i];}return tmp;}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch)return i;}//找不到,返回nposreturn npos;}size_t find(const char* str, size_t pos = 0){assert(pos < _size);const char* ptr = strstr(_str, str);if (ptr){return ptr - _str;}else{return npos;}}void clear(){_str[0] = '\0';_size = 0;}//方法一:bool operator<(const string& s) const{int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);return ret == 0 ? _size < s._size : ret < 0;}方法二://bool operator<(const string& s)//{//	//return strcmp(_str, s._str) < 0;由于\0,所以不能用strcmp//	size_t i1 = 0;//	size_t i2 = 0;//	while (i1 < _size && i2 < s._size)//	{//		if (_str[i1] < s._str[i2])//			return true;//		else if (_str[i1] > s._str[i2])//			return false;//		else//		{//			++i1;//			++i2;//		}//	}//	//在循环之外,只有一种情况是true//	//return i1 == _size && i2 != s._size;//	//或者判断size大小//	return _size < s._size;//}bool operator==(const string& s) const{//先比较size过滤各种情况return _size == s._size&& memcmp(_str, s._str, _size < s._size) == 0;}bool operator<=(const string& s) const{return (*this < s || *this == s);}bool operator>(const string& s) const{return !(*this <= s);}bool operator>=(const string& s) const{return !(*this < s);}bool operator!=(const string& s) const{return !(*this == s);}private:size_t _size;size_t _capacity;char* _str;static size_t npos;};size_t string::npos = -1;//流插入(向终端插入数据),有多少字符就输出多少字符,中间有'\0'也不终止std::ostream& operator<<(std::ostream& out, const string& s){//要访问私有变量才用友元for (auto ch : s){out << ch;}return out;}//流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据std::istream& operator>>(std::istream& in, string& s){s.clear();//不能写成 in >> s.str,因为s可能没空间来存放数据char ch = in.get();//处理前缓冲区前面的空格或者换行while (ch == ' ' || ch == '\n'){ch = in.get();}char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}};
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

总结

        我们模拟实现的string,具有部分功能,还有一部分没有实现,有兴趣的同学可以参照std的string再进行模拟实现,下节我们将讲解vector并进行实现。

        最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

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

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

相关文章

【微服务 SpringCloud】实用篇 · 服务拆分和远程调用

微服务&#xff08;2&#xff09; 文章目录 微服务&#xff08;2&#xff09;1. 服务拆分原则2. 服务拆分示例1.2.1 导入demo工程1.2.2 导入Sql语句 3. 实现远程调用案例1.3.1 案例需求&#xff1a;1.3.2 注册RestTemplate1.3.3 实现远程调用1.3.4 查看效果 4. 提供者与消费者 …

美创科技入选“内蒙古自治区第一届网络安全应急技术支撑单位”

近日&#xff0c;内蒙古自治区党委网信办、国家网络应急技术处理协调中心内蒙古分中心评选“内蒙古自治区网络安全应急技术支撑单位”结果公布。 经自治区各地区、各部门和单位推荐各单位自主申报&#xff0c;资料审查和专家评审等环节&#xff0c;美创科技成功入选“内蒙古自治…

Linux将磁盘空闲空间转移到其他目录下(home目录转移到root目录下)

目录 1、查看分区 2、备份home分区文件 &#xff08;重要&#xff09; 3、卸载/home 4、删除/home所在的lv 5、扩展/root所在的lv&#xff08;我这里还扩展了、dev/sr0&#xff09; 6、扩展/root文件系统。 7、重新创建home lv 创建时计算好剩余的磁盘容量&#xff0c;建…

数据预处理—滑动窗口采样数据

一个简单的例子&#xff1a; # data: 这是要应用滑动窗口采样的输入数据&#xff0c;通常是一个序列&#xff0c;例如列表或NumPy数组。 # window_size: 这是滑动窗口的大小&#xff0c;表示每个窗口中包含的元素数量。 # step_size: 这是滑动窗口移动的步长&#xff0c;表示每…

一个可以解决企业跨网文件交换难题的软件所具备的特性必须有哪些

在当今数字化办公的浪潮中&#xff0c;企业跨网文件交换成为了不可忽视的重要需求。无论是内部网络还是外部网络&#xff0c;都存在着各种跨网文件交换的场景&#xff0c;然而这一过程也面临着一系列的挑战。本文将深入探讨企业跨网文件交换的难题以及一款可以解决企业跨网文件…

vue 树状结构数据渲染 (java 处理 list ->树状)

树状结构 Element ui https://element.eleme.cn/#/zh-CN/component/tree <el-tree :data"data" :props"defaultProps" node-click"handleNodeClick"></el-tree><script>export default {data() {return {data: [{label: 一级…

日常中msvcp71.dll丢失怎样修复?分享5个修复方法

在 Windows 系统中&#xff0c;msvcp71.dll 是一个非常重要的动态链接库文件&#xff0c;它承载了许多应用程序和游戏的运行。如果您的系统中丢失了这个文件&#xff0c;那么您可能会遇到无法打开程序、程序崩溃或出现错误提示等问题。本文将介绍 5 个快速修复 msvcp71.dll 丢失…

2000-2022年上市公司CEO 高管及董事会环保背景数据(5W+ )(原始数据+处理代码Stata do文档)

2000-2022年上市公司CEO 高管及董事会环保背景数据&#xff08;5W &#xff09;&#xff08;原始数据处理代码Stata do文档&#xff09; 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;证券代码、股票代码、年份、股票简称、ST或PT为1&#xff0c;否则为0、金融业为1&a…

【操作系统】线程的实现方式:用户线程和内核线程

1 用户级线程 完全在用户空间中实现和管理的线程。 它们的创建、同步和调度由应用程序通过用户级别的线程库实现&#xff0c;所有的线程管理工作都由应用程序负责&#xff0c;无需操作系统内核干预。在用户看来有多个线程&#xff0c;但操作系统并不能意识到线程的存在。 缺点…

Android apkanalyzer简介

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、用法3.1 使用 Android Studio3.1.1…

VR全景广告:让消费者体验沉浸式交互,让营销更有趣

好的产品都是需要广告宣传的&#xff0c;随着科技的不断发展&#xff0c;市面上的广告也和多年前的传统广告不同&#xff0c;通过VR技术&#xff0c;可以让广告的观赏性以及科技感更加强烈&#xff0c;并且相比于视频广告&#xff0c;成本也更低。 在广告营销中&#xff0c;关键…

深度解析自动化测试流程(纯干货)

最近很多小伙伴咨询自动化测试到底该怎么做&#xff1f;流程是什么样的&#xff1f;在每个阶段都需要注意什么&#xff1f;本文也就主要从自动化测试的基本流程入手&#xff0c;对面试自动化测试工程师的同学会有不少帮助。对于在职的朋友&#xff0c;也可以参考此流程&#xf…

Java集合类

Java集合类 集合类 集合类其实就是为了更好地组织、管理和操作我们的数据而存在的&#xff0c;包括列表、集合、队列、映射等数据结构。 集合根接口 Java中已经帮我们将常用的集合类型都实现好了&#xff0c;我们只需要直接拿来用就行了 所有的集合类最终都是实现自集合根…

什么是客户端?一文了解客户端定义、特点与功能、搭建方法

客户端&#xff1a;定义、特点与功能、搭建方法 1. 定义&#xff1a; 客户端是计算机网络中的一个术语&#xff0c;指的是在网络通信中充当主动发起请求并接收服务响应的一方。通常&#xff0c;客户端是指运行在终端设备上的软件或硬件实体&#xff0c;通过与服务器进行通信来…

华为数通方向HCIP-DataCom H12-831题库(单选题:301-310)

第301题 关于配置防火墙安全区域的安全级别的描述,错误的是 A、同一系统中,两个安全区域不允许配置相同的安全级别 B、只能为自定义的安全区域设定安全级别 C、安全级别一旦设定不允许更改 D、新建的安全区域,系统默认其安全级别为1 答案:D 解析: 新创建的安全区域缺省未…

交通目标检测-行人车辆检测流量计数 - 计算机竞赛

文章目录 0 前言1\. 目标检测概况1.1 什么是目标检测&#xff1f;1.2 发展阶段 2\. 行人检测2.1 行人检测简介2.2 行人检测技术难点2.3 行人检测实现效果2.4 关键代码-训练过程 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 毕业设计…

在前端html页面中向服务器发送post登录请求

目录 前言 搭建服务器 搭建前端登录页面 获取表单值 使用axios发送post登录请求 前言 一般在html页面中向服务器发送post请求的模块为登录请求&#xff0c;本文将介绍如何向服务器发送post请求 搭建服务器 如何搭建服务器请看JWT认证这篇文章&#xff0c;有详细的解说。…

SpringCloud学习笔记-gateway网关自定义全局过滤器

需求&#xff1a;定义全局过滤器&#xff0c;拦截请求&#xff0c;判断请求的参数是否满足下面条件&#xff1a; 参数中是否有authorization&#xff0c; authorization参数值是否为admin 如果同时满足则放行&#xff0c;否则拦截 实现&#xff1a; 在gateway中定义一个过…

《SQLi-Labs》04. Less 23~28a

title: 《SQLi-Labs》04. Less 23~28a date: 2023-10-19 19:37:40 updated: 2023-10-19 19:38:40 categories: WriteUp&#xff1a;Security-Lab excerpt: 联合注入&#xff0c;注释符过滤绕过之构造闭合&#xff0c;%00 截断、二次注入、报错注入&#xff0c;空格过滤绕过&…

【Java基础面试二十四】、String类有哪些方法?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;String类有哪些方法&…