C++ String类(带你一篇文章搞定C++中的string类)
LaNzikinh篮子 2024-09-12 16:35:01 阅读 58
感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步
数据结构习题_LaNzikinh篮子的博客-CSDN博客
初阶数据结构_LaNzikinh篮子的博客-CSDN博客
收入专栏:C++_LaNzikinh篮子的博客-CSDN博客
其他专栏:c语言基础_LaNzikinh篮子的博客-CSDN博客
个人主页:LaNzikinh-CSDN博客
文章目录
前言一.auto和范围for二.sting类的常用接口三.string类的模拟实现总结
前言
我们前面对C++的泛型编程做了了解,然后我们继续来说,C++标准库里string类
一.auto和范围for
1.1迭代器
如果我们要便利一串数字,我们在C语言中遍历东西,我们都会想到利用这个循环来遍历,而在C++中,我们有一个全新的语法来遍历东西,那就是迭代器。每种数据结构都有一种对应的迭代器,迭代器一般实现为容器的嵌套类型,在容器内部提供具体的实现。但是容器不同,底层元素遍历的方式也不同,那么为什么说迭代器遍历所有容器的方式是一样的呢?那是因为迭代器提供了常用的operator!=,operator++,operator*等运算符的重载函数,把迭代容器的细节全部隐藏在这些通用的运算符重载函数里面,因此用户侧表现出来的就是,迭代器遍历所有容器的方式都是一样的,其实底层都是不一样的。这里不做过多的解释。我们来看下面的例子
<code>string s1;
string s2("hello world");
//s2[0] = 'x';
//cout << s2 << endl;
//下标遍历
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i];
}
cout << endl;
//迭代器遍历,it看作一个指针
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it;
it++;
}
第一个,我们就是正常以前C语言当中的下标遍历循环遍历,下面那一种就是我们的这个迭代器遍历。
迭代器除了正向迭代器,还有反向迭代器,反向迭代器字面意思就是从后往前遍历,迭代器一共有四种,正向iterator,反向reverse_iterator,常量迭代器const_iterator,反向常量迭代器reverse_const_iterator,这四种。
string::rever_iterator rit=s1.begin();
while(rit!=si.end())
{
rit++;
}
1.2auto和范围for
为什么要讲这么多迭代器的东西呢?就是因为auto和范围for的底层就是迭代器.
//auto,范围for,字符赋值,自动迭代,自动判断结束
// 底层就是迭代器
for (auto& ch : s2)
{
cout << ch;
}
auto会自动推到类型,s2自动赋值给ch。
二.sting类的常用接口
2.1size()和capacity()
这两个就是来看一下你这个里面的容量是多少空间是多少,size():返回字符串有效字符长度,capacity():返回空间总大小,补充:length(),也可以返回字符串有效字符长度
string s1("hello world");
cout << s1.size()<<" "<< s1.capacity();
max_size()就是容量的最大值
2.2reserve()和resize(),注意:reverse()
这两个函数是经常容易搞混的两个函数,一个是开辟空间,你给一个值,我直接给你开通那么大,还有一个就是逆置函数,可以将一串字符给逆置
reserve():为字符串预留空间
<code>void test_string2()
{
string s1("hello world");
cout << s1.size() << " " << s1.capacity();
cout << endl;
s1.reserve(100);
cout << s1.size() << " " << s1.capacity();
}
由实验可知,在string中,reserve()是不会缩容的,开了多少就是多少
注意在string中没有逆置
resize()调整字符串大小,将有效字符的个数该成n个,多出的空间用字符c填充
2.3插入删除字符函数
s.inset(插入),s.erase(删除),s.append(尾插字符串),s.push_back(尾插一个字符),operator+=(尾插入)
<code>string s("hello world");
s += "io";
cout << s << endl;
s.append("yyy");
cout << s << endl;
//下标控制
s.insert(0, "end");
cout << s << endl;
//下标控制
s.erase(6, 10);
cout << s << endl;
2.4replace(),clear(),empty()
replace()是替换字符的意思可以将字符串中的字符替换
string s2("hello world");
s2.replace(5,1,"**");
cout << s2 << endl;
通过下标控制的
empty():检测字符串释放为空串,是返回true,否则返回false,clear():清空有效字符
2.5substr(),find(),operator+
substr():在str中从pos位置开始,截取n个字符,然后将其返回
find():从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
<code>string s("hello world");
int a = s.find("h");
cout << a << endl;
注意非成员函数operator+:尽量少用,因为传值返回,导致深拷贝效率低
2.6输入输出流
他们都属于string类非成员函数
operator>>输入流,operator<<输出流
但是如果输入一个空格,就无法接收到了,该怎么办呢?
getline()获取一行字符串,特别的输入,遇到空格不会结束会继续输入数据
2.7特殊函数c_str()返回C格式字符串
他的本质是返回底层空间的指针,比如想用printf输出string,printf接收的是指针,没办法直接输出string,所以就可以用string.c str()进行输出
三.string类的模拟实现
在这里只会实现在string中重要的成员函数,要了解他们的底层逻辑,其他的会使用的可以了
我们先来看他的头文件
<code>using namespace std;
namespace lanzi
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
string(const char* str = " ")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//深拷贝
//s1(s2);
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// s2 = s1
// s1 = s1
//赋值
string& operator=(const string& s)
{
if (this != &s)
{
// s1 = s1
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
{
return _str;
}
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos = 0, size_t len = npos);
private:
//char _buff[16];
char* _str;
size_t _size;
size_t _capacity;
//static const size_t npos = -1;
static const size_t 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);
bool operator!=(const string& s1, const string& s2);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
要实现的就是这样,现在我们一一来实现
3.1构造与析构
我们先来完成最简单的构造与析构函数,注意:在string中是存在\0的,所以自己模拟实现的时候一定要注意这个细节。
初始化
用new来开辟一个空间,但是要存放\0,然后把size,capacity都赋为0
string() :_str(new char[1]{'\0'}), _size(0), _capacity(0)
{}
构造
其实和初始化是可以进行合并的,但是要注意_capacity不包含\0,所以在开空间的时候要多开一个,用来存放\0,用缺省值来实现,如果你不传东西,我就给你初始化,反之构造
string(const char* str = " ")
{
_size = strlen(str);
// _capacity不包含\0,所以在开空间的时候要多开一个,用来存放\0
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
析构
把开辟的空间全部释放即可
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
3.2赋值与拷贝构造
当String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。所以这里我们需要自己来实现的赋值和拷贝构造的深拷贝
拷贝构造
<code>//深拷贝
//s1(s2);
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
赋值:存在特殊情况,自己给自己赋值
// s2 = s1
// s1 = s1
//赋值
string& operator=(const string& s)
{
if (this != &s)
{
// s1 = s1
delete _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
3.3容易的成员函数
不做讲解直接代码
迭代器相关的成员函数
typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
clear函数,size,capacity函数,
void clear()
{
_str[0] = '\0';
_size = 0;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
[]引用函数
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
以上这些函数都是作为内联函数,在类里面实现的,因为他们经常反复调用,所以在类里用内联函数去实现,节省效率
3.4reserve()扩容函数
动态开辟一个指定空间,然后利用strcpy去实现复制
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
3.5尾插函数push_back
如果满了,就扩容
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
3.6尾插字符串append()
因为是字符串,所以和字符有区别,要注意实现
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;
}
3.7+=实现尾插
直接复用
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
3.8插入函数insert()
插入一个字符
往后移动,效率极差
void string::insert(size_t pos, char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//存在\0
int end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
插入一个字符串
先将原来的字符移动位置,然后在指定位置上插入
void string::insert(size_t pos, const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
int end = _size + len;
//注意,画图
while (end > pos+len-1)
{
_str[end] = _str[end - len];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = s[i];
}
_size += len;
}
3.9删除函数erase()
pos表示起始位置,len为要删除的长度,如果长度高于后面大小就全部删除,如果没有,就删指定大小
void string::erase(size_t pos, size_t len = npos)
{
assert(pos > _size);
if (len > _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
for (size_t i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
}
3.11查找函数find()
查找一个字符
根据所给的位置,一一遍历,找到就返回,没有就返回空
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;
}
}
查找一个字符串
直接利用函数strstr去找
size_t string::find(const char* str, size_t pos)
{
assert(pos > _size);
{
char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
//因为在string中,都是返回下标所以要减去_str
return ptr - _str;
}
}
}
3.21生成子串函数substr()
len大于剩余字符长度,更新一下len,然后赋值
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
// len大于剩余字符长度,更新一下len
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
3.31比较函数
只要实现两个,其他都可以复用
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 s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{
return !(s1 <s2);
}
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 !(s1 == s2);
}
3.41流插入提取函数
流提取,利用auto,for来遍历
ostream& operator<<(ostream& out, const string& s)
{
for (auto& ch : s)
{
out << ch;
}
return out;
}
流插入
注意:流插入遇到空格就停止接受,所以要用get()来接受空格,先创建一个buff数组,提高效率,利用get来接收字符,如果不是空和/0的话,就把它放到数组里,如果数组满了的话,就直接结束,将buff数组的值全部放进string的类中,没有的话就继续获取数据
//很难
istream& operator>>(istream& in, string& s)
{
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\0')
{
buff[i++] = ch;
if (i==N-1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
总结
string类是C++标准库里的一个类,为什么学习string类,因为C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。其实在标准库还有很多这里内容,下次在一一讲解
上一篇: 【Java】已解决:org.springframework.beans.factory.BeanCurrentlyInCreationException Bean当前正在创建中异常
下一篇: 在VScode下配置C/C++环境(tasks.json、launch.json、c_cpp_properties.json)
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。