C++ --> string类模拟实现(附源码)

简介: C++ --> string类模拟实现(附源码)

前言:

C++中STL扮演着极其重要的角色,学习C++重中之重的就是学习STL,虽然string不作为containers的其中一员,但是也是值得学习的le类。下面就进行string的模拟实现

string的模拟实现和顺序表是差不多,就是增加了C++的特性。

string 模拟实现

存储结构

结构上使用命名空间mystr进行封装,防止与库冲突,使用class封装成为对象string:

  • 定义 _size 方便记录string的大小。
  • 定义 _capacity方便记录string的容量大小
  • 定义 char* _str是存储数据,进行动态new出空间
  • nopsnpos用于表示在字符串中未找到所查找的子串,或者表示一个超出字符串长度的有效索引位置。npos的值通常被定义为std::size_t类型的最大值,这是一个无符号整数类型,因此npos实际上是一个非常大的正整数,用于表示没有找到匹配项或字符串的结束位置.
namespace mystr
{
    class string
    {
        public:
        static const size_t npos = -1;
        private:    
        size_t _size;
        size_t _capacity;
        char* _str;
    };
}

默认构造函数

构造函数

  • 全缺省的构造函数也是默认构造函数,结尾给""常量字符串末尾存在\0;
  1. 默认构造函数:全缺省、无参、编译器默认生成的构造函数称之为默认构造函数
  • 采取初始化列表,对于常量引用可以进行初始化。strlen计算出大小,初始化_size
    2. 注意:初始化顺序就是声明的顺序,这个也是为什么将char* _str放在最后;
  • _capacoty初始化容量+1的目的是给\0开辟空间;
  • 最后将str进行拷贝,这里采用memcpy不采用strcpy
    3. memcpy可以将全部内容拷贝,strcpy会识别\0停止,假使字符串hello\0 world构造就不会得到我们想要的结果
string(const char* str = "")
    :_size(strlen(str))
        , _capacity(_size)
        , _str(new char[_capacity + 1])
    { 
        memcpy(_str, str, _capacity + 1);
    }

拷贝构造函数

  • 众所周知的是当不存在拷贝构造函数,编译器会自动生成拷贝构造函数;
  1. 编译器生成的仅仅会实现数值上的拷贝(浅拷贝)
  2. _strnew出空间,要实现内容上的拷贝(深拷贝)
  • _capacoty初始化容量+1的目的是给\0开辟空间;
string(const string& s)
{
    _capacity = s._capacity;
    _size = s._size;
    _str = new char[s._capacity+1];
    memcpy(_str, s._str, _capacity+1);
}

赋值运算符重载

赋值预算符重载的底层逻辑是和拷贝构造函数是一样的,在这里就不过多介绍了

string& operator=(const string& s)
{

    _capacity = s._capacity;
    _size = s._size;
    _str = new char[_capacity + 1];
    memcpy(_str, s._str, _capacity + 1);

}

析构函数

程序的整个历程开辟空间,不进行空间的释放不是一个好的习惯,这里析构函数就要上场了

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

迭代器(iterator)

  • string的迭代器的本质就是指针,根据C语言的指针很容易就可以理解,就是将 char * 进行
    typefed char* iterator
  • 迭代器实现两个版本 const非const只读可读可写

begin()

iterator begin()
{
    return _str;
}

iterator begin()const 
{
    return _str;
}

end()

iterator end()
{
    return _str + _size;
}

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

容量(capacity)

size

  • mstr::string类定义了_size直接将其返回
size_t size()const
{
return _size;
}

capacity

  • mstr::string类定义了_capacity直接将其返回
size_t capacity() const
{
    return _capacity;
}

resize

  • resize是将后面指定大小初始化指定的字符(缺省:\0
  • 进行容量的检查,不够扩容
  • 一层循环初始化为ch
  • 修改_size长度为n
void resize(size_t n,char ch = '\0')
{
    if (n < _size)
    {
        _str[n ] = '\0';
        _size = n;
    }
    else
    {
        reverse(n);

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

        _str[_size] = '\0';
        _size = n;
    }

}

reverse

  • 由于很多地方进行复用,需要在函数内部进行判断提高效率
  • 开辟一个大于原始空间的新的空间,将 _str拷贝过去,改变_str的指向,将新开辟的空间释放谨防内存泄漏
void reverse(size_t n)
{
    if (n > _capacity)
    {
        char* tmp = new char[n + 1];
        memcpy(tmp, _str, _capacity + 1);

        delete[] _str;
        _capacity = n;
        _str = tmp;
    }

}

empty

直接判断_size是否为0即可

bool empty()const
{
return _size == 0 ? true : false;
}

clear

  • 直接将首位置赋值为\0,修改_size大小即可
void clear()
{
    _str[0] = '\0';
    _size = 0;
}

修改(modify)

push_back

C++string中 push_back 函数声明:void push_back (char c);在字符串后追加一个字符;

  • 开始要检查容量 进行扩容这里用 reverse 实现
  • 由于一个字符,仅仅需要在_size位置上直接赋值,在_size+1的位置上赋值\0
 void push_back(char ch)
{
    if (_size == _capacity)
    {
        reverse(_capacity == 0 ? 4 : _capacity * 2);
    }

    _str[_size] = ch;
    _str[_size + 1] = '\0';
    _size++;
}


append

  • append后面追加一个字符可以进行复用 push_back
  • append后面追加一个字符串和添加一个string类是一样的思路是一样;
//追加一个字符
void append(const char ch)
{
push_back(ch);
}
//追加一个字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reverse(_capacity + len);
}
memcpy(_str + _size, str, len + 1);
}
//追加一个string类
void append(const string& str)
{
if (_size + str._size == _capacity)
{
reverse(_size + str._size);
}

memcpy(_str + _size, str._str, str._size+1);
}

operator+=

由于是实现了append实现运算符重载就方便跟多,功能一样,结果一样直接进行复用

//追加一个字符
string& operator+= (const char* str)
{
    append(str);

    return *this;
}
//追加一个字符串
string& operator+= (const string& str)
{
    append(str._str);

    return *this;
}
//追加一个string类
string& operator+= (char ch)
{
    push_back(ch);
    return *this;
}

inset

  • 实现两个版本,插入字符和插入字符串
  • string类是没有实现push_front头插入,insert就有很大的作用了在pos位置进行插入
  • 首先进行pos位置的断言,保证pos在字符串的有效位置
  • 进行容量检查,进行扩容.
  • pos位置后面的字符(以及pos位置)依次向后面移len个长度,在pos位置插入字符(字符串)
  • i!= nposfor循环中如果i变成了0,之后再去减1 ,size_t下的-1会变为无线大,会陷入死循环。
  • 最后不要忘记将_size进行修改
//插入字符
string& insert(size_t pos, char c)
{
    assert(pos < _size);

    if (_capacity == _size)
    {
        reverse(_size+1);
    }

    for (size_t i = _size; i >= pos&& i!= npos; i--)
    {
        _str[i + 1] = _str[i];
    }
    _str[pos] = c;
    _size++;

    return *this;
}
//插入字符串
string& insert(size_t pos, const char* str)
{
    assert(pos < _size);
    size_t len = strlen(str);
    if (_capacity == _size)
    {     
        reverse(_size + len);
    }

    for (size_t i = _size; i >= pos&& i!= npos; i--)
    {
        _str[i + len] = _str[i];

    }
    memcpy(_str + pos, str, len);
    _size+=len;

    return *this;

}

erase

  • 首先进行pos位置的断言,保证pos在字符串的有效位置
  • erase是在pos位置删除len个字符(缺省值:npos
  • 函数主体进入进len的判断,如果len == npos pos + len >= _size超出字符串的长度,就是从pos后全部删除
  • 否则没有超过将pos + len位置后面的数据将 pos位置移动直至移动到\0
string& erase(size_t pos, size_t len = npos)
{
    assert(pos < _size);
    if (len == npos || pos + len >= _size)
    {
        _str[pos] = '\0';
        _size = pos;

    }
    else
    {
        size_t end = pos + len;

        while (end <= _size)
        {
            _str[pos++] = _str[end++];
        }

        _size -= len;
    }

}

swap

  • 利用C++库中的swap进行string类的交换
void swap(string& s)
{
string tmp(s);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}

元素访问(Element access)

operator [ ]

  • 实现const非const两种,只读和可读可改
  • 充分利用字符串特性可以进行下标的访问
//const
char& operator[](size_t index)
{
    assert(index < _size);

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

    return _str[index];
}

字符串操作(String operations)

c_str

  • 直接返回_str
const char* c_str()
{
    return _str;
}

find

  • 首先进行pos位置的断言,保证pos在字符串的有效位置
  • 字符串查找利用C语言中的strstr函数进行查找
  • 返回下面的下标,利用指针减指针的方法,没有找到返回npos
size_t find(const char* s, size_t pos = 0) const
{
    assert(pos < _size);
    char* p = strstr(_str, s);

    if (p)
    {
        return p - _str;
    }
    else
    {
        return npos;
    }

}

关系运算符(relational operators)

进行比较的重载

  • 实现< ==其他的进行复用即可
  • 使用memcpy进行比较,比较字符串较小的那个_size < s._size ?_size : s._size
  • 返回 为 0 返回比较长(_size < s._size)的那个否则返回假( ret < 0
//重载<
bool operator<(const string& s)
{
    int ret = memcmp(_str, s._str, (_size < s._size ?_size : s._size));

    return ret == 0 ? _size < s._size : ret < 0;
}
//重载==
bool operator==(const string& s)
{
    return _size == s._size && memcmp(_str, s._str, _size);
}
//重载<=
bool operator<=(const string& s)
{
    return !(*this > s);
}
//重载>
bool operator>(const string& s)
{
    return !(*this <= s);
}
//重载>=
bool operator>=(const string& s)
{
    return !(*this < s);
}
//重载!=
bool operator!=(const string& s)
{
    return !(*this == s);
}

流提取和插

operator<<

  • 这个函数是写在类外面的一个友元函数
  • 使用范围for进行实现
ostream& operator<<(ostream& out, const mystr::string& s)
{
  for (auto ch : s)
  {
    out << ch;
  }
  return out;
}

operator>>

  • s.clear();这句话是清理缓冲区上次cin的残留
  • 第一个while循环是处理缓冲区的空格
  • 创建一个数组,避免多次开辟空间,直至大小到128拷贝会加到string s 中
  • 最后的if语句是字符遇见空格或者换行结束,末尾添加\0
istream& operator>>(istream& in, string& s)
{
    s.clear();

    char ch = in.get();
    // 处理前缓冲区前面的空格或者换行
    while (ch == ' ' || ch == '\n')
    {
        ch = in.get();
    }
    char buff[128];
    int i = 0;

    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        if (i == 127)
        {
            buff[i] = '\0';
            s += buff;
            i = 0;
        }

        ch = in.get();
    }

    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }

    return in;
}

mystr:: string 源码

#pragma once

#include<iostream>

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

using namespace std;


namespace mystr
{
  class string
  {
    friend ostream& operator<<(ostream& _cout, const mystr::string& s);

    friend istream& operator>>(istream& _cin,mystr::string& s);
  public:
    typedef char* iterator;
    typedef const char* const_iterator;


    string(const char* str = "")
      :_size(strlen(str))
      , _capacity(_size)
      , _str(new char[_capacity + 1])
    { 
      memcpy(_str, str, _capacity + 1);
    }
    //析构函数
    ~string()
    {
      _size = 0;
      _capacity = 0;
      delete[] _str;
      _str = nullptr;
    }
    // 拷贝构造函数
    string(const string& s)
    {
      _capacity = s._capacity;
      _size = s._size;
      _str = new char[s._capacity+1];
      memcpy(_str, s._str, _capacity+1);
    }
    //赋值预算符重载
    string& operator=(const string& s)
    {

      _capacity = s._capacity;
      _size = s._size;
      _str = new char[_capacity + 1];
      memcpy(_str, s._str, _capacity + 1);

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

    //迭代器
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    iterator begin()const 
    {
      return _str;
    }
    iterator end()const
    {
      return _str + _size;
    }
    
    //capacity

    size_t size()const
    {
      return _size;
    }
    size_t capacity() const
    {
      return _capacity;
    }

    void reverse(size_t n)
    {
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];
        memcpy(tmp, _str, _capacity + 1);

        delete[] _str;
        _capacity = n;
        _str = tmp;
      }

    }
    void resize(size_t n,char ch = '\0')
    {
      if (n < _size)
      {
        _str[n ] = '\0';
        _size = n;
      }
      else
      {
        reverse(n);

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

        _str[_size] = '\0';
        _size = n;
      }

    }

    bool empty()const
    {
      return _size == 0 ? true : false;
    }
    
    //access

    //modify
    
    void push_back(char ch)
    {
      if (_size == _capacity)
      {
        reverse(_capacity == 0 ? 4 : _capacity * 2);
      }

      _str[_size] = ch;
      _str[_size + 1] = '\0';
      _size++;
    }

    void append(const char ch)
    {
      push_back(ch);
    }
    void append(const char* str)
    {
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reverse(_capacity + len);
      }
      memcpy(_str + _size, str, len + 1);
    }

    void append(const string& str)
    {
      if (_size + str._size == _capacity)
      {
        reverse(_size + str._size);
      }

      memcpy(_str + _size, str._str, str._size+1);
    }


    string& operator+= (const char* str)
    {
      append(str);

      return *this;
    }
    string& operator+= (const string& str)
    {
      append(str._str);

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

    void clear()
    {
      _str[0] = '\0';
      _size = 0;
    }
    void swap(string& s)
    {
      string tmp(s);
      std::swap(_str, tmp._str);
      std::swap(_size, tmp._size);
      std::swap(_capacity, tmp._capacity);
    }
    //relational operators
    bool operator<(const string& s)
    {
      int ret = memcmp(_str, s._str, (_size < s._size ?_size : s._size));

      return ret == 0 ? _size < s._size : ret < 0;
    }

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

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


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

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

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

    // 返回c在string中第一次出现的位置

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

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

      return npos;
    }


    // 返回子串s在string中第一次出现的位置

    size_t find(const char* s, size_t pos = 0) const
    {
      assert(pos < _size);
      char* p = strstr(_str, s);

      if (p)
      {
        return p - _str;
      }
      else
      {
        return npos;
      }
    
    }

    // 在pos位置上插入字符c/字符串str,并返回该字符的位置

    string& insert(size_t pos, char c)
    {
      assert(pos < _size);

      if (_capacity == _size)
      {
        reverse(_size+1);
      }

      for (size_t i = _size; i >= pos; i--)
      {
        _str[i + 1] = _str[i];
      }
      _str[pos] = c;
      _size++;
    
      return *this;
    }

    string& insert(size_t pos, const char* str)
    {
      assert(pos < _size);
      size_t len = strlen(str);
      if (_capacity == _size)
      {     
        reverse(_size + len);
      }

      for (size_t i = _size; i >= pos; i--)
      {
        _str[i + len] = _str[i];

      }
      memcpy(_str + pos, str, len);
      _size+=len;

      return *this;
    
    }

    string& erase(size_t pos, size_t len = npos)
    {
      assert(pos < _size);

      if (_capacity == _size)
      {
        reverse(_size + len);

      }
      if (len == npos || pos + len >= _size)
      {
        _str[pos] = '\0';
        _size = pos;

      }
      else
      {
        size_t end = pos + len;

        while (end <= _size)
        {
          _str[pos++] = _str[end++];
        }

        _size -= len;
      }
    
    }

  private:    
    size_t _size;

    size_t _capacity;
    char* _str;

    public:

    
    const static size_t npos;
  };

  const size_t string::npos = -1;
  
  ostream& operator<<(ostream& out, const mystr::string& s)
  {
    for (auto ch : s)
    {
      out << ch;
    }
    return out;
  }

  istream& operator>>(istream& in, string& s)
  {
    //清除缓冲区
    s.clear();

    char ch = in.get();

    while (ch == ' ' || ch == '\n')
    {
      ch = in.get();
    }

    char buff[128];
    int i = 0;

    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == 127)
      {
        buff[i] = '\0';
        s += buff;
        i = 0;
      }
      ch = in.get();
    }

    if (i!= 0)
    {
      buff[i] = '\0';
      s += buff;
    }

    return in;

  }

}                                                                                              

目录
相关文章
|
4月前
|
C++
基本二叉树与排序二叉树(C++源码)
本程序实现二叉树基本操作与二叉排序树应用。支持前序建树、四种遍历、求深度、叶子数、第K层节点数及查找功能;并实现二叉排序树的构建、中序输出与查找比较次数统计,分析不同插入顺序对树形态和查找效率的影响。
|
4月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
345 5
|
8月前
|
对象存储 C++ 容器
c++的string一键介绍
这篇文章旨在帮助读者回忆如何使用string,并提醒注意事项。它不是一篇详细的功能介绍,而是一篇润色文章。先展示重载函数,如果该函数一笔不可带过,就先展示英文原档(附带翻译),最后展示代码实现与举例可以直接去看英文文档,也可以看本篇文章,但是更建议去看英文原档。那么废话少说直接开始进行挨个介绍。
161 3
|
10月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
407 12
|
8月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
213 0
|
8月前
|
存储 编译器 C语言
关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论
你真的了解string的'\0'么?你知道创建一个string a("abcddddddddddddddddddddddddd", 16);这样的string对象要创建多少个对象么?你知道string与vector进行扩容时进行了怎么的操作么?你知道怎么求Vector 最大 最小值 索引 位置么?
207 0
|
8月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
344 0
|
11月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
11月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
11月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!