写模板string类, 认识STL的模块分离策略----小话c++(7)

作者:陈曦

日期:2012-6-6 17:01:10

环境:[Mac  10.7.1  Lion  Intel-based  x64  gcc4.2.1  xcode4.2]

转载请注明出处


Q: 我们需要写什么样的string类。

A: 首先做个简单的需求分析。我们需要编写一个可以以char 为单位的字符串以及以wchar_t为单位的字符串类。同时,它需要能完成比较操作。自然,我们需要使用模板:


template<class charType>
class basic_string_test;

typedef basic_string_test<char>      string_test;
typedef basic_string_test<wchar_t>   wstring_test;

接着,我们来完成构造函数和析构函数:

template<class charType>
class basic_string_test
{
public:
    basic_string_test(const char *str = NULL, size_t capacity = defaultCapacity);
    ~basic_string_test();

public:
    template <class T>
    friend ostream& operator<<(ostream &os, const basic_string_test<T> &s);
    
private:
    static  const size_t    defaultCapacity = 32;
    
private:
    char    _stack_buf[defaultCapacity];    // small buffer for storing small string
    char    *_str;              // storing a slightly bigger string when _stack_buf can't store so much characters; if it's not NULL, then use it instead of _stack_buf
    size_t  _len;               // length of the string
    size_t  _capacity;          // capacity of the string
};

template<class charType>
basic_string_test<charType>::basic_string_test(const char *str, size_t capacity)
{
    // is null
    if(str == NULL)
    {
        _str = NULL;
        _len = 0;
        _capacity = defaultCapacity;
        return;
    }
    
    size_t  len = strlen(str);
    // len is less than defaultCapacity
    if(len < defaultCapacity)
    {
        strcpy(_stack_buf, str);
        _str = NULL;
        _len = len;
        _capacity = defaultCapacity;
        return;
    }
    // len is greater than defaultCapacity
    // _capacity is increased by defaultCapacity as unit
    size_t  needsCapacity = static_cast<size_t>(ceil((len + 1) / (double)defaultCapacity)) * defaultCapacity;

    _str = new char[needsCapacity];
    if(_str == NULL)
        throw std::bad_alloc();
    strcpy(_str, str);
    _len = len;
    _capacity = needsCapacity;
}

template<class charType>
basic_string_test<charType>::~basic_string_test()
{
    if(_str)
        delete []_str;
}

template<class T>
ostream& operator<<(ostream &os, const basic_string_test<T> &s)
{
    if(s._str)
        return os << s._str;
    else
        return os << s._stack_buf;
}


Q: 构造函数中抛出的异常会导致内存泄露问题吗?

A: 不过,这个类在抛出异常前,并没有对象被主动申请,这不会导致问题。


Q: 先测试一个例子吧。

A: 如下代码:

    string_test s("hello");
    std::cout << s << std::endl;
    
    string_test s1("0123456789 0123456789 0123456789 0123456789");
    std::cout << s1 << std::endl;

运行结果如下:
hello
0123456789 0123456789 0123456789 0123456789

Q: 对于拷贝构造函数和重载赋值运算符函数呢?

A: 当然,它们是很必要的。因为我们经常会使用形如string s = "hello";  string s1(s); 之类的代码。

template<class charType>
basic_string_test<charType>::basic_string_test(const basic_string_test& str)
{
    if(this != &str)
    {
        if(str._str)
        {
            allocStr(str._capacity, str._str, str._len);
        }
        else
        {
            strcpy(_stack_buf, str._stack_buf);
            _str = NULL;
            _len = str._len;
            _capacity = str._capacity;
        }
    }
}

template
  
   
basic_string_test
   
    & basic_string_test
    
     ::operator=(const basic_string_test& str) { if(this != &str) { if(str._str) // will process _str { if(_capacity >= str._len) // just copy { strcpy(_str, str._str); _len = str._len; _capacity = str._capacity; } else { char *temp = new char[str._capacity]; if(temp == NULL) throw std::bad_alloc(); delete []_str; _str = temp; strcpy(_str, str._str); _len = str._len; _capacity = str._capacity; } } else // just process _stack_buf { if(_capacity >= str._len) // just copy { strcpy(_stack_buf, str._stack_buf); _str = NULL; _len = str._len; _capacity = str._capacity; } else { char *temp = new char[str._capacity]; if(temp == NULL) throw std::bad_alloc(); delete []_str; _str = temp; strcpy(_str, str._str); _len = str._len; _capacity = str._capacity; } } } return *this; }
    
   
  

template<class T>
void    basic_string_test<T>::allocStr(size_t capacity, const char *origin_str, size_t len)
{
    _str = new char[capacity];
    if(_str == NULL)
        throw std::bad_alloc();
    strcpy(_str, origin_str);
    _len = len;
    _capacity = capacity;
}

测试代码如下:
#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;
#define COUT_STR(str)       std::cout << (str) << std::endl;

int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    string_test s("hello");
    COUT_STR(s)
    s = temp;
    COUT_STR(s)
    
    string_test s1(temp);
    COUT_STR(s1)
    s1 = s;
    COUT_STR(s1)
    
    return 0;
}

运行结果:
hello
0123456789 0123456789 0123456789 0123456789
0123456789 0123456789 0123456789 0123456789
0123456789 0123456789 0123456789 0123456789

Q: 形如string s;这样的代码s是被赋值为""空字符串的,上面的逻辑不太符合的。

A: 修改如下:

template<class charType>
basic_string_test<charType>::basic_string_test(const char *str, size_t capacity)
{
    // is null
    if(str == NULL)
    {
        _str = "";
        _len = 0;
        _capacity = defaultCapacity;
        return;
    }
    
    size_t  len = strlen(str);
    // len is less than defaultCapacity
    if(len < defaultCapacity)
    {
        strcpy(_stack_buf, str);
        _str = NULL;
        _len = len;
        _capacity = defaultCapacity;
        return;
    }
    // len is greater than defaultCapacity
    // _capacity is increased by defaultCapacity as unit
    size_t  needsCapacity = static_cast<size_t>(ceil((len + 1) / (double)defaultCapacity)) * defaultCapacity;
    allocStr(needsCapacity, str, len);
}

测试代码:
int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    string_test s;
    COUT_STR(s)
    s = temp;
    COUT_STR(s)
    
    string_test s1(temp);
    COUT_STR(s1)
    s1 = s;
    COUT_STR(s1)
    
    return 0;
}

可惜崩溃了,崩溃位置:
       if(str._str)        // will process _str
        {
            if(_capacity >= str._len)    // just copy
            {
                strcpy(_str, str._str);
                _len = str._len;
                _capacity = str._capacity;
            }
            else
            {
                char *temp = new char[str._capacity];
                if(temp == NULL)
                    throw std::bad_alloc();
                delete []_str;  // crashed
                _str = temp;
                strcpy(_str, str._str);
                _len = str._len;
                _capacity = str._capacity;
            }
        }

原因在于,_str = ""; 此时的_str又被析构,显然出错。再次修改:
template<class charType>
basic_string_test<charType>& basic_string_test<charType>::operator=(const basic_string_test& str)
{
    if(this != &str)
    {
        if(str._str)        // will process _str
        {
            if(_capacity >= str._len)    // just copy
            {
                strcpy(_str, str._str);
                _len = str._len;
                _capacity = str._capacity;
            }
            else
            {
                char *temp = new char[str._capacity];
                if(temp == NULL)
                    throw std::bad_alloc();
                if(strcmp(_str, ""))
                    delete []_str;
                _str = temp;
                strcpy(_str, str._str);
                _len = str._len;
                _capacity = str._capacity;
            }
        }
        else    // just process _stack_buf
        {
            if(_capacity >= str._len)    // just copy
            {
                strcpy(_stack_buf, str._stack_buf);
                _str = NULL;
                _len = str._len;
                _capacity = str._capacity;
            }
            else
            {
                char *temp = new char[str._capacity];
                if(temp == NULL)
                    throw std::bad_alloc();
                delete []_str;
                _str = temp;
                strcpy(_str, str._str);
                _len = str._len;
                _capacity = str._capacity;
            }
        }
    }
    return *this;
}

Q: 其实写了这么多,是不是已经忘记了basic_string_test<wchar_t>  wstring_test; 这种用法呢?前面的代码是不是已经专门对于char类型字符串进行考虑,忘记了这个呢?

A: 是的。前面的处理已经忘记这个了。所以,此时需要将这种情况也加进去。不过问题在于,如何能判断当前使用的模板参数是char还是wchar_t呢?当然,我们尝试使用typeid来得到类型信息,所有代码如下:

#include <iostream>
#include <cwchar>
#include <cstdlib>
#include <cmath>
#include <exception>
#include <typeinfo>
using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;
#define COUT_STR(str)       std::cout << (str) << std::endl;

template<class charType>
class basic_string_test
{
public:
    basic_string_test(const charType *str = NULL, size_t capacity = defaultCapacity);
    ~basic_string_test();

    basic_string_test(const basic_string_test& str);
    basic_string_test& operator=(const basic_string_test& str);
    
public:
    //template <class T>
    friend ostream& operator<<(ostream &os, const basic_string_test<char> &s);
    
    //template <class T>
    friend wostream& operator<<(wostream &os, const basic_string_test<wchar_t> &s);
    
private:
    void    allocStr(size_t capacity, const charType *origin_str, size_t len);
    
private:
    static  const size_t    defaultCapacity = 32;
    
private:
    charType    _stack_buf[defaultCapacity];    // small buffer for storing small string
    charType    *_str;              // storing a slightly bigger string when _stack_buf can't store so much characters; if it's not NULL, then use it instead of _stack_buf
    size_t  _len;               // length of the string
    size_t  _capacity;          // capacity of the string
};

template<class charType>
basic_string_test<charType>::basic_string_test(const charType *str, size_t capacity)
{
    // is null
    if(str == NULL)
    {
        if(typeid(charType) == typeid(char))
            _str = "";
        else if(typeid(charType) == typeid(wchar_t))
            _str = L"";
        
        _len = 0;
        _capacity = defaultCapacity;
        return;
    }
    
    size_t  len;
    if(typeid(charType) == typeid(char))
        len = strlen(str);
    else if(typeid(charType) == typeid(wchar_t))
        len = wcslen(str);
    
    // len is less than defaultCapacity
    if(len < defaultCapacity)
    {
        if(typeid(charType) == typeid(char))
            strcpy(_stack_buf, str);
        else if(typeid(charType) == typeid(wchar_t))
            wcscpy(_stack_buf, str);

        _str = NULL;
        _len = len;
        _capacity = defaultCapacity;
        return;
    }
    // len is greater than defaultCapacity
    // _capacity is increased by defaultCapacity as unit
    size_t  needsCapacity = static_cast<size_t>(ceil((len + 1) / (double)defaultCapacity)) * defaultCapacity;
    allocStr(needsCapacity, str, len);
}

template<class charType>
basic_string_test<charType>::~basic_string_test()
{
    if(_str)
        delete []_str;
}

template<class charType>
basic_string_test<charType>::basic_string_test(const basic_string_test& str)
{
    if(this != &str)
    {
        if(str._str)
        {
            allocStr(str._capacity, str._str, str._len);
        }
        else
        {
            if(typeid(charType) == typeid(char))
                strcpy(_stack_buf, str._stack_buf);
            else if(typeid(charType) == typeid(wchar_t))
                wcscpy(_stack_buf, str._stack_buf);

            _str = NULL;
            _len = str._len;
            _capacity = str._capacity;
        }
    }
}

template<class charType>
basic_string_test<charType>& basic_string_test<charType>::operator=(const basic_string_test& str)
{
    if(this != &str)
    {
        if(str._str)        // will process _str
        {
            if(_capacity >= str._len)    // just copy
            {
                if(typeid(charType) == typeid(char))
                    strcpy(_str, str._str);
                else if(typeid(charType) == typeid(wchar_t))
                    wcscpy(_str, str._str);
                
                _len = str._len;
                _capacity = str._capacity;
            }
            else
            {
                charType *temp = new charType[str._capacity];
                if(temp == NULL)
                    throw std::bad_alloc();
                if(strcmp(_str, ""))
                    delete []_str;
                _str = temp;
                
                if(typeid(charType) == typeid(char))
                    strcpy(_str, str._str);
                else if(typeid(charType) == typeid(wchar_t))
                    wcscpy(_str, str._str);
                
                _len = str._len;
                _capacity = str._capacity;
            }
        }
        else    // just process _stack_buf
        {
            if(_capacity >= str._len)    // just copy
            {
                if(typeid(charType) == typeid(char))
                    strcpy(_stack_buf, str._stack_buf);
                else if(typeid(charType) == typeid(wchar_t))
                    wcscpy(_stack_buf, str._stack_buf);
                
                _str = NULL;
                _len = str._len;
                _capacity = str._capacity;
            }
            else
            {
                charType *temp = new charType[str._capacity];
                if(temp == NULL)
                    throw std::bad_alloc();
                delete []_str;
                _str = temp;
                
                if(typeid(charType) == typeid(char))
                    strcpy(_str, str._str);
                else if(typeid(charType) == typeid(wchar_t))
                    wcscpy(_str, str._str);
                
                _len = str._len;
                _capacity = str._capacity;
            }
        }
    }
    return *this;
}



//template<class T>
ostream& operator<<(ostream &os, const basic_string_test<char> &s)
{
    if(s._str)
        return os << s._str;
    else
        return os << s._stack_buf;
}

wostream& operator<<(wostream &os, const basic_string_test<wchar_t> &s)
{
    return os;
}

template<class charType>
void    basic_string_test<charType>::allocStr(size_t capacity, const charType *origin_str, size_t len)
{
    _str = new charType[capacity];
    if(_str == NULL)
        throw std::bad_alloc();
    if(typeid(charType) == typeid(char))
        strcpy(_str, origin_str);
    else if(typeid(charType) == typeid(wchar_t))
        wcscpy(_str, origin_str);

    _len = len;
    _capacity = capacity;
}


typedef basic_string_test<char>      string_test;
typedef basic_string_test<wchar_t>   wstring_test;

int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    string_test s;
    COUT_STR(s)
    s = temp;
    COUT_STR(s)
    
    string_test s1(temp);
    COUT_STR(s1)
    s1 = s;
    COUT_STR(s1)
    
    return 0;
}


结果出现了编译错误:
error: cannot convert 'const char*' to 'const wchar_t*' for argument '1' to 'size_t wcslen(const wchar_t*)'

并且在所有对于wchar_t字符串操作的函数,即wcs开头的函数均会有这样的问题。


Q: 为什么会出现这样的问题?

A: 这是因为typeid是运行时函数,编译期无法计算它的值。在测试代码中,使用了basic_string_test<char>版本的代码,编译器会进行basic_string_test模板类的char实例化。也就是,每个函数中的charType均为char, 内部保存的_str也是char *, 可是此时却使用wcslen来操作它,当然会出错了。


Q: 这该怎么办呢?

A: 当然,需要将typeid判断charType是何种类型的地方全部分离出去,由一个单独模块控制。显然,如果每一种不同的判断,比如复制,判断是否相等,长度,小于,大于等操作,如果把它们都写成模板全局函数,是没问题的。但是,缺乏了监管,模块性上显得松散了,所以,我们可以写一个类,来统一管理它们。

char_traits_test.h文件:

#ifndef CHAR_TRAITS_TEST_H
#define CHAR_TRAITS_TEST_H

#include <iostream>
#include <cwchar>
#include <cstdlib>
#include <cmath>
#include <exception>
#include <typeinfo>
using namespace std;

template<typename char_type>
struct char_traits_test
{
public:
    static  bool equal(const char_type& c1, const char_type& c2)
    {
        return c1 == c2;
    }
    
    static  bool less_than(const char_type& c1, const char_type& c2)
    {
        return c1 < c2;
    }
    
    static size_t len(const char_type* s);
    
    static char_type* copy(char_type* s1, const char_type* s2);
    
    static  void assign_empty(char_type*& s);
    static  bool is_empty(const char_type* s);
};

template<>
struct char_traits_test<char>
{
public:
   static size_t len(const char* s)
    {
        return strlen(s);
    }
    
    static char* copy(char* s1, const char* s2)
    {
        return strcpy(s1, s2);
    }
    
    static  void assign_empty(char*& s)
    {
        s = "";
    }
    
    static  bool is_empty(const char* s)
    {
        return !strcmp(s, "");
    }
};

template<>
struct char_traits_test<wchar_t>
{
public:
    static size_t len(const wchar_t* s)
    {
        return wcslen(s);
    }
    
    static wchar_t* copy(wchar_t* s1, const wchar_t* s2)
    {
        return wcscpy(s1, s2);
    }
    
    static  void assign_empty(wchar_t*& s)
    {
        s = L"";
    }
    
    static  bool is_empty(const wchar_t* s)
    {
        return !wcscmp(s, L"");
    }
};

#endif

string_test.h头文件:
#include <iostream>
#include <cwchar>
#include <cstdlib>
#include <cmath>
#include <exception>
#include <typeinfo>
using namespace std;

#include "char_traits_test.h"

#ifndef STRING_TEST_H
#define STRING_TEST_H

template<class char_type, class char_traits_type>
class basic_string_test
{
public:
    basic_string_test(const char_type *str = NULL, size_t capacity = defaultCapacity);
    ~basic_string_test();
    
    basic_string_test(const basic_string_test& str);
    basic_string_test& operator=(const basic_string_test& str);
    
public:
    
    friend ostream& operator<<(ostream &os, const basic_string_test<char, char_traits_type> &s);
    
    
    friend wostream& operator<<(wostream &os, const basic_string_test<wchar_t, char_traits_type> &s);
    
private:
    void    allocStr(size_t capacity, const char_type *origin_str, size_t len);
    
private:
    static  const size_t    defaultCapacity = 32;
    
private:
    char_type    _stack_buf[defaultCapacity];    // small buffer for storing small string
    char_type    *_str;              // storing a slightly bigger string when _stack_buf can't store so much characters; if it's not NULL, then use it instead of _stack_buf
    size_t  _len;               // length of the string
    size_t  _capacity;          // capacity of the string
};

template<class char_type, class char_traits_type>
basic_string_test<char_type, char_traits_type>::basic_string_test(const char_type *str, size_t capacity)
{
    // is null
    if(str == NULL)
    {
        char_traits_type::assign_empty(const_cast<char_type *&>(_str));
        
        _len = 0;
        _capacity = defaultCapacity;
        return;
    }
    
    size_t  len;
    len = char_traits_type::len(str);
    
    // len is less than defaultCapacity
    if(len < defaultCapacity)
    {
        char_traits_type::copy(_stack_buf, str);
        
        _str = NULL;
        _len = len;
        _capacity = defaultCapacity;
        return;
    }
    // len is greater than defaultCapacity
    // _capacity is increased by defaultCapacity as unit
    size_t  needsCapacity = static_cast<size_t>(ceil((len + 1) / (double)defaultCapacity)) * defaultCapacity;
    allocStr(needsCapacity, str, len);
}

template<class char_type, class char_traits_type>
basic_string_test<char_type, char_traits_type>::~basic_string_test()
{
    if(_str && !char_traits_type::is_empty(_str))
        delete []_str;
}

template<class char_type, class char_traits_type>
basic_string_test<char_type, char_traits_type>::basic_string_test(const basic_string_test& str)
{
    if(this != &str)
    {
        if(str._str)
        {
            allocStr(str._capacity, str._str, str._len);
        }
        else
        {
            char_traits_type::copy(_stack_buf, str._stack_buf);
            
            _str = NULL;
            _len = str._len;
            _capacity = str._capacity;
        }
    }
}

template<class char_type, class char_traits_type>
basic_string_test<char_type, char_traits_type>& basic_string_test<char_type, char_traits_type>::operator=(const basic_string_test& str)
{
    if(this != &str)
    {
        if(str._str)        // will process _str
        {
            if(_capacity >= str._len)    // just copy
            {
                char_traits_type::copy(_str, str._str);
                
                _len = str._len;
                _capacity = str._capacity;
            }
            else
            {
                char_type *temp = new char_type[str._capacity];
                if(temp == NULL)
                    throw std::bad_alloc();
                if(_str != NULL && !char_traits_type::is_empty(_str))
                    delete []_str;
                _str = temp;
                
                char_traits_type::copy(_str, str._str);
                _len = str._len;
                _capacity = str._capacity;
            }
        }
        else    // just process _stack_buf
        {
            if(_capacity >= str._len)    // just copy
            {
                char_traits_type::copy(_stack_buf, str._stack_buf);
                
                _str = NULL;
                _len = str._len;
                _capacity = str._capacity;
            }
            else
            {
                char_type *temp = new char_type[str._capacity];
                if(temp == NULL)
                    throw std::bad_alloc();
                _str = temp;
                
                char_traits_type::copy(_str, str._str);
                
                _len = str._len;
                _capacity = str._capacity;
            }
        }
    }
    return *this;
}


ostream& operator<<(ostream &os, const basic_string_test<char, char_traits_test<char> > &s)
{
    if(s._str)
        return os << s._str;
    else
        return os << s._stack_buf;
}

wostream& operator<<(wostream &wos, const basic_string_test<wchar_t, char_traits_test<wchar_t> > &s)
{
    if(s._str)
        return wos << s._str;
    else
        return wos << s._stack_buf;
}

template<class char_type, class char_traits_type>
void    basic_string_test<char_type, char_traits_type>::allocStr(size_t capacity, const char_type *origin_str, size_t len)
{
    _str = new char_type[capacity];
    if(_str == NULL)
        throw std::bad_alloc();
    char_traits_type::copy(_str, origin_str);
    
    _len = len;
    _capacity = capacity;
}


typedef basic_string_test<char, char_traits_test<char> >      string_test;
typedef basic_string_test<wchar_t, char_traits_test<wchar_t> >   wstring_test;


#endif

testForCpp.cpp源代码:
#include "string_test.h"

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;
#define COUT_STR(str)       std::cout << (str) << std::endl;

#define WCOUT_ENDL(str)      std::wcout << #str << " is " << (str) << std::endl;
#define WCOUT_STR(str)       std::wcout << (str) << std::endl;

int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    wchar_t *temp_ws = L"0123456789 0123456789 0123456789 0123456789";
    string_test s;
    COUT_ENDL(s)
    s = temp;
    COUT_ENDL(s)
    
    string_test s1(temp);
    COUT_ENDL(s1)
    s1 = s;
    COUT_ENDL(s1)
    
    wstring_test ws;
    WCOUT_ENDL(ws)
    ws = temp_ws;
    WCOUT_ENDL(ws)
    
    return 0;
}

输出结果:
s is 
s is 0123456789 0123456789 0123456789 0123456789
s1 is 0123456789 0123456789 0123456789 0123456789
s1 is 0123456789 0123456789 0123456789 0123456789
ws is 
ws is 0123456789 0123456789 0123456789 0123456789

这样就ok了。


Q: 不过还是发现,basic_string_test类实现代码中,有一些关于内存申请释放的,它们显得很固定,也很另类,也很孤独,因为它们使用new和delete限定了内存配置,它是否也可以封装出来?

A: 是的。不过,在这里,我们不准备再把这个加到模板参数里面来,因为我们认为系统的new和delete已经可以满足需求了。下面将系统的basic_string类声明列出,它便含有内存配置参数:

  template<typename _CharT, typename _Traits = char_traits<_CharT>,
           typename _Alloc = allocator<_CharT> >
    class basic_string;

Q: 如果需要获取字符串中某个位置的字符或者设置某个位置的字符,怎么办?

A: 如下,在basic_string_test类中增加代码,先做个简单的:

public:
    size_t  len() const { return _len; }
    size_t  capacity() const { return _capacity; }
    
public:
    char_type   get_ch(size_t index) const 
    {
        return (_str ? _str[index] : _stack_buf[index]);
    }
    
    // return the original char at index
    char_type   set_ch(size_t index, const char_type& new_ch)
    {
        char_type* p = (_str ? _str : _stack_buf);
        char_type   temp = p[index];
        p[index] = new_ch;
        return temp;
    }

len函数和capacity函数与此关系不大,只是为了完备此类加上的;

get_ch和set_ch实现了获取和设置的功能。测试代码如下:

int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    wchar_t *temp_ws = L"0123456789 0123456789 0123456789 0123456789";
    string_test s = temp;
    COUT_ENDL(s)
    COUT_ENDL(s.get_ch(1))
    COUT_ENDL(s.set_ch(1, 'a'))
    COUT_ENDL(s)
    
    wstring_test ws = temp_ws;
    WCOUT_ENDL(ws)
    
    return 0;
}

输出结果:
s is 0123456789 0123456789 0123456789 0123456789
s.get_ch(1) is 1
s.set_ch(1, 'a') is 1
s is 0a23456789 0123456789 0123456789 0123456789
ws is 0123456789 0123456789 0123456789 0123456789

Q: 上面的获取和设置代码是否也可以移到char_traits_test类中?

A: 是的。主要在于此部分代码是否具有特性;如果以后有种新的字符,它对于字符串的字符操作不能简单的用=操作符,放到char_traits_test中实现有利于代码耦合度降低,修改也很方便。


Q: 使用set和get函数的方式是不是显得不够优雅,形如s[1]这样的表达式是采用运算符重载,它如何进行运算符重载?

A: 是的,运算符重载可以更方便地替代对应的函数。在basic_string_test类中增加如下代码:

public:
    typedef char_type& reference;
    typedef const char_type& const_reference;

public:
    const_reference operator[](size_t index) const
    {
        return (_str ? _str[index] : _stack_buf[index]);
    }
    
    reference   operator[](size_t index)
    {
        return (_str ? _str[index] : _stack_buf[index]);
    }

Q: 为什么需要一个const版本,一个非const版本?

A: 如果是一个const对象调用operator[],如果没有const版本函数,那么就会找不到函数。将上面的operator[]的const函数版本删掉,如下代码就会编译出错:

int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    wchar_t *temp_ws = L"0123456789 0123456789 0123456789 0123456789";
    const string_test s = temp;
    COUT_ENDL(s)
    COUT_ENDL(s[1])
    
    return 0;
}

编译错误:
Passing 'const string_test' as 'this' argument of 'char_type& basic_string_test<char_type, char_traits_type>::operator[](size_t) [with char_type = char, char_traits_type = char_traits_test<char>]' discards qualifiers

Q: 有时,我们可能需要字符串对象能够向上层保证至少已经可以存储多少数据了,reserve函数该如何编写?

A: 首先,它的原型如下:

void    reserve(size_t res_arg)

res_arg的值不能超过max_size的值,如果超过,抛出一个长度错误的异常;

同时,res_arg如果比_capacity要小,那么什么也不用处理;

当res_arg比_capacity大的时候,以defaultCapacity为大小单位增加_capacity, 直到_capacity不小于res_arg.

代码如下:

template<class char_type, class char_traits_type>
void    basic_string_test<char_type, char_traits_type>::reserve(size_t res_arg)
{
    if(res_arg > this->max_size())
        throw std::length_error("basic_string_test reserve: length is too big");
    if(res_arg > _capacity) 
    {
        size_t  needsCapacity = static_cast<size_t>(ceil((res_arg + 1) / (double)defaultCapacity)) * defaultCapacity;

        char_type* temp = new char_type[needsCapacity];
        if(!temp)
            throw std::bad_alloc();
        if(_str)
        {
            char_traits_type::copy(temp, _str);  
            delete []_str;
            _str = temp;
        }
        else
        {
            char_traits_type::copy(temp, _stack_buf);
            _str = temp;
        }
        _capacity = needsCapacity;
    }
}

测试代码如下:
int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    wchar_t *temp_ws = L"0123456789 0123456789 0123456789 0123456789";
    string_test s = temp;
    COUT_ENDL(s)
    COUT_ENDL(s[1])
    s.reserve(1);
    COUT_ENDL(s)
    COUT_ENDL(s.capacity())
    s.reserve(65);
    COUT_ENDL(s)
    COUT_ENDL(s.capacity())
    
    return 0;
}

运行结果:
s is 0123456789 0123456789 0123456789 0123456789
s[1] is 1
s is 0123456789 0123456789 0123456789 0123456789
s.capacity() is 64
s is 0123456789 0123456789 0123456789 0123456789
s.capacity() is 96

Q: 对于字符串的迭代器,如何编写?

A: 首先,需要在basic_string_test类中定义一下基本类型:

public:
    typedef char_type& reference;
    typedef const char_type& const_reference;
    
    typedef char_type* pointer;
    typedef const char_type* const_pointer;
    
    typedef iterator_test<pointer> iterator;
    typedef iterator_test<const_pointer> const_iterator;
    
    typedef reverse_iterator_test<pointer> reverse_iterator;
    typedef reverse_iterator_test<const_pointer> reverse_const_iterator;

并且,需要实现begin, end等方法:
public:
    iterator    begin() {  return iterator(_str ? _str : _stack_buf); }
    iterator    end() {  return iterator(_str ? (_str + _len) : (_stack_buf + _len)); }
    reverse_iterator    rbegin() {  return reverse_iterator(_str ? (_str + _len - 1) : (_stack_buf + _len - 1)); }
    reverse_iterator    rend() {  return reverse_iterator(_str ? _str - 1 : _stack_buf - 1); }

对于iterator_test和reverse_iterator_test模板类,如下:
#ifndef ITERATOR_TEST_H
#define ITERATOR_TEST_H

template <class iter_type>
struct iterator_traits_test
{
    typedef typename iter_type::pointer      pointer;
    typedef typename iter_type::reference    reference;
};

template <class T>
struct iterator_traits_test<T *>
{
    typedef T*  pointer;
    typedef T&  reference;
};

template <class T>
struct iterator_traits_test<const T *>
{
    typedef const T*  pointer;
    typedef const T&  reference;
};

template <class iter_type>
class iterator_test
{
public:
    typedef typename iterator_traits_test<iter_type>::pointer    pointer;
    typedef typename iterator_traits_test<iter_type>::reference  reference;
    
public:
    iterator_test():_cur(iter_type()) { }
    iterator_test(const iter_type& iter):_cur(iter) { }
public:
    iter_type   cur() const { return _cur; }
    
public:
    reference   operator*() const { return *_cur; }
    pointer     operator->() const { return _cur; }
    iterator_test&  operator++() { ++_cur; return *this; }
    iterator_test   operator++(int) { return iterator_test(_cur++); }
    iterator_test&  operator--() { --_cur; return *this; }
    iterator_test   operator--(int) { return iterator_test(_cur--); }
    bool        operator!=(const iterator_test& iter) const
    {
        return _cur != iter.cur();
    }
    
private:
    iter_type   _cur;
    
};


template <class iter_type>
class reverse_iterator_test
{
public:
    typedef typename iterator_traits_test<iter_type>::pointer    pointer;
    typedef typename iterator_traits_test<iter_type>::reference  reference;
    
public:
    reverse_iterator_test():_cur(iter_type()) { }
    reverse_iterator_test(const iter_type& iter):_cur(iter) { }
public:
    iter_type   cur() const { return _cur; }
    
public:
    reference   operator*() const { return *_cur; }
    pointer     operator->() const { return _cur; }
    reverse_iterator_test&  operator++() { --_cur; return *this; }
    reverse_iterator_test   operator++(int) { return reverse_iterator_test(_cur--); }
    reverse_iterator_test&  operator--() { ++_cur; return *this; }
    reverse_iterator_test   operator--(int) { return reverse_iterator_test(_cur++); }
    bool        operator!=(const reverse_iterator_test& reverse_iter) const
    {
        return _cur != reverse_iter.cur();
    }
    
private:
    iter_type   _cur;
    
};


#endif

测试代码:

#define COUT_STARS          std::cout << "******************" << std::endl;

int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    wchar_t *temp_ws = L"0123456789 0123456789 0123456789 0123456789";
    
    string_test s = "hello";
    COUT_ENDL(s)
    
    string_test::iterator it;
    for(it = s.begin(); it != s.end(); ++it)
        COUT_STR(*it)
        
    COUT_STARS
        
    string_test::reverse_iterator reverse_it;
    for(reverse_it = s.rbegin(); reverse_it != s.rend(); ++reverse_it)
        COUT_STR(*reverse_it)
        
    return 0;
}

输出:
s is hello
h
e
l
l
o
******************
o
l
l
e
h

Q: 对于判断字符串是否相等、大于或者小于的函数,该如何写?

A: 这里,可能需要重载很多函数,operator==, operator>, operator<, operator>=, operator!=等等函数。同时,需要考虑,判断函数左侧为char *类型或者wchar_t *类型,右侧为basic_string_test类型的比较,所以需要重载的函数较多,但是不是复杂,这里不再列出。


Q: 对于需要实现交换两个字符串的swap函数,如何编写?

A: 对于一个简单的思考,是需要重新申请空间来保存交换后的字符串。当然,是可以优化的,如果动态内存指针_str有效,可以交换这个指针。

template<class char_type, class char_traits_type>
void    basic_string_test<char_type, char_traits_type>::swap(basic_string_test& another_s)
{
    if(_str)
    {
        if(another_s._str)
        {
            std::swap(_str, another_s._str);
            std::swap(_len, another_s._len);
            std::swap(_capacity, another_s._capacity);
        }
        else
        {
            another_s._str = _str;
            char_traits_type::copy(_stack_buf, another_s._stack_buf);
            _str = NULL;
            std::swap(_len, another_s._len);
            std::swap(_capacity, another_s._capacity);
        }
    }
    else
    {
        if(another_s._str)
        {
            _str = another_s._str;
            char_traits_type::copy(another_s._stack_buf, _stack_buf);
            another_s._str = NULL;
            std::swap(_len, another_s._len);
            std::swap(_capacity, another_s._capacity);
        }
        else
        {
            char_type   temp_stack_buf[defaultCapacity];
            char_traits_type::copy(temp_stack_buf, _stack_buf);
            char_traits_type::copy(_stack_buf, another_s._stack_buf);
            char_traits_type::copy(another_s._stack_buf, temp_stack_buf);
            std::swap(_len, another_s._len);
            std::swap(_capacity, another_s._capacity);
        }
    }
}

测试代码:
int main (int argc, const char * argv[])
{
    char *temp = "0123456789 0123456789 0123456789 0123456789";
    wchar_t *temp_ws = L"0123456789 0123456789 0123456789 0123456789";
    
    string_test s = "hello";
    COUT_ENDL(s)
    string_test s1 = temp;
    COUT_ENDL(s1)
    s.swap(s1);
    COUT_STR("after swap:")
    COUT_ENDL(s)
    COUT_ENDL(s1)
    
    return 0;
}

输出结果:
s is hello
s1 is 0123456789 0123456789 0123456789 0123456789
after swap:
s is 0123456789 0123456789 0123456789 0123456789
s1 is hello

Q: 从一个字符串中寻找另一个字符串和它匹配的起始位置,代码该如何写?

A: 一种笨的方法就是将两个字符串挨个字符比较,当完全匹配了子字符串后,返回起始位置。当然,这样效率会低,下面将采用KMP匹配算法实现它。

template<class char_type, class char_traits_type>
size_t  basic_string_test<char_type, char_traits_type>::find(const basic_string_test& sub_str)
{
    char_type*  p_str = (_str ? _str : _stack_buf);
    const char_type*  p_sub_str = (sub_str._str ? sub_str._str : sub_str._stack_buf);
    if(_len < sub_str._len)
        return (size_t)-1;
    if(_len == sub_str._len)
    {
        if(!char_traits_type::cmp(p_str, p_sub_str))
            return 0;
        else
            return (size_t)-1;
    }
    
    // simple kmp
    size_t  str_cur = 0;
    size_t  sub_str_cur = 0;
    size_t  next = 1;

    while (!char_traits_type::eos(p_str, str_cur) && !char_traits_type::eos(p_sub_str, sub_str_cur))
    {
        // if not equal, reset the sub_str_cur, and calculate the next str_cur
        if(!char_traits_type::equal(p_str[str_cur], p_sub_str[sub_str_cur]))
        {
            sub_str_cur = 0;
            if(next > str_cur)
                str_cur = next;
            else
                ++str_cur;
            continue;
        }
        
        if(!char_traits_type::eos(p_str, str_cur + 1))
        {
            // reset next value
            if(p_str[str_cur + 1] != p_sub_str[0])
                next = str_cur + 2;
            else
                next = str_cur + 1;
        }
        // if equal, increase str_cur and sub_str_cur
        ++str_cur;
        ++sub_str_cur;
    }
    if(sub_str_cur == sub_str._len)
        return str_cur - sub_str_cur;
    return -1;
}

上面的代码是为了方便,没有将kmp算法分离出来。

测试代码:

int main (int argc, const char * argv[])
{
    char *temp = "abedefzabdcabcfsdsdlabcde";
    wchar_t *temp_ws = L"0123456789 0123456789 0123456789 0123456789";
    
    string_test s = temp;
    COUT_ENDL(s)
    string_test s1 = "abc";
    size_t  ret = s.find(s1);
    COUT_ENDL(ret)
    
    return 0;
}

输出结果:
s is abedefzabdcabcfsdsdlabcde
ret is 11

代码下载地址:http://download.csdn.net/detail/cxsjabcabc/4356326


作者:陈曦

日期:2012-6-6 17:01:10

环境:[Mac  10.7.1  Lion  Intel-based  x64  gcc4.2.1  xcode4.2]

转载请注明出处



你可能感兴趣的:(C++,String,null,basic,iterator,reference)