C++的string学习总结

一.标准库的string类

1.string类是什么?

  • string是表示字符串的字符串
  • string类的接口与常规容器的接口基本相同,在添加一些专门用来操作string的常规接口
  • string类在底层实际上是basic_string模板类的别名。typedef basic_string<char,char_traits,allocator> string
  • string类不能操作多字节或者变长字符序列
  • 使用string类的时候,必须包含头文件string以及using namespace std

2.string类常用接口总结

a.string类对象的常见构造函数
  • string():构造空的string类对象,既空字符串
  • string(const char* s):使用C语言的字符串构造string类对象
  • string(size_t n,char c):构造后的string类对象包括n个字符c
  • string(const string& s):用string类对象s拷贝构造另一个对象
  • string(const string& s, size_t n):使用对象s中的第n个字符开始构造新的string类对象
int main()
{
 string s1; // 构造空的string类对象s1 
 string s2("hello,world"); //使用C字符串构造
 string s3(10, 'a'); // 10个a构造对象
 string s4(s2); //拷贝构造
 string s5(s2, 9);//从s2对象下标为9的位置开始构造
 cout << s1.c_str() << endl;
 cout << s2.c_str() << endl;
 cout << s3.c_str() << endl;
 cout << s4.c_str() << endl;
 cout << s5.c_str() << endl;
 return 0;
}

运行结果如下:
在这里插入图片描述

b.string类对象常用的容量操作
  • size_t size() const:返回字符串有效字符长度
  • size_t length() const:返回字符串有效的长度
  • size_t capacity() const:返回空间总大小,既容量
  • bool empty() const:判断字符串是否为空串,是返回true,不是返回false
  • void clear():清空有效的字符
  • void resize(size_t n,char c):将有效字符的个数改成n个,多出来的空间用0填充
  • void reserve(size_t res_arg = 0):修改string类的容量,为字符串预留空间
void test1()
{
 string s("hello,world"); 
 cout << s.length() << endl; // 11
 cout << s.size() << endl; // 11
 cout << s.capacity() << endl; 
 // 15 ,capacity是15但是实际上开辟了16个char的空间,最后还有`\0`
 cout << s << endl;//string类对象支持直接用cin和cout进行输入输出
 
 //将对象s中的字符串清空,只改变有效数据的大小,不改变容量的大小
 s.clear(); 
 cout << s.size() << endl; // 0
 cout << s.capacity() << endl; // 15
 // 将s中有效字符个数增加到10个,多出位置用'j'进行填充
 s.resize(10, 'j');
 cout << s.size() << endl; // 10
 cout << s.capacity() << endl; // 15
 cout << s << endl; // jjjjjjjjjj
 //将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充 
 s.resize(15);
 cout << s.size() << endl; // 15
 cout << s.capacity() << endl; // 15
 cout << s << endl; // jjjjjjjjjj\0\0\0\0\0
 //将s中有效字符个数缩小到5个
 s.resize(5);
 cout << s.size() << endl; // 5
 cout << s.capacity() << endl; // 15
 cout << s << endl; // jjjjj
 cout << "--------------" << endl;
 //测试reserve
 string s2("hehe");
 //reserve不会改变string中有效元素个数 
 cout << s2.size() << endl;//4
 cout << s2.capacity() << endl;//15
 s2.reserve(100);
 cout << s2.size() << endl;//4
 cout << s2.capacity() << endl;//111
 //当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小
 s2.reserve(50);
 cout << s2.size() << endl; // 4
 cout << s2.capacity() << endl; // 111
}

关于容量操作需要注意的一些地方:

  • size()length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()
  • clear()只是将string中有效字符清空,不改变底层空间大小
  • resize(size_t n)resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变
  • reserve(size_t res_arg=0)string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserve不会改变容量大小
c.string类对象的访问操作
  • char& operator[] (size_t pos):返回pos位置的字符,也可以修改
  • const char& operator[] (size_t pos):返回pos位置字符,只能访问不能修改
void test1()
{
 string s1("hello");
 const string s2("world");
 cout << s1[0] << " " << s2[0] << endl; //h w
 s1[0] = 'x';
 // s2[0] = 'x';这个不能修改,因为它是const修饰的对象不能修改
 cout << s1[0] << endl;// x
}
d.string类对象常见的修改操作
  • void push_back(char c):在字符串尾插入字符c
  • string& append(const char* s):在字符串后边追加一个C语言字符串
  • string& operator+=(const string& str):在字符串后边追加string类字符串
  • string& operator+=(const char* s):在字符串后边追加C语言字符串
  • string& operator+=(char c):在字符串后追加字符c
  • const char* c_str() const:返回C格式的字符串,既char*
  • size_t find(char c,size_t pos=0) const:从字符串pos位置开始向后找字符c,找到返回该字符在字符串中的位置,找不到返回-1
  • size_t rfind(char c, size_t pos = npos):从字符串pos位置开始向前查找字符c,返回该字符在字符串中的位置
  • string substr(size_t pos=0,size_t n=npos) const:在str中从pos位置开始,截取n个字符,然后将其返回
  • string& erase (size_t pos = 0, size_t len = npos):删除从pos开始向后的len个字符
void test2()
{
 string str("hello");
 str.push_back('C');//在str后边追加字符C
 cout << str << endl;
 str.append(" world");//在str后边追加字符串
 cout << str << endl;
 str += 'X'; //str后边追加字符X
 cout << str << endl;
 str += "xxx";//str后边追加字符串xxx
 cout << str << endl;
 cout << str.c_str() << endl;//以C语言形式打印字符串
}
// npos是string里面的一个静态成员变量    
// static const size_t npos = -1;
void test3()
{
 string file("test.cpp");
 size_t pos = file.rfind('.');
 if (pos == string::npos)
 {
  cout << "文件不存在后缀" << endl;
 }
 else{
  cout << file.substr(pos, file.size() - pos) << endl;
 }
}
void test4()
{
 string url("http://www.baidu.com/");
 size_t start = url.find("://");
 if (start == string::npos){
  cout << "url error" << endl;
  return;
 }
 //find找不到返回-1,但是用size_t接收finish是一个很大的数字
 size_t finish = url.find('/', start + 3);
 cout << url.substr(start + 3, finish - start - 3) << endl;
 //删除url的前缀
 int pos = url.find("://");
 url.erase(0, pos + 3);
 cout << url << endl;
}

如果存在多次插入的操作,我们可以利用reserve提高插入数据的效率,避免增容带来的开销。

//增容4次
void test5()
{
 string s;
 int sz = s.capacity();
 for (int i = 0; i < 100; ++i)
 {
  s += 'x';
  if (sz != s.capacity())
  {
   cout << s.capacity() << endl;
  }
 }
}
//没有增容
void test5_op()
{
 string s;
 s.reserve(100);
 int sz = s.capacity();
 for (int i = 0; i < 100; ++i)
 {
  s += 'x';
  if (sz != s.capacity())
  {
   cout << s.capacity() << endl;
  }
 }
}
  • string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串
  • string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好,这样可以减少调用扩容函数增加的额外开销
e.string类的一些非成员函数
  • operator+:一个字符串加一个字符串,需要要另一个字符串接收返回值s=s1+s2
  • operator>>:输入运算符重载
  • operator<<:输出运算符重载
  • getline:获取一行的字符串
  • relational operators:比较两个字符串的大小

3.string类对象的三种访问方式

  • 迭代器--不常用,跟其他容器保持统一的访问方式
void test()
{
 string num("1234");
 /*迭代器,底层类似指针
 用来遍历string,访问容器
 迭代器是string类中的内部类型,是typedef出来的
 begin指向第一个位置,end指向最后一个数据的下一个迭代器  [)左闭右开区间
 迭代器给出一个统一的方式访问容器,不需要关心容器底层的细节*/
 string::iterator it = num.begin();
 int res = 0;
 while (it != num.end()){
  res *= 10;
  res += (*it) - '0';
  ++it;
 }
 cout << res << endl;
 //vector为类名,vector<int>为类型
 vector<int> v;
 v.push_back(1);
 v.push_back(2);
 vector<int>::iterator vit = v.begin();
 while (vit != v.end()){
  cout << *vit << " ";
  ++vit;
 }
 cout << endl;
}
void test1()
{
 //反向迭代器
 string num("1234");
 string::reverse_iterator rit = num.rbegin();//rbrgin--4,rend--1
 while (rit != num.rend()){
  cout << *rit << " ";
  (*rit) += 1; //迭代器遍历可以修改
  ++rit;
 }
 cout << endl;
}
int StrToNum(const string& str)
{
 //const修饰的迭代器只能读不能写
 int num = 0;
 string::const_iterator it = str.begin(); //begin存在重载函数-const修饰的迭代器
 while (it != str.end())
 {
  num *= 10;
  num += (*it) - '0';
  ++it;
  //(*it) = 3;错误不能修改
 }
 cout << num << endl;
 return num;
}
  • for+下标--更加常用 (sring\vector\双端队列支持operator[],可以像数组一样访问)
void test2()
{
 //for + 下标
 string s1("hello");
 for (int i = 0; i < s1.size(); i++)
 {
  cout << s1[i] << " ";
 }
 cout << endl;
}
  • 语法糖C++11,新式for循环
void test3()
{
	string str("2345");
 	int res = 0;
 	for (auto e : str)
 	{
  		res *= 10;
  		res += (e - '0');
 	}
 	cout << res << endl;
}

二.string类的模拟实现

1.浅拷贝和深拷贝

浅拷贝:也称值拷贝,编译器只是将对象中的值拷贝过来。如果对象中存在资源(堆上的内存),最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源操作时,就会发生发生了非法访问
在这里插入图片描述
深拷贝:如果一个类中涉及到资源的管理,我们必须给每个对象独立的分配资源,保证多个对象之间不会因为共享资源造成多次释放的程序崩溃问题。一个类中如果涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

在这里插入图片描述

2.传统写法和现代写法

  • 传统写法
/**
 *  模拟实现string类的构造、拷贝构造、赋值运算符重载
 *  
*/
class String
{
public:
 //构造函数
 String(char* str = "")
 {
  // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言
  if (str == nullptr)
  {
   assert(false);
   return;
  }
  _str = new char[strlen(str) + 1];
  strcpy(_str, str);
 }
 //析构函数
 ~String()
 {
  if (_str)
  {
   delete[] _str;
   _str = nullptr;
  }
 }
 //拷贝构造(深拷贝)
 String(const String& s)
  :_str(new char[strlen(s._str) + 1])
 {
  strcpy(_str, s._str);
 }
 //赋值运算符重载
 String& operator=(const String& s)
 {
  if (this != &s)//防止自己给自己赋值
  {
   delete[] this->_str; // s2的空间比当前空间大,所以要先释放s1既当前对象,
                        //然后在开辟和s2一样大的空间进行拷贝
   _str = new char[strlen(s._str) + 1];
   strcpy(_str, s._str);
  }
  return *this;//连续赋值
 }
private:
 char* _str;
};
  • 现代写法
/**
 *  模拟实现string类的构造、拷贝构造、赋值运算符重载
 *  
*/
cl
class String
{
public:
 //构造函数
 String(char* str = "")
 {
  // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言
  if (str == nullptr)
  {
   assert(false);
   return;
  }
  _str = new char[strlen(str) + 1];
  strcpy(_str, str);
 }
 //析构函数
 ~String()
 {
  if (_str)
  {
   delete[] _str;
   _str = nullptr;
  }
 }
 //拷贝构造(深拷贝)
 String(const String& s)
  :_str(nullptr)
 {
  String tmp(s._str);//复用构造函数
  swap(_str, tmp._str);
 }
 //赋值运算符重载
 String& operator=(String s) //传值过程中存在一份拷贝构造
 {
  swap(_str, s._str);
  return *this;
 }
private:
 char* _str;
};

3.写时拷贝

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。如果以只读的方式使用资源,那么就直接在源资源上加引用。但是如果使用只写的方式使用资源,那么就拷贝一份资源,然后使用。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放,否则就不能释放,因为还有其他对象在使用该资源

  • 9
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,可以使用索引来访问和操作字符串。通过使用方括号和索引值,可以获取字符串中特定位置的字符。例如,str1\[i\]表示字符串str1中索引为i的字符。 在引用\[1\]的代码示例中,使用了索引来遍历字符串str1并打印每个字符。for循环中的i从0递增到str1的长度-1,通过str1\[i\]来获取每个字符并使用printf函数打印出来。 另外,在引用\[3\]的代码示例中,使用了索引来插入字符串。通过调用insert函数,可以在指定的索引位置插入指定的字符串。例如,str1.insert(1,"ss")将字符串"ss"插入到str1的索引为1的位置。 总结起来,C++中的字符串索引可以用于访问和操作字符串中的特定字符,可以通过方括号和索引值来实现。 #### 引用[.reference_title] - *1* *3* [c++ string类使用学习;length、empty、find、insert;索引取值、遍历](https://blog.csdn.net/weixin_42357472/article/details/122696837)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【跟学C++C++String类用法详解【番外1】](https://blog.csdn.net/qq_41225961/article/details/124902421)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值