STL之string

news2024/11/20 16:36:25

目录

  • string的基本实现
    • 一. string的基本架构
    • 二. 常用方法及实现
      • 1.初始化和清理
        • a. 构造函数
        • b. 析构函数
        • c. swap
        • d. 拷贝构造
        • e. operator=
      • 2.成员访问
        • a. operator[]
        • b. size
        • c. c_str
      • 3.迭代器iterator
        • a. begin
        • b. end
      • 4.增删查改
        • a. reserve
        • b. resize
        • c. insert
        • d. push_back
        • e. append
        • f. operator+=
        • g. erase
        • h. find
        • i. substr
        • j. clear
      • 5.比较运算
      • 6.小结
    • 三.IO
        • a. operator<<
        • b. operator>>
        • c. getline

string的基本实现

一. string的基本架构

class string
{
private:
	char* _str;//指向动态申请的空间       
	size_t _size;//实际存储的字符串大小      
	size_t _capacity;//有效数据的空间大小
};

使用库中的string类需要包含头文件#inlcude<string>,并且使用std::命名空间
在这里插入图片描述

_size:h~d的字符个数; _capacity:动态申请的空间 - 1(去除’\0’)

(所以实际动态申请的空间是:_capacity+1,多开辟一个来存放’\0’标识结尾)

二. 常用方法及实现

实现的顺序来依次进行介绍

1.初始化和清理

a. 构造函数

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

当没有传参时,缺省值为"":一个空的字符串。字符串结尾都隐藏了一个字符\0,因此空的string对象中,也有一个\0。

b. 析构函数

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

析构函数,来完成对动态申请空间的释放。

c. swap

void swap(string& temp)
{
    std::swap(_str, temp._str);
    std::swap(_size, temp._size);
    std::swap(_capacity, temp._capacity);
}

交换两个string对象的内容,通过作用域限定符::来调用库函数中的swap函数,完成对内置类型的成员变量的交换。

d. 拷贝构造

传统写法:

string (const string& s)
    :_str(new char[s._capacity + 1])
    ,_size(s._size)
    ,_capacity(s._capacity)
{
	strcpy(_str,s._str);
}

现代写法:

通过一个局部对象,和swap来完成

string(const string& s)
	:_str(nullptr)
    ,_size(0)
    ,_capacity(0)
{
    string temp(s._str);
    swap(temp);
}

通过初始化列表,对内置类型的成员变量进行初始化,然后和经构造函数得到的temp对象进行交换内容,完成拷贝构造。该函数执行完,局部对象temp会自动调用析构函数,并销毁。

e. operator=

传统写法:

string& operator=(const string& s)
{
    //如果是自己给自己赋值,则不需要进行操作
    if(this != &s)
    {
        char* temp = new char[s._capacity + 1];
        strcpy(temp, s._str);

        delete[] _str;
        _str = temp;
        _size = s._size;
        _capacity = s._capacity;
    }

    return *this;
}

现代写法

 string& operator=(string s)
 {
     swap(s);
     return *this;
 }

传参时,会调用拷贝构造函数完成对局部对象s的初始化,然后交换*this和s的内容,完成对自身对象的赋值。

2.成员访问

a. operator[]

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

返回pos下标字符的引用(可以读取、修改),如果pos越界,则直接assert,使程序崩溃。

b. size

size_t size() const 
{
    return _size;
}

c. c_str

const char* c_str() const
{
    return _str;
}

3.迭代器iterator

typedef char* iterator;
//const迭代器不能进行写操作
typedef const char* const_iterator;

a. begin

iterator begin()
{
    return _str;
}
const_iterator begin() const 
{
    return _str;
}

返回第一个元素

b. end

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

返回最后一个元素的下一个位置

4.增删查改

a. reserve

void reserve(size_t n)
{
    if(n > _capacity)
    {
        //多申请1个空间,用于存放'\0
        char* temp = new char[n+1];
        strcpy(temp,_str);
        delete[] _str;

        _str = temp;
        _capacity = n;
    }
}

申请n个字节的空间,如果n<原空间大小,则不做改变

b. resize

void resize(size_t n, char ch = '\0')
{
    if(n > _size)
    {
        resize(n);
        //将新增的存放字符ch
        for(size_t i = _size; i < n; ++i)
        {
            _str[i] = ch;
        }

    }
    //将最后一个元素存放\0
    _str[n] = '\0';
    _size = n;
}

调整字符串n个字符,如果n<原字符长度,则_str[n] = ‘\0’,只保留前n个元素。

如果n>原字符长度,保留原字符,新增的字符为ch。

c. insert

string& insert(size_t pos, char ch)
{
    //断言插入的位置
    assert(pos <= _size);
    //空间以满就扩容
    if(_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }
    
	//末尾_size处的'\0'也要一起向后挪动1位
    size_t end = _size;
    while(end >= pos)
    {
        _str[end+1] = _str[end];
        --end;
    }
    //pos位置插入字符ch
    _str[pos] = ch;
    ++_size;

    return *this;
}
string& insert(size_t pos, const char* s)
{
    assert(pos <= _size);
    size_t len = strlen(s);
    //判断空间是否足够
    if(_size+len > _capacity)
    {
        reserve(_size + len);
    }
	//末尾_size处的'\0'也要一起向后挪动len位
    size_t end = _size;
    while(end >= pos)
    {
        _str[end+len] = _str[end];
        --end;
    }
	//从pos位置开始,拷贝s的前len个字符(不要s的\0)
    strncpy(_str+pos, s, len);
    _size += len;

    return *this;
}

d. push_back

void push_back(char ch)
{
    insert(_size,ch);
}

在字符串尾加一个字符ch

e. append

void append(const char* s)
{
    insert(_size,s);
}

在字符串尾加一个字符串s

f. operator+=

string& operator+=(char ch)
{
    push_back(ch);
    return *this;
}
string& operator+=(const char* s)
{
    append(s);
    return *this;
}

g. erase

 const static size_t npos = -1;

size_t是无符号整型,-1则其是最大的数

string& erase(size_t pos, size_t len = npos)
{
    assert(pos < _size);
    //删除从pos位开始到最后的字符
    if(len == npos || len+pos >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else 
    {
        //删除从pos位置开始len个字符
        strcpy(_str+pos,_str+pos+len);
        _size -= len;
    }

    return *this;
}

h. find

size_t find(char ch, size_t pos = 0) const 
{
    assert(pos < _size);

    for(size_t i = 0; i < _size; ++i)
    {
        if(ch == _str[i])
        {
            return i;
        }
    }

    return npos;
}

size_t find(const char* s, size_t pos = 0) const 
{
    assert(s);
    assert(pos < _size);
	//查找s是不是_str+pos的子字符串,不是返回nullptr
    const char* ptr = strstr(_str+pos, s);
    if(ptr != nullptr)
    {
        return ptr - _str;
    }
    else 
    {
        return npos;
    }
}

i. substr

string substr(size_t pos, size_t len = npos) const 
{
    assert(pos < _size);

    size_t end = len;
    if(len == npos || len+pos > _size)
    {
        end = _size;
    }
    //substr 是*this字符串从pos位置开始的len个字符组成的字符串
    string temp;
    for(size_t i = pos; i < end; ++i)
    {
        temp += _str[i];
    }

    return temp;
}

j. clear

void clear()
{
    _str[0] = '\0';
    _size = 0;
}

清空字符串

5.比较运算

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

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

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

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

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

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

6.小结

  1. 上述的函数都是放在自己实现的class string类中,最好将该类用自己的命名空间给包含,避免冲突。

例如:

#include <iostream>
#include <string.h>
#include <assert.h>

namespace yyjs
{
    class string
    {}
}
  1. 在实现的过程中,总是担心处理不好’\0’。其实只需注意:初始化时结尾有无\0,在进行增删时\0是否在合适位置。所以只要构造函数、insert、erase解决好‘\0’的问题,其他地方不需要担心。(值得一提的是,申请空间时,总是会多申请1字节,用来存放\0)

  2. 对于成员函数是否用const修饰,需要考虑到使用场景。如果是没有写的函数操作都可以加上const(普通对象也可以调用const成员函数);但是对于insert、reserve等有修改内容的函数,不加上const(const对象本身不能修改内容,因此不能调用这些函数)。

    对于begin()函数,由于其差异主要是其返回值类型是否为const,因此需要对函数用const修饰并构造重载。

  3. 在这里插入图片描述

当此值用作字符串成员函数中的 len(或 sublen)参数的值时,表示“直到字符串的末尾”。

三.IO

对于string对象使用<<<<是需要实现其运算符重载的

但是如果写在成员函数中,就需要调用的对象处于第一个操作数的位置,例如:str << cout;

因此放在类外实现,例如:

using std::ostream;
using std::istream;

namespace yyjs
{
    ostream& operator<<(ostream& _cout, const string& s);
    istream& operator>>(istream& _cin, string& s);
    istream& getline(istream& _cin, string& s)
}

a. operator<<

ostream& operator<<(ostream& _cout, const string& s)
{
    for (auto& e : s)
    {
        _cout << e;
    }
    return _cout;
}

b. operator>>

istream& operator>>(istream& _cin, string& s)
{
    s.clear();
	char ch;
    //_cin >> ch;
    //cin>> 不会读到空格和换行,因此用get()
	ch = _cin.get();
    //读到空格或换行就结束
	while (ch != ' ' || ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
    return _cin;
}

c. getline

istream& getline(istream& _cin, string& s)
{
    s.clear();
	char ch;
	ch = _cin.get();
    //到换行符截止
	while (ch != '\n')
	{
		s += ch;
		ch = _cin.get();
	}
	return _cin;
}

每次增加一个字符,可能会导致多次扩容(有strcpy)效率较低

istream& getline(istream& _cin, string& s)
{
    s.clear();
    
    const int N = 64;
    char buff[N];
    char c;
    c = _cin.get();
    int num = 0;
    
    while (c != '\n')
    {
        buff[num++] = c;
        if (num == N - 1)
        {
            buff[num] = '\0';
            s += buff;
            num = 0;
        }
        c = _cin.get();
    }

    buff[num] = '\0';
    s += buff;
	return _cin;
}

用一个buff数组先缓存一下cin输入,可以减少扩容次数。


🦀🦀观看~~

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

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

相关文章

【连续介质力学】连续体运动学

简介 一个质点从t0出发&#xff0c;随着时间有不同的构形&#xff0c;运动的描述是运动学。 所以需要建立运动方程来表征连续体是如何演化及其性质&#xff08;例如位移、速度、加速度、质量密度、温度等&#xff09;如何随时间变化 初始构形 或者 参考构形&#xff1a; B …

Java反射与注解

文章目录 一、 注解1.简介2. 元注解3. 自定义注解 二、 反射1. 简介2. 理解Class类并获取Class实例3. 类的加载与初始化4. 类加载器ClassLoader5. 获取运行时类的完整结构6. 动态创建对象执行方法7. 反射操作泛型8. 反射操作注解 一、 注解 1.简介 Annotation是JDK5.0开始引入…

day52|动态规划13-子序列问题

子序列系列问题 300.最长递增子序列 什么是递增子序列&#xff1a; 元素之间可以不连续&#xff0c;但是需要保证他们所在位置是元素在数组中的原始位置。 dp数组dp[i]表示以nums[i]为结尾的最长递增子序列的长度。递归函数&#xff1a;dp[i] max(dp[j]1,dp[j])初始化条件&…

Linux常用命令——gpm命令

在线Linux命令查询工具 gpm 提供文字模式下的滑鼠事件处理 补充说明 gpm命令是Linux的虚拟控制台下的鼠标服务器&#xff0c;用于在虚拟控制台下实现鼠标复制和粘贴文本的功能。 语法 gpm(选项)选项 -a&#xff1a;设置加速值&#xff1b; -b&#xff1a;设置波特率&…

chatgpt赋能python:Python中如何取出字符串中的数字并赋予新的变量

Python 中如何取出字符串中的数字并赋予新的变量 在 Python 中&#xff0c;我们经常需要处理字符串&#xff0c;其中可能包含多种类型的数据。当我们需要获取字符串中的数字时&#xff0c;该怎样做呢&#xff1f;本文将介绍取出字符串中的数字的方法&#xff0c;并赋予新的变量…

chatgpt赋能python:Python利器之数字提取

Python 利器之数字提取 Python 是一门强大的编程语言&#xff0c;被广泛应用在数据分析、人工智能等领域。在很多数据分析的场景下&#xff0c;需要从文本中提取数字&#xff0c;本文将介绍如何使用 Python 快速提取文本中的所有数字。 使用正则表达式提取数字 正则表达式&a…

YOLOv7 模型融合

将两种训练模型的检测结果融合 首先需要两种模型&#xff0c;一个是yolov7原有的模型&#xff0c;另一个是你训练出来的模型 其中yolov7.pt是官方提供的模型&#xff0c;有80个类别 其中yolov7_3.7HRW.pt是我们自己训练的模型&#xff0c;有三个分类&#xff0c;举手、看书、写…

WPF开发txt阅读器6:用树形图管理书籍

txt阅读器系列&#xff1a; 需求分析和文件读写目录提取类&#x1f48e;列表控件与目录字体控件绑定书籍管理系统 TreeView控件 TreeView可以通过可折叠的节点来显示层次结构中的信息&#xff0c;是表达文件系统从属关系的不二选择&#xff0c;其最终效果如下 为了构建这个树…

UnityVR--组件6--Animation动画

目录 新建动画Animation Animation组件解释 应用举例1&#xff1a;制作动画片段 应用举例2&#xff1a;添加动画事件 Animator动画控制器 应用举例3&#xff1a;在Animator中设置动画片段间的跳转 本篇使用的API&#xff1a;Animation、Animator以及Animator类中的SetFlo…

【Python可视化大屏】「淄博烧烤」热评舆情分析大屏

文章目录 一、开发背景二、爬虫代码2.1 展示爬取结果2.3 导入MySQL数据库 三、可视化代码3.1 大标题3.2 词云图&#xff08;含&#xff1a;加载停用词&#xff09;3.3 玫瑰图&#xff08;含&#xff1a;snownlp情感分析&#xff09;3.4 柱形图-TOP10关键词3.5 折线图-讨论热度趋…

ViewOverlay-使用简单实践

ViewOverlay-使用简单实践 一、ViewOverlay能实现什么&#xff1f;二、基础用法2.1 一个简单案例2.2 overlay.add(drawable)/groupOverlay.add(view)之后&#xff0c;不显示问题解决2.2.1 add(Drawable)方法2.2.1 add(View)方法 一、ViewOverlay能实现什么&#xff1f; 在Andro…

北京软件外包开发流程

随着软件的规模越做越多&#xff0c;功能也越来越复杂&#xff0c;对项目管理和开发人员也提出了更高的要求。软件开发的流程通常包括需求分析、项目评估与计划、设计、编码、测试等多个环节&#xff0c;今天和大家分享这方面的知识&#xff0c;希望对大家有所帮助。 软件外包开…

关系代数表达式练习(针对难题)

教师关系T&#xff08;T#,TNAME,TITLE&#xff09;课程关系C(C#,CNAME,TNO)学生关系S(S#,SNAME,AGE,SEX)选课关系SC(S#,C#,SCORE) 检索至少选修了C2,C4两门课程的学生学号&#xff1a; 涉及减法相关&#xff1a; 检索不学C2课的学生姓名和年龄 涉及除法相关&#xff1a; 检索…

使用SQL语句创建存储过程

前言: 本篇文章是记录学校学习SQL server中知识,可用于复习资料. 目录 前言:一、存储过程的创建1、创建简单存储过程2、创建带参数的存储过程3、创建带输出参数的存储过程 二 、使用T一SQL语句管理和维护存储过程2.1 使用sp_helptext查看存储过程student_sc的定义脚本2.2 使用…

Redis安装与启动

概念 简介&#xff1a;Redis是基于内存的数据结构存储系统&#xff0c;它可以用作:数据库、缓存和消息中间件。特点&#xff1a;Redis是用C语言开发的一个开源的高性能健值对(key-value)数据库&#xff0c;官方提供的数据是每秒内查询次数十万加。它存储的value类型比较丰富&a…

问麻了…阿里一面索命27问,过了就60W+

前言 在40岁老架构师尼恩的&#xff08;50&#xff09;读者社区中&#xff0c;经常有小伙伴&#xff0c;需要面试阿里、 百度、头条、美团、京东等大厂。 下面是一个小伙伴成功拿到通过了阿里三次技术面试&#xff0c;小伙伴通过三个多小时技术拷问&#xff0c;最终拿到 offe…

git修改commit的注释内容

文章目录 1. 查看所有commit2. 修改最近一次commit注释3. 修改某一次commit注释 要修改 Git 中的 commit 注释内容&#xff0c;可以使用 git commit --amend 命令。具体步骤如下&#xff1a; 1. 查看所有commit 运行 git log --oneline 命令&#xff0c;查看需要修改的 commi…

202316读书笔记|《作家榜名著:花间集》——花半坼,雨初晴,满身香雾簇朝霞,娇艳轻盈香雪腻

202316读书笔记|《作家榜名著&#xff1a;花间集》——花半坼&#xff0c;雨初晴&#xff0c;满身香雾簇朝霞&#xff0c;娇艳轻盈香雪腻 《作家榜名著&#xff1a;花间集》作者赵崇祚 房开江。这里有绮丽的艳词&#xff0c;缱绻的缠绵&#xff0c;温婉绵延的思愁。或慵懒梳洗迟…

springboot+vue.js学生宿舍报修信息管理系统68ozj

本学生宿舍信息管理系统管理员&#xff0c;学生&#xff0c;维修人员&#xff0c;商家四个角色。管理员功能有个人中心&#xff0c;班级管理&#xff0c;学生管理&#xff0c;维修人员管理&#xff0c;商家管理&#xff0c;宿舍信息管理&#xff0c;宿舍安排管理&#xff0c;报…

【沐风老师】详解3DMAX一键破损插件PolyDamage使用方法

3DMAX一键破损插件教程 3dMax一键破损插件PolyDamage是一种快速添加模型损坏和缺陷的工具。现实生活中没有什么是完美的,所以给你的模型增加一些破损会看起来更逼真。PolyDamage是使用其他软件包手动雕刻损伤的快速替代方案。PolyDamage为混凝土墙、地面、石头和柱子增添了…