今天我们来看看字符串类。在之前的学习中,我们得知:在 C 语言中,它是不支持真正意义上的字符串;它用字符数组和一组函数实现字符串操作;C 语言是不支持自定义类型的,因此无法获得字符串类型。那么从 C 到 C++ 的进化过程中引入了自定义类型,在 C++ 中可以通过类完成字符串类型的定义。那么问题来了:在 C++ 中的原生类型系统是否包含字符串类型呢?我们将会在本节博客中来回答这个问题。
下来我们先来看看 DTLib 库中字符串类的设计,结构如下图所示
它的具体实现如下图所示
那么我们在实现时应注意那些事项呢?
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。我们来看看结果
我们此时已经实现了字符串的一些基本操作。那么我们接下来会实现一些比较高级的操作,功能如下
重载数组访问操作符 [ ] :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; }
我们来看看编译结果
下来我们来继续是实现判断是否以指定字符串开始或结束,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; 我们来看看具体的实现思路,如下图所示
如果是以某个字符串开头或者结束的时候,我们在前几个或者后几个字符处来进行对比,看看是否相同,从而来达到实现我们想要的功能。具体源码如下
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; }
我们来看看编译结果
我们继续来实现剩下的功能,在指定位置处插入字符串:a> String& insert(int i, const char* s); b> String& insert(int i, const String& s); 实现思路如下
我们在某个位置处插入一个字符串,重新申请一个堆空间,然后将其插入,最后将剩下的字符串接上。最后要记着更新 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; }
我们看看编译结果
我们来实现最后一个功能,去掉字符串两端的空白字符:String& trim(); 实现思路如下
我们来分析下,在它的前后两端去除空白字符。也就是说,定义两个整数,前后遍历,直至它不等于 '\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; }
我们在上面实现的是链式去除,因此上面的先去除再直接打印操作是可以的。我们来看看编译结果
我们直至现在已经实现了一些字符串比较高级的功能。那么在后面,我们再思考下,是否能基于现在实现的基础上,如何在目标字符串中查找是否存在指定的子串?通过今天对字符串的学习,总结如下:1、C/C++ 语言本身不支持字符串类型;2、C 语言通过字符数组和一组函数支持字符串操作;3、C++ 通过自定义字符串类型支持字符串操作;4、字符串类型通过 C 语言中的字符串函数实现。