既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
+ [拷贝赋值运算符](#_618)
+ [获取对象的成员属性](#_630)
+ [重载可读可写[ ]和可读[ ]](#___646)
+ [定义迭代器](#_663)
+ [增容处理](#_690)
+ [rsize和reverse函数](#rsizereverse_721)
+ [指定位置插入操作](#_774)
+ [删除](#_820)
+ [查找](#_842)
+ [重载输入输出](#_868)
string类对象的常见构造
介绍常用的几个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;
}
打印结果:
string类对象的访问及遍历操作迭代器介绍
函数名称 | 功能说明 |
---|---|
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;
}
};
string类对象的修改操作
函数名称 | 功能说明 |
---|---|
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._str);
return \*this;
}
注意:想要复用拷贝构造函数,这里的_str必须初始化为nullptr,否则程序是会有隐患的
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
_str一交换
swap(tmp._str, _str);
//临时对象出了作用域就销毁了,会自动调用它的析构函数
}
//拷贝赋值运算符现代写法
string& operator=(string s)
//s通过调用拷贝构造函数完成的深拷贝
{
//还是一样的思路,由于拷贝构造函数已经被我们实现了,
//所以就不会存在浅拷贝的问题,所以通过值传递,即使
//栈帧被销毁了,两个对象也互不影响,这也是一种复用的方法
//直接上手交换指针this._str和s._str
swap(_str, s._str);
return *this;
}
注意:想要复用拷贝构造函数,这里的\_str必须初始化为nullptr,否则程序是会有隐患的
[外链图片转存中...(img-T7g1uZuz-1715735966200)]
[外链图片转存中...(img-9v5JmrHd-1715735966201)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**