当前位置: 首页 > news >正文

c++初阶知识——string类详解

 

目录

 

前言:

1.标准库中的string类

1.1 auto和范围for

auto

 范围for

1.2 string类常用接口说明

1.string类对象的常见构造

1.3 string类对象的访问及遍历操作

1.4. string类对象的修改操作 

 1.5 string类非成员函数

2.string类的模拟实现 

2.1 经典的string类问题 

2.2 浅拷贝 

2.3 深拷贝 

2.4 string类实现 

3.写时拷贝 


前言:

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列
的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问。

1.标准库中的string类

在使用string类时,必须包含#include头文件以及using namespace std;

1.1 auto和范围for

auto

(1)在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期
推导而得

(2)用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

(3)当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

(4)auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

(5)auto不能直接用来声明数组

#include <map>
using namespace std;
int main()
{
std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
"橙子" }, {"pear","梨"} };
// auto的用武之地
//std::map<std::string, std::string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此
C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围
内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象上进行遍历
范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)
1. string类对象的常见构造
return 0;
}

 范围for

(1)对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

(2)范围for可以作用到数组和容器对象上进行遍历

(3)范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

示例:

#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{int array[] = { 1, 2, 3, 4, 5 };// C++98的遍历for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){array[i] *= 2;}for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){cout << array[i] << endl;}// C++11的遍历for (auto& e : array)e *= 2;for (auto e : array)cout << e << " " << endl;string str("hello world");for (auto ch : str){cout << ch << " ";}cout << endl;
return 0;
}

1.2 string类常用接口说明

1.string类对象的常见构造

注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接
口保持一致,一般情况下基本都是用size()。


2. clear()只是将string中有效字符清空,不改变底层空间大小。


3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不
同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char
c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数
增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。


4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参
数小于string的底层空间总大小时,reserver不会改变容量大小。 

1.3 string类对象的访问及遍历操作

1.4. string类对象的修改操作 

注意
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差
不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可
以连接字符串。


2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留
好。 

 1.5 string类非成员函数

2.string类的模拟实现 

2.1 经典的string类问题 

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让
学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析
构函数。大家看下以下string类的实现是否有问题?

// 为了和标准库区分,此处使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void TestString()
{
String s1("hello bit!!!");
String s2(s1);
}

 

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认
的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。 

2.2 浅拷贝 

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致
多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该
资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一
不想分享就你争我夺,玩具损坏。

所以可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。 

2.3 深拷贝 

 如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给
出。一般情况都是按照深拷贝方式提供。

 

 

2.4 string类实现 

 能否写好string反映出我们对类和对象知识的理解是否深刻,这一块知识如果理解得不够深刻,我们的c++程序就会经常出现此类问题。为了方便管理,我们将string的实现分为3个文件是实现:

string.h :

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;namespace Myobject
{class string{public:typedef char* iterator;typedef const char* const_iterator;string& operator+=(char ch);string& operator+=(const char* str);void append(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);size_t find(const char* str, size_t pos);string substr(size_t pos, size_t len);string& operator=(const string& s);string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}void test0_01();void reserve(size_t n);void push_back(char ch);/*string():_str(new char[1] {'\0'}),_size(0),_capacity(0){}*/iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}const char* c_str() const{return _str;}size_t size(){return _size;}size_t capacity(){return _capacity;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const	char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos;};ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}

string.cpp :

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"namespace Myobject
{const size_t string::npos = -1;string string::substr(size_t pos, size_t len){assert(pos < _size);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;}size_t string::find(char ch, size_t pos){assert(pos < _size);for (size_t i = 0; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t string::find(const char* str, size_t pos){assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}void string::erase(size_t pos, size_t len){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;}}void string::insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;if (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;}//插入单个字符void string::insert(size_t  pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size == 2 * _capacity ? len + _size : 2 * _capacity);}size_t end = _size + len;while (end - 2 > pos){_str[end] = _str[end - len];end--;}for (int i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}void string::append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size == 2 * _capacity ? len + _size : 2 * _capacity);}strcpy(_str + _size, str);_size += len;//	_str[_size] = '\0';}string& string::operator+=(const char* str){append(str);return *this;}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::push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}//尾插string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator=(const string& s){if (this != &s){delete[] _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;return *this;}}ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){char ch;ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}
}

test.cpp :

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"namespace Myobject
{void test_01(){string s1;string s2("hello world");cout << s1.c_str() << endl;cout << s2.c_str() << endl;for (int i = 0; i < s2.size(); i++){s2[i] += 2;}cout << s2.c_str() << endl;s2 += 'A';s2 += 'B';string::iterator it = s2.begin();while (it != s2.end()){cout << *it << " ";it++;}cout << endl;for (auto ch : s2){cout << ch << " ";}cout << endl;s2.insert(0, '$');for (auto ch : s2){cout << ch << " ";}cout << endl;s2.insert(8, "%%%%%%%");for (auto ch : s2){cout << ch << " ";}cout << endl;s2.erase(8, 100);for (auto ch : s2){cout << ch << " ";}cout << endl;/*s2.append("hehe");for (auto ch : s2){cout << ch << " ";}s2 += "hello";for (auto ch : s2){cout << ch << " ";}*/}void test02(){string s1("hello world");string s2 = s1.substr(6, 5);cout << s2.c_str() << endl;string s3("hello bit");s2 = s3;cout << s1 << endl;cout << s2 << endl;cin >> s1;cout << s1 << endl;}
}
int main()
{Myobject::test02();//Myobject::test_01();return 0;
}

3.写时拷贝 

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该
资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有
其他对象在使用该资源。

本章完。 

 

 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Java反射机制及其应用
  • 学习记录——day24 多进程编程
  • java远程调试
  • 转世重生之当程序员从零开始,不可错过的Jupyter Notebook的详细安装教程 ♪(^∇^*)
  • 【黑马java基础】网络通信
  • 远程连接工具连接不上虚拟机的解决方式(Xshell/Windterm...)
  • ECRS工时分析软件:工业工程精益生产的智慧引擎
  • 日本央行首次加息,同时缩减国债购买规模
  • 当服务器出现蓝屏时有哪些应对措施呢?
  • 智慧宿舍中的楼层管理
  • 图论之最短路径问题(朴素Dijksra算法\堆优化版Dijksra算法\Bellman-Ford\SPFA)
  • 超详细的MySQL CRUD 并配备了大量的测试用例, 包教包会
  • magic-api-server通过classpath增加数据库驱动
  • 【C++/STL】:哈希的应用 -- 位图布隆过滤器
  • 链表的实现(C++版)
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • ES6 学习笔记(一)let,const和解构赋值
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • magento2项目上线注意事项
  • Netty 4.1 源代码学习:线程模型
  • Vue2 SSR 的优化之旅
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 深度学习在携程攻略社区的应用
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 由插件封装引出的一丢丢思考
  • 再次简单明了总结flex布局,一看就懂...
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​1:1公有云能力整体输出,腾讯云“七剑”下云端
  • ​渐进式Web应用PWA的未来
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • #单片机(TB6600驱动42步进电机)
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (5)STL算法之复制
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (一)appium-desktop定位元素原理
  • (一)WLAN定义和基本架构转
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • *算法训练(leetcode)第四十天 | 647. 回文子串、516. 最长回文子序列
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET Core 版本不支持的问题
  • .net 简单实现MD5
  • .NET 设计模式初探
  • .NET 使用 XPath 来读写 XML 文件
  • .net6 webapi log4net完整配置使用流程
  • .Net组件程序设计之线程、并发管理(一)
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • @RequestParam,@RequestBody和@PathVariable 区别