当前位置: 首页 > news >正文

C++篇(7)string类的模拟实现

一、string的成员变量

string和数据结构中的顺序表类似,本质上可以理解成字符顺序表,其成员变量仍然是_str,_size和_capacity。但是,C++标准库里面也有一个string,和我们要自己实现的string类冲突了,该如何解决呢?这里就体现出了命名空间的作用。

namespace bit
{class string{private:char* _str;size_t _size;size_t _capacity;};
}

二、string的构造与析构

首先先来实现无参的构造函数。

string();

提到string的构造,我们第一想法可能是这样写:

string::string():_str(nullptr),_size(0),_capacity(0)
{}

但是这里要注意,这样写会有一个很大的问题。我们来看下面这个样例。

#include "string.h"namespace bit
{void test_string1(){string s1;cout << s1.c_str() << endl;}
}int mian()
{bit::test_string1();return 0;
}

运行代码之后我们发现,程序崩溃了!

程序的退出码非0,程序崩溃

那为什么程序会崩溃呢?因为这里的s1的_str是const char*的空指针,我们要打印出s1的字符,就会对该空指针解引用,程序就会崩溃。但是换成标准库里的string类,程序能正常运行。

说明我们的string类实现是有问题的。_str应该也有一份空间,该空间初始化成'\0'。

string::string():_str(new char[1]{'\0'}),_size(0),_capacity(0)
{}

现在再运行刚才的程序,就没有问题了。

这里我们先不急着重载流插入和流提取,因为比较复杂,后面再细讲,这里先用c_str来返回const char*的指针,同样可以打印出字符串。c_str()函数实现如下:

const char* string::c_str() const
{return _str;
}

接着我们来看带参数的构造函数该如何实现。

string(const char* str);

可能会有人直接把str的值传给_str。

string::string(const char* str):_str(str)
{}

注意,不能直接把str的值给_str,因为str是const char*类型,而_str是char*类型,二者类型不匹配。所以,这里应该要给_str开一块同样大小的空间,再把str里面的内容拷贝过来。

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

注意,strlen不包含字符串后面的'\0',但是strcpy会把'\0'一起拷贝过来。所以_str开的空间要加1,而size和capacity在C++委员会中规定,不包含'\0',所以不用加1。

但是,strlen是在运行时计算,这里用了三次strlen,那么该如何优化呢?能不能用strlen初始化_size,再用_size初始化_capacity和_str(如下面代码所示)

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

这个方法看似没有问题,但是有一个大坑!初始化列表是按声明的顺序初始化,所以要是按照下面代码优化的话,我们就需要把_size放在_str的上面声明。但是这样写就极大地增加了代码的耦合度,降低了可读性。所以,我们这里可以不用初始化列表来全部初始化,而是可以放在函数体内初始化。

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

析构函数的实现很简单,这里直接给出代码。

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

三、string的相关函数

3.1 尾插一个字符

尾插字符的操作很简单,就是在_size的位置插入字符,然后让_size++,这里有个细节要注意一下,字符串的末尾是\0,所以要在_size++后的位置再插入\0。接下来再考虑内存不够需要扩容的情况,这里我们先实现reserve函数用来申请空间。我们新申请一块空间,把原空间的内容拷贝到新空间中,释放旧空间,让_str指向新空间即可。

void string::reserve(size_t n)
{if (n > _capacity){char* str = new char[n + 1];strcpy(str, _str);delete[] _str;_str = str;_capacity = n;}
}void string::push_back(char ch)
{if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';
}

3.2 追加一个字符串

首先考虑扩容的问题,这里的扩容就不能像之前一样扩二倍,因为要考虑追加的字符串的长度。如果追加的字符串过长,那么扩二倍还不够。所以这里要用三目运算符判断一下。

void string::append(const char* str)
{size_t len = strlen(str);if (len + _size > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}strcpy(_str + _size, str);_size += len;
}

3.3 流插入运算符重载

对于流插入运算符重载,我们第一时间想到的就是把s.c_str()输出即可。(如下面代码所示)

ostream& operator<<(ostream& out, const string& s)
{out << s.c_str();return out;
}

看似没有任何问题,但是下面这个样例程序就会出现bug。

可以看到,当我在字符串的后面加上几个'\0'之后,再加上一个字符'!',输出结果中并没有出现'!'这个字符。但是标准库里的string重载的流插入运算符却仍然能正常打印。

那么到底错在哪了呢?我们输出的是c_str,底层是const char*的指针,在打印时遇到'\0'就自动结束了。所以,这里应该一个字符一个字符地打印。

ostream& operator<<(ostream& out, const string& s)
{//out << s.c_str();for (size_t i = 0;i < s.size();i++){out << s[i];}return out;
}

这样写看似没问题了,但是下面这个样例又出现了bug。

输出结果中出现了乱码。但是标准库中的却没有任何问题。

打开程序监视窗口之后我们发现,字符串中少了一个'\0'和一个'!',所以这一块内容出现的是随机值。

那到底是什么原因导致的呢?这里我们插入的字符串长度较大,所以需要扩容。而我们扩容用到的是strcpy,strcpy遇到'\0'就停止拷贝了,所以'\0'和'!'才没有出现在字符串中,然后我们再把新插入的字符串拷贝到str+_size的位置,所以中间的那两个位置就变成了随机值,而通常情况下,汉字占两个字节,所以就会出现一个“屯”字。所以我们就不能用strcpy来拷贝,而是要用memcpy。

void string::reserve(size_t n)
{if (n > _capacity){char* str = new char[n + 1];//strcpy(str, _str);memcpy(str, _str, _size + 1);delete[] _str;_str = str;_capacity = n;}
}

3.4 任意位置插入字符

思路:将pos位置之后的字符依次向后挪动,然后把字符插入到pos位置。

void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪动数据size_t end = _size;while (end >= pos){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;
}

看似没有任何问题,但是有一个特殊的例子就会出现bug。

可以看到,头插一个字符之后,程序运行崩溃了。因为这里的end是size_t类型的,也就是无符号整型,但是pos==0,也就是说end不可能小于pos,循环会一直进行,出现越界的情况。那是不是我们把end的类型改成int就可以了呢?

程序仍然崩溃了,这又是为什么呢?因为pos是size_t的类型,当end和pos进行比较时会发生整型提升。也就是int转换为unsigned_int继续比较,也就是说,虽然我们写成了int类型,但是没有任何用。一种解决办法是把pos强制转换成int类型,这里给出第二种办法,把end初始值赋值为_size+1.

void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪动数据/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];end--;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;
}

3.5 任意位置插入字符串

和上面思想类似,就不做过多赘述了。

void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//挪动数据/*int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}*/size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0;i < len;i++){_str[pos + i] = str[i];}_size += len;
}

3.6 查找字符串的子串

查找子串的方法有很多(比如KMP算法),这里就直接调用C++库里面的strstr函数来完成。strstr返回的是一个const char*的指针,如果没有找到就返回npos,找到了就用该位置的指针 - 字符串开始位置的指针就是该位置的下标。

size_t string::find(const char* str, size_t pos) const
{const char* p1 = strstr(_str + pos, str);if (p1 == nullptr){return npos;}else{return p1 - _str;}
}

类似地,我们也可以实现提取字符串中的子串的代码。这两个函数可以帮助我们解析网址。

string string::substr(size_t pos, size_t len) const
{if (len == npos || len >= _size - pos){len = _size - pos;}string ret;ret.reserve(len);for (size_t i = 0;i < len;i++){ret += _str[pos + i];}return ret;
}

注意,这里ret是一个临时变量,作为返回值时要有拷贝构造函数,否则就是编译器默认生成的浅拷贝,会导致程序运行出错。(关于拷贝构造的实现,见后文)

void split_url(const string& url)
{size_t i1 = url.find(':');if (i1 != string::npos){cout << url.substr(0, i1) << endl;}size_t i2 = i1 + 3;size_t i3 = url.find('/', i2);if (i3 != string::npos){cout << url.substr(i2, i3 - i2) << endl;cout << url.substr(i3 + 1) << endl;}cout << endl;
}void test_string4()
{string url1 = "https://legacy.cplusplus.com/reference/string/string/";string url2 = "https://legacy.cplusplus.com/reference/vector/vector/";split_url(url1);split_url(url2);
}

3.7 比较运算符重载

从两个字符串下标为0的位置开始,一个字符一个字符地比较(比较它们的ASCII码值),如果相等就继续向后遍历。重载了<和==运算符之后,其他的比较运算符重载实现就可以复用了。

// s1 < s2
// "hello"   "hello"   ->  flase
// "hellox"  "hello"   ->  false
// "hello"   "hellox"  ->  true
bool string::operator<(const string& s) const
{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] < s[i2]){return true;}else if (_str[i1] > s[i2]){return false;}else{++i1;++i2;}}return i2 < s._size;
}bool string::operator==(const string& s) const
{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] != s[i2]){return false;}else{++i1;++i2;}}return i1 == _size && i2 == s._size;
}bool string::operator<=(const string& s) const
{return *this < s || *this == s;
}bool string::operator>(const string& s) const
{return !(*this <= s);
}
bool string::operator>=(const string& s) const
{return !(*this < s);
}bool string::operator!=(const string& s) const
{return !(*this == s);
}

3.8 流提取运算符重载

输入字符串的本质其实就是一个字符一个字符的读取,当输入到空格或者换行时,读取结束,把读取到的字符加到数组中去。按照这个思路,可以写出如下代码:

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

这段代码看似没有问题,我们给上一个测试用例运行一下:

我们输入了xxxxx和yyyyy,按理来说应该输出xxxxx和yyyyy,但是控制台并没有给出输出结果,说明我们重载的流提取运算符有问题。那么问题出在哪里呢?编译器认为这里的空格和换行符相当于是字符和字符之间的分隔符,会自动忽略,所以出现了bug。因此这里不应该用in来输入,在C语言中可以用getchar来读取,在C++中的输入流则是提供了成员函数get。修改之后的代码如下:

istream& operator>>(istream& in, string& s)
{char ch = in.get();// in >> ch;while (ch != ' ' && ch != '\n'){s += ch;// in >> ch;ch = in.get();}return in;
}

那是不是这样就行呢?我们接着用刚才那段测试用例测试一下:

还是有问题!输出的字符串的前面多了“hello world”,这又是什么原因呢?因为我们这里的s1和s2对象原本就有值,重载的函数里面是直接在字符串的后面添加字符,所以出现了问题。因此,如果之前的string对象原本就有值的话,需要先清理一下。这里就需要再实现一个函数clear,把string对象的下标为0的位置重置成‘\0’,然后把_size重置成0就可以了。

void string::clear()
{_str[0] = '\0';_size = 0;
}
istream& operator>>(istream& in, string& s)
{s.clear();char ch = in.get();// in >> ch;while (ch != ' ' && ch != '\n'){s += ch;// in >> ch;ch = in.get();}return in;
}

这样就没有任何问题了。

3.9 读取一行字符串

如果我们要一次读取一行字符串,并且字符串中有空格的话,这时候用cin就行不通了。C++的标准库string中提供了getline函数,这里我们也模拟实现一下。

原理思路和流提取运算符重载类似,就不再赘述了。

istream& getline(istream& in, string& str, char delim)
{str.clear();char ch = in.get();while (ch != delim){str += ch;ch = in.get();}return in;
}

可以看到,程序是正常运行的:

但是这样写还是有一些小的缺陷,如果我们输入的字符串很长的话,那么底层就会不断地扩容,影响效率并且还会造成一定空间的浪费。那么有没有什么优化的方案呢?有的兄弟,有的。

我们开一个数组buff,大小为128(也可以是其他大小,根据实际情况而定),每次读取到一个字符之后不要直接放在字符串str后面,而是先存储到buff数组中。当buff数组存储满了之后,直接把buff数组里的值一次性全部加到str字符串里面。

istream& operator>>(istream& in, string& s)
{s.clear();char buff[128];int i = 0;char ch = in.get();// in >> ch;while (ch != ' ' && ch != '\n'){//s += ch;buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}// in >> ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;
}

3.10 拷贝构造(1)

拷贝构造的原理很简单,开一样大的空间,然后拷贝新数据,释放旧空间并指向新空间。

传统写法如下:

// s2(s1)
string::string(const string& s)
{_str = new char[s._capacity + 1];memcpy(_str, s.c_str(), s._size + 1);_size = s._size;_capacity = s._capacity;
}

下面给出另一种写法(现代写法)

3.11 拷贝构造(2)

按照前面的想法我们是自己开了一块空间,但是这里我们不再自己申请空间,而是让构造函数帮我们申请空间tmp。但是tmp里面的数据内容是我_str想要的,所以调用swap函数交换。

注意:这里不能调用C++算法库里的swap进行交换,因为算法库里的swap是创建了一个中间对象进行交换(如下图所示),又需要调用拷贝构造和赋值运算符重载,就会陷入死循环!

算法库里的swap函数

所以这里需要将内置类型进行交换(也就是把_str,_size 和 _capacity逐个交换)。初步代码如下:

string::string(const string& s)
{string tmp(s._str);swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);
}

但是这里的交换代码要经常调用,所以我们可以把它们提取出来,单独实现一个成员函数swap用于交换。(可以看到,C++中的string也有一份属于自己的swap成员函数,如下图所示)

string自己的swap函数

但是这里我们自己实现swap函数时又会遇到问题,程序编译时会有现在局部找swap函数,就会出现参数不匹配的情况,所以要在前面加上指定作用域std。完整代码如下:

void string::swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}// s3(s1)
string::string(const string& s)
{string tmp(s._str);//this->swap(tmp);swap(tmp);
}

3.12 赋值运算符重载

和拷贝构造函数的实现类似,赋值运算符重载同样要开一样大的空间,然后拷贝新数据,释放旧空间并指向新空间。传统写法代码如下:

// s1 = s2
string& string::operator=(const string& s)
{if (this != &s){char* tmp = new char[s._capacity + 1];memcpy(tmp, s.c_str(), s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}
原理图

同理,赋值运算符重载也可以用现代写法:

string& string::operator=(const string& s)
{if (this != &s){string tmp(s);swap(tmp);}return *this;
}

甚至还可以写得更简洁一些:

// s1 = s2
string& string::operator=(string tmp)
{swap(tmp);return *this;
}

这里是传值传参,需要调用拷贝构造,也就是说这里的参数tmp就是s2的临时拷贝,所以直接交换就可以了。

现代写法与传统写法在效率上并没有太大区别,因为都要进行深拷贝,但是现代写法的代码更简洁,所以更推荐现代写法。

3.13 swap函数

或许有人会疑惑,swap不是刚刚才实现过了吗,为什么又要实现一遍?在官方的库中,你会发现string有两个swap函数,一个是成员函数,另一个是全局函数。

C++官方库里的两个swap函数

那为什么这样设计呢?算法库中也有一个swap(之前在3.11展示过),但是算法库里的swap在底层是三次深拷贝,程序代价很大!但是string里的swap函数底层效率就高很多。

string的全局函数swap

算法库中的swap参数是模版,而string的全局函数swap参数就是string类,当交换的是两个string类型的对象时,编译器会优先调用现成的函数(也就是参数是string类类型的swap)。所以C++库的设计者就会在全局也设计一个swap函数,与算法库中的swap构成重载。所以编译器在调用swap时就会优先调用“成品”,而不会调用模版。

void swap(string& x, string& y)
{x.swap(y);
}

四、完整参考代码

string.h

#pragma once
#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;namespace bit
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//string();string(const char* str = "");const char* c_str() const;~string();string(const string& s);//string& operator=(const string& s);string& operator=(string tmp);void swap(string& s);size_t size() const;char& operator[](size_t i);const char& operator[](size_t i) const;void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void pop_back();string& insert(size_t pos, char ch);string& insert(size_t pos, const char* str);string& erase(size_t pos, size_t len = npos);size_t find(char ch, size_t pos = 0) const;size_t find(const char* str, size_t pos = 0) const;string substr(size_t pos, size_t len = npos) const;void clear();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 = nullptr;size_t _size = 0;size_t _capacity = 0;public:static const size_t npos;};ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);istream& getline(istream& in, string& str, char delim = '\n');void swap(string& x, string& y);
}

string.cpp

#define  _CRT_SECURE_NO_WARNINGS 1
#include "string.h"namespace bit
{const size_t string::npos = -1;string::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::begin() const{return _str;}string::const_iterator string::end() const{return _str + _size;}/*string::string():_str(new char[1]{'\0'}),_size(0),_capacity(0){}*/string::string(const char* str):_size(strlen(str)){_capacity = _size;_str = new char[_size + 1];//strcpy(_str, str);memcpy(_str, str, _size + 1);}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}// s2(s1)/*string::string(const string& s){_str = new char[s._capacity + 1];memcpy(_str, s.c_str(), s._size + 1);_size = s._size;_capacity = s._capacity;}*/void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// s3(s1)string::string(const string& s){string tmp(s._str);//this->swap(tmp);swap(tmp);}// s1 = s2/*string& string::operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];memcpy(tmp, s.c_str(), s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}*//*string& string::operator=(const string& s){if (this != &s){string tmp(s);swap(tmp);}return *this;}*/// s1 = s2string& string::operator=(string tmp){swap(tmp);return *this;}const char* string::c_str() const{return _str;}size_t string::size() const{return _size;}char& string::operator[](size_t i){assert(i < _size);return _str[i];}const char& string::operator[](size_t i) const{assert(i < _size);return _str[i];}void string::reserve(size_t n){if (n > _capacity){char* str = new char[n + 1];//strcpy(str, _str);memcpy(str, _str, _size + 1);delete[] _str;_str = str;_capacity = n;}}void string::push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//strcpy(_str + _size, str);memcpy(_str + _size, str, len + 1);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::pop_back(){assert(_size > 0);--_size;_str[_size] = '\0';}string& string::insert(size_t pos, char ch){assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪动数据/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];end--;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;return *this;}string& string::insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//挪动数据/*int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}*/size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0;i < len;i++){_str[pos + i] = str[i];}_size += len;return *this;}string& string::erase(size_t pos, size_t len){assert(pos < _size);//要删除的数据大于pos后面的字符个数//pos后面全删if (len == npos || len >= (_size - pos)){_size = pos;_str[_size] = '\0';}else{size_t i = pos + len;memmove(_str + pos, _str + i, _size + 1 - i);_size -= len;}return *this;}size_t string::find(char ch, size_t pos) const{for (size_t i = pos;i < _size;i++){if (ch == _str[i]){return i;}}return npos;}size_t string::find(const char* str, size_t pos) const{const char* p1 = strstr(_str + pos, str);if (p1 == nullptr){return npos;}else{return p1 - _str;}}string string::substr(size_t pos, size_t len) const{if (len == npos || len >= _size - pos){len = _size - pos;}string ret;ret.reserve(len);for (size_t i = 0;i < len;i++){ret += _str[pos + i];}return ret;}// s1 < s2// "hello"   "hello"   ->  flase// "hellox"  "hello"   ->  false// "hello"   "hellox"  ->  truebool string::operator<(const string& s) const{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] < s[i2]){return true;}else if (_str[i1] > s[i2]){return false;}else{++i1;++i2;}}return i2 < s._size;}bool string::operator==(const string& s) const{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] != s[i2]){return false;}else{++i1;++i2;}}return i1 == _size && i2 == s._size;}bool string::operator<=(const string& s) const{return *this < s || *this == s;}bool string::operator>(const string& s) const{return !(*this <= s);}bool string::operator>=(const string& s) const{return !(*this < s);}bool string::operator!=(const string& s) const{return !(*this == s);}void string::clear(){_str[0] = '\0';_size = 0;}ostream& operator<<(ostream& out, const string& s){//out << s.c_str();for (size_t i = 0;i < s.size();i++){out << s[i];}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[128];int i = 0;char ch = in.get();// in >> ch;while (ch != ' ' && ch != '\n'){//s += ch;buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}// in >> ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}istream& getline(istream& in, string& str, char delim){str.clear();char ch = in.get();while (ch != delim){str += ch;ch = in.get();}return in;}void swap(string& x, string& y){x.swap(y);}
}

test.cpp

#define  _CRT_SECURE_NO_WARNINGS 1
#include "string.h"namespace bit
{void test_string1(){string s1;cout << s1.c_str() << endl;string s2("hello world");cout << s2.c_str() << endl;for (size_t i = 0;i < s2.size();i++){s2[i]++;}cout << s2.c_str() << endl;for (auto ch : s2){cout << ch << " ";}cout << endl;const string s3("hello world");string::const_iterator it3 = s3.begin();while (it3 != s3.end()){cout << *it3 << " ";++it3;}cout << endl;}void test_string2(){string s1("hello world");s1.push_back('x');cout << s1.c_str() << endl;s1.append(" hello bit");cout << s1.c_str() << endl;s1 += 'y';s1 += "zzzzzzz";cout << s1.c_str() << endl << endl;std::string s2("hello world");s2 += '\0';s2 += '\0';s2 += '!';cout << s2 << endl;cout << s2.c_str() << endl;s2 += "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";cout << s2 << endl;cout << s2.c_str() << endl;}void test_string3(){string s1("hello world");s1.insert(6, 'x');cout << s1 << endl;s1.insert(0, 'x');cout << s1 << endl << endl;string s2("hello world");s2.insert(6, "xxx");cout << s2 << endl;string s3("hello world");s3.erase(7);cout << s3 << endl;string s4("hello world");s4.erase(7, 100);cout << s4 << endl;string s5("hello world");s5.erase(7, 3);cout << s5 << endl;s5.pop_back();cout << s5 << endl;}void split_url(const string& url){size_t i1 = url.find(':');if (i1 != string::npos){cout << url.substr(0, i1) << endl;}size_t i2 = i1 + 3;size_t i3 = url.find('/', i2);if (i3 != string::npos){cout << url.substr(i2, i3 - i2) << endl;cout << url.substr(i3 + 1) << endl;}cout << endl;}void test_string4(){string url1 = "https://legacy.cplusplus.com/reference/string/string/";string url2 = "https://legacy.cplusplus.com/reference/vector/vector/";split_url(url1);split_url(url2);}void test_string5(){/*string s1("hello world"), s2("hello world");cout << s1 << " " << s2 << endl;cin >> s1 >> s2;cout << s1 << " " << s2 << endl;*/string s1;getline(cin, s1);cout << s1 << endl;}void test_string6(){string s1("hello"), s2("hellozyc");string s3(s1);cout << s1 << endl;cout << s3 << endl;s1[0] = 'x';cout << s1 << endl;cout << s3 << endl;s1 = s2;cout << s1 << endl;cout << s2 << endl;}void test_string7(){string s1("hello"), s2("hellozyc");swap(s1, s2);s1.swap(s2);}
}int main()
{bit::test_string7();return 0;
}


文章转载自:

http://0LUX2J1D.kpmxn.cn
http://lWGGlM22.kpmxn.cn
http://xMlSG4YJ.kpmxn.cn
http://xWtf8Oao.kpmxn.cn
http://jC5lODbr.kpmxn.cn
http://ICWIMjDi.kpmxn.cn
http://5vkRGXQu.kpmxn.cn
http://4CnjVZ2A.kpmxn.cn
http://ZVNkIPhu.kpmxn.cn
http://patL87qf.kpmxn.cn
http://iMlfFrbp.kpmxn.cn
http://pVmnDSWc.kpmxn.cn
http://4GmzZLY7.kpmxn.cn
http://kTEMWDEQ.kpmxn.cn
http://q9yEFJie.kpmxn.cn
http://Wg7nt9b1.kpmxn.cn
http://W9ZKIPFb.kpmxn.cn
http://0HCc4E7q.kpmxn.cn
http://qill0IkG.kpmxn.cn
http://qaaM7eLS.kpmxn.cn
http://aRB0eDsr.kpmxn.cn
http://dIDt16AL.kpmxn.cn
http://PZtcsmam.kpmxn.cn
http://xyrDShld.kpmxn.cn
http://B6IwDdF1.kpmxn.cn
http://MTUrr0xs.kpmxn.cn
http://hgAgpTLT.kpmxn.cn
http://G59LRebL.kpmxn.cn
http://S5lv8L32.kpmxn.cn
http://E2vMP5zD.kpmxn.cn
http://www.dtcms.com/a/377504.html

相关文章:

  • 弱加密危害与修复方案详解
  • 【Linux】Linux常用指令合集
  • Android- Surface, SurfaceView, TextureView, SurfaceTexture 原理图解
  • 如何设计Agent 架构
  • MySQL主从不一致?DBA急救手册:14种高频坑点+3分钟定位+无损修复!
  • 拍我AI:PixVerse国内版,爱诗科技推出的AI视频生成平台
  • 3D柱状图--自定义柱子颜色与legend一致(Vue3)
  • LeetCode热题100--199. 二叉树的右视图--中等
  • Next系统学习(三)
  • Python深度学习:NumPy数组库
  • Django时区感知
  • PostgreSQL15——Java访问PostgreSQL
  • Shell 函数详解
  • 【系统分析师】第21章-论文:系统分析师论文写作要点(核心总结)
  • Linux 命令(top/ps/netstat/vmstat/grep/sed/awk)及服务管理(systemd)
  • 【图像生成】提示词技巧
  • 揭秘Linux:开源多任务操作系统的强大基因
  • (ICLR-2025)深度压缩自动编码器用于高效高分辨率扩散模型
  • 《Why Language Models Hallucinate》论文解读
  • 【机器学习】通过tensorflow实现猫狗识别的深度学习进阶之路
  • AD5362BSTZ电子元器件 ADI 高精度数字模拟转换器DAC 集成电路IC
  • DMA-M2M存储器与存储器之间读写
  • Mistral Document AI已正式登陆Azure AI Foundry(国际版)
  • 机器学习实战(二):Pandas 特征工程与模型协同进阶
  • Flutter 朦胧效果布局大全:5种方法实现优雅视觉层次
  • 【CVPR2023】奔跑而非行走:追求更高FLOPS以实现更快神经网络
  • PHP学习(第三天)
  • 数仓简要笔记-1
  • 机器人商业化落地需要突破的关键性技术
  • AI 技术体系核心概念