栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【初识C++】4.1 STL-string

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【初识C++】4.1 STL-string

文章目录
    • 1.标准库中的string类
      • 1.1打开string文档
      • 1.2string重要接口
        • 1.2.1string构造函数
        • 1.2.2string的遍历方式
        • 1.2.3string的增删查改
        • 1.2.4string其他重要接口
    • 2.string类的模拟实现
      • 2.1模拟须知:
      • 2.2Makefile的编写
      • 2.3string类成员变量
      • 2.4string类成员函数
      • 2.4.1默认成员函数
      • 2.4.2string类的遍历
      • 2.4.3string类的扩容
    • 3.string类增删查改
      • 3.1string类(增)
      • 3.2string类 (删)
      • 3.3string类 (查)
      • 3.4string类逻辑操作的重载
    • 4.my_string 源码
      • 4.1my_string.h
      • 4.2my_string.cpp
      • 4.3test.cpp
    • [小结]:

1.标准库中的string类 1.1打开string文档

首先打开我们的C++文档


点击Reference,这是了C/C++库函数的参考手册,进入后往下拉

这个文档是通过头文件分类的,我们查找的 < string > 在最底部

我们要学习的是其中的 类的实例化:string类
string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类

string类为什么需要模板?
因为我们常见的string是一个个字符(char)拼接而成,但是字符并非仅仅限于char还有unsigned char,wstring中的宽字符(wchar_t char),中文字符,日文字符等。这些字符串功能,特征类似,所以我们创建模板类来适应各式各样的字符

我们最常用的Ascall(阿斯克码表)对应的是英文字符,中文字符也有对应的编码表(gbk),一个中文字符占两个字节。世界上还有各种各样的语言,各种语言使用的编码表也不同,有utf-8,utf-16,> utf-32等,这里不展开细说。

1.2string重要接口 1.2.1string构造函数


接口函数的声明

接口函数的功能,及功能和返回值的说明

接口函数参数解释说明

接口函数使用实例

文档下面有每一个函数的使用用例,可以查看用例结合文档描述来推导出函数的使用方法
我们重点学习(1)(2)(4)接口

(1)
string s1;
(2)
string s3(s2);
(4)
string s2("hello world"); 等价于 string s2 = "hello world"
1.2.2string的遍历方式

1.下标+[ ]

#include 
#include 
using namespace std;

int main()
{
  string s1("hello world");
  for (size_t i = 0; i < s1.size(); i++)
  {
    cout << s1[i] << " ";
  }
  cout << endl;
  return 0;
} 

使用方法一遍历越界会直接报错,因为方法一的底层实现运用了断言

2.迭代器
所有容器都可以用迭代器遍历

#include 
#include 
using namespace std;
//1.C++迭代器的给的区间大部分都是左闭右开的区间
//[begin(), end()),end()为最后一个函数再后面一个
//2。迭代器是一个像指针一样的东西,因为它的用法就是在模拟指针
int main()
{
  string s1("hello world");
  string::iterator it = s1.begin();
  while (it != s1.end())    //必须要使用!=
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  return 0;
}

反向迭代器,便于我们呢反向遍历
    string s2("123456");
    string::reverse_iterator rit = s2.rbegin();
    while (rit != s2.rend())
    {
      cout <<*rit << " ";
      rit++;
    }


C++还有针对const对象的const类型的迭代器
普通的迭代器可以对数据结构进行读写,但是对于const保护的对象来说,使用迭代器权限放大了无法调用

//const迭代器
  const string s3("hello world");
  string::const_iterator it = s3.begin();
  while (it != s3.end())    
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
在使用const迭代器时,我们可以使用cbegin()和cend(),这样更规范
当然不用问题也不大

3.范围for

    string s4("hello world");
    for (auto e : s3)      //依次取容器中的数据赋值给e
    {
        cout << e << " ";
    }
    cout << endl;
    
1.2.3string的增删查改

1.增
push_back:在尾部插入字符

append:在尾部插入字符,字符串等

operator+=:最常用的方法,可以在尾插字符,字符串

(不推荐)insert:在任意位置插入字符或字符串,1,3条比较常用,2有时候会用到

删:

(不推荐)erase:从pos位置处删除len个字符,全缺省,如果全没写就全删,缺一个则保留pos位置前的内容

clear:清空字符串里面的数据,使其变成空字符串,但是容量和原字符串相同

我们可以通过小程序来验证clear前后容量是否变化
#include 
#include 
#include 
using namespace std;
int main()
{
	string s1 = "hello hello hello hello world";
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.clear();
	cout << s1.capacity() << endl;
	cout << s1.size() << endl;
}

查:

size(lenth):查看有效字符长度,不包括’‘,lenth是STL出现之前的接口,后为了容器统一性建议使用size

capacity:有效字符的容量,空字符串也有容量,操作系统给了16个字节’‘占了一个输出是15

empty:查看是否为空字符串,若为空返回真,若非空返回假

resize:改变对象的size,若对象不足size,在对象中默认插入’’,也可以后面加入字符,将字符串填充至size
我们可以使用resize初始化string和其他数据结构

reserse:改变容量,当我们向一个数据结构不断插入数据,它就会不断的增容,每一次增容都要开辟一个新空间,然后将数据拷贝过去,效率比较低。我们可以通过reserse手动增容,只增容一次提高效率

1.2.4string其他重要接口

**1.c_str:**返回字符串

c_str:使用场景

在实际使用中。比如strlenz()这样的函数括号内只能接字符串的头指针
我们可以通过c_str将string以char*的形式返回
#include 
#include 
#include 
using namespace std;

int main()
{
  string str1 = "hello world";

  str1.resize(20);
  str1 += "!!!";

  cout << strlen(str1.c_str()) << endl;

  return 0;
}

c_str和string的不同:返回生成的字符串和原来的string有些许不同

#include 
#include 
#include 
using namespace std;

int main()
{
  string str1 = "hello world";

  str1.resize(20);   //改变size
  str1 += "!!!";     //在尾部输入三个!!!
  cout << "str1=" <<  str1 << endl;
  cout << "str1.c_str1="<< str1.c_str()<< endl;

  return 0;

//输出
str1=hello world!!!
str1.c_str1=hello world

c_str返回的是字符串的头节点,和C中相似,遇到’‘就停止
但是string类直接输出会将size个有效字符全部打出(在g++编译器中’‘为空而非空格,在VS中’‘打印在屏幕上是一个空格)

结论:尽量使用cout<< str << endl;直接输出,使用c_str可能会输出不完整

2. find:查找字符串内字符或字符串,返回第一次出现时的索引值,若没找到返回-1

目标:打印出文件名后缀

  string filename = "mytest.cpp";
  size_t pos = filename.find('.');
  if (pos != string::npos)
  {
    string  suff(filename, pos, filename.size() - pos);//创建端详suff保存后缀
    cout << suff << endl;
  }
  cout << string::npos << endl;
  printf("%d", string::npos);

string::npos:string类中的一个全局变量,类型时usinged int,数值是18446744073709551615(最大的整形),但是用int输出就是-1

3.substr:从pos位置向后取len个字符,返回一个string类的对象

4.rfind:从末尾开始find,其余和find相似

还有string类非成员函数:operator+,operator>>, operator<<,getline(获取一行字符串),relational_operators(大小比较)都比较重要,但根据其函数名我们基本就可以理解它的用法这里就不再赘述,在后面的模拟实现会一一讲到。

2.string类的模拟实现

因为模拟实现使用的是声明,定义,测试分离的模式,我会将string类的整体实现代码贴在博客的结尾,便于找到声明和定义的位置

2.1模拟须知:

在不同编译器中,使用的标准库也有所不同,当我们使用相同的代码放在不同的编译器下进行编译。发现输出的结果是不一样的。显然这两个string类的成员变量有所不同。所以接下来进行的模拟实现并非完全按照STL库编写的代码实现。而是运用已有知识在完成C++设定语法的前提下,模拟STL库的实现。主要目的是为了理解重要成员函数的底层实现和特性,便于更好的使用。

2.2Makefile的编写

首先按照惯例新建四个文件,然后编写Makefile文件。Makefile的编写和之前基本一致,但是我们接下来使用的语法可能会有C++11的语法,所以要在最后加上-std=c++11参数

[clx@VM-20-6-centos my_string]$ ll
total 16
-rw-rw-r-- 1 clx clx 183 Jul 26 22:34 Makefile
-rw-rw-r-- 1 clx clx 942 Jul 26 23:48 my_string.cpp
-rw-rw-r-- 1 clx clx 593 Jul 26 23:48 my_string.h
-rw-rw-r-- 1 clx clx 204 Jul 26 23:42 test.cpp
[clx@VM-20-6-centos my_string]$ cat Makefile
mytest: my_string.o test.o
		g++ -o $@ $^ -std=c++11
my_string.o:my_string.cpp
		g++ -c $< -std=c++11
test.o:test.cpp
		g++ -c $< -std=c++11
.PHONY:clean
clean:
		rm -f ./*.o mytest


2.3string类成员变量

为了方便我们增容和插入字符,string类一定有_size和_capacity的成员变量,但是为什么在g++编译器下s1的大小只有八字节呢。八个字节仅仅够64位机器下存储一个指针。显然在g++对string类对象所占空间进行了划分。比如将前两个字节给了_size,后两个字节给了_capacity,还有四个字节存储_str的指针。我们就按照这个思路进行string类的构建
在不少成员函数中,都用到了npos这个string类中的静态成员函数,我们也应该将npos加上

[clx@VM-20-6-centos my_string]$ cat my_string.h
#pragma once 
#include 
#include 
#include 
using namespace std;

namespace clx
{
  class string 
  {
    public:
    private:
      char* _str;
      size_t _size;
      size_t _capacity;
      static size_t npos;
  };
}

2.4string类成员函数 2.4.1默认成员函数

. 首先肯定是四个默认成员函数,模拟实现和现实更接近,还应编写一个函数my_swap()

#pragma once 
#include 
#include 
#include 
using namespace std;

namespace clx
{
  class string 
  {
    public:
      void my_swap(string& s);
      //Four default function
      string(const char* s = "");   //default constructs function
      string(const string& s); //copy constructs function
      string& operator=(string s); //Assignment operator overload
      ~string();  //destructor function

      char* c_str();

    private:
      char* _str;
      size_t _size;
      size_t _capacity;
      static size_t npos;
  };
  void string_test1();
  
}

1.my_swap函数的模拟实现

void clx::string::my_swap(string& s)
{
  ::swap(_str, s._str);
  ::swap(_size, s._size);
  ::swap(_capacity , s._capacity);
}

my_swap函数的主要作用就是将两个string类型进行交换,而实现交换等同于交换它们的成员变量。::swap表示调用的是全局的swap函数

2.默认构造函数的实现

clx::string::string(const char* s)
  :_str(new char[strlen(s) + 1])
  ,_size(0)
  ,_capacity(0)
{
  strcpy(_str, s);
  _size = strlen(s);
  _capacity = _size;
}

默认构造函数默认初始化成空字符串,在实际开空间时应该多留一个空间给’’

3.拷贝构造函数的实现

clx::string::string(const string& s)
  :_str(nullptr)
   ,_size(0)
   ,_capacity(0)
{
  string tmp(s._str);
  my_swap(tmp);
}

首先还是先使用初始化列表对其进行初始化,然后先通过默认构造函数创建一个s的拷贝类对象,然后将tmp对象和新建的类对象进行交换。就得到了和s相同的类对象

4.赋值运算符重载

clx::string& clx::string::operator=(string s)
{
  my_swap(s);
 return *this;
}

首先注意函数的形参类型是string,形参可以修改并且是实参的一份拷贝。我们就可以让this和其交换,这样原本this的成员函数就到了拷贝出来的s对象中。函数结束s的生命周期结束,s被析构。但是假如自己给自己赋值时会出现_str的地址会发生变化,但是实际应用中,自己给自己赋值没有什么意义,所以对此函数的实用性并不造成很大影响

5.析构函数

clx::string::~string()
{
  delete[] _str;
  _str = nullptr;
  _size = _capacity = 0;
}

清理_str在内存中的空间,并将其制空

浅拷贝和深拷贝:
首先我们构建一个简单的类,此时我们并没有写拷贝函数,系统会默认生成拷贝函数,当我们运行该程序时发现系统报错了,这是系统生成的拷贝构造函数只是单纯的浅拷贝
浅拷贝:它的功能就是将实参的全部成员变量赋值给新生成的成员函数的成员变量,这样两个类对象的_str全部指向相同空间,对一个类对象进行操作也就会影响另外一个类对象。并且在调用析构函数时,系统不能释放已经释放过的空间,所以就出现了系统报错。
深拷贝:重新开辟一块空间进行拷贝,两块空间没有关联

namespace clx
{
	class string
	{
	public:
		string(const char* s)
			:_str(new char[strlen(s) + 1])
		{
			strcpy(_str, s);
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
	};
}

int main()
{
	clx::string s1("hello world");
	clx::string s2(s1);
	return 0;
}


写完了五个函数,设计一个小实验来测试函数的性能
为了方便测试我们在再写一个c_str()函数便于查看结果

6.c_str()函数

char* clx::string::c_str()
{
  return _str;
}

返回string类指向的首个字符串

测试函数(需要再clx命名空间中声明一下)

 void clx::string_test1()
  {
    string str1("hello world");
    string str2(str1);
    string str3;
    str3 = str1;
    cout << str1.c_str() << endl;
    cout << str2.c_str() << endl;
    cout << str3.c_str() << endl; 
  }

最后在test.cpp中完成测试

[clx@VM-20-6-centos my_string]$ cat test.cpp
#include "my_string.h"
int main()
{
  clx::string_test1();
  return 0;
  
}

-rw-rw-r-- 1 clx clx 183 Jul 26 22:34 Makefile
-rw-rw-r-- 1 clx clx 942 Jul 26 23:48 my_string.cpp
-rw-rw-r-- 1 clx clx 593 Jul 27 09:09 my_string.h
-rw-rw-r-- 1 clx clx 204 Jul 26 23:42 test.cpp
[clx@VM-20-6-centos my_string]$ make
g++ -c my_string.cpp -std=c++11
g++ -c test.cpp -std=c++11
g++ -o mytest my_string.o test.o -std=c++11
[clx@VM-20-6-centos my_string]$ ./mytest
hello world
hello world
hello world
2.4.2string类的遍历

在string类的介绍中我们说有三种方法遍历string,第一种是最方便的operator[] ,第二种就是实现迭代器,第三种是最新出现的范围for(类似于迭代器)。接下来进行逐一实现.
因为考虑到之后的对象可能被const修饰,所以接下来的函数会提供两份,一份供const的调用,另一份供非const调用
1.size()

//定义
size_t clx::string::size() const 
{
  return _size;
}
size_t clx::string::size() 
{
  return _size;
}

返回对象的_size,因为下面有函数需要使用,所以先写在前面

2.operator[ ]

char& clx::string::operator[](size_t pos)
{
  return _str[pos];
}
char clx::string::operator[](size_t pos) const
{
  return _str[pos];
}

通过重载拿到字符,可以遍历也可以修改

3.迭代器

//注意这一块是卸载my_string.h中的,因为.h很长就将这部分置顶,截取出来
namespace clx
{
  class string 
  {
    public:
      typedef char* iterator;
      typedef const char* const_iterator;
      iterator& begin() {
        return _str;
      }
      iterator end() {
        return _str + _size;
      }
      const_iterator begin() const  {
        return _str;
      }
      const_iterator end() const  {
        return _str + _size;
      }

通过类型的重命名和两个成员函数,拿到_str的起始地址和结尾地址,从而进行遍历.反向迭代器原理比较复杂,这里就先不写了

4.范围for

for (auto ch : str3)
  {
    cout << ch << " ";
  }
  cout << endl;

编译器会自动将范围for转换为迭代器,只要写了迭代器范围for就可以使用,但迭代器一定要是标准的迭代器,如果你将成员函数begin变成Begin,那么编译器就找不到对应的函数,范围for就无法使用。
auto表示自动识别类型,假如你知道类型替换auto也可以

以上即是string类遍历的三种方式,我们也进行一个小测试测试一下三种方法

void clx::string_test2()
{
  string str1("hello world");
  string str2("hello Linux");
  string str3("hello Centos");
  for ( size_t i = 0; i < str1.size(); i++)
  {
    cout << str1[i] << " " ;
  }
  cout << endl;
  string::iterator it = str2.begin();
  while (it != str2.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  for (auto ch : str3)
  {
    cout << ch << " ";
  }
  cout << endl;
}
//test.cpp
[clx@VM-20-6-centos my_string]$ cat test.cpp
#include "my_string.h"
int main()
{
  //clx::string_test1();
  clx::string_test2();
  return 0;
  
}

编译运行我们的代码

[clx@VM-20-6-centos my_string]$ make
g++ -c my_string.cpp -std=c++11
g++ -c test.cpp -std=c++11
g++ -o mytest my_string.o test.o -std=c++11
[clx@VM-20-6-centos my_string]$ ./mytest
h e l l o   w o r l d 
h e l l o   L i n u x 
h e l l o   C e n t o s 

2.4.3string类的扩容

string类的扩容有两种方法,resize()和 reserve,resize比reserve多了一个对多余部分进行填充的功能

1.reserve()

size_t clx::string::reserve(size_t n)
{
  if ( n > _capacity )
  {
    char* tmp = new char[n + 1];
    strcpy(tmp, _str);
    delete[] _str;
    _str = tmp;
    _capacity = n;
    return 1;
  }
  else if(n < _capacity)
  {
    char* tmp = new char[n+1];
    strncpy(tmp, _str, n);
    delete[] _str;
    _str = tmp;
    _str[n] = '';
    _capacity = n;
    _size = n;
    return 1;

  }
  else if(n == _capacity)
  {
    return 1;
  }
  return 0;
  
}

这个第一次写有点冗长了,分支中有蛮多东西是可以合并的。思想就是n假如变化了就再开一块空间把原字符串拷过去,n的大小决定拷贝过去字符的数量。

2.resize()

size_t clx::string::resize(size_t n, char ch)
{
  if (n > _size)
  {
    if(n > _capacity)//如果n大于容量就需要扩容
    {
      this->reserve(n);
    }
    memset(_str + _size, ch, n - _size);//填充字符
    _str[n] = '';
    _size = n;
    return 1;

  }
  else 
  {
    _size = n;
    _str[n] = '';
    return 1;
  }
  return 0;
}

改变size并且填充字符,最后不要忘记加上’‘

还是老规矩测试一下

void clx::string_test3()
{
  string str1("hello world");
  str1.resize(20, 'x');
  cout << str1.reserve(30) << endl; 
  cout << str1.c_str() << endl;
}

[clx@VM-20-6-centos my_string]$ make
g++ -c my_string.cpp -std=c++11
g++ -c test.cpp -std=c++11
g++ -o mytest my_string.o test.o -std=c++11
[clx@VM-20-6-centos my_string]$ ./mytest
1
hello worldxxxxxxxxx

3.string类增删查改 3.1string类(增)

1.push_back()

clx::string& clx::string::push_back(char ch)
{
  if (_size == _capacity) 
  {
    reserve(2 * _capacity);
  }
  _str[_size] = ch;
  _size++;
  return *this;
}

尾插一个字符

2.append

clx::string& clx::string::append(const char* s)
{
  size_t len = strlen(s);
  if (_capacity < len + _size)
  {
    reserve(len + _size);
  }
  strncpy(_str+_size, s, len);
  _size += len;
  _str[_size] = '';
  return *this;
}

尾插一个字符串

3.insert

clx::string& clx::string::insert(size_t pos, char ch)
{
   if (_capacity == _size)
   {
     reserve(2 * _capacity);
   }
   char* end = _str + _size;
   while (end != (_str + pos))
   {
     *(end + 1) = *(end);
     end--;
   }
   *(_str + pos + 1) = *(_str + pos);
   *(_str + pos) = ch;
   _size++;
   return *this;
}

clx::string& clx::string::insert(size_t pos, const char* s)
{
  size_t len = strlen(s);
  if (_capacity < len + _size)
  {
    reserve(len + _size);
  }
  char* end = _str + _size + 1;
  while (end != (_str + pos))
  {
    *(end - 1 + len) = *(end -1);
    end--;
  }
  strncpy(_str + pos, s, len);
  _size = len + _size;
  return *this;

}

在尾部插入一个字符/字符串。因为这里频繁的移动数据,并且这个算法的时间结构是
n^2,效率非常低尽量少用。可以采取新建一个字符串然后拷贝进去,这样效率会高很多

3.operator+=

clx::string& clx::string::operator+=(char ch)
{
  push_back(ch);
  return  *this;
}

clx::string& clx::string::operator+=(const char* s)
{
  append(s);
  return *this;
}

直接复用push_back 和 append

小测试

void clx::string_test4()
{
  string str1("hello ");
  str1.push_back('w');
  cout << str1.c_str() << endl;
  str1.append("orld");
  cout << str1.c_str() << endl;
  str1.insert(0, "clx");
  cout << str1.c_str() << endl;
  str1.insert(str1.size(), 'c');
  cout << str1.c_str() << endl;
  str1 += '4';
  cout << str1.c_str() << endl;
  str1 += "Centos";
  cout << str1.c_str() << endl;
 
}

[clx@VM-20-6-centos my_string]$ make
g++ -c my_string.cpp -std=c++11
g++ -c test.cpp -std=c++11
g++ -o mytest my_string.o test.o -std=c++11
[clx@VM-20-6-centos my_string]$ ./mytest
hello w
hello world
clxhello world
clxhello worldc
clxhello worldc4
clxhello worldc4Centos

小结:这一块比较简单,和顺序表的插入相似,但是要注意这一块使用的整形为size_t,要注意size_t 类型中 -1会变成最大整形。移动数据时需要小心

3.2string类 (删)

1.erase

clx::string& clx::string::erase(size_t pos, size_t len)
{
  assert(pos < _size);//检验pos合法性
  if (len == npos || pos + len >= _size)
  {
    _str[pos] = '';
    _size = pos;
    return *this;
  }
  size_t begin = pos + len;
  while (begin <= _size)
  {
    _str[begin - len] = _str[begin];
    begin++;
  }
  _size = pos;
  return *this;
}

需要判断len受否过大

2.clear

clx::string& clx::string::clear()
{
  erase();
  return *this;
}

清空字符串,但是字符串的容量不变

小测试

void clx::string_test5()
{
  string str1("hello world");
  str1.erase(0, 2);
  cout << str1.c_str() << endl;
  str1.erase(str1.size() - 1, 3);
  cout << str1.c_str() << endl;
  str1.clear();
  cout << str1.c_str() << endl;

}

g++ -o mytest my_string.o test.o -std=c++11
[clx@VM-20-6-centos my_string]$ ./mytest
llo world
llo world

[clx@VM-20-6-centos my_string]$ 

3.3string类 (查)

find:

size_t clx::string::find(char ch, size_t pos) const 
{
  for (size_t i = pos; i < _size; i++)
  {
    if (ch == _str[i])
    {
      return i;
    }
  }
  return npos;
}

size_t clx::string::find(const char* s, size_t pos) const 
{
  if (strstr(_str + pos, s) == nullptr)
  {
    return npos;
  }
  else 
  {
    return strstr(_str + pos, s) - _str;
  }
}

rfind:

size_t clx::string::rfind(char ch, size_t pos) const 
{
  for (size_t end = _size -1; end >= pos && end != npos; end--)
  {
    if (_str[end] == ch)
    {
      return end;
    }
  }
  return npos;
}
size_t clx::string::rfind(const char* s, size_t pos) const 
{
  size_t len = strlen(s);
  size_t end = 0;
  for (end = _size -1 - len; end >= pos && end != npos; end--)
  {
    if(_str[end] == *s)
    {
      size_t cur = 0;
      for (cur = 0; cur < len; cur++)
      {
        if (_str[end + cur] != *(s + cur))
        {
          break;
        }
      }
      if (cur == len)
      {
        return end;
      }
    }
  }
  return npos;
}

和写strstr差不多,就是要从右往左遍历首字符,我们也可以将string对象拷贝一份进行逆置操作,再将s也逆置一下就可以调用我们的C函数strstr实现此功能。

小测试:

void clx::string_test6()
{
  string str1("hello world");
  cout << str1.find('o') << endl;
  cout << str1.find("llo") << endl;
  cout << str1.find('v') << endl;
  cout << str1.rfind('h') << endl;
  cout << str1.rfind("he") << endl;
}

[clx@VM-20-6-centos my_string]$ make
g++ -c my_string.cpp -std=c++11
g++ -c test.cpp -std=c++11
g++ -o mytest my_string.o test.o -std=c++11
[clx@VM-20-6-centos my_string]$ ./mytest
4
2
18446744073709551615
0
0
3.4string类逻辑操作的重载

一:逻辑运算符的重载

bool clx::operator==(const string str1, const string str2)
{
  return !strcmp(str1.c_str(), str2.c_str());
}

bool clx::operator!=(const string str1, const string str2)
{
  return strcmp(str1.c_str(), str2.c_str());
}

bool clx::operator>=(const string str1, const string str2)
{
  return (str1 == str2 || str1 > str2); 
}
bool clx::operator<=(const string str1, const string str2)
{
  return (str1 == str2 || str1 < str2); 
}
bool clx::operator>(const string str1, const string str2)
{
  return strcmp(str1.c_str(), str2.c_str());
}
bool clx::operator<(const string str1, const string str2)
{
  return strcmp(str1.c_str(), str2.c_str());
}


这里复用了strcmp函数,不熟悉可以看一下

二:输入输出运算符重载

ostream& clx::operator<<(ostream& out, const string str)
{
  for (auto ch : str)
  {
    out << ch << " ";
  }
  out << endl;
  return out;
}

istream& clx::operator>>(istream& in , string& str)
{
  str.clear();
  char ch;
  ch = in.get();
  while( ch !=' ' && ch != 'n')
  {
   str += ch; 
    ch = in.get();
  }
  return in;
}

在输入操作符重载时,应该使用in.get()函数获取字符,单纯的cin函数不会接受空格和换行符,将其看作间隔符。cin.get()函数会将任何输入的字符都获取进来

getline:

istream& clx::operator>>(istream& in , string& str)
{
  str.clear();
  char ch;
  ch = in.get();
  while(ch != 'n')
  {
   str += ch; 
    ch = in.get();
  }
  return in;
}

getLine函数和operator>>函数的实现几乎一摸一样,只要将operator>>函数中的while循环语句中ch != ’ '取消掉

小测试:

void clx::string_test7()
{
  string str1("hello world");
  string str2("hello Linux");
  string str3 = str1;
  string str4 = "hello Centos";
  string str5;
  string str6;
  clx::getline(cin, str6);
  cin >> str5;

  cout << (str6==str1) << endl;
  cout << (str5==str1) << endl;
  cout << (str1==str3) << endl;
  cout << (str4>=str3) << endl;
  cout << (str1!=str2) << endl;
  cout << str1;
  cout << str3;
}

[clx@VM-20-6-centos my_string]$ ./mytest
hello world
hello
1
0
1
1
1
h e l l o   w o r l d 
h e l l o   w o r l d 

4.my_string 源码 4.1my_string.h
//my_string.h
#pragma once 
#include 
#include 
#include 
using namespace std;

namespace clx
{
  class string 
  {
    public:
      //lterator
      typedef char* iterator;
      typedef const char* const_iterator;
      iterator& begin() {
        return _str;
      }
      iterator end() {
        return _str + _size;
      }
      const_iterator begin() const  {
        return _str;
      }
      const_iterator end() const  {
        return _str + _size;
      }
      void my_swap(string& s);
      //Four default function
      string(const char* s = "");   //default constructs function
      string(const string& s); //copy constructs function
      string& operator=(string s); //Assignment operator overload
      ~string();  //destructor function

      //[] operator overload
      char& operator[](size_t pos);
      char operator[](size_t pos) const;
      //out put size
      size_t size() const;
      size_t size();
      //out put string
      char* c_str();
      char* c_str() const;
      //chang capacity success return 1 fail return 0
      size_t reserve(size_t n);
      //resizes the string to a length of n characters
      size_t resize(size_t n, char ch = '');

      //string increase
      string& push_back(char ch);
      string& append(const char* s);
      string& insert(size_t pos, const char* s);
      string& insert(size_t pos, char ch);
      string& operator+=(const char* s);
      string& operator+=(char ch);
      //string delete 
      string& erase(size_t pos = 0, size_t len = npos);
      string& clear();
      //find char or string 
      size_t find(char ch, size_t pos = 0) const;
      size_t find(const char* s, size_t pos = 0) const ;
      size_t rfind(char ch, size_t pos = 0) const;
      size_t rfind(const char* s, size_t pos = 0) const ;
    private:
      char* _str;
      size_t _size;
      size_t _capacity;
      static size_t npos;
  };
  //Logical operator overloading
  bool operator==(const string str1, const string str2);
  bool operator!=(const string str1, const string str2);
  bool operator>=(const string str1, const string str2);
  bool operator<=(const string str1, const string str2);
  bool operator>(const string str1, const string str2);
  bool operator<(const string str1, const string str2);
  //io operator overloading
  ostream& operator<<(ostream& out, const string str);
  istream& operator>>(istream& in , string& str);
  istream& getline(istream& in , string& str);
  void string_test1();
  void string_test2(); 
  void string_test3();
  void string_test4();
  void string_test5();
  void string_test6();
  void string_test7();
}

4.2my_string.cpp
#include "my_string.h"
size_t clx::string::npos = -1;

void clx::string::my_swap(string& s)
{
  ::swap(_str, s._str);
  ::swap(_size, s._size);
  ::swap(_capacity , s._capacity);
}

clx::string::string(const char* s)
  :_str(new char[strlen(s) + 1])
  ,_size(0)
  ,_capacity(0)
{
  strcpy(_str, s);
  _size = strlen(s);
  _capacity = _size;
}

clx::string::string(const string& s)
  :_str(nullptr)
   ,_size(0)
   ,_capacity(0)
{
  string tmp(s._str);
  my_swap(tmp);
}

clx::string& clx::string::operator=(string s)
{
  my_swap(s);
 return *this;
}

clx::string::~string()
{
  delete[] _str;
  _str = nullptr;
  _size = _capacity = 0;
}

  void clx::string_test1()
  {
    string str1("hello world");
    string str2(str1);
    string str3;
    str3 = str1;
    cout << str1.c_str() << endl;
    cout << str2.c_str() << endl;
    cout << str3.c_str() << endl; 
  }

char* clx::string::c_str()
{
  return _str;
}

char* clx::string::c_str() const 
{
  return _str;
}

char& clx::string::operator[](size_t pos)
{
  return _str[pos];
}

char clx::string::operator[](size_t pos) const
{
  return _str[pos];
}

size_t clx::string::size() const 
{
  return _size;
}
size_t clx::string::size() 
{
  return _size;
}

void clx::string_test2()
{
  string str1("hello world");
  string str2("hello Linux");
  string str3("hello Centos");
  for ( size_t i = 0; i < str1.size(); i++)
  {
    cout << str1[i] << " " ;
  }
  cout << endl;
  string::iterator it = str2.begin();
  while (it != str2.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  for (auto ch : str3)
  {
    cout << ch << " ";
  }
  cout << endl;
}


size_t clx::string::reserve(size_t n)
{
  if ( n > _capacity )
  {
    char* tmp = new char[n + 1];
    strcpy(tmp, _str);
    delete[] _str;
    _str = tmp;
    _capacity = n;
    return 1;
  }
  else if(n < _capacity)
  {
    char* tmp = new char[n+1];
    strncpy(tmp, _str, n);
    delete[] _str;
    _str = tmp;
    _str[n] = '';
    _capacity = n;
    _size = n;
    return 1;

  }
  else if(n == _capacity)
  {
    return 1;
  }
  return 0;
  
}

size_t clx::string::resize(size_t n, char ch)
{
  if (n > _size)
  {
    if(n > _capacity)
    {
      this->reserve(n);
    }
    memset(_str + _size, ch, n - _size);
    _str[n] = '';
    _size = n;
    return 1;

  }
  else 
  {
    _size = n;
    _str[n] = '';
    return 1;
  }
  return 0;
}

void clx::string_test3()
{
  string str1("hello world");
  str1.resize(20, 'x');
  cout << str1.reserve(30) << endl; 
  cout << str1.c_str() << endl;
}

clx::string& clx::string::push_back(char ch)
{
  if (_size == _capacity)
  {
    reserve(2 * _capacity);
  }
  _str[_size] = ch;
  _size++;
  return *this;
}

clx::string& clx::string::append(const char* s)
{
  size_t len = strlen(s);
  if (_capacity < len + _size)
  {
    reserve(len + _size);
  }
  strncpy(_str+_size, s, len);
  _size += len;
  _str[_size] = '';
  return *this;
}

clx::string& clx::string::insert(size_t pos, char ch)
{
   if (_capacity == _size)
   {
     reserve(2 * _capacity);
   }
   char* end = _str + _size;
   while (end != (_str + pos))
   {
     *(end + 1) = *(end);
     end--;
   }
   *(_str + pos + 1) = *(_str + pos);
   *(_str + pos) = ch;
   _size++;
   return *this;
}

clx::string& clx::string::insert(size_t pos, const char* s)
{
  size_t len = strlen(s);
  if (_capacity < len + _size)
  {
    reserve(len + _size + 1);
  }
  char* end = _str + _size + 1;
  while (end != (_str + pos))
  {
    *(end - 1 + len) = *(end -1);
    end--;
  }
  strncpy(_str + pos, s, len);
  _size = len + _size;
  return *this;

}

clx::string& clx::string::operator+=(char ch)
{
  push_back(ch);
  return  *this;
}

clx::string& clx::string::operator+=(const char* s)
{
  append(s);
  return *this;
}

void clx::string_test4()
{
  string str1("hello ");
  str1.push_back('w');
  cout << str1.c_str() << endl;
  str1.append("orld");
  cout << str1.c_str() << endl;
  str1.insert(0, "clx");
  cout << str1.c_str() << endl;
  str1.insert(str1.size(), 'c');
  cout << str1.c_str() << endl;
  str1 += '4';
  cout << str1.c_str() << endl;
  str1 += "Centos";
  cout << str1.c_str() << endl;
}

clx::string& clx::string::erase(size_t pos, size_t len)
{
  if (len == npos || pos + len >= _size)
  {
    _str[pos] = '';
    _size = pos;
    return *this;
  }
  size_t begin = pos + len;
  while (begin <= _size)
  {
    _str[begin - len] = _str[begin];
    begin++;
  }
  _size = pos;
  return *this;
}

clx::string& clx::string::clear()
{
  erase();
  return *this;
}

void clx::string_test5()
{
  string str1("hello world");
  str1.erase(0, 2);
  cout << str1.c_str() << endl;
  str1.erase(str1.size() - 1, 3);
  cout << str1.c_str() << endl;
  str1.clear();
  cout << str1.c_str() << endl;

}

size_t clx::string::find(char ch, size_t pos) const 
{
  for (size_t i = pos; i < _size; i++)
  {
    if (ch == _str[i])
    {
      return i;
    }
  }
  return npos;
}

size_t clx::string::find(const char* s, size_t pos) const 
{
  if (strstr(_str + pos, s) == nullptr)
  {
    return npos;
  }
  else 
  {
    return strstr(_str + pos, s) - _str;
  }
}

size_t clx::string::rfind(char ch, size_t pos) const 
{
  for (size_t end = _size -1; end >= pos && end != npos; end--)
  {
    if (_str[end] == ch)
    {
      return end;
    }
  }
  return npos;
}
size_t clx::string::rfind(const char* s, size_t pos) const 
{
  size_t len = strlen(s);
  size_t end = 0;
  for (end = _size -1 - len; end >= pos && end != npos; end--)
  {
    if(_str[end] == *s)
    {
      size_t cur = 0;
      for (cur = 0; cur < len; cur++)
      {
        if (_str[end + cur] != *(s + cur))
        {
          break;
        }
      }
      if (cur == len)
      {
        return end;
      }
    }
  }
  return npos;
}
void clx::string_test6()
{
  string str1("hello world");
  cout << str1.find('o') << endl;
  cout << str1.find("llo") << endl;
  cout << str1.find('v') << endl;
  cout << str1.rfind('h') << endl;
  cout << str1.rfind("he") << endl;
}

bool clx::operator==(const string str1, const string str2)
{
  return !strcmp(str1.c_str(), str2.c_str());
}

bool clx::operator!=(const string str1, const string str2)
{
  return strcmp(str1.c_str(), str2.c_str());
}

bool clx::operator>=(const string str1, const string str2)
{
  return (str1 == str2 || str1 > str2); 
}
bool clx::operator<=(const string str1, const string str2)
{
  return (str1 == str2 || str1 < str2); 
}
bool clx::operator>(const string str1, const string str2)
{
  return strcmp(str1.c_str(), str2.c_str());
}
bool clx::operator<(const string str1, const string str2)
{
  return strcmp(str1.c_str(), str2.c_str());
}
ostream& clx::operator<<(ostream& out, const string str)
{
  for (auto ch : str)
  {
    out << ch << " ";
  }
  out << endl;
  return out;
}
istream& clx::operator>>(istream& in , string& str)
{
  str.clear();
  char ch;
  ch = in.get();
  while( ch !=' ' && ch != 'n')
  {
   str += ch; 
    ch = in.get();
  }
  return in;
}

istream& clx::getline(istream& in , string& str)
{
  str.clear();
  char ch;
  ch = in.get();
  while(ch != 'n')
  {
   str += ch; 
    ch = in.get();
  }
  return in;
}

void clx::string_test7()
{
  string str1("hello world");
  string str2("hello Linux");
  string str3 = str1;
  string str4 = "hello Centos";
  string str5;
  string str6;
  clx::getline(cin, str6);
  cin >> str5;

  cout << (str6==str1) << endl;
  cout << (str5==str1) << endl;
  cout << (str1==str3) << endl;
  cout << (str4>=str3) << endl;
  cout << (str1!=str2) << endl;
  cout << str1;
  cout << str3;
}


4.3test.cpp
#include "my_string.h"


  //void string_test1()
  //{
  //  clx::string str1 = "hello world";
  //  clx::string str2(str1);
  //  clx::string str3 = str1;
  //}

int main()
{
  //clx::string_test1();
  //clx::string_test2();
  //clx::string_test3();
  //clx::string_test4();
  //clx::string_test5();
  //clx::string_test6();
  clx::string_test7();
  return 0;
  
}

[小结]:

以上即是【初识C++】4.1 STL-string的全部内容,这篇文章列举了STL string类中重要的成员函数,并对其中常用的成员函数进行了模拟实现,但是还有挺多的函数以及其重载函数我们未能实现。可以通过复用已经模拟的函数进行完善我们的stirng类。
如果这篇博客对你有所帮助,请帮博主点一个赞吧

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1030368.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号