C++——string(下)

一、string的模拟实现

核心思想:

类虽然不支持多个文件下的声明和定义分离,但是我把它们放在不同的文件夹下,使用同一片命名空间域(不同的文件下命名空间域会进行合并)就解决了这个问题。

类对于短小的代码默认是内联的。

迭代器可以是typedef(原生指针)也可以是内部类。

流提取、流插入和比较运算符一般都定义成全局的(类外)。

实现:

string.h

cpp 复制代码
#include<string.h>
#include<iostream>
#include<assert.h>
namespace my_code {//避免和库里实现的string发生冲突,定义一个命名空间域
	class string {
	public:
		string(const char* str = "") {//缺省的构造函数(""表示\0的意思,无参的构造和有参的构造合并)
			assert(str);//防止传空指针
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size+1];//多申请一个空间用于存放\0
			strcpy(_str, str);
		}
		~string() {//析构函数
			_size = _capacity = 0;
			delete[] _str;
			_str = nullptr;
		}
		string(string& s) {//拷贝构造
			_str = new char[s._capacity + 1];
			//可以直接写s._capacity而不用写是s.capacity()是因为类内允许不同对象互相访问私有成员
			strcpy(_str,s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		string& operator=(string& s) {//赋值运算符重载(把原对象搞成和传入对象一样的内容)
			if (this == &s)//处理自己给自己赋值的情况
				return *this;
			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 {//允许外部只读_str
			return _str;
		}
		size_t size() const{//有效字符的个数
			return _size;
		}
		char& operator[](size_t pos){//返回引用是为了让你通过[]修改字符(给普通对象用,可读可写)
			assert(pos < _size);//防止越界访问
			return _str[pos];//_str表示首元素的地址
		}
		const char& operator[](size_t pos)const {//给const对象用(只读)
			assert(pos < _size);//后面必须加const,this指针的类型不同才能构成重载函数
			return _str[pos];
		}
		void clear() {//完成clear的功能
			_str[0] = '\0';
			_size = 0;
		}
		typedef char* iterator;//这里模拟实现的迭代器就是原始指针
		iterator begin() {//迭代器的begin
			return _str;//数组名就是首元素的地址
		}
		iterator end() {
			return _str + _size;//+_size而不是+_size-1(end指向的是最后一个有效字符的下一个位置)
		}

		typedef const char* const_iterator;//const迭代器
		const_iterator cbegin() const{
			return _str;
		}
		const_iterator cend() const{
			return _str + _size;
		}
		size_t capacity()const {
			return _capacity;
		}

		void reserve(size_t n);//扩容
		void push_back(char ch);//尾插一个字符
		string& operator += (char ch);//重载+=(尾插一个字符)
		void append(const char* str);//尾插一个字符串
		string& operator+=(const char* str);//重载+=(尾插一个字符串)
		void insert(size_t pos, char ch);//在pos位置插入单个字符
		void insert(size_t pos, const char* str);//在pos位置插入单个字符串
		void erase(size_t pos,size_t len = npos);//模拟erase的功能(删除从pos开始往后的len个字符)
		size_t find(char ch, size_t pos = 0);//从pos位置开始查找某个字符,找到了返回下标,找不到返回npos
		size_t find(const char* str, size_t pos = 0);//从pos位置开始查找某个字符串的下标
		string substr(size_t pos = 0,size_t len = npos);//从pos个位置取len个字符构建一个新的对象		
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		static const size_t npos;//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);
	std::ostream& operator<<(std::ostream& out,string& s);
	std::istream& operator>>(std::istream& in, string& s);
}

string.cpp

cpp 复制代码
#include"string.h"
namespace my_code{
	const size_t string::npos = -1;
	void string::reserve(size_t n) {//扩容
		if (n > _capacity) {
			char* tmp = new char[n + 1];//多扩一个位置存放\0
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	void string::push_back(char ch) {//尾插一个字符
		if (_capacity == _size) {
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}
	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) {
			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) {//在pos位置插入单个字符
		assert(pos <= _size);//防止越界(等于不越界,等于是为了尾插)
		if (_capacity == _size) {
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		size_t end = _size + 1;//end表示的是\0需要挪动的位置(插入后size的真实大小)
		while (end > pos) {
		//挪动数据(循环终止条件:明确最后一次挪动的位置是pos+1的位置)
			_str[end] = _str[end-1];//先写出第一次挪动的情况再改写
			end--;
		}
		_str[pos] = ch;//插入数据
		_size++;
	}
	void string::insert(size_t pos, const char* str) {//在pos位置插入单个字符串
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity) {
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		size_t end = _size + len;
		_str[_size + len] = _str[_size];
		while (end > pos + len - 1) {//循环终止条件
		//明确最后一个挪动的位置是插入字符串后的下一个位置(pos + l的位置)
			_str[end] = _str[end-len];////先写出第一次挪动的情况再改写
			end--;
		}
		//strncpy(_str + pos, str, len);//不能用strcpy它会拷贝des字符串的\0截断数据
		//_str[pos] = str[0];//开始条件
		//_str[pos + len-1] = str[len-1];//结束条件
		for (int i = 0; i < len; i++) {
			_str[pos + i] = str[i];
		}
		_size += len;
	}
	void string::erase(size_t pos, size_t len) {//删除从pos个位置往后的len个字符
		assert(pos < _size);
		// a b c d e
		if (len > _size - pos) {
			_str[pos] = '\0';
			_size = pos;
		}
		else {
			//_str[pos] = _str[pos + len];//第一次挪动就是把删除后的下一个位置挪到pos的位置
			//将pos+len位置下的数据挪到pos位置下
			//_str[_size - len] = _str[_size];//最后一次挪动数据就是将\0的位置挪到有效字符的下一位
			//\0的位置就是_size,删除len个字符,有效字符就变为了_size-len
			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) {//查找某个字符的下标
		assert(pos < _size);
		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) {//从pos位置开始查找某个字符串的下标
		assert(pos < _size);
		char* tmp = strstr(_str + pos,str);
		if (tmp == nullptr) {
			return npos;
		}
		return tmp - _str;//减出来是下标(数组是连续存储的,地址从低向高走)
	}
	string string::substr(size_t pos, size_t len) {
		assert(pos < _size);
		//a b c d e f
		if (len >= _size - pos) {//_size - pos表示的是剩下的字符个数
			len = _size - pos;
		}
		string sub;
		reserve(len);
		for (int i = 0; i < len; i++) {//i从0开始计数,len从1开始计数
			sub += _str[pos + i];
		}
		return sub;
	}
	bool operator>(const string& s1, const string& s2) {
		return strcmp(s1.c_str(), s2.c_str()) > 0;
		//strcmp返回的是int这里需要的是bool值,所以要比较一下
	}
	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 s2 > s1;
	}
	bool operator>=(const string& s1, const string& s2) {
		return (s1 > s2) || (s1 == s2);
	}
	bool operator<=(const string& s1, const string& s2) {
		return (s2 > s1) || (s1 == s2);
	}
	std::ostream& operator<<(std::ostream& out,string& s) {//重载流提取
		for (auto ch : s) {
			out << ch;
		}
		return out;
	}
	std::istream& operator>>(std::istream& in, string& s) {//重载流插入
		s.clear();
		char ch;
		ch = in.get();//in.get()表示读取一个字符然后返回,用ch存读取到的数据
		while (ch != ' ' && ch != '\0') {
			s += ch;
			ch = in.get();
		}
		return in;
	}
	//std::istream& operator>>(std::istream& in, string& s)//重载流插入的优化(解决屏藩扩容的问题)
	// 思路:开一个buffer空间,先把ch存到buffer里,最后将buffer给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;
	//		}
	//		//in >> ch;
	//		ch = in.get();
	//	}
	//	if (i > 0)
	//	{
	//		buff[i] = '\0';
	//		s += buff;
	//	}
	//	return in;
	//}
}

二、编码

(1)基本概念

编码:符号和值的映射关系。例如在ASCII编码表中,a的值就是97。

(2)ASCII编码表

ascii编码表就是常用的英美符号被编成了表,能够让计算机存储英美符号。(计算机只存储二进制)

cpp 复制代码
int main() {
	char ch = 'a';
	ch = 'a'+ 1;//ch变为b(打印的过程是查编码表的过程)
	cout << ch << endl;//'a' + 1此时编码变为98计算机通过查找ASCII表得知98是b
}

(3)统一码(Unicode)

①基本概念

Unicode就是用来表示各个国家文字符号的编码表。
UTF-8、UTF-16、UTF-32 是这张表在计算机里具体怎么存成二进制的方案。

②UTF-8、UTF-16、UTF-32

UTF-8:只能一字节一字节的变长。最多4字节。(1/2/3/4字节)windows下的文件如text、docx、exe都是通过utf-8转编码存储的。
UTF-16:2字节2字节的变长,最多4字节。(2/4字节)
UTF-32:固定4字节。

正因为有了这几种格式所以C++11引入了u16string、u32string。这也是string被写成了类模板而不是类的原因。

cpp 复制代码
int main() {
	char str[] = "奶龙";
	cout << str << endl;
	str[1] += 50;
	cout << str << endl;
	str[3]++;
	cout << str << endl;
}

(4)国标(GBK)

GBK是中国自研的编码用来表示中日韩三国的汉字,其中还包括中国部分少数民族文字,繁体字。

二、OJ题

(1)仅反转字母

题目链接:917. 仅仅反转字母 - 力扣(LeetCode)

思路:创建左右两个指针,保证左右指针都是字母的情况下交换。且右指针的位置不能超过左指针。

解答:

cpp 复制代码
class Solution {
public:
    string reverseOnlyLetters(string s) {
       int left = 0,right = s.size() - 1;
       while(left < right){
        while(left < right && !isalpha(s[left])){//加left < right是为了防止全是非字母的情况
            left++;
        }
        while(left < right && !isalpha(s[right])){//加left < right是为了防止right超过了left的位置
            right--;
        }
        swap(s[left++],s[right--]);
       } 
       return s;
    }
};

(2)字符串中的第一个唯一字符

题目链接:387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

思路:创建一个数组用于统计小写字母'a'-'z'每个字符出现的次数。根据次数进行返回处理。

解答:

cpp 复制代码
class Solution {
public:
    int firstUniqChar(string s) {
        int count[26] = {0};//创建一个数组,用来统计每个字符出现的次数
        for(auto ch: s){//统计次数
            count[ch-'a']++;//count[ch-'a']表示的是该字符在count中的位置,++表示次数+1
        }
        for(size_t i = 0;i  <s.size();i++){//找出出现的第一个字符
            if(count[s[i] - 'a'] == 1)//最终要返回s的下标,所以要通过s去定位该字符在count中的位置,然后进行判断
            return i;
        }
        return -1;
    }
};

相关推荐
学习永无止境@2 小时前
灰度图像中值滤波算法实现
图像处理·算法·计算机视觉
ysa0510302 小时前
斐波那契上斐波那契【矩阵快速幂】
数据结构·c++·笔记·算法
CHANG_THE_WORLD2 小时前
模拟解析:宽度数组 `[1,2,1]`,10个条目的 XRef 流
java·前端·算法
lixinnnn.2 小时前
多源BFS:矩阵距离
算法·宽度优先
CHANG_THE_WORLD2 小时前
PDFium 处理通用 `W` 数组的方式
数据结构·算法
lixinnnn.3 小时前
多源BFS:刺杀大使
算法·宽度优先
AI成长日志3 小时前
【笔面试算法学习专栏】堆与优先队列实战:力扣hot100之215.数组中的第K个最大元素、347.前K个高频元素
学习·算法·leetcode
北顾笙9803 小时前
day18-数据结构力扣
数据结构·算法·leetcode
阿Y加油吧3 小时前
LeetCode 中等难度 | 回溯法进阶题解:单词搜索 & 分割回文串
算法·leetcode·职场和发展