【C++初阶】string类

【C++初阶】string类

🥕个人主页:开敲🍉

🔥所属专栏:C++🥭

🌼文章目录🌼

1. 为什么学习string类?

    1.1 C语言中的字符串

    1.2 实际中

2. 标准库中的string类

    2.1 string类

    2.2 auto和范围for

    2.3 string类常用接口说明

3. string类的模拟实现

    3.1 经典的string类问题

        3.1.1 浅拷贝

       3.2.2 深拷贝

    3.2 string类模拟实现

1. 为什么学习string类?
    1.1 C语言中的字符串

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

    1.2 实际中

  在我们日常的刷题中,有关字符串的题目基本上是以string的形式出现的,并且在日常的生活当中,为了简单、方便快捷,基本上也都是使用的string,很少有人使用C标准库中的字符串操作函数。

2. 标准库中的string类
    2.1 string类

  string - C++ Reference (cplusplus.com)

  这里面有关于string类的详细说明,包括它的每个接口的功能以及接口的使用环境等。其中比较重要的接口有:operator=、iterators有关的begin、end的所有接口(自己实现时子需要实现begin、end即可,后面的rbegin和rend等理解即可)、size、resize、capacity、reserve、clear、empty、operator[]、operator+=、append、push_back、insert、erase、swap、c_str、find、find_first_of

    2.2 auto和范围for

auto关键字:

  在这里补充两个C++11的小语法,方便我们后面的学习

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

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

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

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

  auto不能直接用来声明数组

  看到这里你可能觉得auto没什么作用,我知道我想要的是什么类型的我为什么还要它自动识别。别着急,后面你就会明白auto是有多好用。

范围for:

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

for(auto+变量 : 迭代范围)

{

        //循环体
}

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

  范围for看似非常智能很厉害,实际上底层就是迭代器(迭代器是什么后面会讲,此处记住即可)

    2.3 string类常用接口说明

1. string的常见构造函数:

2. string类对象的容量(Capacity)操作:

size(重点):返回字符串有效字符长度

length:返回字符串有效字符长度

capacity:返回所开空间总大小

empty(重点):检查字符串是否为空

clear(重点):仅删除数据,而不销毁空间

reserve(重点):为字符串预留一块空间,可以数据量小时可以省去开辟空间的消耗

rsize(重点):将有效的字符减为n(自己传)个,如果是增加,则多加的字符改为c(自己传)

注:

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

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

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

  ④ reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参
数小于string的底层空间总大小时,reserver不会改变容量大小(Linux环境下会改变,但不会影响数据的存储)。

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

operator[](重点):返回pos位置字符的引用(方便修改),const string类调用

begin+end(重点):begin获取字符串开头第一个字符的迭代器+end获取最后一个字符下一个位置的迭代器

//后面还有rbegin+rend、crbegin+crend、cbegin+cend等迭代器,这里不一一赘述

4. string类对象的修改操作:

push_back(重点):在字符串后尾插一个字符

append(重点):在字符串后尾插一个字符串

operator+=(重点):重载+=运算符,可以完成尾插操作

c_str(重点):返回字符串地址

find+npos(重点):从字符串pos位置开始往后查找所要字符,并返回其所在位置。如果遍历完字符串也找不到,则返回npos

rfind:从字符串pos位置开始往前查找所要字符,并返回其所在位置。如果找不到,则返回npos

substr:从字符串pos位置开始截取n个字符并返回

注:

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

  ② 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留
好,这样可以节剩开辟空间的效率消耗。

5. string类非成员函数

operator+:尽量少用,因为传值返回,导致深拷贝,导致效率降低

operator<<(重点):输入运算符重载,用于方便输出string字符串中的内容

operator>>(重点):输入运算符重载

getline(重点):获取一行字符串,遇到'\0'才会停止获取,在OJ的字符串题中很常用

relational operators(重点):大小比较,重载了各种类型的比较运算符

3. string类的模拟实现
    3.1 经典的string类问题
        3.1.1 浅拷贝

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

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

       3.2.2 深拷贝

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

    3.2 string类模拟实现

//string.h头文件的声明

#pragma once


#include <iostream>
#include <assert.h>
using namespace std;


namespace gjk
{
    class string
    {
    public:

        typedef char* iterator;


        string(const char* s = "")
        {
            _str = new char[strlen(s) + 1];
            strcpy(_str, s);
            _size = strlen(s);
            _capacity = _size;
        }

        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }

        const char& operator[](size_t pos) const
        {
            assert(pos < _size);
            return _str[pos];
        }

        size_t size()
        {
            return _size;
        }

        const size_t size() const
        {
            return _size;
        }

        size_t capacity()
        {
            return _capacity;
        }

        const size_t capacity() const
        {
            return _capacity;
        }

        
        char* c_str()
        {
            return _str;
        }

        //迭代器
        iterator begin()
        {
            return _str;
        }

        const iterator begin() const
        {
            return _str;
        }

        iterator end()
        {
            return _str + _size;
        }

        const iterator end() const
        {
            return _str + _size;
        }

        ~string()
        {
            delete[] _str;
            _str = nullptr;
            _size = _capacity = 0;
        }

        //扩容
        void dilatancy(int newcapacity);
        //预留空间
        void reserve(size_t n);
        //改变有效字符数
        void rsize(size_t n, char ch = '\0');
        //尾插
        void push_back(char ch);
        //尾插字符串
        void append(const char* s);
        //指定插入字符
        void insert(size_t pos, char ch);
        //指定删除n字符
        void erase(size_t pos,size_t n = npos);
        //指定插入字符串
        void insert(size_t pos, const char* s);
        //清除数据
        void clear();
        //判空
        bool empty();

        //+=一个字符运算符重载
        string& operator+=(char ch);
        //+=字符串运算符重载
        string& operator+=(const char* s);


        //比较运算符重载
        bool operator==(const string& s);

        bool operator>(const string& s);

        bool operator<(const string& s);

        bool operator>=(const string& s);

        bool operator<=(const string& s);

        bool operator!=(const string& s);


        //找到字符第一次出现的位置
        int find(char ch,size_t pos = 0);

        //找到字符串第一次出现的位置
        int find(const char* s, size_t pos = 0);

    private:
        char* _str;
        size_t _size;
        size_t _capacity;

        const static size_t npos   = -1;
    };
}

//string.cpp文件的定义

#define _CRT_SECURE_NO_WARNINGS 1


#include "string.h"


namespace gjk
{
    //扩容
    void string::dilatancy(int newcapacity)
    {
        char* tmp = new char[newcapacity + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = newcapacity;
    }


    //预留空间
    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::rsize(size_t n, char ch)
    {
        if (n <= _size)
        {
            _size = n;
            _str[_size] = '\0';
        }
        else
        {
            while (_size != n)
            {
                if (_size == _capacity)
                    dilatancy(_capacity == 0 ? 4 : 2 * _capacity);
                _str[_size++] = ch;
            }
            _str[_size] = '\0';
        }
    }

    //尾插
    void string::push_back(char ch)
    {
        if (_size == _capacity)
            dilatancy(_capacity == 0 ? 4 : 2 * _capacity);
        _str[_size++] = ch;
        _str[_size] = '\0';
    }


    //尾插字符串
    void string::append(const char* s)
    {
        int len = strlen(s);
        if (_size + strlen(s) > _capacity)
            dilatancy(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
        strcpy(_str + _size, s);
        _size += len;
    }


    //指定插入字符
    void string::insert(size_t pos, char ch)
    {
        assert(pos <= _size);
        if (_size == _capacity)
            dilatancy(_capacity == 0 ? 4 : 2 * _capacity);
        size_t end = _size + 1;
        while (end > pos)
        {
            _str[end] = _str[end - 1];
            end--;
        }
        _str[pos] = ch;
        _size++;
    }


    //指定删除n个字符
    void string::erase(size_t pos,size_t n)
    {
        assert(pos < _size);
        if (n >= _size - pos||n == npos)
        {
            _str[pos] = '\0';
            _size -= (_size - pos);
        }
        else
        {
            size_t end = pos + n;
            while (end <= _size)
            {
                _str[pos++] = _str[end++];
            }
            _size -= n;
        }
    }

    //指定插入字符串
    void string::insert(size_t pos, const char* s)
    {
        assert(pos <= _size);
        int len = strlen(s);
        if (_size + strlen(s) > _capacity)
            dilatancy(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
        size_t end = _size + len + 1;
        while (end - len > pos)
        {
            _str[end] = _str[end - len - 1];
            end--;
        }
        memcpy(_str + pos, s, len);
        _size += len;
    }


    //清除数据
    void string::clear()
    {
        _str[0] = '\0';
        _size = 0;
    }


    //判空
    bool string::empty()
    {
        return _size == 0;
    }


    //+=一个字符运算符重载
    string& string::operator+=(char ch)
    {
        push_back(ch);
        return *this;
    }


    //+=字符串运算符重载
    string& string::operator+=(const char* s)
    {
        append(s);
        return *this;
    }


    //比较运算符重载
    bool string::operator==(const string& s)
    {
        return strcmp(_str, s._str) == 0;
    }

    bool string::operator>(const string& s)
    {
        return strcmp(_str, s._str) > 0;
    }


    bool string::operator<(const string& s)
    {
        return !(*this == s || *this > s);
    }


    bool string::operator>=(const string& s)
    {
        return !(*this < s);
    }


    bool string::operator<=(const string& s)
    {
        return !(*this > s);
    }


    bool string::operator!=(const string& s)
    {
        return !(*this == s);
    }

    //找到字符第一次出现的位置
    int string::find(char ch,size_t pos)
    {
        assert(pos < _size);
        while (_str[pos] != ch && pos < _size)
        {
            pos++;
        }
        if (pos == _size)
            return -1;
        return pos;
    }


    //找到字符串第一次出现的位置
    int string::find(const char* s, size_t pos)
    {
        assert(pos < _size);
        int len = strlen(s);
        while (_str[pos] != s[0] && pos < _size)
        {
            pos++;
        }
        if (pos == _size||_size-pos < len)
            return -1;
        int ret = memcmp(_str + pos, s, len);
        if (!ret)
            return pos;
        return -1;
    }
}

   

                                                 创作不易,点个赞呗,蟹蟹啦~             

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

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

相关文章

Web响应式设计———1、Grid布局

1、网格布局 Grid布局 流动网格布局是响应式设计的基础。它通过使用百分比而不是固定像素来定义网格和元素的宽度。这样&#xff0c;页面上的元素可以根据屏幕宽度自动调整大小&#xff0c;适应不同设备和分辨率。 <!DOCTYPE html> <html lang"en"> &l…

Mysql-索引视图

目录 1.视图 1.1什么是视图 1.2为什么需要视图 1.3视图的作用和优点 1.4创建视图 1.5更新视图 1.6视图使用规则 1.7修改视图 1.8删除视图 2.索引 2.1什么是索引 2.2索引特点 2.3索引分类 2.4索引优缺点 2.5创建索引 2.6查看索引 2.7删除索引 1.视图 1.1什么是…

go中map

文章目录 Map简介哈希表与Map的概念Go语言内建的Map类型Map的声明Map的初始化Map的访问Map的添加和修改Map的删除Map的遍历 Map的基本使用Map的声明与初始化Map的访问与操作Map的删除Map的遍历Map的并发问题实现线程安全的Map 3. Map的访问与操作3.1 访问Map元素代码示例&#…

释疑 803-(1)概述 精炼提纯版

目录 习题 1-01计算机网络可以向用户提供哪些服务? 1-02 试简述分组交换的要点。 1-03 试从多个方面比较电路交换、报文交换和分组交换的主要优缺点。 1-05 互联网基础结构的发展大致分为哪几个阶段?请指出这几个阶段最主要的特点。 1-06 简述互联网标准制定的几个阶段…

web网站组成

web网站由四部分组成&#xff1a;浏览器 前端服务器 后端服务器 数据库服务器 流程&#xff1a; 1.浏览器输入网站后&#xff0c;向前端服务器发送请求&#xff0c;前端服务器响应&#xff0c;静态的数据给浏览器。 2.前端代码中script中有url,这个是向后台发送请求的网…

手撕数据结构---------顺序表和链表

1.线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构&#xff0c;常⻅的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的⼀条直…

Python研究生毕业设计,数据挖掘、情感分析、机器学习

最近在学校毕业了&#xff0c;其中有很多毕业论文使用到的代码&#xff0c;如数据挖掘、情感分析、机器学习、数据预测处理、划分数据集和测试集&#xff0c;绘制分类任务&#xff0c;词汇表示&#xff1a;使用TF-IDF向量化器&#xff0c;线性回归、多元线性回归、SVR回归模型&…

ecshop网站部署

目录 步骤1 ecshop网站的部署 一、安装环境 二、设置开机启动 ​三、 测试php ​四、上传安装包 五、安装ecshop 步骤1 ecshop网站的部署 一、安装环境 yum install -y httpd mariadb-server php php-devel php-mysql 浏览器访问&#xff1a;192.168.30.2 二、设置开机启…

LeetCode 415.字符串相加 C++写法

LeetCode 415.字符串相加 C写法 思路&#x1f914;&#xff1a; 首先不能用stoi和tostring来做&#xff0c;如果给一个很大的数那一定存不下。我们可以从后往前一位一位的取&#xff0c;创建一个变量存储进位用于计算下一位数&#xff0c;之后取模得到当前数字&#xff0c;每一…

k8s部署rabbitmq集群

1 部署集群 1.1 安装 # 创建一个中间件的命名空间 kubectl create namespace middleware # 创建ConfigMap,包含RabbitMQ的配置文件内容 kubectl apply -f rabbitmq-configmap.yaml # 配置用于存储RabbitMQ数据的PersistentVolume&#xff08;PV&#xff09;和PersistentVolum…

iPhone 17系列取消17 Plus版本?新一代苹果手机迎来新变革

随着科技的飞速发展&#xff0c;苹果公司再次准备刷新我们的期待&#xff0c;即将推出的iPhone 17系列携带着一系列令人兴奋的升级。今年&#xff0c;苹果打破了常规&#xff0c;将四款新机型带入市场——iPhone 17、17 Pro、17 Pro Max&#xff0c;以及一款全新的成员&#xf…

Java开发之反射与动态代理

#来自ゾフィー&#xff08;佐菲&#xff09; 1 反射&#xff08;Reflect&#xff09; 运行期间&#xff0c;获取类的信息&#xff0c;进行一些操作。 运行时构造类的对象。运行时获取类的成员变量和方法。运行时调用对象的方法&#xff08;属性&#xff09;。 2 Class 类 Cla…

IntelliJ IDEA 2024.1.4最新实用教程!!爽到飞起!!

IntelliJ IDEA 2024.1.4最新破解教程&#xff01;&#xff01;直接2099&#xff01;&#xff01;爽到飞起&#xff01;&#xff01;【资源在末尾】安装馆长为各位看官准备了多个版本&#xff0c;看官可根据自己的需求进行下载和选择安装。https://mp.weixin.qq.com/s/oBgoHdFU4…

视图,存储过程和触发器

目录 视图 创建视图&#xff1a; 视图的使用 查看库中所有的视图 删除视图 视图的作用&#xff1a; 存储过程&#xff1a; 为什么使用存储过程&#xff1f; 什么是存储过程&#xff1f; 存储过程的创建 创建一个最简单的存储过程 使用存储过程 删除存储过程 带参的存储…

前端面试宝典【Javascript篇】【1】

欢迎来到《前端面试宝典》&#xff0c;这里是你通往互联网大厂的专属通道&#xff0c;专为渴望在前端领域大放异彩的你量身定制。通过本专栏的学习&#xff0c;无论是一线大厂还是初创企业的面试&#xff0c;都能自信满满地展现你的实力。 核心特色&#xff1a; 独家实战案例…

VMare centos 7 设置固定ip

第一步获取网关 查看虚拟机的网关-》编辑-》虚拟网络编辑器 NAT模式-》NAT设置 获取网关IP 192.168.70.2 第二步获取主机dns1 在本地主机获取dns1&#xff0c;本地主机调出cmd输入ipconfig dns1为192.168.31.1 用管理员权限的账号进入需要设置固定ip的虚拟机&#xff0c;在t…

使用AOP优化Spring Boot Controller参数:自动填充常用字段的技巧

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 使用AOP优化Spring Boot Controller参数&#xff1a;自动填充常用字段的技巧 前言为什么使用AOP为…

java8函数式编程学习(二):optional,函数式接口和并行流的学习

简介 java8函数式编程中optional的简单使用&#xff0c;函数式接口的了解&#xff0c;并行流的使用。 optional 可以更优雅的来避免空指针异常。类似于包装类&#xff0c;把具体的数据封装到optional对象内部&#xff0c;然后使用optional的方法去操作封装好的数据。 创建o…

Python编程入门指南:从基础到高级

Python编程入门指南&#xff1a;从基础到高级 一、Python编程语言简介 1. Python是什么&#xff1f; Python是一门广泛使用的计算机程序编程语言&#xff0c;由荷兰人吉多范罗苏姆&#xff08;Guido van Rossum&#xff09;于1991年首次发行。Python是一种解释型、交互式、面…

汽车免拆诊断案例 | 2018 款别克阅朗车蓄电池偶尔亏电

故障现象 一辆2018款别克阅朗车&#xff0c;搭载LI6发动机和GF6变速器&#xff0c;累计行驶里程约为9.6万km。车主反映&#xff0c;该车停放一晚后&#xff0c;蓄电池偶尔亏电。 故障诊断 接车后用虹科Pico汽车示波器和高精度电流钳&#xff08;30 A&#xff09;测量该车的寄…