【C++】string类的模拟实现(中)

简介: 【C++】string类的模拟实现(中)

4. 析构函数

析构函数就非常简单了,使用delete[]释放空间即可

~st


3.迭代器


对于string的迭代器,原生指针就能很好的支持迭代器行为,所以我们直接用原生指针。这里我们只实现正向迭代器的const和非const版本。

typedef char* iterator;
typedef const char* const_iterator;
iterator beign()
{
    return _str;
}
iterator end()
{
    return _str + _size;
}
const_iterator beign() const
{
    return _str;
}
const_iterator end() const
{
    return _str + _size;
}


4.容量相关


1.size和capacity

size和capacity直接返回对象中的成员变量即可

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


2. reserve

reserve是对string对象的容量进行操控的,当容量小于传入的值时,将会扩容,如果容量大于传入的值将不会做任何操作

{
    if (n > _capacity)
    {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}


3. resize

resize的功能就是对string对象的size重新规划,当传入的n > capacity时,需要扩容,然后使用‘\0’来填充(默认情况下),当n小于元素个数时,将会把元素个数直接变成n,当n在size和capacity之间时,将会把后续的内容填充能‘\0’(默认情况下)

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

4. clear和empty

clear是清除所有数据,但是不销毁的函数,empty是判断是否为空的函数

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


5.数据访问


对于string的数据访问,我们一般有两种方式,第一就是通过迭代器(范围for也是迭代器的方式),第二种就是通过operator[],所以这里我们只实现一下operator[]

//非const版本,可读可写
char operator[](size_t pos)
{
    assert(pos < _size);
    return _str[pos];
}
//const版本,可读不可写
const char operator[](size_t pos) const
{
    assert(pos < _size);
    return _str[pos];
}


6.数据操作


在学习数据结构的时候,我们一般学习的就是这种数据结构的增删查改四种操作,对于string类也是这样,所以在数据操作中,也分为增删查改四种。


1.插入数据

1. push_back

尾插一个字符,在增加数据的时候,我们需要考虑是否需要扩容

void push_back(char ch)
{
    if (_capacity == _size)
    {
        size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;//扩容扩二倍
        reserve(newCapacity);
    }
    _str[_size] = ch;
    ++_size;
    _str[_size] = '\0';//这里注意尾插之后要对字符串结尾的\0做一下补充
}


2. append

尾插一串字符(一个字符串),这里有很多重载,我们只实现一个C-string类型的。

void append(const char* str)
{
    //这里需要判断一下需不需要扩容,如果需要的话要扩多大
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }
    strcpy(_str + _size, str);
    _size += len;
}


3. operator+=

对于上述的两种插入方式,我们可以使用一个运算符重载全部解决,直接复用即可

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


4.insert

1. 插入字符

按照insert的逻辑,我们很容易的可以写出以下代码

string insert(size_t pos, char ch)
{
    //判断位置是否合法
    assert(pos <= _size);
    //扩容
    if (_size == _capacity)
    {
        size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
        reserve(newCapacity);
    }
    //挪动数据
    size_t end = _size;
    while (pos <= end)
    {
        _str[end + 1] = _str[end];
        --end;
    }
    _str[pos] = ch;
    ++_size;
    return *this;
}


但是,如果当传入的pos==0时,将会出现死循环的问题,因为pos的类型是size_t,所以在进行比较的时候,编译器会进行隐式类型转换,把end转换成size_t的类型进行比较,所以如果传入的pos==0,就不可能出现pos > end的情况,也就是死循环了,那么我们要怎么解决这个问题呢?其实有两种解决方案


  • 方案一:强转

将end定义成int类型,然后while中的判断把pos强转成int,这样就可以避免隐式类型转换,从而规避死循环的情况发生

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


但是这种方法看起来有点不太高级,所以这里我们有了另一种方法


  • 方案二:将end指向\0后面的位置,这样就不会出现end<0的情况
size_t end = _size + 1;
while (pos < end)
{
    _str[end] = _str[end - 1];
    --end;
}


2. 插入字符串

插入字符串之前,我们同样要判断容量是否充足。这里在拷贝的过程中,我们要注意使用strcpy的时候,会把\0拷贝进去,所以我们不能使用strcpy而是使用strncpy。

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 + pos;
    while (pos + len - 1 < end)
    {
        _str[end] = _str[end - len];
        --end;
    }
    strncpy(_str + pos, str, len);
    _size += len;
    return *this;
}


相关文章
|
24天前
|
存储 安全 C语言
C++ String揭秘:写高效代码的关键
在C++编程中,字符串操作是不可避免的一部分。从简单的字符串拼接到复杂的文本处理,C++的string类为开发者提供了一种更高效、灵活且安全的方式来管理和操作字符串。本文将从基础操作入手,逐步揭开C++ string类的奥秘,帮助你深入理解其内部机制,并学会如何在实际开发中充分发挥其性能和优势。
|
24天前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
2天前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
31 16
|
2天前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
31 11
|
8天前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、&quot;+&quot;操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
|
6天前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
45 6
|
27天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
8天前
|
存储 JavaScript Java
课时44:String类对象两种实例化方式比较
本次课程的主要讨论了两种处理模式在Java程序中的应用,直接赋值和构造方法实例化。此外,还讨论了字符串池的概念,指出在Java程序的底层,DOM提供了专门的字符串池,用于存储和查找字符串。 1.直接赋值的对象化模式 2.字符串池的概念 3.构造方法实例化
|
27天前
|
C++
模拟实现c++中的string
模拟实现c++中的string
|
24天前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。