当前位置: 首页 > news >正文

手机上怎么做网站电商app制作开发

手机上怎么做网站,电商app制作开发,优秀电商设计网站,全球网站排名查询文章目录 前言一、STL库介绍二、标准库中的string类1、string类介绍2、string类使用3.1 string类的构造函数3.2 string类对象的容量操作3.3 string类对象的遍历操作3.4 string类对象的访问操作3.5 string类对象的修改操作3.6 string类对象的字符串操作 三、模拟实现string类四、… 文章目录 前言一、STL库介绍二、标准库中的string类1、string类介绍2、string类使用3.1 string类的构造函数3.2 string类对象的容量操作3.3 string类对象的遍历操作3.4 string类对象的访问操作3.5 string类对象的修改操作3.6 string类对象的字符串操作 三、模拟实现string类四、windows下的VS中的string类和Linxu下的g中的string类。 前言 一、STL库介绍 STL(standard template libaray-标准模板库)是C标准库的重要组成部分不仅是一个可复用的组件库而且 是一个包罗数据结构与算法的软件框架。 STL的版本 (1). 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本本着开源精神他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本–所有STL实现版本的始祖。 (2). P. J. 版本 由P. J. Plauger开发继承自HP版本被Windows Visual C采用不能公开或修改缺陷可读性比较低符号命名比较怪异。 (3). RW版本 由Rouge Wage公司开发继承自HP版本被C Builder 采用不能公开或修改可读性一般。 (4). SGI版本 由Silicon Graphics Computer SystemsInc公司开发继承自HP版 本。被GCC(Linux)采用可移植性好可公开、修改甚至贩卖从命名风格和编程 风格上看阅读性非常高。 STL的六大组件 二、标准库中的string类 1、string类介绍 当我们查看c文档中关于string类的定义时可以发现string类是依靠basic_string类模板显示实例化出来的一个类。 我们可以查看basic_string这个类模板是这样定义的。并且根据basic_string这个类模板生成了4个模板实例。string类只是其中一个。 那么为什么要根据该模板生成4个模板实例呢 这是因为在计算机刚发明时因为计算机只能存储1010的二进制位数据而美国人使用的字母和符号是无法直接存储到计算机内的。所以美国人就想到了一套ASCII码方式。 在计算机中所有的数据在存储和运算时都要使用二进制数表示因为计算机用高电平和低电平分别表示1和0例如像a、b、c、d这样的52个字母包括大写以及0、1等数字还有一些常用的符号例如*、#、等在计算机中存储时也要使用二进制数来表示而具体用哪些二进制数字表示哪个符号当然每个人都可以约定自己的一套这就叫编码而大家如果要想互相通信而不造成混乱那么大家就必须使用相同的编码规则于是美国有关的标准化组织就出台了ASCII编码统一规定了上述常用符号用哪些二进制数来表示。 这样使用0-127就将全部字符都表示出来了即还不到一个字节所以ASCII码中每个字符占一个字节。 但是随着计算机在全世界范围被使用计算机中只显示这127个欧美字符是不行的还需要显示其它国家的语言。那么像我们中国使用的是象形文字ASCII编码那一套对我们就不适用了。所以又出现了一种unicode码的编码方式。 统一码Unicode也叫万国码、单一码由统一码联盟开发是计算机科学领域里的一项业界标准包括字符集、编码方案等。 统一码是为了解决传统的字符编码方案的局限而产生的它为每种语言中的每个字符设定了统一并且唯一的二进制编码以满足跨语言、跨平台进行文本转换、处理的要求。 如果把各种文字编码形容为各地的方言那么统一码就是世界各国合作开发的一种语言。在这种语言环境下不会再有语言的编码冲突在同屏下可以显示任何语言的内容这就是统一码的最大好处。就是将世界上所有的文字用2个字节统一进行编码。那样像这样统一编码2个字节就已经足够容纳世界上所有语言的大部分文字了。 在统一码中汉字“字”对应的数字是23383。在统一码中我们有很多方式将数字23383表示成程序中的数据包括UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写可以翻译成统一码字符集转换格式即怎样将统一码定义的数字转换成程序数据。所以在c中为了应对不同的编码格式才根据basic_string模板实例化了4中类。 在string类中一个字符占1个字节在wstring类中一个字符占2个字节在u16string类中一个字符占2个字节在u32string类中一个字符占4个字节。 2、string类使用 string类有对应的构造函数和析构函数还有赋值运算符的重载函数。 3.1 string类的构造函数 int main() {//会调用string类的默认构造函数string()string s1;//会调用string类的string(const char* s)构造函数因为参数不为string对象的引用而是字符串地址。string s2(hello world);//下面的对于s3的初始化应该先将hello string字符串隐式转换为临时string对象然后调用构造函数将该临时对象初始化//然后再调用拷贝构造将临时对象拷贝给s3对象。但是编译器会直接优化为调用构造函数。// 构造 拷贝构造 - 优化为构造//调用的也是string(const char* s)构造函数string s3 hello string;//会调用拷贝构造函数string (const string str)string s4(s3);//此时会调用string (const string str, size_t pos, size_t len npos)这个构造函数因为第一个参数为string对象的引用//意思为从s3字符的下标为6的位置开始拷贝拷贝的长度为6。string s5(s3, 6, 6);cout s5 endl;//当拷贝的长度大于s3的长度时此时会只拷贝到s3末尾就停止了。//该构造函数的第三个参数缺省值npos为-1而-1的补码按无符号整数解析就是int表示的最大的无符号整数//所以如果第三个参数不给的话就是默认将s3字符串拷贝到结尾再停止。string s6(s3, 6, 12);cout s6 endl;//此时会调用string (const char* s,size_t n)这个构造函数因为第一个参数为字符串地址。//该函数表示的意思是将hello world字符串的前5个字符拷贝到s7对象中。string s7(hello world, 5);cout s7 endl;//当第二个参数n大于字符串长度时会拷贝到\0就停止了string s8(hello worlda, 20);cout s8 endl;//此时会调用string (size_t n,char c)这个构造函数。//会将s9中初始化为10个*字符string s9(10, *);cout s9 endl;return 0; } 3.2 string类对象的容量操作 注意 (1). size()与length()方法底层实现原理完全相同引入size()的原因是为了与其他容器的接口保持一致一般情况下基本都是用size()。 (2). clear()只是将string中有效字符清空不改变底层空间大小。 int main() {string s1(hello world);string s2();//返回字符串有效字符长度cout s1.size() endl;//返回字符串有效字符长度cout s1.length() endl;//返回容器可以容纳的最大元素数。cout s1.max_size() endl;//返回空间总大小cout s1.capacity() endl;//检测字符串是否为空串cout s1.empty() endl;//清空有效字符s1.clear();cout s1.empty() endl;//缩容将s1对象的capacity缩到和size一样大//但是缩容的开销很大一般不会使用s1.shrink_to_fit();cout s1.capacity() endl;return 0; }resize和reserve区别 当使用string存储字符串时如果字符串长度太大时string会自动进行扩容。如果字符串过大但是string自动扩容每次扩的都很少时此时想要直接申请够存储字符串的空间就可以使用reserve和resize。reserve和resize这两个成员函数可以预先开辟指定大小的空间。当需要知道开辟多少空间时可以使用reserve提前将空间开好减少扩容提高效率。因为要考虑对齐等因素开的空间会比实际要求的大但是绝对不会比实际要求的少。 注意 (1). resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个不同的是当字符个数增多时resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意resize在改变元素个数时如果是将元素个数增多可能会改变底层容量的大小如果是将元素个数减少底层空间总大小不变。 (2). reserve(size_t res_arg0)为string预留空间不改变有效元素个数当reserve的参数小于string的底层空间总大小时reserver不会改变容量大小。即reserve只会改capacity不会改size。而resize会将capacity和size都改变。 int main() {//扩容string s1(hello world);s1.reserve(100);cout s1.size() endl;cout s1.capacity() endl;//扩容初始化//如果resize不给第二个参数则会初始化为\0string s2(hello world);s2.resize(100, x);cout s2.size() endl;cout s2.capacity() endl;string s3(hello world);s3.resize(100);return 0; } 可以看到使用resize会将预先开辟出来的空间初始化。而reserve只是将空间开辟出来并不会进行初始化。所以reserve只会改capacity不会改size。而resize会将capacity和size都改变。如果不给第二个参数会初始化为’\0’如果给第二个参数则会将后面的值初始化为给定的字符。 resize还可以删除数据当传入的第一个参数比size小时就会删除数据比capacity大时就会扩容。 int main() {//扩容初始化//当resize第一个参数比capacity大时就会扩容string s1(hello world);s1.resize(16,x);cout s1.size() endl;cout s1.capacity() endl;//当resize第一个参数比size小时就会删除数据。string s2(hello world);s2.resize(5);cout s2.size() endl;cout s2.capacity() endl;cout s2 endl; //helloreturn 0; }3.3 string类对象的遍历操作 string类的迭代器。 当我们想要遍历string类中存的字符串的每一个字符时可以使用三种方法来遍历。 循环遍历 int main() {string s1(hello world);for (int i 0; i s1.size(); i){cout s1[i] ;}cout endl;return 0; }范围for 其实使用范围for范围for底层还是使用迭代器范围for只是对迭代器又封装了一层。 int main() {string s1(hello world);for (auto ch : s1){cout ch ;}cout endl;return 0; }迭代器 可以先将迭代器·想象为指针begin就是返回字符串的起始地址而end就是返回结束位置的下一个字符的指针。 int main() {string s1(hello world);string::iterator it s1.begin();while (it ! s1.end()){cout *it ;it;}cout endl;return 0; }迭代器除了有正向迭代器外还有被const修饰的正向迭代器还有反向迭代器和被const修饰的反向迭代器。当想要遍历被const修饰的对象时此时就需要使用const_iterator迭代器。并且const_iterator迭代器只能访问对象内容并不能改变对象的内容。 其中begin()和end都有两种一种是不被const修饰的一种是被const修饰的。当创建的为iterator迭代器时会自动去调用不被const修饰的begin()当创建的为const_iterator迭代器时会自动去调用被const修饰的begin()。 void func(const string s) {//const 正向迭代器只能遍历和读数据不能写数据。string::const_iterator it s.begin();while (it ! s.end()){cout *it ;it;}cout endl;//const 反向迭代器只能遍历和读数据不能写数据。string::const_reverse_iterator rit s.rbegin();while (rit ! s.rend()){cout *rit ;rit;}cout endl; } int main() {string s1(hello world);//正向迭代器可以遍历数据也可以读写数据string::iterator it s1.begin();while (it ! s1.end()){cout *it ;it;}cout endl;//创建一个反向迭代器将从后向前遍历s1中的字符串。可以遍历数据也可以读写数据//string::reverse_iterator rit s1.rbegin();//此时可以使用auto来自动推断变量的类型。//auto是根据右边的返回值来推左边的类型的虽然可以简化代码但是代码的可读性差了auto rit s1.rbegin();while (rit ! s1.rend()){cout *rit ;rit;}cout endl;func(s1);return 0; }3.4 string类对象的访问操作 使用operator[]和a都可以访问指定位置的元素只不过当使用[]越界访问时会直接中止程序而使用at越界访问时会抛异常。 int main() {string s1(hello world);//使用 [] 越界访问会中止程序。//s1[100];//使用 at 越界访问会抛异常try{s1.at(100);}catch (const exception e){cout e.what() endl;}//返回字符串的第一个字符也可以修改这个字符cout s1.front() endl;s1.front() !;cout s1 endl;//返回字符串的最后一个字符就是\0前面的字符也可以修改这个字符cout s1.back() endl; //返回ds1.back() #;cout s1 endl;string s2(hello\0world);cout s2.back() endl; //返回oreturn 0; }3.5 string类对象的修改操作 字符串的尾插 int main() {string s1(hello);//push_back()为尾插一个字符。s1.push_back( );s1.push_back(!);cout s1 endl;//append是尾插字符串s1.append(world);cout s1 endl;//可以尾插一个字符也可以尾插字符串//的底层还是调用的push_bakc和append只是多了一层封装使用起来更加简单明了s1 ;s1 !;s1 world;cout s1 endl;return 0; }当一直向string类中插入字符时就要涉及到扩容的问题了。可以看到在VS下每次扩容为原来的1.5倍。 int main() {//观察扩容情况string s;size_t sz s.capacity();cout capacity changed: sz endl;for (int i 0; i 100; i){s.push_back(c);if (sz ! s.capacity()){sz s.capacity();cout capacity changed: sz endl;}}return 0; }可以看到第一次扩容为两倍扩容那么为什么呢 这是因为在string中当字符串长度小于16时其实并没有动态申请空间存放字符串而是将字符串存放到string类的_buf数组内当字符串长度大于16后才开始进行扩容然后动态申请空间将字符串存到动态申请的空间中。这样做的目的是防止了每有一个小的字符串就要动态申请空间然后申请的空间很小就会产生很多碎片空间而浪费空间。 可以看到当字符串长度小于16时就会存在_Buf数组中。 当我们使用sizeof查看s对象大小时可以看到大小为28这是因为除了两个int型变量和一个指针变量外还有_Buf数组的16个字节的大小。 string扩容在不同的编译器下是不同的比如windows下VS1.5倍扩容linux下g2倍扩容。并且g中没有buf数组。这是因为VS用的PJ版STLg中用了SGI版本STL。 insert的使用 int main() {string s1(hello world);string s2(world);//调用string insert(size_t pos, const string str)函数在s1对象的pos位置插入s2对象的全部字符串s1.insert(0, s2);cout s1 endl;//调用string insert(size_t pos, const string str, size_t subpos, size_t sublen);函数//在s1对象的pos位置插入s2对象的从subpos位置开始长度为sublen的字符串。//如果sublen的长度超过s2的长度则会将s2的字符串全部插入到s1中然后就停止。s1.insert(0, s2, 0, 4);cout s1 endl;//调用string insert(size_t pos, const char* s)函数在pos位置插入字符串ss1.insert(0, hello);cout s1 endl;//调用string insert(size_t pos, const char* s, size_t n)函数//在s1对象的pos位置插入s字符串的前n个字符如果n大于字符串s的长度则会将字符串s全部插入s1对象的字符串中然后就停止。s1.insert(0, hello, 3);cout s1 endl;//调用string insert(size_t pos, size_t n, char c)函数//在s1对象的pos位置插入n个c字符s1.insert(0, 2, c);cout s1 endl;//调用iterator insert(const_iterator p, size_t n, char c)函数//在s1对象的字符串的begin()向后移5个位置处插入n个字符c。s1.insert(s1.begin() 5, 3, c);cout s1 endl;//调用iterator insert(const_iterator p, char c)函数//在s1对象的字符串的begin()向后移5个位置处插入字符c。s1.insert(s1.begin() 5, y);cout s1 endl;return 0; }erase使用 insert和erase不推荐经常使用因为它们在字符串中间插入或删除数据都会挪动数据效率比较低开销还很大 int main() {string s1(hello world);//会调用string erase(size_t pos 0, size_t len npos)函数//将s1对象的字符串从pos位置开始向后删除len个字符s1.erase(5, 1);cout s1 endl;//会调用string erase(size_t pos 0, size_t len npos)函数//因为没有给第二个参数而npos-1,即代表无符号最大的数所以会将s1pos位置之后的字符全部删除s1.erase(5);cout s1 endl;//会调用iterator erase(const_iterator p)函数//将s1.begin()向后移5个位置然后将后面的一个字符删除s1.erase(s1.begin() 5);cout s1 endl;//会调用iterator erase(const_iterator first, const_iterator last)函数//将s1.begin()向后移5个位置然后到s1.end()之间的字符都删除s1.erase(s1.begin() 5, s1.end());cout s1 endl;return 0; }replace使用 int main() {string s1(hello world);string s2(abcdefgh);//调用string replace(size_t pos, size_t len, const string str)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s2对象中的字符串//如果len大于s1对象的字符串的长度则会将s1的全部字符串替换为s2的字符串。//s1.replace(0, 5, s2); //abcdefgh world//s1.replace(0, 50, s2); //abcdefghcout s1 endl;//调用string replace(const_iterator i1, cosnt_iterator i2, const string str)函数//将s1.begin()向后移5个位置然后到s1.end()的位置之间的字符串替换为s2对象的字符串//s1.replace(s1.begin() 5, s1.end(), s2);cout s1 endl;//调用string replace(size_t pos, size_t len, const string str, size_t subpos, size_t sublen)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s2对象中的字符串从subpos位置开始的sublen长度的字符//s1.replace(0, 5, s2, 0, 5);cout s1 endl;//调用string replace(size_t pos, size_t len, const char* s)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s字符串//如果len大于s1对象的字符串的长度则会将s1的全部字符串替换为s字符串。//s1.replace(0, 5, abcde);cout s1 endl;//调用string replace(const_iterator i1, cosnt_iterator i2, const char* s)函数//将s1.begin()向后移5个位置然后到s1.end()的位置之间的字符串替换为s字符串//s1.replace(s1.begin() 5, s1.end(), abcde);cout s1 endl;//调用string replace(size_t pos, size_t len, const char* s, size_t n)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s字符串的前n个字符//s1.replace(0, 5, abcde, 3);cout s1 endl;//调用string replace(const_iterator i1, cosnt_iterator i2, const char* s, size_t n)函数//将s1.begin()向后移5个位置然后到s1.end()的位置之间的字符串替换为s字符串的前n个字符//s1.replace(s1.begin() 5, s1.end(), abcde, 3);cout s1 endl;//调用string replace(size_t pos, size_t len, size_t n, char c)函数//将s1对象的字符串从pos位置向后len长度的字符替换为n个c字符//s1.replace(0, 5, 5, x);cout s1 endl;//调用string replace(const_iterator i1, cosnt_iterator i2, size_t n, char c)函数//将s1.begin()向后移5个位置然后到s1.end()的位置之间的字符串替换为n个c字符s1.replace(s1.begin() 5, s1.end(), 5, x);cout s1 endl;return 0; } assign使用 assign为调用的对象分配一个新的字符串来替代原来的内容。 int main() {string s1;string s2(The quick brown fox jumps over a lazy dog.);//调用string assign(const string str)函数//将s1对象的字符串替换为s2对象的字符串s1.assign(s2);cout s1 endl;//调用string assign(const string str, size_t subpos, size_t sublen)函数//将s1对象的字符串替换为s2对象从subpos位置开始向后sublen长度的字符串s1.assign(s2, 10, 20);cout s1 endl;//调用string assign(const char* s)函数//将s1对象的字符串替换为字符串ss1.assign(hello world);cout s1 endl;//调用string assign(const char* s, size_t n)函数//将s1对象的字符串替换为字符串s的前n个字符s1.assign(hello world, 5);cout s1 endl;//调用string assign(size_t n, char c)函数//将s1对象的字符串替换为n个c字符s1.assign(10, x);cout s1 endl;return 0; } swap使用 我们知道在std库中有一个swap的函数模板该模板实例的swap为浅拷贝即会将string类的内容都互换而string类里面的swap函数只是将两个string类类型对象的指向字符串的指针互换一下即完成了内容的互换所以string类里面的swap函数效率更高。 int main() {string s1(hello world);string s2(world hello);cout s1 endl;cout s2 endl;//将s1和s2的字符串内容互换s1.swap(s2);cout s1 endl;cout s2 endl;return 0; }3.6 string类对象的字符串操作 find使用 int main() {string s1(The quick brown fox jumps over a lazy dog.);string s2(quick);//调用size_t find(const string str, size_t pos 0) const noexcept;函数//从s1对象的字符串的起始位置开始找s2对象的字符串并返回该字符串在s1对象中第一次出现的位置。//如果找不到就返回npos.size_t pos s1.find(s2,0);cout pos endl;//调用size_t find(const char* s, size_t pos 0) const;函数//从s1对象的字符串的起始位置开始找s字符串并返回该字符串在s1对象中第一次出现的位置。//如果找不到就返回npos.size_t pos1 s1.find(quick, 0);cout pos1 endl;//调用size_t find(const char* s, size_t pos, size_type n) const;函数//从s1对象的字符串的起始位置开始找s字符串的前n个字符组成的字符串并返回该字符串在s1对象中第一次出现的位置。size_t pos2 s1.find(br, 0, 2);cout pos2 endl;//调用size_t find(char c, size_t pos 0) const noexcept;函数//从s1对象的字符串的起始位置开始找字符c并返回该字符在s1对象中第一次出现的位置。size_t pos3 s1.find(b, 0);cout pos3 endl;return 0; }find和replace练习 将字符串中的空格都替换为%20。 下面的代码可以完成替换但是使用replace将1个字符替换为3个字符时可能s1对象的容量不够会需要扩容此时会增加开销。 int main() {string s1(The quick brown fox jumps over a lazy dog.);//练习将s1字符串中的空格都换为%20size_t pos s1.find( );while (pos ! string::npos){s1.replace(pos, 1, %20);pos s1.find( , pos 3);}cout s1 endl;return 0; }所以可以先提前遍历s1对象的字符串中有多少空格然后提前将s1的空间开好避免replace时频繁扩容增加开销。 int main() {string s1(The quick brown fox jumps over a lazy dog.);size_t num 0;for (auto ch : s1){if (ch ){num;}}//提前开空间避免replace时频繁开空间s1.reserve(s1.size() 2 * num);//练习将s1字符串中的空格都换为%20size_t pos s1.find( );while (pos ! string::npos){s1.replace(pos, 1, %20);pos s1.find( , pos 3);}cout s1 endl;return 0; }虽然我们提前将s1的空间开好可以提高一些效率但是replace方法替换都会移动数据移动数据的开销更大。所以我们可以重新创建一个string类类型对象用追加的形式将转换好的字符都尾插到新对象中。然后将新对象赋值给s1。 这样以空间换时间可以使程序的效率提高一些。 int main() {string s1(The quick brown fox jumps over a lazy dog.);string newStr;size_t num 0;for (auto ch : s1){if (ch ){num;}}//提前将newStr的空间开好newStr.reserve(s1.size() 2 * num);for (auto ch : s1){if (ch ! ){newStr ch;}else{newStr %20;}}s1 newStr;cout newStr endl;return 0; }c_str()使用 c_str是返回一个以’\0’符结尾的字符串。直接打印s1遇到\0不会停止因为符号的重载函数是按s1.size打印的size有多大就打印多少个字符。但是c_str返回的是一个字符串的地址即一个const char类型的指针所以遇到\0会停止。即c_str返回的是c语言中的字符串。c_str返回c语言中的字符串就是为了兼容c语言的一些接口因为有一些c语言的函数需要传入以’\0\结尾的字符串即const char类型的指针。而不是传入c中的string类类型的对象。 int main() {string s1(hello world);cout s1 endl;cout s1.c_str() endl;cout (void*)s1.c_str() endl;s1 \0;s1 \0;s1 xxxxxxxx;//直接打印s1对象遇到\0不会停止打印cout s1 endl;//而s1.c_str()返回的是一个c语言中的字符串所以遇到\0就会结束cout s1.c_str() endl;return 0; }substr使用 substr为截取字符串的作用。 int main() {string s1(hello world);//substr函数为截取字符串即从pos位置截取len长度的字符串返回。//如果第二个参数没有则默认从pos位置将字符串全部截取然后返回。string tmp s1.substr(0,5);cout tmp endl;//练习截取文件名的后缀string file(string.cpp);size_t pos file.find(.);if (pos ! string::npos){string suffix file.substr(pos);cout suffix endl;}return 0; }rfind使用 int main() {string s1(hello world hello);string s2(hello);//调用size_t rfind(const string str, size_t pos npos)const noexcept;函数//从s1对象的字符串的pos位置开始向前查找对象s2的字符串找到了就返回字符串在s1中的位置//如果不传第二个参数就默认从s1字符串的末尾开始查找。size_t pos1 s1.rfind(s2, 0);cout pos1 endl;string file(string.cpp.tar.zip);//从后向前找字符.size_t pos file.rfind(.);if (pos ! string::npos){string suffix file.substr(pos);cout suffix endl;}return 0; }find配合substr查找字符串练习 查找url路径中的地址。 int main() {string url(https://legacy.cplusplus.com/reference/string/string/rfind/);cout url endl;size_t start url.find(://);if (start string::npos){cout invalid url endl;}start 3;size_t finish url.find(/, start);//打印://到第一个/之间的字符串string address url.substr(start, finish - start);cout address endl;return 0; }find_first_of使用 int main() {//将s1字符串中的abcd都换为*string s1(The quick brown fox jumps over a lazy dog.);//在s1对象的字符串中查找abcd字符串中的字符第一个出现的位置并且将该位置返回size_t found s1.find_first_of(abcd);while (found ! string::npos){s1[found] *;//继续向后找s1对象的字符串中的abcd字符found s1.find_first_of(abcd, found 1);}cout s1 endl;return 0; }find_last_of使用 find_first_of为从前向后查找而find_last_of就是从后向前查找。 int main() {//将s1字符串中的abcd都换为*string s1(The quick brown fox jumps over a lazy dog.);//在s1对象的字符串中查找abcd字符串中的字符最后一个出现的位置并且将该位置返回size_t found s1.find_last_of(abcd);while (found ! string::npos){s1[found] *;//继续向前找s1对象的字符串中的abcd字符found s1.find_last_of(abcd, found 1);}cout s1 endl;return 0; } find_first_not_of使用 find_first_of为查找s1中含有字符串中字符的位置返回而find_first_not_of是查找s1中不是这样字符串中字符的位置返回。 int main() {//将s1字符串中除了abcd的字符都换为*string s1(The quick brown fox jumps over a lazy dog.);//在s1对象的字符串中查找不是abcd字符串中的字符的第一个出现的位置并且将该位置返回size_t found s1.find_first_not_of(abcd);while (found ! string::npos){s1[found] *;//继续向后找s1对象的字符串中不是abcd的字符found s1.find_first_not_of(abcd, found 1);}cout s1 endl;return 0; } find_last_not_of使用 find_first_not_of是从前向后找而find_last_not_of为从后向前找。 三、模拟实现string类 当我们知道了string类的大致操作后我们也可以模拟实现一下string类。 当我们写到有参构造函数时会发现形参str在初始化列表赋值给_str时发生了错误。这是因为str为const char类型而_str为char类型将const char类型赋值给char发生了权限放大所以会出现报错。 那么上面这个问题该怎么解决呢 我们可以将_str也改为const char类型但是将_str改为const char类型后虽然上面的情况不报错了但是又遇到了新的问题。当我们对[]操作符进行重载时会看到发生了错误因为函数返回的是一个char类型的引用而_str[pos]是一个const char的类型的字符所以又发生了权限放大。 并且如果将_str设为const char类型以后就不可以修改_str字符串的值了这是肯定不可以的所以我们不能将_str设为const char类型。那么我们的有参构造函数就不能像上面那样在初始化列表中将成员变量全部初始化了我们需要像下面那样将_str在构造函数内初始化。先使用new申请空间然后使用strcpy函数进行拷贝这样就完成了_str的初始化。 然后我们测试时发现报出了异常这是因为当创建对象使用默认的无参构造函数时此时_str为nullptr空指针然后s1.c_str()返回的是一个nullptr当使用coutnullptr时因为cout有自动识别类型的特性而s1.c_str()返回的是一个const char类型的指针但是cout不会直接将这个指针打印出来它会打印出来这个指针指向的字符串即将这个指针解引用找到指针指向的字符串进行打印当遇到’\0’就停止。而此时s1.c_str()返回的是nullptr所以cout就会对nullptr进行解引用然后就会造成对空指针解引用。就会出现异常了。 所以在无参构造函数中不能将_str初始化为nullptr。我们可以在无参构造函数中将_str赋值为’\0’这样当打印时遇到’\0’就停止打印而第一个就是’\0’就会打印空字符串了。 然后我们将析构函数也写出来。 然后我们可以将有参构造函数和无参构造函数和为一个全缺省构造函数。此时需要注意的时缺省参数给的缺省值不能给str的值为nullptr空指针而要使str指向’\0’字符即str的值为’\0’字符所在的空间而不是str的值为0。 然后我们再来看拷贝构造函数。我们知道编译器默认生成的拷贝构造函数为浅拷贝即如果使用编译器自带的默认拷贝构造函数那么会出现两个string类类型对象指向同一片空间的情况例如下面的图片中的情况。并且在s2对象销毁时调用析构函数delete[] _str后在s3对象销毁时也会调用析构函数再次delete[] _str而此时的_str已经被s2对象的析构函数给delete[]过了所以对s2的_str进行了两次delete[]故出现了错误。并且使用浅拷贝当改变s2对象中_str的值时s3对象中_str的值也会改变。 所以我们需要自己重写深拷贝的拷贝构造函数。拷贝构造函数也有初始化列表所以我们在初始化列表中将_size和_capacity直接初始化为要拷贝对象的_size和_capacity。然后在拷贝构造函数内重新申请_capacity1大小的空间给新对象并且使用strcpy将s对象的_str内容拷贝给新对象的_str。此时可以看到s2对象的_str指向的空间和s3对象的_str指向的空间不是一片空间。但是s2对象和s3对象的内容都相同并且修改s2对象的内容时不会影响s3对象的内容。 接下来我们实现赋值运算符的重载函数。该函数就需要分如下三种情况。这样我们实现赋值运算符的重载函数时还需要进行判断。但是我们也可以不进行判断直接将s1对象的_capacity设置为和s2的_capacity一样然后将s1对象_str的空间释放重新为s1的_str开辟一个大小为_capacity的新空间然后将s2的_str的内容拷贝给s1的_str。 下面的代码可以实现s1s2即将s2赋值给s1但是当将自己赋值给自己时就会打印乱码。这是因为在赋值运算符重载函数中刚开始就将s1对象的_str的空间释放了然后这个空间的值就为随机值了然后strcpy(_str,s._str)将随机值拷贝过来给s1的_str所以s1的_str就为随机值了。 所以我们要先判断一下如果是s1s1的情况时就不需要做任何操作。并且我们提前释放s1对象的_str的空间也有安全隐患。例如如果我们申请空间不成功时此时s1对象的_str的数据就造成丢失了。所以我们应该先申请空间然后再释放s1的_str的空间。 然后我们再实现size()函数用来返回对象的字符串长度。 当我们实现了size()函数后我们实现一个Print函数用来遍历对象的字符串。然后我们会发现在测试时可以调用size()函数和使用[]操作符的重载函数但是在Print函数中使用size()函数和[]操作符的重载函数发生了错误。这是因为在Print中使用size()函数和[]操作符的重载函数发生了权限放大。因为在Print函数中s被const修饰所以当调用成员函数size()和[]操作符的重载函数时传入的this指针为const string类型而成员函数size()和[]操作符的重载函数的this为string*类型所以发生了权限放大。 此时就应该将size()函数和[]操作符的重载函数使用const修饰这样Print函数中就可以调用这两个函数了。此时虽然Print函数可以调用这两个函数了但是[]操作符的重载函数被const修饰了那么就不能使用[]操作符来改变string类类型对象中_str的值了例如s[1]就不可以了这肯定是不合理的。 所以此时我们就需要写两个[]操作符的重载函数一个[]操作符的重载函数被const修饰这个函数给被const修饰的对象调用。另一个[]操作符的重载函数不使用const修饰这个函数给那些需要改变对象的值的情况调用。因为编译器会去调用最匹配的[]操作符的重载函数所以这样的设计就合理了。 然后我们再使用指针来实现string类的迭代器。使用迭代器可以读数据也可以修改数据。 我们在前面说过范围for在底层就是使用迭代器来完成的其实范围for就类似于宏的替换当遇到范围for的语法就在底层替换为迭代器。所以支持了迭代器就支持了范围for。范围for会在底层傻瓜式的调用迭代器的begin、end等方法。所以只要实现了begin、end方法就可以使用范围for了。但是如果我们将begin改为Begin()此时再使用范围for就会出错也为范围for只能傻瓜式的调用begin和end等方法如果没有名字为这些的函数就会使用不了范围for。 当我们在测试时调用begin和end函数可以调用但是在Print函数中调用begin和end时就会发生错误这是因为此时又发生了权限放大在Print函数中对象s被const修饰而begin()和end()的隐藏形参为string this没有被const修饰所以调用begin()和end()时发生了权限放大。 所以此时我们就需要再写一个被const修饰的begin和end方法并且这两个方法返回的类型都为const char* 类型的指针因为当创建const_iterator的迭代器就说明不希望通过这个迭代器改变对象的值所以返回一个const char类型的指针就不能通过解引用这个指针来改变指针指向的值。但是这个指针的值是可以改变的因为还需要通过这个指针然后访问后面的元素。例如const char str不能str “abc”但是可以str。 此时Print函数中被const修饰的对象s可以调用被const修饰的begin和end函数并且此时只能通过迭代器来读取对象s的数据了并不能通过迭代器来改变对象s的值了。因为被const修饰的begin()函数返回的是const char类型的指针不能通过解引用来修改这个指针指向的内容。 简单的实现了迭代器后我们再来实现一下string类类型对象的比较即实现比较符的重载函数。 上面的这些比较运算符的重载函数当里面的this和const修饰的s对象位置交换一下时我们发现就会出错这是因为又发生了权限放大。s对象被const修饰当s对象调用比较运算符的重载函数时这个函数的隐藏参数为string * this 而s对象被const修饰即传入的是一个const string 类型的指针所以发生了权限放大。 此时我们就需要将上面这些比较运算符的重载函数都使用const修饰。这也使我们明白了那些string类的成员函数中内部不修改成员变量的值的成员函数都建议加上const修饰这样才能减少代码出错的概率。 像c_str()这样的函数使用const修饰后那些被const修饰的对象也可以调用这些函数了。 而那些有时候需要修改成员变量数据的成员函数有时候又需要被const修饰的对象调用的函数。就需要写两个版本一个不被const修饰的版本用于修改成员变量数据时调用。一个被const修饰的版本用于给被const修饰的对象调用。 然后我们再来实现尾插字符和尾插字符串的函数。但是当尾插字符和字符串时可能会遇到容量不够的情况此时就需要进行扩容。 所以我们要先实现reserve扩容函数然后再实现尾插字符和尾插字符串的函数。当我们像下面这样实现了reserve函数后我们测试时会发现这个reserve函数会进行缩容即当我们传入的值n比s1对象的字符串长度小时此时也会开辟一个n1大小的新空间然后将原来的数据拷贝过去此时因为使用的strcpy拷贝即strcpy拷贝是遇到’\0’才停止所以tmp中的数据还是原来的_str但是因为n小于_str字符串的长度开辟的空间为n1大小根本不够放原来的_str字符串的所以就会出现错了。 所以我们就需要在扩容时先判断一下如果n_capacity时就不进行缩容操作了只有当n_capacity时才进行增容操作。 然后我们再实现push_back函数此时我们测试时会发现给s1的字符串尾插一个字符后打印的字符串出现了乱码。这是因为在push_back()中我们将_str原来的’\0’字符变为了ch字符所以插入ch字符后_str字符串就没有了’\0’字符串结束符。 所以我们需要在尾插字符ch后重新将_str的最后一个字符后设置一个’\0’字符串结束符。 此时当我们测试时又会发现一个新的问题即当我们给一个_capacity为0的对象调用push_back()函数进行尾插时因为我们的扩容写的为_capacity2所以当_capacity为0时_capacity2还是0即我们没有给对象扩容成功。 此时我们有两种方法可以解决这个问题第一个办法是在构造函数中不让_capacity的起始为0即在构造函数初始化时就开辟少量的空间然后使_capacity不为0。第二个办法就是在push_back函数调用reserve()函数进行扩容时先判断_capacity的值如果_capacity为0就不给reserve函数传参为_capacity*2而传入一个具体的值。 第一种办法 第二种办法 然后我们再实现append()函数。因为append函数是尾插一个字符串所以开辟的空间为_sizelen大小。然后将字符串插入到原字符串的后面即_str_size的位置。因为_str为字符串首字符的位置而_str_size为字符串的最后一个字符的后面的位置即为’\0’的位置。而尾插str就是将str插入到这个位置。并且因为strcpy函数中会在最后将目标字符串的后面加上’\0’字符所以我们不需要关系乱码问题。 将strcpy换成strcat也可以实现但是使用strcat还需要自己去找\0的位置然后在\0的位置后追加str字符串但是本来就知道\0的位置为 _ str _ size所以使用strcat会多执行不必要的代码。增加开销。 实现了尾插字符和尾插字符串字符相当于上面这两个函数的结合所以接下来我们就实现运算符的重载函数。因为运算符的重载函数可能需要处理单个字符或一个字符串所以我们对进行了重载当一个字符时在底层调用push_back()函数当一个字符串时在底层调用append()函数。 我们知道c中的string类中reserve函数为扩容函数并且不会对扩容的空间进行初始化。而resize函数会对扩容的空间进行初始化如果调用resize函数时只传入一个参数则会将扩容的空间初始化为’\0’如果传入了第二个参数则会将扩容的空间初始化为第二个参数。所以我们可以尝试来实现一下resize函数。c中是对resize函数进行了重载写了两个版本一个是一个参数的版本还有一个是两个参数的版本。而我们可以尝试使用半缺省参数的方法将这两个函数和为一个函数。可以看到在测试代码中对象s1的最后面加上了’\0’字符并且s1和s2的_size和_capacity都被改变了。而且官方实现的resize中当n小于_str字符串的长度时会将字符串直接删除为长度为n并且也不会实现缩容因为缩容需要很大的代价缩容也是开辟新空间再将值拷贝到新的空间中也会有很大的开销。所以我们自己在实现时也不能缩容需要像实现reserve一样进行判断。 然后我们接着实现在某位置插入字符或字符串还有在某位置删除字符串。我们知道在c官方的string库中有一个size_t的变量npos这个变量的值为-1但是该变量为无符号整型即其实npos表示int型整数表示的最大值。我们自己实现string类时可以将npos定义为静态成员变量因为有很多成员函数都使用npos来作为缺省值。当我们定义静态成员变量npos时发现不可以给静态成员变量缺省值。这是因为普通成员变量给缺省值是在初始化列表中对成员变量进行初始化而静态成员变量并没有初始化列表进行初始化所以不可以给静态成员变量缺省值。 但是c语法中允许被const修饰的静态成员变量在定义时进行初始化并且这种语法只支持整型。double类型的静态成员变量使用const修饰时就不能在在定义时被初始化了。 可能这种语法是为了下面的情况时更方便。 但是我们自己在写代码时还是使用正常的写法将静态成员变量在类中定义在全局域中进行初始化。 然后我们来实现在某位置插入n个字符。我们想到的是当endpos时就将end位置的字符向后移n位这样移到最后就会在pos位置空出n个位置用来插入n个字符但是当pos为0时因为end为size_t类型为无符号整型所以当end-1时会按最大的int型整数来算end值所以此时end还是大于pos的就还会执行while后面的语句这样就会出现越界访问了所以会报异常。 此时我们可以将end_size1然后当pos为0时就不会再出现越界的现象了。 当实现了某位置插入字符后我们再写insert的重载函数来实现某位置插入字符串。其基本思路和上面插入n个字符类似只不过在拷贝数据时需要使用strncpy函数因为strcpy函数遇到’\0’就停止拷贝如果str“abc\0defg”则使用strcpy只会拷贝abc就会少拷贝字符。 当insert函数实现了其实前面写的push_back函数和append函数就可以复用insert函数来实现了。 下面我们再来实现erase函数在c库中的erase函数在删除字符串时也不会进行缩容所以我们实现erase时也不需要进行缩容处理。 接下来我们实现string库里面的swap函数我们知道在std库中有一个swap函数的模板而这个模板的调换两个对象是先创建一个临时对象然后再调用对象的赋值运算符等进行交换这个开销是很大的。而string类中的swap函数只是将三个成员变量交换一下即可所有string类中的swap函数效率更高。 然后我们再实现find函数进行字符或字符串的查找。下面实现的是查找字符如果找到了就返回该字符第一次出现的下标如果没有出现该字符就将npos返回。 然后我们再实现查找子串在内部使用strstr函数来查找子串strstr返回的是子串的地址而find函数返回的是子串第一次出现的下标所以我们可以使用子串的地址减_str的头地址就得到了子串的下标。 然后我们再来实现流插入符的重载函数和流提取符的重载函数。我们在实现日期类的流插入符重载函数和流提取符重载函数时使用了友元函数的概念那么我们实现这两个函数必须要实现成友元函数吗当然不需要因为对于对象的流插入我们不需要访问对象的_str成员变量我们可以使用迭代器或者范围for来遍历对象的字符串。 然后我们使用同样的思路来实现流提取符重载函数。但是下面的代码在测试时我们发现当输入为空格或换行符时并没有结束输入还是一直让我们输入。这是因为c或c中规定多个字符之间的间隔是空格或者换行所以使用inch时会将空格或者换行当作字符之间的间隔然后不会提取到缓冲区里面去这样就会一直没有结束符而一直循环输入。 此时我们可以使用get()函数来提取数据get()函数会将输入的所有字符都放入缓冲区中。此时就可以正常输入了。但是我们发现当测试时又出现了乱码这是因为在函数中为sch当结束时没有’\0’字符串结束符了。并且库里面的流提取符重载函数会清除对象中原来字符串的内容。 所以我们实现的流提取符重载函数也要先清除原来的字符串内容我们需要写一个clear成员函数在每次使用流提取符重载函数时先将对象的字符串清空。 这样每次输入时都会将之前的字符串先清空。我们使用来给对象s每次追加一个字符当我们输入的字符串比较长时就会频繁的进行扩容所以我们可以进行一下优化库里面是使用了一个buff数组每当buff数组满了之后就进行一次扩容。这样可以减少扩容的频率。 到此我们模拟实现string类就基本完成了库里面还有很多方法我们只模拟实现了string类比较常见的一些方法。 四、windows下的VS中的string类和Linxu下的g中的string类。 在windows下的VS编译器中string类中有一个大小为16的Buf数组但是只能存15个有效字符因为最后一个空间要留个’\0’当string类中的字符串大小小于15时字符串的内容就存到_Buf这个数组中当string类中的字符串大小大于15时就会存在动态申请的堆区中了。使用_Buf数据可以使string类的字符串很短时就不需要动态申请内存了这样也减少了内存碎片的产生。而每个string类大小为28个字节因为_Buf占16个字节size_t类型的size和capacity共占8字节还有一个指针指向动态申请的空间也占4个字节。 在Linux下的g编译器中string类类型对象的大小在32位电脑下为4字节在64位电脑下为8字节即只有一个指针的大小。在g中string是通过写时拷贝实现的string对象内部只包含了一个指针该指针将来指向一块堆空间内部包含了如下字段 空间总大小字符串有效长度引用计数指向堆空间的指针用来存储字符串。 在Linux下的g中创建一个string类类型的对象是这样实现的。 当通过拷贝构造创建s2时g中这样处理。即先将s2中的_ptr指针也指向s1的空间s1和s2先同时访问这一片空间并且此时引用计数_refcount为2引用计数是为了防止重复析构的因为如果s1和s2都指向一个空间的话那么会调用两次析构函数来释放这片空间但是有了_refcount的概念后只有当_refcount1时才调用析构函数而如果_refcount不为1时如果有一个指向这片空间的对象销毁了_refcount就 -1。 如果向s2对象中插入数据或删除数据时即对这片空间进行写操作时此时会新开辟一片空间然后将s1指向的空间的内容都拷贝到新空间去这样s2修改的就是新空间的内容了就不会影响s1指向的空间里的内容。而如果只对s2对象进行读操作时就不需要进行上面的拷贝操作这样就减少了一次深拷贝。这就是写时拷贝的大致思想。
http://www.hkea.cn/news/14488284/

相关文章:

  • 做门户网站需要注册公司吗网页制作公司有哪些职位
  • 网站租用服务器价格网站建设策划方案ppt
  • 西安做网站朋朋计算机网络技术毕业设计
  • 优秀网站菜单网站内做营销活动使用工具
  • 辽宁住房建设厅网站阳泉做网站
  • 广州建设厅电工网站网站制作教程书籍
  • 长春网页网站制作东莞工厂网站建设
  • 手机怎样用网站做成软件医院网站开发公司
  • 公司网站建设需要哪些设备wordpress相册短代码
  • 广州网站开发外包成都房地产信息网官网
  • 学校网站建设宗旨基础的网站建设
  • 一站式网站开发唯美谷网站建设
  • 四川省微信网站建设怎么在各大网站做推广
  • 偃师网站建设建个人网站赚钱吗
  • 网站模型怎么做创作图片的软件
  • 电子商务网站的特点台州哪家做企业网站比较好
  • 网站后期维护合同网站布局策划的流程
  • 网站管理系统软件深圳公司标牌制作
  • wordpress房屋网站模板营销策划
  • 鲅鱼圈做网站做鲜花的网站有哪些
  • 全屏展示网站图片如何做自适应网站制作公司广州
  • 郑州做网站设计的公司免费网站服务
  • 做音乐网站赚钱吗购物网站排名2017
  • 酷虎云建站wordpress标签小工具栏
  • aspx 网站开发工具wordpress网上在线插件
  • 建一个英文网站需要多少钱网站设置支付宝在线支付
  • 网页设计美工培训班资源网站优化排名软件公司
  • 空气过滤棉上海网站建设大数据营销模型
  • 网站如何做参考文献网站百度无排名
  • 百度推广太原网站建设WordPress新版文章标签