IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 了解string以及简单模拟实现(c++) -> 正文阅读

[C++知识库]了解string以及简单模拟实现(c++)

00. 前言

我们竟然要用string,就要知道string里面有什么?
模拟的意义在于了解它的底层逻辑更便于我们去使用。
参考文档:
链接:string类

01. string中常用类成员

namespace Ding
{
    class string
    {
    public:
        typedef char* iterator;
        typedef const char* const_iterator;
        // iterator 
        const_iterator begin() const;
        const_iterator end() const;
        iterator begin();
        iterator end();


        string(const char* str = "")
        {
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];

            strcpy(_str, str);
        }

        /* 传统写法
        string(const string& s)
          :_str(new char[s._capacity+1]);
          ,_size(s._size);
          ,_capacity(s._capacity);
        {
          strcpy(_str, s._str);
        }

          */
        void swap(string& tmp)
        {
            ::swap(_str, tmp._str);
            ::swap(_size, tmp._size);
            ::swap(_capacity, tmp._capacity);
        }

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


        ~string()
        {
            delete[] _str;
            _str = nullptr;
            _capacity = _size = 0;
        }
        //string& operator=(const string &s);
        string& operator=(string s);
        string& operator=(char c);

        //access
        char& operator[](size_t pos);
        const char& operator[](size_t pos) const;

        //modify
        void push_back(char c);

        string& operator+=(char c);
        string& operator+=(const string& str);

        void append(const char* str);

        string& operator+=(const char* str);

        void clear();

        const char* c_str() const;

        void reserve(size_t n);

        //capacity
        size_t size() const;

        size_t capacity() const;

        bool empty() const;
        void resize(size_t n, char c = '\0');

        // 在pos位置上插入字符c/字符串str,并返回该字符的位置
        string& insert(size_t pos, char c);
        string& insert(size_t pos, const char* str);

        // 删除pos位置上的元素
        string& erase(size_t pos, size_t len = npos);
        //
        bool operator<(const string& s);

        bool operator<=(const string& s);

        bool operator>(const string& s);

        bool operator>=(const string& s);

        bool operator==(const string& s);

        bool operator!=(const string& s);

        // 返回c在string中第一次出现的位置

        size_t find(char c, size_t pos = 0) const;

        // 返回子串s在string中第一次出现的位置     

        size_t find(const char* s, size_t pos = 0) const;

        //返回子字符串
        string substr(size_t pos, size_t len) const;
    private:
        char* _str;
        size_t _size;
        size_t _capacity;
    public:
        const static size_t npos=-1; //此处叫声明--给缺省值
    };

//不使用友元的原因是未访问私有成员变量。
    ostream& operator<<(ostream& out, const string& s);
    istream& operator>>(istream& in, string& s);

}

解释:
我们为了与库里的string区分开,我们使用命名空间加以区别。

03. 为什么学习string

1. C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
.
2. 在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

04. string模拟实现

0. 析构函数:

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

1. string类对象的常见构造

  • 函数名称:
    • string()
    • string(const char* s)
    • string(const string&s)
  • 功能说明
    • 构造空的string类对象,即空字符串。
    • 用C-string来构造string类对象
    • 拷贝构造函数

0)测试案例:

void Teststring1()
{
	string s1; // 构造空的string类对象s1
	string s2("hello Ding"); // 用C格式字符串构造string类对象s2
	string s3(s2); // 拷贝构造s3
}

1)string() --构造函数

string()
	:_str(new char[1])
	, _size(0)  //记录有效值
	, _capacity(0) //字符串最后的‘\0’不算有效值
	//上面的是初始化列表的应用。
{
	_str[0] = '\0';
}

空字符串中含"\0"

2)string(const char* s)–带参构造

string(const char* str)
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];

    strcpy(_str, str);
}

1-2)string(const char* str = “”)–缺省构造

存在1)2)这两种构造方式,这时我们就可以俩者结合一下;利用缺省的性质来构造

string(const char* str = "") //”“空字符串隐含一个'\0'
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];

	strcpy(_str, str);
}

深浅拷贝的问题

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
先看下面一段代码:

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array; 
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

一运行上面代码就会崩溃。
在这里插入图片描述

编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝,会导致堆上开辟的空间,二次释放(free)

示意图:
在这里插入图片描述

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

3)string(const string&s)–拷贝构造函数

切记拷贝构造的时候存在深浅拷贝问题;我们需要另开空间,来解决。

  • 传统写法
//传统写法
string(const string& s)
	:_str(new char[s._capacity + 1]);//初始化列表
, _size(s._size);
, _capacity(s._capacity);
{
	strcpy(_str, s._str);
}
  • 现代写法(推荐使用)
void swap(string& tmp)
{
    ::swap(_str, tmp._str);//::去全局去找,也就是调用库里面的swap
    ::swap(_size, tmp._size);
    ::swap(_capacity, tmp._capacity);
}

string(const string& s)
    :_str(nullptr)
    , _size(0)
    , _capacity(0)
    //初始化列表
{
    string tmp(s._str);//调用构造函数。
    swap(tmp);
}

测试结果

在这里插入图片描述

2. string类对象的容量操作

  • 函数名称
    • size与length(我们通常使用size)
    • capacity
    • empty
    • clear
    • reserve
    • resize
  • 功能说明
    • 返回字符串有效字符长度
    • 返回空间总大小
    • 检测字符串释放为空串,是返回true,否则返回false
    • 清空有效字符
    • 为字符串预留空间
    • 将有效字符的个数该成n个,多出的空间用字符c填充

0)代码演示

在这里插入图片描述
当然写了属于自己的流输出cout也可以测。

在这里插入图片描述
测试代码:

void Teststring2()
{
	// 注意:string类对象支持直接用cin和cout进行输入和输出
	Ding::string s("hello, bit!!!");//调用库里面的。
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(15);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
}

1)size–字符串有效字符长度

我的是定义与声明分离,所以需要Ding::string:: 域。

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

2)capacity–开辟空间总大小

size_t Ding::string::capacity() const
{
    return _capacity;
}

3)empty–检测字符串是否为空

bool Ding::string::empty() const
{
    return _capacity == 0;
}

4)clear–清空有效字符

清楚字符串,我们保证有效值为零,理论是是空字符串就可以了。

void Ding::string::clear()
{
    _str[0] = '\0';
    _size = 0;
}

5)reserve–字符串预留空间

void Ding::string::reserve(size_t n)
{
    if (n > _capacity)//检验是否扩容
    {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;

        _str = tmp;
        _capacity = n;

    }
}

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

void Ding::string::resize(size_t n, char c)
{
    reserve(n); //空间不够时开空间

    if (n > _size)
    {
        //插入
        for (size_t i = _size; i < n; i++)
        {
            _str[i] = c;
        }
        _str[n] = '\0';
        _size = n;
    }
    else
    {
        _str[n] = '\0';
        _size = n;
    }

}

3. string类对象的访问及遍历操作

  • 函数名称
    • operator[]
    • begin+ end begin
  • 功能说明
    • 返回pos位置的字符,const string类对象调用
    • 范围for
    • 获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
    • C++11支持更简洁的范围for的新遍历方式

0)代码演示

void Teststring3()
{
	std::string s("Hello Ding");
	// 3种遍历方式:
	// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
	// 另外以下三种方式对于string而言,第一种使用最多
	// 1. for+operator[]
	for (size_t i = 0; i < s.size(); ++i)
		cout << s[i] << "-";
	cout << endl;

	// 2.迭代器
	// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
	// 
	//std::string::iterator it = s.begin();
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << "-";
		++it;
	}
	cout << endl;

	
	// 3.范围for
	for (auto ch : s)
		cout << ch << endl;
}

在这里插入图片描述
说明:
范围for的底层逻辑就是迭代器。
迭代器可能是指针也可能不是指针,但它像指针那样用。
typedef char iterator;*
typedef const char const_iterator;*

2)迭代器iterator

const_iterator begin() const;
const_iterator end() const;
iterator begin();
iterator end();

模拟实现:

Ding::string::iterator Ding::string::begin()
{
    return _str;
}
Ding::string::iterator Ding::string::end()
{
    return _str + _size;
}

Ding::string::const_iterator Ding::string::begin() const
{
    return _str;
}

Ding::string::const_iterator Ding::string::end() const
{
    return _str + _size;
}

3)operator[]

char& Ding::string::operator[](size_t pos)
{
    assert(pos < _size);

    return _str[pos];
}

const char& Ding::string::operator[](size_t pos) const
{
    assert(pos < _size);

    return _str[pos];
}

4. string类对象的修改操作

  • 函数名称
    • push_back
    • append
    • operator+=
    • c_str
    • find + npos
    • substr
  • 功能说明
    • 在字符串后尾插字符c
    • 在字符串后追加一个字符串
    • 在字符串后追加字符串str或字符
    • 返回C格式字符串
    • 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 (npos我们模拟定义是无符号-1)
    • 在str中从pos位置开始,截取n个字符,然后将其返回(子字符串)

注意:

  1. 在string尾部追加字符时,s.push_back? / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

代码演示:

void Teststring4()
{
	Ding::string str;
	str.push_back(' ');   // 在str后插入空格
	str.append("Hello");  // 在str后追加一个字符"hello"
	str += 'D';           // 在str后追加一个字符'b'   
	str += "ing";          // 在str后追加一个字符串"it"
	cout << str << endl;
	cout << str.c_str() << endl;   // 以C语言的方式打印字符串


	// npos是string里面的一个静态成员变量
	// static const size_t npos = -1;

	// 取出url中的域名
	Ding::string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == Ding::string::npos)
	{
		cout << "invalid url" << endl;
		return;
	}
	start += 3;
	size_t finish = url.find('/', start);
	Ding::string address = url.substr(start, finish - start);
	cout << address << endl;

	// 删除url的协议前缀
	size_t pos = url.find("://");
	url.erase(0, pos + 3);
	cout << url << endl;
}

在这里插入图片描述

1)string—insert模拟实现

Ding::string& Ding::string::insert(size_t pos, char c)
{
    assert(pos <= _size);
    //扩容 
    if (_size == _capacity)
    {
        size_t capacity = _capacity == 0 ? 4 : 2 * _capacity;

        reserve(capacity);
    }
//挪动数据
    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];
        --end;
    }

    _str[pos] = c;
    ++_size;

    return *this;
}
Ding::string& Ding::string::insert(size_t pos, const char* str)
{
    assert(pos <= _size);
    size_t len = strlen(str);
    //扩容
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }

    size_t end = _size + len;
    while (end > pos)
    {
        _str[end] = _str[end - 1];
        --end;
    }
    strcpy(_str + pos, str);
    _size += len;

    return *this;
}

2)string—push_back模拟实现

尾插:

void Ding::string::push_back(char c)
{
    /*if(_size==_capacity)//扩容
    {
      size_t capacity= _capacity==0? 4 : 2*_capacity;

      reserve(capacity);
    }
    _str[_size]=c;
    ++_size;
    _str[_size]='\0';
  */

    insert(_size, c);//调用
}

3)string–append模拟实现

void Ding::string::append(const char* str)
{
    /* size_t len = strlen(str);

     if(_size + len > _capacity)
     {
       reserve(_size + len);//扩容
     }

     strcpy(_str+_size, str);
     _size = _size + len;
     _str[_size] = '\0';
   */

    insert(_size, str);
}

4)string–find模拟实现

// 返回子串s在string中第一次出现的位置     

size_t Ding::string::find(const char* s, size_t pos) const
{
    assert(pos < _size);
    assert(s);

    const char* str = strstr(_str + pos, s);//c语言查子字符串
    if (str == nullptr)
    {
        return npos;
    }
    else
    {
        return str - _str;//指针相减,得到两者之间距离。
    }

}

单个字符

size_t Ding::string::find(char c, size_t pos) const
{
    assert(pos < _size);

    for (size_t i = pos; i < _size; ++i)
    {
        if (_str[i] == c)
        {
            return i;
        }

    }

    return npos;
}

5)string–erase模拟实现

Ding::string& Ding::string::erase(size_t pos, size_t len)
{
    assert(pos < _size);

    if (len == npos || len + pos >= _size)//删完
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else//pos位置以后的删完
    {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }

    return *this;
}

6)string–+= 模拟实现

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

Ding::string& Ding::string::operator+=(const string& str)
{
    append(str._str);
    return *this;
}

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

string–赋值重载

注意:深浅拷贝问题。

Ding::string& Ding::string::operator=(string& s)
{
	if(this != &s)//排除自己给自己赋值
	{
		string tmp(s._str);//构造
		swap(tmp);
	}

    return *this;
}

下面使用传值,那么下面s就是临时拷贝,相等与替代了tmp的作用;此时也是深拷贝。

Ding::string& Ding::string::operator=(string s)
{
    swap(s);
    return *this;
}

单个字符。

Ding::string& Ding::string::operator=(char c)
{ 
	(*this).clear;//先清除。
    *this += c;

    return *this;
}

7)string—substr模拟实现

// 返回字字符串
Ding::string Ding::string::substr(size_t pos, size_t len) const
{
    assert(pos < _size);

    size_t reallen = len;
    if (len == npos || pos + len > _size)//返回整个字符串
    {
        reallen = _size - pos;
    }

    string sub;
    for (size_t i = pos; i < reallen + pos; ++i)
    {
        sub += _str[i];
    }

    return sub;
}

8)string–c_str模拟实现

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

5. string类非成员函数

  • 函数 :
    • operator>>
    • operator<<
  • 功能说明:
    • 输入运算符重载
    • 输出运算符重载

1)流插入-cout

思路:一个一个字符的读取

ostream& Ding::operator<<(ostream& out, const string& s)
{
    for (size_t i = 0; i < s.size(); ++i)
    {
        out << s[i];
    }

    return out;
}

2)流提取-cin

思路:一个一个提取到string如果扩容的话效率太低;我们先用buff开出一定空间,先把东西存到它里面;我们再取出。

istream& Ding::operator>>(istream& in, string& s)
{
    s.clear();
    //当字符串很长的时候,会不断扩容,效率底。
    char ch;
    ch = in.get();//cin自动碰到' '就不读取了;我们这里使用cin.get()

    const size_t N = 32;
    char buff[N];
    size_t i = 0;

    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;

        if (i == N - 1)
        {
            buff[i] = '\0';
            s += buff;
            i = 0;
        }
        ch = in.get();
    }
    buff[i] = '\0';//字符串末尾有'\0'
    s += buff;

    return in;
}

.
.
.
感觉有所收获的话,友友们给小丁一个赞👍

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 18:45:50  更:2022-08-19 18:48:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 11:09:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码