【C++】STL-string模拟实现

news2024/12/29 9:33:32

文章目录

    • 驼峰法命名
  • 面试题:写一个简洁版的string
    • string成员变量
    • 构造函数
    • 析构函数
    • 拷贝构造函数
    • 获取C形式的字符串 c_str
    • 赋值重载 operator=
  • 简易版代码:
  • string的改造 ->支持增删查改
    • 接口总览
      • string成员变量
      • 构造函数
      • 交换
      • 拷贝构造
      • 赋值重载operator=
      • 析构函数
      • 返回元素个数
      • 返回容量
    • 遍历:
        • operator[]重载
      • 迭代器
        • 变量定义:
        • begin() 和end()
        • 范围for
    • 容量
      • reserve
      • resize
        • push_back
        • append
        • operator+=
          • 插入字符
          • 插入字符串
        • Insert
          • **在pos位置插入一个字符**
          • 在pos**位置之前**插入字符串
          • 简化push_back
          • 简化append
      • erase
    • npos
    • find
        • 查找一个字符
        • 查找一个字符串
    • rfind
        • 查找一个字符
        • 查找一个字符串
    • 比较
    • clear()
    • empty()
    • 流提取和流插入运算符重载
        • >>
        • <<
    • getline
  • 总结
    • 关于string的深入讨论
    • 关于string的深浅拷贝
    • string支持一个const char*的构造函数
  • String.h

驼峰法命名

1.函数,类名 单词首字母大写

  • 例如: PushBack
  1. 变量 第一个单词小写,后面的单词首字母大写
  • 例如: valueOver

在stl,linux源码中,所有都是小写,单词和单词之间用_分割


面试题:写一个简洁版的string

  • 1.考察的实现string的四个默认成员函数,深浅拷贝的问题
  • 2.可以写传统写法,也可以写现代写法

string成员变量

一个char*类型的指针


构造函数

注意:

  • 构造函数的缺省值不能为NULL,查文档也可得知无参时默认的参数时空串""
  • 我们要多开辟一个空间存放\0
//默认参数为空字符串,不能为NULL,不然strlen会报错! 要给空字符串
string(const char* str = "")
	:_str(new char[strlen(str)+1])   //要多开辟一个空间用来存放\0
{
    strcpy(_str, str);//\0也会拷贝过去
}

析构函数

~string()
{
    delete[] _str;//释放_str指向的空间  ->new[] 要和delete[]配合使用
    _str = nullptr;//指针置空
}

拷贝构造函数

image-20220204120746823


传统写法

  • 为了减少拷贝,传引用
  • 思路:开辟和要拷贝的对象的字符串大小一样的空间 ->使用库函数strcpy拷贝字符串
//s2(s1)
string(const string& s)
	:_str(new char[strlen(s._str)]+1) //要多开辟一个空间用来存放\0
{
   strcpy(_str, s._str);//字符串拷贝
}

现代写法

  • 为了减少拷贝,传引用
  • 先构造一个临时对象,然后临时对象和this指向的对象 进行字符串交换
//现代写法
string(const string& s)
	:_str(nullptr)//要初始化_str,否则为随机值,交换后,tmp._str就指向随机的空间,析构的时候释放非法空间
{
    //调用的是构造函数创建tmp,这里的参数是字符串!不是对象
    //string tmp(s)  ->这样是拷贝构造,s是对象,而s._str才是对象的内容->字符串
    string tmp(s._str);
    swap(_str, tmp._str);//调用std域里面的swap交换函数,交换两个字符串
 }

注意: string tmp(s) ->这样才是拷贝构造,s是对象,s._str是对象的内容-字符串, 如果用的是拷贝构造来构造tmp,就是错误的! 不断的复用拷贝构造,陷入死循环

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6OZM9tJm-1671703499128)( https://mangoimage.oss-cn-guangzhou.aliyuncs.com/image-20220204162145388.png)]


获取C形式的字符串 c_str

返回类型:const char*

//获取C形式的字符串
const char* c_str() const	//函数后加const修饰,const的对象也能调用
{
    return _str;
}

赋值重载 operator=

传统写法

  • 防止自己给自己赋值 比较地址是否一致
  • 减少拷贝->传引用
  • 为了可以连续赋值,所以要有返回值, 由于*this这块空间出了作用域不销毁,可以传引用返回 (this是局部变量指针,出了作用域就销毁了,是*this不销毁)
//case1:s1 = s2  case2:s1 = s1  case3:s1 = s2 = s3
string& operator=(const string& s)
{
    //防止自己给自己赋值 s1=s1,比较地址是否一致
    if (this != &s) 
    {
        delete[] _str;//先释放原来空间的内容
        _str = new char[strlen(s._str) + 1]; //多开辟一个空间保存\0
        strcpy(_str, s._str);
    }
    return *this;
}

注意:由于不会对对象s进行修改,所以参数可以传引用 + 用const修饰


现代写法

注意:此时的参数是传值,且不能加const修饰!

原因:要跟原对象进行交换,所以不能传引用,否则导致原对象改变! 也不能加const,否则不能被修改

// 现代写法
//s1 = s2 ->先用s2拷贝构造一个对象s,s和s2就有相同大小的空间和值,然后再拿s和s1交换
string& operator=(string s)
{
    swap(_str, s._str);//s是临时对象,出了作用域就销毁
    return *this;
}

由于s是临时对象,出了作用域自动调用析构函数销毁

如果这里是自己给自己赋值,会导致s1的本身空间地址发生改变,因为二者指向的空间发生了交换


这里调用swap函数,这里不仅需要引用#include<algorithm>函数,还需要指定类域 std::swap(tmp1,tmp2)

当然,我们也可以选择自己实现:

//注意!!这里要传引用!!,否则临时对象tmp析构,会导致交换后的空间也被释放
void swap(char*& str)
{
    char* tmp = str;
    str = _str;
    _str = tmp;
}
//函数内部调用swap只需传一个参数:
swap(tmp._str);

简易版代码:

#include<string.h>
#include<algorithm>
namespace Mango
{
class string
{
public:
	string(const char* str = "")
		:_str(new char[strlen(str)+1])
	{
		strcpy(_str, str);
	}
	~string()
	{
		delete[] _str;
		_str = nullptr;
	}
	//传统写法拷贝构造
	string(const string& s)
	{
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	}
	
    /*
	//现代写法的拷贝构造函数
	string(const string s)
	{ 
		_str = nullptr;
		string tmp(s._str);
		swap(tmp._str);
	}
	*/
	void swap(char*& str)
	{
		char* tmp = str;
		str = _str;
		_str = tmp;
	}
	const char* c_str() const
	{
		return _str;
	}
	string& operator=(const string& s)//为了支持连续赋值,所以返回引用
	{
		if (this != &s)
		{
			delete[] _str;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str,s._str);
		}
		return *this;
	}
	
	//现代写法
	/*string& operator=(string s)
	{
		swap(s._str);
		return *this;
	}
	*/
private:
	char* _str;
};
}

string的改造 ->支持增删查改

接口总览

#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
//不包含上面的文件,下面会报错
namespace Mango
{
	//模拟实现string类
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		string(const char* str = "");         //构造函数
		string(const string& s);              //拷贝构造函数
		string& operator=(const string& s);   //赋值运算符重载函数
		~string();                            //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//容量和大小相关函数
		size_t size();
		size_t capacity();
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		bool empty()const;

		//修改字符串相关函数
		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;

		//访问字符串相关函数
		char& operator[](size_t i);
		const char& operator[](size_t i)const;
		size_t find(char ch, size_t pos = 0)const;
		size_t find(const char* str, size_t pos = 0)const;
		size_t rfind(char ch, size_t pos = npos)const;
		size_t rfind(const char* str, size_t pos = 0)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;
		bool operator!=(const string& s)const;

	private:
		char* _str;       //存储字符串
		size_t _size;     //记录字符串当前的有效长度
		size_t _capacity; //记录字符串当前的容量
		static const size_t npos; //静态成员变量(整型最大值)
	};
	//静态成员变量在类外初始化
	const size_t string::npos = -1;

	//<<和>>运算符重载函数
	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, const string& s);
	istream& getline(istream& in, string& s);
}

string :管理字符串的数组,可以增删查改 , 字符串数组的结尾有\0

string成员变量

private:
    char* _str;//指向动态开辟的数组
    size_t _size;//字符串有效字符个数
    size_t _capacity;//容量

构造函数

image-20220204164422890

\0不是有效字符是标识字符串结尾的字符

  • _capacity表示的时能存储有效字符的个数,多开辟的一个空间是给\0的
//默认参数为空字符串,不能为NULL,不然strlen会报错! 要给空字符串
string(const char* str = "")
{
    _size = strlen(str);//如果没传参数,默认str为空串,大小为0
    _capacity = _size;
    _str = new char[_capacity + 1];//多开辟一个空间给\0
    strcpy(_str, str);//字符串拷贝,\0也会被拷贝
}

交换

注意:先去局部域去找swap函数,所以要指定域, :: 域限定符 左边没有写,默认在全局域中找

//s1.swap(s2)
void swap(string& s)
{
    ::swap(_str, s._str);
    ::swap(_size, s._size);
    ::swap(_capacity, s._capacity);
}

传引用是为了减少拷贝,因为传值传参会调用拷贝构造

image-20220204205108483

//C++98,c++11增加了右值引用的移动语义,优化了swap函数模板
::swap(s1,s2);//调用库的
s1.swap(s2);//自己写的 this->swap(s2)

拷贝构造

容量和size都可以不初始化,但是_str要初始化为空指针,不然就是随机值!交换的时候就会指针非法访问!

注意:拷贝构造和赋值重载两个函数,如果我们不写,编译器会帮我们自动生成一份,对呀内置类型完成的时浅拷贝,对于自定义类型会调用它自己的拷贝构造函数,如果使用默认的拷贝构造就会出现问题:

  • s2(s1) 如果是浅拷贝,s2和s1指向同一块空间,更改s1的内容会导致s2也受影响,析构的时候,会对这块空间析构两次

image-20220614114354245

所以我们需要实现深拷贝,传统写法:动态开辟空间,把数据拷贝过来 或者现代写法:先构造一个对象,然后交换

传统写法:

//拷贝构造 s2(s1)
string(const string& s)
    :_size(strlen(s._str))//写法2:_size(s._size)
    ,_capacity(s._capacity)
    {
		_str = new char[_capacity+1];//多开辟一个空间存放\0
        strcpy(_str,s._str);
    }

现代写法:

复用含参数的构造函数构造对象tmp,然后交换两个对象

image-20220614140724930

  • tmp是临时对象,出了作用域调用析构函数销毁, 如果tmp指向一个随机空间,析构的时候会崩溃
  • new/malloc出来的空间才能被释放,但是释放空指针没有问题free(nullptr);//没问题
//s2(s1)
string(const string& s)
	:_str(nullptr),_capacity(0),_size(0)   //要初始化_str,不然就是随机值
{
    string tmp(s._str);//先用s构造一个临时对象 注意:这里的参数是字符串!!!
    swap(tmp);  //this->swap(tmp)
}

由于修改对象s,所以可以传引用!


赋值重载operator=

传统写法: 开辟新空间,把数据拷贝过来

同样的,如果我们不实现,默认生成的就是浅拷贝

image-20220614135212418


所以我们需要自己实现一份深拷贝,先释放旧空间,然后开辟新空间,把数据拷贝过来

  • 注意:要防止自己给自己赋值,如:s1=s1, 这样会导致错误,把原空间销毁了
  • 有可能是连续赋值,所以要有返回值,由于*this这块空间出了作用域不销毁,可以传引用返回
//可能存在Bug的代码
string& operator=(const string& s)
{
    if(this != &s)
    {
        delete[] _str;//先释放旧空间
        _str = new char[s._capacity+1];//开辟和s一样大小的空间
        strcpy(_str,s_str);
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}

上述可能存在的问题是什么?

new空间可能会失败,会直接抛出异常,而我们之前先把_str的空间释放了,所以就会导致_str变为野指针,所以可以稍微换一种写法

  • 先用临时变量接收这块空间的地址,然后拷贝内容到这块空间上,然后再处理
string& operator=(const string& s)
{
    if(this != &s)
    {
       char* tmp = new char[s._capacity+1];//为\0也要预留空间
       strcpy(tmp,s._str);
       delete[] _str;//释放原空间
       _str = tmp;
        
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}

现代写法: 创建一个对象出来进行交换

  • 防止自己给自己赋值 比较地址是否一致
  • 由于*this这块空间出了作用域不销毁,可以传引用返回 (this是局部变量地址(对象的地址),出了作用域就销毁了,而*this是对象,出了作用域不销毁)
  • 此处是传值传参! 不可以传引用,否则会对原对象进行修改!
/*写法1:依旧传引用,但是在内部构造临时对象交换
		string& operator=(const string& s)
		{
			if(this != &s)
			{
                string tmp(s._str);
                swap(tmp);
			}
			return *this;
		}
*/
//写法2:传值,然后直接交换

string& operator=(string s)
{
    //防止自己给自己赋值
	if(this != &s)
    {
        swap(s); //this->swap(s)
    }
    return *this;
}

此时参数的s是局部变量,出了作用域会调用析构函数销毁

如果此时不防止自己给自己赋值是否可行?

此时虽然不会发生误销毁原空间的情况,但是会导致地址发生改变

image-20220614142126081


析构函数

~string()
{
    delete[] _str;// 释放数组空间
    _str = nullptr;//指针置空
    _size = 0;
    _capacity = 0;
}

返回元素个数

元素个数不可能为负数 所以返回类型为size_t

size_t size()  const
{
    return _size;
}

加const修饰函数,这样普通对象和const对象都能调用


返回容量

容量不可能为负数 所以返回类型为size_t

size_t capacity()  const
{
    return _capacity;
}

加const修饰函数,这样普通对象和const对象都能调用


遍历:

at()作用和operator[]类似 ,不同点是:at()越界抛异常,[]越界直接报错

operator[]重载

返回第i个位置的字符

普通对象 :可读可写

返回下标为i的字符的引用,出了作用域空间还在->用引用返回

//operator[]
//可读可写,所以返回引用
char& operator[](size_t i)
{
    assert(i < _size);//保证i的合法性
    return _str[i];
}

const对象:只读

出了作用域空间还在->用引用返回

//只读
const char operator[](size_t i) const
{
    assert(i < _size);//保证i的合法性
    return _str[i];
}

迭代器

分为const迭代器和普通迭代器, const对象调用函数,优先选择最匹配的const成员函数

变量定义:

public:
	typedef char* iterator;
	typedef const char* const_iterator;

begin() 和end()

image-20220204210250639

begin() :_str位置 end() :_str+_size位置

//普通迭代器 - 普通对象调用
iterator begin()
{
    return _str;
}
iterator end()
{
    return _str+_size;
}

//const迭代器 -const对象调用
const_iterator begin() const
{
    return _str;
}
const_iterator end() const
{
    return _str + _size;
}

有了迭代器自然就支持了范围for了


迭代器遍历对象

void test()
{
    string s1 = "hello world";
    string::iterator it = s1.begin();//调用string的迭代器
    while (it != s1.end())
    {
        cout << *it << " ";
        it++;
    }
}

范围for

看起来很神奇,但是原理很简单,范围for会被编译器替换成迭代器形式,也就是说范围for是有迭代器支持的

for(auto ch:s1)
{
    cout<<ch<<" ";//ch本质就是s的每一个字符
}

注意:迭代器的begin和end函数名要小写!不然使用范围for会报错


容量

容量为n,实际空间为n+1个,因为有一个空间是用来存放\0的, _size是有效字符个数,当_size == _capacity 空间就满了

reserve

开辟空间 ->只改变capacity 不改变size

  • 由于容量不为负数,所以参数类型:size_t
  • 先开辟n+1个空间,然后把原空间的内容拷贝到新空间,再释放原空间,指针指向新空间
//开空间 capacity变,size不变
void reserve(size_t n)
{
    //判断是否是扩容
    if (n > _capacity)
    {
        char* tmp = new char[n + 1];//多开辟一个空间存放\0
        //strcpy(tmp, _str);//把原内容拷贝到新空间,使用strcpy可能存在问题我!
        //把_str的内容拷贝到tmp,共拷贝_size+1个字节
        strncpy(tmp,_str,_size+1);//+1是为了把原空间的最后位置的\0也拷贝
        delete[] _str;//释放原空间

        _str = tmp;//_str指向新空间
        _capacity = n;//容量改为n  _size不变
    }
}

注意:不能使用strcpy函数,因为如果有效字符包含\0,则会发生错误! 所以应该使用strncpy,直接根据有效字符的个数拷贝

image-20220205100519827


resize

开辟空间 ->改变capacity && 改变size

默认用\0初始化

image-20220204215826439


image-20220204220305210

if(n<_size)
{
    _size = n;//减少有效字符个数为n个
    _str[_size] = '\0';//提前终止
}

针对case2和case3:可以写成一种情况: 都需要填充字符

//开空间+初始化 capacity和size都要改变
//默认填充字符为\0
void resize(size_t n, char val = '\0')
{
    //case1
    if (n < _size)
    {
        _size = n;//减少有效字符个数为n个
        _str[_size] = '\0';//提前终止
    }
    else
    {
        //判断是否是增容 -针对case3
        if (n > _capacity)
        {
            reserve(n);//扩容成n个空间,然后下面再填充数据
        }
        //case2和case3
        //填数据,从原字符串\0位置填,填到字符个数为n个
        for (size_t i = _size; i < n; i++)
        {
            _str[i] = val;
        }
        _str[n] = '\0';//最后在下标为n位置放\0
        _size = n;//字符个数变为n个
    }
}

push_back

要注意最后要存放\0

//插入字符-尾插
void push_back(char ch)
{
    //先判断容量够不够
    if (_size == _capacity)
    {
        //两倍扩容
        //如果一开始构造的是空串,_capacity = 0,*2之后还是0,reserve(0),没有开辟空间,然后下面对_size位置访问->就是非法访问,err
       // reserve(_capacity * 2); ->err,
        reserve(_capacity==0?4:_capacity*2);
    }
    //插入字符 + 处理\0
    _str[_size] = ch;//在原来\0位置插入字符
    _str[_size + 1] = '\0';//在后面放\0
    _size++;//有效字符+1
    
    //上面三行代码也可以简写为	: _str[_size] = ch ,_str[++_size] = '\0'
}

append

  • 不宜两倍扩容,因为两倍扩容也不知道够不够,直接把容量扩成插入之后的总长度
//尾插入字符串 -> 相当于在_size位置往后插入,可以复用insert函数
void append(const char* str)
{
    //计算插入之后总长度
    size_t len = _size + strlen(str);//原有长度+插入字符串长度
    //如果len=_capacity刚好能存放,有\0
    if (len > _capacity)
    {
        //扩容
        reserve(len);//复用reserve函数,reserve内部处理了\0,把\0也拷贝到新空间了
    }
    strcpy(_str + _size, str);//在原字符串末尾(\0位置)插入字符串,str中的\0也会被拷贝
    _size = len;//长度变为插入后总长度
}

image-20220204214834795

如果使用strcat(追加函数):需要找到原字符串\0位置,然后再插入,需要遍历前面的字符,效率低.

此处可以直接算出\0位置在哪里,用strcpy更高效


operator+=

插入字符

复用push_back函数

返回插入之后的对象,出了作用域还在!所以可以返回引用

//s1+='x'
string& operator+=(char ch)
{
    push_back(ch);
    return *this;
}

插入字符串

复用append函数

返回插入之后的对象,出了作用域还在!所以可以返回引用

//s1+="xxxx"
string& operator+=(const char* str)
{
    append(str);
    return *this;
}

Insert

在pos位置插入一个字符
  • 检查pos位置的合法性
  • 检查容量够不够
  • 把pos位置及其后面的数据往后移动 //pos位置的字符也要往后移动
  • 在pos位置插入字符
  • 有效字符++
  • 返回插入之后的对象,由于出了作用域不销毁,返回引用

image-20220204224606685

Bug1: 若最初为空串:reserve(0) ,没有开辟空间出来,但是后序却访问了_str[pos]位置->非法访问


image-20220204224625822

//Insert
string& insert(size_t pos, char ch)
{
    //检查pos的合法性
    //可以在_size位置插入(原来字符串的\0位置)
    assert(pos<=_size);
    //先检查容量
    if (_size == _capacity)
    {
        reserve(_capacity==0?4:_capacity*2);
    }
    //解决办法1:使用指针
    /*
		char* end = _str + _size;//指向的是\0的位置
		//从后往前移动数据
		while (end >= _str + pos)
		{
			*(end + 1) = *end;
			--end;
		}
	*/
    /*解决办法2:强转pos的类型为int
		int end = _size;//最后一个有效字符的下一个位置,即\0位置
		while(end>=(int)pos)
		{
			_str[end+1]=_str[end];
			--end;
		}
	*/

    //解决办法3:让end和pos不能相等
    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];//从后往前拷贝
        --end;
    }
    _str[pos] = ch;//把字符插入到pos位置
    _size++;//数据个数+1
    //返回插入数据后的字符串
    return *this;
}

在pos位置之前插入字符串
  • 检查pos位置的合法性
  • 检查插入之后的容量够不够
  • 把pos位置及其后面的数据往后移动len个位置(len:插入字符串的长度)
  • 在pos位置开始往后拷贝字符串,注意:不能用strcpy!!! 因为strcpy会拷贝\0
  • 有效字符+len
  • 返回插入之后的对象,由于出了作用域不销毁,返回引用

移动数据时,需要注意上面insert的Bug2情况!

//在pos位置插入字符串
string& insert(size_t pos, const char* str)
{
    //保证pos位置的合法性
    //可以在_size位置插入,
    assert(pos <= _size);
    size_t len = strlen(str);

    //插入字符串之后容量是否够
    //如果len + _size = _capacity刚好能放满,因为_capacity多开了一个空间存放\0
    if (len + _size > _capacity)
    {
        //扩容 -开辟 原来字符串长度+插入字符串长度 个空间
        reserve(_size+len);
    }

    //挪动数据,从\0位置开始移动
    char* end = _str + _size;
    //pos位置的字符也要往后移动,尽管是在最后一个位置插入,也能保证插入后最后一个位置是\0,
    while (end >= _str + pos)
    {
        *(end + len) = *end;//每个字符往后移动len个位置
        --end;
    }
    //插入字符串
    //strcpy(_str+pos,str)//不能用strcpy,因为strcpy会把插入字符串的\0也拷贝过去,可能导致提前结束
    //这里我们要根据字节数拷贝
    strncpy(_str + pos, str, len);//从_str+pos位置开始把str拷贝过去,只拷贝str中的len个字符(不含\0)
    _size += len;//有效字符个数+len

    return *this;
}

注意:插入字符串的时候使用strncpy,不能使用strcpy,否则会将待插入的字符串后面的’\0’也插入到字符串中,


简化push_back
void push_back(char ch)
{
    insert(_size,ch);//尾插一个字符 ->在_size位置插入一个字符
}

简化append
void append(const char* str)
{
    insert(_size,str);//尾插一个字符串 ->在_size位置插入一个字符串
}

erase

从pos位置开始删除len个字符

情况分析:

情况1:删除位置后面的字符个数 < 要删除的个数

image-20220613104925734


情况2:删除位置后面的字符个数 >= 要删除的个数

image-20220613105041352


//从pos位置开始删除len个字符
//len的缺省值为npos,size_t类型,其值为42亿多,字符串没有这么长
//如果不传len,即删除pos位置后面的所有字符
string& erase(size_t pos, size_t len = npos)
{
    //pos不能为\0位置,保证pos的合法性
    assert(pos < _size);
    size_t sz = _size - pos;//计算pos位置后面剩余的字符个数
    //case1:剩余的字符长度<=要删的长度 ->后面的全删完
    if (len >= sz) //len=npos的时候,也会进入这里
    {
        //把pos位置搞成\0,后面的直接忽略
        _str[pos] = '\0';
        _size = pos;//有效字符变成pos个
    }
    //case2:剩余的字符长度大于要删的长度
    else
    {
        //把_str+pos+len后面的字符往前覆盖到_str+pos位置
        strcpy(_str+pos, _str + pos + len);
        _size -= len;//删去了len个字符
    }
    return *this;
}

npos

静态成员变量->在类里面声明,在类外面定义赋值! 可以不加static

image-20220205003431795

find

查找一个字符

从pos位置开始查找字符,找到了返回下标,没有找到返回npos

//默认从0位置开始找
size_t find(char ch, size_t pos = 0)
{
    //不能在\0位置开始往后找
    assert(pos < _size);
    //从pos位置遍历查找
    for (size_t i = pos; i < _size; i++)
    {
        if (_str[i] == ch)
        {
            return i;
        }
    }
    //找不到
    return npos;
}

查找一个字符串

从pos位置开始查找字符串,找到了返回下标,没有找到返回npos

  • 查找子串,复用strstr函数
//默认从0位置开始找
size_t find(const char* str, size_t pos = 0)
{
    //不能在\0位置开始往后找
    assert(pos < _size);
    //从pos位置开始找子串str首次出现的位置,找到了返回指针
    const char* ret = strstr(_str + pos, str);
    if (ret != nullptr)
    {
        //找到了,要返回下标 
        //指针-指针就是相距的距离 ==> 距离起始位置的下标
        return ret - _str;
    }
    else
    {
        //找不到
        return npos;
    }
}

rfind

和find基本一致,只不过是从后往前找

思路:

先把字符串进行逆序, 然后复用find的逻辑

image-20220205092024921

查找pos位置的字符/字符串 ->复用find函数,找逆置之后的字符串的pos'位置

注意:最后要返回的是pos位置


查找一个字符

  • 首先我们需要用对象拷贝构造一个临时对象tmp,因为我们并不希望调用rfind函数后原对象被修改

  • 将tmp对象的字符串逆置,然后将所给pos镜像对称一下再调用find函数

  • 将从find函数接收到的返回值镜像对称一下作为rfind函数的返回值返回即可,

//默认从npos位置开始找
//从后往前找
size_t rfind(char ch, size_t pos = npos) const
{
    string tmp(*this); //拷贝构造
    reverse(tmp.begin(), tmp.end()); //调用reverse逆置对象tmp的字符串
    //pos大于字符串有效长度
    if (pos >= _size) 
    {
        pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
    }
    pos = _size - 1 - pos; //将pos改为pos'位置
    size_t ret = tmp.find(ch, pos); //复用find函数
    if (ret != npos)
        return _size - 1 - ret; //找到了,返回pos位置
    else
        return npos; //没找到,返回npos
}

注:rfind函数规定,当所给的pos大于等于字符串的有效长度时,看作所给pos为字符串最后一个字符的下标,


查找一个字符串

  • 用对象拷贝构造一个临时对象tmp,然后将tmp对象的C字符串逆置,

  • 需要拷贝一份待查找的字符串,将其逆置,

  • 将所给pos对称一下,改为pos’位置再调用find函数,

注意:通过find函数得到pos'位置后,得到的是待查找字符串的最后一个字符在对象C字符串中的位置

而我们需要返回的是待查找字符串在对象C字符串中的第一个字符的位置

所以还需做进一步调整后才能作为rfind函数的返回值返回

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

比较

比较操作只需写两个,其他的复用即可

比较函数可以重载为类的成员函数也可也重载为全局的函数, 重载为成员函数的好处是:不必借助友元就可以访问类的成员,如果写在全局,如果要访问类的私有/保护成员,就要在类种写成友元函数

//字符串比较定义为全局的比较好,看着不别扭,要注意定义的顺序,因为要复用函数!
//可以使用内联->直接在调用的地方展开,效率高
inline bool operator<(const string& s1, const string& s2) 
{
	return strcmp(s1.c_str(), s2.c_str()) <0;
}
inline bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str())== 0;
}
inline bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}
inline bool operator>(const string& s1, const string& s2)
{
	return !(s1 <= s2);
}
inline bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}
inline bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

如果我们想不使用strcmp函数,我们也可以自己遍历两个字符串比较字符

bool operator<(const string& s1, const string& s2)
{
	size_t i = 0;
	size_t j = 0;
	while (i < s1.size() && j < s2.size())
	{
		//挨个字符比较
		if (s1[i] < s2[j])
		{
			return true;
		}
		else if (s1[i] > s2[j])
		{
			return false;
		}
		else //s1[i] == s2[j],比较下一个字符
		{
			i++;
			j++;
		}
	}
	//有一个字符串遍历结束了 ||两个字符串都遍历结束了
	return s1.size() < s2.size() ? true : false;
}

如果写在类里面:只需要传一个参数,因为第一个参数默认是this指针

加const修饰函数,这样普通对象和const对象都能调用

//>运算符重载
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 strcmp(_str, s._str) < 0;
	return !(*this >= s);//复用>= (*this)->operator=(s) ,*this是string对象,this是地址
}
//<=运算符重载
bool operator<=(const string& s)const
{
    //return (*this < s) || (*this == s);
	return !(*this > s);//(*this)->operator>(s) ,*this是string对象,this是地址
}
//!=运算符重载
bool operator!=(const string& s)const
{
	return !(*this == s); //(*this)->operator==(s) ,*this是string对象,this是地址
}

clear()

清空已有内容

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

empty()

判断字符串是否为空

有空间但是没有数据 -> 也是空

#include<string>
int main()
{
	string s1;
	s1.reserve(10);//只开空间
	cout << s1.empty() << endl;//1
	return 0;
}

//判空
bool empty() 
{
    //判断方式1:return _size == 0; 
	return strcmp(_str, "") == 0;//判断字符串是否是空串
}

注意:两个字符串相比较不能用 == ,要使用strcmp函数 也可以直接判断_size是不是0


流提取和流插入运算符重载

注意:下面两个函数重载的函数都是写在类外的

原因:如果写在类内部,则函数的第一个成员默认是this指针, 流对象和对象抢占左操作数的位置,调用的时候就会看起来很奇怪, 所以要重载成全局的函数

问:需要重载成友元函数吗?

看该函数是否需要访问私有成员

>>

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

标准库里面也是先清空再写入的:

image-20220614151834118


err代码:

原因:如果原来对象的字符串中有内容,就会在原来的内容上增加,就不符合逻辑

>>会自动跳过空格和换行,所以不能使用>> ->使用get

//in是cin的别名,返回引用是为了支持连续赋值:  cin >> s1 >> s2
istream& operator>>(istream& in, string& s)
{
    //先读取一个字符
	char ch;
	in >> ch;
	//读取字符插入,读取到空格或是’\n’便停止读取,
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		in >> ch;//继续读取下一个字符
	}
	return in;
}

正确写法: cin.get()可以用来接收字符 要清空原有内容

cin是流对象,get是它的方法

istream& operator>>(istream& in, string& s)
{
	//先清空已有内容,否则会出错
	s.clear();
	char ch;
	ch = in.get();//类似getchar(),读取一个字符
	//读取字符插入,读取到空格或是’\n’便停止读取
    //注意:用的是&&  遇到空格或者\n都停止!!!
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();//继续读取下一个字符
	}
	return in;
}

<<

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

注意:s对象不修改!所以可以传引用和const修饰 out就是cout的别名,为了可以输出多个对象(支持连续输出),所以要返回out的引用

//out是cout的别名,返回引用是为了支持连续输出: cout << s1<<s2<<endl;
ostream& operator<<(ostream& out, const string& s)
{ 
    for (auto ch : s) 
    {
        out << ch;
    }
    /*//我们关注的是size,而不是\0
    for(size_t i = 0;i<s.size();i++)
    {
    	out << s[i];
    }
    */
    return out;
}

注意:不能直接使用out<<s.c_str()<<endl; 或者 out << s._str<<endl;的方式打印

我们可以使用标准库的string进行验证:

因为以字符串形式输出,关注的是\0,而我们输出关注的是_size,

image-20220614151544143


getline

getline函数用于读取一行含有空格的字符串,实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符,

istream& getline(istream& in, string& s)
{
	//先清空已有内容
	s.clear();
	char ch;
	ch = in.get();//类似getchar(),读取一个字符
	//读取字符插入,读取到空格或是’\n’便停止读取,
	while (ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

总结

string是一个管理字符数组的类,要求这个字符数组结尾用\0标识

1.拷贝构造和赋值重载实现深拷贝

2.增删查找的相关接口(跟顺序表类似)

3.重载了一些常见的运算符 如: > < >> << []

4.迭代器


image-20220205110734833


image-20220205110749293


具体要不要加const要看接口的功能性质

1.只读接口函数 -> 加const

2.只写接口函数 ->不加const

3.可读可写接口函数 ->分为加const版本和不加const版本


关于string的深入讨论

C++标准只规定了string要实现的接口功能,具体如何实现,看各个库的实现人自己决定的

  • 1.vs系列的编译器,由微软工程师实现
  • 2.gcc/g++GNU的c & c++编译器 由开源组织实现的
  • 3.Clang编译器,Clang是一个C语言,C++,Objective-C语言的轻量级编译器

虽然它们的结构不一样,实现也就不一样,但是整体增删查改的逻辑大同小异

image-20220205112503889

关于string的深浅拷贝

image-20220205112751808

计数为1时,才是独享这块空间!


vs下的深拷贝:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mS46X4kU-1671703499146)( https://mangoimage.oss-cn-guangzhou.aliyuncs.com/image-20220205112840504.png)]

一开始两个对象占据的是不同一块空间, 二者不相影响


Linux版本下的深拷贝 ->写时拷贝

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vcf54Isi-1671703499146)( https://mangoimage.oss-cn-guangzhou.aliyuncs.com/image-20220205112902496.png)]

一开始二者占据的是同一块空间, 更改某一个对象时,二者的地址发生改变.


string支持一个const char*的构造函数

Mango::string s2 = "hello";//先构造一个临时对象"hello",然后拿这个临时对象拷贝构造s2, 连续的构造,会被编译器优化,直接用"hello"构造s2

如果不想发生这种隐式类型转化:加explicit关键字

//构造函数
explicit string(const char* str = "")
{
    _size = strlen(str);//如果没传参数,str为空串,大小为0
    _capacity = _size;
    _str = new char[_capacity + 1];//多开辟一个空间给\0
    strcpy(_str, str);//字符串拷贝,\0也会被拷贝
}

String.h

  • 为了防止命名冲突 ->写在自己定义的命名空间内
  • 整个模拟实现的代码都是写在.h文件下的, 不可以.h放声明, .cpp放定义 因为模板不支持分离编译
  • string标准库中提供了一个swap交换函数,而全局std域也提供了一个swap的模板函数, 二者都是对两个对象的成员进行交换
    • 而string类中的swap仅仅是对3个成员变量进行交换,效率更高
    • 如果选择全局域std中的swap,则要进行3次深拷贝(拷贝构造+赋值重载)
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
#pragma warning(disable:26495)
namespace Mango
{
	//模拟实现string类
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		//构造函数
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];//为\0开辟空间
			strcpy(_str, str);
		}

		//拷贝构造函数
		//s2(s1)
		//现代写法:
		/*
		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);
			swap(tmp);
		}
		*/
		
		//传统写法:
		string(const string& s)
		{
			_size = s._size;
			_capacity = s._capacity;
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}

		//赋值运算符重载函数
		//s1 =s2
		// 
		//传统写法:
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				delete[] _str;
				_size = s._size;
				_capacity = s._capacity;
				_str = new char[_capacity + 1];
				strcpy(_str, s._str);
			}
			return *this;
		}
		//现代写法:
		/*
		string& operator=(const string& s)
		{
			if(this != &s)
			{
				string tmp(s._str);//调用构造函数,构造临时对象
				swap(tmp);
			}
			return *this;
		}
		*/
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		//迭代器相关函数
		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()
		{
			return _size;
		}
		size_t capacity()
		{
			return _capacity;
		}
		//开空间 ->只修改_capacity 不修改_size
		void reserve(size_t n)
		{
			//增容才处理
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];//多开一个空间给\0
				//strcpy(tmp, _str);//不建议使用strcpy,建议使用strncpy按字节拷贝
				strncpy(tmp, _str, _size + 1);
				_capacity = n;
				_str = tmp;
			}
		}
		void resize(size_t n, char ch = '\0')
		{
			//根据n的大小决定怎么调整
			if (n < _size)
			{
				_str[n] = '\0';//直接缩小到n位置
				_size = n;
			}
			else
			{
				//需要填充字符
				if (n > _capacity)
				{
					reserve(n);
				}
				//从_size位置填充到n位置
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_str[n] = '\0';
				_size = n;
			}
		}
		bool empty() const
		{
			return _size == 0;
		}

		//修改字符串相关函数
		void push_back(char ch)
		{
			//判断是否需要增容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			//插入字符 + 处理\0
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = _size + strlen(str);//插入后的总长度
			if (len > _capacity)
			{
				reserve(len);//扩容成len个空间
			}
			//在尾部追加
			strcpy(_str + _size, str);
			_size = len;
		}
		string& operator+=(char ch)
		{
			//insert(_size,ch);
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			//insert(_size,str);
			append(str);
			return *this;
		}
		string& insert(size_t pos, char ch)
		{
			assert(pos <=_size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			//[pos,_size]的字符往后移动,\0也后移
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				*(end+1) = *end ;
				end--;
			}
			//插入到pos位置 
			_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);
			}
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				//所有字符往后挪len个长度
				*(end + len) = *end;
				end--;
			}
			//在pos位置插入该字符串 -> strcpy
			strncpy(_str + pos, str, len);//从_str+pos位置开始把str拷贝过去,只拷贝len个字符(不含\0)
			_size += len;//修改字符串长度
			return *this;
		}
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			size_t sz = _size - len;//剩余字符个数
			//从pos位置要删除的长度>剩余字符个数
			if (len >= sz)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				//把_str + _pos+len后面的字符往前拷贝覆盖
				strcpy(_str + pos, _str + pos + len);
			}
			return *this;
		}
		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		const char* c_str()const
		{
			return _str;
		}

		//访问字符串相关函数
		char& operator[](size_t i)
		{
			assert(i < _size);
			return _str[i];
		}
		const char& operator[](size_t i)const
		{
			assert(i < _size);
			return _str[i];
		}
		size_t find(char ch, size_t pos = 0)const
		{
			assert(pos < _size);
			//遍历查找
			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)const
		{
			assert(pos < _size);
			const char* ret = strstr(_str+pos, str);
			if (ret)
			{
				return ret - _str;
			}
			else
			{
				return npos;
			}
		}
		//默认从npos位置开始找
		//从后往前找
		size_t rfind(char ch, size_t pos = npos) const
		{
			string tmp(*this); //拷贝构造
			reverse(tmp.begin(), tmp.end()); //调用reverse逆置对象tmp的字符串
			//pos大于字符串有效长度
			if (pos >= _size)
			{
				pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
			}
			pos = _size - 1 - pos; //将pos改为pos'位置
			size_t ret = tmp.find(ch, pos); //复用find函数
			if (ret != npos)
				return _size - 1 - ret; //找到了,返回pos位置
			else
				return npos; //没找到,返回npos
		}
		//默认从npos位置开始找
		size_t rfind(const char* str, size_t pos = npos) const
		{
			string tmp(*this); //拷贝构造
			reverse(tmp.begin(), tmp.end()); //调用reverse逆置tmp对象的字符串
			size_t len = strlen(str); //待查找的字符串的长度
			char* arr = new char[len + 1]; //开辟arr字符串(用于拷贝str字符串),多给一个空间是给\0的
			strcpy(arr, str); //拷贝str给arr
			size_t left = 0, right = len - 1; //设置左右指针
			//逆置字符串arr
			while (left < right)
			{
				::swap(arr[left], arr[right]);//使用的是std全局域的swap函数,
				left++;
				right--;
			}
				if (pos >= _size) //所给pos大于字符串有效长度
				{
					pos = _size - 1; //重新设置pos为字符串最后一个字符的下标
				}
			pos = _size - 1 - pos; //将pos改为镜像对称后的位置pos'
			size_t ret = tmp.find(arr, pos); //复用find函数从pos'位置开始找字符串arr
			delete[] arr; //销毁arr指向的空间,避免内存泄漏
			if (ret != npos)
				return _size - ret - len; //找到了,返回ret镜像对称后再调整的位置
			else
				return npos; //没找到,返回npos
		}

		//关系运算符重载函数
		bool operator>(const string& s)const
		{
			return strcmp(_str, s._str) > 0;
		}
		bool operator>=(const string& s)const
		{
			//*this就是string对象,两个string对象比较
			return ((*this) > s) || ((*this) == s);
		}
		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 strcmp(s._str, _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; //静态成员变量(整型最大值)
	};
	//静态成员变量在类外初始化
	const size_t string::npos = -1;

	//<<和>>运算符重载函数
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();//先读取一个字符
		while (ch != '\n' && ch != ' ')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}
	ostream& operator<<(ostream& out, const string& s)
	{
		//范围for遍历输出即可,本质是迭代器
		for (auto ch : s)
		{
			out << ch ;
		}
		/*
		for(int i = 0;i<_size;i++)
		{
			cout << s[i];
		}
		*/
		return out;
	}
	istream& getline(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();//先读取一个字符
		while (ch != '\n' )
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}
	//测试反向查找
	void testString8()
	{
		string tmp("Mango Hello world");
		cout << tmp.rfind('e') << endl;
		cout << tmp.rfind("He") << endl;
	}
	//测试现代写法的成员函数
	void testString1()
	{
		string s0;
		string s1("Always");
		string s2(s1);
		cout << s2.c_str() << endl;
		string s3("more than words");
		s3 = s1;
		cout << s3.c_str() << endl;
		s3 = s3;
	}
	//测试流插入流提取运算符重载
	void testString7()
	{
		//string s;
		string s("Hello Mango");
		cin >> s;
		cout << s << endl;
		// 不能以字符串形式输出,测试标准库
		string s1("more than");
		s1 += '\0';
		s1 += "words";
		cout << s1 << endl;
		cout << s1.c_str() << endl;
	}
	// 测试比较大小运算符重载
	void testString6()
	{
		string s1("abcd");
		string s2("abcd");
		cout << (s1 <= s2) << endl;
		string s3("abcd");
		string s4("abcde");
		cout << (s3 <= s4) << endl;
		string s5("abcde");
		string s6("abcd");
		cout << (s5 <= s6) << endl;
	}
	// 测试insert和erase
	void testString5()
	{
		string s(" Mango Hello");
		s.insert(0, "Lemon");
		cout << s.c_str() << endl;
		s.insert(5, '!');
		cout << s.c_str() << endl;
		s.erase(0, 7);
		cout << s.c_str() << endl;
		s.erase(6);
		cout << s.c_str() << endl;
	}
	// 测试查找
	void testString4()
	{
		string s("Mango");
		cout << s.find('m') << endl;
		cout << s.find("max") << endl;
	}
	// 测试resize
	void testString3()
	{
		string s("Mango"); // capacity - 12
		s.resize(5);
		cout << s.c_str() << endl;
		s.resize(7, '!');
		cout << s.c_str() << endl;
		s.resize(20, '~');
		cout << s.c_str() << endl;
	}
	// 测试尾插字符及字符串push_back/append,同时测试reserve
	void testString2()
	{
		string s("more than words");
		s.push_back('~');
		s.push_back(' ');
		cout << s.c_str() << endl;
		s.append("zhabuduodele");
		cout << s.c_str() << endl;
		s += '~';
		s += "yiyandingzhen";
		cout << s.c_str() << endl;
	}
}
/*
bool operator<(const string& s1, const string& s2)
{
	size_t i = 0;
	size_t j = 0;
	while (i < s1.size() && j < s2.size())
	{
		//挨个字符比较
		if (s1[i] < s2[j])
		{
			return true;
		}
		else if (s1[i] > s2[j])
		{
			return false;
		}
		else //s1[i] == s2[j],比较下一个字符
		{
			i++;
			j++;
		}
	}
	//有一个字符串遍历结束了 ||两个字符串都遍历结束了
	return s1.size() < s2.size() ? true : false;
}
*/

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

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

相关文章

Python图像识别实战(四):搭建卷积神经网络进行图像二分类(附源码和实现效果)

前面我介绍了可视化的一些方法以及机器学习在预测方面的应用&#xff0c;分为分类问题&#xff08;预测值是离散型&#xff09;和回归问题&#xff08;预测值是连续型&#xff09;&#xff08;具体见之前的文章&#xff09;。 从本期开始&#xff0c;我将做一个关于图像识别的…

大疆A3飞控使用|飞控配置

大疆A3飞控使用|飞控配置大疆A3飞控介绍总体特性飞行特性外围设备保护功能SDK拓展拓展功能A3 飞控使用配置连接飞机基本设置机架安装遥控器电调动力配置感度电池控制参数调试基础感度动力带宽高级感度灵敏度控制器性能参数大疆A3飞控介绍 全新A3系列飞控系统结合安全可靠和精准…

【虹科案例】用于超高磁场的虹科 digitizerNETBOX——高采样率和完全同步采样

应用背景 国际 MegaGauss 科学实验室是东京大学固态物理研究所 (ISSP) 的一部分。实验室的目的是研究固态材料&#xff08;如半导体、磁性材料、金属、绝缘体、超导材料&#xff09;在超高磁场下的物理特性&#xff0c;这些领域还包括研究新材料并控制其阶段和功能。实验室脉冲…

百度工程师带你体验引擎中的nodejs

作者 | 糖果candy 导读 如果你是一个前端程序员&#xff0c;你不懂得像PHP、Python或Ruby等动态编程语言&#xff0c;然后你想创建自己的服务&#xff0c;那么Node.js是一个非常好的选择。 Node.js 是运行在服务端的 JavaScript&#xff0c;如果你熟悉Javascript&#xff0c;那…

【Kotlin 协程】Flow 异步流 ③ ( 冷流 | 流被收集时运行 | 流的连续性 )

文章目录一、冷流 ( 流被收集时运行 )二、流的连续性一、冷流 ( 流被收集时运行 ) Flow 异步流 的 构建器函数 flow 函数 中的 代码 , 在 调用 Flow#collect 函数 时 , 也就是在 Flow 异步流 收集元素时 , 才会 执行 flow 构建器 中的代码 ; 这种机制的异步流 称为 冷流 ; 代…

移动WEB开发之响应式布局--Bootstrap栅格系统

栅格系统简介 栅格系统英文为“grid systems”,也有人翻译为“网格系统”&#xff0c;它是指将页面布局划分为等宽的列&#xff0c;然后通过列数 的定义来模块化页面布局。 Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统&#xff0c;随着屏幕或视口&#xff08;vi…

spring-statemachine状态机梳理

目录 一、基本回顾 1、为什么要用状态机 2、什么是状态机 3、状态机可归纳为4个要素 4、对应Spring StateMachine的核心步骤 5、简单例子 添加maven依赖 定义状态枚举和事件枚举 完成状态机的配置 简单测试一下 添加Listener 监听器,当状态变更时&#xff0c;触发方…

1. SpringMVC概述与入门

1. SpringMVC简介 SpringMVC是一种基于Java实现MVC模型的轻量级Web框架优点 使用简单&#xff0c;开发便捷&#xff08;相比于Servlet&#xff09;灵活性强 2. 入门案例 2.1 实现步骤分析 1 创建web工程&#xff08;Maven结构&#xff09; 2 设置tomcat服务器&#xff0c;加…

Qt属性系统(Qt Property System)

Qt提供了巧妙的属性系统&#xff0c;它与某些编译器支持的属性系统相似。然而&#xff0c;作为平台和编译器无关的库&#xff0c;Qt不能够依赖于那些非标准的编译器特性&#xff0c;比如__property 或者 [property]。Qt的解决方案能够被任何Qt支持的平台下的标准C编译器支持。它…

Kafka工作流程简介

消息传递模式简介: 一个消息系统负责将数据从一个应用程序传递到另外一个应用程序中&#xff0c;应用程序只关注数据&#xff0c;无需关注数据在多个应用之间是如何传递的。 分布式消息传递基于可靠的消息队列&#xff0c;在客户端应用和消息系统之间异步传递消息。 消息传递有…

MySQL面试常问问题(日志) —— 赶快收藏

目录 1.MySQL日志文件有哪些&#xff1f;分别介绍下作用&#xff1f; 2.binlog和redo log有什么区别&#xff1f; 3.一条更新语句怎么执行的了解吗&#xff1f; 4.那为什么要两阶段提交呢&#xff1f; 5.redo log怎么刷入磁盘的知道吗&#xff1f; 1.MySQL日志文件有哪些&…

Typora配合PicGo阿里云图床配置

写博客的时候&#xff0c;刚开始直接在各大平台上直接写&#xff0c;后来还是觉得不太方便&#xff0c;需要在各大平台之间来回切换。于是就改用Typora&#xff0c;但是有个问题就是图片的处理&#xff0c;只能放在本地。想要发布到各大平台&#xff0c;就需要图床。本文结合阿…

2022年安徽最新水利水电施工安全员模拟试题及答案

百分百题库提供水利水电施工安全员考试试题、水利水电施工安全员考试预测题、水利水电施工安全员考试真题、水利水电施工安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 15.围堰工程可以直接判定为生产安全重大事故隐…

【UE4 第一人称射击游戏】02-玩家健康和护甲

步骤&#xff1a; 1.在“ThirdPersonCharacter”中添加两个浮点变量“Health”和“Armor” 将默认值设为1.0&#xff0c;表示默认100% 2.新建一个控件蓝图&#xff0c;命名为“FPSHUD” 打开“FPSHUD”&#xff0c;添加两个进度条&#xff0c;分别表示当前的生命值和护甲量 调…

InnoDB架构体系

2、InnoDB架构体系 2.1、内存结构 2.1.1、buffer pool InnoDB内存缓存区&#xff0c;使用空间换时间的思想&#xff0c;给数据做了一个缓存。把热点的数据存储在内存中&#xff0c;减少IO次数&#xff0c;提高效率。 show variables like %innodb_buffer_pool%;buffer pool …

基于polar码和SCMA的多用户检测的联合检测译码matlab仿真,polar采用SCAN软译码,SCMA用MPA算法

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 构造的核心是通过信道极化&#xff08;channel polarization&#xff09;处理&#xff0c;在编码侧采用方法使各个子信道呈现出不同的可靠性&#xff0c;当码长持续增加时&#xff0c;部分信道将…

【C语言】初阶习题

目录 1.图案问题 2、时分秒转换 3、打印1-100之间所有3的倍数的数字 4、打印100~200之间的素数 5、给定两个数&#xff0c;求这两个数的最大公约数 6、在屏幕上输出9*9乘法口诀表 7、计算1/1-1/21/3-1/41/5 …… 1/99 - 1/100 的值&#xff0c;打印出结果 8、二分查找 …

说明DBCO-PEG-SH二苯并环辛炔-聚乙二醇-巯基科研试剂材料,DBCO-PEG-SH结构式

结构式 英文&#xff1a;DBCO-PEG-SH&#xff0c;DBCO-PEG-Thiol 中文&#xff1a;二苯并环辛炔-聚乙二醇-巯基 溶剂&#xff1a;溶于水、DMSO等常规有机溶剂 性状&#xff1a;液体/固体白色或淡黄色粉末&#xff0c;取决于分子量 用途范围&#xff1a;广泛应用于医药、生物…

一文搞懂Linux下并制作环形缓冲区

1.环形缓冲区log_buf[]又是存在内核的哪个文件呢&#xff1f; 位于/proc/kmsg里,所以除了dmesg命令查看,也可以使用cat /proc/kmsg来查看 2.但是,dmesg命令和cat /proc/kmsg有所不同 2.1 dmesg命令 每次使用,都会打印出环形缓冲区的所有信息 2.2 cat /proc/kmsg 只会打印…

项目管理中,进度计划是摆设吗?

1、忽视进度计划 项目管理中&#xff0c;有的人认为进度计划是摆设&#xff0c;不重视计划&#xff0c;只是为了满足合同工期&#xff0c;做给客户看&#xff0c;因此草率的编制进度计划。 在通过客户要求后&#xff0c;就将计划搁置在一边&#xff0c;这就导致后期在执行任务…