在 C++ 编程的世界里,string
类是处理字符串的得力助手。它为开发者提供了便捷、高效的字符串操作方式,相较于 C 语言中以'\0'
结尾的字符数组和str
系列库函数,string
类更符合面向对象编程(OOP)的思想,让字符串处理变得轻松许多。
一、为什么选择 string 类
1.1 C 语言字符串的不足
在 C 语言的世界里,字符串以特殊的'\0'字符作为结尾标识,这种设计虽然简洁,但也带来了诸多隐患。开发者在操作字符串时,必须手动管理底层内存空间,稍有疏忽就可能引发越界访问问题,导致程序崩溃或出现难以调试的错误。例如,使用strcpy函数进行字符串拷贝时,若目标字符数组的空间不足以容纳源字符串,就会发生内存溢出,覆盖相邻内存区域的数据。而且,C 标准库中的str系列函数(如strlen、strcat等)与字符串数据本身相互分离,这不仅不利于代码的模块化组织,也不符合面向对象编程的封装思想,使得代码的维护和扩展变得困难重重。
1.2 实际应用需求
在实际的软件开发场景中,无论是在线评测(OJ)平台上的算法题目,还是企业级项目的日常开发工作,string类的使用频率都极高。它的优势在于简单易用、操作便捷,能够显著提升开发效率。例如,在处理用户输入的文本信息、解析文件中的字符串数据,或者进行字符串的拼接、查找等操作时,string类都能轻松应对,极大地减少了开发者编写复杂底层代码的工作量。
二、标准库中的 string 类
2.1 基本使用
在 C++ 程序中使用string类,首先需要包含<string>头文件,这是引入string类定义的关键步骤。同时,为了避免每次使用string类时都显式指定命名空间,通常会引入using namespace std;语句。不过,在大型项目中,为防止命名冲突,也可以采用std::string的形式来明确使用标准库中的string类。
string类提供了丰富多样的构造函数,以满足不同的创建需求。例如,通过string s1;可以创建一个空字符串,此时该字符串不包含任何有效字符;使用string s2("hello bit");能够基于 C 风格字符串构造一个string对象,将"hello bit"的内容复制到新创建的string对象中;而string s3(s2);则是通过拷贝构造函数,基于已有的string对象s2创建一个与之内容完全相同的新对象s3。
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1;
string s2("Hello, World!");
string s3(s2);
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
cout << "s3: " << s3 << endl;
return 0;
}
2.2 辅助语法:auto 和范围 for
- auto 关键字:在 C++11 标准推出后,auto关键字被赋予了全新的功能和含义。它能够让编译器在编译阶段自动推导变量的类型,极大地简化了变量声明的过程。在声明指针类型变量时,auto和auto*的效果是等同的,都能让编译器正确推导指针类型;而在声明引用类型变量时,必须显式加上&符号,以明确表示这是一个引用变量。
cpp
int num = 10;
auto a = num; // a为int类型
auto* p = # // p为int*类型
auto& r = num; // r为int&类型
然而,auto关键字也存在一些使用限制。它不能作为函数的参数类型,因为函数参数需要明确的类型定义,以便在函数调用时进行参数传递和类型检查;虽然auto可以作为函数的返回值类型,但在实际编程中并不建议轻易使用,因为这可能会导致代码的可读性和可维护性下降;此外,auto也无法直接用于声明数组,因为数组的大小和元素类型需要在声明时明确指定。
在遍历map容器时,auto关键字的优势得以充分体现。通过auto it = dict.begin();这样简洁的语句,就能声明一个合适类型的迭代器,方便地遍历map中的键值对。
cpp
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, int> dict = {{"apple", 1}, {"banana", 2}};
for (auto it = dict.begin(); it != dict.end(); ++it)//等价于for(auto e:dict)
{
cout << it->first << ": " << it->second << endl;
}
return 0;
}
- 范围 for:C++11 引入的范围 for 循环,为数组和容器的遍历操作带来了革命性的改变。它无需开发者手动编写复杂的迭代器初始化、条件判断和迭代器递增代码,而是自动完成迭代、数据提取和结束条件判断等操作,使代码更加简洁明了,同时也降低了因手动编写迭代逻辑而导致的错误风险。
cpp
int main(){
int array[] = {1, 2, 3, 4, 5};
for (auto& e : array) {
e *= 2;
}
for (int num : array) {
cout << num << " ";
}
return 0;
}
上述代码中,第一个范围 for 循环通过引用的方式遍历数组array,对每个元素进行乘以 2 的操作;第二个范围 for 循环则以值传递的方式遍历数组,并输出每个元素的值。
2.3 常用接口说明

- size()和length()这两个成员函数的功能都是返回字符串中有效字符的数量,二者在本质上没有区别,size()的设计是为了与其他标准容器的接口保持一致性,方便开发者统一使用。
cpp
size_t size()const {
return _size;
}
int mian(){
string s;
s.size();//返回类型为size_t
return 0;
}
- empty()函数用于判断字符串是否为空,即有效字符个数是否为 0,它返回一个布尔值,若为空则返回true,否则返回false。
cpp
bool empty()const {
if (_size==0)
{
return true;
}
return false;
}
int main(){
string s;
s.empty();//直接检查size
return0;
}
- clear()函数可以清空字符串中的有效字符,但并不会释放底层的内存空间,只是将字符串的长度置为 0,后续向字符串中添加字符时,若空间足够,无需重新分配内存。
cpp
void clear() {
_size = 0;
_str[0] = '\0';
}
int main(){
string s;
s.clear();
return 0;
}
- reserve()函数用于预留指定大小的内存空间,它能够提前为字符串可能的增长做好准备,避免在频繁添加字符时因内存重新分配而带来的性能开销。
cpp
void reserve(size_t n) {
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (_str != nullptr)
{
memcpy(tmp, _str, _size + 1);
delete[] _str;
}
_str = tmp;
_capacity = n;
}
}
- resize()函数则用于改变字符串的有效字符个数,当指定的新长度大于当前长度时,会在字符串末尾按需填充指定字符(默认为'\0'),若新长度小于当前长度,则会截断字符串。
cpp
void resize(size_t n, char c='\0') {
if (n != _size)
{
if (n < _size)
{
_str[n + 1] = '\0';
_size = n;
}
else
{
for (size_t i = _size; i < n; i++)
{
push_back(c);
}
}
}
}
cpp
string str = "hello";
cout << "Size: " << str.size() << endl;
cout << "Length: " << str.length() << endl;
cout << "Is empty? " << (str.empty()? "Yes" : "No") << endl;
str.reserve(20);
str.resize(10, 'a');
cout << "Resized str: " << str << endl;
- 访问及遍历操作:operator[]操作符为访问字符串中指定位置的字符提供了便捷方式,它与访问数组元素的方式类似,通过索引即可获取对应位置的字符。begin()和end()函数分别返回指向字符串首字符和尾后字符的迭代器,利用这两个迭代器,可以使用传统的for循环对字符串进行遍历;rbegin()和rend()函数则返回反向迭代器,用于从字符串末尾开始向前遍历;而 C++11 的范围 for 循环,更是以一种简洁优雅的方式实现了字符串的遍历。
cpp
string str = "world";
for (size_t i = 0; i < str.size(); ++i) {//下标[]访问
cout << str[i];
}
cout << endl;
for (string::iterator it = str.begin(); it != str.end(); ++it) {//迭代器
cout << *it;
}
cout << endl;
for (auto c : str) {//范围for
cout << c;
}
cout << endl;
- 修改操作:push_back、append和operator+=都可以实现向字符串末尾追加内容的功能。push_back函数用于添加单个字符,append函数可以追加字符串常量、string对象等多种形式的内容,而operator+=操作符使用起来最为灵活和广泛,它可以直接与字符串常量、string对象进行拼接。c_str()函数返回一个以'\0'结尾的 C 风格字符串,常用于将string对象传递给需要 C 风格字符串参数的函数。
cpp
void string::push_back(char c) {
if (_capacity == _size)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size++] = c;
_str[_size] = '\0';
}
string& string::operator+=(char c) {
push_back(c);
return *this;
}
void append(const char* str) {
size_t len = strlen(str);
if (_capacity < _size + len)
{
size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newcapacity);
}
memcpy(_str+_size, str, len + 1);
_size += len;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
- find和rfind函数用于在字符串中查找指定字符或子串的位置,find是从字符串开头开始查找,rfind则是从字符串末尾开始查找,若查找成功,返回找到的位置索引,否则返回string::npos(一个表示不存在的特殊值);substr函数用于截取字符串中的子串,它可以指定起始位置和截取长度,返回截取得到的新字符串。
cpp
size_t string::find(const char* s, size_t pos ) const {
assert(pos < _size);
const char* p = strstr(_str,s);
if (p == nullptr)
{
return npos;
}
return p - _str;
}
cpp
string str = "hello world";
str.push_back('!');
str.append(" How are you?");
str += " I'm fine.";
cout << "Modified str: " << str << endl;
size_t pos = str.find("world");
if (pos != string::npos) {
cout << "Found 'world' at position: " << pos << endl;
}
string sub = str.substr(6, 5);
cout << "Substring: " << sub << endl;
- 非成员函数:operator>>和operator<<是针对string类进行的输入和输出运算符重载,通过它们可以方便地使用cin进行字符串输入,使用cout进行字符串输出;getline函数能够读取一整行字符串,包括其中的空格字符,它以换行符作为输入结束的标志,常用于获取用户输入的多行文本;关系运算符(如==、!=、<、>等)用于比较两个字符串的大小,比较规则是按照字典序逐个字符进行比较。
cpp
std::ostream& operator<<(std::ostream& _cout, const string& s) {
for (size_t i = 0; i < s._size; i++)
{
_cout << s[i];
}
return _cout;
}
std::istream& operator>>(std::istream& _cin, string& s) {
char buf[256];
char ch;
size_t i = 0;
ch=_cin.get();
while (ch != ' ' && ch != '\n')
{
buf[i++] = ch;
ch= _cin.get();
if (i == 255)
{
buf[i] = '\0';
s.append(buf);
i = 0;
}
}
if (i != 0)
{
buf[i] = '\0';
s.append(buf);
}
return _cin;
}
cpp
string input;
cout << "Please enter a sentence: ";
getline(cin, input);
cout << "You entered: " << input << endl;
string str1 = "apple";
string str2 = "banana";
if (str1 < str2) {
cout << str1 << " is less than " << str2 << endl;
}
2.4 vs 和 g++ 下 string 结构差异
- vs 下:在 Visual Studio(vs)环境下,32 位平台中string对象占用 28 字节的内存空间。其内部采用联合体(union)的设计方式,当字符串长度小于 16 时,会使用内部固定的字符数组来存储字符串内容,这种方式避免了频繁的堆内存分配,提高了存储效率;当字符串长度大于等于 16 时,则会从堆上动态开辟空间来存储字符串。此外,string对象中还包含用于记录字符串长度、堆上空间容量的字段,以及一个指向堆空间的指针,以便在需要时访问堆上的数据。
- g++ 下:在 GNU C++ 编译器(g++)中,string对象仅占 4 字节,它通过写时拷贝(Copy-On-Write,简称 COW)技术来实现高效的内存管理。内部只有一个指向堆空间的指针,堆空间中除了存储字符串内容外,还包含空间总大小、字符串有效长度和引用计数等重要字段。引用计数用于记录当前有多少个string对象共享同一块堆内存,在对象创建和销毁时,通过增减引用计数来管理内存,只有当引用计数为 1 时,才真正释放堆内存,从而避免了不必要的内存复制操作,提高了程序的性能。
三、string 类的应用实践
通过解决一系列实际问题,能够更加深入地掌握string类的使用技巧和特性。下面以几个经典问题为例进行详细分析:
- 仅仅反转字母:给定一个字符串,要求将其中的字母部分进行反转,而其他非字母字符保持原位不变。可以采用双指针法来解决该问题,定义两个指针,一个指向字符串的开头,一个指向字符串的末尾,然后向中间移动,当两个指针都指向字母时,交换它们所指向的字符,直到两个指针相遇,即可完成字母的反转。
cpp
#include <iostream>
#include <string>
using namespace std;
string reverseOnlyLetters(string s) {
int left = 0, right = s.size() - 1;
while (left < right) {
while (left < right &&!isalpha(s[left]))
++left;
while (left < right &&!isalpha(s[right]))
--right;
swap(s[left], s[right]);
++left;
--right;
}
return s;
}
int main() {
string str = "ab-cd";
cout << reverseOnlyLetters(str) << endl;
return 0;
}
- 找字符串中第一个只出现一次的字符:对于给定的字符串,需要找出其中第一个只出现一次的字符。可以利用一个数组来统计每个字符出现的次数,遍历字符串,将每个字符对应的数组元素计数加 1。然后再次遍历字符串,找到第一个计数为 1 的字符,即为所求。
cpp
#include <iostream>
#include <string>
using namespace std;
char firstUniqChar(string s) {
int count[256] = {0};
for (char c : s) {
++count[c];
}
for (char c : s) {
if (count[c] == 1) {
return c;
}
}
return'';
}
int main() {
string str = "leetcode";
cout << firstUniqChar(str) << endl;
return 0;
}
- 字符串里面最后一个单词的长度:给定一个字符串,其中包含多个单词,单词之间由空格分隔,要求计算最后一个单词的长度。可以使用rfind函数找到字符串中最后一个空格的位置,然后计算从该位置到字符串末尾的字符个数,即为最后一个单词的长度。若字符串中不存在空格,即只有一个单词,则整个字符串的长度就是最后一个单词的长度。
cpp
#include <iostream>
#include <string>
using namespace std;
int lengthOfLastWord(string s) {
size_t pos = s.rfind(' ');
if (pos == string::npos) {
return s.size();
}
return s.size() - pos - 1;
}
int main() {
string str = "Hello World";
cout << lengthOfLastWord(str) << endl;
return 0;
}
- 验证一个字符串是否是回文:判断一个字符串是否为回文,即正序和倒序读取该字符串时内容完全相同。首先将字符串中的字母统一转换为大写或小写,消除大小写差异对判断的影响。然后使用双指针法,一个指针从字符串开头,一个指针从字符串末尾,向中间移动并比较对应位置的字符,若在移动过程中发现字符不相等,则该字符串不是回文,否则是回文。
cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
bool isPalindrome(string s) {
int left = 0, right = s.size() - 1;
while (left < right) {
while (left < right &&!isalnum(s[left]))
++left;
while (left < right &&!isalnum(s[right]))
--right;
if (tolower(s[left])!= tolower(s[right])) {
return false;
}
++left;
--right;
}
return true;
}
int main() {
string str = "A man, a plan, a canal: Panama";
cout << (isPalindrome(str)? "Yes" : "No") << endl;
return 0;
}
- 字符串相加:给定两个字符串形式的非负整数,要求将它们相加并以字符串形式返回结果。由于字符串中的数字是按位存储的,所以可以从后往前逐位相加,同时处理进位问题。在相加过程中,若某一位的和大于等于 10,则将进位记录下来,加到下一位的计算中。最后将计算得到的结果反转,得到正确的相加结果。
cpp
string addStrings(string num1, string num2) {
string sub="";
sub.reserve(max(num1.size(),num2.size())+1);
string::reverse_iterator it1=num1.rbegin();
string::reverse_iterator it2=num2.rbegin();
int next=0;
while(it1!=num1.rend()||it2!=num2.rend())
{
char a='0';
char b='0';
if(it1!=num1.rend())
a=*it1;
if(it2!=num2.rend())
b=*it2;
int tmp=a-'0'+b-'0'+next;
sub+=tmp%10+'0';
next=tmp/10;
if(it1!=num1.rend())
it1++;
if(it2!=num2.rend())
it2++;
}
if(next==1)
{
sub+='1';
}
reverse(sub.begin(), sub.end());
return sub;
}
四、string 类的模拟实现
4.1 经典问题:浅拷贝与深拷贝
在 C++ 中,当一个类涉及资源管理(如string
类管理字符数组资源)时,如果没有显式定义拷贝构造函数和赋值运算符重载,编译器会合成默认版本。这些默认版本采用浅拷贝方式,即简单地将对象中的值逐位拷贝过来。这就导致多个对象会共享同一份资源,比如两个string
对象指向同一块字符数组内存。当其中一个对象销毁时,它会释放这块资源,而其他对象并不知道资源已被释放,此时若继续对该资源进行操作,就会引发访问违规错误,程序可能崩溃。
例如,假设有如下简单的String
类(为与标准库区分,这里使用String
):
cpp
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
当进行拷贝构造时,如String s2(s1);
,若未显式定义拷贝构造函数,编译器合成的默认拷贝构造函数会使s1
和s2
共用同一块内存空间。当TestString
函数结束,s2
先被销毁,释放了共享的内存,此时s1
的_str
就成了野指针,再销毁s1
时就会出错。
为解决浅拷贝问题,需要采用深拷贝方式。深拷贝的核心思想是为每个对象独立分配资源,使它们之间不会因共享资源而产生多次释放导致的错误。就像每个孩子都有自己独立的玩具,不会因为争抢同一个玩具而出现问题。
4.2 传统版与现代版实现
传统版:
传统版的string
类实现中,构造函数负责为字符串分配足够的内存空间并复制内容。如果传入的指针为空,通过assert(false)
来断言程序出错(因为空指针在这种情况下不符合预期),然后返回。拷贝构造函数同样为新对象分配内存,并将源对象的字符串内容复制过去,保证每个对象都有自己独立的字符串副本。
赋值运算符重载函数首先判断是否是自我赋值(this != &s
),如果不是,则先为新内容分配内存,复制源对象的字符串,再释放旧的内存,最后返回当前对象的引用,以支持链式赋值操作。析构函数则负责释放对象所占用的内存资源,确保内存不泄漏。
cpp
class String
{
public:
String(const char* str = "")
:_size(strlen(str))
{
_str = new char[_size + 1];
memcpy(_str, str, _size + 1);
_capacity = _size;
}
String(const String& s)
: _str(new char[s._size + 1])
{
memcpy(_str, s._str,s._size+1);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[s._size + 1];
memcpy(pStr, s._str,s._size+1);
delete[] _str;
_str = pStr;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
现代版:
现代版的构造函数与传统版类似,确保在传入空指针时进行合理处理,并正确分配和初始化字符串内存。拷贝构造函数先将_str
初始化为nullptr
,然后创建一个临时的String
对象strTmp
,用源对象的字符串初始化它。接着通过swap
函数交换当前对象和临时对象的_str
指针,这样当前对象就拥有了源对象字符串的独立副本,而临时对象在离开作用域时会自动销毁其原来指向的内存,避免了内存泄漏。
赋值运算符重载函数采用了一种巧妙的方式,它直接将参数对象作为值传递进来(这会触发参数对象的拷贝构造函数,创建一个副本),然后通过swap
函数交换当前对象和副本对象的_str
指针。这样,原来的内容会被副本对象带走,当副本对象离开作用域时会自动释放,而当前对象则拥有了新的字符串内容。这种方式不仅实现了深拷贝,还简化了代码,提高了效率。最后同样通过析构函数释放对象占用的内存。
cpp
class String
{
public:
String(const char* str = "")
:_size(strlen(str))
{
_str = new char[_size + 1];
memcpy(_str, str, _size + 1);
_capacity = _size;
}
template <class InputIterator>
string(InputIterator first, InputIterator last) {
while (first!=last)
{
*this += *first;
first++;
}
}
String(const String& s)
{
string tmp(s.begin(), s.end());
swap(tmp);
}
String& operator=(String s)
{
if (&s != this)
{
string tmp(s.begin(), s.end());
swap(tmp);
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
对比传统版和现代版的赋值运算符重载,现代版利用了参数的传值拷贝和swap
操作,代码更简洁,效率也有所提升。因为传统版需要先分配新内存、复制内容、释放旧内存,而现代版通过一次swap
操作就完成了赋值,减少了内存分配和复制的次数。
4.3 写时拷贝(了解)
写时拷贝是一种在浅拷贝基础上优化的策略,它引入了引用计数机制。引用计数用于记录资源被多少个对象使用。在对象构造时,将资源的引用计数初始化为 1,表示当前只有一个对象使用该资源。当有新的对象需要使用同一资源时,引用计数增加 1,此时这些对象共享该资源,采用浅拷贝的方式进行赋值,提高了效率,因为避免了频繁的内存分配和复制操作。
当某个对象被销毁时,它会先将引用计数减 1。然后检查引用计数的值,如果计数变为 0,说明该对象是资源的最后一个使用者,此时才释放该资源;如果计数不为 0,说明还有其他对象在使用该资源,不能释放,从而避免了多次释放同一资源的错误。
不过,写时拷贝在读取时存在一些缺陷。由于多个对象共享同一份资源,当一个对象对资源进行写操作时,其他对象感知不到这种变化。如果在读取过程中,资源被其他对象修改,可能会导致读取到不一致的数据。例如,多个string
对象共享一块字符数组,其中一个对象通过operator[]
修改了某个字符,其他正在读取该字符数组的对象可能会读到修改后的值,这可能不符合程序的预期逻辑。
在实际应用中,写时拷贝适用于读操作频繁、写操作较少的场景,可以有效提高性能。但在对数据一致性要求较高,写操作较为频繁的情况下,需要谨慎使用,或者结合其他机制来保证数据的正确性。
五、总结
C++ 的string
类功能强大,掌握其基本使用、常用接口、底层结构和模拟实现,能让开发者在处理字符串相关问题时更加得心应手。无论是在算法竞赛、日常开发还是面试中,对string
类的深入理解都将成为你的有力武器。希望大家通过不断实践,熟练运用string
类,编写出更高效、更健壮的代码。
附件:模拟实现string常用函数
cpp
//string.h
#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
namespace _string
{
class string
{
friend std::ostream& operator<<(std::ostream& _cout, const string& s);
friend std::istream& operator>>(std::istream& _cin, string& s);
public:
typedef char* iterator;
typedef const char* const_iterator;
public:
string(const char* str = "");
template <class InputIterator>
string(InputIterator first, InputIterator last);
string(const string& s);
~string();
string& operator=(const string& s);
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
void push_back(char c);
string& operator+=(char c);
void append(const char* str);
string& operator+=(const char* str);
void clear();
void swap(string& s);
const char* c_str()const;
size_t size()const;
size_t capacity()const;
bool empty()const;
void resize(size_t n, char c = '\0');
void reserve(size_t n);
char& operator[](size_t index);
const char& operator[](size_t index)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;
size_t find(char c, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
string& insert(size_t pos, char c);
string& insert(size_t pos, const char* str);
string& erase(size_t pos, size_t len);
private:
char* _str;
size_t _capacity;
size_t _size;
public:
static const size_t npos = -1;
};
std::ostream& operator<<(std::ostream& _cout, const string& s);
std::istream& operator>>(std::istream& _cin, string& s);
};
cpp
//string.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"_string.h"
namespace _string
{
string::string(const char* str )
:_size(strlen(str))
{
_str = new char[_size + 1];
memcpy(_str, str, _size + 1);
_capacity = _size;
}
template <class InputIterator>
string::string(InputIterator first, InputIterator last) {
while (first!=last)
{
*this += *first;
first++;
}
}
string::string(const string& s) {
string tmp(s.begin(), s.end());
swap(tmp);
}
string::~string() {
if (this != nullptr)
{
delete[] _str;
}
_size = _capacity = 0;
}
string& string::operator=(const string& s) {
if (&s != this)
{
string tmp(s.begin(), s.end());
swap(tmp);
}
return *this;
}
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;
}
void string::push_back(char c) {
if (_capacity == _size)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size++] = c;
_str[_size] = '\0';
}
string& string::operator+=(char c) {
push_back(c);
return *this;
}
//void string::append(const char* str) {
// for (int i = 0; str[i] != '\0';i++)
// {
// push_back(str[i]);
// }
//}
void string::append(const char* str) {
size_t len = strlen(str);
if (_capacity < _size + len)
{
size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newcapacity);
}
memcpy(_str+_size, str, len + 1);
_size += len;
}
string& string::operator+=(const char* str) {
append(str);
return *this;
}
void string::clear() {
_size = 0;
_str[0] = '\0';
}
void string::swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* string::c_str()const {
return _str;
}
size_t string::size()const {
return _size;
}
size_t string::capacity()const {
return _capacity;
}
bool string::empty()const {
if (_size==0)
{
return true;
}
return false;
}
void string::resize(size_t n, char c) {
if (n != _size)
{
if (n < _size)
{
_str[n + 1] = '\0';
_size = n;
}
else
{
for (size_t i = _size; i < n; i++)
{
push_back(c);
}
}
}
}
void string::reserve(size_t n) {
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (_str != nullptr)
{
memcpy(tmp, _str, _size + 1);
delete[] _str;
}
_str = tmp;
_capacity = n;
}
}
char& string::operator[](size_t index) {
assert(index < _size);
return _str[index];
}
const char& string::operator[](size_t index)const {
assert(index < _size);
return _str[index];
}
bool string::operator<(const string& s) const {
size_t l1 = _size;
size_t l2 = s._size;
size_t p1 = 0;
size_t p2 = 0;
while (p1 < l1 && p2 < l2)
{
if (_str[p1] > s._str[p2])
{
return false;
}
else if (_str[p1] < s._str[p2])
{
return true;
}
p1++;
p2++;
}
//if (p1 == l1 && p2 < l2)
//{
// return false;
//}
//else
//{
// return true;
//}
return p1 == l1 && p2 < l2;
}
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 {
size_t l1 = _size;
size_t l2 = s._size;
size_t p1 = 0;
size_t p2 = 0;
while (p1 < l1 && p2 < l2)
{
if (_str[p1] != s._str[p2])
{
return false;
}
p1++;
p2++;
}
return l1 == p1 && l2 == p2;
}
bool string::operator!=(const string& s) const {
return !(*this == s);
}
size_t string::find(char c, size_t pos) const {
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t string::find(const char* s, size_t pos ) const {
assert(pos < _size);
const char* p = strstr(_str,s);
if (p == nullptr)
{
return npos;
}
return p - _str;
}
string& string::insert(size_t pos, char c) {
size_t end = _size + 1;
if (_capacity == _size)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
++_size;
return *this;
}
string& string::insert(size_t pos, const char* str) {
size_t len = strlen(str);
if (_capacity < _size + len)
{
size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
reserve(newcapacity);
}
size_t end = _size + len + 1;
while (end > pos + len-1)
{
_str[end] = _str[end - len];
end--;
}
memcpy(_str + pos, str, len);
_size += len;
return *this;
}
string& string::erase(size_t pos, size_t len) {
assert(pos < _size);
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
memmove(_str + pos, _str + pos + len, _size + 1 - (pos + len));
_size -= len;
}
}
std::ostream& operator<<(std::ostream& _cout, const string& s) {
for (size_t i = 0; i < s._size; i++)
{
_cout << s[i];
}
return _cout;
}
std::istream& operator>>(std::istream& _cin, string& s) {
char buf[256];
char ch;
size_t i = 0;
ch=_cin.get();
while (ch != ' ' && ch != '\n')
{
buf[i++] = ch;
ch= _cin.get();
if (i == 255)
{
buf[i] = '\0';
s.append(buf);
i = 0;
}
}
if (i != 0)
{
buf[i] = '\0';
s.append(buf);
}
return _cin;
}
};