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 语言中的字符串函数实现;