C++利剑string类(详解)

简介: C++利剑string类(详解)

前言:大家都知道在C语言里面的有  char  类型,我接下来要讲的 string 类功能是使用 char  类型写的类,当然这个是C++官方写的,接下来我们将会学会使用它,我们会发现原来  char  这种类型是还能这么好用,授人以鱼不如授人以渔,接下来我将会讲不少干货,不仅仅是教会我们使用,还会教我们如何模拟实现一个  string  类,我也会教大家如何去读英文文档,话不多说,正文开始。



一,string  类初识

1)英文文档的查找和阅读

想要了解一个语言里面的一个语法,有什么比直接去它的官网看原汁原味的英文文档更加得劲呢?我现在贴出C++的官网cplusplus.com/reference/

打开之后我们会看到这个页面

因为新版本不具有搜索功能,为了我们方便使用,我们切回老版本,点击图上的这个

之后我们就能在上面的reserch进行搜索了

我们搜索  string  就会显示 string 类的提供的所有接口和功能,我们先看它对string类的描述,也就是第一大段

这上面明确说明了,string类是一串char字符,并且提供了一系列的接口,在下面是它的接口或者成员函数,让我们来看看,如何查看成员函数的功能,使用方法。

我们阅读英文文献不一定要全部明白意思,我们可以从上面的四个方面入手,就能大概理解功能和使用方法,碰到不会的单词我们可以用搜索引擎,但不建议使用翻译软件直接翻译,因为这样即不准确也不利用我们的成长,以后我们还会阅读不少的英文文献,只有自身硬才能笑到最后。

2)string类初步使用

string 如何创建对象

在使用时,我们首先可以把它定义的对象当作一个字符串,但是这个对象拥有很多C语言字符串没有的特性,比如不需要我们考虑它的空间够不够,我们可以使用运算符对它进行操作,也就是进行+,+=,=等操作,并且提供了许多接口帮助我们减少手搓的代码量

话不多说,我们先定义一个string 对象吧

string c1;      //里面只有一个\0
string c2("wzdhxhn");   //里面是"wzdxhn\0"
string c3=("wzdxhn");   //里面是"wzdxhn\0"
string c4=c3;           //里面也是"wzdxhn\0"
string c5(c4);          //里面还是"wzdxhn\0"
c1=c5+c4;              //里面可是"wzdxhnwzdxhn\0"哦
c5+=c5;                  //里面是"wzdxhnwzdxhn\0"哦
for(int i=0;i<7;i++)
c2[i]=6;                  //c2里面的内容将会全部变成6
cout<<c1;
cin>>c1;                    //支持输入输出流

没错,真的是一场酣畅淋漓的初始化和赋值,string都支持这种操作,并且拷贝都是深拷贝哦,如果在C里面我们就需要循坏进行赋值了,刚开始是不是就感受到了它的便利。编译器:最终还是我承受了一切。

string  成员函数的使用

前提知识:size_t npos = -1这个是C++库里面的,代表整形的最大值,如果它是默认参数,代表知道\0等结束标志结尾

1)size()

没有函数参数,这个函数可以获取string里面字符串的长度,不包括\0,然后返回

string s("hello");
cout<<s.size();      //输出s的长度5
2)  length()

功能类似size,没有参数,也是返回字符串长度,不包括\0

string s("hello");
cout<<s.length();      //输出s的长度5
3)  max_size()

 

没有参数,返回字符串里面ASCLL码值的最大值这个可以改变字符串的大小,

4)resize()

只有一个参数就是字符串调整后的大小(以\0为标注),没有返回值,可以扩大和缩小字符串长度,扩大可以指定一个参数填充,也可以选择不指定,这个参数有默认参数,但要注意这个如果指定大小小于字符串的大小则会将数据覆盖

string s("hello");
cout<<s.resize(10,'6');    //输出:hello66666
cout<<s.resize(2);         //输出:he
5)  capacity()

没有参数,返回一个无符号整形,代表调整后储存字符串空间的大小,这个会返回string类的字符串空间的容量,要注意的是,字符串空间的容量不等于长度,可能有一些空间存在,但是我们没使用,相当于多开了空间

6)    reserve()

一个参数没有返回值,这个可以重新定义容量,但是要注意只能扩大,不能缩小容量,因为编译器会做判断,如果重定义的容量小于之前的字符串空间的容量就不会进行任何操作,也不会报错。只有一个参数,就是你再次定义的容量大小

7)  clear()

没有参数,没有返回值,顾名思义,清理数据,大小和长度。,但是不会改变开的空间大小,也就是字符串空间大小

8)    at()

这个相当于下标访问,只有一个参数就是你要访问的字符串位置 ,最后返回你要的下标元素的引用

string s("hello");
s.at(2);    //等价于s[2]
9)  back() /front()

没有参数,返回最后一个字符,front()则是返回第一个字符

string s("hello");
cout<<s.back();       //输出:o
10)append()

这个有两个函数参数,一个是插入的字符串或者字符,第二个参数有默认参数0,代表插入的位置,返回类型是插入后的string类

string s("hello");
s.append(" world");
cout<<s;      //输出:hello world
11)  push_back()

这个成员函数有一个参数,没有返回值,是要注意插入的只能是字符,或者ASCLL码值而且是尾插,插入字符串请使用append

string s("nihao");
s.push_back('h');  
cout<<s;         //输出:nihaoh
10)    insert()

这个函数支持的参数很多

从图什么我们能知道,返回类型是插入后的string类或者迭代器,这个insert功能强大,不仅支持插入字符,也支持字符串,支持各种各样的插入方式,即带来了遍历也变得复杂了

13)erase

这个函数功能很明确了,就是删除字符串,没有参数,返回值是删除后的string类或者迭代器,因此不做举例了

14)c_str()

这个会将string类型 转化为C语言的char* 类型,返回类型是  const char*   ,但要注意返回类型是const类型,只读不写

15)find()/rfind

这个函数的作用就是查找字符或者字符串里面的字符,返回它在字符串里面的位置,注意找字符串的时候不是找子字符串而是找属于这个子串里面的字符

find()是从前往后找,rfind()则是从后往前找,参数差不多

string s("hello,world");
cout<<s.find('l',5);  结果是:10,返回为world里面的l,因为的从5 o开始查找的
16)substr()

这个函数的作用就是返回原字符串中的一个子字符串,返回参数为char* ,参数很简单,应该是子字符串的位置,一个是长度,如果不指定长度,默认到\0

string s("hello world");
cout<<s.substr(0,5);  //输出:hello
17)getline()

这个函数的作用就是cin的一个拓展,没有参数和没有返回值,众所周知,cin输入字符串碰到空白会暂停,但是getline要碰到\0才会停止

string s;
cin>>s;     //输入:jascsahjas jscasjbsnc,但最后只有jascsahjas成功输出了
s.getline(); //输入:jascsahjas jscasjbsnc    s里面是:jascsahjas jscasjbsnc

二,模拟实现

备注:博主想偷懒了,答应大家的模拟实现不能再给大家做详细的解释了,但我会贴出源码供大家参考,里面只有一点点的注释,如果大家有不懂的,可以评论区@我,我会给大家一一解答,大家记得先看下面的私有成员,不要直接从头看到尾哦

namespace bit     //使用命名空间,防止于库的string类型冲突
{
  class string
  {
  public:
    typedef char* iterator;      //string类的迭代器可以使用这个代替
  public:
    string(const char* str = "") //当无参数时,默认只有一个\0
    {
      _size = strlen(str);
      _capacity = _size;
      _str = new char[_capacity+1];
      strcpy(_str, str);
    }
    string(const string& s): _str(nullptr), _size(0), _capacity(0) //初始化列表
    {
      string tmp(s._str);
      this->swap(tmp);
    }
    string& operator=(const string &s)
    {
         if(this != &s)
         {
             string temp(s);
             this->swap(temp);
        }
       return *this;
    }
    ~string()
    {
      if (_str)
      {
        delete[] _str;
        _str = nullptr;
      }
    }
    //
    // iterator
    iterator begin()
    {
      return _str;      //通过地址可以判断迭代器的位置
    }
    iterator end()
    {
      return _str + _size;
    }
    /
    // modify
    void push_back(char c)
    {
      if (_size == _capacity)
      reserve(_capacity*2);
      _str[_size++] = c;
      _str[_size] = '\0';
    }
    string& operator+=(char c)
    {
      push_back(c);            //嘻嘻,复用已有的功能,偷懒
      return *this;
    }
    void append(const char* str);
    string& operator+=(const char* str);
    void clear()    //清除数据,但空间不变,尽量减少开空间的频率
    {
      _size = 0;
      _str[_size] = '\0';
    }
    void swap(string& s)
    {
      std::swap(_str, s._str);  //官方库的swap函数,又是一个偷懒小技巧
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    const char* C_Str()const     //哈哈,是不是很简单
    {
      return _str;
    }
    /
    // capacity
    size_t size()const   //有手就行
    {
      return _size;
    }
    size_t capacity()const
    {
      return _capacity;
    }
    bool empty()const
    {
      return _size == 0;
    }
    void resize(size_t newSize, char c = '\0')
    {
      if (newSize > _size)
      {
        // 如果newSize大于底层空间大小,则需要重新开辟空间
        if (newSize > _capacity)
        {
          reserve(newSize);
        }
        memset(_str + _size, c, newSize - _size); //调用C官方库,初始化,不懂,参考我以前的博客
      }
      _size = newSize;
      _str[newSize] = '\0';
    }
    void reserve(size_t newCapacity)
    {
      // 如果新容量大于旧容量,则开辟空间
      if (newCapacity > _capacity)
      {
        char* str = new char[newCapacity + 1];
        strcpy(str, _str);
        // 释放原来旧空间,然后使用新空间
        delete[] _str;
        _str = str;
        _capacity = newCapacity;
      }
    }
    /
    // access
    char& operator[](size_t index)//很容易实现吧
    {
      assert(index < _size);
      return _str[index];
    }
    const char& operator[](size_t index)const  //要记住const类型要单独处理
    {
      assert(index < _size);
      return _str[index];
    }
    /
    //relational operators
    bool operator<(const string& s)const
    {
      int res = strcmp(_str, s._str); //C官方库,以前博客有这种函数的模拟实现和讲解
      if(res < 0)
        return true;
      return false;
    }
    bool operator<=(const string& s)const
    {
       return !(*this > s);
    }
    bool operator>(const string& s)const
    {
      int res = strcmp(_str, s._str);
      if(res > 0)
        return true;
      return false;
    }
    bool operator>=(const string& s)const
    {
      return !(*this < s);
    }
    bool operator==(const string& s)const
    {
      int res = strcmp(_str, s._str);
      if(res == 0)
        return true;
      return false;
    }
    bool operator!=(const string& s)const
    {
      return !(*this == s);
    }
    // 返回c在string中第一次出现的位置
    size_t find (char c, size_t pos = 0) const   //只读不改,最好const,同时支持非const类型
    {
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == c)
return i;//找到,返回下标
}
return -1;//未找到
}
    // 返回子串s在string中第一次出现的位置
    size_t find (const char* s, size_t pos = 0) const
    {
      assert(s);          //断言,如果条件为假就报错,结束程序并报错
assert(pos < _size);
const char* src = _str + pos;
while (*src)
{
const char* match = s;//如果不匹配,返回子串起始处重新查找
const char *cur = src;
while (*match && *match==*cur)//结束条件
{
++match;
++cur;
}
if (*match == '\0')//找到子串
{
return src - _str;//返回下标
}
else
{
++src;
}
}
return -1;//未找到
    }
    // 在pos位置上插入字符c/字符串str,并返回该字符的位置
    string& insert(size_t pos, char c)
    {
      assert(pos <= _size);
if (_size > _capacity)
{
//扩容
char *newstr = new char[_capacity * 2 + 1];//开空间
strcpy(newstr, _str);
delete[] _str;
_str = newstr;
_capacity *= 2;
//Expand(_capacity * 2);
}
//移数据
for (int i = _size; i >= (int)pos; --i)
{
_str[i + 1] = _str[i];
}
_str[pos] = c;
_size++;
return *this;
    }
    string& insert(size_t pos, const char* str)
    {
      size_t len = strlen(str);
if (_size + len > _capacity)//扩容
{
//扩容
char *newstr = new char[_capacity * 2 + 1];//开空间
strcpy(newstr, _str);
delete[] _str;
_str = newstr;
_capacity *= 2;
//Expand(_size + len);
}
//后移数据
for (int i = _size; i >= (int)pos; --i)
{
_str[len + i] = _str[i];
}
//拷贝字符串
while (*str != '\0')
{
_str[pos++] = *str++;
}
_size += len;
return *this;
    }
    // 删除pos位置上的元素,并返回该元素的下一个位置
    string& erase(size_t pos, size_t len)
    {
      assert(pos < _size);
if (pos + len >= _size)//pos位置之后全为0
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
    }
  private:
    friend ostream& operator<<(ostream& _cout, const bit::string& s);
    friend istream& operator>>(istream& _cin, bit::string& s);
  private:
    char* _str;
    size_t _capacity;
    size_t _size;
  };
};
//输入流重载
istream& bit::operator>>(istream& _cin, bit::string& s)
{
  //预分配100个空间
  char *str = (char *)malloc(sizeof(char)*100);
  char *buf = str;        //buf可以防止频繁开空间
  int i = 1;
  //预处理:跳过流里面的所有空格和回车
  while ((*buf = getchar()) == ' ' || (*buf == '\n'));
  
  for ( ; ; ++i)
  {
    if (*buf == '\n') //回车跳出
    {
      *buf = '\0';
      break;
    }
    else if (*buf == ' ') //空格跳出
    {
      *buf = '\0';
      break;
    }
    else if (i % 100 == 0) //空间不足
    {
      i += 100; //追加100个空间
      str = (char *)realloc(str,i);
    }
    else  //每次getchar()一个值
    {
      buf = (str+i);//为了避免realloc返回首地址改变,不使用++buf,而是用str加上偏移.
      //每次读取一个字符
      *buf = getchar();
    }
  }
  //输入完成,更新s
s._str = str;
s._capacity = s._size = i;
  
  return _cin;
}
//输出流重载
ostream& bit::operator<<(ostream& _cout, const bit::string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
_cout << s[i];
}
return _cout;
}
相关文章
|
8天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
48 18
|
9天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
34 13
|
9天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
29 5
|
9天前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
21 5
|
9天前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
27 4
|
9天前
|
设计模式 IDE 编译器
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如&lt;string&gt;、&lt;cstdlib&gt;等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
25 3
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
98 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
70 2
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
127 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
137 4