一:概念
字符串是几乎在所有编程语言中可以实现的非常重要和有用的数据类型,尽管形式字符串可以有任意(但有限)的长度,实际语言的字符串的长度经常被限制到一个人工极大值。一般的说,有两种类型的字符串数据类型:“定长字符串”,它有固定的极大长度并且不管是否达到了这个极大值都使用同样数量的内存;和“变长字符串”,它的长度不是专断固定的并且依赖于实际的大小使用可变数量的内存。现在编程语言中的多数字符串是变长字符串,变成字符串对内存分配策略要求很高。
二:表示法
一种常用的表示法是使用一个字符代码的数组,下图仅仅为了更好理解字符串,实际字符串在内存不是这样结构(一般用ASCII表示,它的长度可以使用一个结束符(一般是NUL,ASCII代码是0,在C编程语言中使用这种方法)。或者在前面加入一个整数值来表示它的长度),如下图
三:各种语言中实现
C:没有字符串这个数据类型,而是使用字符数组来保存字符串;
C++:为解决C中处理字符串一些不足(心内存是否足够、字符串长度等等),C++提供string类;
Java:提供一个不可变的String(final)类;
四:代码分析(C++)
(1)符串中作为一种基本数据类型,用到的非常频繁(截取、复制、查找)字符串是各类书籍里边讲20/80原则中20,所有各种语言对字符串实现都非常高效
(2)复制作为替换、构造、追加、底层实现,各个语言对复制操作都是调用系统的方法,比如java处理字符串复制也用
(public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length));
1、构造函数
1 //申明空字符串变量时候初始化变量 2 //1、在实际开发工作,最好能做到变量申明时就做初始化工作, 3 //如果先申明空字符串变量、在赋值,相当于多一次赋值工作 4 UString::UString():ch(0),lenght(0) { 5 } 6 7 //字符串数组做初始化变量 8 UString::UString(const char* str) { 9 //这是一个inline的函数,更多inline的知识请点击链接 10 StrCpChar(str,strlen(str)); 11 } 12 //字符串数组做初始化变量, 13 UString::UString(const char* str,const size_t &len) { 14 //这是一个inline的函数,更多inline的知识请点击链接 15 StrCpChar(str,len); 16 }
2、析构字符串
1 //析构函数 2 UString::~UString() { 3 Clear(); 4 } 5 /** 6 *清空字符串,并且回收内存 7 */ 8 void UString::Clear(){ 9 10 if(ch != 0){ 11 //删除字符串并释内存,这样做很危险 12 delete[] ch; 13 ch = 0; 14 } 15 16 length = 0; 17 } 18 /** 19 * 判断字符串是否为空 20 */ 21 bool UString::IsEmpty()const{ 22 return length == 0; 23 }
3、插入字符串
1 /** 2 * 在字符串指定位置插入新的字符串(仅仅为更深入学习字符串,在实际中仅作为Replace底层实现) 3 * @param s 准备插入的字符串 4 * @param pos 开始位置 5 * @return ture表示插入成功,false插入失败 6 */ 7 void UString::StrInsert(const size_t &pos,const UString &s){ 8 const char *msg = ""; 9 if(pos < 0){ 10 msg = "UString index out of range:"; 11 } 12 if(pos > length){ 13 msg = "UString index out of range:"; 14 } 15 if(msg != ""){ 16 std::__throw_out_of_range(__N(msg)); 17 } 18 19 //计算原字符串和新插入字符串长度 20 size_t len = s.length + length; 21 char *p = new char[len]; 22 23 //插入原字符串pos前的字符串到新空间 24 for(size_t i = 0; i < pos; ++i){ 25 p[i] = ch[i]; 26 } 27 28 //插入目标字符串到新空间 29 for(size_t i = 0; i < s.length; ++i){ 30 p[pos + i] = s.ch[i]; 31 } 32 33 //插入原字符串pos后面的字符串到新空间 34 for(size_t i = pos; i < length; i++){ 35 p[i + s.length] = ch[i]; 36 } 37 38 //删除原字符串并释放内存(很危险) 39 delete[] ch; 40 41 //设置新拼接的字符串 42 ch = p; 43 length = len; 44 45 }
插入字符串过程如下图:
4、删除字符串
1 /** 2 * 从指定的位置删除指定长度字符串(仅仅为更深入学习字符串,在实际中仅作为Replace底层实现) 3 * @param pos 开始位置 4 * @param len 字符串长度 5 * @return ture表示插入成功,false插入失败 6 */ 7 void UString::StrDelete(const size_t &pos,const size_t &len){ 8 9 const char *msg = ""; 10 if(pos < 0){ 11 msg = "UString index out of range:"; 12 } 13 if(pos > length){ 14 msg = "UString index out of range:"; 15 } 16 if(len < 0){ 17 msg = "UString index out of range:"; 18 } 19 if(length-pos < len){ 20 msg = "UString index out of range:"; 21 } 22 23 if(msg != ""){ 24 std::__throw_out_of_range(__N(msg)); 25 } 26 27 size_t i = 0; 28 char *p = new char[length - len]; 29 30 //插入原字符串pos前的字符串到新空间 31 for(i = 0; i < pos; ++i){ 32 p[i] = ch[i]; 33 } 34 35 //插入原字符串pos后面的字符串到新空间 36 for(i = pos; i < length-len; ++i){ 37 p[i] = ch[i + len]; 38 } 39 40 //删除原字符串并释放内存(很危险) 41 delete[] ch;//Clear(); 42 43 //字符串长度 44 ch = p; 45 length = length - len; 46 47 }
5、替换字符串;
/** * 替换字符串(1、查找原字符串;2、删除原字符串;3、插入新字符串) * @param oldStr 原字符串 * @param newStr 新字符串 */ void UString::Replace(const UString &oldStr,const UString &newStr){ size_t i = 0; //处理一个字符串中有多个相同oldStr while(i != -1){ //查找原字符串 i = IndexOf(oldStr,i); if(i != -1){ //删除原字符串 StrDelete(i,oldStr.length); //插入新字符串 StrInsert(i,newStr); //改变i值查询一个oldStr字符串 i = i + oldStr.length; } } }
6、追加字符串
1 /** 2 * 给目标字符串追加字符串 3 * @param s 需要追加字符串 4 */ 5 void UString::StrAppend(const UString &s){ 6 7 //重新计算字符串长度 8 size_t len = length + s.length; 9 10 //申请新空间 11 char *p = new char[len]; 12 13 //复制老空间的数据到新生产的空间里 14 for(size_t i = 0; i < length; i++){ 15 p[i] = ch[i]; 16 } 17 18 //把需要追加的字符串追加新空间里 19 for(size_t i = 0; i < s.length; i++){ 20 p[i + length] = s.ch[i]; 21 } 22 //删除老数据并释放内存,这样做很危险 23 delete[] ch; 24 ch = p; 25 26 //更新字符串长度 27 length = len; 28 29 }
7、比较字符串
1 /** 2 *比较两个字符串是否相等,true:相等,flase:不相等 3 */ 4 bool UString::IsCompare(const UString &s)const{ 5 6 if(s.length && length){ 7 //取长度较小的字符串长度,做比较循环长度 8 //size_t 在C++中,设计 size_t 就是为了适应多个平台的 ,很多开源项目中都用是这个 9 size_t len = s.length > length ? length:s.length; 10 11 //循环比较每一个字符 12 for(size_t i = 0; i < len; i++){ 13 if(ch[i] != s.ch[i]){ 14 return false; 15 } 16 } 17 18 return true; 19 } 20 21 return false; 22 }
8、截取字符串
1 /** 2 * 截取字符串函数,从给定的位置截取指定长度的字符串 3 * @param s 源字符串 4 * @param pos 开始位置 5 * @param len 截取长度 6 * @return 截取后的字符串 7 */ 8 void UString::SubString(UString &s,const size_t &pos,const size_t &len){ 9 10 //错误消息 11 const char *msg = ""; 12 13 //写一个通用的方法,需要关注三个地方; 14 //1、明确的注释(漂亮的方法名和参数名);2、高效易懂的程序代码(更偏重高效);3、准确的异常信息 15 //在出现不能满足该方法的参数时,立马抛出异常,不应该返回false,这样会给程序带来很隐晦的bug 16 //下面处思想和java中String的SubString方法的API一样,不幸是char不能拼接字符串,更好的思路还在学习中 17 if(pos < 0){ 18 msg = "UString index out of range:"; 19 } 20 if(length < pos){ 21 msg = "UString index out of range:"; 22 } 23 if(len < 0){ 24 msg = "UString index out of range:"; 25 } 26 if(length + 1 - pos < len){ 27 msg = "UString index out of range:"; 28 } 29 30 if(msg != ""){ 31 //C++提供异常处理方法,如果出错立马停住(return;)并且抛出异常 32 std::__throw_out_of_range(__N(msg)); 33 } 34 35 //循环截取源字符串 36 char *p = new char[len]; 37 for(size_t i = 0; i < len; ++i){ 38 p[i] = ch[pos+i]; 39 } 40 41 //设置截取后的字符串 42 s.ch = p; 43 s.length = len; 44 } 45 /** 46 * 截取字符串函数,从给定的位置截取后面的所有字符串 47 * @param s 源字符串 48 * @param pos 开始位置 49 * @return 截取后的字符串 50 */ 51 void UString::SubString(UString &s,const size_t &pos){ 52 return this->SubString(s,pos,length-pos); 53 }
9、查找字符串位置
1 /** 2 * 从指定的位置开始查找指定的字符串, 3 * @param s 需要查找字符串 4 * @param pos 指定位置 5 */ 6 size_t UString::IndexOf(const UString &s,const size_t &pos){ 7 8 const char *msg = ""; 9 if(pos < 0){ 10 msg = "UString index out of range:"; 11 } 12 if(pos > length){ 13 msg = "UString index out of range:"; 14 } 15 if(msg != ""){ 16 std::__throw_out_of_range(__N(msg)); 17 } 18 19 UString r; 20 size_t index = -1; 21 22 // 23 for(size_t i = pos; i < length - s.length + 1; ++i){ 24 25 SubString(r,i,s.length); 26 27 if(r.IsCompare(s)){ 28 index = i; 29 break; 30 } 31 32 } 33 34 return index; 35 } 36 /** 37 * 从指定的位置开始查找指定的字符串, 38 */ 39 size_t UString::IndexOf(const UString &s){ 40 return IndexOf(s,0); 41 }
10、运行结果
测试各个方法代码
1 void UString::test(){ 2 UString t("hello sunysen!"),p,s; 3 t.StrCout(); 4 5 std::cout<<"-----------------copy begin--------------------"<<std::endl; 6 p.StrCopy(t); 7 p.StrCout(); 8 std::cout<<"-----------------copy end--------------------"<<std::endl; 9 10 std::cout<<"-----------------sub begin--------------------"<<std::endl; 11 t.SubString(s,1); 12 s.StrCout(); 13 14 t.SubString(s,1,1); 15 s.StrCout(); 16 std::cout<<"-----------------sub end--------------------"<<std::endl; 17 18 std::cout<<"-----------------delete begin--------------------"<<std::endl; 19 t.StrDelete(2,2); 20 t.StrCout(); 21 std::cout<<"-----------------delete end--------------------"<<std::endl; 22 23 std::cout<<"-----------------insert begin--------------------"<<std::endl; 24 t.StrInsert(2,UString("ll")); 25 t.StrCout(); 26 std::cout<<"-----------------insert end--------------------"<<std::endl; 27 28 std::cout<<"-----------------index begin--------------------"<<std::endl; 29 std::cout<<"hello index = "<<t.IndexOf("hello")<<std::endl; 30 std::cout<<"-----------------end begin--------------------"<<std::endl; 31 32 std::cout<<"-----------------replace begin--------------------"<<std::endl; 33 t.Replace("hello","hellor"); 34 t.StrCout(); 35 std::cout<<"-----------------replace end--------------------"<<std::endl; 36 37 t.StrAppend(" hello sunysen!"); 38 t.StrCout(); 39 }
11、完整代码
UString.h源代码如下:
1 /* 2 * UString.h 3 * 4 * Created on: Mar 24, 2013 5 * Author: sunysen 6 */ 7 8 #ifndef USTRING_H_ 9 #define USTRING_H_ 10 11 #include "string.h" 12 13 class UString { 14 private: 15 char *ch; 16 size_t length; 17 public: 18 UString(); 19 20 UString(const char* str); 21 22 UString(const UString &s); 23 24 UString(const char* str,const size_t &len); 25 26 ~UString(); 27 28 UString& StrAssign(const UString &s); 29 30 void Clear(); 31 32 void StrCopy(const UString &s); 33 34 bool IsEmpty()const; 35 36 bool IsCompare(const UString &s)const; 37 38 size_t Length(const UString &s) const; 39 40 void Concat(const UString &s,const UString &s2); 41 42 void SubString(UString &s,const size_t &pos,const size_t &len); 43 44 void SubString(UString &s,const size_t &pos); 45 46 UString SubString(const size_t &pos,const size_t &len); 47 48 void StrCout(); 49 50 void StrInsert(const size_t &pos,const UString &s); 51 52 void StrDelete(const size_t &pos,const size_t &len); 53 54 size_t IndexOf(const UString &s,const size_t &pos); 55 56 size_t IndexOf(const UString &s); 57 58 void Replace(const UString &OldStr,const UString &newStr); 59 60 void StrAppend(const UString &s); 61 62 inline void StrCpChar(const char* str,const size_t &len){ 63 ch = new char[len]; 64 65 //这样方式赋值效率很低,跟多请参考C++提供char_traits.h文件 66 for(size_t i = 0; i < len; ++i){ 67 ch[i] = str[i]; 68 } 69 70 length = len; 71 }; 72 73 void test(); 74 75 }; 76 77 #endif /* USTRING_H_ */
UString.cpp源代码如下:
1 /* 2 * UString.cpp 3 * 4 * Created on: Mar 24, 2013 5 * Author: sunysen 6 */ 7 8 #include "UString.h" 9 #include <iostream> 10 11 //申明空字符串变量时候初始化变量 12 //1、在实际开发工作,最好能做到变量申明时就做初始化工作, 13 //如果先申明空字符串变量、在赋值,相当于多一次赋值工作 14 UString::UString():ch(0),lenght(0) { 15 } 16 17 //字符串数组做初始化变量 18 UString::UString(const char* str) { 19 //这是一个inline的函数,更多inline的用户请点击 20 StrCpChar(str,strlen(str)); 21 } 22 //字符串数组做初始化变量, 23 UString::UString(const char* str,const size_t &len) { 24 //这是一个inline的函数,更多inline的用户请点击 25 StrCpChar(str,len); 26 } 27 28 //字符串数组做初始化变量, 29 UString::UString(const UString &s) { 30 31 length = s.length; 32 ch = new char[length]; 33 34 for(size_t i = 0; i < length; ++i){ 35 ch[i] = s.ch[i]; 36 } 37 38 } 39 //析构函数 40 UString::~UString() { 41 Clear(); 42 } 43 /** 44 *清空字符串,并且回收内存 45 */ 46 void UString::Clear(){ 47 48 if(ch != 0){ 49 //删除字符串并释内存,这样做很危险 50 delete[] ch; 51 ch = 0; 52 } 53 54 length = 0; 55 } 56 /** 57 * 判断字符串是否为空 58 */ 59 bool UString::IsEmpty()const{ 60 return length == 0; 61 } 62 63 /** 64 *比较两个字符串是否相等,true:相等,flase:不相等 65 */ 66 bool UString::IsCompare(const UString &s)const{ 67 68 if(s.length && length){ 69 //取长度较小的字符串长度,做比较循环长度 70 //size_t 在C++中,设计 size_t 就是为了适应多个平台的 ,很多开源项目中都用是这个 71 size_t len = s.length > length ? length:s.length; 72 73 //循环比较每一个字符 74 for(size_t i = 0; i < len; i++){ 75 if(ch[i] != s.ch[i]){ 76 return false; 77 } 78 } 79 80 return true; 81 } 82 83 return false; 84 } 85 86 /** 87 * 截取字符串函数,从给定的位置截取指定长度的字符串 88 * @param s 源字符串 89 * @param pos 开始位置 90 * @param len 截取长度 91 * @return 截取后的字符串 92 */ 93 void UString::SubString(UString &s,const size_t &pos,const size_t &len){ 94 95 //错误消息 96 const char *msg = ""; 97 98 //写一个通用的方法,需要关注三个地方; 99 //1、明确的注释(漂亮的方法名和参数名);2、高效易懂的程序代码(更偏重高效);3、准确的异常信息 100 //在出现不能满足该方法的参数时,立马抛出异常,不应该返回false,这样会给程序带来很隐晦的bug 101 //下面处思想和java中String的SubString方法的API一样,不幸是char不能拼接字符串,更好的思路还在学习中 102 if(pos < 0){ 103 msg = "UString index out of range:"; 104 } 105 if(length < pos){ 106 msg = "UString index out of range:"; 107 } 108 if(len < 0){ 109 msg = "UString index out of range:"; 110 } 111 if(length + 1 - pos < len){ 112 msg = "UString index out of range:"; 113 } 114 115 if(msg != ""){ 116 //C++提供异常处理方法,如果出错立马停住(return;)并且抛出异常 117 std::__throw_out_of_range(__N(msg)); 118 } 119 120 //循环截取源字符串 121 char *p = new char[len]; 122 for(size_t i = 0; i < len; ++i){ 123 p[i] = ch[pos+i]; 124 } 125 126 //设置截取后的字符串 127 s.ch = p; 128 s.length = len; 129 } 130 /** 131 * 截取字符串函数,从给定的位置截取后面的所有字符串 132 * @param s 源字符串 133 * @param pos 开始位置 134 * @return 截取后的字符串 135 */ 136 void UString::SubString(UString &s,const size_t &pos){ 137 return this->SubString(s,pos,length-pos); 138 } 139 140 /** 141 * 在字符串指定位置插入新的字符串(仅仅为更深入学习字符串,在实际中仅作为Replace底层实现) 142 * @param s 准备插入的字符串 143 * @param pos 开始位置 144 */ 145 void UString::StrInsert(const size_t &pos,const UString &s){ 146 const char *msg = ""; 147 if(pos < 0){ 148 msg = "UString index out of range:"; 149 } 150 if(pos > length){ 151 msg = "UString index out of range:"; 152 } 153 if(msg != ""){ 154 std::__throw_out_of_range(__N(msg)); 155 } 156 157 //计算原字符串和新插入字符串长度 158 size_t len = s.length + length; 159 char *p = new char[len]; 160 161 //插入原字符串pos前的字符串到新空间 162 for(size_t i = 0; i < pos; ++i){ 163 p[i] = ch[i]; 164 } 165 166 //插入目标字符串到新空间 167 for(size_t i = 0; i < s.length; ++i){ 168 p[pos + i] = s.ch[i]; 169 } 170 171 //插入原字符串pos后面的字符串到新空间 172 for(size_t i = pos; i < length; i++){ 173 p[i + s.length] = ch[i]; 174 } 175 176 //删除原字符串并释放内存(很危险) 177 delete[] ch; 178 179 //设置新拼接的字符串 180 ch = p; 181 length = len; 182 183 } 184 /** 185 * 从指定的位置删除指定长度字符串(仅仅为更深入学习字符串,在实际中仅作为Replace底层实现) 186 * @param pos 开始位置 187 * @param len 字符串长度 188 */ 189 void UString::StrDelete(const size_t &pos,const size_t &len){ 190 191 const char *msg = ""; 192 if(pos < 0){ 193 msg = "UString index out of range:"; 194 } 195 if(pos > length){ 196 msg = "UString index out of range:"; 197 } 198 if(len < 0){ 199 msg = "UString index out of range:"; 200 } 201 if(length-pos < len){ 202 msg = "UString index out of range:"; 203 } 204 205 if(msg != ""){ 206 std::__throw_out_of_range(__N(msg)); 207 } 208 209 size_t i = 0; 210 char *p = new char[length - len]; 211 212 //插入原字符串pos前的字符串到新空间 213 for(i = 0; i < pos; ++i){ 214 p[i] = ch[i]; 215 } 216 217 //插入原字符串pos后面的字符串到新空间 218 for(i = pos; i < length-len; ++i){ 219 p[i] = ch[i + len]; 220 } 221 222 //删除原字符串并释放内存(很危险) 223 delete[] ch;//Clear(); 224 225 //字符串长度 226 ch = p; 227 length = length - len; 228 229 } 230 231 size_t UString::IndexOf(const UString &s,const size_t &pos){ 232 233 const char *msg = ""; 234 if(pos < 0){ 235 msg = "UString index out of range:"; 236 } 237 if(pos > length){ 238 msg = "UString index out of range:"; 239 } 240 if(msg != ""){ 241 std::__throw_out_of_range(__N(msg)); 242 } 243 244 UString r; 245 size_t index = -1; 246 247 for(size_t i = pos; i < length - s.length + 1; ++i){ 248 249 SubString(r,i,s.length); 250 251 if(r.IsCompare(s)){ 252 index = i; 253 break; 254 } 255 256 } 257 258 return index; 259 } 260 261 size_t UString::IndexOf(const UString &s){ 262 return IndexOf(s,0); 263 } 264 /** 265 * 替换字符串(1、查找原字符串;2、删除原字符串;3、插入新字符串) 266 * @param oldStr 原字符串 267 * @param newStr 新字符串 268 */ 269 void UString::Replace(const UString &oldStr,const UString &newStr){ 270 271 size_t i = 0; 272 //处理一个字符串中有多个相同oldStr 273 while(i != -1){ 274 275 //查找原字符串 276 i = IndexOf(oldStr,i); 277 if(i != -1){ 278 //删除原字符串 279 StrDelete(i,oldStr.length); 280 //插入新字符串 281 StrInsert(i,newStr); 282 //改变i值查询一个oldStr字符串 283 i = i + oldStr.length; 284 } 285 286 } 287 288 } 289 void UString::StrAppend(const UString &s){ 290 291 size_t len = length + s.length; 292 293 char *p = new char[len]; 294 295 for(size_t i = 0; i < length; i++){ 296 p[i] = ch[i]; 297 } 298 299 for(size_t i = 0; i < s.length; i++){ 300 p[i + length] = s.ch[i]; 301 } 302 delete[] ch; 303 ch = p; 304 length = len; 305 306 } 307 308 void UString::StrCout(){ 309 if(ch != 0){ 310 for(size_t i = 0; i < length; ++i){ 311 std::cout<<ch[i]; 312 } 313 std::cout<<"\n"<<std::endl; 314 } 315 } 316 317 void UString::StrCopy(const UString &s){ 318 319 ch = new char[s.length]; 320 321 if(ch != 0){ 322 for(size_t i = 0; i < s.length; ++i){ 323 ch[i] = s.ch[i]; 324 } 325 } 326 327 length = s.length; 328 } 329 void UString::test(){ 330 UString t("hello sunysen!"),p,s; 331 t.StrCout(); 332 333 std::cout<<"-----------------copy begin--------------------"<<std::endl; 334 p.StrCopy(t); 335 p.StrCout(); 336 std::cout<<"-----------------copy end--------------------"<<std::endl; 337 338 std::cout<<"-----------------sub begin--------------------"<<std::endl; 339 t.SubString(s,1); 340 s.StrCout(); 341 342 t.SubString(s,1,1); 343 s.StrCout(); 344 std::cout<<"-----------------sub end--------------------"<<std::endl; 345 346 std::cout<<"-----------------delete begin--------------------"<<std::endl; 347 t.StrDelete(2,2); 348 t.StrCout(); 349 std::cout<<"-----------------delete end--------------------"<<std::endl; 350 351 std::cout<<"-----------------insert begin--------------------"<<std::endl; 352 t.StrInsert(2,UString("ll")); 353 t.StrCout(); 354 std::cout<<"-----------------insert end--------------------"<<std::endl; 355 356 std::cout<<"-----------------index begin--------------------"<<std::endl; 357 std::cout<<"hello index = "<<t.IndexOf("hello")<<std::endl; 358 std::cout<<"-----------------end begin--------------------"<<std::endl; 359 360 std::cout<<"-----------------replace begin--------------------"<<std::endl; 361 t.Replace("hello","hellor"); 362 t.StrCout(); 363 std::cout<<"-----------------replace end--------------------"<<std::endl; 364 365 t.StrAppend(" hello sunysen!"); 366 t.StrCout(); 367 }
六:环境
1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;
2、开发工具:Eclipse+make
七:题记
1、上面仅仅简单介绍字符串常用操作,还有更多知识需要阅读系统自带的String类(字符串查找、重复的字符串处理、高效字符串赋值),
2、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;
3、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;
4、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛";
欢迎继续阅读“启迪思维:数据结构和算法”系列