008-C++String

string类

1. string类存在的原因

1.1 C语言中的字符串

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

1.2 C++的string类

C++STL库中将字符串封装成了string类,并提供了相应的结构,这样我们对字符串的使用就能更加方便快捷,并且不用担心越界等风险。

2. 标准库中的string类

2.1 string类

C++文档中的介绍:string - C++ Reference — string - C++ Reference

Strings are objects that represent sequences of characters.

The standard string class provides support for such objects with an interface similar to that of a standard container of bytes, but adding features specifically designed to operate with strings of single-byte characters.

The string class is an instantiation of the basic_string class template that uses char (i.e., bytes) as its character type, with its default char_traits and allocator types (see basic_string for more info on the template).

Note that this class handles bytes independently of the encoding used: If used to handle sequences of multi-byte or variable-length characters (such as UTF-8), all members of this class (such as length or size), as well as its iterators, will still operate in terms of bytes (not actual encoded characters).

总结:

  1. string是表示字符串的字符串类

  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;

  4. 不能操作多字节或者变长字符的序列。

在使用string类时必须包含头文件#include <string>,string类也在std命名空间中。

2.2 string类的常用结构

  1. string类常见构造

    函数名称功能说明
    string()构造空的string类对象,即空字符串
    string(const char* s)用C的字符串来构造string类对象
    string(size_t n, char c)string类对象中包含n个字符c
    string(const string& s)拷贝构造函数
    int main()
    {
    	string s1; // 空字符串
    	string s2("hello string"); // 使用C语言字符串进行构造
    	string s3(9, 'a'); // 创建一个包含9个'a'字符的字符串
    	string s4(s2); //拷贝构造
    
    	return 0;
    }
    
  2. string类对象的容量操作

    函数名称功能说明
    size返回字符串有效字符长度
    length返回字符串有效字符长度
    capacity返回空间总大小
    empty检测字符串释放为空串,是返回true,否则返回false
    clear清空有效字符
    reserve为字符串预留空间
    resize将有效字符的个数改成n个,多出的空间用字符c填充,如果未指定字符,则填充0
    int main()
    {
    	string s1("hello string");
    	cout << s1.size() << endl;
    	cout << s1.length() << endl;
    	cout << s1.capacity() << endl;
    	cout << s1.empty() << endl;
    	s1.clear();
    	cout << s1.empty() << endl;
    	s1.reserve(20);
    	cout << s1.capacity() << endl;
    	s1.resize(30, '0');
    	cout << s1 << endl;
    
    	return 0;
    }
    

    在这里插入图片描述

    注意:

    • size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
    • clear()只是将string中有效字符清空,不改变底层空间大小。
    • resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
    • reserve(size_t n=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
  3. string类对象的访问及遍历操作

    函数名称功能说明
    operator[]返回字符串对应下标位置的引用(与C语言字符串功能类似)
    begin、endbegin()获取一个字符的迭代器、end()获取最后一个字符下一个位置的迭代器
    rbegin、rendrbegin()获取一个字符的反向迭代器、rend()获取最后一个字符下一个位置的反向迭代器
    cbegin、cend与begin、end不同的是,这个是const修饰的迭代器,也就是不允许修改指向的内容
    crbegin、crendconst修饰的反向迭代器。

    关于迭代器:

    • 暂时可以将它理解成一个指针,可以进行解引用操作。
    • begin就是第一个字符串中第一个位置的指针,end就是指向字符串中最后一个位置下一个位置的指针(左闭右开),rbegin和rend正好相反,rbegin是指向最后一个位置的指针,rend是指向第一个位置的前一个位置的指针。
    • 对正向迭代器进行++操作会将迭代器往后移一个位置,对反向迭代器++则是将迭代器往前移一个位置。
    • string中正向迭代器的类型是string::iterator、反向迭代器则是string::reverse_iterator
    int main()
    {
    	string s1("hello string");
    	cout << s1[0] << endl;
    	auto it = s1.begin(); // 因为迭代器类型较长,这里可以使用auto自动识别,更加方便一些
    	while (it != s1.end())
    		cout << *it++;
    	cout << endl;
    	auto rit = s1.rbegin();
    	while (rit != s1.rend())
    		cout << *rit++;
    	cout << endl;
    
    	return 0;
    }
    

    在这里插入图片描述

    补充:string类的对象也同样支持前面说过的范围for(详见:001-C++入门-CSDN博客文章中9. 基于范围的for循环)

  4. string类对象的修改操作

    函数名称功能说明
    push_back在字符串后尾插字符
    append在字符串后追加一个字符串
    operator+=在字符串后追加一个字符串
    c_str返回C格式字符串(const char*)
    find从字符串指定位置开始往后找字符c或字符串,不传指定位置默认从第一个位置开始找,返回该字符在字符串中的位置,没找到返回npos(size_t的最大值,转成int本质上就是-1)。
    rfind从字符串指定位置开始往前找字符c,不传指定位置默认从最后一个位置开始找,返回该字符在字符串中的位置,没找到返回npos
    substr在str中从pos位置开始,截取n个字符,然后将其返回。
    int main()
    {
    	string s;
    	s.push_back('a');
    	s.append("bb");
    	s += "ccc";
    	printf("%s\n", s.c_str());
    	cout << s.find('a') << ' ' << (int)s.find('d') << endl;
    	cout << s.rfind('c') << ' ' << (int)s.rfind('d') << endl;
    	cout << s.substr(0, 3) << endl;
    
    	return 0;
    }
    

    在这里插入图片描述

    说明:

    • 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
    • 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好,这样可以减少底层扩容的次数,提升效率。
  5. string类非成员函数

    函数名称功能说明
    operator+将两个字符串拼接称一个字符串返回,尽量少用,因为传值返回,导致深拷贝效率低
    operator>>输入运算符重载(读取到空格就停止)
    operator<<输出运算符重载
    getline从指定流中获取一行字符串(读取到换行符才停止)
    relaticonal operators(关系运算符重载,也就是<、<=、>、>=、==、!=的重载)大小比较
    int main()
    {
    	string s1("aaa");
    	string s2("bbb");
    	cout << s1 + s2 << endl;
    	string s3;
    	cout << "输入:";
    	getline(cin, s3);
    	cout << s3 << endl;
    
    	return 0;
    }
    

    在这里插入图片描述

  6. vs和g++下string结构的说明

    注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

    • vs下string的结构

      string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

      • 当字符串长度小于16时,使用内部固定的字符数组来存放
      • 当字符串长度大于等于16时,从堆上开辟空间
      union _Bxty
      { 	// storage for small buffer or pointer to larger one
          value_type _Buf[_BUF_SIZE];
          pointer _Ptr;
          char _Alias[_BUF_SIZE]; // to permit aliasing
      } _Bx;
      

      这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

      其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量。

      最后:还有一个指针做一些其他事情。

      故总共占16+4+4+4=28个字节。

      在这里插入图片描述

    • g++下string的结构

      g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

      • 空间总大小
      • 字符串有效长度
      • 引用计数
      struct _Rep_base
      {
          size_type 		_M_length;
          size_type 		_M_capacity;
          _Atomic_word 	_M_refcount;
      }
      
      • 指向堆空间的指针,用来存储字符串。

3.string类的模拟实现

这里我们只实现部分上面提及的常用接口。

String.hpp

#pragma once

#include <iostream>

namespace my
{
	class String
	{
		friend void swap(String& s1, String& s2);
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		String(const char* s = "")
			:_size(strlen(s))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, s);
			_str[_size] = '\0';
		}

		String(size_t n, char c)
			:_capacity(n)
			,_size(n)
		{
			_str = new char[_capacity + 1];
			for (int i = 0; i < _size; i++)
				_str[i] = c;
			_str[_size] = '\0';
		}

		String(const String& s)
		{
			String tmp(s._str);
			swap(*this, tmp);
		}

		String& operator=(String s)
		{
			swap(*this, s);
			return *this;
		}

		~String()
		{
			delete[] _str;
		}

		size_t size()
		{
			return _size;
		}

		size_t length()
		{
			return _size;
		}

		size_t capacity()
		{
			return _capacity;
		}

		bool empty()
		{
			return _size == 0;
		}

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

		void reserve(size_t n)
		{
			if (n > _capacity || (n < _capacity && n > _size))
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

		void resize(size_t n, char c = '\0')
		{
			if (n < _size)
			{
				_str[n] = '\0';
				_size = n;
				return;
			}
			if (n > _capacity)
				reserve(n);
			while (_size < n)
				_str[_size++] = c;
			_str[_size] = '\0';
		}

		char& operator[](size_t n)
		{
			return _str[n];
		}

		const char& operator[](size_t n) const
		{
			return _str[n];
		}

		const char* c_str() const
		{
			return _str;
		}

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator cbegin() const
		{
			return _str;
		}

		const_iterator rend() const
		{
			return _str + _size;
		}

		void push_back(char c)
		{
			if (_capacity == _size)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				char* tmp = new char[newCapacity + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
			}
			_str[_size++] = c;
			_str[_size] = '\0';
		}

		String& operator+=(const char* s)
		{
			size_t sz = strlen(s);
			reserve(sz + _size);
			for (int i = 0; i < sz; i++)
				push_back(s[i]);
			return *this;
		}

		String& operator+=(char c)
		{
			push_back(c);
			return *this;
		}

		String& operator+=(const String& s)
		{
			reserve(s._size + _size);
			for (int i = 0; i < s._size; i++)
				push_back(s[i]);
			return *this;
		}

		size_t find(char c, size_t pos = 0)
		{
			for (int i = pos; i < _size; i++)
				if (_str[i] == c)
					return i;
			return npos;
		}

		size_t find(const char* s, size_t pos = 0)
		{
			char* p = strstr(_str + pos, s);
			if (p != nullptr) return p - _str;
			return npos;
		}

		String substr(size_t pos, size_t len = npos)
		{
			String sub;
			len = std::min(len, _size - pos);
			for (int i = pos; i < pos + len; i++)
				sub += _str[i];
			return sub;
		}
	private:
		char* _str;
		size_t _capacity;
		size_t _size;
	public:
		static const size_t npos;
	};

	const size_t String::npos = -1;

	String operator+(String s1, String& s2)
	{
		s1 += s2;
		return s1;
	}

	std::istream& operator>>(std::istream& in, String& s)
	{
		s.clear();
		char c;
		char tmp[128];
		int i = 0;
		c = in.get();
		while (c != '\n' && c != ' ')
		{
			tmp[i++] = c;
			if (i == 127)
			{
				tmp[i] = '\0';
				s += tmp;
				i = 0;
			}
			c = in.get();
		}
		if (i != 0)
		{
			tmp[i] = '\0';
			s += tmp;
		}
		return in;
	}

	std::ostream& operator<<(std::ostream& out, String& s)
	{
		out << s.c_str();
		return out;
	}

	bool operator<(String& s1, String& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	bool operator==(String& s1, String& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	bool operator<=(String& s1, String& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(String& s1, String& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(String& s1, String& s2)
	{
		return !(s1 < s2);
	}

	bool operator!=(String& s1, String& s2)
	{
		return !(s1 == s2);
	}

	std::istream& getline(std::istream& in, String& s)
	{
		s.clear();
		char c;
		char tmp[128];
		int i = 0;
		c = in.get();
		while (c != '\n')
		{
			tmp[i++] = c;
			if (i == 127)
			{
				tmp[i] = '\0';
				s += tmp;
				i = 0;
			}
			c = in.get();
		}
		if (i != 0)
		{
			tmp[i] = '\0';
			s += tmp;
		}
		return in;
	}

	void swap(String& s1, String& s2)
	{
		std::swap(s1._str, s2._str);
		std::swap(s1._capacity, s2._capacity);
		std::swap(s1._size, s2._size);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值