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;
}
相关文章
|
19天前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
40 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
16天前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
16 2
|
19天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
21 4
|
19天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
18 4
|
19天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
22天前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
48 4
|
26天前
|
存储 安全 Java
【一步一步了解Java系列】:认识String类
【一步一步了解Java系列】:认识String类
23 2
|
20天前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
15 0
|
24天前
|
C语言 C++
深度剖析C++string(中)
深度剖析C++string(中)
43 0
|
24天前
|
存储 编译器 程序员
深度剖析C++string(上篇)(2)
深度剖析C++string(上篇)(2)
34 0