1,歷史遺留問題:
1,C 語言不支持真正意義上的字符串;
2,C 語言用字符數組和一組函數實現字符串操作;
1,字符數組模擬字符串;
2,字符數組以 \0 來結束就是合法字符串;
3,C 語言中沒有單獨類型支持字符串,要么是字符數組,要么是 char* 指針;
3,C 語言不支持自定義類型,因此無法獲得字符串類型;
2,從 C 到 C++ 的進化過程引入了自定義類型;
在 C++ 中可以通過類完成字符串類型的定義;
C++ 中的原生類型不包含字符串類型,要兼容 C 語言;
3,C++ 中通過庫來支持字符串類型:
1,stl 中就有 string 類,這是官方承認的 string 類型;
2,Qt 中提供 QString;
3,MFC 中提供 CString;
4,數據結構庫中也應該包含字符串庫,否則拿出去應該沒人使用的,本文就是設計自己的 String 類;
5,DTLib 中字符串類的設計:
1,所以字符串類的設計方案基本一致,只不過是出自不同廠商,對應的類型不同,在對應的類型下面使用設計的方法確是一樣的;
2,繼承自 Object,依賴 C 語言關於字符串函數的一個包(函數集);
3,要注意設計的成員函數的先后秩序;
6,DTLib 中字符串類的實現(本博文中實現了包括KMP算法應用在內的一系列字符串功能函數,包括后續“字符串類——字符串類的創建(下)”、“字符串類———KMP子串查找算法”、“字符串類——KMP算法的應用”博文中的內容,不限於下圖):
7,實現時的注意事項:
1,無縫實現 String 對象與 char* 字符串的互操作;
2,操作符重載函數需要考慮是否支持 const 版本;
3,通過 C 語言中的字符串函數實現 String 的成員函數;
8,C 語言中的字符串類型:
1,常量字符串:
1,const char* string,string 指向字符數組首地址;
2,char string[],string[] 為字符數組;
2,即要么是字符串數組本身,要么是指向字符數組首地址(一般的是這樣)的 char*;
3,本質還是字符數組;
9,字符串類 String 的實現:
1,String.h 的實現:
1 #ifndef DTSTRING_H 2 #define DTSTRING_H 3 4 #include "Object.h" 5 6 namespace DTLib 7 { 8 9 class String : public Object // 庫規則中每個類都要繼承自頂層父類 Object 10 { 11 protected: // 面相對象的技術封裝 C 語言中的字符串實現。 12 char* m_str; // 指向字符串,字符串的表現形式是字符數組; 13 int m_length; // 當前字符串的長度; 14 15 void init(const char* s); // 初始化函數 16 /* 比對函數,為 startWith() 服務,前兩個參數為字符數組的首地址,第三個參數是字符數組長度;長度范圍內字符數組對應元素都相等,返回真; */ 17 bool equal(const char* l, const char* r, int len) const; 18 static int* make_pmt(const char* p); 19 static int kmp(const char* s, const char* p); 20 21 public: 22 String(); 23 String(char c); 24 String(const char* s); 25 String(const String& s); // 拷貝構造函數; 26 27 int length() const; // 得到字符串長度; 28 const char* str() const; // 字符串對象與傳統字符串進行互操作的轉換函數 29 30 /* 判斷當前的字符對象是否以 s 開頭,判斷當前的字符對象是否以 s 結束 */ 31 bool startWith(const char* s) const; 32 bool startWith(const String& s) const; 33 bool endOf(const char* s) const; 34 bool endOf(const String& s) const; 35 36 /* 將字符串 s 插入到對象下標為 i 處,返回 String& 是為了鏈式操作,返回字符串自己 */ 37 String& insert(int i, const char* s); 38 String& insert(int i, const String& s); 39 40 /* 去掉字符串中的空格 */ 41 String& trim(); 42 43 int indexOf(const char* ) const; 44 int indexOf(const String& s) const; 45 46 /* 刪除字符串中的子串 s */ 47 String& remove(int i, int len); // 刪除下標 i 處指定長度 len 的長度; 48 String& remove(const char* s); 49 String& remove(const String& s); 50 51 /* 用 s 替換字符串中的 t */ 52 String& replace(const char* t, const char* s); 53 String& replace(const String& t, const char* s); 54 String& replace(const char* t, const String& s); 55 String& replace(const String& t, const String& s); 56 57 /* 提取以 i 為起點去長度為 len 的子串 */ 58 String sub(int i, int len) const; // 因為這里不會改變當前字符串狀態,所以為 const 成員函數; 59 60 /* 字符串對象應該能夠像字符數組一樣,通過每一個下標來訪問每一個字符; */ 61 char& operator [] (int i); // 引用意味着可以被賦值,可以出現在賦值符號左邊(此時是對象),給沒有被 const 修飾的版本用; 62 char operator [] (int i) const; // 不能作為左值,所以不能返回引用對象;給 const 修飾的常對象版本使用 63 64 /* 比較操作符重載函數,兼容字符串對象與 C 語言中 const char* 所代表的字符串中的邏輯操作;封裝 strcmp 函數完成 ;const 版本給被 const 修飾的常對象使用的,未被 const 修飾的對象也可以作為參數被調用;*/ 65 bool operator == (const String& s) const; 66 bool operator == (const char* s) const; 67 bool operator != (const String& s) const; 68 bool operator != (const char* s) const; 69 bool operator > (const String& s) const; 70 bool operator > (const char* s) const; 71 bool operator < (const String& s) const; 72 bool operator < (const char* s) const; 73 bool operator >= (const String& s) const; 74 bool operator >= (const char* s) const; 75 bool operator <= (const String& s) const; 76 bool operator <= (const char* s) const; 77 78 79 /* 加法操作符重載函數 */ 80 String operator + (const String& s) const; 81 String operator + (const char* s) const; 82 String& operator += (const String& s); 83 String& operator += (const char* s); 84 85 /* 減法操作符重載函數 */ 86 String operator - (const String& s) const; 87 String operator - (const char* s) const; 88 String& operator -= (const String& s); //成員會改變的,所以不能用const修飾了 89 String& operator -= (const char* s); 90 91 /* 賦值操作符重載函數 */ 92 String& operator = (const String& s); 93 String& operator = (const char* s); 94 String& operator = (char c); // 加上一個字符 95 96 ~String(); 97 }; 98 99 } 100 101 #endif // DTSTRING_H
2,String.cpp 的實現:
1 #include <cstring> 2 #include <cstdlib> 3 #include "DTString.h" 4 #include "Exception.h" 5 6 using namespace std; 7 namespace DTLib 8 { 9 10 /* 建立指定字符串的 pmt(部分匹配表)表 */ 11 int* String::make_pmt(const char* p) // O(m),只有一個 for 循環 12 { 13 int len = strlen(p); 14 int* ret = static_cast<int*>(malloc(sizeof(int) * len)); 15 16 if ( ret != NULL ) 17 { 18 int ll = 0; //定義 ll,前綴和后綴交集的最大長度數,largest length;第一步 19 ret[0] = 0; // 長度為 1 的字符串前后集都為空,對應 ll 為 0; 20 for(int i=1; i<len; i++) // 從第一個下標,也就是第二個字符開始計算,因為第 0 個字符前面已經計算過了; 第二步 21 { 22 /* 算法第四步 */ 23 while( (ll > 0) && (p[ll] != p[i]) ) // 當 ll 值為零時,轉到下面 if() 函數繼續判斷,最后賦值與匹配表,所以順序不要錯; 24 { 25 ll = ret[ll - 1]; // 從之前匹配的部分匹配值表中,繼續和最后擴展的那個字符匹配 26 } 27 28 /* 算法的第三步,這是成功的情況 */ 29 if( p[ll] == p[i] ) // 根據 ll 來確定擴展的種子個數為 ll,而數組 ll 處就處對應的擴展元素,然后和最新擴展的元素比較; 30 { 31 ll++; // 若相同(與假設符合)則加一 32 } 33 34 ret[i] = ll; // 部分匹配表里存儲部分匹配值 ll 35 } 36 } 37 38 return ret; 39 } 40 41 /* 在字符串 s 中查找子串 p */ 42 int String::kmp(const char* s, const char* p) // O(m) + O(n) ==> O(m+n), 只有一個 for 循環 43 { 44 int ret = -1; 45 int sl = strlen(s); 46 int pl = strlen(p); 47 int* pmt = make_pmt(p); 48 49 if( (pmt != NULL) && (0 < pl) && (pl <= sl) ) // 判斷查找條件 50 { 51 for(int i=0, j=0; i<sl; i++) // i 的值要小於目標竄長度才可以查找 52 { 53 while( (j > 0) && (s[i] != p[j]) ) // 比對不上的時候,持續比對, 54 { 55 j = pmt[j-1]; //移動后應該繼續匹配的位置,j =j-(j-LL)= LL = PMT[j-1] 56 } 57 58 if( s[i] == p[j] ) // 比對字符成功 59 { 60 j++; // 加然后比對下一個字符 61 } 62 63 if( j == pl ) // 這個時候是查找到了,因為 j 增加到了 pl 的長度; 64 { 65 ret = i + 1 - pl; // 匹配成功后,i 的值停在最后一個匹配成功的字符上,這樣就返回匹配成功的位置 66 break; 67 } 68 } 69 } 70 71 free(pmt); 72 73 return ret; 74 } 75 76 /* 通過參數 s,具體產生當前字符串對象當中的數據,供構造函數使用;實現的方法就是封裝 */ 77 void String::init(const char* s) 78 { 79 m_str = strdup(s); // 當前字符串當中的數據通過 m_str 指針指向; 80 81 if( m_str ) // 復制失敗會返回空指針; 82 { 83 m_length = strlen(m_str); // 獲取長度; 84 } 85 else 86 { 87 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to creat String object ..."); 88 } 89 } 90 91 /* 比對函數,為 startWith() 服務,前兩個參數為字符數組的首地址,第三個參數是字符數組長度;長度范圍內字符數組對應元素都相等,返回真; */ 92 bool String::equal(const char* l, const char* r, int len) const 93 { 94 bool ret = true; 95 for(int i=0; i<len && ret; i++) // 這里的 ret 看似沒用,實則只要有元素不相等就停止循環、非常重要; 96 { 97 ret = ret && (l[i] == r[i]); // 如果有一個位置的字符不相等,則結束比較,返回 false; 98 } 99 100 return ret; 101 } 102 103 String::String() 104 { 105 init(""); 106 } 107 108 String::String(const char* s) 109 { 110 init(s ? s : ""); // 將空指針轉換為空字符串,s 正確了返回 s,錯誤了返回 “”(這是空字符串) 111 } 112 113 String::String(const String& s) 114 { 115 init(s.m_str); 116 } 117 118 /* 字符作為初始值創建字符串對象 */ 119 String::String(char c) 120 { 121 char s[] = {c, '\0'}; // 用字符數組模擬字符串的初始化方式,這是兩種初始化中的一種; 122 init(s); 123 } 124 125 int String::length() const 126 { 127 return m_length; 128 } 129 130 /* 字符串對象與傳統字符串進行互操作的轉換函數 */ 131 const char* String::str() const // 直接返回字符串首地址 132 { 133 return m_str; // 字符串對象本身就是通過字符指針來指向的,這樣就可以直接轉換; 134 } 135 136 /* 判斷當前的字符對象是否以 s 開頭 */ 137 bool String::startWith(const char* s) const 138 { 139 bool ret = (s != NULL); 140 141 if( ret ) 142 { 143 int len = strlen(s); 144 ret = (len < m_length) && equal(m_str, s, len); // 如果參數字符串 s 長度比當前字符串長度更長,直接返回 false; 145 } 146 147 return ret; 148 } 149 150 bool String::startWith(const String& s) const 151 { 152 return startWith(s.m_str); // 代碼復用了上面的 153 } 154 155 bool String::endOf(const char* s) const // s 這個字符串是否是以字符開始 156 { 157 bool ret = (s != NULL); 158 159 if( ret ) 160 { 161 int len = strlen(s); 162 char* str = m_str + (m_length - len); // 計算最后 n 個字符表示的字符串; 163 ret = (len < m_length) && equal(str, s, len); // 如果參數字符串 s 長度比當前字符串長度更長,直接返回 false; 164 } 165 166 return ret; 167 } 168 169 bool String::endOf(const String& s) const 170 { 171 return endOf(s.m_str); // 代碼復用了上面的 172 } 173 174 /* 第 i 個位置插入字符串 s,返回字符串對象是為了實現鏈式操作 */ 175 String& String::insert(int i, const char* s) 176 { 177 if( (0 <= i) && (i <= m_length) ) 178 { 179 if( (s != NULL) && (s[0] != '\0') ) // 不為空和空字符串; 180 { 181 int len = strlen(s); 182 char* str = reinterpret_cast<char*>(malloc(m_length + len + 1)); 183 184 if( str != NULL ) 185 { 186 strncpy(str, m_str, i); // 當前字符串的前 i 個字符拷貝出來到 str 187 strncpy(str + i, s, len);//將參數字符串s全部拷貝到 str + i 上去 188 strncpy(str + i + len, m_str + i, m_length - i); // 將最后字符串拷貝出來 189 str[m_length + len] = '\0'; // 最后添加結束符 190 free(m_str); // 釋放當前字符串的堆空間 191 m_str = str; // 使用申請出來的堆空間中的字符串; 192 m_length = m_length + len; 193 } 194 else 195 { 196 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert String value ..."); 197 } 198 } 199 } 200 else 201 { 202 THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ..."); 203 } 204 205 return *this; 206 } 207 208 String& String::insert(int i, const String& s) 209 { 210 return insert(i, s.m_str); 211 } 212 213 String& String::trim() 214 { 215 int b = 0; 216 int e = m_length - 1; 217 218 while( m_str[b] == ' ' ) b++; // 確定中間字符串開始位置; 219 while( m_str[e] == ' ' ) e--; // 確定中間字符竄結束位置; 220 221 if( b == 0 ) 222 { 223 m_str[e + 1] = '\0'; // 最開始沒有空格的時候; 224 m_length = e + 1; 225 } 226 else 227 { 228 for(int i=0, j=b; j<=e; i++, j++) 229 { 230 m_str[i] = m_str[j]; // 將當前的含有的非空字符挪到前面去; 231 } 232 m_str[e - b + 1] = '\0'; // 添加結束符 233 m_length = e - b + 1; // 合法的字符個數; 234 } 235 236 return *this; // 實現鏈式調用 237 } 238 239 int String::indexOf(const char* s) const // 子串查找,返回下標 240 { 241 return kmp(m_str, s ? s : ""); 242 } 243 244 int String::indexOf(const String &s) const 245 { 246 return kmp(m_str, s.m_str); 247 } 248 249 /* 刪除下標 i 處長度為 len 的字符串 */ 250 String& String::remove(int i, int len) // 和 insert() 返回的是相同的函數,還可以以字符串類繼續訪問,如查看刪除后的字符串等 251 { 252 if( (0 <= i ) && (i < m_length) ) 253 { 254 int n = i; 255 int m = i + len; // 在 (n, m) 范圍之內的字符都要刪除掉 256 257 while( (n < m) && (m < m_length) ) // 刪除的字符串長度是不能大於當前的長度的,否則沒有意義 258 { 259 m_str[n++] = m_str[m++]; // 很經典 260 } 261 262 m_str[n] = '\0'; //因為n是不斷增加的,直到 m 為等於 length 263 m_length = n; 264 } 265 266 return *this; 267 } 268 269 String& String::remove(const char *s) // 刪除子串 270 { 271 return remove(indexOf(s), s ? strlen(s) : 0); 272 } 273 274 String& String::remove(const String &s) // 刪除子串 275 { 276 return remove(indexOf(s), s.length()); 277 } 278 279 /* 用 s 替換字符串中的 t */ 280 String& String::replace(const char* t, const char* s) 281 { 282 int index = indexOf(t); // 查找 t 的位置 283 284 if( index >= 0 ) // t 存在於當前的字符串中 285 { 286 remove(t); // 不要復制粘貼代碼,要復用 287 insert(index, s); 288 } 289 290 return *this; 291 } 292 293 String& String::replace(const String& t, const char* s) 294 { 295 return replace(t.m_str, s); 296 } 297 298 String& String::replace(const char* t, const String& s) 299 { 300 return replace(t, s.m_str); 301 } 302 303 String& String::replace(const String& t, const String& s) 304 { 305 return replace(t.m_str, s.m_str); 306 } 307 308 String String::sub(int i, int len) const // 查找當前字符串中第 i 個位置長度為 len 的字符串 310 { 311 String ret; 312 313 if( (0 <= i) && (i < m_length) ) 314 { 315 if( len < 0 ) len = 0; // 當小於零時候,不可能,要歸一化到 0 316 if(len+i > m_length) len = m_length - i; // 只能夠提取這么長的長度 317 char* str = reinterpret_cast<char*>(malloc(len + 1)); 318 319 if( str != NULL ) 320 { 321 strncpy(str, m_str + i, len); // 從 m_str + i 位置拷貝 len 長度的字符串,這里 m_str 是字符串起始位置 322 } 323 324 str[len] = '\0'; 325 ret = str; // 返回子串 326 327 free(str); 328 } 329 else 330 { 331 THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invaid ..."); 332 } 333 334 return ret; 335 } 336 337 char& String::operator [] (int i) 338 { 339 if( (0 <= i) && (i < m_length) ) 340 { 341 return m_str[i]; // 封裝; 342 } 343 else 344 { 345 THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ..."); 346 } 347 } 348 349 char String::operator [] (int i) const 350 { 351 return (const_cast<String&>(*this))[i]; // 在當前版本當中對非 const 代碼復用上面 352 } 353 354 /* 直接封裝 C 語言中的相關函數,直接封裝 C 語言庫中 strcmp() 函數即可 */ 355 bool String::operator ==(const String& s) const 356 { 357 return (strcmp(m_str, s.m_str) == 0); // strcmp 的函數原型為 int strcmp(const char* s1, const char* s2) 358 } 359 360 bool String::operator ==(const char* s) const 361 { 362 return (strcmp(m_str, s ? s : "") == 0); // 三目運算符是為了防止 s 為空指針。 363 } 364 365 bool String::operator != (const String& s) const 366 { 367 return !(*this == s); 368 } 369 370 bool String::operator != (const char* s) const 371 { 372 return !(*this == s); 373 } 374 375 bool String::operator > (const String& s) const 376 { 377 return (strcmp(m_str, s.m_str) > 0); 378 } 379 380 bool String::operator > (const char* s) const 381 { 382 return (strcmp(m_str, s ? s : "") > 0); 383 } 384 385 bool String::operator < (const String& s) const 386 { 387 return (strcmp(m_str, s.m_str) < 0); 388 } 389 390 bool String::operator < (const char* s) const 391 { 392 return (strcmp(m_str, s ? s : "") < 0); 393 } 394 395 bool String::operator >= (const String& s) const 396 { 397 return (strcmp(m_str, s.m_str) >= 0); 398 } 399 400 bool String::operator >= (const char* s) const 401 { 402 return (strcmp(m_str, s ? s : "") <= 0); 403 } 404 405 bool String::operator <= (const String& s) const 406 { 407 return (strcmp(m_str, s.m_str) <= 0); 408 } 409 410 bool String::operator <= (const char* s) const 411 { 412 return (strcmp(m_str, s ? s : "") <= 0); 413 } 414 415 String String::operator + (const String& s) const 416 { 417 return (*this + s.m_str); 418 } 419 420 String String::operator + (const char* s) const 421 { 422 String ret; // 定義一個兩個字符串拼接后結果的字符串對象; 423 int len = m_length + strlen(s ? s : ""); // 三目運算符是為了防止 s 為空指針; 424 char* str = reinterpret_cast<char*>(malloc(len + 1)); // 堆空間申請內存,並將 void* 重新解釋為 char* 425 426 if( str ) 427 { 428 strcpy(str, m_str); // 當前對象的字符串拷貝到申請的堆空間中 429 strcat(str, s ? s : ""); // 將要添加的字符串拼接到對象字符串后面 430 431 free(ret.m_str); // 這里歸還之前指針所指的內存; 432 433 ret.m_str = str; // 這里又重新賦值了指針; 434 ret.m_length = len; 435 } 436 else 437 { 438 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to add String values"); 439 } 440 441 return ret; 442 } 443 444 String& String::operator += (const String& s) 445 { 446 return (*this = *this + s.m_str); //這里用到了賦值操作符,因此必須實現其重載; 447 } 448 449 String& String::operator += (const char* s) 450 { 451 return (*this = *this + s); 452 } 453 454 String String::operator - (const String& s) const // 字符串自身會被改變 455 { 456 return String(*this).remove(s); // 直接調用構造函數產生一個新的臨時字符串對象,值和當前字符串對象值相同,然后調用臨時對象的remove() 函數將子串刪除,最后將刪除結果返回,但是當前的字符串沒有被改變,因為是拷貝賦值 457 } 458 459 String String::operator - (const char* s) const // 字符串自身會被改變 460 { 461 return String(*this).remove(s); // 同上 462 } 463 464 String& String::operator -= (const String& s) // 字符串自生不會被改變 465 { 466 return remove(s); 467 } 468 469 String& String::operator -= (const char* s) 470 { 471 return remove(s); 472 } 473 474 String& String::operator = (const String& s) 475 { 476 return(*this = s.m_str); 477 } 478 479 String& String::operator = (const char* s) 480 { 481 if( m_str != s ) 482 { 483 char* str = strdup(s ? s : ""); // 復制一份字符串; 484 485 if( str ) // 復制是否成功 486 { 487 free(m_str); 488 m_str = str; 489 m_length = strlen(m_str); 490 } 491 else 492 { 493 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to assign new String value..."); 494 } 495 } 496 497 return *this; 498 } 499 500 String& String::operator = (char c) 501 { 502 char s[] = {c, '\0'}; 503 return (*this = s); 504 } 505 506 String::~String() 507 { 508 free(m_str); 509 } 510 511 }
10,本節課字符串類測試代碼:
1 #include <iostream> 2 #include "DTString.h" 3 4 using namespace std; 5 using namespace DTLib; 6 7 void test_1() 8 { 9 cout << "test_1() begin ..." << endl; 10 String s; 11 s = 'D'; 12 13 cout << s.str() << endl; 14 cout << s.length() << endl; 15 cout << (s == "D") << endl; 16 cout << (s > "CCC") << endl; 17 18 s += " Delphi Tang "; 19 20 cout << s.str() << endl; 21 cout << s.length() << endl; 22 cout << (s == "D Delphi Tang ") << endl; 23 cout << "test_1() end ..." << endl; 24 } 25 26 void test_2() 27 { 28 cout << "test_2() begin ..." << endl; 29 30 String a[] = {"E", "D", "C", "B", "A"}; // 字符串的比較遵循字典當中的順序。 31 String min = a[0]; 32 33 for(int i=0; i<5; i++) 34 { 35 if( min > a[i] ) 36 { 37 min = a[i]; 38 } 39 } 40 41 cout << "min = " << min.str() << endl; 42 43 cout << "test_2() end ..." << endl; 44 } 45 46 int main() 47 { 48 test_1(); 49 test_2(); 50 51 return 0; 52 }
11,字符串類實現的實質是:
1,使用面向對象的技術來對原來所使用過的 C 語言字符串相關函數進行合理封裝,方便開發;
2,封裝達到的最終目的是代碼復用;
12,小結:
1,C/C++ 語言本身不支持字符串類型;
2,C 語言通過字符數組和一組函數支持字符串操作;
1,不方便也不利於代碼復用
3,C++ 通過自定義字符串類型支持字符串操作;
1,通過面向對象的技術來封裝 C 語言中的函數,封裝后的結果是我們擁有一個完整的字符串類型,並且這個字符串類型在實際工程開發中非常方便;
4,字符串類型通過 C 語言中的字符串函數實現;