C++string的使用及模拟(2)

简介: C++string的使用及模拟(2)

4、string类对象修改操作


image.png


  • 使用示例1:


void Teststring6()
{
  string str;
  cout << str << endl;
  str.push_back('c'); // 在str后插入字符
  cout << str << endl;
  str.append("hello"); // 在str后追加一个字符"hello"
  cout << str << endl;
  str += '\0'; // 在str后追加结束符
  str += "cole"; // 在str后追加一个字符串"cole"
  cout << str << endl;//即使遇到‘\0’也不会停止,只有把字符串给完全遍历完了才会停止遍历
  cout << str.c_str() << endl; // 以C语言的方式打印字符串(遇到\0就停止)
}


image.png


  • 使用示例2:


void Teststring7()
{
  // 获取file的后缀
  string file("string.cpp");
  size_t pos = file.rfind('.');//从后往前找字符. 
  string suffix(file.substr(pos, file.size() - pos));//拷贝pos位置之后的字符串
  cout << suffix << endl;
  // 取出url中的域名
  string url("http://www.cplusplus.com/reference/string/string/find/");
  cout << url << endl;
  size_t start = url.find("://");//从前往后找字符串 
  if (start == string::npos)//没找到返回npos
  {
    cout << "invalid url" << endl;
    return;
  }
  start += 3;
  size_t finish = url.find('/', start);//再往后找结束位置
  string address = url.substr(start, finish - start);//拷贝子串
  cout << address << endl;
  // 删除url的协议前缀
  pos = url.find("://");
  url.erase(0, pos + 3);
  cout << url << endl;
}


image.png


注:npos是string里面的一个静态成员变量


static const size_t npos = -1;


注意:

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


对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好(提高效率)


5、string类非成员函数


image.png


注:对上述函数只是了解一下,对于其他的操作函数可以查阅文档了解


三、模拟实现string类


注:这里我们只是模拟实现string的一些常用接口,并非要完全复刻,学习下string类的底层,让对string类的理解更深一点就行了


1、实现string类接口展示


注:模拟时为了避免与C++本身提供的string类造成命名冲突,我们选择在命名空间里进行实现


namespace cole
{
    class string
    {
        friend ostream& operator<<(ostream& _cout, const cole::string& s);
        friend istream& operator>>(istream& _cin, cole::string& s);
        friend istream& getline(istream& _cin, string& s);
    public:
        typedef char* iterator;
        typedef const char* const_iterator;
        string(const char* str = "");
        string(const string& s);
        string& operator=(const string& s);
        ~string();
        void swap(string& s);
        // iterator
        iterator begin();
        iterator end();
        const_iterator begin()const;
        const_iterator end()const;
        // modify
        void push_back(char c);
        string& operator+=(char c);
        void append(const char* str);
        string& operator+=(const char* str);
        void clear();
        const char* c_str()const;
        // capacity
        size_t size()const;
        size_t capacity()const;
        bool empty()const;
        void resize(size_t n, char c = '\0');
        void reserve(size_t n);
        // access
        char& operator[](size_t index);
        const char& operator[](size_t index)const;
        //relational operator
        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);
        // 返回c在string中第一次出现的位置
        size_t find(char c, size_t pos = 0) const;
        // 返回子串s在string中第一次出现的位置
        size_t find(const char* s, size_t pos = 0) const;
        // 在pos位置上插入字符c/字符串str,并返回该字符
        string& insert(size_t pos, char c);
        string& insert(size_t pos, const char* str);
        // 删除pos位置上的长度个字符
        string& erase(size_t pos, size_t len=npos);
    private:
        char* _str;
        size_t _capacity;
        size_t _size;
        static const size_t npos;
    };
    //初始化静态私有成员变量
    const size_t string::npos = -1;
}


2、深浅拷贝问题


一般来说对于只有内置类型的成员变量,我们可以选择编译器提供的拷贝构造函数和赋值重载函数(自己不写),编译器提供的这两个函数能够完成浅拷贝(拷贝值),但是对于存在指针成员变量的类,浅拷贝是不行的


  • 示例:


namespace cole
{
  class string
  {
  public:
    string(const char* str = "")
    {
      // 断言不为空指针
      assert(str);
      _str = new char[strlen(str) + 1];
      strcpy(_str, str);
    }
    ~string()
    {
      if (_str)
      {
        delete[] _str;
        _str = nullptr;
      }
    }
  private:
    char* _str;
  };
  // 测试
  void Teststring()
  {
    string s1("hello cole");
    string s2(s1);
  }
}
int main()
{
  cole::Teststring();
  return 0;
}


  • 结果:


image.png


  • 解释:


image.png


即值拷贝(浅拷贝)让s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,由此对于涉及资源的管理,我们需要自己以深拷贝的方式写拷贝构造函数和赋值重载函数


3、string类深拷贝写法


  • 深拷贝概念:


给每个对象独立分配资源,保证多个对象之间不会因为共享资源而造成多种错误以及程序释放崩溃的问题


  • 传统式:比较常规,易于理解


class string
{
public:
    //构造函数
    string(const char* str = "")
    {
        // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
        assert(str);
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    //拷贝构造函数
    string(const string& s)
        : _str(new char[strlen(s._str) + 1])
    {
        strcpy(_str, s._str);
    }
    //赋值
    string& operator=(const string& s)
    {
        if (this != &s)//避免自己赋值自己(没有意义)
        {
            delete[]_str;
            _str = new char[strlen(s._str) + 1];
            strcpy(_str, s._str);
        }
        return *this;
    }
    //析构函数
    ~string()
    {
        delete[]_str;
        _str = nullptr;
    }
private:
    char* _str;
};


  • 现代式:简洁,效果一致


class string
{
public:
  //构造函数
  string(const char* str = "")
  {
    if (nullptr == str)//处理空指针
      str = "";
    _str = new char[strlen(str) + 1];
    strcpy(_str, str);
  }
  //拷贝构造
  string(const string& s)
    : _str(nullptr)//将自己赋值为空
  {
    string strTmp(s._str);//调用构造函数,生成一个具有相同内容的string
    swap(_str, strTmp._str);//将string对象的各成员变量相互交换
  }//拷贝构造结束strTmp会先析构再销毁
  //赋值重载
  string& operator=(string s)//传值参数,会调用拷贝构造,生成一个具有相同内容的string
  {
    swap(_str, s._str);//将string对象的各成员变量相互交换
    return *this;
  }//赋值重载结束束s会先析构再销毁
  //析构函数
  ~string()
  {
    delete[] _str;
    _str = nullptr;
  }
private:
  char* _str;
};



4、string类其他常用接口模拟


  • 实现代码:


ostream& operator<<(ostream& _cout, const string& s)
{
    for (auto ch : s)//会替换成string的迭代器
    {
        _cout << ch;
    }
    return _cout;
}
istream& operator>>(istream& _cin, string& s)
{
    s.clear();//清理
    char ch;
    ch = _cin.get();//能够接收空格和结束符
    while (ch != ' ' && ch != '\n')
    {
        s += ch;
        ch = _cin.get();
    }
    return _cin;
}
istream& getline(istream& _cin, string& s)
{
    s.clear();
    char ch;
    ch = _cin.get();
    while (ch != '\n')
    {
        s += ch;
        ch = _cin.get();
    }
    return _cin;
}
//构造函数
string::string(const char* str="")
{
    if(str==nullptr)
        str="";
    _str = new char[strlen(str) + 1];
    _size = strlen(str);
    _capacity = _size;
    strcpy(_str, str);
}
//交换string
void string::swap(string& s)
{
    ::swap(_str, s._str);
    ::swap(_size, s._size);
    ::swap(_capacity, s._capacity);
}
//拷贝构造
string::string(const string& s)
    :_str(nullptr)
    , _size(0)
    , _capacity(0)
{
    string tmp(s._str);
    swap(tmp);
}
//赋值重载
string& string::operator=(const string& s)//传引用
{
    //避免自己给自己赋值
    if (this != &s)
    {
        string tmp(s._str);
        swap(tmp);
    }
    return *this;
}
//析构函数
string::~string()
{
    delete[] _str;
    _str = nullptr;
    _size = 0;
    _capacity = 0;
}
// iterator(普通对象)
string::iterator string::begin()
{
    return _str;
}
string::iterator string::end()
{
    return _str + _size;
}
// iterator(const对象)
string::const_iterator string::begin()const
{
    return _str;
}
string::const_iterator string::end()const
{
    return _str + _size;
}
// modify
//尾插字符
void string::push_back(char c)
{
    满空间就扩容
    //if (_size == _capacity)
    //{
    //    reserve(_capacity == 0 ? 4 : _capacity * 2);
    //}
    //_str[_size] = c;
    //_size++;
    insert(_size, c);
}
string& string::operator+=(char c)
{
    push_back(c);
    return *this;
}
//追加字符串
void string::append(const char* str)
{
    /*size_t len = _size + strlen(str);
    if (len > _capacity)
    {
        reserve(len);
    }
    strcpy(_str + _size, str);
    _size = len;*/
    insert(_size, str);
}
string& string::operator+=(const char* str)
{
    append(str);
    return *this;
}
void string::clear()
{
    _str[0] = '\0';
    _size = 0;
}
const char* string::c_str()const
{
    return _str;
}
size_t string::size()const
{
    return _size;
}
size_t string::capacity()const
{
    return _capacity;
}
bool string::empty()const
{
    return _size == 0;
}
//开空间+初始
void string::resize(size_t n, char c)
{
    if (n < _size)
    {
        _str[n] = '\0';
        _size = n;
    }
    else
    {
        if (n > _capacity)
        {
            reserve(n);
        }
        int pos = _size;
        while (pos < n)
            _str[pos++] = c;
        _str[n] = '\0';
        _size = n;
    }
}
//开空间
void string::reserve(size_t n)
{
    //需要的空间大于容量就开空间
    if (n > _capacity)
    { 
        char* tmp = new char[n + 1];
        strncpy(tmp, _str, _size + 1);//strcpy不能将\0后的内容也拷贝
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}
// access
char& string::operator[](size_t index)
{
    assert(index < _size);
    return _str[index];
}
const char& string::operator[](size_t index)const
{
    assert(index < _size);
    return _str[index];
}
//relational operator
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 strcmp(_str, s._str);
}
bool string::operator!=(const string& s)
{
    return !(*this == s);
}
// 返回c在string中第一次出现的位置
size_t string::find(char c, size_t pos) const
{
    //pos的合理性
    assert(pos < _size);
    for (size_t i = pos; i < _size; i++)
    {
        if (_str[i] == c)
            return i;
    }
    //没找到
    return npos;
}
// 返回子串s在string中第一次出现的位置
size_t string::find(const char* s, size_t pos) const
{
    //pos的合理性
    assert(pos < _size);
    const char* ret = strstr(_str + pos, s);
    if (ret)
        return ret - _str;
    //没找到
    return npos;
}
// 在pos位置上插入字符c/字符串str
string& string::insert(size_t pos, char c)
{
    assert(pos <= _size);
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }
    char* end = _str + _size;
    while (end >= _str + pos)
    {
        *(end + 1) = *end;
        end--;
    }
    _str[pos] = c;
    _size++;
    return *this;
}
string& string::insert(size_t pos, const char* str)
{
    assert(pos <= _size);
    size_t len1 = strlen(str);
    size_t len2 = len1 + _size;
    if (len2 > _capacity)
    {
        reserve(len2);
    }
    char* end = _str + _size;
    while (end >= _str + pos)
    {
        *(end + len1) = *end;
        end--;
    }
    strncpy(_str + pos, str, len1);
    _size += len1;
    return *this;
}
// 删除pos位置上的长度个字符
string& string::erase(size_t pos, size_t len)
{
    assert(pos < _size);
    size_t leftlen = _size - pos - 1;
    if (len >= leftlen)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }
    return *this;
}
相关文章
|
4天前
|
算法 C++ 容器
【C++】string模拟实现
【C++】string模拟实现
12 1
|
4天前
|
C语言 C++
【C++】string类(常用接口)
【C++】string类(常用接口)
21 1
|
2天前
|
存储 算法 搜索推荐
C++|STL简介-string-vector基础运用
C++|STL简介-string-vector基础运用
|
4天前
|
C语言 C++ 容器
C++ string类
C++ string类
9 0
|
4天前
|
编译器 C++
【C++】继续学习 string类 吧
首先不得不说的是由于历史原因,string的接口多达130多个,简直冗杂… 所以学习过程中,我们只需要选取常用的,好用的来进行使用即可(有种垃圾堆里翻美食的感觉)
9 1
|
4天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽
16 0
|
4天前
|
存储 安全 测试技术
【C++】string学习 — 手搓string类项目
C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。
18 0
【C++】string学习 — 手搓string类项目
|
4天前
|
存储 C++ 容器
【C++从练气到飞升】09---string语法指南(二)
【C++从练气到飞升】09---string语法指南(二)
|
4天前
|
存储 Linux C语言
【C++从练气到飞升】09---string语法指南(一)
【C++从练气到飞升】09---string语法指南(一)
|
4天前
|
C++
【C++】string类(介绍、常用接口)
【C++】string类(介绍、常用接口)
18 2