今天我们来看看字符串类。在之前的学习中,我们得知:在 C 语言中,它是不支持真正意义上的字符串;它用字符数组和一组函数实现字符串操作;C 语言是不支持自定义类型的,因此无法获得字符串类型。那么从 C 到 C++ 的进化过程中引入了自定义类型,在 C++ 中可以通过类完成字符串类型的定义。那么问题来了:在 C++ 中的原生类型系统是否包含字符串类型呢?我们将会在本节博客中来回答这个问题。

        下来我们先来看看 DTLib 库中字符串类的设计,结构如下图所示

字符串类(二十五)_重载

        它的具体实现如下图所示

字符串类(二十五)_操作符_02

        那么我们在实现时应注意那些事项呢?

        1、无缝实现 String 对象与 char* 字符串的互操作;

        2、操作符重载函数需要考虑是否支持 const 版本;

        3、通过 C 语言中的字符串函数实现 String 的成员函数。


        String 的具体实现如下


DTString.h 源码

#ifndef DTSTRING_H
#define DTSTRING_H

#include "Object.h"

namespace DTLib
{

class String : public Object
{
protected:
    char* m_str;
    int m_length;

    void init(const char* s);
public:
    String();
    String(char c);
    String(const char* s);
    String(const String& s);

    int length() const;
    const char* str() const;

    bool operator == (const String& s) const;
    bool operator == (const char* s) const;
    bool operator != (const String& s) const;
    bool operator != (const char* s) const;
    bool operator > (const String& s) const;
    bool operator > (const char* s) const;
    bool operator < (const String& s) const;
    bool operator < (const char* s) const;
    bool operator >= (const String& s) const;
    bool operator >= (const char* s) const;
    bool operator <= (const String& s) const;
    bool operator <= (const char* s) const;

    String operator + (const String& s) const;
    String operator + (const char* s) const;
    String& operator += (const String& s);
    String& operator += (const char* s);

    String& operator = (const String& s);
    String& operator = (const char* s);
    String& operator = (char c);

    ~String();
};

}

#endif // DTSTRING_H


DTString.cpp 源码

#include <iostream>
#include <cstring>
#include <cstdlib>
#include "DTString.h"
#include "Exception.h"

using namespace std;

namespace DTLib
{

void String::init(const char* s)
{
    m_str = strdup(s);

    if( m_str )
    {
        m_length = strlen(m_str);
    }
    else
    {
        THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create String object ...");
    }
}

String::String()
{
    init("");
}

String::String(char c)
{
    char s[] = {c, '\0'};

    init(s);
}

String::String(const char* s)
{
    init(s ? s : "");
}

String::String(const String& s)
{
    init(s.m_str);
}

int String::length() const
{
    return m_length;
}

const char* String::str() const
{
    return m_str;
}

bool String::operator == (const String& s) const
{
    return (strcmp(m_str, s.m_str) == 0);
}

bool String::operator == (const char* s) const
{
    return (strcmp(m_str, s ? s : "") == 0);
}

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

bool String::operator != (const char* s) const
{
    return !(*this==s);
}

bool String::operator > (const String& s) const
{
    return (strcmp(m_str, s.m_str) > 0);
}

bool String::operator > (const char* s) const
{
    return (strcmp(m_str, s ? s : "") > 0);
}

bool String::operator < (const String& s) const
{
    return (strcmp(m_str, s.m_str) < 0);
}

bool String::operator < (const char* s) const
{
    return (strcmp(m_str, s ? s : "") < 0);
}

bool String::operator >= (const String& s) const
{
    return (strcmp(m_str, s.m_str) >= 0);
}

bool String::operator >= (const char* s) const
{
    return (strcmp(m_str, s ? s : "") >= 0);
}

bool String::operator <= (const String& s) const
{
    return (strcmp(m_str, s.m_str) <= 0);
}

bool String::operator <= (const char* s) const
{
    return (strcmp(m_str, s ? s : "") <= 0);
}

String String::operator + (const String& s) const
{
    return (*this + s.m_str);
}

String String::operator + (const char* s) const
{
    String ret;
    int len = m_length + strlen(s ? s : "");
    char* str = reinterpret_cast<char*>(malloc(len + 1));

    if( str )
    {
        strcpy(str, m_str);
        strcat(str, s ? s : "");

        free(ret.m_str);

        ret.m_str = str;
        ret.m_length = len;
    }
    else
    {
        THROW_EXCEPTION(NoEnoughMemoryException, "No memory to add String values ...");
    }

    return ret;
}

String& String::operator += (const String& s)
{
    return (*this = *this + s.m_str);
}

String& String::operator += (const char* s)
{
    return (*this = *this + s);
}

String& String::operator = (const String& s)
{
    return (*this = s.m_str);
}

String& String::operator = (const char* s)
{
    if( m_str != s )
    {
        char* str = strdup(s ? s : "");

        if( str )
        {
            free(m_str);

            m_str = str;
            m_length = strlen(m_str);
        }
        else
        {
            THROW_EXCEPTION(NoEnoughMemoryException, "No memory to assign new String value ..."
                                                     "");
        }
    }

    return *this;
}

String& String::operator = (char c)
{
    char s[] = {c, '\0'};

    return (*this = s);
}

String::~String()
{
    free(m_str);
}

}

        我们来测试下上面的代码,测试代码如下

#include <iostream>
#include "DTString.h"

using namespace std;
using namespace DTLib;

void test_1()
{
    cout << "test_1() begin ..." << endl;

    String s;

    s = 'D';

    cout << "s.str() = " << s.str() << endl;
    cout << "s.length() = " << s.length() << endl;
    cout << "(s == 'D') = " << (s == 'D') << endl;
    cout << "(s > CCC) = " << (s > "CCC") << endl;

    s += " hello world";

    cout << "s.str() = " << s.str() << endl;
    cout << "s.length() = " << s.length() << endl;
    cout << "(s > D hello world) = " << (s == "D hello world") << endl;
    cout << "test_1() end ..." << endl;
}

void test2()
{
    cout << "test_2() begin ..." << endl;

    String a[] = {"E", "D", "C", "B", "A"};
    String min = a[0];

    for(int i=0; i<5; i++)
    {
        if( min > a[i] )
        {
            min = a[i];
        }
    }

    cout << "min = " << min.str() << endl;

    cout << "test_2() end ..." << endl;
}

int main()
{
    test_1();

    test2();

    return 0;
}

        我们先来分析下,在函数 test_1 中,我们先定义 String s = 'D'; s.str = D; s.length = 1; (s == 'D') = 1; (s > "CCC") = 1;  在下面 + " hello world" 后,它的长度就变成 13 了。在函数 test_2 后,打印最小的字符串,应该最后输出为 A。我们来看看结果

字符串类(二十五)_操作符_03

        我们此时已经实现了字符串的一些基本操作。那么我们接下来会实现一些比较高级的操作,功能如下

字符串类(二十五)_重载 _04

        重载数组访问操作符 [ ] a> char& operator [] (int i); b> char operator [] (int i) const; 注意事项:当 i 的取值不合法时,抛出异常,合法的范围是 (0 <= i) && (i < m_length)。源码如下

char& String::operator [] (int i)
{
    if((0 <= i) && (i < m_length))
    {
        return m_str[i];
    }
    else
    {
        THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
    }
}

char String::operator [] (int i) const
{
    return (const_cast<String&>(*this))[i];
}

        我们来测试下,测试代码如下

#include <iostream>
#include "DTString.h"

using namespace std;
using namespace DTLib;

int main()
{
    String s = "hello world";

    for(int i=0; i<s.length(); i++)
    {
        cout << s[i] << endl;
    }
    
        return 0;
}

        我们来看看编译结果

字符串类(二十五)_操作符_05

        下来我们来继续是实现判断是否以指定字符串开始或结束,a> bool startWith(const char* s) const; b> bool startWith(const String& s) const; c> bool endOf(const char* s) const; d> bool endOf(const String& s) const; 我们来看看具体的实现思路,如下图所示

字符串类(二十五)_字符串_06

        如果是以某个字符串开头或者结束的时候,我们在前几个或者后几个字符处来进行对比,看看是否相同,从而来达到实现我们想要的功能。具体源码如下

bool String::equal(const char* l, const char* r, int len) const
{
    bool ret = true;

    for(int i=0; i<len; i++)
    {
        ret = ret && (l[i] == r[i]);
    }

    return ret;
}

bool String::startWith(const char* s) const
{
    bool ret = (s != NULL);

    if( ret )
    {
        int len = strlen(s);

        ret = (len < m_length) && equal(m_str, s, len);
    }

    return ret;
}

bool String::startWith(const String& s) const
{
    return startWith(s.m_str);
}

bool String::endOf(const char* s) const
{
    bool ret = (s != NULL);

    if( ret )
    {
        int len = strlen(s);
        char* str = m_str + (m_length - len);

        ret = (len < m_length) && equal(str, s, len);
    }

    return ret;
}

bool String::endOf(const String& s) const
{
    return endOf(s.m_str);
}

        测试代码如下

#include <iostream>#include "DTString.h"

using namespace std;
using namespace DTLib;

int main()
{
    String s = "hello world";
    
    cout << s.startWith("he") << endl;
    cout << s.endOf("world") << endl;
    
    return 0;
}

        我们来看看编译结果

字符串类(二十五)_重载 _07

        我们继续来实现剩下的功能,在指定位置处插入字符串:a> String& insert(int i, const char* s); b> String& insert(int i, const String& s); 实现思路如下

字符串类(二十五)_操作符_08

        我们在某个位置处插入一个字符串,重新申请一个堆空间,然后将其插入,最后将剩下的字符串接上。最后要记着更新 m_str, m_length。具体源码实现如下

String& String::insert(int i, const char* s)
{
    if( (0 <= i) && (i <= m_length) )
    {
        if( (s != NULL) && (s[0] != '\0') )
        {
            int len = strlen(s);
            char* str = reinterpret_cast<char*>(malloc(m_length + len + 1));

            if( str != NULL )
            {
                strncpy(str, m_str, i);
                strncpy(str + i, s, len);
                strncpy(str + i + len, m_str + i, m_length - i);

                str[m_length + len] = '\0';

                free(m_str);

                m_str = str;
                m_length = m_length + len;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert string value ...");
            }
        }
    }
    else
    {
        THROW_EXCEPTION(INvalidOPerationException, "Parameter i is invalid ...");
    }

    return *this;
}

String& String::insert(int i, const String& s)
{
    return insert(i, s.m_str);
}

        测试代码如下

#include <iostream>
#include "DTString.h"

using namespace std;
using namespace DTLib;

int main()
{
    String s = "";

    s.insert(0, "hello,");
    s.insert(6, "world");

    cout << s.str() << endl;
    
    return 0;
}

        我们看看编译结果

字符串类(二十五)_重载 _09

        我们来实现最后一个功能,去掉字符串两端的空白字符:String& trim(); 实现思路如下

字符串类(二十五)_重载 _10

        我们来分析下,在它的前后两端去除空白字符。也就是说,定义两个整数,前后遍历,直至它不等于 '\0 '。此时已经到了我们所需要的位置,再重新进行组合即可。具体源码实现如下

String& String::trim()
{
    int b = 0;
    int e = m_length -1;

    while( m_str[b] == ' ' ) b++;
    while( m_str[e] == ' ' ) e--;

    if( b == 0 )
    {
        m_str[e + 1] = '\0';

        m_length = e + 1;
    }
    else
    {
        for(int i=0, j=b; j<=e; i++, j++)
        {
            m_str[i] = m_str[j];
        }

        m_str[e - b + 1] = '\0';

        m_length = e - b + 1;
    }

    return *this;
}

        测试代码如下

#include <iostream>
#include "DTString.h"

using namespace std;
using namespace DTLib;

int main()
{
    String s = "  abc  ";

    cout << s.trim().str() << endl;

    if( s.trim().insert(0, "hello,").endOf("abc") && s.startWith("hello") )
    {
        cout << "[" << s.trim().str() << "]" << endl;
    }

    return 0;
}

        我们在上面实现的是链式去除,因此上面的先去除再直接打印操作是可以的。我们来看看编译结果

字符串类(二十五)_字符串_11

        我们直至现在已经实现了一些字符串比较高级的功能。那么在后面,我们再思考下,是否能基于现在实现的基础上,如何在目标字符串中查找是否存在指定的子串?通过今天对字符串的学习,总结如下:1、C/C++ 语言本身不支持字符串类型;2、C 语言通过字符数组和一组函数支持字符串操作;3、C++ 通过自定义字符串类型支持字符串操作;4、字符串类型通过 C 语言中的字符串函数实现。