STL之string

目录

  • 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,并且使用std::命名空间
STL之string_第1张图片

_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 
#include 
#include 

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. STL之string_第2张图片

当此值用作字符串成员函数中的 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输入,可以减少扩容次数。


观看~~

你可能感兴趣的:(一块来学C++,数据结构,c++,开发语言)