摘要:目录类对象的常见构造类对象的访问及遍历操作迭代器介绍类对象的修改操作深浅拷贝问题深拷贝传统写法深拷贝的现代写法模拟实现构造函数拷贝构造函数交换函数拷贝赋值运算符获取对象的成员属性重载可读可写和可读定义迭代器增容处理和函
介绍常用的几个string接口函数,如果需要学习更多的请参考官方文档: string构造函数.
函数名称 | 功能说明 |
---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
string (const string& str, size_t pos, size_t len = npos) | 从str对象中由pos位置开始截取len个长度的字符,len > str长度就结束 |
string (const char* s, size_t n) | 从s指向的字符数组中复制前n个字符。 |
void functest() { //函数原型:string() string s1; //调用默认构造函数创建一个string对象,对象的内容是空字符串 //类似于这种初始化方式:string s1("") //函数原型:string(const char* s) string s2("hello cpp"); //调用string的构造函数用字符串去初始化一个string对象 //函数原型:string(const string&s) string s3(s2); //调用string类的拷贝构造函数创建s3对象 //函数原型:string (const string& str, size_t pos, size_t len = npos) string s4(s3, 0, 5); //从s3对象中由0位置开始截取5个长度的字符 char arr[] = "https://blog.csdn.net/m0_53421868?spm=1000.2115.3001.5343"; //函数原型:string (const char* s, size_t n) string s5(arr, 5); //截取字符数组的n个长度字符 cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; cout << s4 << endl; cout << s5 << endl;}
打印结果:
函数名称 | 功能说明 |
---|---|
operator[] (重点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持 |
使用正向迭代器遍历string对象
void functest() { string s1; string s2("hello cpp"); //返回字符串首字符的迭代器给sc,让sc指向这个字符 string::iterator sc = s2.begin(); while (sc != s2.end()) { cout << *sc << " "; sc++; }}
使用反向迭代器遍历string对象
void functest() { string s1; string s2("abcdefg"); //返回最后一个字符的迭代器让sc指向 string::reverse_iterator sc = s2.rbegin(); while (sc != s2.rend()) { cout << *sc << " "; ++sc; }}
而以上两种迭代器都是可以去修改对象的值的,还有一种只读的迭代器不允许修改对象的值
void functest() { string s1; string s2("abcdefg"); string::const_iterator sc = s2.begin(); while (sc != s2.end()) { //可读但不能修改 cout << *sc++ ; }}
反向只读迭代器遍历string对象
void functest() { string s1; string s2("abcdefg"); //返回const_reverse_iterator 的迭代器支持反向遍历操作, //但不支持修改 string::const_reverse_iterator sc = s2.rbegin(); while (sc != s2.rend()) { cout << *sc++; }}
总结:以上几种迭代器的操作比较简单,读者可以结合文档自己去使用
链接: 做题.
题目描述:
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
测试用例:
实现思路:
找出字符串中只出现一次的字符可以采用计数的方法,记录每个字符在数组中出现的位置统计他出现的次数,最后只出现一次的字符就是我们要找的字符
范围for + operator[]
class Solution {public: int firstUniqChar(string s) { int count[26] = {0}; for(auto e : s) { //记录每个字符在count数组中出现的次数(相对映射) count[e - "a"]++; } for(size_t i = 0; i < s.size(); i++) { //检查某个字符映射在数组中出现的次数, //如果等于1就表示只出现一次 if(count[s[i] - "a"] == 1) { return i; } } return -1; }};
使用迭代器遍历对象的源字符串中的字符再通过count数组计数
class Solution {public: int firstUniqChar(string s) { int count[26] = { 0 }; string::iterator sc = s.begin(); //sc接受s.begin返回的迭代器 while (sc != s.end()) { count[*sc - "a"]++; sc++; } for (size_t i = 0; i < s.size(); i++) { if (count[s[i] - "a"] == 1) { return i; } } return -1; }};
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 在字符串中搜索由参数指定的序列的最后一次出现时的下标位置。 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
insert | 指定位置pos处插入一个字符串 |
erase | 从指定pos位置开始删除len个长度的字符 |
插入操作:
void functest2() { string s; s.push_back("h"); s.push_back("e"); s.push_back("l"); s.push_back("l"); s.push_back("o"); cout << s << endl;// hello s.append("word");// helloword cout << s << endl; //append还可以指定一个迭代器区间, //将这段区间的字符串追加到s对象// 函数原型:string& append (const string& str, size_t subpos, size_t sublen); string s3("defgh"); s.append(s3, 2,3); //从字符串中下标位置为2的字符起开始截取3个字符追加到s对象 cout << s << endl;//hellowordfgh //string成员函数operator+=同样也支持追加字符串的操作 s += "abc"; cout << s << endl; //hellowordfghabc //指定位置插入 //函数原型:string& insert (size_t pos, const char* s); string s("abcdefg"); s.insert(0,"c");//err,参数是字符串,这样使用不对 s.insert(0, "x"); //0位置处插入一个x,整体字符串往后挪动,不推荐使用,挪动数据的效率太低}
需要注意的一些string成员函数c_str
string s("hello word"); cout << s << endl; //自定义类型的s在输出的时候会 //调用重载的 operator<< (ostream& os, const string& str); cout << s.c_str() << endl; //s.c_str()函数返回的是一个c风格的字符串他是内置类型, //使用的是全局的operator<<(cout, const char *) cout << "------------------" << endl; //所以会在输出的时候各不相同 s.resize(20); s += "!!!"; cout << s << endl; cout << s.c_str() << endl;
效果:
1、调用内置类型的operator<<(cout, const char *) 输出字符串的时候遇到‘/0’就会停止
2、调用自定义类型的operator<< (ostream& os, const string& str)即使遇到‘/0’也不会停止,只有把字符串给完全遍历完了才会停止遍历
删除操作:
void functest3() { string s("abcdefg"); //函数原型:string& erase (size_t pos = 0, size_t len = npos); //函数功能:指定pos位置处开始,删除len个长度的字符 s.erase(1,2); cout << s << endl;//adefg}
查找操作:
从前往后
//string拷贝构造函数原型: 这里会给缺省值//string (const string& str, size_t pos, size_t len = npos);string buff1("test.cpp");int pos = buff1.find(".");if (pos != string::npos) //npos值是-1,类型是无符号整形,所以是整形的最大值,字符串并不会存储这么长{ string buff2(buff1,pos, buff1.size() - pos); //调用拷贝构造函数创建buff2对象, //从pos位置开始截取len个长度的字符串创建一个string对象 //也可以使用这种写法去截取后缀: //即使使用了函数提供的缺省值也并不用过于担心,npos是最大值也好 //只会截取有效内容,超过不管 string buff3(buff1,pos); //不调用拷贝构造函数同样也能做到, substr截取 //函数原型:string substr (size_t pos = 0, size_t len = npos) const; string buff4 = buff1.substr(pos); cout << buff2 << endl;//.cpp}
从后往前
//函数原型://size_t rfind (const string& str, size_t pos = npos) const;//函数功能是返回指定参数在字符串中最后一次出现的位置string buff1("test.cpp.zip");int pos = buff1.rfind(".");if (pos != string::npos) { string buff2(buff1, pos); cout << buff2 << endl;}
使用string成员函数查找网络域名跟协议使用举例,详细解释请看注释
//返回协议名string GetAgreeMent(const string& s){ //查找"://",找到返回该字符串的起始下标位置 size_t pos = s.find("://"); if (pos != string::npos) { //由于是左闭右开区间【0,5),所以只需要pos - 0就能计算出协议名的长度 return s.substr(0, pos - 0); } else { //找不到返回空串 return string(); }}//返回域名string GetDomain(const string& s) { //查找"://",找到返回该字符串的起始下标位置 size_t pos = s.find("://"); if (pos != string::npos) { //计算出域名的起始下标位置,从这个位置开始查找"/" size_t start = pos + 3; size_t end = s.find("/", start); if (end != string::npos) { //同样的左闭右开区间,开区间的位置减去闭区间的位置就是字符串的长度 return s.substr(start, end - start); } else { //找不到返回空串 return string(); } } else { //找不到返回空串 return string(); } }void functest4() { string url1 = "https://blog.csdn.net/m0_53421868?spm=1000.2115.3001.5343"; string url2 = "https://bbs.csdn.net/forums/mzt"; cout << GetDomain(url1) << endl; cout << GetAgreeMent(url1) << endl; cout << GetDomain(url2) << endl; cout << GetAgreeMent(url2) << endl;}
string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size(重点) | 统计字符个数 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符,将size置零 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
测试:
string s;s.resize(10);//插入10个"/0",默认以"/0"填充cout << s << endl;string s1;s1.resize(10,"x");//指定插入10个字符,以"x"填充cout << s1 << endl;string s3("hello word");s3.resize(20,"x"); //将空间扩容到20,多出来的空间用"x"填充,并且是尾插的方式cout << s3 << endl;
需要注意的一些增容函数
与resize函数相似的有reserve 他并不会改变空间的内容,只做增容
浅拷贝一般都是在拷贝构造一个对象的时候完成值拷贝的一个过程,因为我们不写编译器默认的生成的拷贝构造函数完成的是浅拷贝,这样对象出了作用域开始调用析构函数的时候会导致同一块空间被释放两次,就会引发程序崩溃
namespace mzt { class string { public: //重载operator<< friend ostream &operator<< (ostream &out, string &str) { out << str._str << endl; return out; } string(const char* str = "") : _str(new char[strlen(str) + 1]) , _size(0) ,_capacity(0) { strcpy(_str, str); } //我们不写编译器会默认生成一个拷贝构造函数完成浅拷贝 /* string(const string & str) : _str(new char[strlen(str._str) + 1]) { strcpy(_str, str._str); }*/ ~string() { delete[] _str; _str = nullptr; } private: char* _str; size_t _capacity; size_t _size; }; void stringtest() { mzt::string s("hello world"); mzt::string s1(s); cout << s; cout << s1; }}
即使打印出了hello world,但是这个程序还是存在问题,因为两个对象的_str指针指向的是同一块空间所以当被delete的时候就会被析构两次,这是不被允许的,解决办法深拷贝,重新创建一个空间,并把值存过去,让两个对象之间的内容互不影响
namespace mzt { class string { public: friend ostream &operator<< (ostream &out, string &str) { out << str._str << endl; return out; } string(const char* str = "") : _str(new char[strlen(str) + 1]) , _size(0) ,_capacity(0) { strcpy(_str, str); } //我们不写编译器会默认生成一个拷贝构造函数完成浅拷贝 //解决办法深拷贝,开辟一块新的空间,把值拷贝过去 string(const string & str) : _str(new char[strlen(str._str) + 1]) { strcpy(_str, str._str); } //重载operator=也是一样的做法, //开空间拷贝值避免出现浅拷贝的问题 string& operator=(const string& s) { if (this != &s) { delete[] _str; _str = new char[strlen(s._str) + 1]; strcpy(_str, s._str); } return *this; } ~string() { delete[] _str; _str = nullptr; } private: char* _str; size_t _capacity; size_t _size; }; void stringtest() { mzt::string s("hello world"); mzt::string s1(s); cout << s; cout << s1; }}
推荐使用现代深拷贝的方法:
原因1: 代码简洁
原因2:可读性强
//我们不写编译器会默认生成一个拷贝构造函数完成浅拷贝//解决办法做深拷贝string(const string &s) : _str(nullptr) //_str必须初始化为nullptr,才去交换tmp指针, //否则_str就是野指针了,当tmp出了作用域析构野指针会有非法内存访问{ //调用构造函数利用s._str做参数构造临时对象 string tmp(s._str); //将临时对象的指针_str和this._str一交换 swap(tmp._str, _str); //临时对象出了作用域就销毁了,会自动调用它的析构函数}//拷贝赋值运算符现代写法string& operator=(string s)//s通过调用拷贝构造函数完成的深拷贝{ //还是一样的思路,由于拷贝构造函数已经被我们实现了, //所以就不会存在浅拷贝的问题,所以通过值传递,即使 //栈帧被销毁了,两个对象也互不影响,这也是一种复用的方法 //直接上手交换指针this._str和s._str swap(_str, s
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/123192.html
摘要:本文介绍了类的常用接口的使用,并对其进行了模拟实现,对模拟实现中涉及到的深浅拷贝问题进行了解析。在此之前,必须提到一个经典问题。为了解决浅拷贝问题,所以中引入了深拷贝。但是实际使用中需要是第一个形参对象,才能正常使用。 本文介绍了string类的常用接口的使用,并对其进行了模拟实现,对模拟实...
摘要:编写测试用例代码打开框架自动生成的测试代码文件编写测试用例,测试增删改查效果,测试代码如下注释,它可以对类成员变量方法及构造函数进行标注,完成自动装配的工作。 文章系列 【从零入门系列-0】Sprint Boot 之 Hello World 【从零入门系列-1】Sprint Boot 之 程序结构设计说明 【从零入门系列-2】Sprint Boot 之 数据库实体类 前言 前一章...
摘要:拷贝构造函数示例构造无参构造函数总结容器和容器的构造方式几乎一致,灵活使用即可赋值操作功能描述给容器进行赋值函数原型重载等号操作符将区间中的数据拷贝赋值给本身。清空容器的所有数据删除区间的数据,返回下一个数据的位置。 ...
阅读 4360·2021-11-22 09:34
阅读 2688·2021-11-12 10:36
阅读 740·2021-08-18 10:23
阅读 2634·2019-08-30 15:55
阅读 3108·2019-08-30 15:53
阅读 2080·2019-08-30 15:44
阅读 1358·2019-08-29 15:37
阅读 1398·2019-08-29 13:04