一、string模拟实现
1.1 创建类
class string
{
public:
private:
char* _str;
int _size;
int _capacity;
};
1.2 构造与析构
为什么这里用缺省值const char* str = ""----->如果在main函数中使用string s1();这种方式,创建一个什么也不初始化的空类,不使用缺省值的话编译不通过还得继续在声明定义一个string()这种形式,使用缺省值的话,则可以传递一个""空字符传里面还包含了'\0'
总的来说就是少些了一个string()这种形式的构造
声明
string(const char* str = "");
~string();
定义
string::string(const char* str)
//提高效率->只计算一次字符长度
:_size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
1.3 迭代器封装
这里采用最简单的方式模拟string的迭代器,实际上迭代器很复杂
声明
typedef char* iterator;
iterator begin();
iterator end();
定义
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
声明
此处是 const string类型的迭代器
typedef const char* const_iterator;
const_iterator begin() const;
const_iterator end() const;
为什么这个声明只能被const string类型使用?如果没有声明定义这种形式,const string s1使用 iterator begin(); 会导致 权限放大
为什么导致权限放大? iterator begin();隐藏的形参是 string* this,传递的是const的类型导致权限放大
为什么const_iterator it1 = s1.begin(); 时候可以it1++? 因为const string* this 限制的是*this而不是this。
定义
string::const_iterator string::begin() const
{
return _str;
}
string::const_iterator string::end() const
{
return _str + _size;
}
1.4 遍历string
我们已经写好迭代器了,可以进行迭代器的循环
在main函数中,我们先定义一个string类,发生遍历循环,这里使用的是iterator封装的类型
bit::string s2("hello world");
bit::string::iterator it2 = s2.begin();
while (it2 != s2.end())
{
cout << *it2 << " ";
it2++;
}
以上代码遍历成功,我们再来使用此代码遍历一下
bit::string s2("hello world");
bit::string::iterator it2 = s2.begin();
for (auto e : s2)
{
cout << e << " ";
}
依旧遍历成功,但是注意的是这里如果把下面的代码
iterator begin();
iterator end();
改成这样的代码
iterator Begin();
iterator end();
上面的for( auto e : s2)就编译不成功,因为此循环底层是迭代器,接受的是begin()
一个const string类型的遍历则是这种方式,使用的是const_iterator封装的类型
const bit::string s1("hello");
bit::string::const_iterator it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
it1++;
}
使用此代码也可以发生遍历
for (auto e : s1)
{
cout << e << " ";
}
还有一种遍历的形式没有写这里需要引入operator[]
声明
char& operator[](size_t pos);
const char& operator[](size_t pos) const;
定义
char& string::operator[](size_t pos)
{
assert(pos<_size);
return _str[pos];
}
const char& string::operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
为什么string类型和const string类型不能使用一个还分别声明 定义了这两种形式只写char& operator[](size_t pos);一个行不行?
不行,如果写了const string类型传不过去,因为形参是string *this依旧是权限放大的问题,用到了成员变量_str
为什么是char&?
因为用的是引用,相当于别名,可以对里面的值进行修改
bit::string s2("hello world");
bit::string::iterator it2 = s2.begin();
for (int i = 0; i < s2.size(); i++)
{
s2[i] = 'a';
cout << s2[i] << " ";
}
1.5 修改数据
提前开辟好空间
声明 reserve
void reserve(size_t n);
定义 用途开空间
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//n + 1中的1是给\0开的空间
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
声明 push_back,append
void push_back(char ch);
void append(const char* str);
定义 push_back尾插字符,append尾插字符串
void string::push_back(char ch)
{
if (_size == _capacity) {
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
_str[_size + 1] = '\0';
++_size;
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
// strcat(_str, str);
strcpy(_str + _size, str);
_size += len;
}
声明 insert,erase
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
定义 insert可以在某个位置插字符
void 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;
}
如果pos = 0的话
为什么会报错?
我们先看这样的图
为什么最后end为0后再一次循环变成这么大?不应该变成-1吗
因为end的类型是size_t类型
http://t.csdnimg.cn/6uIZT这篇文章的练习1的知识点有讲解
那我们如何修改?
把size_t end = _size;改成int end = _size;可以吗?我们试试
我们调试的过程中发现end=-1的时候还进入循环体,原因还是在上面的那篇文章,end与pos的类型不一样,会发生隐式类型转换,有符号会转换成无符号
怎么才能把无符号的改成有符号的呢?
我们把pos强制类型转换int,这样代码就能正常运行了
int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
除了上面的方法还有其他办法吗?非得强制类型转换吗?
我们每次都是因为最后的时候end = 0的时候 end再--,要么变成一个很大的数,要么变成-1底层进行无符号转换了,如果我们让end = 0 的时候就结束
之前是下图的方式
现在把end挪动新_size的最后一个位置,循环体改成_str[end] = _str[end - 1];使判断条件改成end>pos,最后end是不是为0的时候结束循环
代码形式
void string::insert(size_t pos, char ch)
{
if (_size == _capacity) {
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
定义 insert在某个位置插入字符串
这种定义方式和插入某个字符的问题相差不大,只是复杂了一点
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
size_t end = _size + len;
//while (end >= (int)pos) //使用强转这种方式也可以
//{
// _str[end + len] = _str[end];
// --end;
//}
while (end > pos+len-1)
{
_str[end] = _str[end - len];
--end;
}
memcpy(_str + pos, str, len);
_size += len;
}
为什么 循环条件是end > pos + len -1?
根据数学中的各个变量推算得出一共要挪动的次数
定义erase,消除某段的数
void erase(size_t pos, size_t len = npos);
在这里要提的是npos
这里在类里面声明,因为有多个包含.h的.cpp文件,所以不能在.h类外面定义,在某个定义的.cpp中定义npos,const size_t string::npos = -1;
因为静态成员变量类似于全局变量---->为什么类似于全局变量?---->全局变量与静态成员变量都在静态区。且全局变量被多个文件包含发生重定义。在实际当中两者在.h中同时声明定义,被多个.cpp文件包含导致发生了重定义,而且他们的解决措施一样就是在.h声明,只在一个.cpp中定义。
http://t.csdnimg.cn/yh88X如果还没有理解建议看看这篇文章中的static
private:
char* _str;
int _size;
int _capacity;
const static size_t npos;//类里面声明,类外面定义
稍微了解一下
特例:只有整形才有这种特例---既是声明又是定义
const static size_t npos = -1;
不加const就不行,发生报错
static size_t npos = -1;
不是整形也不行
const static double zz = 2.22;
声明 operator+=
string operator+=(char ch);
string operator+=(const char* ch);
定义 operator+=
string string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string string::operator+=(const char* ch)
{
append(ch);
return *this;
}
首先判断,这里两者返回的都是string行不行?
我们返回的是 *this ----> 而*this 发生临时对象的拷贝构造,创建了一个与*this一样,但是这里注意的是我们这里没有写拷贝构造,发生的是浅拷贝
临时对象与this指向的空间一样,发生析构的时候,析构了两次,所以不行
然后我们应该如何修改错误,使用string&返回可不可以,可以,为什么?在这里记住引用返回不会生成临时对象,返回的是*this的别名,另外*this在mian函数的栈不会发生析构,所以可以。
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* ch)
{
append(ch);
return *this;
}
http://t.csdnimg.cn/MJoME不理解可以看这篇文章的引用返回和传值返回的区别,以及了解上面的赋值重载
另外的方法就是,写一个拷贝构造---进行深拷贝
string::string(const string& s)
:_size(strlen(s._str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
关于拷贝构造为什么使用必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。至于为什么看下面文章
声明 operator=
如果不声明当你定义一个 string s1("hello") string s2("hehehe")
s1 = s2 ,无法进行深拷贝
string& operator=(const string& s);
定义 operator=
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
声明 swap
void swap(string& s);
定义 swap
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
1.6 查找字符
声明 find
size_t find(char ch, size_t pos = 0);
size_t find(const char* sub, size_t pos = 0);
定义 find,查找字符
size_t string::find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i - pos + 1;
}
}
return npos;
}
size_t string::find(const char* sub, size_t pos)
{
const char* p = strstr(_str + pos, sub);
return p - _str;
}
这里我们使用了strstr库函数
从str1中查找str2
http://t.csdnimg.cn/2nIRi
1.7 取子串
声明 substr
string substr(size_t pos = 0, size_t len = npos);
定义 substr从哪个位置开始取多少长度
string string::substr(size_t pos, size_t len)
{
//len大于后面剩余字符,有多少取多少
if (len > _size - pos)
{
string sub(_str + pos);
return sub;
}
else
{
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
}
1.8 比较大小
定义
bool operator<(const string& s) const;
bool operator>(const string& s) const;
bool operator<=(const string& s) const;
bool operator>=(const string& s) const;
bool operator==(const string& s) const;
声明 这里使用strcmp 来进行大小的判断,这里还使用复用了
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator>(const string& s) const
{
return !(*this <= s);
}
bool string::operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool string::operator>=(const string& s) const
{
return !(*this < s);
}
bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
1.9 流插入流提取
声明
istream& operator>> (istream& is, string& str);
ostream& operator<< (ostream& os, const string& str);
定义
istream& operator>> (istream& is, string& str)
{
str.clear();
char ch;
ch = is.get();
char buff[32];//缓冲数组
size_t i = 0;
while (ch != ' ' && ch != '\n')//读字符读不到空格
{
buff[i++] = ch;
if (i == 31)
{
buff[32] = '\0';
str += buff;
i = 0;
}
ch = is.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return is;
}
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
cin会跳过空白字符,比如空格和换行,而get()从输入缓冲区读取单个字符时不忽略分隔符
1.10 简便写法
传统写法的拷贝
string::string(const string& s)
:_size(strlen(s._str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
老老实实的开辟一个空间,把size,capacity都赋值过去,然后把里面的内容赋值过来
现代写法---->让编译器去干活
我创建一个变量tmp去拷贝构造,然后与*this发生交换
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
传统写法的赋值
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
现代写法
string& string::operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}
创建一个变量,和s一模一样,然后让tmp与this指向的方向一换,最后让this析构,过河拆桥
二、string-练习题
2.1 字符串相加 ![](https://i-blog.csdnimg.cn/direct/f21662769a8c45e9a0026259647e4378.png)
核心思想采用高位补0 。
先转换为整数,但是整数有最大限制,只能一个字符一个字符的转换,利用数学思想进行加法运算,位数不够采用高位补0。
class Solution {
public:
string addStrings(string num1, string num2) {
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int next = 0;
string ans;
while(end1 >= 0 || end2 >= 0)
{
//用char减去‘0’,得到int值,若该位不存在则为0
int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
//相加
int sum = val1 + val2 + next;
next = sum / 10;
sum = sum % 10;
//将该位相加的结果存到ans字符串中
ans += ('0'+sum);
//更新end1、end2
--end1;
--end2;
}
if(next == 1)
{
ans += ('1');
}
reverse(ans.begin(), ans.end());
return ans;
}
};
2.2 字符串中的第一个唯一字符
这里采用的是先计算次数,再找次数为1的字符
class Solution {
public:
int firstUniqChar(string s) {
int cout[26] = {0};
for(int i = 0; i < s.size(); i++)
{
cout[s[i]-'a']++;
}
for(int i = 0;i < s.size(); i++)
{
if(cout[s[i]-'a'] == 1)
{
return i;
}
}
return -1;
}
};
2.3 验证回文串
class Solution {
public:
bool isPalindrome(string s) {
//先统一
string s2;
for(int i = 0; i < s.size(); i++)
{
//只要大小写,数字
if(('a' <= s[i] && s[i] <= 'z') || ('A' <= s[i] && s[i] <= 'Z') || ('0' <= s[i] && s[i] <= '9') )
{
//把大写转变成小写
if('A' <= s[i] && s[i] <= 'Z')
{
s[i] += 32;
}
//小写字母加到新字符串
s2 += s[i];
}
}
cout<<s2<<endl;
int left = 0;
int right = s2.size()-1;
while(left<=right)
{
if(s2[left] != s2[right])
{
return false;
}
left++;
right--;
}
return true;
}
};
在这里学习几个库函数
检查是否是字符数字,字母
检查是不是字母
检测是不是字符数字
把大写字母转换成小写字母
把小写字母转换成大写字母
这里是判断是不是小写,大写,字母的
上面代码(把大小写字符数字保留下来)~可以转换成这样
for(char ch: s)
{
if(isalnum(ch))
{
s2 += tolower(ch);
}
}
2.4 字符串相乘![](https://i-blog.csdnimg.cn/direct/446b4b979d7f412ead30dd3d930cc17f.png)
思路:
拆分,相加
class Solution {
public:
string multiply(string num1, string num2) {
if (num1 == "0" || num2 == "0") {
return "0";
}
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int next = 0;
string num3;
int cont = 0;
string string_sum = "0";
for(int i = end1; i >= 0 ; i--)
{
int _num1 = num1[i] - '0';
for(int j = end2; j >= 0; j--)
{
int _num2 = num2[j] - '0';
int sum = _num1 * _num2 + next;
num3.push_back(sum%10 + '0');
next = sum/10;
}
if(next>0)
{
num3.push_back(next+'0');
}
reverse(num3.begin(),num3.end());
cout<<num3<<endl;
for(int i = 0;i<cont;i++)
{
num3 += '0';
}
string_sum = Add(string_sum,num3);
cont++;
num3.clear();
next = 0;
}
return string_sum;
}
string Add(string num1,string num2)
{
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int next = 0;
string ans;
while(end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
int sum = val1 + val2 + next;
next = sum / 10;
sum = sum % 10;
ans += ('0'+sum);
--end1;
--end2;
}
if(next == 1)
{
ans += ('1');
}
reverse(ans.begin(), ans.end());
return ans;
}
};