STL常用梳理——STRING

STL简介

STL(Standard Template Library,标准模板库),是惠普实验室开发的一系列软件的统 称。现在主要出现在 c++中,但是在引入 c++之前该技术已经存在很长时间了。 STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL 几乎所有的代码都采用了模板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。
STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。 算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte. 迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator–等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。 仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template 。适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。 空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.

STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。
在这里插入图片描述

String常见接口函数

参考标准库中接口函数了解参数及使用使用场景可以更好上手[string]。对于String的接口函数是比较冗余的。在每一个接口函数中实现的函数重载比较繁多。因为早期实现时的参考不足,作为字符串的有关类函数。实现接口函数就要考虑是字符还是字符串。因为是String的底层存储空间是连续的,可以理解为一个操作字符串的顺序表。所以函数的接口十分类似。
字符串相加

算法:数学加式实现,决定字符串从尾向前,对于结果保存和进位保存。加法进位后,保存结果需要转换成字符串保存,便于返回。最后需要进行进位检查。翻转字符串。

class Solution {
    
    
public:
    string addStrings(string num1, string num2) {
    
    
        int len1=num1.size()-1;
        int len2=num2.size()-1;
        int flag=0;
        string str="";
        while(len1>=0||len2>=0)
        {
    
    
            int ret1=len1>=0?num1[len1--]-'0':0;
            int ret2=len2>=0?num2[len2--]-'0':0;

            int ret=ret1+ret2+flag;
            
            if(ret>9)
            {
    
    
                flag=ret/10;
                ret%=10;
            }
            else
            {
    
    
                flag=0;
            }
        str+=(ret+'0');
        }
        if(flag) str+='1';
        reverse(str.begin(),str.end());
        return str;
    }
};

通过该题的算法实现。使用到的STL中的容器(string)、迭代器、算法。可以看出String使用场景大多遍历字符串,修改字符,翻转字符……;和C语言中字符函数(str)功能有着异曲同工。所以对于string大多就是掌握字符串查找修改。

深度了解String

熟悉函数常见的接口函数及其使用场景,就可以按照这样的套路进行更深层次的了解。可以参考C语言的str函数来进行接口函数的功能封装。
对于类的模拟实现需要实现他的6个默认成员函数。虽然规带默认的成员函数会如果没有编写,编译器会自动生成。但是对于默认生成的拷贝构造函数会有实现时深浅拷贝问题。编译器直接生成的进行内置类型的传递赋值会出现浅拷贝问题。浅拷贝结构示意图:
在这里插入图片描述

对于空间会自己指向同一个内存空间地址。在进行析构函数释放空间时,对于同一个空间释放2次。编译器会自己检查空间的释放过程。

构造函数

 string(const char* str = "")
            :_size(strlen(str))
        {
    
    
            _capacity = _size == 0 ? 3 : _size;//避免扩容时容量为0
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

拷贝构造


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

赋值重载

  string& operator=(const string& s)
        {
    
    
            if(*this!=s)
            {
    
    
                char* tmp = new char[s._capacity + 1];
                strcpy(tmp, _str);
                delete[] _str;
                
                _str = tmp;

                _size = s._size;
                _capacity = s._capacity;
            }

            return *this;
        }

析构函数

   ~string()
    {
    
    

        delete[] _str;
        _str = nullptr;
        _size = _capacity = 0;
    }

运算符重载

对于字符串的比较字符串大小,进行字符的ascll码比较。就是运算符的重载实现。其余运算符重载可以进行复用和逻辑与、逻辑或操作实现。

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

查找

实现的方法就是类似于数据结构中的顺序表。实现插入时需要实现字符串的出入。对于空间的开辟,C++中通常是搭配使用管理内存,扩容是对于C语言中的realloc不会使用,如果对于空间开辟和释放不配套使用。编译器会报错处理,析构函数实现也不友好。String自己实现函数reverse。

 void reserve(size_t n)
        {
    
    
            char* tmp = new char[n + 1];
            strcpy(tmp, _str);
            delete[] _str;

            _str = tmp;

            _capacity = n;
        }

对于String中涉及内存相关的,都是开空间,拷贝数据,释放空间

在返回值的接受时使用引用接受返回值,参数传递时也可以使用引用传参减少拷贝带来的消耗提升效率。写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

 string& erase(size_t pos, size_t len)
        {
    
    
            assert(pos < _size);
            //长度大于字符长度,pos位置后直接删除
            if (pos == npos || pos + len >= _size)
            {
    
    
                _str[pos] = '\0';
                _size = pos;

            }
            else
            {
    
    
                //长度在字符串长度之内,直接用pos位置后字符串覆盖
                strcpy(_str + pos, pos + len + _str);
                _size -= len;
            }
            return *this;
        }
 string& insert(size_t pos, const char* str)
        {
    
    
            assert(pos <= _size);
            size_t len = strlen(str);
            
            if (_capacity < len + _size)
            {
    
    
                reserve(len + _size);
            }
            size_t end = _size + len;
//挪动数据    
            while (end >= pos)
            {
    
    
                _str[end] = _str[end - len];
                end--;

            }
            //拷贝数据
            strncpy(_str+pos, str, len);
            _size += len;

            return *this;
        }

猜你喜欢

转载自blog.csdn.net/github_73587650/article/details/130451976