【STL】string的模拟实现

string类的模拟实现

  • 一、接口函数总览
  • 二、默认成员函数
    • 1、构造函数
    • 2、拷贝构造函数
      • (1)写法一:传统写法
      • (2)写法二:现代写法
    • 3、赋值运算符重载函数
      • (1)写法一:传统写法
      • (2)现代写法
    • 4、析构函数
  • 三、迭代器相关函数
    • 1、begin和end
    • 2、const begin 和 const end
    • 3、利用迭代器进行遍历打印的演示
  • 四、容量和大小相关函数
    • 1、size和capacity
      • (1)size
      • (2)capacity
    • 2、reserve和resize
      • (1)reserve
      • (2)resize
    • 3、empty
  • 五、修改字符串相关函数
    • 1、push_back
    • 2、append
    • 3、operator+=
    • 4、insert
      • (1)insert插入单个字符
      • (2)insert插入字符串
    • 5、erase
      • (1)情况1:删掉pos位置及之后所有的字符
      • (2)情况2:删掉pos位置及其之后的有效字符只需删除一部分
      • (3)代码书写
    • 6、clear
    • 7、swap
    • 8、c_str
  • 六、访问字符串相关函数
    • 1、operator[]
    • 2、const operator[]
    • 3、find和rfind
      • (1)find
        • i、正向查找第一个匹配的字符
        • ii、正向查找第一个匹配的字符串
      • (2)rfind
        • i、反向查找第一个匹配的字符
        • ii、反向查找第一个匹配的字符串
  • 七、关系运算符重载函数
  • 八、>>和<<运算符的重载以及getline函数
    • 1、>>运算符重载
    • 2、<<运算符重载
    • 3、getline函数
  • 九、测试代码
  • 十、代码总览


一、接口函数总览

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

namespace JRH
{
	class string
	{
	public:
		typedef char* iterator; // 迭代器(原生)
		typedef const char* const_iterator; // const迭代器(原生)
	public:
		// 1、默认成员函数
		string(const char* str = "");         // 构造函数(""代表着个数为0)
		string(const string& s);              // 拷贝构造函数
		string& operator=(const string& s);   // 赋值运算符重载函数
		~string();                            // 析构函数
		// 2、迭代器相关函数
		iterator begin();                  // 头
		iterator end();                    // 尾
		const_iterator begin() const;      // const头
		const_iterator end() const;        // const尾
		// 3、容量和大小相关函数
		size_t size();                          // 有效个数大小
		size_t capacity();                      // 总的容量
		void reserve(size_t n);                 // 扩容,不可缩容
		void resize(size_t n, char ch = '\0');  // 扩容,也可缩容 
		bool empty() const;                     // 判空
		// 4、修改字符串相关函数
		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);        // 删除   
		void clear();                                 // 清空
		void swap(string& s);                         // 交换
		const char* c_str()const;                     // 与C字符进行交互
		// 5、访问字符串相关函数
		char& operator[](size_t i);                          // 访问
		const char& operator[](size_t i) const;              // const访问 
		size_t find(char ch, size_t pos = 0) const;          // 查找
		size_t find(const char* str, size_t pos = 0) const;  // const查找
		size_t rfind(char ch, size_t pos = npos) const;      // 整数查找
		size_t rfind(const char* str, size_t pos = 0) const; // const整数查找
		// 6、关系运算符重载
		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;  // 不等于
	private:
		char* _str;                // 存储字符串
		size_t _size;              // 记录字符串当前的有效长度
		size_t _capacity;          // 记录字符串当前的容量
		static const size_t npos;  // 静态成员变量,只针对整型,整型最大值
	};
	// >> 和 << 运算符重载函数
	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, const string& s);
	istream& getline(istream& in, string& s);
}

二、默认成员函数

1、构造函数

构造函数设置为缺省参数,若不传入参数,则默认构造为空字符串。字符串的初始大小和容量均设置为传入C字符串的长度(不包括’\0’)

		string(const char* str = "")         // 构造函数(""代表着个数为0)
		{
			_size = strlen(str); // 初始化定义字符串当前的长度
			_capacity = _size; // 初始化容量就是当前长度
			_str = new char[_capacity + 1]; // 多开一个空间以便存放'\0'
			strcpy(_str, str); // 将传入的str拷贝给this对象的_str
		}

2、拷贝构造函数

我们先了解一下深浅拷贝的区别:

  1. 浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
  2. 深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。

(1)写法一:传统写法

深拷贝的传统写法是先开辟一块足以容纳源对象字符串的空间,然后将源对象的字符串拷贝过去,接着把源对象的其他成员变量也赋值过去即可。因为拷贝对象的_str与源对象的_str指向的并不是同一块空间,所以拷贝出来的对象与源对象是互相独立的

在这里插入图片描述

		string(const string& s)              // 拷贝构造函数 -- 传统写法
			:_str(new char[s._capacity + 1]) // 多开一个空间用来存放'\0'
			, _size(s._size)
			, _capacity(s._capacity)
		{
			strcpy(_str, s._str); // 将s的字符串拷贝过去
			_size = s._size;      // s的数量拷贝过去
			_capacity = s._capacity; // 容量也拷贝过去
		}

(2)写法二:现代写法

先根据源字符串的C字符串调用构造函数构造一个tmp对象(这个tmp对象可以用string类默认构造构造出来的),然后再将tmp对象与拷贝对象的数据交换即可。拷贝对象的_str与源对象的_str指向的也不是同一块空间,是互相独立的。

在这里插入图片描述

		string(const string& s)              // 拷贝构造函数 -- 现代写法
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str); // 调用构造函数,构造出一个C字符串为s._str的对象
			swap(tmp); // 交换这两个对象
		}

3、赋值运算符重载函数

(1)写法一:传统写法

该传统写法与上面的拷贝构造的传统写法相类似,只是左值的_str在开辟新空间之前需要先将原来的空间释放掉,并且在进行操作之前还需判断是否是自己给自己赋值,若是自己给自己赋值,则无需进行任何操作。

		string& operator=(const string& s)   // 赋值运算符重载函数 -- 传统写法
		{
			if (this != &s)
			{
				delete[] _str; // 释放掉原来的空间
				_str = new char[s._capacity + 1]; // 申请一块空间,+1为'\0'
				strcpy(_str, s._str); // 将s._str拷贝一份到_str
				_size = s._size; // 将s的大小给对象
				_capacity = s._capacity; // 同样将容量给对象
			}
			return *this; // 支持连续赋值
		}

(2)现代写法

赋值运算符重载函数的现代写法与拷贝构造函数的现代写法也是非常类似,但拷贝构造函数的现代写法是通过代码语句调用构造函数构造出一个对象,然后将该对象与拷贝对象交换;而赋值运算符重载函数的现代写法是通过采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可。

现代写法1:

		string& operator=(string s)   // 赋值运算符重载函数 -- 现代写法1(编译器接收右值的时候自动调用拷贝构造函数)
		{
			swap(s); // 交换两个对象
			return *this; // 支持连续赋值
		}

上面的写法是有纰漏的,因为如果是自己给自己赋值的操作也会进行,虽然操作之后对象中_str指向的字符串的内容不变,但是字符串存储的地址发生了改变,为了避免这种操作我们可以采用下面这种写法:

		string& operator=(const string& s)   // 赋值运算符重载函数 -- 现代写法2
		{
			if (this != &s) // 避免自己给自己赋值
			{
				string tmp(s._str); // 建立一个临时对象
				swap(tmp); // 交换
			}
			return *this; // 支持连续赋值,返回左值
		}

4、析构函数

因为在对象在堆区中申请一块空间,我们需要手动进行delete释放掉,所以我们首先需要先释放掉堆上申请的空间,并将其指针置为空,空间大小全都定为0即可,代码如下:

		~string()                            // 析构函数
		{
			delete[] _str; // 将对象空间释放掉
			_str = nullptr; // 将指针置为空防止野指针
			_size = 0; // 将空间大小置为0
			_capacity = 0; // 将空间容量置为0
		}

三、迭代器相关函数

string类中的迭代器实际上就是字符指针,我们给字符指针取个别名为iterator和const iterator即可,我们看下面的代码定义:

		typedef char* iterator; // 迭代器(原生)
		typedef const char* const_iterator; // const迭代器(原生)

1、begin和end

begin就是返回首字符的地址,而end就是返回尾字符的下一个地址,也就是’\0’,我们直接实现代码:

		iterator begin()                  // 头
		{
			return _str; // 实现首字母的地址
		}
		iterator end()                    // 尾
		{
			return _str + _size; // 实现尾字符的下一个字符的地址
		}

2、const begin 和 const end

与上面几乎一致,我们只需要加一个const即可。

		const_iterator begin() const      // const头
		{
			return _str; // 实现首字母的const地址
		}
		const_iterator end() const        // const尾
		{
			return _str + _size; // 实现尾字符的下一个字符的const地址
		}

3、利用迭代器进行遍历打印的演示

其实其本质就是指针的从前往后移动:

string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
	cout << *it << " ";
	it++;
}
cout << endl;

而范围for就更加明显了,因为范围for的底层就是迭代器的遍历打印:

string s("hello worle");
for(auto e : s)
{
	cout << e << " ";
}
cout << endl;

四、容量和大小相关函数

1、size和capacity

因为string类的成员变量是私有的,我们并不能直接对其进行访问,所以string类设置了size和capacity这两个成员函数,用于获取string对象的大小和容量。

(1)size

size函数用于获取字符串当前的有效长度(不包括’\0’)

		size_t size()                          // 有效个数大小
		{
			return _size; // 返回当前字符串的有效长度
		}

(2)capacity

capacity函数用于获取字符串当前的容量。

		size_t capacity()                      // 总的容量
		{
			return _capacity; // 返回当前字符串的容量
		}

2、reserve和resize

(1)reserve

规则:

  1. 当n大于对象当前的capacity时,将capacity扩大到n或大于n
  2. 当n小于对象当前的capacity时,什么也不做
		void reserve(size_t n)                 // 扩容,不可缩容(大小不变)
		{
			if (n > _capacity) // 只有当n大于容量的时候才进行操作
			{
				char* tmp = new char[n + 1]; // 多开一个空间用于存放'\0'
				strncpy(tmp, _str, _size + 1); // 将_str的所有都拷贝到tmp临时对象中,包括一堆\0
				delete[] _str; // 将其_str释放掉
				_str = tmp; // 新开辟的空间给_str
				_capacity = n; // 将n给对象的新的容量
			}
		}

注意:我们这里为甚用strncpy而不是strcpy,原因如下图:
在这里插入图片描述

(2)resize

  1. 当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’
  2. 当n小于当前的size时,将size缩小到n
		void resize(size_t n, char ch = '\0')  // 扩容,也可缩容(改变大小)
		{
			if (n <= _size) // n比大小小的时候
			{
				_size = n; // 将_size调成n
				_str[_size] = '\0'; // 将_str的最后一个元素直接定为'\0'
			}
			else
			{
				if (n > _capacity) // 判断是否需要增容
				{
					reserve(n);
				}
				for (size_t i = 0; i < n; i++) // 将_size扩大到n,ch为一个个字符
				{
					_str[i] = ch;
				}
				_size = n; // _size更新
				_str[_size] = '\0'; // 最后加一个\0
			}
		}

3、empty

empty是string的判空函数,我们可以调用strcmp函数来实现,strcmp函数是用于比较两个字符串大小的函数,当两个字符串相等时返回0。

		bool empty() const                     // 判空
		{
			return strcmp(_str, "") == 0; // 比较_str是否为空
		}

五、修改字符串相关函数

1、push_back

push_back函数的作用就是在当前字符串的后面尾插上一个字符,尾插之前首先需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’,否则打印字符串的时候会出现非法访问,因为尾插的字符后方不一定就是’\0’。

		void push_back(char ch)                      // 尾插一个字符
		{
			if (_size == _capacity) // 满的时候
			{
				// 扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch; // 最后一个字符定为ch
			_str[_size++] = '\0'; // 先实现最后一个字符的后一个字符为\0,再将_size++
		}

2、append

append函数的作用是在当前字符串的后面尾插一个字符串,尾插前需要判断当前字符串的空间能否容纳下尾插后的字符串,若不能,则需要先进行增容,然后再将待尾插的字符串尾插到对象的后方,因为待尾插的字符串后方自身带有’\0’,所以我们无需再在后方设置’\0’。

		void append(const char* str)                 // 尾增一个字符串
		{
			size_t len = _size + strlen(str); // 尾插str后字符串的大小(不包括'\0')
			if (len > _capacity) // 判断是否需要增容
			{
				reserve(len); // 增容
			}
			strcpy(_str + _size, str); // 将str尾插到字符串后面
			_size = len; // 字符串大小改变
		}

3、operator+=

  • +=运算符的重载是为了实现字符串与字符、字符串与字符串之间能够直接使用+=运算符进行尾插。

+=运算符实现字符串与字符之间的尾插直接调用push_back函数即可。

		string& operator+=(char ch)                  // 尾增一个字符
		{
			push_back(ch); // 调用push_back 
			return *this;  // 支持连续+=
		}

+=运算符实现字符串与字符串之间的尾插直接调用push_back函数即可。

		string& operator+=(const char* str)          // 尾增一个字符串 
		{
			append(str); // 调用append
			return *this; // 支持连续+=,且其为左值
		}

4、insert

(1)insert插入单个字符

在这里插入图片描述

insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可。

		string& insert(size_t pos, char ch)          // 随机插入一个字符
		{
			assert(pos <= _size); // 检查下标合法性
			if (_size == _capacity) // 增容
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			// 定义end到除了末尾\0的最末位元素
			char* end = _str + _size; 
			while (end >= _str + pos) // end从后往前到pos位置时候
			{
				*(end + 1) = *end; // 后面一个字符等于前面一个字符,等于字符往后移动
				end--;
			}
			_str[pos] = ch; // 插入
			_size++; // 有效字符长度++
			return *this; // 支持连续赋值,左值
		}

(2)insert插入字符串

insert函数用于插入字符串时,首先也是判断pos的合法性,若不合法则无法进行操作,再判断当前对象能否容纳插入该字符串后的字符串,若不能则还需调用reserve函数进行扩容。插入字符串时,先将pos位置及其后面的字符统一向后挪动len位(len为待插入字符串的长度),给待插入的字符串留出位置,然后将其插入字符串即可。

		string& insert(size_t pos, const char* str)  // 随机插入一个字符串
		{
			assert(pos <= _size); // 坐标合法
			size_t len = strlen(_str); // 定义一下长度
			if (len + _size > _capacity) // 扩容
			{
				reserve(len + _size); // 扩大到len+_size的大小的容量
			}
			char* end = _str + _size; // end定义为最末尾元素(\0前一个)
			while (end >= _str + pos) // 从后往前找位置
			{
				*(end + len) = *end;
				end--;
			}
			strncpy(_str + pos, str, len); // pos位置开始放上指定字符串
			_size += len;
			return *this; // 支持连续插入
		}

5、erase

erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性,进行删除操作的时候分两种情况:

(1)情况1:删掉pos位置及之后所有的字符

这时我们只需在pos位置放上’\0’,然后将对象的size更新即可。

在这里插入图片描述

(2)情况2:删掉pos位置及其之后的有效字符只需删除一部分

这时我们可以用后方需要保留的有效字符覆盖前方需要删除的有效字符,此时不用在字符串后方加’\0’,因为在此之前字符串末尾就有’\0’了。

在这里插入图片描述

(3)代码书写

		string& erase(size_t pos, size_t len)        // 删除
		{
			assert(pos < _size); // 检测下标的合法性
			size_t n = _size - pos; // 计算后面数的大小
			if (len >= n) // 此时pos后面的数全部删掉
			{
				_size = pos; // 更新此时_size的大小
				_str[_size] = '\0'; // 直接定义\0即可
			}
			else // 删一部分
			{
				strcpy(_str + pos, _str + pos + len); // 将len后面的数覆盖以pos为开头的数
				_size -= len; // 更新_str
			}
			return *this; // 支持连续删除
		}

6、clear

clear函数用于将对象中存储的字符串置空,实现时直接将对象的_size置空,然后在字符串后面放上’\0’即可。

		void clear()                                 // 清空
		{
			_size = 0; // 将_size到首元素位置
			_str[_size] = '\0'; // 首元素直接定义为\0
		}

7、swap

用于交换对象中的各个变量,那我们直接调用库中的swap函数即可,我们加上std::告诉编译器是找的是全局变量中的swap函数。

		void swap(string& s)                         // 交换
		{
			std::swap(_str, s._str); // 交换字符串
			std::swap(_size, s._size); // 交换有效字符大小
			std::swap(_capacity, s._capacity); // 交换容量
		}

8、c_str

c_str函数用于获取对象C类型的字符串,实现时直接返回对象的成员变量_str即可。

		const char* c_str() const                     // 与C字符进行交互
		{
			return _str;
		}

六、访问字符串相关函数

在C字符串中我们通过[ ] +下标的方式可以获取字符串对应位置的字符,并可以对其进行修改,实现[ ] 运算符的重载时只需返回对象C字符串对应位置字符的引用即可,这样便能实现对该位置的字符进行读取和修改操作了,但需要注意在此之前检测所给下标的合法性。

1、operator[]

		char& operator[](size_t i)                          // 访问
		{
			assert(i < _size); // 检测下标的合法性
			return _str[i]; // 可读可写
		}

2、const operator[]

这个操作符是不能进行修改而可以进行读取的,因为在很多情况下访问不修改只读取,所以我们实现一个const重载。

		const char& operator[](size_t i) const              // const访问 
		{
			assert(i < _size); // 检测下标的合法性
			return _str[i]; // 只能读不能写
		}

3、find和rfind

find函数和rfind函数都是用于在字符串中查找一个字符或是字符串,find函数和rfind函数分别用于正向查找和反向查找,即从字符串开头开始向后查找和从字符串末尾开始向前查找。

(1)find

i、正向查找第一个匹配的字符

首先判断所给pos的合法性,然后通过遍历的方式从pos位置开始向后寻找目标字符,若找到,则返回其下标;若没有找到,则返回npos。(npos是string类的一个静态成员变量,其值为整型最大值)

		size_t find(char ch, size_t pos = 0)         // 查找字符
		{
			assert(pos < _size); // 检查坐标合法性
			for (size_t i = pos; i < _size; i++) // 从pos位置往后查找
			{
				if (_str[i] == ch)
				{
					return i; // 找到了,返回下标
				}
			}
			return npos; // 没有找到对应字符,返回npos
		}
ii、正向查找第一个匹配的字符串

首先也是先判断所给pos的合法性,然后我们可以通过调用strstr函数进行查找。strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个空指针。若是找到了目标字符串,我们可以通过计算目标字符串的起始位置和对象C字符串的起始位置的差值,进而得到目标字符串起始位置的下标。

		size_t find(const char* str, size_t pos = 0)  // 查找字符串
		{
			assert(pos < _size); // 检查坐标合法性
			const char* ret = strstr(_str + pos, str); // 用strstr查找相对应的字符串
			if (ret)
			{
				return ret - _str; // 返回字符串第一个元素下标
			}
			else
			{
				return npos; // 没找到返回npos
			}
		}

(2)rfind

实现rfind函数时,我们可以考虑复用已经写好了的两个find函数,但rfind函数是从后先前找,所以我们需要将对象的C字符串逆置一下,若是查找字符串,还需将待查找的字符串逆置一下,然后调用find函数进行查找,但注意传入find函数的pos以及从find函数接收到的pos都需要镜像对称一下。
在这里插入图片描述

i、反向查找第一个匹配的字符

首先我们需要用对象拷贝构造一个临时对象tmp,因为我们并不希望调用rfind函数后对象的C字符串就被逆置了。我们将tmp对象的C字符串逆置,然后将所给pos镜像对称一下再调用find函数,再将从find函数接收到的返回值镜像对称一下作为rfind函数的返回值返回即可。
注意:当长度超过字符串长度时候,其返回的是最后一个坐标的下标,对于rfind则是首元素下标为0。

		size_t rfind(char ch, size_t pos = npos)      // 整数查找
		{
			string tmp(*this); // 拷贝构造一个tmp临时对象
			std::reverse(tmp.begin(), tmp.end()); // 调用reverse逆置对象tmp
			if (pos >= _size) // 所给pos大于字符串有效长度
			{
				pos = _size - 1; // 重新设置pos为字符串最后一个字符的下标
			}
			pos = _size - 1 - pos; // 将pos改为镜像对称后的位置
			size_t ret = tmp.find(ch, pos); // 复用find函数
			if (ret != npos)
				return _size - 1 - ret; // 找到了,返回ret镜像对称后的位置
			else
				return npos; // 没找到,返回npos
		}
ii、反向查找第一个匹配的字符串

首先我们还是需要用对象拷贝构造一个临时对象tmp,然后将tmp对象的C字符串逆置,同时我们还需要拷贝一份待查找的字符串,也将其逆置。然后将所给pos镜像对称一下再调用find函数。注意:此时我们将从find函数接收到的值镜面对称后,得到的是待查找字符串的最后一个字符在对象C字符串中的位置,而我们需要返回的是待查找字符串在对象C字符串中的第一个字符的位置,所以还需做进一步调整后才能作为rfind函数的返回值返回。

		size_t rfind(const char* str, size_t pos = 0) // 查找第一个匹配字符串
		{
			string tmp(*this); // 拷贝构造对象tmp
			std::reverse(tmp.begin(), tmp.end()); // 调用reverse逆置对象tmp的C字符串
			size_t len = strlen(str); // 待查找的字符串的长度
			char* arr = new char[len + 1]; // 开辟arr字符串(用于拷贝str字符串)
			strcpy(arr, str); // 拷贝str给arr
			size_t left = 0, right = len - 1; // 设置左右指针
			// 逆置字符串arr
			while (left < right)
			{
				std::swap(arr[left], arr[right]);
				left++;
				right--;
			}
			if (pos >= _size) // 所给pos大于字符串有效长度
			{
				pos = _size - 1; // 重新设置pos为字符串最后一个字符的下标
			}
			pos = _size - 1 - pos; // 将pos改为镜像对称后的位置
			size_t ret = tmp.find(arr, pos); // 复用find函数
			delete[] arr; // 销毁arr指向的空间,避免内存泄漏
			if (ret != npos)
				return _size - ret - len; // 找到了,返回ret镜像对称后再调整的位置
			else
				return npos; // 没找到,返回npos
		}

七、关系运算符重载函数

关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现。

那么我们这里的string类只需要实现重载>和==即可,其余均可复用:

		bool operator==(const string& s) const  // 等于
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator>(const string& s) const   // 大于
		{
			return strcmp(_str, s._str) > 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);
		}

八、>>和<<运算符的重载以及getline函数

1、>>运算符重载

重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入。输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取。

	istream& operator>>(istream& in, string& s)
	{
		s.clear(); // 清空
		char ch = in.get(); // 读取第一个字符
		while (ch != ' ' && ch != '\n') // 当读取到的字符不是空格或'\n'的时候继续读取
		{
			s += ch; // 将读取到的字符尾插到字符串后面
			ch = in.get(); // 继续读取字符串
		}
		return in; // 支持连续输入
	}

2、<<运算符重载

重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印。实现时我们可以直接使用范围for对对象进行遍历即可。

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s) // 连续打印
		{
			out << s;
		}
		return out; // 支持连续输出
	}

3、getline函数

实现逻辑与上面的>.运算符重载基本相同,只是当读取到’\n’的时候才停止读取字符。

	istream& getline(istream& in, string& s) // 读取一行有空格的字符
	{
		s.clear(); // 清空字符串
		char ch = in.get(); // 读取一个字符
		while (ch != '\n') // 当读取到的字符不是'\n'的时候继续读取
		{
			s += ch; // 将读取到的字符尾插到字符串后面
			ch = in.get(); // 继续读取字符
		}
		return in; // 支持连续输入
	}

九、测试代码

Print函数:

	void Print(string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			cout << s[i] << " ";
		}
		cout << endl;
		// 只能读不能写
		string::const_iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		for (auto ch : s)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

test函数:

	void Test_String1()
	{
		string s1("xxxxxxx\n");
		string s2("hello world\n");
		s1 = s2;
		cout << s1.c_str() << endl;
		cout << s1.c_str() << endl;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		s2[0]++;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		string s3(s2);
		cout << s2.c_str();
		cout << s3.c_str();
	}

	// 遍历string
	void Test_String2()
	{
		string s1("hello world\n");
		for (size_t i = 0; i < s1.size(); ++i)
		{
			// 修改
			s1[i]++;
		}
		cout << endl;
		for (size_t i = 0; i < s1.size(); ++i)
		{
			// 读取
			cout << s1[i] << " ";
		}
		cout << endl;
		Print(s1);
	}

	// 遍历string -- 迭代器
	void Test_String3()
	{
		string s1("hello world\n");
		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		for (auto ch : s1)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void Test_String4()
	{
		string s1("hello world");
		string s2("yyyyy");
		string s3("xx");
		cout << (s1 < s2) << endl;
		cout << (s2 > s3) << endl;
		cout << (s2 == s3) << endl;
	}

	void Test_String5()
	{
		string s1("hello world");
		s1.push_back('x');
		s1.append("yyyy");
		cout << s1.c_str() << endl;
	}

	void Test_String6()
	{
		string s1("hello world");
		s1.insert(5, 'x');
		cout << s1.c_str() << endl;
	}

	void Test_String7()
	{
		string s1;
		s1.resize(20, 'x');
		cout << s1.c_str() << endl;
		s1.resize(30, 'y');
		cout << s1.c_str() << endl;
		s1.resize(10);
		cout << s1.c_str() << endl;
	}

	void Test_String8()
	{
		string s1("hello world");
		s1.insert(0, "xxx");
		cout << s1.c_str() << endl;
	}

	void Test_String9()
	{
		string s1;
		cin >> s1;
		cout << s1.c_str() << endl;
	}

代码测试结果:
在这里插入图片描述

十、代码总览

stl_string.h:

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;

namespace JRH
{
	class string
	{
	public:
		typedef char* iterator; // 迭代器(原生)
		typedef const char* const_iterator; // const迭代器(原生)
	public:
		// 1、默认成员函数
		string(const char* str = "")         // 构造函数(""代表着个数为0)
		{
			_size = strlen(str); // 初始化定义字符串当前的长度
			_capacity = _size; // 初始化容量就是当前长度
			_str = new char[_capacity + 1]; // 多开一个空间以便存放'\0'
			strcpy(_str, str); // 将传入的str拷贝给this对象的_str
		}
		//string(const string& s)              // 拷贝构造函数 -- 传统写法
		//	:_str(new char[s._capacity + 1]) // 多开一个空间用来存放'\0'
		//	, _size(s._size)
		//	, _capacity(s._capacity)
		//{
		//	strcpy(_str, s._str); // 将s的字符串拷贝过去
		//	_size = s._size;      // s的数量拷贝过去
		//	_capacity = s._capacity; // 容量也拷贝过去
		//}
		string(const string& s)              // 拷贝构造函数 -- 现代写法
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str); // 调用构造函数,构造出一个C字符串为s._str的对象
			swap(tmp); // 交换这两个对象
		}
		//string& operator=(const string& s)   // 赋值运算符重载函数 -- 传统写法
		//{
		//	if (this != &s)
		//	{
		//		delete[] _str; // 释放掉原来的空间
		//		_str = new char[s._capacity + 1]; // 申请一块空间,+1为'\0'
		//		strcpy(_str, s._str); // 将s._str拷贝一份到_str
		//		_size = s._size; // 将s的大小给对象
		//		_capacity = s._capacity; // 同样将容量给对象
		//	}
		//	return *this; // 支持连续赋值
		//}
		string& operator=(string s)   // 赋值运算符重载函数 -- 现代写法1(编译器接收右值的时候自动调用拷贝构造函数)
		{
			swap(s); // 交换两个对象
			return *this; // 支持连续赋值
		}
		//string& operator=(const string& s)   // 赋值运算符重载函数 -- 现代写法2
		//{
		//	if (this != &s) // 避免自己给自己赋值
		//	{
		//		string tmp(s._str); // 建立一个临时对象
		//		swap(tmp); // 交换
		//	}
		//	return *this; // 支持连续赋值,返回左值
		//}
		~string()                            // 析构函数
		{
			delete[] _str; // 将对象空间释放掉
			_str = nullptr; // 将指针置为空防止野指针
			_size = 0; // 将空间大小置为0
			_capacity = 0; // 将空间容量置为0
		}

		// 2、迭代器相关函数
		iterator begin()                  // 头
		{
			return _str; // 实现首字母的地址
		}
		iterator end()                    // 尾
		{
			return _str + _size; // 实现尾字符的下一个字符的地址
		}
		const_iterator begin() const      // const头
		{
			return _str; // 实现首字母的const地址
		}
		const_iterator end() const        // const尾
		{
			return _str + _size; // 实现尾字符的下一个字符的const地址
		}

		// 3、容量和大小相关函数
		size_t size()                          // 有效个数大小
		{
			return _size; // 返回当前字符串的有效长度
		}
		size_t capacity()                      // 总的容量
		{
			return _capacity; // 返回当前字符串的容量
		}
		void reserve(size_t n)                 // 扩容,不可缩容(大小不变)
		{
			if (n > _capacity) // 只有当n大于容量的时候才进行操作
			{
				char* tmp = new char[n + 1]; // 多开一个空间用于存放'\0'
				strncpy(tmp, _str, _size + 1); // 将_str的所有都拷贝到tmp临时对象中,包括一堆\0
				delete[] _str; // 将其_str释放掉
				_str = tmp; // 新开辟的空间给_str
				_capacity = n; // 将n给对象的新的容量
			}
		}
		void resize(size_t n, char ch = '\0')  // 扩容,也可缩容(改变大小)
		{
			if (n <= _size) // n比大小小的时候
			{
				_size = n; // 将_size调成n
				_str[_size] = '\0'; // 将_str的最后一个元素直接定为'\0'
			}
			else
			{
				if (n > _capacity) // 判断是否需要增容
				{
					reserve(n);
				}
				for (size_t i = 0; i < n; i++) // 将_size扩大到n,ch为一个个字符
				{
					_str[i] = ch;
				}
				_size = n; // _size更新
				_str[_size] = '\0'; // 最后加一个\0
			}
		}
		bool empty() const                     // 判空
		{
			return strcmp(_str, "") == 0; // 比较_str是否为空
		}

		// 4、修改字符串相关函数
		void push_back(char ch)                      // 尾插一个字符
		{
			if (_size == _capacity) // 满的时候
			{
				// 扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch; // 最后一个字符定为ch
			_str[_size++] = '\0'; // 先实现最后一个字符的后一个字符为\0,再将_size++
		}
		void append(const char* str)                 // 尾增一个字符串
		{
			size_t len = _size + strlen(str); // 尾插str后字符串的大小(不包括'\0')
			if (len > _capacity) // 判断是否需要增容
			{
				reserve(len); // 增容
			}
			strcpy(_str + _size, str); // 将str尾插到字符串后面
			_size = len; // 字符串大小改变
		}
		string& operator+=(char ch)                  // 尾增一个字符
		{
			push_back(ch); // 调用push_back 
			return *this;  // 支持连续+=
		}
		string& operator+=(const char* str)          // 尾增一个字符串 
		{
			append(str); // 调用append
			return *this; // 支持连续+=,且其为左值
		}
		string& insert(size_t pos, char ch)          // 随机插入一个字符
		{
			assert(pos <= _size); // 检查下标合法性
			if (_size == _capacity) // 增容
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			// 定义end到除了末尾\0的最末位元素
			char* end = _str + _size; 
			while (end >= _str + pos) // end从后往前到pos位置时候
			{
				*(end + 1) = *end; // 后面一个字符等于前面一个字符,等于字符往后移动
				end--;
			}
			_str[pos] = ch; // 插入
			_size++; // 有效字符长度++
			return *this; // 支持连续赋值,左值
		}
		string& insert(size_t pos, const char* str)  // 随机插入一个字符串
		{
			assert(pos <= _size); // 坐标合法
			size_t len = strlen(_str); // 定义一下长度
			if (len + _size > _capacity) // 扩容
			{
				reserve(len + _size); // 扩大到len+_size的大小的容量
			}
			char* end = _str + _size; // end定义为最末尾元素(\0前一个)
			while (end >= _str + pos) // 从后往前找位置
			{
				*(end + len) = *end;
				end--;
			}
			strncpy(_str + pos, str, len); // pos位置开始放上指定字符串
			_size += len;
			return *this; // 支持连续插入
		}
		string& erase(size_t pos, size_t len)        // 删除
		{
			assert(pos < _size); // 检测下标的合法性
			size_t n = _size - pos; // 计算后面数的大小
			if (len >= n) // 此时pos后面的数全部删掉
			{
				_size = pos; // 更新此时_size的大小
				_str[_size] = '\0'; // 直接定义\0即可
			}
			else // 删一部分
			{
				strcpy(_str + pos, _str + pos + len); // 将len后面的数覆盖以pos为开头的数
				_size -= len; // 更新_str
			}
			return *this; // 支持连续删除
		}
		void clear()                                 // 清空
		{
			_size = 0; // 将_size到首元素位置
			_str[_size] = '\0'; // 首元素直接定义为\0
		}
		void swap(string& s)                         // 交换
		{
			std::swap(_str, s._str); // 交换字符串
			std::swap(_size, s._size); // 交换有效字符大小
			std::swap(_capacity, s._capacity); // 交换容量
		}
		const char* c_str() const                     // 与C字符进行交互
		{
			return _str;
		}

		// 5、访问字符串相关函数
		char& operator[](size_t i)                          // 访问
		{
			assert(i < _size); // 检测下标的合法性
			return _str[i]; // 可读可写
		}
		const char& operator[](size_t i) const              // const访问 
		{
			assert(i < _size); // 检测下标的合法性
			return _str[i]; // 只能读不能写
		}
		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; // 找到了,返回下标
				}
			}
			return npos; // 没有找到对应字符,返回npos
		}
		size_t find(const char* str, size_t pos = 0)  // 查找字符串
		{
			assert(pos < _size); // 检查坐标合法性
			const char* ret = strstr(_str + pos, str); // 用strstr查找相对应的字符串
			if (ret)
			{
				return ret - _str; // 返回字符串第一个元素下标
			}
			else
			{
				return npos; // 没找到返回npos
			}
		}
		size_t rfind(char ch, size_t pos = npos)      // 整数查找
		{
			string tmp(*this); // 拷贝构造一个tmp临时对象
			std::reverse(tmp.begin(), tmp.end()); // 调用reverse逆置对象tmp
			if (pos >= _size) // 所给pos大于字符串有效长度
			{
				pos = _size - 1; // 重新设置pos为字符串最后一个字符的下标
			}
			pos = _size - 1 - pos; // 将pos改为镜像对称后的位置
			size_t ret = tmp.find(ch, pos); // 复用find函数
			if (ret != npos)
				return _size - 1 - ret; // 找到了,返回ret镜像对称后的位置
			else
				return npos; // 没找到,返回npos
		}
		size_t rfind(const char* str, size_t pos = 0) // 查找第一个匹配字符串
		{
			string tmp(*this); // 拷贝构造对象tmp
			std::reverse(tmp.begin(), tmp.end()); // 调用reverse逆置对象tmp的C字符串
			size_t len = strlen(str); // 待查找的字符串的长度
			char* arr = new char[len + 1]; // 开辟arr字符串(用于拷贝str字符串)
			strcpy(arr, str); // 拷贝str给arr
			size_t left = 0, right = len - 1; // 设置左右指针
			// 逆置字符串arr
			while (left < right)
			{
				std::swap(arr[left], arr[right]);
				left++;
				right--;
			}
			if (pos >= _size) // 所给pos大于字符串有效长度
			{
				pos = _size - 1; // 重新设置pos为字符串最后一个字符的下标
			}
			pos = _size - 1 - pos; // 将pos改为镜像对称后的位置
			size_t ret = tmp.find(arr, pos); // 复用find函数
			delete[] arr; // 销毁arr指向的空间,避免内存泄漏
			if (ret != npos)
				return _size - ret - len; // 找到了,返回ret镜像对称后再调整的位置
			else
				return npos; // 没找到,返回npos
		}

		// 6、关系运算符重载
		bool operator>(const string& s) const   // 大于
		{
			return strcmp(_str, s._str) > 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 strcmp(_str, s._str) == 0;
		}
		bool operator!=(const string& s) const  // 不等于
		{
			return !(*this == s);
		}
	private:
		char* _str;                // 存储字符串
		size_t _size;              // 记录字符串当前的有效长度
		size_t _capacity;          // 记录字符串当前的容量
		static const size_t npos;  // 静态成员变量,只针对整型,整型最大值
	};
	// >> 和 << 运算符重载函数
	istream& operator>>(istream& in, string& s)
	{
		s.clear(); // 清空
		char ch = in.get(); // 读取第一个字符
		while (ch != ' ' && ch != '\n') // 当读取到的字符不是空格或'\n'的时候继续读取
		{
			s += ch; // 将读取到的字符尾插到字符串后面
			ch = in.get(); // 继续读取字符串
		}
		return in; // 支持连续输入
	}
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s) // 连续打印
		{
			out << s;
		}
		return out; // 支持连续输出
	}
	istream& getline(istream& in, string& s) // 读取一行有空格的字符
	{
		s.clear(); // 清空字符串
		char ch = in.get(); // 读取一个字符
		while (ch != '\n') // 当读取到的字符不是'\n'的时候继续读取
		{
			s += ch; // 将读取到的字符尾插到字符串后面
			ch = in.get(); // 继续读取字符
		}
		return in; // 支持连续输入
	}
	void Print(string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			cout << s[i] << " ";
		}
		cout << endl;
		// 只能读不能写
		string::const_iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		for (auto ch : s)
		{
			cout << ch << " ";
		}
		cout << endl;
	}
	void Test_String1()
	{
		string s1("xxxxxxx\n");
		string s2("hello world\n");
		s1 = s2;
		cout << s1.c_str() << endl;
		cout << s1.c_str() << endl;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		s2[0]++;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		string s3(s2);
		cout << s2.c_str();
		cout << s3.c_str();
	}

	// 遍历string
	void Test_String2()
	{
		string s1("hello world\n");
		for (size_t i = 0; i < s1.size(); ++i)
		{
			// 修改
			s1[i]++;
		}
		cout << endl;
		for (size_t i = 0; i < s1.size(); ++i)
		{
			// 读取
			cout << s1[i] << " ";
		}
		cout << endl;
		Print(s1);
	}

	// 遍历string -- 迭代器
	void Test_String3()
	{
		string s1("hello world\n");
		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		for (auto ch : s1)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void Test_String4()
	{
		string s1("hello world");
		string s2("yyyyy");
		string s3("xx");
		cout << (s1 < s2) << endl;
		cout << (s2 > s3) << endl;
		cout << (s2 == s3) << endl;
	}

	void Test_String5()
	{
		string s1("hello world");
		s1.push_back('x');
		s1.append("yyyy");
		cout << s1.c_str() << endl;
	}

	void Test_String6()
	{
		string s1("hello world");
		s1.insert(5, 'x');
		cout << s1.c_str() << endl;
	}

	void Test_String7()
	{
		string s1;
		s1.resize(20, 'x');
		cout << s1.c_str() << endl;
		s1.resize(30, 'y');
		cout << s1.c_str() << endl;
		s1.resize(10);
		cout << s1.c_str() << endl;
	}

	void Test_String8()
	{
		string s1("hello world");
		s1.insert(0, "xxx");
		cout << s1.c_str() << endl;
	}

	void Test_String9()
	{
		string s1;
		cin >> s1;
		cout << s1.c_str() << endl;
	}
}

main.cc:

#include"stl_string.h"

int main()
{
	JRH::Test_String1();
	JRH::Test_String2();
	JRH::Test_String3();
	JRH::Test_String4();
	JRH::Test_String5();
	JRH::Test_String6();
	JRH::Test_String7();
	JRH::Test_String8();
	JRH::Test_String9();
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【开源】JAVA+Vue.js实现天然气工程运维系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3.4 安…

东风联手华为打造首款SUV,车长超5米,配纯电和增程双动力系统

在上个月&#xff08;2024年1月份&#xff09;&#xff0c;东风汽车和华为达成了战略合作计划&#xff0c;两家品牌将联手打造全新的汽车品牌——奕派汽车&#xff0c;而目前我们从相关渠道获悉&#xff0c;其首款SUV车型已经获得了实拍亮相&#xff0c;而新车的内部代号为S59&…

MySQL篇----第十四篇

系列文章目录 文章目录 系列文章目录前言一、MySQL 数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化?二、锁的优化策略三、索引的底层实现原理和优化四、什么情况下设置了索引但无法使用前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽…

Java使用opencsv完成对csv批量操作

文章目录 前言一、maven二、造数三、代码部分1.OpenCsvController2.OpenCsvUtil3.StudentInfo4.CodeToValue 三、效果展示1.download2.upload 总结 前言 csv文件是不同于excel文件的另一种文件&#xff0c;常常以,作为分隔符&#xff0c;本篇将通过JavaBean的形式完成对csv文件…

《Git 简易速速上手小册》第2章:理解版本控制(2024 最新版)

文章目录 2.1 本地仓库与版本历史2.1.1 基础知识讲解2.1.2 重点案例&#xff1a;回滚错误提交2.1.3 拓展案例 1&#xff1a;利用 git bisect 查找引入 bug 的提交2.1.4 拓展案例 2&#xff1a;合并提交历史 2.2 远程仓库的使用2.2.1 基础知识讲解2.2.2 重点案例&#xff1a;在 …

CSP-动态规划-最长公共子序列(LCS)

一、动态规划 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;主要用于求解可以被分解为相似子问题的复杂问题&#xff0c;特别是在优化问题上表现出色&#xff0c;如最短路径、最大子数组和、编辑距离等。动态规划的核心思想是将原问题分解为较小的子…

Old Money 和 New Money

&#xff08;1&#xff09; 我想借用一下&#xff1a;Old Money 和 New Money这两个词&#xff0c;但不是欧洲那种Old Money 和 New Money的定义。 我定义的Old Money是&#xff1a; 人脉关系、资源 信息差、成本差 我定义的New Money是&#xff1a; 科技是第一生产力 2015年以…

计算机网络——09Web-and-HTTP

Web and HTTP 一些术语 Web页&#xff1a;由一些对象组成对象可以是HTML文件、JPEG图像&#xff0c;JAVA小程序&#xff0c;声音剪辑文件等Web页含有一个基本的HTML文件&#xff0c;该基本HTML文件又包含若干对象的引用&#xff08;链接&#xff09;通过URL对每个对象进行引用…

程序员与电脑:不眠之夜的背后故事

在这个数字化飞速发展的时代&#xff0c;程序员和他们的电脑成了不可分割的伙伴。 如果你有机会深夜走过城市的某个角落&#xff0c;透过窗户瞥见那些亮着的电脑屏幕&#xff0c;你可能会好奇&#xff1a;这些电脑为什么总是开着的&#xff1f; 难道程序员们都有失眠症吗&…

猫头虎分享已解决Bug ‍ || 修改mongodb3.0副本集用户密码遇到 BeanDefinitionParsingException

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Solidworks:从草图到工程图纸,掌握正确的工作流程

1. 草图不及太在意构造线和尺寸标注的美观性&#xff0c;只要确保模型尺寸正确即可 因为草图不是最终输出的&#xff0c;这个阶段的工作重点是建立尺寸正确的实体模型&#xff0c;所以不要在意构造线和尺寸标注是否美观。 2. 工程图纸中标注尽量按照操作提示放置位置 工程图…

《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)

文章目录 10.1 理解系统负载10.1.1 重点基础知识10.1.2 重点案例&#xff1a;服务器响应变慢10.1.3 拓展案例 1&#xff1a;多核 CPU 系统的负载解读10.1.4 拓展案例 2&#xff1a;分析具体时间段的系统负载 10.2 优化性能10.2.1 重点基础知识10.2.2 重点案例&#xff1a;优化 …

‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序

遇到 vue-cli-service 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 的错误时&#xff0c;通常意味着Vue CLI没有被正确安装或配置在项目中。这可能是因为node_modules目录缺失了必要的包&#xff0c;或者局部安装的Vue CLI没有被正确设置到系统的PATH环境…

springsecurity6使用

spring security 中的类 &#xff1a; AuthenticationManager : 实现类&#xff1a;ProviderManager 管理很多的 provider &#xff0c;&#xff0c;&#xff0c; 经常使用的&#xff0c;DaoAuthenticationProvider , 这个要设置一个 UserDetailService , 查找数据库&#xff…

操作系统基础:IO管理概述【上】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 &#x1f3c6;&#x1f3c6;本文完整PDF源文件请翻阅至文章底部下载。&#x1f3c6;&#x1f3c6; &#x1f3ae;1 I/O设备的基本概念与分类&#x1f52b;1.1 总览&#x…

538. 把二叉搜索树转换为累加树

给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 提醒一下&#xff0c;二叉搜索树满足下列约束条件&#…

屏幕字体种类介绍

[ Script and font support in Windows ] [Windows 中的脚本和字体支持&#xff3d; 在Windows 2000 以前&#xff0c;Windows 的每个主要版本都会添加对新脚本的文本显示支持。本文介绍了每个主要版本中的更改。 Since before Windows 2000, text-display support for new scr…

云原生之基石-Docker Compose

1. 前言 在上一篇文章中介绍了基本的Docker工具&#xff0c;我们对单个应用程序进行单机单进程部署&#xff0c;制作Dockerfile文件&#xff0c;执行docker build来生成docker镜像&#xff0c; 执行docker run来运行一个容器&#xff0c;自己指定需要的参数如-v&#xff0c;但是…

【教3妹学编程-算法题】输入单词需要的最少按键次数 I

3妹&#xff1a;2哥&#xff0c;新年好鸭~ 2哥 : 新年好&#xff0c;3妹这么早啊 3妹&#xff1a;是啊&#xff0c;新年第一天要起早&#xff0c;这样就可以起早一整年 2哥 :得&#xff0c;我还不了解你&#xff0c;每天晒到日上三竿 3妹&#xff1a;嘿嘿嘿嘿&#xff0c;一年是…

作业2.12

1、选择题 1.1、以下程序的输出结果是____A____。 main() { int k11,k22,k33,x15; if(!k1) x--; else if(k2) if(k3) x4; else x3; printf(“x%d\n”,x); } A x4 B x15 C x14 D x3 1.2、有以下程序&#xff0c;while循环执行____A____次。 int main&#x…