一、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://www.pswp.cn/bicheng/96490.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96490.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/96490.shtml

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

相关文章

【直接套模板】如何用 Web of Science 精准检索文献?

在文献检索的时候遇到一些问题&#xff0c;单独使用关键词检索出来的文章数量太多&#xff0c;如果是多加一些限定词&#xff0c;又什么都检索不到&#xff1a;比如我明明知道某篇论文已经发表&#xff0c;但在 Web of Science (WoS) 里却检索不到。这其实和检索式的写法密切相…

HTTP 协议:从原理到应用的深度剖析

一、什么是HTTP协议&#xff1f;HTTP协议&#xff0c;全称 Hyper Text Transfer Protocol&#xff08;超⽂本传输协议&#xff09;的缩写&#xff0c;是⽤于服务器与客户端浏览器之间传输超⽂本数据&#xff08;⽂字、图⽚、视频、⾳频&#xff09;的应⽤层协议。它规定了客户端…

【算法--链表】138.随机链表的复制--通俗讲解

算法通俗讲解推荐阅读 【算法–链表】83.删除排序链表中的重复元素–通俗讲解 【算法–链表】删除排序链表中的重复元素 II–通俗讲解 【算法–链表】86.分割链表–通俗讲解 【算法】92.翻转链表Ⅱ–通俗讲解 【算法–链表】109.有序链表转换二叉搜索树–通俗讲解 【算法–链表…

为什么现在企业注重数据可视化?一文讲清可视化数据图表怎么做

目录 一、企业注重数据可视化的原因 1.提升数据理解效率 2.发现数据中的规律和趋势 3.促进企业内部沟通与协作 4.增强决策的科学性 5.提升企业竞争力 二、可视化数据图表的基本概念 1.常见的可视化图表类型 2.可视化图表的构成要素 3.可视化图表的设计原则 三、制作…

Cursor 辅助开发:快速搭建 Flask + Vue 全栈 Demo 的实战记录

Cursor 辅助开发&#xff1a;快速搭建 Flask Vue 全栈 Demo 的实战记录 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个…

实战:用 Python 搭建 MCP 服务 —— 模型上下文协议(Model Context Protocol)应用指南

&#x1f4cc; 实战&#xff1a;用 Python 搭建 MCP 服务 —— 模型上下文协议&#xff08;Model Context Protocol&#xff09;应用指南 标签&#xff1a;#MCP #AI工程化 #Python #LLM上下文管理 #Agent架构&#x1f3af; 引言&#xff1a;为什么需要 MCP&#xff1f; 在构建大…

宋红康 JVM 笔记 Day16|垃圾回收相关概念

一、今日视频区间 P154-P168 二、一句话总结 System.gc()的理解&#xff1b;内存溢出与内存泄漏&#xff1b;Stop The World;垃圾回收的并行与并发&#xff1b;安全点与安全区域&#xff1b;再谈引用&#xff1a;强引用&#xff1b;再谈引用&#xff1a;软引用&#xff1b;再谈…

OpenCV 高阶 图像金字塔 用法解析及案例实现

目录 一、什么是图像金字塔&#xff1f; 二、图像金字塔的核心作用 三、图像金字塔的核心操作&#xff1a;上下采样 3.1 向下采样&#xff08; pyrDown &#xff09;&#xff1a;从高分辨率到低分辨率 1&#xff09;原理与步骤 2&#xff09;关键注意事项 3&#xff09;…

【ARMv7】系统复位上电后的程序执行过程

引子&#xff1a;对于ARMv7-M系列SOC来说&#xff0c;上电后程序复位执行的过程相对来说比较简单&#xff0c;因为绝大部分芯片&#xff0c;都是XIP&#xff08;eXecute In Place&#xff0c;就地执行&#xff09;模式执行程序&#xff0c;不需要通过BooROM->PL(preloader)-…

神经网络的初始化:权重与偏置的数学策略

在深度学习中&#xff0c;神经网络的初始化是一个看似不起眼&#xff0c;却极其重要的环节。它就像是一场漫长旅程的起点&#xff0c;起点的选择是否恰当&#xff0c;往往决定了整个旅程的顺利程度。今天&#xff0c;就让我们一起深入探讨神经网络初始化的数学策略&#xff0c;…

第 16 篇:服务网格的未来 - Ambient Mesh, eBPF 与 Gateway API

系列文章:《Istio 服务网格详解》 第 16 篇:服务网格的未来 - Ambient Mesh, eBPF 与 Gateway API 本篇焦点: 反思当前主流 Sidecar 模式的挑战与权衡。 深入了解 Istio 官方的未来演进方向:Ambient Mesh (无边车模式)。 探讨革命性技术 eBPF 将如何从根本上重塑服务网格的…

摆动序列:如何让数组“上下起伏”地最长?

文章目录摘要描述题解答案题解代码分析代码解析示例测试及结果时间复杂度空间复杂度总结摘要 今天我们要聊的是 LeetCode 第 376 题 —— 摆动序列。 题目的意思其实很有意思&#xff1a;如果一个序列里的相邻差值能保持正负交替&#xff0c;就叫做“摆动”。比如 [1, 7, 4, 9…

玩转Docker | 使用Docker部署KissLists任务管理工具

玩转Docker | 使用Docker部署KissLists任务管理工具 前言 一、KissLists介绍 KissLists简介 KissLists核心特点 KissLists注意事项 二、系统要求 环境要求 环境检查 Docker版本检查 检查操作系统版本 三、部署KissLists服务 下载KissLists镜像 编辑部署文件 创建容器 检查容器状…

【滑动窗口】C++高效解决子数组问题

个人主页 &#xff1a; zxctscl 专栏 【C】、 【C语言】、 【Linux】、 【数据结构】、 【算法】 如有转载请先通知 文章目录前言1 209. 长度最小的子数组1.1 分析1.2 代码2 3. 无重复字符的最长子串2.1 分析2.2 代码3 1004. 最大连续1的个数 III3.1 分析3.2 代码4 1658. 将 x …

[rStar] 搜索代理(MCTS/束搜索)

第2章&#xff1a;搜索代理(MCTS/束搜索) 欢迎回到rStar 在前一章中&#xff0c;我们学习了求解协调器&#xff0c;它就像是解决数学问题的项目经理。 它组织整个过程&#xff0c;但本身并不进行"思考"&#xff0c;而是将这项工作委托给其专家团队。 今天&#x…

Electron 核心模块速查表

为了更全面地覆盖常用 API&#xff0c;以下表格补充了更多实用方法和场景化示例&#xff0c;同时保持格式清晰易读。 一、主进程模块 模块名核心用途关键用法 示例注意事项app应用生命周期管理• 退出应用&#xff1a;app.quit()• 重启应用&#xff1a;app.relaunch() 后需…

Qt C++ 图形绘制完全指南:从基础到进阶实战

Qt C 图形绘制完全指南&#xff1a;从基础到进阶实战 前言 Qt框架提供了强大的2D图形绘制能力&#xff0c;通过QPainter类及其相关组件&#xff0c;开发者可以轻松实现各种复杂的图形绘制需求。本文将系统介绍Qt图形绘制的核心技术&#xff0c;并通过实例代码演示各种绘制技巧…

二分搜索边界问题

在使用二分搜索的时候&#xff0c;更新条件不总是相同&#xff0c;虽然说使用bS目的就是为了target&#xff0c;但也有如下几种情况&#xff1a;求第一个target的索引求第一个>target的索引求第一个>target的索引求最后一个target的索引求最后一个<target的索引求最后…

【springboot+vue3】博客论坛管理系统(源码+文档+调试+基础修改+答疑)

目录 一、整体目录&#xff1a; 项目包含源码、调试、修改教程、调试教程、讲解视频、开发文档&#xff08;项目摘要、前言、技术介绍、可行性分析、流程图、结构图、ER属性图、数据库表结构信息、功能介绍、测试致谢等约1万字&#xff09; 二、运行截图 三、代码部分&…

20250907_梳理异地备份每日自动巡检Python脚本逻辑流程+安装Python+PyCharm+配置自动运行

一、逻辑流程(autocheckbackup.py在做什么) 1.连接Linux服务器 用 paramiko 登录你配置的 Linux 服务器(10.1.3.15, 10.1.3.26),进入指定目录(如 /home, /backup/mes),递归列出文件。 采集到的信息:服务器IP、目录、数据库名称、文件名、大小、修改时间。 2.连接Wind…