【C++初阶】string类
🥕个人主页:开敲🍉
🔥所属专栏:C++🥭
🌼文章目录🌼
1. 为什么学习string类?
1.1 C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列
的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问。
1.2 实际中
在我们日常的刷题中,有关字符串的题目基本上是以string的形式出现的,并且在日常的生活当中,为了简单、方便快捷,基本上也都是使用的string,很少有人使用C标准库中的字符串操作函数。
2. 标准库中的string类
2.1 string类
string - C++ Reference (cplusplus.com)
这里面有关于string类的详细说明,包括它的每个接口的功能以及接口的使用环境等。其中比较重要的接口有:operator=、iterators有关的begin、end的所有接口(自己实现时子需要实现begin、end即可,后面的rbegin和rend等理解即可)、size、resize、capacity、reserve、clear、empty、operator[]、operator+=、append、push_back、insert、erase、swap、c_str、find、find_first_of
2.2 auto和范围for
auto关键字:
在这里补充两个C++11的小语法,方便我们后面的学习
① 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型
指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期
推导而得,也就是说,auto能够自动识别对象的类型。
② 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
③ 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
④ auto不能作为函数的参数,可以作为返回值,但是建议谨慎使用
⑤ auto不能直接用来声明数组
看到这里你可能觉得auto没什么作用,我知道我想要的是什么类型的我为什么还要它自动识别。别着急,后面你就会明白auto是有多好用。
范围for:
① 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
for(auto+变量 : 迭代范围)
{
//循环体
}
② 范围for可以作用到数组和容器对象上进行遍历
③ 范围for看似非常智能很厉害,实际上底层就是迭代器(迭代器是什么后面会讲,此处记住即可)
2.3 string类常用接口说明
1. string的常见构造函数:
2. string类对象的容量(Capacity)操作:
size(重点):返回字符串有效字符长度
length:返回字符串有效字符长度
capacity:返回所开空间总大小
empty(重点):检查字符串是否为空
clear(重点):仅删除数据,而不销毁空间
reserve(重点):为字符串预留一块空间,可以数据量小时可以省去开辟空间的消耗
rsize(重点):将有效的字符减为n(自己传)个,如果是增加,则多加的字符改为c(自己传)
注:
① size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接
口保持一致,一般情况下基本都是用size()。
② clear()只是将string中有效字符清空,不改变底层空间大小。
③ resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不
同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char
c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数
增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
④ reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参
数小于string的底层空间总大小时,reserver不会改变容量大小(Linux环境下会改变,但不会影响数据的存储)。
3. string类对象的访问及遍历操作:
operator[](重点):返回pos位置字符的引用(方便修改),const string类调用
begin+end(重点):begin获取字符串开头第一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
//后面还有rbegin+rend、crbegin+crend、cbegin+cend等迭代器,这里不一一赘述
4. string类对象的修改操作:
push_back(重点):在字符串后尾插一个字符
append(重点):在字符串后尾插一个字符串
operator+=(重点):重载+=运算符,可以完成尾插操作
c_str(重点):返回字符串地址
find+npos(重点):从字符串pos位置开始往后查找所要字符,并返回其所在位置。如果遍历完字符串也找不到,则返回npos
rfind:从字符串pos位置开始往前查找所要字符,并返回其所在位置。如果找不到,则返回npos
substr:从字符串pos位置开始截取n个字符并返回
注:
① 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差
不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可
以连接字符串。
② 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留
好,这样可以节剩开辟空间的效率消耗。
5. string类非成员函数
operator+:尽量少用,因为传值返回,导致深拷贝,导致效率降低
operator<<(重点):输入运算符重载,用于方便输出string字符串中的内容
operator>>(重点):输入运算符重载
getline(重点):获取一行字符串,遇到'\0'才会停止获取,在OJ的字符串题中很常用
relational operators(重点):大小比较,重载了各种类型的比较运算符
3. string类的模拟实现
3.1 经典的string类问题
3.1.1 浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致
多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该
资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
例如:一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我夺,玩具损坏。
3.2.2 深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给
出。一般情况都是按照深拷贝方式提供。
3.2 string类模拟实现
//string.h头文件的声明
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace gjk
{
class string
{
public:typedef char* iterator;
string(const char* s = "")
{
_str = new char[strlen(s) + 1];
strcpy(_str, s);
_size = strlen(s);
_capacity = _size;
}char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}size_t size()
{
return _size;
}const size_t size() const
{
return _size;
}size_t capacity()
{
return _capacity;
}const size_t capacity() const
{
return _capacity;
}
char* c_str()
{
return _str;
}//迭代器
iterator begin()
{
return _str;
}const iterator begin() const
{
return _str;
}iterator end()
{
return _str + _size;
}const iterator end() const
{
return _str + _size;
}~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}//扩容
void dilatancy(int newcapacity);
//预留空间
void reserve(size_t n);
//改变有效字符数
void rsize(size_t n, char ch = '\0');
//尾插
void push_back(char ch);
//尾插字符串
void append(const char* s);
//指定插入字符
void insert(size_t pos, char ch);
//指定删除n字符
void erase(size_t pos,size_t n = npos);
//指定插入字符串
void insert(size_t pos, const char* s);
//清除数据
void clear();
//判空
bool empty();//+=一个字符运算符重载
string& operator+=(char ch);
//+=字符串运算符重载
string& operator+=(const char* s);
//比较运算符重载
bool operator==(const string& s);bool operator>(const string& s);
bool operator<(const string& s);
bool operator>=(const string& s);
bool operator<=(const string& s);
bool operator!=(const string& s);
//找到字符第一次出现的位置
int find(char ch,size_t pos = 0);//找到字符串第一次出现的位置
int find(const char* s, size_t pos = 0);private:
char* _str;
size_t _size;
size_t _capacity;const static size_t npos = -1;
};
}//string.cpp文件的定义
#define _CRT_SECURE_NO_WARNINGS 1
#include "string.h"
namespace gjk
{
//扩容
void string::dilatancy(int newcapacity)
{
char* tmp = new char[newcapacity + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = newcapacity;
}
//预留空间
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}//改变有效字符数
void string::rsize(size_t n, char ch)
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
while (_size != n)
{
if (_size == _capacity)
dilatancy(_capacity == 0 ? 4 : 2 * _capacity);
_str[_size++] = ch;
}
_str[_size] = '\0';
}
}//尾插
void string::push_back(char ch)
{
if (_size == _capacity)
dilatancy(_capacity == 0 ? 4 : 2 * _capacity);
_str[_size++] = ch;
_str[_size] = '\0';
}
//尾插字符串
void string::append(const char* s)
{
int len = strlen(s);
if (_size + strlen(s) > _capacity)
dilatancy(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
strcpy(_str + _size, s);
_size += len;
}
//指定插入字符
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
dilatancy(_capacity == 0 ? 4 : 2 * _capacity);
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
//指定删除n个字符
void string::erase(size_t pos,size_t n)
{
assert(pos < _size);
if (n >= _size - pos||n == npos)
{
_str[pos] = '\0';
_size -= (_size - pos);
}
else
{
size_t end = pos + n;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= n;
}
}//指定插入字符串
void string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
int len = strlen(s);
if (_size + strlen(s) > _capacity)
dilatancy(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
size_t end = _size + len + 1;
while (end - len > pos)
{
_str[end] = _str[end - len - 1];
end--;
}
memcpy(_str + pos, s, len);
_size += len;
}
//清除数据
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
//判空
bool string::empty()
{
return _size == 0;
}
//+=一个字符运算符重载
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
//+=字符串运算符重载
string& string::operator+=(const char* s)
{
append(s);
return *this;
}
//比较运算符重载
bool string::operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}bool string::operator>(const string& s)
{
return strcmp(_str, s._str) > 0;
}
bool string::operator<(const string& s)
{
return !(*this == s || *this > s);
}
bool string::operator>=(const string& s)
{
return !(*this < s);
}
bool string::operator<=(const string& s)
{
return !(*this > s);
}
bool string::operator!=(const string& s)
{
return !(*this == s);
}//找到字符第一次出现的位置
int string::find(char ch,size_t pos)
{
assert(pos < _size);
while (_str[pos] != ch && pos < _size)
{
pos++;
}
if (pos == _size)
return -1;
return pos;
}
//找到字符串第一次出现的位置
int string::find(const char* s, size_t pos)
{
assert(pos < _size);
int len = strlen(s);
while (_str[pos] != s[0] && pos < _size)
{
pos++;
}
if (pos == _size||_size-pos < len)
return -1;
int ret = memcmp(_str + pos, s, len);
if (!ret)
return pos;
return -1;
}
}
创作不易,点个赞呗,蟹蟹啦~