探索C/C++的奥秘之string类

string叫串,是一个管理字符数组的类,其实就是一个字符数组的顺序表,通过成员函数对字符串进行增、删、查、改。

C++标准库里面的东西都在std这个命名空间中。

int main()
{
    string s1;
    std:: string s2;
    std::string name("xsq");
    cout << name << " " << "and" << " ";
    name = "lsw";
    cout << name << endl;
    return 0;

1. 为什么学习string类?

1.1 C语言中的字符串

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

1.2 两个面试题(暂不做讲解)

字符串转整形数字

字符串相加

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本 都使用string类,很少有人去使用C库中的字符串操作函数。

2. 标准库中的string类

2.1 string类(了解)

string类的文档介绍

1. 字符串是表示字符序列的类

2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。

3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。

4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。

5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:

1. string是表示字符串的字符串类

2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;

4. 不能操作多字节或者变长字符的序列。

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

2.2 string类的常用接口说明(注意下面我只讲解最常用的接口) 

1. string类对象的常见构造 

 学习STL不要想着每个都掌握,不然会很难受,我们掌握最常用的,剩下的我们知道string可以怎么怎么样,用的时候查文档。

int main()
{
    string s1;            //构造空的string类对象,即空字符串
    string s2("hello");   //用C-string来构造string类对象
    string s3(10, '*');   //n个字符去初始化
    string s3(s2);        //拷贝构造
    return 0;
}

 拷贝一个字符对象的内容,从pos个位置开始,拷贝npos个字符。npos是string这个类里面的一个静态成员变量,这个地方不是-1,反而是整型的最大值。

int main()
{
    //字符串大小的比较
    string s1("hello world");
    string s2(s1, 6, 5);
    cout << (s1 == s2) << endl;
    cout << (s1 > s2) << endl;
    return 0;
}

和C语言一样,比较字符串的大小都是按照ASCI码比较。

int main()
{
    string s1("hello world");
    string s2(s1, 6);//后面没给参数,表示的是一个很大的值
    cout << s2 << endl;
    return 0;
}

https://cplusplus.com/reference/string/string/string/ 中https:是网络协议,cplusplus.com是域名

这三个是运算符重载,同时这三个函数又构成函数重载。 

int main()
{
    string s1;
    string s2;
    s1 = s2;
    s1 = "1111";
    s2 = '1';
    return 0;

字符串的增:

如果我们想尾插一个字符,可以调用上面的成员函数。

如果我们想尾插一个字符串,可以调用上面的成员函数。

int main()
{
    string s1("hello");
    //尾插一个字符
    s1.push_back(' ');
    //尾插一个字符串
    s1.append("world");
    cout << s1 << endl;
    return 0;
}

string的好处是空间不够,push_back、append可以扩容。

+=可以+=一个字符,+=可以+=一个字符串,+=可以+=一个string对象。

int main()
{
    string s1("hello");
    s1 += ' ';
    s1 += "world";
    cout << s1 << endl;
    return 0;
}

本质还是尾插,只是在string类中重载了+=,operator+=一个字符,就是调用push_back, operator+=字符串,就是调用append。

把x转成string对象:

int main()
{
    //要求x转成string对象
    size_t x = 0;
    //cin >> x;
    string xstr;
    while (x)
    {
        size_t val = x % 10;
        xstr += ('0' + val);
        x /= 10;
    }
    cout << xstr << endl;
    return 0;
}

字符是存储的ASCII码,数字1对应的ASCII码不是字符1,需要加上字符'0'才是字符'1'的ASCII码。

.sln是打开项目的额文件。

但是上述的结果不符合我们的需求,我们还需要逆置一下,这里就不做过多讲解。

这里重载了一个运算符operator[],重载了一个[],[]这个运算符本质是一种解引用,是数组来访问它的数据的。重载[]之后string字符串可以当作数组使用。

int main()
{
    string s1("hello");
    //字符串的修改
    for (size_t i = 0; i < s1.size(); i++)
    {
        s1[i]++;
    }
    s1[0]--;
    for (size_t i = 0; i < s1.size(); i++)
    {
        cout << s1[i];
    }
    cout << endl;
    return 0;
}

迭代器是如何访问的呢?迭代器是像指针一样的东西,但不一定是指针。迭代器是增加一种访问的方式,迭代器的代码是怎么写的,任何容器的迭代器都是这样的,下节课我们会学string的底层,其实string底层就是指针。

begin对于任何的容器都是第一个位置的迭代器, 

下面这两句代码看似相同,实际上底层有很大差别。 

s3[1];   //*(s3+1);
s1[1];   //s1.operator[](1); 

int main()
{
    //string对象的两种实例化方式
    string s0;
    string s1("hello");
    //遍历string的三种方式
    cout << s1 << endl;
    //下标 + [],用于string和vector
    cout << s1.size() << endl;//size()不算字符串末尾的\0
    //\0不算有效字符,\0是一个特殊字符,标识字符串结束的特殊字符
    //有些地方\0不会显示的打印出来,其实已经访问到了
    for (size_t i = 0; i < s1.size(); i++)
    {
        cout << s1[i];
    }
    cout << endl;
    //迭代器
    string::iterator it = s1.begin();
    while (it != s1.end())//end返回的是最后一个数据的下一个位置,end指向的是\0
    {
        (*it)--;
        it++;
    }

    //迭代器也可以修改数据
    it = s1.begin();
    while (it != s1.end())
    {
        cout << *it;
        it++;
    }
    cout << endl;
    //第三种访问string的方式,范围for,自动迭代,自动判断结束
    //范围for从底层的角度来说,底层是替换为迭代器
    for (auto e : s1)//依次取s1的数据赋值给给变量e,这个变量自动推导出类型char
    {
        cout << e;//这里的e是*it的拷贝
    }
    cout << endl;
    //其实也可以下面这样写
    for (char e : s1)
    {
        cout << e;
    }
    cout << endl;
    for (auto& e : s1)
    {
        e++;//这里的e是*it的别名
    }
    cout << endl;
    //根本就没有范围for,实际底层都是迭代器,有了迭代器才有了范围for
    //任何容器都支持迭代器,并且用法是类似的
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    vector<int>::iterator vit = v.begin();
    while (vit != v.end())
    {
        cout << *vit;
        vit++;
    }
    cout << endl;
    //下面这两个是函数模板
    reverse(s1.begin(), s1.end());
    reverse(v.begin(), v.end());
    //范围for只支持顺着遍历,不支持倒着遍历
    for (auto e:s1)
    {
        cout << e;
    }
    cout << endl;
    for (auto e: v)
    {
        cout << e;
    }
    cout << endl;
    sort(v.begin(), v.end());
    for (auto e : v)
    {
        cout << e;
    }
    return 0;
}

总结:iterator提供了一种统一的方式访问和修改容器的数据,这种统一的方式可以去访问简单的数组,稍微复杂的链表、更复杂的树、哈希表等等这样的结构。

是链表、树啥的都可以逆置。

sort要求传的还是迭代器,链表不支持用sort。

迭代器除了提供普通迭代器,还提供了反向迭代器,就是有时候我们需要倒着遍历。

rbegin()在最后一个位置,rend()在第一个数据的前一个位置。范围for不能倒着遍历,只有反向迭代器能倒着遍历。

int main()
{
    string s1("hello world");
    //有了迭代器后,auto的价值就更明显了
    //string::reverse_iterator vit = s1.rbegin();
    auto vit = s1.rbegin();
    while (vit != s1.rend())
    {
        //cout << *vit;
        //vit++;
        //也可以修改数据
        *vit += 3;
        //cout << *vit;
        //vit++;
    }
    cout << endl;
    return 0;
}

//下面的代码编不过
void Func(const string& s)
{
    string::iterator vit = s.begin();
    while (vit != s.end())
    {
        cout << *vit;
        vit++;
    }
}
int main()
{
    string s1("hello world");
    Func(s1);
    return 0;

const对象不能用普通的迭代器,要用const迭代器。

这相当于一个权限的放大问题,普通迭代器可以读和写,const迭代器只能读不能写。 

//下面这样就可以了

void Func(const string& s)
{
    string::const_iterator vit = s.begin();
    while (vit != s.end())
    {
        cout << *vit;
        vit++;
    }
}
int main()
{
    string s1("hello world");
    Func(s1);
    return 0;
}

int main()
{
    //size和length的返回结果是一样的,size的意思是数据个数,length是长度的意思。
    string s1("hello world");
    cout << s1.size() << endl;
    cout << s1.length() << endl;

    return 0;
}

大家可以发现,string不在容器这个位置上,严格来说,string不属于STL,属于标准库,STL属于标准库的一部分,string属于标准库,string产生的比较早。string最开始定义的接口是length,STL出来以后被迫加了一个size,顺序表、string、甚至链表都可以叫长度。

 返回字符串的最大长度。不同的编译器或者不同版本的编译器得到的结果是不一样的,不稳定。

capacity可以看容量。

同样的代码,在Linux下的capacity是11。

clear是清理数据的意思,clear会让size改变为0,不能让capacity改变。

会对我们的容量进行改变:

这里要插入字符串就不会扩容了,空间已经开好了。reserve的应用场景:提前知道要开多少空间,提前把空间开好。

int main()
{
    string s2;
    s2.reserve(200);
    cout << s2.capacity() << endl;
    return 0;
}

vs下给出的空间会比提前开的空间大,在Linux下开多少,空间就是多少。

假如reserve100个空间,它可能给的不是100,可能会比100大,而且如果显示的是空间大小是111,它的实际大小会是112,\0也会占用一个空间。如果我们用clear清理数据,再reserve个空间,那么它的容量就会缩小。

reserve是单纯的开空间,也就是对空间进行改变,resize是开空间+填值初始化,初始化的值是"\0"。

空间是不会轻易的缩小,如果缩容也是开辟一块新的空间,把原有的数据部分拷贝到新的空间中,把原来的空间释放掉。

 at跟[]的功能是一样的,但是我们平常不太使用,at是早些时候没有支持运算符重载的时候提供的。at如果访问的数据超出会抛异常,[]是断言,这两个读和写都会报错。

int main()
{
    string s1("hello world");
    s1.at(0) = 'x';
    cout << s1 << endl;
    return 0;

assign是一个赋值的意思,就是我对你覆盖了。

int main()
{
    string s1("hello world");
    s1.append("sssssss");
    cout << s1 << endl;
    s1.assign("1111111");
    cout << s1 << endl;
    return 0;
}

int main()
{
    string s1("hello world");
    s1.append("sssssss");
    cout << s1 << endl;
    s1.assign("1111111");
    cout << s1 << endl;
    s1.insert(0, "hello");
    cout << s1 << endl;
    s1.insert(0, 10, 'x');
    cout << s1 << endl;
    s1.insert(s1.begin(), 10, 'y');
    cout << s1 << endl;
    s1.insert(s1.begin() + 10, 10, 'z');
    cout << s1 << endl;
    return 0;
}

insert虽然好,但是也不要多用,因为有效率的问题。

int main()
{
    string s1("hello world");
    s1.erase(5, 1);
    cout << s1 << endl;
    s1.erase(5);
    cout << s1 << endl;
    string s2("hello world");
    s2.erase(s2.begin());
    cout << s2 << endl;
    return 0;
}

int main()
{
    string s1("hello world hello bit");
    s1.replace(6, 5, "xxxxxxxxxxx");
    cout << s1 << endl;
    //讲s2中的空格替换为20%
    string s2("hello world hello bit");
    string s3;
    for (auto ch : s2)
    {
        if (ch != ' ')
        {
            s3 += ch;
        }
        else
        {
            s3 += "20%";
        }
    }
    s2 = s3;
    cout << s2 << endl;
    return 0;

int main()
{
    string s1("hello world hello bit");
    s1.replace(6, 5, "xxxxxxxxxxx");
    cout << s1 << endl;
    //讲s2中的空格替换为20%
    string s2("hello world hello bit");
    string s3;
    for (auto ch : s2)
    {
        if (ch != ' ')
        {
            s3 += ch;
        }
        else
        {
            s3 += "20%";
        }
    }
    s2 = s3;
    //这两个打印的结果虽然一样,但是第一个调用的是string中重载的流插入和流提取
    //第二个是识别成char* ,是调用库里面的对这个的支持
    cout << s2 << endl;
    cout << s2.c_str() << endl;
    return 0;
}

c_str是更好的跟C的一些接口进行配合,C++要兼容C,你需要用某些库,不排除那个库是用C语言写的,比如做个简单的比方,C++里面有些要访问数据库的时候,它会给你提供一些api,方便去访问、连接数据库等等,对于C/C++它没有专门去访问C++的版本,它只提供了一个C的版本,它的这个C版本,如果你是纯C的项目可以用,C++的项目也可以用。 

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

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

相关文章

【刷题】优选算法

优选算法 双指针 202. 快乐数 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 【思路】 第一个实例是快乐数&#xff0c;因为会变为1且不断是1的循环 第二个实例不可能为1&#xff0c;因为会陷入一个没有1的循环 根据两个实例和鸽巢原理可以发现不断的平方和最…

openEuler的aarch64操作系统上安装k3s

1、需要安装docker容器引擎&#xff08;省略&#xff09; 2、安装ks3命令 curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRRORcn INSTALL_K3S_SKIP_SELINUX_RPMtrue INSTALL_K3S_SELINUX_WARNtrue sh -s -- --docker 其中&#xff1a…

Synchronized锁、锁的四种状态、锁的升级(偏向锁,轻量级锁,重量级锁)

目录 1. Synchronized锁 1.1 介绍 1.2 三种应用方式★ 1.2.1 synchronized同步方法 1.2.2 synchronized 同步静态方法 1.2.3 synchronized 同步代码块 1.3 Synchronized锁底层原理 1.3.1 简答 1.3.2 详述 1. Monitor对象 2. Monitor与对象锁关联时 具体的流程&#…

【网络】数据链路层

目录 以太网 以太网的帧格式 MSS 交换机 MTU对UDP的影响 ARP协议 数据链路层是软件层的最底层协议&#xff0c;它的下面就是物理层&#xff0c;那么下面我们就来介绍一下它负责在网络通信中完成什么工作 我们前面说的IP协议是解决如何进行跨网络转发的&#xff0c;也就是…

零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——下

细节较多&#xff0c;篇幅较大&#xff0c;分为上/下两部分发布在两篇文章内 另一部分详见下面文章 零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——上https://blog.csdn.net/weixin_62808713/article/details/143572185 八、第二层数据库服务器权限获取 猜到新闻资…

13-鸿蒙开发中的综合实战:华为登录界面

大家好&#xff0c;欢迎来到鸿蒙开发系列教程&#xff01;今天&#xff0c;我们将通过一个综合实战项目来实现一个华为登录界面。这个项目将涵盖输入框组件、按钮组件、文本组件和布局容器的使用&#xff0c;帮助你更好地理解和应用这些组件。无论你是初学者还是有一定经验的开…

告别复杂协作:Adobe XD的简化替代方案

Adobe XD是一款集成UI/UX设计和原型创建功能的设计平台。它允许用户进行网页、移动应用的设计&#xff0c;以及原型的绘制&#xff0c;并且能够将静态设计转化为动态的交互原型。尽管Adobe XD提供了这些功能&#xff0c;但它依赖于第三方插件&#xff0c;且插件库有限&#xff…

ctfshow web文件上传 web166-170

1.web166 通过源码上传发现只能传zip&#xff0c;尝试一下图片上传也不行 把随便一张图片打包成zip文件&#xff0c;上传后发现有一个下载的地方,猜测是文件上传&#xff0c;尝试zip伪协议发现失败&#xff0c;打包php文件也失败了&#xff0c;不知为什么&#xff0c;&#x…

二开CS—上线流量特征shellcode生成修改模板修改反编译打包

前言 免杀几乎讲的差不多了&#xff0c;今天讲个CS的二次开发。我们原生态的CS特征肯定都是被提取完的了&#xff0c;包括它的流量特征&#xff0c;而我们要做的就是把它的流量特征给打乱&#xff0c;还可以修改生成的后门&#xff0c;使其生成即免杀。 实验环境 CS4.4&…

7.《双指针篇》---⑦三数之和(中等偏难)

题目传送门 方法一&#xff1a;双指针 1.新建一个顺序表用来返回结果。并排序数组。 2.for循环 i 从第一个数组元素遍历到倒数第三个数。 3.如果遍历过程中有值大于0的则break&#xff1b; 4.定义左右指针,以及target。int left i 1, right n - 1; int target -nums[i];…

Muse-Ant-Desgin-Vue 改造成 Vite+Vue3

后台地址&#xff1a;https://www.creative-tim.com/product/muse-vue-ant-design-dashboard?refantdv-official 一、配置 ViteAntDesginVue 配置ViteAntDesginVue ViteAntDesginVue配置&#xff1a;https://blog.csdn.net/qq_17523181/article/details/143241626 安装vue-ro…

实习作假:阿里健康实习做了RABC中台,还优化了短信发送流程

最近有二本同学说&#xff1a;“大拿老师&#xff0c;能帮忙看下简历吗&#xff1f;” 如果是从面试官的角度来看&#xff0c;这个同学的实习简历是很虚假的。 但是我们一直强调的是&#xff1a;校招的实习简历是不能出现明显的虚假。 首先&#xff0c;你去公司做事情&#…

疯狂Java讲义-Java基础类库

Java基础类库 本章思维导图 5-0Java基础类库.png 用户互动 使用Scanner获取键盘输入 Scanner主要提供了两个方法来扫描输入 hasNextXxx(); 是否还有下一个输入项&#xff0c;其中Xxx可以是int、long等代表基本数据类型的字符串。 nextXxx(); 获取下一个输入项。Xxx的含义与前一…

[前端] 为网站侧边栏添加搜索引擎模块

前言 最近想给我的个人网站侧边栏添加一个搜索引擎模块&#xff0c;可以引导用户帮助本站SEO优化&#xff08;让用户可以通过点击搜索按钮完成一次对本人网站的搜索&#xff0c;从而实现对网站的搜索引擎优化&#xff09;。 最开始&#xff0c;我只是想实现一个简单的百度搜索…

汇聚全球前沿科技产品,北京智能科技产业展览会·世亚智博会

在北京这座古老而又充满现代气息的城市中&#xff0c;一场科技与创新的盛宴正悄然上演——北京智能科技产业展览会&#xff08;简称&#xff1a;世亚智博会&#xff09;&#xff0c;作为全球前沿科技的汇聚地&#xff0c;不仅展示了人工智能、5G通信、虚拟现实等尖端技术的最新…

JAVA基础:数组 (习题笔记)

一&#xff0c;编码题 1&#xff0c;数组查找操作&#xff1a;定义一个长度为10 的一维字符串数组&#xff0c;在每一个元素存放一个单词&#xff1b;然后运行时从命令行输入一个单词&#xff0c;程序判断数组是否包含有这个单词&#xff0c;包含这个单词就打印出“Yes”&…

猎板PCB2到10层数的科技进阶与应用解析

1. 单层板&#xff08;Single-sided PCB&#xff09; 定义&#xff1a;单层板是最基本的PCB类型&#xff0c;导线只出现在其中一面&#xff0c;因此被称为单面板。限制&#xff1a;由于只有一面可以布线&#xff0c;设计线路上有许多限制&#xff0c;不适合复杂电路。应用&…

2025年山东省考报名流程图解

2025年山东公务员考试备考开始 为大家整理了从笔试到录用的全部流程&#xff0c;希望可以帮助到你们&#xff01;参考2024年山东省考公告整理&#xff0c;请以最新公告为准&#xff01; 一、阅读公告和职位表 二、职位查询 三、网上报名 四、确认缴费 五、网上打印准考证 六、参…

修改elementUI等UI组件样式的5种方法总结,哪些情况需要使用/deep/, :deep()等方式来穿透方法大全

文章目录 方法 1:全局修改样式示例:修改 `ElMessage` 的背景色和字体颜色方法 2:修改特定类型的 `ElMessage` 样式-全局-不需要穿透示例:修改 `ElMessage` 成功类型的样式方法 3:通过 Scoped CSS 在组件内部修改-局部-不需要穿透方法 4:使用 JavaScript 动态修改样式-不需…

pandas——对齐运算+函数应用

引言&#xff1a;对齐运算是数据清洗的重要过程&#xff0c;可以按索引对齐进行运算&#xff0c;如果没对齐的位置则补NaN&#xff0c;最后也可以填充NaN 一、Series的对齐运算 1.Series 按行、索引对齐 import pandas as pds1 pd.Series(range(10, 20), indexrange(10)) s2…