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