字符串类——字符串类的创建(上)

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

猜你喜欢

转载自www.cnblogs.com/dishengAndziyu/p/10923271.html