初识C++|string类的使用及模拟实现

🍬 mooridy-CSDN博客

🧁C++专栏(更新中!)

目录

一、string类简介

二、string类的基本成员变量

三、string构造

四、string析构

五、string赋值重载

六、string类对象的访问操作

6.1operator[]⭐

七、string类遍历操作

方法1:下标+[]

方法2:迭代器——像指针的东西(但底层实现不一定是指针)

方法3:范围for

*补充语法:auto和范围for

auto关键字

范围for

八、迭代器及相关函数

九、string类对象的容量操作

size⭐&&lenth

capacity

reserve⭐

clear ⭐

empty ⭐

resize ⭐

 十、string类对象的修改操作

push_back

append

operator+= ⭐

insert⭐

erase

replace

十一、string类对象的其他字符串操作

c_str⭐

substr

find & rfind

find_frist_of &find_last_of 

find_first_not_of &find_last_not_of 

综合练习:分割文件名

十二、string类对象的非成员函数重载

getline

十三、string类的模拟实现

string.h

string.cpp

test.cpp

十四、编码

ASCII编码表

统一码(Unicode)

GBK

十五、浅拷贝&深拷贝

浅拷贝

深拷贝

十六、String类的传统写法和现代写法

十七、写时拷贝(了解)


一、string类简介

string原型

最基本的string类

由于编码方式不同,又衍生出不同的string类

由此可见,string是一个模板

string类——管理字符串的类,是用字符的顺序表实现的

在使用string类时,必须包含#include <string>以及using namespace std;

注意不是<string.h>!

二、string类的基本成员变量

class string
{
private:
	char _buff[16];
	char*  _str;

	size_t _size;
	size_t _capacity;
};

三、string构造

string类共有7中构造方式(C++98):

其中,最为重要的是(1)默认构造函数——构造长度为零的空字符串。

(2)拷贝构造(4)带参构造

string s1;//无参
string s3(s2);//拷贝构造
string s2("hello world");//带参

(3)给一个str,从str的pos处开始拷贝len个字符进行拷贝构造

如果len太大超出范围,则拷贝范围终点为字符串末尾,不会报错。

第三个值len有缺省值npos,如果不给参数,也是拷贝范围终点为字符串末尾。

string s4(s2, 6, 15);
//从s2的下标为6处开始拷贝15个字符进行拷贝构造

(5) 从 s 指向的字符数组中复制前 n 个字符用于构造。

string s5(s2, 6);

(6) 用n个字符 c 构造字符串。

string s7(10, 'X');

四、string析构

析构函数会自动调用

五、string赋值重载

最常用的是(1)

六、string类对象的访问操作

6.1operator[]

通过使用str[pos]可以访问到pos位置的字符,,返回的是元素的引用,可进行读和修改

string s2("hello world");

s2[0] = 'x';//xello world

与at的区别:[ ]越界断言,at越界抛异常

七、string类遍历操作

方法1:下标+[]

	// 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;
	}

注意:

任何容器的迭代器,都属于类域  eg.vector<int>::iterator

s2.begin()返回的是第一个字符的地址!

s2.end()返回的是最后一个字符的后一个位置的地址!

迭代器的牛逼之处——所有的容器都可以用这种类似的方式访问

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

方法3:范围for


	for (auto& ch : s2)
	{
		ch -= 2;
		cout << ch << " ";
	}

特点:自动赋值,自动迭代,自动判断结束
 底层就是迭代器

思考:为什么范围for内修改了s2,但遍历结束后打印s2却没有改变呢?

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

	
	// 2、迭代器
	//string::iterator it = s2.begin();
	cout << "迭代器" << endl;
	auto it = s2.begin();
	while (it != s2.end())
	{
		*it += 2;

		cout << *it << " ";
		++it;
	}
	cout << endl;

	cout << s2 << endl << endl;

	

	
	cout << "范围for" << endl;
	for (auto ch : s2)
	{
		ch -= 2;
		cout << ch << " ";
	}
	cout << endl;

	cout << s2 << endl;
}

因为上面的ch仅仅是s2中每个字符的拷贝,这里的修改修改的是局部变量,并没有实际完成对s2中每个字符的修改

而迭代器中,我们可以简单理解为*it就是字符串s2中的字符本身

如果我们想要在范围for中修改s2,应当使用传引用传参。

for (auto& ch : s2)

这三种遍历方式在性能上并没有太大区别,应当根据实际场景作判断。

*补充语法:auto和范围for

auto关键字

在早期 C/C++ auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量,后来这个
不重要了。 C++11 中,标准委员会变废为宝赋予了 auto 全新的含义即: auto 不再是一个存储类型
指示符,而是作为一个新的类型指示符来指示编译器, auto 声明的变量必须由编译器在编译时期
推导而得
auto的价值:推导变量类型,替换长类型,简化代码
缺点:牺牲了可读性
	map<string, string> dict;
	//map<string, string>::iterator mit = dict.begin();
	auto mit = dict.begin();

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
auto y = &x;
auto* z = &x;
auto& m = x;

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际
只对第一个类型进行推导,然后用推导出来的类型定义其他变量
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto cc = 3, dd = 4.0;

auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
return 3;
}

auto 不能直接用来声明数组
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };

auto声明的值必须要有初始值定义
// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
auto e;

范围for

C++11 中引入了基于范围的 for 循环。
for 循环后的括号由冒号 分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围 ,自动迭代,自动取数据,自动判断结束。
范围 for 可以作用到数组容器对象上进行遍历
范围 for 的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
for (auto& e : array)
e *= 2;

八、迭代器及相关函数

分为四种类型:
函数名称功能说明
begin获取一个字符的迭代器
end
获取最后一个字符下一个位 置的迭代器
rbegin获取一个字符的迭代器
rend
获取最后一个字符下一个位 置的迭代器
iterator
 string s2("hello world");
	string::reverse_iterator rit = s2.rbegin();
	while (rit != s2.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	
reverse_iterator
这里需要用到rbegin(),rend()
rbegin():指向字符串的最后一个字符
rend():指向字符串第一个字符
注意:对反向迭代器而言,从后往前是正方向,所以是++rit
string::reverse_iterator rit = s2.rbegin();
	while (rit != s2.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

const_iterator

这里使用的begin()便是下方的函数,end(),rbegin(),rend()同理。

const string s3("hello world");
	//string::const_iterator cit = s3.begin();
	auto cit = s3.begin();
	while (cit != s3.end())
	{
		//*cit += 2;
		cout << *cit << " ";
		++cit;
	}

const_reverse_iterator
auto rcit = s3.rbegin();
	while (rcit != s3.rend())
	{
		// *rcit += 2;
		cout << *rcit << " ";
		++rcit;
	}
	cout << endl;

九、string类对象的容量操作

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)
检测字符串释放为空串,是返回 true ,否则返回 false
clear (重点)清空有效字符
reserve (重点)为字符串预留空间
resize (重点)将有效字符的个数改成n个,多出的空间用字符c填充

size&&lenth

1.size和length都可以返回字符串有效字符长度,但在其它容器中lenth不具有通用性,size更加通用。
string s2("hello world");
	cout << s2.length() << endl;
	cout << s2.size() << endl;

capacity

void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}
vs2022环境下
注意这里的空间是不包含'\0'的,因此实际空间应该还要在此基础上+1
那么第一次就是2倍扩容,而后面的大致为1.5倍扩容
原因是为什么呢?
因为VS下做了特殊处理,当字符串空间大小小于16时,该字符串会存储到类对象内部的buff数组中,即并没有直接储存到str所指向的空间。当字符串空间大于等于16时,buff数组废弃,直接储存到str所指向的空间上(堆区域)。
class string
{
private:
	char _buff[16];
	char*  _str;

	size_t _size;
	size_t _capacity;
};

Linux环境下

可见Linux环境下的扩容就是简单的2倍扩容(没有buff数组)

reserve

作用:为字符串预留空间,避免频繁扩容
// 提前开空间,避免扩容,提高效率
	s.reserve(100);

会开大于等于n的空间

开辟的空间不包括'\0'

当n大于字符串当前容量时,肯定会扩容

当n小于字符串当前容量时,会发起一个不具有约束力的请求,去缩小容量,且该函数不能修改字符串的内容。具体情况如下图所示:

g++(注意g++环境下进行了内存对齐)

vs

            

clear

清空有效字符
string s2("hello worldxxxxxxxxxxxxx");
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

s2.clear();
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

empty

检测字符串释放为空串,是返回true,否则返回false

resize

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

如果n比size小,会删除数据,如果n比size大,则多余空间会插入字符c,默认为'\0'

 十、string类对象的修改操作

函数名称  功能说明
push_back  在字符串后尾插字符c
append在字符串后面追加一个字符串
operator+=()(⭐) 在字符产后面追加字符串str
insert(⭐)在指定位置插入字符或字符串
erase (⭐)删除字符串中的一部分
replace 替换指定区间的字符串

push_back

在字符串后尾插字符c

注意:只能尾插一个字符,不能尾插字符串

 
string s2("hello world");
s2.push_back('?');
cout << s2 << endl;

append

在字符串后追加一个字符串
string& append (const string& str);		// 追加一个string对象
// 追加一个string对象中的指定字符串长度
string& append (const string& str, size_t subpos, size_t sublen);	
 
string& append (const char* s);				// 追加一个字符串
string& append (const char* s, size_t n);	// 追加字符串中的前n个字符
string& append (size_t n, char c);			// 追加n个字符

	string s2("hello world");
	cout << s2 << endl;
	//追加一个string对象
	string s1("00000");
	s2.append(s1);
	// 追加一个string对象中的指定字符串长度
	string s3("54321");
	s2.append(s3,0,2);
	//追加一个字符串
	s2.append(" Yes!");
	//追加字符串中的前n个字符
	s2.append("haha",2);
	//追加n个字符
	s2.append(3,'!');
	cout << s2 << endl;

operator+=

在字符串后追加字符串str
string s2("hello world");
	cout << s2 << endl;
	string s1("@@@");
	s2 += s1;
	s2 += " hahahahaha";
	s2 += "~";
	cout << s2 << endl;

insert⭐

在指定位置插入字符或字符串

注意:根据C语言数据结构的基础,可知插入时会大量挪动数据,使得时间复杂度提升,程序性能下降,因此需要谨慎使用insert。

//在指定位置插入一个string对象
 string& insert (size_t pos, const string& str);
// 在指定位置插入一个string对象里的一部分
 string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);

// 在指定位置插入一个字符串	
 string& insert (size_t pos, const char* s);
// 在指定位置插入一个字符串的前n个字符
 string& insert (size_t pos, const char* s, size_t n);

// 在指定位置插入n个字符
 string& insert (size_t pos, size_t n, char c);

// 在指定迭代器的位置插入n个字符
    void insert (iterator p, size_t n, char c);

// 在指定迭代器的位置插入一个字符,并且返回该位置的迭代器
iterator insert (iterator p, char c);

这里只演示最常用的几种:

string s2("hello world");
	cout << s2 << endl;
	
	//在指定位置插入一个string对象
	string s1("aa");
	s2.insert(0, s1);

	//在指定位置插入一个字符串
	s2.insert(10, "_mid_ ");

	// 在指定位置插入n个字符
	char ch1 = '*';
	s2.insert(0,4,ch1);
	// 在指定迭代器的位置插入n个字符
	char ch2 = '+';
	s2.insert(s2.begin(), ch2);
	cout << s2 << endl;

erase

删除字符串中的一部分

//从指定位置开始,删除指定长度个字符(默认删除到结尾)
 string& erase (size_t pos = 0, size_t len = npos);
//给一个指定位置的迭代器,删除该位置的字符
iterator erase (iterator p);
//给两个指定位置的迭代器,删除在此之间的所有字符
iterator erase (iterator first, iterator last);

代码示例:

string s2("hello world");
	cout << s2 << endl;

	s2.erase(0, 3);//从0位置开始向后删除3个字符
	cout << s2 << endl;
	//头删
	s2.erase(0, 1);
	cout << s2 << endl;
	s2.erase(s2.begin());
	cout << s2 << endl;
	//尾删
	s2.erase(--s2.end());
	cout << s2 << endl;
	s2.erase(s2.size()-1,1);

	//从某个位置后全部删
	s2.erase(2);//个数的默认缺省值为npos
	cout << s2 << endl;

replace

替换指定区间的字符串

replace的相关接口非常多,但是我们只需要掌握最常用的几种即可,其它的可以在必要之时翻阅文档学习。

string s2("hello world");
	cout << s2 << endl;
	
	//从第0个位置开始替换,替换5个字符,替换为:"Embrace"
	s2.replace(0, 5, "Embrace");
	cout << s2 << endl;

	//左闭右开,删除闭,保留开
	string s1("life");
	s2.replace(s2.begin() + 8, s2.end(), s1);
	cout << s2 << endl;

十一、string类对象的其他字符串操作

函数名称   功能说明
c_str 返回 C语言格式字符串
substr(⭐) 在str中从pos位置开始往,截取n个字符,然后将其返回
find(⭐)从字符串pos位置开始往后找字符/字符串,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符/字符串,返回该字符在字符串中的位置
find_frist_of  从前往后找第一个匹配的字符
find_last_of  从后往前找第一个匹配的字符
find_first_not_of  从前往后找第一个不匹配的字符
find_last_not_of 从后往前找第一个不匹配的字符

c_str

返回底层字符串的指针
意义:兼容C语言
string file;
cin>>file;
FILE* fout=fopen(file.c_str(),"r");
fclose();

substr

在str中从pos位置开始,截取n个字符,然后将其返回
string str("hello world");
	cout<<str.substr(0, 5)<<endl;

find & rfind

find: 从字符串pos位置开始往后找第一次出现的字符/字符串,返回该字符在字符串中的位置
rfind:从字符串pos位置开始往前找第一次出现的字符/字符串,返回该字符在字符串中的位置
string str("hello world");

	string substr("world");
	size_t pos1 = str.find(substr);
	cout << pos1 << endl;
	
	//从前往后找
	size_t pos2 = str.find('l');
	//从后往前找
	size_t pos3 = str.rfind('l');
	cout << pos2 << " " << pos3 << endl;

	//从指定位置开始找
	size_t pos4 = str.find('l', 5);
	cout << pos4 << endl;

find_frist_of &find_last_of 

 find_frist_of:从指定位置开始(默认从头),从前往后在当前的string对象中寻找匹配的任何字符,返回位置

find_last_of : 从指定位置开始(默认从尾),从后往前在当前的string对象中寻找匹配的任何字符,返回位置

(小声bb:这里的取名有一些迷惑,如果按照功能取名的话,其实应该叫find_any_of才更为恰当呀)

string str("hello world");
	size_t pos1=str.find_first_of("you");//找到str[4]的o满足条件
	size_t pos2=str.find_last_of("you");//倒着找
	size_t pos3 = str.find_first_of("you",5);//从指定位置开始找
	cout << pos1 << " " << pos2 << " " << pos3<< endl;

find_first_not_of &find_last_not_of 

find_first_not_of:从指定位置开始(默认从头),从前往后在当前的string对象中寻找不匹配的任何字符,返回位置

find_last_not_of :从指定位置开始(默认从尾),从前往后在当前的string对象中寻找不匹配的任何字符,返回位置

这两个操作与上面的操作相同。

综合练习:分割文件名

十二、string类对象的非成员函数重载

getline

在学习C语言的过程中我们发现scanf()在读取字符串的时候读到空格就结束了,无法读取后面内容。那时,我们通常使用gets()函数来解决问题。

现在,又有了一种新的解决方法,getline()函数!

它只有在遇到换行符时才停止读取遇到空格是不会停止的。

并且,它是可以自定义间隔符的。

string str;
getline(cin,str);
cout<<str<<endl;

getline(cin,str,'*');//指定*为间隔符
cout<<str<<endl;

十三、string类的模拟实现

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
namespace A {

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

		typedef char* iterator;
		typedef const char* const_iterator;
		  string(const char* str="") {
			  _size = strlen(str);
			   _str = new char[_size+1];
			   _capacity = _size;
			   strcpy(_str, str);

		  }

		  string(const string& s) {
			  _str = new char[s._capacity+1];
			  strcpy(_str,s._str);
			  _size = s._size;
			  _capacity = s._capacity;

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

		  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;
		  }
		  const char* c_str()const {
			  return _str;
		  }
		  size_t size()const {
			  return _size;
		  }
		  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];
		  }
		  iterator begin() {
			  return _str;
		  }
		  const_iterator end()const {
			  return _str + _size;
		  }
		  const_iterator begin() const {
			  return _str;
		  }
		  iterator end() {
			  return _str + _size;
		  }
		  void push_back(char ch);
		  void append(const char* str);
		  string& operator+=(char ch);
		  string& operator+=(const char* str);
		  void reserve(size_t n);
		  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);
		  void clear();
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		static const  size_t npos;
	};


	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);

	

}

string.cpp

#include "string.h"

namespace A {
	const  size_t string::npos = -1;

	void string::reserve(size_t n) {
		if (n > _capacity) {
			//多开一个给\0
			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;
			_str[_size] = '\0';
		
	}
	void string::append(const char* str) {
		size_t len = strlen(str);
		if (_size + len > _capacity) {
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

	string& string::operator+=(char ch) {
		push_back(ch);
		return *this;
	}

	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);
		}
		size_t end = _size + 1;
		while (end > pos) {
			_str[end] = _str[end - 1];
			end--;
		}
		_str[pos] = ch;
		++_size;
	}
	void string::insert(size_t pos, const char* str) {
		size_t len = strlen(str);
		if (_size + len > _capacity) {
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		size_t end = _size + len;
		while (end >= pos + len) {
			_str[end] = _str[end - len];
			end--;
		}

		for (int 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 (int i = pos + len; i <= _size; i++) {
				_str[i - len] = _str[i];
			}
			_size -= len;
		}
	}


	size_t string::find(char ch, size_t pos) {
		for (int i = pos; i < _size; i++) {
			if (ch == _str[i]) {
				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大于剩余字符长度,更新一下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 strcmp(s1.c_str(), s2.c_str()) <= 0;
	}
	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 strcmp(s1.c_str(), s2.c_str()) >= 0;
	}
	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 strcmp(s1.c_str(), s2.c_str()) != 0;
	}

	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;
		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 string::clear() {
		_str[0] = '\0';
		_size = 0;
	}
}

test.cpp


#include "string.h"

namespace A {
	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 << s2.c_str() << endl;

		for (auto e : s2)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test_string2()
	{
		string s1("hello world");
		s1 += 'x';
		s1 += '#';
		cout << s1.c_str() << endl;
		s1 += "hello life";
		cout << s1.c_str() << endl;
		//s1.insert(5, '$');
		//cout << s1.c_str() << endl;

		s1.insert(0, '$');
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;

		s2.insert(5, "$$$");
		cout << s2.c_str() << endl;

		s2.insert(0, "$$$&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
		cout << s2.c_str() << endl;
	}


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

		string s2("hello world");
		s2.erase(6);
		cout << s2.c_str() << endl;

		string s3("hello world");
		s3.erase(6, 3);
		cout << s3.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;
		cout << (s1 == s2) << endl;
		cout << ("hello world" < s2) << endl;
		cout << (s1 == "hello world") << endl;
		cout << ("hello world" == "hello world") << endl;

		cout << s1 << s2 << endl;

		string s0;
		cin >> s0;
		cout << s0 << endl;
	}

}

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

十四、编码

内存和磁盘中只能储存二进制数字,那么文字和符号是如何储存的呢?这里我们就要提出编码这一概念。

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

ASCII编码表

本质:英文符号和值的映射关系

ascii码表

统一码(Unicode)

普适性:也叫万国码,顾名思义,所有国家的文字符号都可以用该编码方式表示。

兼容性:UTF-8兼容ASCII,它的前128个字符与ASCII码完全一致

扩展性:Unicode设计了多种编码方案(如UTF-8、UTF-16、UTF-32)来适应不同的存储和传输需求。其中最为常用的是UTF-8.

UTF8

变长性:下图UTF8的后缀表示:要用几个字节来代表符号。UTF8可以使用1~4个字节来表示符号。

UTF16

用2个或4个字节表示每个字符。

UTF32

用4个字节为单位表示字符。

GBK

中日韩汉字符号和值的映射关系。(它比utf-8在中日韩汉字方面收录更全面)

windows系列支持该编码方式。

这时我们就可以理解为什么string要实现成模板了。

支持UTF-8

 

支持UTF-16

支持UTF-32

支持宽字符

十五、浅拷贝&深拷贝

浅拷贝

首先,我们来看一个例子:

上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认
的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内
存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致
多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该
资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一
不想分享就你争我夺,玩具损坏。
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父
母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

深拷贝

深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

十六、String类的传统写法和现代写法

传统写法

String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}

现代写法

String& operator=(String s)
{
swap(_str, s._str);
return *this;
}

观察swap模板的底层可以知道,这里进行swap会进行3次深拷贝,是非常低效的!

但其实,有专门定义一个针对string类的swap函数解决了该问题:

通过前面的学习,我们可以知道,优先使用最匹配的

十七、写时拷贝(了解)

写时拷贝:是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该
资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有
其他对象在使用该资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值