Lesson08—string类(4)
c++第八章string类的实现
文章目录
- Lesson08---string类(4)
- 前言
- 一、计算机是怎么储存文字的
- 1. 在此之前先思考一个问题
- 2.编码表
- 2.1 ascll码
- 2.2unicode码
- 2.3UTF码
- 2.4gbk码
 
 
- 二、实现一个简单的string
- 1.构造函数和析构函数
- 2.范围for和迭代器
- 3.常见的成员函数
- 3.1 size
- 3.2 reserve
- 3.3 push_back
- 3.4 append
- 3.5 insert(insert(size_t pos, char ch))
- 3.6 void insert(size_t pos, const char* str);
- 3.7 void erase(size_t pos = 0, size_t len = npos);
- 3.8 size_t find(char ch, size_t pos=0);
- 3.9 size_t find(const char* sub, size_t pos = 0);
- 3.10 拷贝构造函数 My_String(const My_String& str);
- 3.11赋值函数 My_String& operator=(My_String s);
- 3.12 My_String substr(size_t pos = 0, size_t len = npos);
 
- 4.运算符重载
- 5.比较运算符 重载
- 6.流插入和提取
 
前言
这篇文章写了计算机是怎么存储文字的,以及string类的底层是怎么实现的
一、计算机是怎么储存文字的
1. 在此之前先思考一个问题
#define _CRT_SECURE_NO_WARNINGS 
#include<bits/stdc++.h>
using namespace std;
int main()
{
    char buff1[] = "abcd";
    char buff2[] = "你好";
    cout << sizeof(buff1) << endl;
    cout << sizeof(buff2) << endl;
    return 0;
}
上面的代码输出是多少?

 这就很奇怪为什么数量不一样需要的字节却是一样的?
为什么第一个数组明明只有4的字母要的内存却是5的?
通过调试来看看
 
 这里可以看到第一个数组里面存的其实是ascll码,内存里面那个61.62其实就是97的16进制,比实际的数量多1是因为\0,中文是俩个字节
2.编码表
2.1 ascll码
在计算机里面只有010101,它也只认识0101010其他的语言它必须翻译成01010计算机它才能认识,最开始发明计算机的大佬用的是英语,大佬们就对英语进行了编码,对26个字母进行了映射,以及一些标点符号

 
 计算机内存里面只会存ascii码,显示的时候就去找这个表然后显示出来

 早年间计算机只有在美国那边使用,所以最开始就只弄了英文和一些标点符号,但随着计算机的普及一种语言早以满足不了人们

 ascll码的取值范围是0-127,那么上面的负数到底是啥?
2.2unicode码
英文就26个大小一共也就26*2加上一些符号上面的加起来差不多也就100多个,所有的单词都是这些组成,那么中文呢?
汉字十几万个难道也去做一个十几万个的映射表?
unicode也就是统一码,它诞生就是为了编码全世界的文字
 
 
 大部分的文字用俩个字节就足以存下,但这里又会出现一个新的问题,如果中英混合怎么办?
2.3UTF码
如果想实现混合编码那么这几套就必须要兼容,utf码提供了三种编码
 
 用的最多的就是utf8
2.4gbk码
中华的文化博大精深就比如中文不仅有简体字还有繁体字utf就没有gbk处理中文处理的好,gbk就是专门处理中文的一种编码
二、实现一个简单的string
1.构造函数和析构函数
我这里采用多文件的形式
//string.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class My_String
{
	friend ostream& operator<<(ostream& out, My_String& string);
public:
	My_String(const char* str="");
	~My_String();
	const char* c_str()const;
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

#include"string.h"
My_String::My_String(const char* str)
	:_size(strlen(str))
{
	_str = new char[_size + 1];
	_capacity = _size;
	strcpy(_str, str);
}
My_String::~My_String()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}
const char* My_String::c_str() const
{
	return _str;
}
ostream& operator<<(ostream& out,  My_String& string)
{
	cout << string._str;
	return out;
}

2.范围for和迭代器
//string.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class My_String
{
	friend ostream& operator<<(ostream& out, My_String& string);
public:
	typedef char* iterator;
	iterator begin(); 
	iterator end();
	My_String(const char* str="");
	~My_String();
	const char* c_str()const;
	size_t size()const;
	char& operator[](size_t pos);
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

//string.cpp
#include"string.h"
My_String::iterator My_String::begin()
{
	return _str;
}
My_String::iterator My_String::end()
{
	return _str+_size;
}
My_String::My_String(const char* str)
	:_size(strlen(str))
{
	_str = new char[_size + 1];
	_capacity = _size;
	strcpy(_str, str);
}
My_String::~My_String()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}
const char* My_String::c_str() const
{
	return _str;
}
ostream& operator<<(ostream& out,  My_String& string)
{
	cout << string._str;
	return out;
}
size_t My_String::size()const
{
	return _size;
}
char& My_String::operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

 这里需要注意命名最好养成习惯,要不然就会出现各种各样的问题
 
 这样以后就可以用迭代器和范围for来遍历这个数组了,范围for的底层就是迭代器
3.常见的成员函数
3.1 size
这个比较简单就不过多赘述
 
3.2 reserve
这个函数有保留的意思这里我拿来扩容,细节可以参考https://blog.csdn.net/m0_67371175/article/details/141872058?spm=1001.2014.3001.5502的第七条
void My_String::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[]_str;
		_capacity = n;
		_str = tmp;
	}
}

3.3 push_back
这个函数在string容器里面比较复杂我这里就简单实现一下
void My_String::push_back(char ch)
{
	size_t newcapacity;
	if (_size == _capacity)
	{
		if (_capacity == 0)
		{
			newcapacity = 4;
		}
		else
		{
			newcapacity = _capacity * 2;
		}
		My_String::reserve(newcapacity);
	}
	_str[_size] = ch;
	_str[_size + 1] = '\0';
	++_size;
}

3.4 append
这里append和push_back的区别就是一个是字符一个是字符串这里我append是尾插字符串
void My_String::append(const char* str)
{
	size_t len = strlen(str);
	if (_size+len>_capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str+_size, str);
	_size += len;
}

3.5 insert(insert(size_t pos, char ch))
下面代码运行结果是死循环,你找的到问题出在哪里吗
 错误1:
void My_String::insert(size_t pos, char ch)
{
	if (_size == _capacity)
	{
		size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}
	size_t end = _size;
	while (end >= pos)
	{
		_str[end + 1] = _str[end];
		end--;
	}
	_str[pos] = ch;
	_size++;
	
}


 因为这里的end是size_t类型没有符号所以直接就变成了最大的正数了
 
 错误2:
 
 把end换成int类型还是死循环你知道为什么吗?

int类型和size_t类型比较发生隐式类型转换,int类型转换成了size_t了
 
 解决方法就是把pos强制转换成int
 
 这样就可以解决这个问题
 或者也可以这样
 
3.6 void insert(size_t pos, const char* str);
void My_String::insert(size_t pos, const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	int end = _size;
	while (end >= (int)pos)
	{
		_str[end + len] = _str[end];
		end--;
	}
	_size += len;
	memcpy(_str + pos, str, len);
}
size_t len = strlen(str);
if (_size + len > _capacity)
{
	reserve(_size + len);
}
也可以这么写
size_t len = strlen(str);
if (_size + len > _capacity)
{
	reserve(_size + len);
}
size_t end = _size + len;
while (end>= pos+len)
{
	_str[end]=_str[end - len];
	end--;
		
}
memcpy(_str + pos, str, len);
_size += len;
insert函数写好了以后尾插就可以直接调用这个函数了
3.7 void erase(size_t pos = 0, size_t len = npos);
//下面代码还有一个错误可以尝试找一下
void My_String::erase(size_t pos, size_t len)
{
	assert(pos < _size );
	if (pos+len>=_size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

 但是这样写就没错
void My_String::erase(size_t pos, size_t len)
{
	assert(pos < _size );
	if (len>=_size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

 我这里len的缺省值是npos,npos是size_t n=-1,然后它是无符号整型纯的补码,也就是111111…也就是size_t的最大值,如果在+一个数就会溢出
3.8 size_t find(char ch, size_t pos=0);
size_t My_String::find(char ch, size_t pos)
{
	for (size_t i = pos	; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;
}
3.9 size_t find(const char* sub, size_t pos = 0);
size_t My_String::find(const char* sub, size_t pos)
{
	char* p = strstr(_str + pos, sub);
	return p - _str;
}
3.10 拷贝构造函数 My_String(const My_String& str);
My_String::My_String(const My_String& str)
{
	_str = new char[str._capacity + 1];
	_size = str._size;
	_capacity = str._capacity;
	strcpy(_str, str._str);
}
3.11赋值函数 My_String& operator=(My_String s);
My_String& My_String::operator=(My_String s)
{
	if (this != &s) {
		char* temp = new char[s._capacity + 1];
		strcpy(temp, s._str);
		delete[] _str;
		_str = temp;
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}
3.12 My_String substr(size_t pos = 0, size_t len = npos);
My_String My_String::substr(size_t pos, size_t len)
{
	if (len > _size - pos)
	{
		My_String sub(_str + pos);
		return sub;
	}
	else
	{
		My_String sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += pos + i;
		}
		return sub;
	}
}
4.运算符重载
这 里只重载+=比较有意义就先重载这一个
My_String& My_String::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
My_String& My_String::operator+=(const char* str)
{
	append(str);
	return *this;
}

 
 
 
 
 加上const以后就不再报错
5.比较运算符 重载
bool My_String::operator<(const My_String& s) const
{
	return strcmp(_str,s._str) < 0;
}
bool My_String::operator>(const My_String& s) const
{
	return !(*this <= s._str);
}
bool My_String::operator<=(const My_String& s) const
{
	return *this < s || *this == s;
}
bool My_String::operator>=(const My_String& s) const
{
	return !(*this < s._str);
}
bool My_String::operator==(const My_String& s) const
{
	return strcmp(_str,s._str)==0;
}
bool My_String::operator!=(const My_String& s) const
{
	return !(*this == s._str);
}
6.流插入和提取
void My_String::clear()
{
	_str[0] = '\0';
	_size = 0;
}
ostream& operator<<(ostream& out,  My_String& string)
{
	out << string._str;
	return out;
}
istream& operator>>(istream& in, My_String& string)
{
	string.clear();
	char ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		string += ch;
		ch = in.get();
	}
	return in;
}



















