当前位置:首页 » 《资源分享》 » 正文

C++ String类(带你一篇文章搞定C++中的string类)

0 人参与  2024年11月10日 08:42  分类 : 《资源分享》  评论

点击全文阅读


感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步

数据结构习题_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*等运算符的重载函数,把迭代容器的细节全部隐藏在这些通用的运算符重载函数里面,因此用户侧表现出来的就是,迭代器遍历所有容器的方式都是一样的,其实底层都是不一样的。这里不做过多的解释。我们来看下面的例子

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():为字符串预留空间

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+=(尾插入)

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,返回该字符在字符串中的位置

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中重要的成员函数,要了解他们的底层逻辑,其他的会使用的可以了

我们先来看他的头文件

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 = s1delete _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共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。所以这里我们需要自己来实现的赋值和拷贝构造的深拷贝

拷贝构造

//深拷贝//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 = s1delete _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);}//存在\0int 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中,都是返回下标所以要减去_strreturn ptr - _str;}}}

3.21生成子串函数substr()

 len大于剩余字符长度,更新一下len,然后赋值

string string::substr(size_t pos, size_t len){assert(pos < _size);// len大于剩余字符长度,更新一下lenif (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的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。其实在标准库还有很多这里内容,下次在一一讲解


点击全文阅读


本文链接:http://zhangshiyu.com/post/184157.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1