string

目录

string

一、STL简介

1.1.STL的概念

1.2.STL的版本

1.3.STL的六大组件

二、string类

2.1.string类的主要用法

2.2.迭代器遍历

2.3.范围for遍历

2.4.auto关键字

2.5.范围for

2.6.迭代器

2.7.容量操作(Capacity)

2.8.访问及遍历操作

2.9.修改操作

2.10.非成员函数

三、string试题

试题1:仅仅反转字母

试题2:字符串中的第一个唯一字符

试题3:字符串相加

试题4:字符串最后一个单词的长度

四、string的模拟实现

五、编码

六、现代写法优化

七、swap函数问题

八、string浅拷贝问题


一、STL简介

1.1.STL的概念

STL(Standard Template Libaray):标准模板库

是C++标准库的重要组成部分,不仅是一个可复用的组件库

而且是一个包含数据结构和算法的软件框架

1.2.STL的版本

原始版本

在惠普实验室完成的原始版本,开源使用,HP版本是所以STL实现版本的始祖

P.J.版本

继承HP版本,被Windows Visual C++采用,不能公开,修改,可读性低

RW版本

继承HP版本,被C++ Bulider采用,不能公开,修改,可读性一般

SGI版本(主要学习版本)

继承HP版本,被GCC采用,可移植性好,可公开,修改,可读性高

1.3.STL的六大组件

容器:数据结构

算法:排序,逆置,交换...

空间配置器:内存池

......

二、string类

严格来说,string属于C++标准库,但并不属于STL

而从功能上来说,是可以归类到STL的容器里面的

2.1.string类的主要用法

#include<iostream>
#include<string>
#include<assert.h>

using namespace std;

class my_string
{
public:
	char& operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

int main()
{
	//string():默认构造
	string s1;

	//string(const char* s):带参构造
	string s2("Hello World");

	//string(const char& str):拷贝构造
	string s3(s2);

	cin >> s1;

	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;

	//string(const char& str,size_t pos,size_t len = npos):取第6位之后的5个字符
	string s4(s2, 6, 5);
	cout << s4 << endl;

	//string(const char& str,size_t pos,size_t len = npos):取第6位之后的所有字符
	string s5(s2, 6);
	cout << s5 << endl;
	//npos代表-1,表示字符串的末尾

	//string(const char* s,int n):取前五个字符初始化
	string s6("Hello World", 5);
	cout << s6 << endl;

	//char& operator[](size_t i):下标访问符运算符重载
	s6[0] = 'x';
	cout << s6 << endl;

	return 0;
}

2.2.迭代器遍历

迭代器:string类的内部类,所有的容器都有自己的迭代器,可以通过它来访问容器

#include<iostream>
#include<string>
#include<list>

using namespace std;


void test_string1()
{
	string s1;
	string s2("hello world");
	cout << s1 << s2 << endl;

	s2[0] = 'x';
	cout << s1 << s2 << endl;

	//遍历方法1:下标 + []
	/*for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}
	cout << endl;*/

	//遍历方法2:迭代器
	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

    //迭代器访问链表
	list<int> lt = { 1,2,3,4,5,6,7 };
	list<int>::iterator lit = lt.begin();
	while (lit != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	test_string1();
	return 0;
}

2.3.范围for遍历

#include<iostream>
#include<string>

using namespace std;

void test_string1()
{
	string s1;
	string s2("hello world");
	cout << s1 << s2 << endl;

	s2[0] = 'x';
	cout << s1 << s2 << endl;

	//遍历方法1:下标 + []
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}
	cout << endl;

	//遍历方法2:迭代器
	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//遍历方法3:范围for(C++11)
	//自动赋值,自动迭代,自动判断结束
	for (auto ch : s2)
	{
		cout << ch << " ";
	}
	cout << endl;
}

int main()
{
	test_string1();
	return 0;
}

注:

迭代器可以修改s2,但范围for无法修改s2

ch是s2中每个字符的拷贝,修改的只是临时变量

而迭代器中的*it类似于指针解引用

可以在ch前加解引用,取别名后就能够修改s2

#include<iostream>
#include<string>

using namespace std;

void test_string1()
{
	string s1;
	string s2("hello world");
	cout << s1 << s2 << endl;

	s2[0] = 'x';
	cout << s1 << s2 << endl;

	//遍历方法1:下标 + []
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}
	cout << endl;

	//遍历方法2:迭代器
	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		*it += 2;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//遍历方法3:范围for(C++11)
	//自动赋值,自动迭代,自动判断结束,底层为迭代器
    //for (auto ch : s2):别名
	for (auto ch : s2)//拷贝
	{
		ch -= 2;
		cout << ch << " ";
	}
	cout << endl;

	cout << s2 << endl;
}

int main()
{
	test_string1();
	return 0;
}

2.4.auto关键字

C/C++:auto修饰的变量是具有自动存储器的局部变量

C++11:作为一个新的类型指示符来指示编译器

auto声明的变量必须由编译器在编译时期推导而得

主要用法:

#include<iostream>
#include <string>
#include <map>
using namespace std;

int main()
{
    std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"} };
    //std::map<std::string, std::string>::iterator it = dict.begin();
    auto it = dict.begin();
    while (it != dict.end())
    {
        cout << it->first << ":" << it->second << endl;
        ++it;
    }
    return 0;
}

数组不能包含auto的元素类型

auto必须推导同一个类型

auto必须有初始值设定

auto可以作返回值,但需要谨慎使用

auto不能作函数参数

2.5.范围for

C++11:引入了基于范围的for循环

for循环后的括号由冒号“:”分为两部分

前一部分:范围内用于迭代的变量;后一部分:被迭代的范围

作用:在数组和容器对象上进行遍历

特点:自动迭代,自动取数据,自动判断结束

底层:替换为迭代器

#include<iostream>
#include <string>

using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };

	// C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2;
	}
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		cout << array[i] << " ";
	}

	cout << endl;

	// C++11的遍历
	for (auto& e : array)
	{
		e *= 2;
	}
	for (auto e : array)
	{
		cout << e << " " ;
	}
	
	cout << endl;

	string str("hello world");

	for (auto ch : str)
	{
		cout << ch << " ";
	}

	cout << endl;

	return 0;
}

2.6.迭代器

迭代器是一种封装

屏蔽了底层容器的结构和细节

提供了同一的类似访问容器的方式

#include<iostream>
#include<string>

using namespace std;

void test_string1()
{
	string s2("hello world");
	//正向迭代器
	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//反向迭代器
	string::reverse_iterator rit = s2.rbegin();
	while (rit != s2.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

int main()
{
	test_string1();
	return 0;
}

注:反向迭代器也是++rit,但是是倒着走的

普通迭代器:可读可写

#include<iostream>
#include<string>

using namespace std;

void test_string1()
{
	string s2("hello world");
	//正向迭代器
	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		*it += 10;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//反向迭代器
	string::reverse_iterator rit = s2.rbegin();
	while (rit != s2.rend())
	{
		*rit -= 10;
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

int main()
{
	test_string1();
	return 0;
}

const迭代器:只能读,不能写

#include<iostream>
#include<string>

using namespace std;

void test_string1()
{
	const string s3("hello world");
	//正向迭代器
	//string::const_iterator cit = s3.begin();
	auto cit = s3.begin();
	while (cit != s3.end())
	{
		cout << *cit << " ";
		++cit;
	}
	cout << endl;

	//反向迭代器
	//string::const_reverse_iterator rcit = s3.rbegin();
	auto rcit = s3.rbegin();
	while (rcit != s3.rend())
	{
		cout << *rcit << " ";
		++rcit;
	}
	cout << endl;
}

int main()
{
	test_string1();
	return 0;
}

2.7.容量操作(Capacity)

size与length:

返回字符串有效字符长度

length向前兼容size,size更具有通用性

#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s2("hello world");
	cout << s2.length() << endl;
	cout << s2.size() << endl;
}

int main()
{
	test_string3();
	return 0;
}

max_size:

最大的size数

#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s2("hello world");
	cout << s2.max_size() << endl;
}

int main()
{
	test_string3();
	return 0;
}

capacity:

容量(当前申请的空间大小)

#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s2("hello world");
	cout << s2.capacity() << endl;
}

int main()
{
	test_string3();
	return 0;
}

在VS2019中

去除\0,第一次扩容2倍,后续扩容1.5倍

当字符串的字符个数小于16时,会存在_Buf数组里

当字符串的字符个数大于16时,会存在_ptr指向的堆区空间

在g++4.8中

总是2倍扩容,并且没有_Buf数组

reserve:为字符串预留空间

提前开好空间可以避免扩容

如果n大于当前容量,则会将容量扩容到n,或者大于n

在VS中,reserve不会发送缩容(牺牲空间换时间)

在g++中,reserve会发送缩容(保证空间)

#include<iostream>
#include<string>

void test_string3()
{
	string s1;
	s1.reserve(100);
	size_t sz = s1.capacity();
	cout << "capacity changed:" << sz << endl;
}

int main()
{
	test_string3();
	return 0;
}

注:string返回的capacity值不包括\0

resize:将有效字符的个数改成n个,多出的空间用字符c填充

2.8.访问及遍历操作

operator[]:返回pos位置的字符,越界访问时会断言报错

at:与[]功能一样,但越界 访问时会抛异常

2.9.修改操作

push_back:在字符串后尾插字符c

append:在字符串后追加一个字符串

operator+=:在字符串后追加字符串str

insert:插入字符

#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s("hello world");
	s.push_back(' ');
	s.push_back('x');
	s.append("yyyyyy");
	cout << s << endl;
	s += ' ';
	s += "333333";
	cout << s << endl;
	s.insert(0, "hello yonaka ");
	cout << s << endl;
}

int main()
{
	test_string3();
	return 0;
}

erase:删除字符

#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s("hello world");
	s.erase(6, 1);
	cout << s << endl;
	s.erase(0, 1);
	cout << s << endl;
	s.erase(s.begin());
	cout << s << endl;
	s.erase(s.end());
	cout << s << endl;
	s.erase(s.size()-1,1);
	cout << s << endl;
	string ss("hello world");
	ss.erase(6);
	cout << ss << endl;
}

int main()
{
	test_string3();
	return 0;
}

replace:替换字符

#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s("hello world bye world");
	s.replace(5, 4, "%%");
	cout << s << endl;
}

int main()
{
	test_string3();
	return 0;
}

find:查找字符

返回值:

如果匹配,返回第一个匹配成功的字符下标

如果不匹配,返回-1(string::npos)

#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s("hello                        world bye world");

	//方法1:
	size_t pos = s.find(' ');
	while (pos != string::npos)
	{
		cout << s << endl;
		s.replace(pos, 1, "%%");
		pos = s.find(' ',pos+2);
	}
	cout << s << endl;

	//方法2:
	string tmp;
    //tmp.reserve(s.size());
	for (auto ch : s)
	{
		if (ch == ' ')
		{
			tmp += "%%";
		}
		else
		{
			tmp += ch;
		}
	}
	cout << tmp << endl;
	//s = tmp;
	s.swap(tmp);
	cout << s << endl;
	
}

int main()
{
	test_string3();
	return 0;
}

c_str:返回c格式字符串

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string file;
	cin >> file;
	FILE* fout = fopen(file.c_str(), "r");
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	fclose(fout);
}

int main()
{
	test_string3();
	return 0;
}

substr:在str中从pos位置开始,截取n个字符后返回

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s("test.cpp");
	size_t pos = s.find('.');
	string suffix = s.substr(pos);
	cout << suffix << endl;
}

int main()
{
	test_string3();
	return 0;
}

rfind:从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string s("test.cpp.zip");
	size_t pos = s.rfind('.');
	string suffix = s.substr(pos);
	cout << suffix << endl;
}

int main()
{
	test_string3();
	return 0;
}

find_first(any)_of:返回字符串内任何符合字符的下标

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string str("Please,replace the vowels in this sentence by asterisks");
	size_t found = str.find_first_of("abcd");
	while (found != string::npos)
	{
		str[found] = '*';
		found = str.find_first_of("abcd", found + 1);
	}
	cout << str << endl;
}

int main()
{
	test_string3();
	return 0;
}

find_first(any)_not_of:返回字符串内任何不符合字符的下标

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>

using namespace std;

void test_string3()
{
	string str("Please,replace the vowels in this sentence by asterisks");
	size_t found = str.find_first_not_of("abcd");
	while (found != string::npos)
	{
		str[found] = '*';
		found = str.find_first_not_of("abcd", found + 1);
	}
	cout << str << endl;
}

int main()
{
	test_string3();
	return 0;
}

find_last(any)_of:返回字符串内任何符合字符的下标(从后开始)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>

using namespace std;

void SplitFilename(const string& str)
{
	cout << "Splitting:" << str << '\n';
	size_t found = str.find_last_of("/\\");
	cout << "path:" << str.substr(0, found) << '\n';
	cout << "file:" << str.substr(found + 1) << '\n';
}

void test_string3()
{
	string str1("/usr/bin/man");
	string str2("c:\\windows\\winhelp.exe");

	SplitFilename(str1);
	SplitFilename(str2);
}

int main()
{
	test_string3();
	return 0;
}

2.10.非成员函数

operator+

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>

using namespace std;

void test_string3()
{
	string s1("hello");

	string s2 = s1 + "world";
	cout << s2 << endl;

	string s3 = "world" + s1;
	cout << s3 << endl;
}

int main()
{
	test_string3();
	return 0;
}

三、string试题

试题1:仅仅反转字母

题目内容:

给你一串字符s,所有非英文字母保留在原有位置

所有英文字母(大写或小写)位置反转,返回反转后的s

示例:

输入:s = "ab-cd"

输出:"dc-ba"

class Solution 
{
public:
    bool isLetter(char ch)
    {
        if(ch >= 'a' && ch <= 'z')
        {
            return true;
        }
        if(ch >= 'A' && ch <= 'Z')
        {
            return true;
        }
        return false;
    }
    string reverseOnlyLetters(string s)
    {
      int left = 0,right = s.size()-1;
      while(left < right)
      {
        while(left < right && !isLetter(s[left]))
        {
            ++left;
        }
        while(left < right && !isLetter(s[right]))
        {
            --right;
        }
        swap(s[left++],s[right--]);
      }  
      return s;
    }
};

试题2:字符串中的第一个唯一字符

题目内容:

给定一个字符串s,找到它的第一个不重复的字符

并且返回它的索引,如果不存在,则返回-1

示例:

输入:s = "loveleetcode"

输出:2

class Solution 
{
public:
    int firstUniqChar(string s) 
    {
        int count[26] = {0};

        //统计次数
        for(auto ch : s)
        {
            count[ch - 'a']++;
        }

        for(size_t i = 0;i < s.size();++i)
        {
            if(count[s[i] - 'a']== 1)
            {
                return i;
            }
        }

        return -1;
    }
};

试题3:字符串相加

题目内容:

给定两个字符串形式的非负数num1和num2,计算它们的和

并且同样以字符串的形式返回,不能直接将输入字符串转换为整型

示例:

输入:num1 = "456",num2 = "77"

输出:"533"

方法1:insert头插

class Solution 
{
public:
    string addStrings(string num1, string num2) 
    {
        string str;
        int end1 = num1.size()-1,end2 = num2.size()-1;
        int next = 0;
        while(end1 >= 0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret = ret % 10;

            str.insert(str.begin(),'0' + ret);
        }
        if(next == 1)
        {
            str.insert(str.begin(),'1');
        }
        return str;
    }
};

方法2:+=尾插+reverse逆置

class Solution 
{
public:
    string addStrings(string num1, string num2) 
    {
        string str;
        int end1 = num1.size()-1,end2 = num2.size()-1;
        int next = 0;
        while(end1 >= 0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret = ret % 10;

            str += ('0' + ret);
        }
        if(next == 1)
        {
            str += '1';
        }
        reverse(str.begin(),str.end());
        return str;
    }
};

试题4:字符串最后一个单词的长度

题目内容:

对于给定的若干个单词组成的句子

每个单词均由大小写字母混合构成,单词间使用单个空格分隔

输出最后一个单词的长度

示例:

输入:HelloNowcoder

输出:13

#include<iostream>
#include<string>

using namespace std;
int main()
{
	string line;
	// 不要使用cin>>line,因为会它遇到空格就结束了
	// while(cin>>line)
	while (getline(cin, line))
	{
		size_t pos = line.rfind(' ');
		cout << line.size() - pos - 1 << endl;
	}
	return 0;
}

补充:getline函数

当输入到指定字符时按下回车才会停止

#include<iostream>
#include<string>

using namespace std;
int main()
{
	string str;
	getline(cin, str, '*');

	size_t pos = str.rfind(' ');
	cout << str.size() - pos - 1 << endl;
	
	return 0;
}

四、string的模拟实现

4.1.源文件编写

#include "string.h"

namespace bit
{
	//初始化静态npos变量
	const size_t string::npos = -1;

	//申请空间
	void string::reserve(size_t n)
	{
		//如果n大于当前空间值
		if (n > _capacity)
		{
			//申请n+1个空间
			char* tmp = new char[n + 1];
			//将原数据拷贝到新空间
			strcpy(tmp, _str);
			//释放旧空间
			delete[] _str;
			//更新指向空间
			_str = tmp;
			//更新空间大小
			_capacity = n;
		}
	}

	//尾插字符
	void string::push_back(char ch)
	{
		//判断空间大小
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//尾插字符
		_str[_size] = ch;
		//更新有效数据
		++_size;
	}

	//+=字符运算符重载
	string& string::operator+=(char ch)
	{
		//调用尾插函数
		push_back(ch);
		//返回对象别名
		return *this;
	}

	//追加字符串
	void string::append(const char* str)
	{
		//计算追加字符长度
		size_t len = strlen(str);
		//如果当前空间不够
		if (_size + len > _capacity)
		{
			//大于2倍,需要多少开多少,小于2倍按2倍扩容
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		//将追加数据拷贝到当前空间
		strcpy(_str + _size,str);
		//更新有效数据
		_size += len;
	}

	//+=字符串运算符重载
	string& string::operator+=(const char* str)
	{
		//调用追加字符串函数
		append(str);
		//返回对象别名
		return *this;
	}

	//在指定位置插入字符
	void string::insert(size_t pos, char ch)
	{
		//判断有效值
		assert(pos <= _size);
		//如果当前空间不够
		if (_size == _capacity)
		{
			//扩容
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		int end = _size;
		//对pos强制类型转换,避免整型提升后无法比较-1
		while (end >= (int)pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}
		_str[pos] = ch;
		++_size;
	}

	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (len == 0)
		{
			return;
		}
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		//类似希尔的间隔思想
		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;
	}

	//删除字符串
	void string::erase(size_t pos, size_t len)
	{

		assert(pos < _size);

		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			for (size_t i = pos + len; i < _size; i++)
			{
				_str[i - len] = _str[i];
			}
			_size -= len;
		}
	}

	size_t string::find(char ch, size_t pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}

	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);

		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - str;
		}
	}

	//获取指定位置后的字符串
	string string::substr(size_t pos, size_t len)
	{
		//判断有效值
		assert(pos < _size);
		//len大小只能为有效值
		if (len > _size - pos)
		{
			len = _size - pos;
		}
		//声明子串
		string sub;
		//开空间
		sub.reserve(len);
		//循环取字符
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		//返回子串
		return sub;
	}

	bool operator==(const string& s1, const string& s2)
	{
		return (strcmp(s1.c_str(), s2.c_str()) == 0);
	}
	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}
	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}
	bool operator<=(const string& s1, const string& s2)
	{
		return (s1 < s2) || (s1 == s2);
	}
	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}
	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		//清除原有数据
		s.clear();
		const int N = 256;
		char buff[N];
		int i = 0;

		char ch;
		//in >> ch;流提取会默认设置分割符
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

	void test_string1()
	{
		string s1;
		string s2("hello world");
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i] += 2;
		}
		cout << endl;

		//范围for本质:使用迭代器
		for (auto e : s2)
		{
			cout << e << " ";
		}
		cout << endl;

		string::iterator it = s2.begin();
		while (it != s2.end())
		{
			*it += 2;
			cout << *it << " ";
			++it;
		}
		cout << endl;
		cout << s2.c_str() << endl;
	}

	void test_string2()
	{
		string s1("hello world");
		s1 += "bye";
		cout << s1.c_str() << endl;

		s1 += "hello bit";
		cout << s1.c_str() << endl;

		s1.insert(5, 'a');
		cout << s1.c_str() << endl;
	}

	void test_string3()
	{
		string s1("hello world");
		s1.erase(6,3);
		cout << s1.c_str() << endl;

		s1.erase(2);
		cout << s1.c_str() << endl;
	}

	void test_string4()
	{
		string s("test.cpp.zip");
		size_t pos = s.find(".");
		string suffix = s.substr(pos);
		cout << suffix.c_str() << endl;

		string copy(s);
		cout << copy.c_str() << endl;

		s = suffix;
		cout << suffix.c_str() << endl;
		cout << s.c_str() << endl;

		s = s;
		cout << s.c_str() << endl;
	}

	void test_string5()
	{
		string s1("hello world");
		string s2("hello world");

		cout << (s1 > s2) << endl;
	}
}

int main()
{
	bit::test_string5();
	return 0;
}

4.2.头文件编写

#define _CRT_SECURE_NO_WARNINGS 1
#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()
		{
			return _str;
		} 
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

		//默认构造函数(短小频繁调用的函数可以之间放在类中,默认为inline)
		string(const char* str = "")
		{
			_size = strlen(str);
			//_capacity不包含\0
			_capacity = _size;
			//+1补充\0
			_str = new char[_capacity + 1];
			//将数据拷贝
			strcpy(_str, str);
		}

		//赋值运算符重载
		string& operator=(const string& s)
		{
			//防止自己给自己赋值
			if (this != &s)
			{
				//释放原空间
				delete[] _str;
				_str = new char[s._capacity + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		//拷贝构造函数(深拷贝)
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_capacity = s._capacity;
			_size = s._size;
		}

		//返回c格式字符串
		const char* c_str() const
		{
			return _str;
		}

		//返回字符串字符个数
		size_t size() const
		{
			return _size;
		}

		//清除数据
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		//返回当前容量
		size_t capacity() const
		{
			return _capacity;
		}

		//下标访问字符
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

		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 insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		string substr(size_t pos = 0, size_t len = npos);
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		const static size_t npos;
	};
	void test_string1();
	void test_string2();
	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
}

五、编码

文字由各种各样的符号构成

但这些符号无法直接在内存或磁盘中存储

内存和磁盘只有二进制(0/1)

编码:值和符号的映射关系

ASCII编码表:英文符号和值的映射关系

打印的过程是查编码表的过程

统一码(Unicode):万国码,将全世界所有国家的文字进行编码

统一码中,“字”的对应数字是2383,有很多方式将数字23383表示成程序中的数据

包括:UTF-8,UTF-16,UTF-32

UTF(UCS Transformation Format):统一码字符集转换格式

将统一码定义的数字转换为程序数据

char类型的basic_string,为默认的string,支持UTF8多字节的变长编码

1个字节,2个字节,3个字节或4个字节表示符号

char16_t类型的basic_string,为默认的u16string

以2个字节来表示符号

char32_t类型的basic_string,为默认的u32string

以4个字节来表示符号

宽字符类型的basic_string,为默认的wstring

用宽字节来表示符号

当有特殊需求时,需要用GPK编码方式

GPK:包含了全部中日韩文字

比如:windows中文版

​​#include <iostream>
using namespace std;

int main()
{
	char str[] = "宵时待雨";
	char16_t str16[] = u"宵时待雨";
	char32_t str32[] = U"宵时待雨";
	const wchar_t* wstr = L"宵时待雨";

	cout << sizeof(str) << endl;
	cout << sizeof(str16) << endl;
	cout << sizeof(str32) << endl;
	cout << sizeof(wstr) << endl;

	return 0;
}

六、现代写法优化

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

//拷贝构造优化(使用swap算法)
string(const string& s)
{
	string tmp(s._str);//普通构造
	swap(tmp);
}

//赋值运算符重载优化
string& operator=(const string& s)
{
	//防止自己给自己赋值
	if (this != &s)
	{
		string tmp(s._str);
		swap(tmp);
	}
	return *this;
}

注:

此时_str要给nullptr缺省值

tmp出作用域自动调用析构函数释放

//赋值运算符重载再优化
string& operator=(string tmp)
{
	swap(tmp);
	return *this;
}

注:赋值运算符重载传值不会出现无穷递归问题,所以可以直接传tmp值

七、swap函数问题

模板中的swap函数(非成员函数,无this指针):

a是对象s1的别名,b是对象s2的别名

s1会拷贝构造出一个对象c(深拷贝),然后将s2赋给s1(深拷贝),最后将c赋给s2(深拷贝)

一共调用了三次深拷贝,所以不能直接交换对象,而是通过_str,_size,capacity进行交换

可以手动实现string中的swap函数(如图所示:swap优先会调用手动实现的swap函数,而非模板

中的swap函数),所以以下两种写法都正确

八、string浅拷贝问题

析构两次:

当使用默认构造函数或赋值运算符创建新对象时

新对象和原对象中的指针会指向同一块内存

当两个对象同时离开作用域时,会调用两次析构函数

对同一块内存释放了两次

一个修改会影响到另一个:

因为两个对象共同拥有同一块内存

所以对一个对象进行修改时会影响到另一个对象

可以用引用计数写时拷贝的方法解决析构多次的问题

计算当前有几个对象指向这块资源

g++的string结构(使用引用计数和写时拷贝):

创建一个柔性数组结构,分别存放size,capacity,和引用计数变量

最后一个变量为指向堆空间的指针,存储字符串

s1对象中的指针指向这个结构空间,此时计数为1

s2对象中的指针也指向这块空间,此时计数加1

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐