设计精美的国外网站,施工单位招聘信息,桂林象鼻山是什么地貌,网络广告的优缺点各位读者老爷好#xff0c;俺最近在学习string的一些知识。为了更好的了解string的结构#xff0c;俺模拟实现了一个丐版string#xff0c;有兴趣的老爷不妨垂阅#xff01;#xff01;#xff01; 目录
1.string类的定义
2.模拟实现成员函数接口
2.1.constructor俺最近在学习string的一些知识。为了更好的了解string的结构俺模拟实现了一个丐版string有兴趣的老爷不妨垂阅 目录
1.string类的定义
2.模拟实现成员函数接口
2.1.constructor构造函数
2.2.destructor析构函数
2.3.c_str 2.4.size 2.5.operator[]
2.6.begin、end
2.7.capacity
2.8.reserve
2.9.push_back
2.10.append
2.11.operator
2.12.insert
2.13.erase
2.14.find 2.15.substr
2.16.constructor
2.17.operator 2.18.clear 2.19.swap 3.模拟实现非成员函数接口
3.1.relational operators
3.2.operator
3.3.operator
3.4.getline
4.个别接口的不同写法 4.1.constructor的不同写法
4.2.operator的不同写法
4.3.operator的不同写法
5. string模拟实现完整代码
5.1.string.h
5.2.string.cpp
5.3.test.cpp 俺在上篇博客介绍过string是一个由类模板实例化而来的类是用来管理字符串的。其底层实现大致就是字符顺序表。
那么俺模拟实现string是不是要从类模板开始搞起呢当然不是俺可没有这个本事。俺直接定义string类就好。并且string的接口众多俺只模拟实现部分常用接口噢噢
1.string类的定义
namespace HD
{class string{char* _str nullptr;size_t _capacity 0;size_t _size 0;public:typedef char* iterator;typedef const char* const_iterator;static const size_t npos;//string()// :_str(new char[1]{\0})// ,_capacity(0)// ,_size(0)//{//}//string(const char* s)//{// _size strlen(s);// _capacity _size;// _str new char[_capacity1];// strcpy(_str, s);//}string(const char* s )//以上两构造合二为一{_size strlen(s);_capacity _size;_str new char[_capacity 1];strcpy(_str, s);}~string(){delete[] _str;_str nullptr;_size _capacity 0;}const char* c_str()const{return _str;}size_t size()const{return _size;}char operator[](size_t pos){assert(pos _size);return _str[pos];}const char operator[](size_t pos)const{assert(pos _size);return _str[pos];}iterator begin(){return _str;}iterator end(){return _str _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str _size;}size_t capacity()const{return _capacity;}string(const string str)//拷贝构造函数{/写法一//*_str new char[str._capacity 1];strcpy(_str, str._str);_size str._size;_capacity str._capacity;*//写法二/string tmp(str.c_str());swap(tmp);}void reserve(size_t n 0);void push_back(char c);string append(const char* s);string operator (const char* s);string operator(char c);string insert(size_t pos, size_t n, char c);string insert(size_t pos, const char* s);string erase(size_t pos 0, size_t len npos);size_t find(char c, size_t pos 0) const;size_t find(const char* s, size_t pos 0) const;string substr(size_t pos 0, size_t len npos) const;string operator(const string str);//string operator(const string str);void clear();void swap(string str);};
}
对于这个string类的定义如上。
1.俺们知道string类底层是字符顺序表所以俺定义3个private成员变量_str、_capacity、_size来实现并且给这3个成员变量缺省值这3个缺省值是很有意义的意义的体现会在下面的讲解可见。 2.俺将一些成员函数接口的声明和定义全部放到string类当中则默认当成内联函数处理还有一些成员函数接口的声明和定义分离只将这些成员函数的声明放到string类当中。
3.为了防止与命名空间std的string也就是STL的string产生冲突俺实现的string使用自己命名空间如HD封装起来。 PS以下所说的string若没有特殊声明都默认指俺模拟实现的string。若指STL的string俺会用红色字体标注string如string。 2.模拟实现成员函数接口
2.1.constructor构造函数
对于这个接口string内重载了很多个函数。俺暂时就模拟实现两个如下 1.string()
这个函数的模拟实现如下俺将其声明和定义都放到string类中 string():_str(new char[1]{\0}),_capacity(0),_size(0){}
这里俺使用初始化列表来初始化。易知这是一个构造空的string对象的那么当然将_capacity和_size初始化成0但是对于_str俺将其指向堆区动态开辟的一个字节的空间这块空间存储字符\0。
为什么_str不用nullptr来初始化呢其实大有意义让子弹再飞一会原因俺下面介绍。 3.string (const char* s)
这个函数的模拟实现如下俺将其声明和定义都放到string类中 string(const char* s){_size strlen(s);_capacity _size;_str new char[_capacity1];strcpy(_str, s);}这里俺在函数体内初始化。俺们知道这个构造函数的功能是用C-string来构造string类对象。那么我们开好空间记得多开一个空间存放\0将C_string也就是s拷贝到空间上_size和_capacity 初始化为C_string的字节数长度就好。 2.2.destructor析构函数
这个接口就是析构函数string类中申请了资源俺们必须显示写析构函数不然会造成资源的泄露。这个接口的模拟实现如下俺将其声明和定义都放到string类中 ~string(){delete[] _str;_str nullptr;_size _capacity 0;}
若不显示写析构函数_str指向的空间就泄露了。
2.3.c_str 这个接口功能就是返回C形式的字符串也就是返回底层的_str。这个接口的模拟实现如下俺将其声明和定义都放到string类中 const char* c_str()const{return _str;}
这是一个对this指针指向对象的成员变量只进行读访问的函数所以我们将其实现成const成员函数且这样子普通对象和const对象都可调用这个函数。 俺们来使用以下自己模拟实现的接口。事先要先知道的是本工程包含3个文件 …… string.h存放string类的定义和非成员函数的声明 …… string.cpp存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义 …… test.cpp存放测试代码来测试咱们模拟实现的string的功能 …… 俺们到现在介绍了几个接口的模拟实现了咱们在test.cpp中试试 #includestring.h
namespace HD
{void test1(){string S1;string S2(hello world);cout S1.c_str() endl S2.c_str() endl;}
}
int main()
{HD::test1();return 0;
} 运行结果 有几个点需要明确 …… 1.为什么test1函数需要用命名空间HD封装起来 因为若是不用命名空间HD封装起来string类实例化对象S1、S2会调用string的构造函数接口且c_str接口的调用亦是调用string的接口这样子就违背了我们测试模拟实现的接口的初衷。有关命名空间和编译器访问规则的介绍可以看[C]C入门1.0。 …… 2.主函数中调用test1函数为什么要指定命名空间HD域 因为若是不指定命名空间HD域编译器会访问不到test1函数。编译器的访问规则介绍可以看[C]C入门1.0。 …… 3.string()函数的定义中_str不用nullptr来初始化呢 若是用nullptr来初始化_str当空string对象调用c_str接口时就返回nullptr当我们对这个返回值进行操作时就有可能访问到空指针程序就会崩溃。 2.4.size 这个接口这个接口返回字符串的长度。这个接口的模拟实现如下俺将其声明和定义都放到string类中 size_t size()const{return _size;} 这是一个对this指针指向对象的成员变量只进行读访问的函数所以我们将其实现成const成员函数且这样子普通对象和const对象都可调用这个函数。
在test.cpp中测试一下
#includestring.h
namespace HD
{void test2(){string S1;string S2(hello world);cout S1.size() endl S2.size() endl;}
}
int main()
{HD::test2();return 0;
}
运行结果 2.5.operator[]
本接口返回对字符串中位置pos处的字符的引用。且string中本接口重载了2个函数分别供普通对象和const对象使用。俺也模拟实现2个函数并将其声明和定义都放到string类中 char operator[](size_t pos){assert(pos _size);return _str[pos];}const char operator[](size_t pos)const{assert(pos _size);return _str[pos];}
在test.cpp中测试一下
#includestring.h
namespace HD
{void test3(){string S1(nbo);for (int i 0; i S1.size(); i){S1[i] - 1;cout S1[i];}cout endl;const string S2(hello world);for (int i 0; i S2.size(); i){cout S2[i];}}
}
int main()
{HD::test3();return 0;
} 对于char operator[](size_t pos)这个函数这个函数是一个对this指针指向对象的成员变量只进行读访问的函数可是我们不能将其实现成const成员函数为什么呢
因为俺们实现了const char operator[](size_t pos)const这个函数供const对象调用若是还将char operator[](size_t pos)实现成const成员函数的话无法与const char operator[](size_t pos)构成函数重载
2.6.begin、end 有一个问题基于范围的for循环的方式对所有容器都适用。那么到现在为止俺们模拟实现的string适用于基于范围的for循环吗
俺们在test.cpp试试
#includestring.h
namespace HD
{void test4(){string S1(hello world);for (auto ch : S1){cout ch;}cout endl;}
}
int main()
{HD::test4();return 0;
}发现行不通的错误列表有一堆报错 到现在为止模拟实现的string不适用基于范围的for循环的原因也很简单因为范围for的底层很简单容器遍历实际就是替换为迭代器。咱们模拟实现的string还没有模拟实现迭代器也没有模拟实现迭代器相关接口当然不适用了。
那么如何模拟实现迭代器呢 迭代器的实现很复杂其实迭代器本质就是模拟指针的行为啊不管迭代器是用什么方法实现的都希望迭代器能像指针般使用达到访问容器的目的。
那么针对string的模拟实现来说不用搞那么复杂我们正好可以返璞归真利用string类的底层是字符顺序表这个特点俺们就可以用指针模拟实现迭代器因为本身就可以用指针来访问顺序表啊
正向迭代器的模拟实现如下对应的迭代器属于对应容器的类域所以俺将其声明放在string类中 typedef char* iterator;typedef const char* const_iterator; 俺就不模拟实现反向迭代器了反向迭代器就不是用指针可以模拟实现的了复杂的很 模拟实现了正向迭代器俺们就可以模拟实现正向迭代器相关的接口了以下接口的声明和定义都放到string类中 iterator begin(){return _str;}iterator end(){return _str _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str _size;}
这些接口的模拟实现好像不用解释什么根据接口的作用很容易就能写出来啊。
模拟实现了正向迭代器和正向迭代器相关的接口俺们在test.cpp中测试一下
#includestring.h
namespace HD
{void test4(){string S1(hello world);for (auto ch : S1){cout ch;}cout endl;const string S2(God is a girl);string::const_iterator cit S2.begin();while (cit ! S2.end()){cout *cit;cit;}cout endl;}
}
int main()
{HD::test4();return 0;
}
运行结果没问题的基于范围的for循环也能正常使用了 2.7.capacity 这个接口这个接口返回字符串的容量。这个接口的模拟实现如下俺将其声明和定义都放到string类中 size_t capacity()const{return _capacity;} 这是一个对this指针指向对象的成员变量只进行读访问的函数所以我们将其实现成const成员函数且这样子普通对象和const对象都可调用这个函数这点以下不再赘述。
在test.cpp中测试一下
#includestring.h
namespace HD
{void test5(){string S1;string S2(hello world);cout S1.capacity() endl S2.capacity() endl;}
}
int main()
{HD::test5();return 0;
}
运行结果 2.8.reserve 本接口功能是为string预留空间不改变有效元素个数。俺模拟实现这个接口不搞那么复杂当扩容才使其生效。模拟实现本成员函数采用声明和定义分离的写法
string类再次说明string的定义放在了string.h中中声明
void reserve(size_t n 0); string.cpp中定义
#includestring.h
namespace HD
{void string::reserve(size_t n){if (n _capacity){char* p new char[n 1];strcpy(p, _str);delete[] _str;_str p;_capacity n;}}
}
1.缺省参数不能在函数声明和定义中同时出现当函数声明和定义分离时在声明给缺省参数 。
2.别忘记使用命名空间HD封装起来。
3.该函数声明和定义分离所以定义成员函数时成员函数名前需要加string::。
4.该接口模拟实现的思想当需要扩容时动态开辟n1多开一个空间存储\0个字节新空间在将原来的数据拷贝到新空间释放旧空间再让_str指向新空间更新_capacity即可。
在test.cpp中试试
#includestring.h
namespace HD
{void test6(){string S1;string S2(hello world);cout S1.capacity() S2.capacity() endl;S1.reserve(10); S2.reserve(1);cout S1.capacity() S2.capacity() endl;}
}
int main()
{HD::test6();return 0;
}
运行结果符合预期 2.9.push_back
本接口需要实现将字符c附加到字符串末尾使其长度增加1。俺的模拟实现采取本成员函数声明和定义分离的写法
string类再次说明string的定义放在了string.h中中声明
void push_back(char c);
string.cpp中定义
#includestring.h
namespace HD
{void string::push_back(char c){if (_capacity _size)//扩容{reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] c;_str[_size] \0;}
}1.别忘记使用命名空间HD封装起来这点以下不再赘述。
2.该函数声明和定义分离所以定义成员函数时成员函数名前需要加string::这点以下不再赘述。
3.插入字符之前先检查字符串容量是否充足若是不充足就调用reserve接口扩容当字符串旧容量为0时给4个字节空间当字符串旧容量不为0时2倍扩容。将字符c插入字符串末尾并使字符串长度加一再在字符串末尾插入\0就好了。
在test.cpp中试试
#includestring.h
namespace HD
{void test7(){string S1;string S2(hello world);S1.push_back(!);S2.push_back(!);cout S1.capacity() S2.capacity() endl;cout S1.c_str() S2.c_str() endl;}
}
int main()
{HD::test7();return 0;
}运行结果 2.10.append 这个接口string中重载了很多个函数俺就模拟实现string append (const char* s)这个函数。这个函数功能就是在字符串后追加字符串s。 俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明
string append(const char* s); string.cpp中定义
#includestring.h
namespace HD
{string string::append(const char* s){if (_capacity strlen(s) _size)//扩容{size_t n strlen(s);reserve(n _size 2 * _capacity ? n _size : 2 * _capacity);}/写法一///for (int i 0; i strlen(s); i)//{// _str[_size] s[i];//}//_str[_size] \0;/写法二/strcpy(_str _size, s);_size strlen(s);return *this;}
}
插入字符串s之前先检查字符串容量是否充足若是不充足就使用reverse接口扩容这里的扩容个数不能使用push_back接口的逻辑了因为有可能4个字节空间或者字符串旧容量的2倍仍然不够字符串s插入的需要。再将字符串s插入到字符串末尾并且使得字符串长度加strlen(s)并且返回*this就好了。这里插入字符串s到字符串末尾的写法有很多俺写了两个但是不管是那种写法都要保证插入之后字符串末尾有\0。
在test.cpp中试试
#includestring.h
namespace HD
{void test8(){string S1;string S2(hello);S1.append(young man);S2.append( Totoro!);cout S1.capacity() S2.capacity() endl;cout S1.c_str() S2.c_str() endl;}
}
int main()
{HD::test8();return 0;
}
运行结果 2.11.operator
这个接口string中重载了3个函数功能就是通过在字符串的当前值末尾附加其他字符来扩展字符串。俺就模拟实现其中2个函数string operator (const char* s)和string operator (char c)。其实模拟实现这2个函数分别直接调用俺模拟实现的append和push_back然后返回*this就好了。
俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明 string operator (const char* s);string operator(char c);
string.cpp中定义
#includestring.h
namespace HD
{string string::operator (const char* s){append(s);return *this;}string string::operator(char c){push_back(c);return *this;}
}
在test.cpp中试试
#includestring.h
namespace HD
{void test9(){string S1;string S2(hello);S1 !; S2 Totoro;cout S1.c_str() S2.c_str() endl;}
}
int main()
{HD::test9();return 0;
}
运行结果 2.12.insert 这个接口string中重载了7个函数都是在字符串中pos或p指示的字符之前插入其他字符。俺就模拟实现其中两个string insert(size_t pos, size_t n, char c)和string insert(size_t pos, const char* s)。
俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明 string insert(size_t pos, size_t n, char c);string insert(size_t pos, const char* s);
string.cpp中定义
#includestring.h
namespace HD
{string string::insert(size_t pos, size_t n, char c){assert(pos _size);if (_capacity n _size)//扩容{reserve(n _size 2 * _capacity ? n _size : 2 * _capacity);}size_t end _size n;while (end pos n - 1){_str[end] _str[end - n];end--;}for (size_t i 0; i n; i){_str[pos i] c;}_size n;return *this;}string string::insert(size_t pos, const char* s){assert(pos _size);size_t n strlen(s);if (_capacity n _size){reserve(n _size 2 * _capacity ? n _size : 2 * _capacity);}size_t end _size n;while (end pos n - 1){_str[end] _str[end - n];end--;}strncpy(_str pos, s, n);_size n;return *this;}
}
这2个函数的模拟实现逻辑都不难。无非就是处理好字符串容量使之足够插入的需要。然后把从字符位置为pos的字符到字符\0在内的所有字符往后挪动指定个数个字节位置指定个数就是所插入字符的个数或者字符串长度再插入指定个数的字符或者字符串最后返回*this就好了。
在test.cpp中试试
#includestring.h
namespace HD
{void test10(){string S1(A man);string S2(hello);S1.insert(2, young);S2.insert(S2.size(), 5, A);cout S1.c_str() S2.c_str() endl;}
}
int main()
{HD::test10();return 0;
}
运行结果 2.13.erase string在这个接口重载了3个函数。作用都是擦除字符串的一部分缩短其长度。这里俺只模拟实现1个函数string erase(size_t pos 0, size_t len npos)。
俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明
string erase(size_t pos 0, size_t len npos); string.cpp中定义
#includestring.h
namespace HD
{string string::erase(size_t pos, size_t len){assert(pos _size);if (_size - pos len){_str[pos] \0;_size - (_size - pos);}size_t begin pos len;while (begin _size 1){_str[begin - len] _str[begin];begin;}_size - len;return *this;}
}
1.这里用到了缺省参数npos所以我们必须处理npos
npos在string是string类的公共静态成员变量那么俺模拟实现也在string类中声明为公共静态成员变量
static const size_t npos;
根据static成员的知识静态成员变量必须在类外定义定义时不添加static关键字。俺在string.cpp中定义
#includestring.h
namespace HD
{const size_t string::npos -1;
} 2.缺省参数不能在函数声明和定义中同时出现当函数声明和定义分离时在声明给缺省参数以下不再赘述。 3.这个接口的模拟实现思路若是len_size-pos说明要擦除从pos位置开始的全部字符那么处理好\0并使得字符串长度减去擦除字符个数就好了否则将需要擦除的字符串后面剩余的字符串往前挪动len个字节位置并且字符串长度减去len即可。最后返回*this。
在test.cpp中试试
#includestring.h
namespace HD
{void test11(){string S1(A man);string S2(hello fat Totoro);S1.erase();S2.erase(5, 4);cout S1.c_str() endl S2.c_str() endl;}
}
int main()
{HD::test11();return 0;
}
运行结果 2.14.find string中这个接口重载了四个函数。功能就是在字符串中搜索其参数指定的序列的第一个匹配项找到了返回第一个匹配项的字符位置找不到返回string::npos。俺就模拟实现其中2个函数size_t find(char c, size_t pos 0) const和size_t find(const char* s, size_t pos 0) const。
俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明 size_t find(char c, size_t pos 0) const;size_t find(const char* s, size_t pos 0) const;
string.cpp中定义
#includestring.h
namespace HD
{size_t string::find(char c, size_t pos) const{for (size_t i pos; i _size; i){if (_str[i] c){return i;}}return npos;}size_t string::find(const char* s, size_t pos) const{if (const char* p strstr(_str pos, s)){return p - _str;}return npos;}
}
这个模拟实现逻辑很简单不解释了。其中size_t find(const char* s, size_t pos 0) const这个函数俺直接套用了函数strstr对于这个函数有兴趣可以看【C语言】字符函数和字符串函数的介绍。
在test.cpp中试试
#includestring.h
namespace HD
{void test12(){string S1(A man);string S2(hello fat Totoro);cout S1.find(m) endl;cout S2.find(fat) endl;}
}
int main()
{HD::test12();return 0;
}运行结果 2.15.substr 功能在str中从pos位置开始截取len个字符将这些字符构造一个新的字符串对象返回。
俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明
string substr(size_t pos 0, size_t len npos) const;
string.cpp中定义
#includestring.h
namespace HD
{string string::substr(size_t pos, size_t len) const{assert(pos _size);if (len _size - pos){len _size - pos;}string S1;S1.reserve(len);for (int i 0; i len; i){S1 _str[pos i];}return S1;}
}
这个模拟实现思路主要就是构造一个局部空字符串S1将需要截取的字符一个个摘取到S1当中返回S1就好了。
俺们在test.cpp中试试
#includestring.h
namespace HD
{void test13(){string S1(hello fat Totoro);string S2 S1.substr(6, 3);cout S2.c_str() endl;}
}
int main()
{HD::test13();return 0;
}
运行结果 其实模拟实现到此为止这个代码不是在任何环境都能运行成功的
因为substr接口是传值返回理论上是将substr的返回值S1拷贝到一个临时对象当中局部对象S1便销毁了再用临时对象拷贝构造S2的。C规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造但是俺还没有模拟实现拷贝构造呢若未显式定义编译器会生成默认的拷贝构造函数默认的拷贝构造函数完成的是浅拷贝噢
结论就是若没有显示实现拷贝构造函数substr的调用理论上是会出问题的因为浅拷贝导致S2底层_str指向一块已经销毁的空间substr的局部对象底层的_str。
那么在俺的环境VS2022中为什么能运行成功呢
因为俺这个VS2022优化了但是不是任何编译器都会进行优化所以为了保证substr的正常调用必须显示实现拷贝构造函数。
2.16.constructor 模拟实现其中的拷贝构造函数。这个接口的模拟实现如下俺将其声明和定义都放到string类中 string(const string str)//拷贝构造函数{_str new char[str._capacity 1];strcpy(_str, str._str);_size str._size;_capacity str._capacity;}
这个模拟实现思路很简单不解释了。一定记住类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时则拷贝构造函数是一定要写的否则就是浅拷贝会出问题。
2.17.operator string中重载了3个函数。俺就模拟实现了1个函数string operator(const string str)。
注意模拟实现的时候需要完成深拷贝就好思路跟拷贝构造函数大同小异了。若是实现浅拷贝问题就很大俺画图方便理解 俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明
string operator(const string str);
string.cpp中定义
#includestring.h
namespace HD
{string string::operator(const string str){if (thisstr){return *this;}delete[]_str;_str new char[str._capacity 1];strcpy(_str, str._str);_size str.size();_capacity str.capacity();return *this;}
}
这种写法要注意的是防止自己给自己赋值噢若是自己给自己赋值没有检查的话一上来就把_str给delete掉了那就没得玩了 2.18.clear 这个接口擦除字符串的内容该字符串将变为空字符串长度size接口的返回值为0个字符一般不会清理字符串容量。
俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明 void clear();
string.cpp中定义
#includestring.h
namespace HD
{void string::clear(){_str[0] \0;_size 0;}
}
这个接口的模拟实现在test.cpp中试试
#includestring.h
namespace HD
{void test14(){string S1(hello fat Totoro);cout S1.capacity() endl;S1.clear();cout S1.c_str() S1.capacity() endl;}
}
int main()
{HD::test14();return 0;
}
运行结果 2.19.swap 这个接口的功能就是与str交换内容就是两个字符串底层的内容。
俺的模拟实现采取本成员函数声明和定义分离的写法
string类中声明
void swap(string str);
string.cpp中定义
#includestring.h
namespace HD
{void string::swap(string str){std::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}
}
对于这个模拟实现俺直接调用C算法库的swap 调用3次swap分别交换_str、_size、_capacity。
俺们在test.cpp中试试
#includestring.h
namespace HD
{void test15(){string S1(hello world);string S2(I do love you,yes I do love you);cout 交换前S1 S1 S1.size() S1.capacity() endl;cout 交换前S2 S2 S2.size() S2.capacity() endl;cout endl;S1.swap(S2);cout 交换后S1 S1 S1.size() S1.capacity() endl;cout 交换后S2 S2 S2.size() S2.capacity() endl;}
}
int main()
{HD::test15();return 0;
}
运行结果 C算法库的swap是一个函数模板任何两个同类型的变量都可以直接调用以达到交换的目的俺们可以看看截图 但是这个函数模板对于交换两个申请了资源的自定义类型变量就不是很友好虽然可以完成交换但是消耗比较大为什么消耗大 当2个申请了资源的自定义类型类型变量要交换而调用这个函数模板时这个模板就生成对应自定义类型的函数我们看这个生成的函数体内 …… T c(a)用第1个形参a来拷贝构造1个同类型临时对象c由于本自定义类型申请了资源那么调用拷贝构造时就完成1次深拷贝 …… ab将第2个形参b赋值给a调用operator涉及资源申请的自定义类型来说,operator需要实现深拷贝完成第2次深拷贝 …… bc将临时对象c赋值给b调用operator完成第3次深拷贝 交换两个申请了资源的自定义类型变量需要完成3次深拷贝消耗当然大了
这就是为什么string中为什么成员函数接口实现了1个swap了非成员函数接口还要实现1个swap的意义 俺们看string中的成员函数接口swap的底层实现跟俺模拟实现的成员函数接口swap是大同小异的都是把自定义类型拆成若干个内置类型分别交换的这样子交换就不会涉及深拷贝1次深拷贝也没有。 …… string中非成员函数接口swap的行为跟成员函数接口swap是一模一样的也是把自定义类型拆成若干个内置类型分别交换也不涉及深拷贝。 …… 若是string中没有实现非成员函数接口swap那么当2个string对象想要交换且没有调用成员函数接口swap时可以调用swap完成交换会走函数模板就需要3次深拷贝。 …… 若是string中实现了非成员函数接口swap那么那么当2个string对象想要交换且没有调用成员函数接口swap时当去调用swap完成交换时就会走非成员函数接口swap而不是走函数模板因为对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而不会从该模板产生出一个实例这样子不是避免深拷贝了吗。。。 …… 所以string中的非成员函数接口swap和成员函数接口swap都是避免两个string类对象交换时陷入深拷贝的。 3.模拟实现非成员函数接口
3.1.relational operators
string类重载了一堆运算符重载用于比较两个C字符串的关系或者比较一个C字符串和一个C字符串的关系。 俺在这里只是模拟实现比较2个C字符串的关系的函数如下
俺的模拟实现采取函数声明和定义分离的写法
在string.h中声明
namespace HD
{bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator!(const string lhs, const string rhs);
}
别忘了用命名空间HD封装起来以区分命名空间std内实现的非成员函数这点以下不再赘述。
在string.cpp中定义
#includestring.h
namespace HD
{bool operator(const string lhs, const string rhs){return strcmp(lhs.c_str(), rhs.c_str()) 0;}bool operator(const string lhs, const string rhs){return strcmp(lhs.c_str(), rhs.c_str()) 0;}bool operator(const string lhs, const string rhs){return !(lhs rhs);}bool operator(const string lhs, const string rhs){return lhs rhs lhs rhs;}bool operator(const string lhs, const string rhs){return !(lhs rhs);}bool operator!(const string lhs, const string rhs){return !(lhs rhs);}
} 模拟实现直接调用strcmp比较2个C字符串底层的_str就好了。实现了operator和operator其他的函数实现可以直接复用这2个函数的逻辑。
在test.cpp中试试
#includestring.h
namespace HD
{void test16(){string S1(hello world);string S2(I do love you,yes I do love you);cout (S1 S1) endl;cout (S1 ! S1) endl;cout (S1 S2) endl;cout (S1 S2) endl;cout (S1 S1) endl;cout (S1 S2) endl;}
}
int main()
{HD::test16();return 0;
}
运行结果 俺虽然只是模拟实现了比较2个C字符串的关系的函数但是却也可以支持1个C字符串与1个C字符串的比较。举例如下
#includestring.h
namespace HD
{void test17(){const char* S1 hello world;string S2(I do love you,yes I do love you);cout (S1 S1) endl;cout (S1 ! S1) endl;cout (S1 S2) endl;cout (S1 S2) endl;cout (S1 S1) endl;cout (S1 S2) endl;}
}
int main()
{HD::test17();return 0;
}
运行结果 为什么可以这样子呢
因为构造函数不仅可以构造与初始化对象对于单个参数构造函数还具有类型转换的作用。具体介绍可以去看【C】类和对象3.0 。 俺模拟实现了比较2个C字符串的关系的函数却不支持2个C字符串的比较噢因为俺在【C】类和对象2.0中介绍过重载操作符必须有一个类类型参数不能通过运算符重载改变内置类型对象的含义。
例如
#includestring.h
namespace HD
{void test18(){const char* S1 hello world;const char* S2 I do love you,yes I do love you;cout (S1 S1) endl;cout (S1 ! S1) endl;cout (S1 S2) endl;cout (S1 S2) endl;cout (S1 S1) endl;cout (S1 S2) endl;}
}
int main()
{HD::test18();return 0;
}
要搞清楚若是代码写成这样子不会调用模拟实现的非成员函数接口relational operators的函数 也不是2个C字符串的比较两个C字符串的比较要用函数strcmp。这里本质是比较2个地址呢。
运行结果 3.2.operator 流插入运算符重载模拟实现还是很简单的将底层_str的字符一个个输出就好了。
俺的模拟实现采取函数声明和定义分离的写法
在string.h中声明
namespace HD
{ostream operator(ostream os, const string str);
}
在string.cpp中定义
#includestring.h
namespace HD
{ostream operator(ostream os, const string str){for (size_t i 0; i str.size(); i){os str.c_str()[i];}return os;}
}
在test.cpp中试试
#includestring.h
namespace HD
{void test19(){string S1;string S2(hello world);const string S3(I love Totoro);cout S1 endl S2 endl S3 endl;}
}
int main()
{HD::test19();return 0;
}
运行结果 3.3.operator 流提取运算符重载的实现好像也不难俺们模拟实现采用声明和定义分离的写法
在string.h中声明
namespace HD
{istream operator(istream is, string str);
}
在strng.cpp中定义
#includestring.h
namespace HD
{istream operator(istream is, string str){str.clear();char ch;is ch;while (ch || ch \n)//确保第一次拿到的ch不是空格或者换行{is ch;}while (ch ! ch ! \n){str ch;is ch;}return is;}
}
这样子写其实是有问题的俺们可以先在test.cpp中试试
#includestring.h
namespace HD
{void test20(){string S1;cin S1;cout S1;}
}
int main()
{HD::test20();return 0;
}
运行结果如下会陷入死循环 俺们看模拟实现函数体内的第2个while循环导致运行结果陷入死循环的就是第2个while循环。原因就是上一篇博客介绍过的使用C的标准输入对象键盘cin和C语言的函数scanf获取字符串的时候获取不到空格 或换行\n它们均把空格 和换行\n作为默认分隔符号当它们在缓冲区提取到这两个默认分隔符号之一时就停止在缓冲区拿数据了。也就是说第2个while循环的条件语句永远是真的那不就陷入死循环了吗。 解决的办法很多其中一种办法就是调用istream类的成员函数接口get来获取字符
get这个接口就是单单取一个字符没有声明分隔符的概念。
在string.cpp种定义
#includestring.h
namespace HD
{istream operator(istream is, string str){str.clear();char ch;is.get(ch);while (ch || ch \n)//确保第一次拿到的ch不是空格或者换行{is.get(ch);}while (ch ! ch ! \n){str ch;is.get(ch);}return is;}
} 在test.cpp中试试
#includestring.h
namespace HD
{void test20(){string S1;cin S1;cout S1;}
}
int main()
{HD::test20();return 0;
}
运行结果如下没问题 3.4.getline 这个接口string中重载了2个函数。俺也模拟实现2个函数实现方法跟operator大同小异如下
俺们模拟实现采用声明和定义分离的写法
在string.h中声明
namespace HD
{istream getline(istream is, string str);istream getline(istream is, string str, char delim);
}在string.cpp中定义
#includestring.h
namespace HD
{istream getline(istream is, string str){str.clear();char ch;is.get(ch);while (ch ! \n){str ch;is.get(ch);}return is;}istream getline(istream is, string str, char delim){str.clear();char ch;is.get(ch);while (ch ! delim){str ch;is.get(ch);}return is;}
}
在test.cpp中试试
#includestring.h
namespace HD
{void test21(){string S1;getline(cin, S1);cout S1 endlendl;string S2(hello world);getline(cin, S2, );cout S2 endl;}
}
int main()
{HD::test21();return 0;
}
运行结果 4.个别接口的不同写法 4.1.constructor的不同写法 俺一共模拟实现了3个构造函数分别是string()、string(const char* s)、string(const string str)。 对于string()和string(const char* s)俺们可以用一个带缺省参数的构造函数代替 string(const char* s )//以上两构造合二为一{_size strlen(s);_capacity _size;_str new char[_capacity 1];strcpy(_str, s);}
1.string的构造函数接口内并没有重载带缺省参数的构造函数俺实现这个函数可以用来替代以上两个函数罢了
2.当调用这个函数时若我们给了s那么就相当于调用string(const char* s)若我们没有给s那么就相当于调用string()。
3.这里缺省值给空字符串字符串默认结尾有\0的。若是缺省值给了\0其实这个字符串包含2个\0概念要分清捏。 对于string(const string str)俺们还可以这样写 string(const string str)//拷贝构造函数{string tmp(str.c_str());swap(tmp);} 俺们希望用C字符串str来构造一个新对象。俺们可以调用构造函数用C字符串str.c_tr()来构造一个新对象tmp这个新对象tmp不就是我们想要的吗调用swap将tmp和*this的内容交换一下不就好了。
俺们知道string类底层是字符顺序表所以俺定义3个private成员变量_str、_capacity、_size来实现并且给这3个成员变量缺省值这3个缺省值的意义就可以在这里体现
当调用swap将tmp和*this的内容交换后tmp得到的就是*this的旧内容且出了函数作用域就会销毁tmp。若是没有给缺省值当调用构造函数走初始化列表的时_str就得到一个随机值也就是指向某块随机空间当调用swap后并销毁tmp时这块随机空间不就随着tmp的销毁而被delete掉了这是不允许的若是给缺省值_str就得到nullptr当tmp被销毁时是没问题的。
4.2.operator的不同写法 operator还可以这样子写跟构造函数的不同写法的思想是一样的 string string::operator(const string str){string tmp(str);swap(tmp);return *this;} operator也可以如以下代码只不过string中并没有重载以下operator但是下面这个operator也能实现2个string类对象赋值的
俺的模拟实现采取本成员函数声明和定义分离的写法
string中声明
string operator(const string str); 在string.cpp中定义
#includestring.h
namespace HD
{string string::operator(string str){swap(str);return *this;}
}
思路调用swap时要先传参由于C规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造那么理论上operator的形参str拷贝构造一个临时对象这个临时对象再拷贝构造swap的参数str哪怕编译器优化掉了临时对象swap的形参str也是通过ooperator形参str拷贝构造的一个新对象将这个新对象str与*this的内容交换一下不就搞定了。 这个operator的模拟实现为什么参数部分设计成传值而不是传引用呢
若是传引用那么调用swap时就不存在拷贝行为就不会调用拷贝构造函数就没有新对象的产生。在将operator的形参str与*this交换内容不就出大问题了吗。
以上2种写法若是自己给自己赋值也不会出现问题仔细想想就能明白
4.3.operator的不同写法 istream operator(istream is, string str){str.clear();char ch;const size_t n 256;char p[n];is.get(ch);size_t i 0;while (ch || ch \n){is.get(ch);}while (ch ! ch ! \n){p[i] ch;is.get(ch);if (i n-1){p[i] \0;str p;i 0;}}if (i 0){p[i] \0;str p;}return is;}
这种写法与原本那种写法最大的不同就是原本的写法是每取一个字符就插入str容易造成频繁扩容这种写法是每得到一串字符串再插入str扩容次数就少了一种优化吧
5. string模拟实现完整代码 关于这个string模拟实现完整代码如下看完整代码也方便理解有兴趣可以瞅瞅。本工程包括3个文件分别是string.h、string.cpp、test.cpp。
5.1.string.h 存放string类的定义和非成员函数的声明。
#pragma once
#includeassert.h
#includeiostream
#includestring
#includestring.h
using namespace std;
namespace HD
{class string{char* _str nullptr;size_t _capacity 0;size_t _size 0;public:typedef char* iterator;typedef const char* const_iterator;static const size_t npos;//string()// :_str(new char[1]{\0})// ,_capacity(0)// ,_size(0)//{//}//string(const char* s)//{// _size strlen(s);// _capacity _size;// _str new char[_capacity1];// strcpy(_str, s);//}string(const char* s )//以上两构造合二为一{_size strlen(s);_capacity _size;_str new char[_capacity 1];strcpy(_str, s);}~string(){delete[] _str;_str nullptr;_size _capacity 0;}const char* c_str()const{return _str;}size_t size()const{return _size;}char operator[](size_t pos){assert(pos _size);return _str[pos];}const char operator[](size_t pos)const{assert(pos _size);return _str[pos];}iterator begin(){return _str;}iterator end(){return _str _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str _size;}size_t capacity()const{return _capacity;}string(const string str)//拷贝构造函数{/写法一//*_str new char[str._capacity 1];strcpy(_str, str._str);_size str._size;_capacity str._capacity;*//写法二/string tmp(str.c_str());swap(tmp);}void reserve(size_t n 0);void push_back(char c);string append(const char* s);string operator (const char* s);string operator(char c);string insert(size_t pos, size_t n, char c);string insert(size_t pos, const char* s);string erase(size_t pos 0, size_t len npos);size_t find(char c, size_t pos 0) const;size_t find(const char* s, size_t pos 0) const;string substr(size_t pos 0, size_t len npos) const;string operator(const string str);//string operator(const string str);void clear();void swap(string str);};bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator(const string lhs, const string rhs);bool operator!(const string lhs, const string rhs);ostream operator(ostream os, const string str);istream operator(istream is, string str);istream getline(istream is, string str);istream getline(istream is, string str, char delim);
}
5.2.string.cpp 存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义。
#includestring.h
namespace HD
{const size_t string::npos -1;void string::reserve(size_t n){if (n _capacity){char* p new char[n 1];strcpy(p, _str);delete[] _str;_str p;_capacity n;}}void string:: push_back(char c){if (_capacity _size)//扩容{reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] c;_str[_size] \0;}string string::append(const char* s){if (_capacity strlen(s) _size)//扩容{size_t n strlen(s);reserve(n _size 2 * _capacity ? n _size : 2 * _capacity);}/写法一///for (int i 0; i strlen(s); i)//{// _str[_size] s[i];//}//_str[_size] \0;/写法二/strcpy(_str _size, s);_size strlen(s);return *this;}string string::operator (const char* s){append(s);return *this;}string string::operator(char c){push_back(c);return *this;}string string::insert(size_t pos, size_t n, char c){assert(pos _size);if (_capacity n _size)//扩容{reserve(n _size 2 * _capacity ? n _size : 2 * _capacity);}size_t end _size n;while (end posn-1){_str[end] _str[end - n];end--;}for (size_t i 0; i n; i){_str[pos i] c;}_sizen;return *this;}string string::insert(size_t pos, const char* s){assert(pos _size);size_t n strlen(s);if (_capacity n _size){reserve(n _size 2 * _capacity ? n _size : 2 * _capacity);}size_t end _size n;while (end pos n - 1){_str[end] _str[end - n];end--;}strncpy(_str pos, s, n);_size n;return *this;}string string::erase(size_t pos, size_t len){assert(pos _size);if (_size - pos len){_str[pos] \0;_size - (_size - pos);}size_t begin pos len;while (begin _size 1){_str[begin - len] _str[begin];begin;}_size - len;return *this;}size_t string::find(char c, size_t pos ) const{for (size_t i pos; i _size; i){if (_str[i] c){return i;}}return npos;}size_t string::find(const char* s, size_t pos) const{if (const char* p strstr(_str pos, s)){return p - _str;}return npos;}string string::substr(size_t pos, size_t len) const{assert(pos _size);if (len _size - pos){len _size - pos;}string S1;S1.reserve(len);for (int i 0; i len; i){S1 _str[pos i];}return S1;}string string::operator(const string str){/写法一//*if (thisstr)//防止自己给自己赋值{return *this;}delete[]_str;_str new char[str._capacity 1];strcpy(_str, str._str);_size str.size();_capacity str.capacity();return *this;*//写法二/string tmp(str);swap(tmp);return *this;}/*string string::operator(string str){swap(str);return *this;}*/void string::clear(){_str[0] \0;_size 0;}void string::swap(string str){std::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}bool operator(const string lhs, const string rhs){return strcmp(lhs.c_str(), rhs.c_str()) 0;}bool operator(const string lhs, const string rhs){return strcmp(lhs.c_str(), rhs.c_str()) 0;}bool operator(const string lhs, const string rhs){return !(lhs rhs);}bool operator(const string lhs, const string rhs){return lhs rhs lhs rhs;}bool operator(const string lhs, const string rhs){return !(lhs rhs);}bool operator!(const string lhs, const string rhs){return !(lhs rhs);}ostream operator(ostream os, const string str){for (size_t i 0; i str.size(); i){os str.c_str()[i];}return os;}istream operator(istream is, string str){/写法一//*str.clear();char ch;is.get(ch);while (ch || ch \n){is.get(ch);}while (ch ! ch ! \n){str ch;is.get(ch);}return is;*//写法二/str.clear();char ch;const size_t n 256;char p[n];is.get(ch);size_t i 0;while (ch || ch \n){is.get(ch);}while (ch ! ch ! \n){p[i] ch;is.get(ch);if (i n-1){p[i] \0;str p;i 0;}}if (i 0){p[i] \0;str p;}return is;}istream getline(istream is, string str){str.clear();char ch;is.get(ch);while (ch ! \n){str ch;is.get(ch);}return is;}istream getline(istream is, string str, char delim){str.clear();char ch;is.get(ch);while (ch ! delim){str ch;is.get(ch);}return is;}
}5.3.test.cpp 存放测试代码来测试咱们模拟实现的string的功能。
#includestring.h
namespace HD
{void test1(){string S1;string S2(hello world);cout S1.c_str() endl S2.c_str() endl;}void test2(){string S1;string S2(hello world);cout S1.size() endl S2.size() endl;}void test3(){string S1(nbo);for (int i 0; i S1.size(); i){S1[i] - 1;cout S1[i];}cout endl;const string S2(hello world);for (int i 0; i S2.size(); i){cout S2[i];}}void test4(){string S1(hello world);for (auto ch : S1){cout ch;}cout endl;const string S2(God is a girl);string::const_iterator cit S2.begin();while (cit ! S2.end()){cout *cit;cit;}cout endl;}void test5(){string S1;string S2(hello world);cout S1.capacity() endl S2.capacity() endl;}void test6(){string S1;string S2(hello world);cout S1.capacity() S2.capacity() endl;S1.reserve(10); S2.reserve(1);cout S1.capacity() S2.capacity() endl;}void test7(){string S1;string S2(hello world);S1.push_back(!);S2.push_back(!);cout S1.capacity() S2.capacity() endl;cout S1.c_str() S2.c_str() endl;}void test8(){string S1;string S2(hello);S1.append(young man);S2.append( Totoro!);cout S1.capacity() S2.capacity() endl;cout S1.c_str() S2.c_str() endl;}void test9(){string S1;string S2(hello);S1 !; S2 Totoro;cout S1.c_str() S2.c_str() endl;}void test10(){string S1(A man);string S2(hello);S1.insert(2, young);S2.insert(S2.size(), 5, A);cout S1.c_str() S2.c_str() endl;}void test11(){string S1(A man);string S2(hello fat Totoro);S1.erase();S2.erase(5, 4);cout S1.c_str() endl S2.c_str() endl;}void test12(){string S1(A man);string S2(hello fat Totoro);cout S1.find(m) endl;cout S2.find(fat) endl;}void test13(){string S1(hello fat Totoro);string S2 S1.substr(6, 3);cout S2.c_str() endl;}void test14(){string S1(hello fat Totoro);cout S1.capacity() endl;S1.clear();cout S1.c_str() S1.capacity() endl;}void test15(){string S1(hello world);string S2(I do love you,yes I do love you);cout 交换前S1 S1 S1.size() S1.capacity() endl;cout 交换前S2 S2 S2.size() S2.capacity() endl;cout endl;S1.swap(S2);cout 交换后S1 S1 S1.size() S1.capacity() endl;cout 交换后S2 S2 S2.size() S2.capacity() endl;}void test16(){string S1(hello world);string S2(I do love you,yes I do love you);cout (S1 S1) endl;cout (S1 ! S1) endl;cout (S1 S2) endl;cout (S1 S2) endl;cout (S1 S1) endl;cout (S1 S2) endl;}void test17(){const char* S1 hello world;string S2(I do love you,yes I do love you);cout (S1 S1) endl;cout (S1 ! S1) endl;cout (S1 S2) endl;cout (S1 S2) endl;cout (S1 S1) endl;cout (S1 S2) endl;}void test18(){const char* S1 hello world;const char* S2 I do love you,yes I do love you;cout (S1 S1) endl;cout (S1 ! S1) endl;cout (S1 S2) endl;cout (S1 S2) endl;cout (S1 S1) endl;cout (S1 S2) endl;}void test19(){string S1;string S2(hello world);const string S3(I love Totoro);cout S1 endl S2 endl S3 endl;}void test20(){string S1;cin S1;cout S1;}void test21(){string S1;getline(cin, S1);cout S1 endl endl;string S2(hello world);getline(cin, S2, );cout S2 endl;}
}
int main()
{HD::test21();return 0;
}
感谢阅读 文章转载自: http://www.morning.yrcxg.cn.gov.cn.yrcxg.cn http://www.morning.bzlfw.cn.gov.cn.bzlfw.cn http://www.morning.dwtdn.cn.gov.cn.dwtdn.cn http://www.morning.pwmpn.cn.gov.cn.pwmpn.cn http://www.morning.dyhlm.cn.gov.cn.dyhlm.cn http://www.morning.xppj.cn.gov.cn.xppj.cn http://www.morning.rgxll.cn.gov.cn.rgxll.cn http://www.morning.bwrbm.cn.gov.cn.bwrbm.cn http://www.morning.lmhwm.cn.gov.cn.lmhwm.cn http://www.morning.hsrch.cn.gov.cn.hsrch.cn http://www.morning.czrcf.cn.gov.cn.czrcf.cn http://www.morning.ydgzj.cn.gov.cn.ydgzj.cn http://www.morning.hnkkm.cn.gov.cn.hnkkm.cn http://www.morning.mgkb.cn.gov.cn.mgkb.cn http://www.morning.qtkfp.cn.gov.cn.qtkfp.cn http://www.morning.kjnfs.cn.gov.cn.kjnfs.cn http://www.morning.jglqn.cn.gov.cn.jglqn.cn http://www.morning.mjzgg.cn.gov.cn.mjzgg.cn http://www.morning.llcgz.cn.gov.cn.llcgz.cn http://www.morning.grjh.cn.gov.cn.grjh.cn http://www.morning.skbbt.cn.gov.cn.skbbt.cn http://www.morning.yydeq.cn.gov.cn.yydeq.cn http://www.morning.lsqxh.cn.gov.cn.lsqxh.cn http://www.morning.bflws.cn.gov.cn.bflws.cn http://www.morning.kspfq.cn.gov.cn.kspfq.cn http://www.morning.simpliq.cn.gov.cn.simpliq.cn http://www.morning.zyytn.cn.gov.cn.zyytn.cn http://www.morning.yxbrn.cn.gov.cn.yxbrn.cn http://www.morning.3ox8hs.cn.gov.cn.3ox8hs.cn http://www.morning.nkkr.cn.gov.cn.nkkr.cn http://www.morning.hqbnx.cn.gov.cn.hqbnx.cn http://www.morning.qgcfb.cn.gov.cn.qgcfb.cn http://www.morning.smsjx.cn.gov.cn.smsjx.cn http://www.morning.qklff.cn.gov.cn.qklff.cn http://www.morning.fyxtn.cn.gov.cn.fyxtn.cn http://www.morning.rrpsw.cn.gov.cn.rrpsw.cn http://www.morning.rttkl.cn.gov.cn.rttkl.cn http://www.morning.qstjr.cn.gov.cn.qstjr.cn http://www.morning.mtcnl.cn.gov.cn.mtcnl.cn http://www.morning.dplmq.cn.gov.cn.dplmq.cn http://www.morning.zczkm.cn.gov.cn.zczkm.cn http://www.morning.thwcg.cn.gov.cn.thwcg.cn http://www.morning.mstbbs.com.gov.cn.mstbbs.com http://www.morning.hxpff.cn.gov.cn.hxpff.cn http://www.morning.dmwck.cn.gov.cn.dmwck.cn http://www.morning.kzhxy.cn.gov.cn.kzhxy.cn http://www.morning.bhmnp.cn.gov.cn.bhmnp.cn http://www.morning.smcfk.cn.gov.cn.smcfk.cn http://www.morning.grjh.cn.gov.cn.grjh.cn http://www.morning.hlnrj.cn.gov.cn.hlnrj.cn http://www.morning.xhwty.cn.gov.cn.xhwty.cn http://www.morning.tgyqq.cn.gov.cn.tgyqq.cn http://www.morning.pnmgr.cn.gov.cn.pnmgr.cn http://www.morning.lxfyn.cn.gov.cn.lxfyn.cn http://www.morning.bwqcx.cn.gov.cn.bwqcx.cn http://www.morning.rdbj.cn.gov.cn.rdbj.cn http://www.morning.ydyjf.cn.gov.cn.ydyjf.cn http://www.morning.sbwr.cn.gov.cn.sbwr.cn http://www.morning.ypjjh.cn.gov.cn.ypjjh.cn http://www.morning.hrjrt.cn.gov.cn.hrjrt.cn http://www.morning.sblgt.cn.gov.cn.sblgt.cn http://www.morning.nrfrd.cn.gov.cn.nrfrd.cn http://www.morning.zxybw.cn.gov.cn.zxybw.cn http://www.morning.qjmnl.cn.gov.cn.qjmnl.cn http://www.morning.dkzwx.cn.gov.cn.dkzwx.cn http://www.morning.tmbfz.cn.gov.cn.tmbfz.cn http://www.morning.jqrhz.cn.gov.cn.jqrhz.cn http://www.morning.wxgd.cn.gov.cn.wxgd.cn http://www.morning.mhlsx.cn.gov.cn.mhlsx.cn http://www.morning.gqbks.cn.gov.cn.gqbks.cn http://www.morning.ylqb8.cn.gov.cn.ylqb8.cn http://www.morning.ghgck.cn.gov.cn.ghgck.cn http://www.morning.pwzzk.cn.gov.cn.pwzzk.cn http://www.morning.ldpjm.cn.gov.cn.ldpjm.cn http://www.morning.mwzt.cn.gov.cn.mwzt.cn http://www.morning.nkbfc.cn.gov.cn.nkbfc.cn http://www.morning.zrdqz.cn.gov.cn.zrdqz.cn http://www.morning.slfmp.cn.gov.cn.slfmp.cn http://www.morning.dnydy.cn.gov.cn.dnydy.cn http://www.morning.tsqrc.cn.gov.cn.tsqrc.cn