C++模拟实现string的方法详解

目录
  • 1.string 成员变量
  • 2.构造函数
  • 3.拷贝构造、赋值重载和析构函数
    • 1.拷贝构造
    • 2.赋值重载
    • 3.析构函数
  • 4.访问成员变量
  • 5.遍历
    • 1.下标+【】
    • 2.迭代器(iterator)
    • 3.范围for
  • 6.空间的申请
    • 1.reserve
    • 2.resize
  • 7.增删查改
  • 8.重载cin 和 cout
    • 1.cout
    • 2.cin

1.string 成员变量

首先需要一个动态开辟的指针指向这个字符串,然后还需要容量和存储的个数,并且我们不能和标准库的string进行冲突所以我们需要写在我们自己的类域中,并且我们库中还有一个静态的变量是npos,就是无符号的-1,代表整形的最大值:

namespace cyf
{
    class string
    {
    public:
        //成员函数
    private:
        char *_str;
        size_t size;
        size_t capaticy;
        const static size_t npos = -1;
    };
}

这里有一个特例:static成员变量一般在类中声明在类外定义,但是const static int型的变量可以直接在类中定义。

2.构造函数

strlen求出的是\0之前的字符个数,所以_size和_capacity标识的是实际存储的字符个数,在开辟空间时多开辟一个字符用来存储'\0'。

string(const char* s = "")
        {
            _size = strlen(s);
            _capacity = _size;
            _str = new char[_capacity + 1];   

            strcpy(_str, s);  //开辟好空间后将s的内容拷贝至_str
        }

3.拷贝构造、赋值重载和析构函数

1.拷贝构造

_str 维护的是一块空间,所以不能简单的将s._str的值赋值给_str (浅拷贝),而是单独开辟一块空间,让_str指向这一块空间,再将s._str空间中的值拷贝至新开辟的空间,新开辟的空间比_capacity多开一个字节用来存储'\0',作为字符串的结束标志。

//string s1(s)
string(const string& s)
        {
            _size = s._size;
            _capacity = s._capacity;
            _str = new char[s._capacity + 1];

            strcpy(_str, s._str);
        }

2.赋值重载

首先开辟一块空间,将字符串的内容拷贝至这个空间,将_str原来指向的空间释放,_str再指向这个新开辟的空间,size和capacity还是原来的大小。

//s2=s1
        string& operator=(const string& s)
        {
            if (this != &s)   //避免自己给自己赋值
            {
                char* tmp = new char[s._capacity + 1];
                strcpy(tmp, s._str);
                delete[] _str;
                _str = tmp;
                _size = s._size;
                _capacity = s._capacity;
            }
            return *this;
        }

3.析构函数

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

4.访问成员变量

提供接口可以在类的外边查看字符串的内容,存储字符串的元素个数和容量

        const char* c_str()
        {
            return _str;
        }

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

配合之前的构造函数和这里的接口,我们进行验证:

运行结果:

5.遍历

遍历有三种模式:

1.下标+【】

_str是一个指针,那么我们可以通过数组的方式来访问,只需要重载operator []即可。我们还是要重载两个版本的,因为普通变量和const变量的访问权限不一样。

//普通变量,可读可写
char& operator[](size_t pos)
{
    assert(pos < _size);  //检查不能越界访问

    return _str[pos];
}

//const变量,只读属性
char& operator[](size_t pos) const
{
    assert(pos < _size);

    return _str[pos];
}

2.迭代器(iterator)

在string中,迭代器就是一个指针,只不过我们进行了封装,typedef一下就可以啦,同样我们也要实现两个版本的,const和非const的。

typedef char* iterator;
typedef const char* const_iterator;

iterator begin()
{
    return _str;
}

const_iterator begin()const
{
    return _str;
}

iterator end()
{
    return _str +_size;
}

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

3.范围for

我们范围for的底层就是迭代器,所以我们不用实现,只要实现了迭代器,那么我们就可以直接使用范围for,范围for在执行的时候实际还是通过迭代器实现的,上例子:

运行结果:

6.空间的申请

1.reserve

一般是我们原空间容量满了,需要申请空间扩容,我们的扩容函数还是要先申请空间,然后在进行拷贝,接着我们delete原来的空间,把申请的空间的指针和 容量 赋值过去即可。

void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];    //多开一个字节留给'\0';
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;

                _capacity = n;
            }

        }

2.resize

1. 如果我们是传入一个正整数大于_size的值,那么我们可以使用传入的字符(或者缺省值)把我们申请的空间进行初始化,也就是从_size到n-1置为我们传入的字符,n置为' \0 ',最后把_size置为n。

2.如果传入一个小于_size正整数,那么我们把0~_size-1进行初始化为传入的字符(或者缺省值),把n位置置为' \0 ',接着我们会把_size置为n,而_capaticy不变。

void resize(size_t n, char ch = '\0')
        {
            if (n > _size)
            {
                reserve(n);
                for (size_t i = _size; i < n; ++i)
                {
                    _str[i] = ch;
                }
                _size = n;
                _str[_size] = '\0';

            }

            else   //小于
            {
                _str[n] = '\0';
                _size = n;

            }

    }

7.增删查改

1.push_back   尾插一个字符

上来就检查容量,_size==_capacity时就说明没有容量了,得分类讨论:1.原来的字符串有元素2.原来的是空字符串,如果字符串为空,就给4个字节大小的容量 。如果原来的字符串不为空但是需要扩容就调用reserve函数进行扩容,容量扩为2倍,2倍比较合适,避免给的小了频繁的扩容,但是也不能给的太大了,太大了会造成空间的浪费。在_size的位置插入字符ch ,_size++,插入的字符ch 将原来的'\0'给覆盖了,最后再补上'\0',作为字符串的结束标志。

void push_back(char ch)
        {
            if (_size == _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            _str[_size] = ch;
            ++(_size);
            _str[_size] = '\0'; //记得处理 \0
        }

2.append 尾部插入一个字符串

插入字符串的大小不确定,就需要确定是否需要扩容。当插入的字符串的长度加上当前字符串的有效元素个数大于容量_capacity时,就需要扩容,扩后的容量大小为_size+len ,这里给reverse函数传的是有效元素的个数,在reverse函数内部为我们多开了一个字符的大小用来存储'\0'。再进行拷贝工作,这里记得插入元素后_size要进行变换。

void append(const char* str)
        {
            size_t len = strlen(str);
            if (len + _size > _capacity)
            {
                reserve(_size + len);   

            }
            strcpy(_str + _size, str);
            _size += len;
        }

3.insert  在指定位置插入一个字符

上来首先检查要插入的位置是否合理,_size 代表的位置是有效元素的下一个位置即'\0'的位置,pos的范围【0,_size】string字符串的头到尾部之间 ,插入元素要检查容量,记得考虑原string是否为空串的情况。插入数据需要挪动数据,从前往后挪动数据,将end位置确定到'\0'的下一个位置,这样方便头插。最终将pos位置腾出来,插入字符ch 插入数据后_size++。

string& insert(size_t pos, char ch)
        {
            assert(pos <= _size);  //等于size 时候相当于尾部插入
            if (_size == _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            size_t end = _size + 1;  //这里是把\0往  \0的下一个位置挪动 方便头插
            while (end > pos)
            {
                _str[end] = _str[end - 1];
                --end;
            }
            _str[pos] = ch;
            _size++;
            return *this;
        }

4.insert 在指定位置插入一个字符串

string& insert(size_t pos, const char* str),我们先进行断言pos不能超过_size,接着我们开辟空间,这次就不考虑空串的问题了,因为我们要指定开辟的字节数,和上面一样的我们也要进行挪动数据,我们只不过是由每次挪动一个步,变为了挪动 len 步了,最后使用strncpy插入字符串,把_szie +=len 即可。

画图理解:

string& insert(size_t pos, const char* str)
        {
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
                reserve(_size + len);
            }
            size_t end = _size + len;
            while (end > pos + len - 1)
            {
                _str[end] = _str[end - len];
                --end;
            }
            strncpy(_str + pos, str, len);
            _size += len;
            return *this;
        }

实现了上述的接口当然最好用的还是下面的接口,对push_back和append进行封装实现string+=

一个字符 和 string+=一个字符串

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

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

        }

5.删除接口:erase

指定字符串从开始位置到指定位置删除元素。得保证删除的位置在string的内部,当只给定了删除的起始位置没有给结束的位置那么就触法我们的缺省值,即从pos位置开始直到将字符串删完,或者说给定的结束位置大于了字符串的本身长度,那就从pos位置开始直到删除完字符串,实现的方法很简单,直接在pos位置添字符串的结束标志'\0'。  如果给定的两个值都在字符串的内部直接进行从len位置往前拷贝覆盖掉要删除的元素。

string& erase(size_t pos, size_t len = npos)
        {
            assert(pos <= len);
            if (len == npos || len >= _size - pos)
            {
                _str[pos] = '\0';
                _size = pos;
            }
            else
            {
                strcpy(_str + pos, _str + pos + len);
                _size -= len;

            }
            return *this;
        }

6.find字符 从某个位置开始查找字符,如果没有给定开始位置,就用缺省值,默认从开头寻找,找到了就返回元素的下标,没有找到就返回npos。

size_t find(char ch, size_t pos = 0)  ///默认从pos位置开始寻找,有缺省值0,从pos 位置开始往后寻找
        {                                     //对比找到了就返回下标,找不到返回npos
            assert(pos < _size);
            while (pos < _size)
            {
                if (_str[pos] == ch)  //遍历寻找
                {
                    return pos;
                }
                pos++;
            }
            return npos;
        }

7.find字符串  ,从某个位置开始往后寻找字符串,找到了就返回下标,找不到就返回npos

这里套用c语言的库函数strstr进行实现

size_t find(const char* str, size_t pos = 0)
        {
            assert(pos < _size);
            const char* ptr = strstr(_str + pos, str);
            if (ptr == nullptr)  //strstr找不到返回空指针
            {
                return npos;    //转换至cpp找不到就返回npos
            }
            else
            {
                return ptr - _str;  //返回的是下标  指针-指针  ==下标
            }

        }

8.clear  清空字符串的内容

直接在第一个位置加入结束标志就将字符串清空了,将清空后_size就为0,

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

8.重载cin 和 cout

1.cout

依次的输出string对象的内容即可

    ostream& operator<<(ostream& out,const string& s)
    {
        for (size_t i = 0; i < s.size(); i++)
        {
            out << s[i];
        }

        return out;
    }

2.cin

这里注意因为要改变字符串的内容,首先调用clear函数清空原来的内容,因为要改变字符串的内容所以不用const 直接引用改变的就是字符串的本身。因为我们的 in 会默认 '空格' 和 ' \n '是分割符,不进行读取,这样我们就没办法停止。需要使用下 in 的get函数,让我们的来读取 ‘  ’  和         ' \n ',我们看下代码:

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

istream& operator>>(istream& in, string& s)
{
    s.clear();//要先进行清理,否则就会出现剩余的数据也被我们写入了。
    char ch;
    ch = in.get();

    char buff[32];
    size_t i = 0;

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

    buff[i] = '\0';
    s += buff;

    return in;
}

cin和cout的重载不一定是类的友元函数,在类中提供接口,我们也可以直接访问类的成员变量!

到此这篇关于C++模拟实现string的方法详解的文章就介绍到这了,更多相关C++实现string内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++ String部分成员模拟实现流程详解

    目录 string类的成员设计 普通构造函数的模拟 拷贝构造函数的模拟 赋值重载函数的模拟 String的析构函数模拟 补全上述的成员函数 迭代器的简单模拟 其他成员函数的模拟 string类的成员设计 class string { private: char* _str; int _size; int _capacity; }; 说明:以下的五个成员函数的模拟实现,均去除了_size 和_capacity成员变量,目的是为了更方便解释重点.在五个成员函数模拟后,会对string类的设计进行补全

  • C++中string的模拟实现

    c++中的string类可以实现字符串对象的一系列操作,如下图就是从cplusplus上截取的string的一部分功能: 接下来我就简单模拟几个函数实现 首先,我们要给出完整的string类,包括构造函数,析构函数,私有成员char* str 并且在类内声明要实现的函数(本文我只实现了operator=,operator[ ],pushback(),以及三个operator+=,五个insert等) #include<iostream> #include<cstring> usin

  • C++实现String类实例代码

    C++实现String类实例代码 这是一道十分经典的面试题,可以短时间内考查学生对C++的掌握是否全面,答案要包括C++类的多数知识,保证编写的String类可以完成赋值.拷贝.定义变量等功能. #include<iostream> using namespace std; class String { public: String(const char *str=NULL); String(const String &other); ~String(void); String &am

  • c++模拟实现string类详情

    目录 一.string类简介 二.模拟实现 成员变量 成员函数 迭代器 重载运算符[ ] 三.几种常见函数 reserve() resize() push_back() append() 重载+= insert() erase() find() 四.操作符重载 流插入<< 流提取>> 一.string类简介 标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件.作为标准库的一部分,string定义在命名空间std中. 二.模拟实现 成员变量

  • C++模拟实现string的方法详解

    目录 1.string 成员变量 2.构造函数 3.拷贝构造.赋值重载和析构函数 1.拷贝构造 2.赋值重载 3.析构函数 4.访问成员变量 5.遍历 1.下标+[] 2.迭代器(iterator) 3.范围for 6.空间的申请 1.reserve 2.resize 7.增删查改 8.重载cin 和 cout 1.cout 2.cin 1.string 成员变量 首先需要一个动态开辟的指针指向这个字符串,然后还需要容量和存储的个数,并且我们不能和标准库的string进行冲突所以我们需要写在我们

  • java爬虫之使用HttpClient模拟浏览器发送请求方法详解

    0. 摘要 0.1 添加依赖 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency> 0.2 代码 //1. 打开浏览器 创建httpclient对象 CloseableHttpClient httpCl

  • PHP模拟http请求的方法详解

    本文实例讲述了PHP模拟http请求的方法.分享给大家供大家参考,具体如下: 方法一:利用php的socket编程来直接给接口发送数据来模拟post的操作. 建立两个文件post.php,getpost.php post.php内容如下: <?php $flag = 0; $params = ''; $errno = ''; $errstr = ''; //要post的数据 $argv = array( 'var1'=>'abc', 'var2'=>'how are you , my f

  • c#模拟平抛运动动画的方法详解

    主要使用Graphics对象的FillElliple绘制一圆形小球,然后分时控制其显示位置即可.步骤主要如下:1.新建窗体,然后在代码模式中添加如下函数分别控制x和y轴方向运动速度. 复制代码 代码如下: private int runTime=25;//设置平抛运动时间(动画持续时间)        private double Xs(double t)        {            double v0 = 15;            return v0 * t;        }

  • .NET下模拟数组越界的方法详解

    前言 前面一篇文章提到过 数组越界行为,虽然编译器为我们做了大量的检查工作让我们避免这些错误. 但是我觉得还是有必要模拟一下数组越界,感受一下这个错误. 那么对于.NET来说我们怎么来模拟数组越界呢? 一. [VS] 项目 -> 右击 -> 属性 -> 生成 -> (勾选)允许不安全代码 二.测试代码 unsafe private static void OutOfIndexMini() { int* i = stackalloc int[1]; i[0] = 0; //i[0]

  • C++实现String类的方法详解

    目录 前言 string模拟实现 string简单实现 string完整实现 完整代码 前言 在C语言中,没有专门用来表示字符串的类型.C语言的字符串是一系列以’\0’为结尾的字符的集合.虽然C语言为这样的字符串提供了一系列的库函数如strcpy, strcmp等等,但这些函数与字符串这个类型是分开的,这不太符合C++中面试对象的思想,所以在C++中封装了一个string类,来帮助我们操作字符串.string该如何使用,我这里就不做赘述了,大家可以去看看官方文档呀 string - C++ Re

  • Java String对象使用方法详解

    Java String对象使用方法详解 先来看一个例子,代码如下: public class Test { public static void main(String[] args) { String str = "abc"; String str1 = "abc"; String str2 = new String("abc"); System.out.println(str == str1); System.out.println(str1

  • C# 为String类型增加方法详解

    namespace MyExtensionMethods { public static class MyExtensions { public static int MyGetLength(this System.String target) { return target.Length; } } } 使用时,需要引入这个名字空间,引用如下: string str = "dafasdf"; int len = str.MyGetLength(); 以上这篇C# 为String类型增加

  • MySQL数据库设计之利用Python操作Schema方法详解

    弓在箭要射出之前,低声对箭说道,"你的自由是我的".Schema如箭,弓似Python,选择Python,是Schema最大的自由.而自由应是一个能使自己变得更好的机会. Schema是什么? 不管我们做什么应用,只要和用户输入打交道,就有一个原则--永远不要相信用户的输入数据.意味着我们要对用户输入进行严格的验证,web开发时一般输入数据都以JSON形式发送到后端API,API要对输入数据做验证.一般我都是加很多判断,各种if,导致代码很丑陋,能不能有一种方式比较优雅的验证用户数据呢

  • Javascript类型系统之String字符串类型详解

    javascript没有表示单个字符的字符型,只有字符串String类型,字符型相当于仅包含一个字符的字符串 字符串String是javascript基本数据类型,同时javascript也支持String对象,它是一个原始值的包装对象.在需要时,javascript会自动在原始形式和对象形式之间转换.本文将介绍字符串String原始类型及String包装对象 定义 字符串String类型是由引号括起来的一组由16位Unicode字符组成的字符序列 字符串类型常被用于表示文本数据,此时字符串中的

随机推荐