C++ - Tự xây dựng lớp String

Giới thiệu

Bài viết này sẽ hướng dẫn cách tự xây dựng các hàm thành viên phổ biến của lớp String trong C++.

I. Các biến thành viên và hàm mặc định

1. Biến thành viên

class MyString {
private:
    char* m_buffer;      // Con trỏ lưu trữ chuỗi
    size_t m_capacity;   // Dung lượng bộ nhớ
    size_t m_length;     // Độ dài thực tế của chuỗi
    static const size_t NIL;   // Hằng số biểu diễn vị trí không tìm thấy
};

Khởi tạo hằng số tĩnh bên ngoài lớp:

const size_t MyString::NIL = static_cast<size_t>(-1);

2. Các hàm khởi tạo mặc định

a) Hàm khởi tạo có tham số

Triển khai hàm khởi tạo nhận vào chuỗi C-style:

// Khai báo
MyString(const char* src = "");

// Triển khai
MyString::MyString(const char* src)
{
    m_length = std::strlen(src);
    m_capacity = m_length;
    m_buffer = new char[m_length + 1];  // Cấp phát thêm 1 byte cho '\0'
    std::strcpy(m_buffer, src);
}

b) Hàm hủy

Giải phóng bộ nhớ đã cấp phát:

// Khai báo
~MyString();

// Triển khai
MyString::~MyString()
{
    m_length = m_capacity = 0;
    delete[] m_buffer;
    m_buffer = nullptr;
}

c) Hàm sao chép (Copy Constructor)

Cần sử dụng deep copy vì có bộ nhớ động:

// Khai báo
MyString(const MyString& other);

// Triển khai
MyString::MyString(const MyString& other) : m_buffer(nullptr)
{
    m_length = other.m_length;
    m_capacity = other.m_capacity;
    
    // Tạo đối tượng tạm và hoán đổi con trỏ
    MyString temp(other.m_buffer);
    std::swap(m_buffer, temp.m_buffer);
}

d) Toán tử gán

// Khai báo
MyString& operator=(MyString other);

// Triển khai
MyString& MyString::operator=(MyString other)
{
    if (other.m_buffer == m_buffer)
        return *this;
    
    m_length = other.m_length;
    m_capacity = other.m_capacity;
    
    MyString temp(other.m_buffer);
    std::swap(m_buffer, temp.m_buffer);
    
    return *this;
}

II. Các hàm thành viên phổ biến

1. Hàm length

size_t length() const {
    return m_length;
}

2. Hàm capacity

size_t capacity() const {
    return m_capacity;
}

3. Hàm c_str

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

4. Hàm empty

bool empty() const {
    return m_length == 0;
}

5. Hàm clear

void clear() {
    m_buffer[0] = '\0';
    m_length = 0;
}

6. Hàm reserve

Dự trữ bộ nhớ, chỉ tăng không giảm:

void reserve(size_t newCapacity)
{
    if (m_capacity < newCapacity)
    {
        char* tmp = new char[newCapacity + 1];
        std::strcpy(tmp, m_buffer);
        delete[] m_buffer;
        
        m_buffer = tmp;
        m_capacity = newCapacity;
    }
}

7. Hàm resize

Thay đổi kích thước và điền ký tự chỉ định:

void resize(size_t newSize, char fill = '\0')
{
    if (newSize > m_length)
    {
        if (newSize > m_capacity)
            reserve(newSize);
        
        std::memset(m_buffer + m_length, fill, newSize - m_length);
    }
    
    m_length = newSize;
    m_buffer[newSize] = '\0';
}

8. Hàm insert (chèn ký tự)

void insert(size_t pos, char ch)
{
    assert(pos <= m_length);
    
    if (m_length == m_capacity)
    {
        size_t newCap = m_capacity == 0 ? 4 : m_capacity * 2;
        reserve(newCap);
    }
    
    int end = m_length + 1;
    while (end >= static_cast<int>(pos))
    {
        m_buffer[end] = m_buffer[end - 1];
        end--;
    }
    
    m_length++;
    m_buffer[pos] = ch;
}

9. Hàm insert (chèn chuỗi)

void insert(size_t pos, const char* str)
{
    assert(pos <= m_length);
    
    size_t len = std::strlen(str);
    if (m_length + len > m_capacity)
        reserve(m_length + len);
    
    for (int i = m_length; i >= static_cast<int>(pos); i--)
        m_buffer[i + len] = m_buffer[i];
    
    for (size_t i = 0; i < len; i++)
        m_buffer[pos + i] = str[i];
    
    m_length += len;
}

10. Hàm push_back

void push_back(char ch)
{
    insert(m_length, ch);
}

11. Hàm append

void append(const char* str)
{
    insert(m_length, str);
}

12. Hàm erase

void erase(size_t pos = 0, size_t count = NIL)
{
    assert(pos < m_length);
    
    if (count == NIL || count >= m_length - pos)
    {
        m_buffer[pos] = '\0';
        m_length = pos;
    }
    else
    {
        for (size_t i = 0; i <= count; i++)
            m_buffer[pos + i] = m_buffer[pos + count + i];
        m_length -= count;
    }
}

13. Hàm substr

MyString substr(size_t pos = 0, size_t count = NIL) const
{
    assert(pos < m_length);
    
    MyString result;
    size_t end = pos + count;
    
    if (count == NIL || count >= m_length - pos)
        end = m_length;
    
    for (size_t i = pos; i < end; i++)
        result.push_back(m_buffer[i]);
    
    return result;
}

14. Hàm find (tìm ký tự)

size_t find(char ch, size_t start = 0) const
{
    for (size_t i = start; i < m_length; i++)
        if (ch == m_buffer[i])
            return i;
    return NIL;
}

15. Hàm find (tìm chuỗi)

size_t find(const char* sub, size_t start = 0) const
{
    char* p = std::strstr(m_buffer + start, sub);
    if (!p) return NIL;
    return p - m_buffer;
}

16. Hàm swap

void swap(MyString& other)
{
    std::swap(m_buffer, other.m_buffer);
    std::swap(m_length, other.m_length);
    std::swap(m_capacity, other.m_capacity);
}

III. Nạp chồng toán tử

1. Các toán tử so sánh

bool operator<(const MyString& other) const
{
    return std::strcmp(m_buffer, other.m_buffer) < 0;
}

bool operator==(const MyString& other) const
{
    return std::strcmp(m_buffer, other.m_buffer) == 0;
}

bool operator<=(const MyString& other) const
{
    return !(other < *this);
}

bool operator>(const MyString& other) const
{
    return other < *this;
}

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

bool operator!=(const MyString& other) const
{
    return !(*this == other);
}

2. Toán tử +=

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

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

3. Toán tử []

char& operator[](size_t index)
{
    assert(index < m_length);
    return m_buffer[index];
}

const char& operator[](size_t index) const
{
    assert(index < m_length);
    return m_buffer[index];
}

4. Toán tử << và >>

std::ostream& operator<<(std::ostream& os, const MyString& s)
{
    for (size_t i = 0; i < s.length(); i++)
        os << s[i];
    return os;
}

std::istream& operator>>(std::istream& is, MyString& s)
{
    s.clear();
    char ch = is.get();
    while (ch != ' ' && ch != '\n')
    {
        s += ch;
        ch = is.get();
    }
    return is;
}

IV. Iterator

Định nghĩa iterator để duyệt chuỗi:

typedef char* Iterator;
typedef const char* ConstIterator;

Iterator begin() { return m_buffer; }
Iterator end() { return m_buffer + m_length; }
ConstIterator begin() const { return m_buffer; }
ConstIterator end() const { return m_buffer + m_length; }

V. Mã nguồn hoàn chỉnh

MyString.h

#pragma once
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cassert>

namespace utils
{
    class MyString
    {
    public:
        typedef char* Iterator;
        typedef const char* ConstIterator;

    public:
        MyString(const char* str = "");
        ~MyString();
        MyString(const MyString& other);
        MyString& operator=(MyString other);

        MyString& operator+=(char ch);
        MyString& operator+=(const char* str);
        
        char& operator[](size_t idx);
        const char& operator[](size_t idx) const;

        bool operator<(const MyString& other) const;
        bool operator<=(const MyString& other) const;
        bool operator>(const MyString& other) const;
        bool operator>=(const MyString& other) const;
        bool operator==(const MyString& other) const;
        bool operator!=(const MyString& other) const;

        Iterator begin();
        Iterator end();
        ConstIterator begin() const;
        ConstIterator end() const;

        void insert(size_t pos, char ch);
        void insert(size_t pos, const char* str);
        void push_back(char ch);
        void append(const char* str);

        size_t find(char ch, size_t pos = 0) const;
        size_t find(const char* sub, size_t pos = 0) const;

        void clear();
        void erase(size_t pos = 0, size_t count = NIL);

        void swap(MyString& other);
        const char* c_str() const;
        size_t length() const;
        size_t capacity() const;
        bool empty() const;
        void reserve(size_t newCapacity);
        void resize(size_t newSize, char fill = '\0');
        MyString substr(size_t pos = 0, size_t count = NIL) const;

    private:
        char* m_buffer;
        size_t m_capacity;
        size_t m_length;
        static const size_t NIL;
    };

    std::ostream& operator<<(std::ostream& os, const MyString& s);
    std::istream& operator>>(std::istream& is, MyString& s);
}

MyString.cpp

#include "MyString.h"

namespace utils
{
    const size_t MyString::NIL = static_cast<size_t>(-1);

    std::ostream& operator<<(std::ostream& os, const MyString& s)
    {
        for (size_t i = 0; i < s.length(); i++)
            os << s[i];
        return os;
    }

    std::istream& operator>>(std::istream& is, MyString& s)
    {
        s.clear();
        char ch = is.get();
        while (ch != ' ' && ch != '\n')
        {
            s += ch;
            ch = is.get();
        }
        return is;
    }

    MyString::MyString(const char* str)
    {
        m_length = std::strlen(str);
        m_capacity = m_length;
        m_buffer = new char[m_length + 1];
        std::strcpy(m_buffer, str);
    }

    MyString::~MyString()
    {
        m_length = m_capacity = 0;
        delete[] m_buffer;
        m_buffer = nullptr;
    }

    MyString::MyString(const MyString& other) : m_buffer(nullptr)
    {
        m_length = other.m_length;
        m_capacity = other.m_capacity;
        MyString temp(other.m_buffer);
        std::swap(m_buffer, temp.m_buffer);
    }

    MyString& MyString::operator=(MyString other)
    {
        m_length = other.m_length;
        m_capacity = other.m_capacity;
        MyString temp(other.m_buffer);
        std::swap(m_buffer, temp.m_buffer);
        return *this;
    }

    MyString::Iterator MyString::begin() { return m_buffer; }
    MyString::Iterator MyString::end() { return m_buffer + m_length; }
    MyString::ConstIterator MyString::begin() const { return m_buffer; }
    MyString::ConstIterator MyString::end() const { return m_buffer + m_length; }

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

    void MyString::push_back(char ch)
    {
        insert(m_length, ch);
    }

    MyString& MyString::operator+=(const char* str)
    {
        append(str);
        return *this;
    }

    void MyString::append(const char* str)
    {
        insert(m_length, str);
    }

    bool MyString::empty() const { return m_length == 0; }
    size_t MyString::length() const { return m_length; }
    size_t MyString::capacity() const { return m_capacity; }
    const char* MyString::c_str() const { return m_buffer; }

    char& MyString::operator[](size_t idx)
    {
        assert(idx < m_length);
        return m_buffer[idx];
    }

    const char& MyString::operator[](size_t idx) const
    {
        assert(idx < m_length);
        return m_buffer[idx];
    }

    void MyString::clear()
    {
        m_buffer[0] = '\0';
        m_length = 0;
    }

    void MyString::swap(MyString& other)
    {
        std::swap(m_buffer, other.m_buffer);
        std::swap(m_length, other.m_length);
        std::swap(m_capacity, other.m_capacity);
    }

    bool MyString::operator<(const MyString& other) const
    {
        return std::strcmp(m_buffer, other.m_buffer) < 0;
    }

    bool MyString::operator==(const MyString& other) const
    {
        return std::strcmp(m_buffer, other.m_buffer) == 0;
    }

    bool MyString::operator<=(const MyString& other) const
    {
        return !(other < *this);
    }

    bool MyString::operator>(const MyString& other) const
    {
        return other < *this;
    }

    bool MyString::operator>=(const MyString& other) const
    {
        return !(*this < other);
    }

    bool MyString::operator!=(const MyString& other) const
    {
        return !(*this == other);
    }

    size_t MyString::find(char ch, size_t pos) const
    {
        for (size_t i = pos; i < m_length; i++)
            if (ch == m_buffer[i])
                return i;
        return NIL;
    }

    size_t MyString::find(const char* sub, size_t pos) const
    {
        char* p = std::strstr(m_buffer + pos, sub);
        if (!p) return NIL;
        return p - m_buffer;
    }

    void MyString::insert(size_t pos, char ch)
    {
        assert(pos <= m_length);
        if (m_length == m_capacity)
        {
            size_t newCap = m_capacity == 0 ? 4 : m_capacity * 2;
            reserve(newCap);
        }
        int end = m_length + 1;
        while (end >= static_cast<int>(pos))
        {
            m_buffer[end] = m_buffer[end - 1];
            end--;
        }
        m_length++;
        m_buffer[pos] = ch;
    }

    void MyString::insert(size_t pos, const char* str)
    {
        assert(pos <= m_length);
        size_t len = std::strlen(str);
        if (m_length + len > m_capacity)
            reserve(m_length + len);
        for (int i = m_length; i >= static_cast<int>(pos); i--)
            m_buffer[i + len] = m_buffer[i];
        for (size_t i = 0; i < len; i++)
            m_buffer[pos + i] = str[i];
        m_length += len;
    }

    void MyString::erase(size_t pos, size_t count)
    {
        assert(pos < m_length);
        if (count == NIL || count >= m_length - pos)
        {
            m_buffer[pos] = '\0';
            m_length = pos;
        }
        else
        {
            for (size_t i = 0; i <= count; i++)
                m_buffer[pos + i] = m_buffer[pos + count + i];
            m_length -= count;
        }
    }

    void MyString::reserve(size_t newCapacity)
    {
        if (m_capacity < newCapacity)
        {
            char* tmp = new char[newCapacity + 1];
            std::strcpy(tmp, m_buffer);
            delete[] m_buffer;
            m_buffer = tmp;
            m_capacity = newCapacity;
        }
    }

    void MyString::resize(size_t newSize, char fill)
    {
        if (newSize > m_length)
        {
            if (newSize > m_capacity)
                reserve(newSize);
            std::memset(m_buffer + m_length, fill, newSize - m_length);
        }
        m_length = newSize;
        m_buffer[newSize] = '\0';
    }

    MyString MyString::substr(size_t pos, size_t count) const
    {
        assert(pos < m_length);
        MyString result;
        size_t end = pos + count;
        if (count == NIL || count >= m_length - pos)
            end = m_length;
        for (size_t i = pos; i < end; i++)
            result.push_back(m_buffer[i]);
        return result;
    }
}

Thẻ: cpp string-class custom-implementation STL operator-overloading

Đăng vào ngày 21 tháng 10 lúc 14:30