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,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有
其他对象在使用该资源。

本章完。 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/383243.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Git】上传代码命令至codeup云效管理平台

通过git命令上传本地代码库至阿里的codeup云效管理平台的代码管理模块&#xff0c;使用方便&#xff0c;且比github上传网络环境要求低&#xff0c;超大文件&#xff08;>100M&#xff09;的文件也可以批量上传&#xff0c;且上传速度喜人。 目录 &#x1f337;&#x1f33…

信息安全工程师题

2019年10月26日第十三届全国人民代表大会常务委员会第十四次会议通过了《中华人民共和国密码法》&#xff0c;该法自2020年1月1日起施行国密算法即国家密码局认定的国产密码算法&#xff0c;其中包括了SM1、SM2、SM3、SM4等&#xff0c;其中SM1是对称加密算法&#xff0c;加密强…

R语言优雅的进行广义可加模型泊松回归分析

泊松回归&#xff08;Poisson regression&#xff09;是以结局变量为计数结果时的一种回归分析。泊松回归在我们的生活中应用非常广泛&#xff0c;例如&#xff1a;1分钟内过马路人数&#xff0c;1天内火车站的旅客流动数&#xff0c;1天内的银行取钱人数&#xff0c;一周内的销…

【BUG】已解决:No Python at ‘C:Users…Python Python39python. exe’

No Python at ‘C:Users…Python Python39python. exe’ 目录 No Python at ‘C:Users…Python Python39python. exe’ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班…

后端返回一个图片链接,前端如何实现下载功能?

纯原创文章&#xff0c;转载请说明来源。 一、背景 要实现一个下载功能&#xff0c;后端直接返回了一个图片的地址https://xxxxx/pic.jpg。如果我们直接通过window.open(url, _blank) 的方式去下载这个图片&#xff0c;会发现 Chrome 浏览器会对这个图片进行预览&#xff0c;…

Redis 7.x 系列【30】集群管理命令

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 集群信息2.1 CLUSTER INFO 3. 节点管理3.1 CLUSTER MYID3.2 CLUSTER NODES3…

扫雷-C语言

一、前言&#xff1a; 众所周知&#xff0c;扫雷是一款大众类的益智小游戏&#xff0c;它的游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子&#xff0c;同时避免踩雷&#xff0c;踩到一个雷即全盘皆输。 今天&#xff0c;我们的目的就是通过C语言来实现一个简…

SpringBoot源码(1)ApplicationContext和BeanFactory

1、调用getBean方法 SpringBootApplication public class SpringBootDemoApplication {public static void main(String[] args) {ConfigurableApplicationContext applicationContext SpringApplication.run(SpringBootDemoApplication.class, args);applicationContext.get…

关于使用宝兰德bes中间件进行windows部署遇到的问题——license不存在

报错信息 日志文件中是这么报错的 遇到的具体情况&#xff1a; 实例按照**的文档手册正常步骤下去节点部署的时候没有报错&#xff0c;成功启动&#xff0c;但是日志里会有报错信息&#xff0c;也是license不存在实例创建的时候失败了&#xff0c;报错信息如下所示 解决方法…

基于jeecgboot-vue3的Flowable流程-自定义业务表单流程历史信息显示

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、对于自定义业务表单的流程历史记录信息做了调整&#xff0c;增加显示自定义业务表单 <el-tab-pane label"表单信息" name"form"><div v-if"customF…

Fine-BI学习笔记

官方学习文档&#xff1a;快速入门指南- FineBI帮助文档 FineBI帮助文档 (fanruan.com) 1.零基础入门 1.1 功能简介 完成四个流程&#xff1a;新建分析主题、添加数据、分析数据、分享协作。 示例数据获取&#xff1a;5分钟上手FineBI - FineBI帮助文档 (fanruan.com) 1.2 …

Pyqt5新手教程

PyQt界面开发的两种方式&#xff1a;可视化UI 编程式UI &#xff08;1&#xff09;可视化UI&#xff1a;基于Qt Designer可视化编辑工具进行组件拖放、属性设置、布局管理等操作创建界面。 一是将其保存为.ui文件&#xff0c;然后在PyQt应用程序中加载和使用.ui文件。 二是使用…

mac OS matplotlib missing from font(s) DejaVu Sans

如果能搜索到这篇文章&#xff0c;我猜你遇到了和我一样的问题&#xff1a;matplotlib绘图中文乱码。如下&#xff1a; 出现这个问题的原因是&#xff1a;matplotlib使用的字体列表中默认没有中文字体。 这里说一种解决方案&#xff1a;我们可以在文件中手动指定matplotlib使用…

记一次Mycat分库分表实践

接了个活,又搞分库分表。 一、分库分表 在系统的研发过程中,随着数据量的不断增长,单库单表已无法满足数据的存储需求,此时就需要对数据库进行分库分表操作。 分库分表是随着业务的不断发展,单库单表无法承载整体的数据存储时,采取的一种将整体数据分散存储到不同服务…

bool数组的理解和应用[C++]

文章目录 bool数组的用法bool数组的定义声明bool数组的初始化访问和修改数组元素遍历数组 运用bool数组简单代码 在今天做题中发现了bool类不仅能用于函数类型还能用于数组类型&#xff0c;好奇查了查发现bool还有很多用处&#xff1a;基本变量&#xff0c;在枚举类型中会用到&…

Java二十三种设计模式-装饰器模式(7/23)

装饰器模式&#xff1a;动态扩展功能的灵活之选 引言 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;用于在不修改对象自身的基础上&#xff0c;通过添加额外的职责来扩展对象的功能。 基础知识&#xff0c;java设计模式总体来说设计…

大语言模型-GPT2-Generative Pre-Training2

一、背景信息&#xff1a; GPT2是2019年由OpenAI 提出的预训练语言模型。 GPT2提出语言模型式无监督的多任务学习 。旨在通过无监督学习也能达到和finetune一样的效果&#xff0c;并且拥有更强的泛化能能力。 即提出利用语言模型做下游任务时&#xff0c;不需要下游任务的任何…

Python Flask入门到精通:详细教程和实战案例

前言 Flask是一个轻量级的Web框架&#xff0c;用于快速开发Web应用程序。它的设计理念是简洁、灵活和易于扩展&#xff0c;非常适合于从简单的单页应用到复杂的大型项目。通过Flask&#xff0c;可以创建各种Web应用程序&#xff0c;比如博客、电子商务网站、RESTful API等。 …

Unity3d打包到Android

本文参考&#xff1a; Unity3D新手教程&#xff1a;如何打包发布到Android_哔哩哔哩_bilibili 一、Unity 打包Android的环境搭建 1、工具安装 Unity Hub已经集成了Android的环境搭建。 选择Add modules 然后安装Android Build Support下的所有工具。 如果各个工具都安装成功…

H3CNE(路由基础、直连路由与静态路由)

目录 6.1 直连路由 6.2 静态路由理解性实验 6.2.1 配置直连路由 6.2.2 配置静态路由 6.3 路由表的参数与比较 6.3.1 优先级的比较 6.3.2 开销的比较 6.4 路由器中的等价路由、浮动路由、默认路由 6.4.1 等价路由 6.4.2 浮动路由 6.4.3 默认路由(缺省路由) 6.1 直连路…