C++起始之路——string

C++起始之路——string


👇作者其它专栏

《数据结构与算法》《算法》《C++起始之路》


目录

1.为什么学习string类

2.标准库中的string类

3.string类的模拟实现

4.扩展


1.为什么学习string类

1.1C语言中的字符串

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

2.标准库中的string类

2.1string类

string类的文档介绍

使用string类时,必须包含#include<string>头文件与using namespace std;

 2.2auto与范围for

auto关键字

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

用auto声明指针类型时,用auto和auto*没有任何区别;但用auto引用类型是必须加&。

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

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

auto不能直接用来声明数组

#include <iostream>

using namespace std;

int fun1(){

        return 1;

}

//不能做参数

void func2(auto a){}

//可以做返回值,但是建议谨慎使用

auto func3(){

        return 3;

}

int main(){

        int a=1;

        auto b=a;

        auto c='a';

        auto d=func1();

        //编译报错,类型包含auto的符号必须具有初始值设定项

        auto e;

        cout<<typeid(b).name()<<endl;

        cout<<typeid(c).name()<<endl;

        cout<<typeid(d).name()<<endl;

        int x=1;

        auto y=&x;

        auto *z=&x;

        auto &m=x;

        cout<<typeid(x).name()<<endl;

        cout<<typeid(y).name()<<endl;

        cout<<typeid(z).name()<<endl;

        auto aa=1,bb=2;

        //  编译报错,声明符列表中,auto必须始终推导为同一类型

        auto cc=3,dd=4.0;

        //编译报错,auto[],数组不能具有包含auto的元素类型

        auto array[]={1,2,3};

        return 0;

}
#include <iostream>

#include <string>

#include <map>

using namespace std;

int main(){

        std::map<std::string,std::string> fruit={{"apple,"苹果"},{"orange","橙子"},{"pear","梨"}};

        //auto的作用

       //std::map<std::string,std::string>::iterator it=fruit.begin();

        auto it=fruit.begin();

        while(it!=fruit.end()){

                cout<<it->first<<":"<<it->second<<endl;

                ++it;

        }

        return 0;

}

范围for

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

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

●范围for的底层:容器遍历实际就是替换为迭代器

#include <iostream>

#include <string>

#include <map>

using namespace std;

int main(){

        int arr[]={1,2,3,4,5};

        //C++98

        for(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++){

                arr[i]++;

                cout<<arr[i]<<" ";

        }

        cout<<endl;

        //C++11

        for(auto &e:arr){//改变数组传引用

                e++;

                cout<<e<<" ";

        }

        cout<<endl;

        string str("hello wordl");

        for(auto c:str){

                cout<<c<<" ";

        }

        cout<<endl;

        return 0;

2.3sting类的常用接口说明

1.string类对象的常见构造

constructor函数名称

功能说明

string()构造空的string类对象,即空字符串
string(const char*s)用C-string来构造string类对象
string(size_t n,char c)string类对象中包含n个字符c
string(const string& s)拷贝构造函数
void Test(){

        string s1;                                //构造空的string类对象s1

        string s2("hello world");        //用c格式字符串构造string类对象s2

        string s3(s2);                        //拷贝构造

}

2.string类对象的容量操作

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty(重点)检测字符串释放为空串,是返回true,否则返回false
clear(重点)清空有效字符
reserve(重点)为字符串预留空间
resize(重点)将有效字符的个数改成n个,多出的空间用字符c填充

注:

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

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

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

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

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

函数名称功能说明
operator[](重点)返回pos位置的字符,const string类对象调用
begin+endbegin获取第一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
rbegin+rendrbegin获取最后一个字符的迭代器+rend获取第一个字符上一个位置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

4.string类对象的修改操作

函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+=(重点)在字符串后追加字符串str
c_str(重点)返回C格式字符串
find+npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回

注:

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

●对string操作时,若能大概预估放多少字符,可以先通过reserve把空间预留好

5.string类非成员函数

函数名称功能说明
operator+尽可能少用,因为传值返回,导致深拷贝效率低
operator>>(重点)输入运算符重载
operator<<(重点)

输出运算符重载

getline(重点)

获取一行字符串

relational operator(重点)大小比较

6.vs和g++下string结构的说明

注:下述结构是32位平台下进行验证,32位平台下指针占4个字节

●vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

        ▢当字符串长度小于16时,使用内部固定的字符数组来存放

        ▢当字符串长度大于等于16时,从堆上开辟空间

union_Bxty{

        //storage for small buffer or pointer to lager one

        value_type_Buf[_BUF_SIZE];

        pointer _Ptr;

        char _Alias[_BUF_SIZE];//to permit aliasing

}_Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量

最后还有一个指针做一些其它事情

故总共占16+4+4+4=28字节

●g++下string的结构

g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包括了一个指针,该指针将来指向一块堆空间,内部包括了如下字段:

        ▢空间总大小

        ▢字符串有效长度

        ▢引用计数

struct Rep_base{

        size_type _M_length;       

        size_type _M_capacity;

        _Atomic_word _M_refcount;

};

        ▢指向堆空间的指针,用来存储字符串

7.练习

仅仅反转字母

字符串中的第一个唯一字符

字符串最后一个单词的长度

验证回文串验证回文串

字符串相加

3.string类的模拟实现

3.1经典的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 world");

        String s2(s1);

}

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

3.2浅拷贝

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

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享

3.3深拷贝

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

3.3.1传统版写法的string类

class String{

public:

        String(const char *str=""){

                //构造String类对象是,若传递nullptr指针,可以认为程序非

                 if(nullptr==str){

                        assert(false);

                        return ;

                }

                _str=new char [strlen(str)+1];

                strcpy(_str,str);

        }

        String(const String &s)

                :_str(new char[strlen(s._str)+1]

        {

                strcpy(_str,s._str);

        }

        String &operator=(const String &s){

                if(this!=&s){

                        char *pstr=new char[strlen(s._str)+1];

                        strcpy(pstr,s._str);

                        delete[] _str;

                        _str=pstr;

                }

                return *this;

        }

        ~String(){

                if(_str){

                        delete []_str;

                        _str=nullptr;

                }

        }

private:

        char *_str;

};

3.3.2现代版写法的String类

class String{

public:

        String(const char *str=""){

                //构造String类对象是,若传递nullptr指针,可以认为程序非

                 if(nullptr==str){

                        assert(false);

                        return ;

                }

                _str=new char [strlen(str)+1];

                strcpy(_str,str);

        }

        String(const String &s)

                :_str(new char[strlen(s._str)+1]

        {

                String str(s._str);

                swap(_str,str._str);

        }

        String &operator=(String &s){

                swap(_str,s._str);

                return *this;

        }

        String &operator=(const String &s){

                if(this!=&s){

                        String str(s);

                        swap(_str,str._str);

                }

                return *this;

        }

        ~String(){

                if(_str){

                        delete []_str;

                        _str=nullptr;

                }

        }

private:

        char *_str;

};

3.4写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用这的个数。在构造时,将资源的的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,若计数为1,说明该对象是资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

写时拷贝

写时拷贝在读取时的缺陷

3.5string类的模拟实现

string的模拟实现

4.扩展

面试中string的一种正确写法

STL中的string类怎么了?

Read more

Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎 在鸿蒙(OpenHarmony)系统的端云一体化登录、政企应用的安全审计或复杂的跨端权限校验场景中,如何确保来自云端授信中心的 JWT Token 既能被正确解析(Decode),又能被严密地校验其合法性与过期时间?jwt_io 为开发者提供了一套工业级的、基于 RFC 7519 标准的 JSON Web Token 深度处理方案。本文将深入实战其在鸿蒙应用安全底座中的应用。 前言 什么是 JWT IO?它不仅是一个简单的 Base64 解码器,而是一个具备深厚 RFC

By Ne0inhk

Ubuntu 22.04 中禁用 `unattended-upgrades` 完全指南

Ubuntu 22.04 中禁用 unattended-upgrades 完全指南 📌 什么是 unattended-upgrades? unattended-upgrades 是 Ubuntu 系统默认预装的自动更新工具,主要用于自动下载并安装安全更新(如系统漏洞修复、关键组件补丁),无需用户手动干预。其设计目的是提升系统安全性,但在部分场景下(如服务器稳定运行、测试环境控制、带宽受限等),用户可能需要禁用该功能。 ⚠️ 禁用前的重要提醒 * 禁用自动更新后,系统将不再自动获取安全补丁,需手动定期执行更新(推荐 sudo apt update && sudo apt upgrade -y),否则可能面临安全风险。 * 以下方法适用于 Ubuntu 22.04(基于 Debian 11 架构),其他版本可能略有差异。 * 操作前建议备份关键配置文件(如 /etc/apt/

By Ne0inhk
【Linux 网络】理解并应用应用层协议:HTTP(附简单HTTP服务器C++代码)

【Linux 网络】理解并应用应用层协议:HTTP(附简单HTTP服务器C++代码)

前言:         上文我们学习到了什么是守护进程以及其原理【Linux网络】深入理解守护进程(Daemon)及其实现原理-ZEEKLOG博客         本文我们来认识应用层的协议:HTTP! HTTP协议         虽然应用层协议通常可由程序员自定义,但在实际开发中,我们通常直接使用业界专家已经定义好且非常成熟的现成协议。HTTP(超文本传输协议)就是其中最重要、最好用的应用层协议之一。         HTTP是互联网世界的基石,它定义了客户端(如浏览器)与服务器之间进行通信的标准方式,主要用于交换或传输超文本数据(例如 HTML 文档)。         HTTP协议遵循标准的请求-响应(Request-Response)模型:         请求:客户端通过HTTP协议主动向服务器发送请求。         响应:服务器收到客户端发来的请求后进行处理,并将结果作为响应返回给客户端。         HTTP协议具有两个显著特点:         无连接:每一次请求都想要建立一个新的连接,处理完既断开         无状态:服务器不会保存客

By Ne0inhk
Flutter 三方库 http_core_client 的鸿蒙化适配指南 - 打造极简、健壮的 OpenHarmony 网络请求核心组件

Flutter 三方库 http_core_client 的鸿蒙化适配指南 - 打造极简、健壮的 OpenHarmony 网络请求核心组件

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 http_core_client 的鸿蒙化适配指南 - 打造极简、健壮的 OpenHarmony 网络请求核心组件 在 Flutter 应用开发中,网络请求层(Networking Layer)是应用的生命线。虽然 dio 功能强大,但对于追求轻量化、高性能的鸿蒙应用来说,一个精简且核心功能完备的客户端库往往更具优势。http_core_client 为开发者提供了一套基于 BaseClient 封装的极简模型。在 OpenHarmony(鸿蒙)环境下,如何结合其底层网络栈、处理系统的代理配置以及优化连接复用,是构建高标准鸿蒙应用的必修课。本文将带大家深入探讨其适配要点。 前言 随着鸿蒙系统(HarmonyOS)进入原生应用开发的新阶段,网络栈的稳定性与安全性(如 TLS

By Ne0inhk