【c++】string

简介: 【c++】string

迭代器

我们之前学习使用范围for打印一段字符串,其本质其实就是迭代器

下面是一段使用范围for,打印字符类

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1 = "hello world";
  for (auto e : s1)
  {
    cout<< e <<"";
  }
}

如果使用迭代器的话,应该怎么打印string呢?代码如下

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1="hello world";
  string::iterator it = s1.begin();
  while (it != s1.end())
  {
    cout << *it << "";
    ++it;
    }
  cout << endl;
}

迭代器的用法像指针,用指针的方式进行遍历访问.

我们之前c语言遍历数组时,通常采用下标+[],访问一个元素,只适合部分容器,底层物理有一定的连续链式结构,树形,哈希结构,只能用迭代器,迭代器才是容器访问的主流形态.

2.反向迭代器(reverse_iterator)

所谓反向迭代器,如果用指针解释的话,就是有一个指针指向尾巴,倒着遍历

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1 = "hello world";
  string:: reverse_iterator it = s1.rbegin();
  while (it != s1.rend())
  {
    cout << *it << "";
    ++it;
  }
  cout << endl;
}

3.正向常量迭代器

该类型的迭代器只能读取容器内的元素,但是不能修改其值.

正向,反向,常量,普通可以组成四种迭代器.

看一下下面这段代码

#include<iostream>
#include<string>
using namespace std;
void Func(const string& s)
{
  
  string::iterator it =s.begin();
  while (it != s.end())
  {
    
    cout << *it << " ";
    ++it;
  }
  cout << endl;
}
int main()
{
  string s1 = "hello world";
  string::iterator it = s1.begin();
  while (it != s1.end())
  {
    
    cout << *it << "";
    ++it;
  }
  cout << endl;
  Func(s1);
}

这里通过引用将常量迭代器类型要强制类型转换为普通可写的迭代器,这里权限放大了,是不可以的.

如果是权限缩小就是可以的

#include<iostream>
#include<string>
using namespace std;
void Func( string& s)
{
  // 遍历和读容器的数据,不能写
  string::const_iterator it =s.begin();
  while (it != s.end())
  {
    
    cout << *it << " ";
    ++it;
  }
  cout << endl;
}
int main()
{
  string s1 = "hello world";
  string::iterator it = s1.begin();
  while (it != s1.end())
  {
    
    cout << *it << "";
    ++it;
  }
  cout << endl;
  Func(s1);
}

1.简单的string类函数

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1;
  string s2("hello world");
  string s3 = "hello world";
  string s4(s3, 6, 3);//从下标为6的位置开始拷贝3个到s4;      
  cout << s4 << endl;
  string s5(s3, 6,13);//从下标为6的位置开始拷贝,直到拷贝到结尾,下标为6到字符串结尾个数小于13个
  cout << s5 << endl;
  string s6(s3, 6);//从下标为6的位置开始拷贝,直到拷贝到结尾,
  cout << s6 << endl;
  string s7(s3, 6,string::npos);//从下标为6的位置开始拷贝,直到拷贝到结尾,string::npos是-1,相当于size_t 最大值
  cout << s7 << endl;
  string s8("hello world", 3);//拷贝第一个字符串前三个
  cout << s8 << endl;
  string s9(10,'*');//10个*给s9
  cout << s9 << endl;
}

2.length() ,max_size() ,capacity(),size()

size() 返回字符串有效字符长度

length() 返回字符串有效字符长度

max_size() 字符串长度最大值,int的最大值

capacity() 返回空间总大小

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1("hello world");
  cout << s1.size() << endl;
  cout << s1.length() << endl;
  cout << s1.max_size() << endl;
  cout << s1.capacity() << endl;
}

string一开始只有length(),这是c语言中的,当出现STL之后,为了兼容加入了和STL容器求长度函数的一个名字size,这样就可以方便使用STL中的算法

这里的capacity为什么是15??

这里我们就要观察string类是怎么给扩容的了.

看下面这块代码解释

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s;
  cout << sizeof(s) << endl;
  size_t sz = s.capacity();
  cout << "making s grow:\n";
  cout << "capacity changed:" << sz << '\n';
  for(int i=0;i<100;++i)
  {
    s.push_back('c');
    if (sz != s.capacity())
    {
      sz = s.capacity();
      cout << "capacity changed:" << sz << '\n';
    }
  }
}

1.创建一个字符串的类,并计算该类的字节大小,

2.sz记录s的容量

3.在for循环中不断地插入字符,s的容量发生变化时,sz!=s.capacity,然后sz赋值为新容量的大小,我们来观察扩容时容量的变化

发现第一次扩容时是2倍扩容,接下来差不多是1.5倍扩容,s类的字节大小为28,我们可以查看一下内存

发现s的类像这种

class string
{
  char* _ptr;
  char _buf[16];
  size_t _size;
  size_t _capacity;
};

当string类中字符个数<=15个时,存放在_buf[]中,当个数大于15的时候存放在

_ptr中

如果在linux下是怎么扩容的,我们将代码复制过去

1.先安装g++在linux上面,

centos版本

yum -y install gcc gcc-c++ autoconf pcre pcre-devel make automake
yum -y install wget httpd-tools vim

2.创建一个wang.cc

3.vim打开,复制进去

4.g++ wang.cc

5. ./a.out

发现s的字节大小是8,然后是成倍增长.


3.push_back() ,append(),operator+=

push_back() 在字符串后尾插字符c

append() 在字符串后追加一个字符串

operator+= 在字符串后追加字符串str

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1("hello");
  s1.push_back(' ');
  s1.push_back('!');
  cout << s1 << endl;
  string s2("how are you");
  s2.append("dawn");
  cout << s2 << endl;
  string s3("ni hao ma");
  s3 += "wo hen hao";
  s3 += '!';
  cout << s3 << endl;
}


4.reserve(),resize()

reserve() 为字符串预留空间

resize() 将有效字符的个数该成n个,多出的空间用字符c填充

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1("hello world");
  s1.reserve(100);// 扩容
  cout << s1.size() << endl;
  cout << s1.capacity() << endl;
  string s2("hello world");
  s1.resize(100,'x');//扩容+初始化
  cout << s2.size() << endl;
  cout << s2.capacity() << endl;
}

容量一样,大小不一样

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1("hello world");
  s1.reserve(100);
  cout << s1.size() << endl;
  cout << s1.capacity() << endl;
  string s2("hello world");
  s2.resize(100,'x');
  cout << s2.size() << endl;
  cout << s2.capacity() << endl;
  //比size小,删除数据,保留前五个
  s2.resize(5);
  cout << s2.size() << endl;
  cout << s2.capacity() << endl;
}

如果比size小,删除数据,保留前五个


5.at()

如果字符串访问越界

int  main()
{
  string s1 = ("hello world");
  cout << s1[20] << endl;
}

这种会直接报错,暴力

at() 访问字符串元素,超过范围温柔报错

int  main()
{
  string s1 = ("hello world");
  cout << s1.at(2)<< endl;
  cout << s1[4] << endl;
  try
  {
    s1.at(100);
  }
  catch (const exception& e)
  {
    cout << e.what() << endl;
  }
}


6.insert(),erase()

insert() 插入字符

erase() 删除字符

#include<iostream>
#include<string>
using namespace std;
int main()
{
  string s1("world");
  s1.insert(0, "hello");//下标为0处开始插入“hello"
  cout << s1 << endl;
  string s2("world");//下标为5的位置插入一个空格
  s2.insert(5, 1, ' ');
  cout << s2 << endl;
  string s3("world");//下标为5的位置插入一个空格
  s3.insert(5," ");
  cout << s3 << endl;
  string s4("world");
  s4.insert(s4.begin()+5,' ');//迭代器,下标为5的位置插入一个空格
  cout << s4 << endl;
  string s5("world hello"); //删除下标为5的元素
  s5.erase(5,1);
  cout << s5 << endl;
  string s6("world hello");
  s6.erase(s6.begin()+5);//删除下标为5的元素
  cout << s6 << endl;
  string s7("world hello");
  s7.erase(5,30);//删除下标为5开始往后的30个元素,没有30个就删除到结尾
  cout << s7 << endl;
  string s8("world hello");
  s8.erase(5);//删除下标为5开始往后到结尾
  cout << s8 << endl;
}

一般不用,要挪动数据,效率低下


7.replace()

replace() 从给定位置替换字符

int main()
{
  string s1("hello world");//从下标为5的位置开始替换,后面的后移,相当于添加进来
  s1.replace(5, 1, "%%d");
  cout << s1 << endl;
  string s2("hello world i love you");
  size_t num = 0;
  for (auto ch : s2)//范围for找出空格的个数
  {
    if (ch == ' ')
      ++num;
  }
  s2.reserve(s2.size() + 2 * num);//提前开空间,防止string自己开空间1.5倍,或者2倍开,浪费空间,+2*num是由于1换3
  size_t pos = s2.find(' ');
  while (pos != string::npos)//循环将所有的空格处用%20代替,理解为插入
  {
    s2.replace(pos, 1, "%20");
    pos = s2.find(' ', pos + 3);//因为每次找到空格的话之后,再找下一个空格,没必要从开始开始找,直接跳过%20,开始找空格
  }
  cout<< s2 << endl;
}

另一种方法代替

int main()
{
  
  string s2("hello world i love you");
  string newstr;
  size_t num = 0;
  for (auto ch : s2)//范围for找出空格的个数
  {
    if (ch == ' ')
      ++num;
  }
  newstr.reserve(s2.size() + 2 * num);//提前开空间,防止string自己开空间1.5倍,或者2倍开,浪费空间,+2*num是由于1换3
  for (auto ch : s2)
  {
    if (ch != ' ')
      newstr += ch;//不是空格直接挪
    else
      newstr += "%20";//是空格的话,用%20代替,从该位置
  }
  s2 = newstr;//然后拷回去
  
  cout << s2 << endl;
}

8.c_str()

c_str() 返回C格式字符串

int main()
{
  string s1("hello world");
  cout << s1 << endl;
  cout << s1.c_str() << endl;
}

猜一下上述代码结果是什么?

s1.c_str是char*类型,打印出来是字符串

如果强制转化为别的类型,就是首元素地址了

int main()
{
  string s1("hello world");
  cout << s1 << endl;
  cout << s1.c_str() << endl;
  cout << typeid(s1.c_str()).name()<< endl;
  cout << (void*)s1.c_str() << endl;
  cout << typeid((void*)s1.c_str()).name() << endl;
  cout << &s1 << endl;
}

这里注意一下,上面是字符串第一个字符的地址。下面的是字符串类对象的地址,字符串第一个字符的地址前面可能有size,或者capacity,的int大小

int main()
{
  string s1("hello world");
  cout << s1 << endl;
  cout << s1.c_str()<< endl;
  s1 += '\0';
  s1 += '\0';
  s1 += "xxxx";
  cout << s1 << endl;
  cout << s1.c_str() << endl;
}

c_str会遇到反斜杠0结束

假如说我要拷贝我当前写的代码,并且输出到屏幕上,应该怎么搞呢?

int main()
{
  string filename("源.cpp");
  FILE* fout = fopen(filename.c_str(), "r");
  if (fout == NULL)
    perror("fopen fail");
  char ch = fgetc(fout);
  while (ch != EOF)
  {
    cout << ch;
    ch = fgetc(fout);
  }
  fclose(fout);
}


9.substr()

substr() 在str中从pos位置开始,截取n个字符,然后将其返回

int main()//找后缀
{
  string file("string.cpp.tar.zip");
  size_t pos = file.rfind('.');//倒着找.,找到第一个,
  if (pos != string::npos)//如果找到
  {
    string suffix = file.substr(pos);//返回子串
    cout << suffix << endl;
  
  }
}

子串网址怎么取?

int  main()
{string s1("http://www.cplusplus.com/reference/string/string/find/");
  cout << s1 << endl;
  size_t start = s1.find("://");
  if (start == string::npos)
  {
    cout << "invalid s1" << endl;//找不到"://”
  }
  start += 3;//到网址的起始位置
  size_t finish = s1.find('/', start);//从网址的起始位置开始找‘/’
  string address = s1.substr(start, finish - start);//返回从start位置开始finish-start长度的字符子串
  cout << address << endl;
  return 0;
}

10.find_first_of()

find_first_of()

int main()
{
  std::string str("Please, replace the vowels in this sentence by asterisks.");
  std::size_t found = str.find_first_of("abcdv");
  while (found != std::string::npos)
  {
    str[found] = '*';
    found = str.find_first_of("abcdv", found + 1);
  }
  std::cout << str << '\n';
}

在str中找到在found里面出现的用‘*’代替.


11.swap()

swap() 交换两个字符串

int main()
{
  string s1("hello world");
    string s2("xxxxx");
    s1.swap(s2);
    cout << s1 << endl;
    cout << s2 << endl;
  
    swap(s1, s2);
    cout << s1 << endl;
    cout << s2 << endl;
}

与swap(s1, s2),都是交换,区别是什么?

string::swap会好一些.

目录
相关文章
|
27天前
|
C++ 容器
|
17天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
27天前
|
C++ 容器
|
27天前
|
C++ 容器
|
27天前
|
存储 C++ 容器
|
1月前
|
安全 C语言 C++
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
32 4
|
1月前
|
存储 编译器 程序员
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
58 2
|
1月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(三)
【C++】C++ STL 探索:String的使用与理解
|
1月前
|
存储 编译器 C++
【C++】C++ STL 探索:String的使用与理解(二)
【C++】C++ STL 探索:String的使用与理解
|
1月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(一)
【C++】C++ STL 探索:String的使用与理解