C++初阶 —— string类

目录

一,标准库中的string类

二,string类的常用接口

构造函数

容量操作函数

访问及遍历操作函数

修改操作函数

非成员函数(即全局函数)

三,深/浅拷贝

经典string类问题

浅拷贝

深拷贝

写时拷贝(了解)

四,string类的模拟实现


        C语言中,字符串是以“\0”结尾的字符集合;C标准库提供了一些str库函数,但库函数与字符串是分开的,不符合OOP思想,且底层空间需要用户自己管理,容易越界访问;

注:编码格式,ASCII、UTF-8/16/32等;

一,标准库中的string类

string类

  • string是利用动态数组实现的,是在堆上开辟的空间;
  • string类是基于basic_string模板类的一个实例,使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数;
  • string是标准的字符串类,其接口类似于标准字符容器的接口;提供了一系列成员函数和操作符函数,用于对字符串进行赋值、比较、查询、修改等操作;
  • 不能操作多字节或变长字符的序列;

注:在使用string类时,必须包含#include<string>头文件及using namespace std;

二,string类的常用接口

构造函数

  • string(),构造空的string类对象,即空字符串;
  • string(const char*s),用C-string来构造string类对象;
  • string(const string&s),拷贝构造函数;
  • string(size_t n, char c),string类对象中包含n个字符c;
int main()
{
	char str[] = "aba";
	string s1; //默认无参数构造
	string s2(str); //复制以空结尾的字符序列(C字符串)
	string s3(s2); //拷贝构造,即复制
    string s4(5, 'a'); //填充5个连续的a
}

容量操作函数

  • size,返回字符串有效字符长度;
  • length,返回字符串有效字符长度;
  • capacity,返回空间总大小;
  • reserve,为字符串预留空间,对成员变量capacity操作;
  • resize,将有效字符的个数改成n个,多出的空间用字符c填充,对成员变量size操作;
  • empty,检测字符串释放为空串,是true,否false;
  • clear,清空有效字符,size为0,capacity不变;

注:

  • size()与length()方法底层实现原理完全相同,引入size()的原因是与其他容器的接口保持一致,一般基本都使用size();
  • resize(size_t n)与resize(size_t n, char c)都是将字符串中有效字符个数改变为n个,不同的是当字符个数增多时,前者用0来填充,后者用字符c来填充;resize改变元素个数时,增多可能会改变底层容量的大小,减少空间大小不变(vs/g++);
  • clear()只是将string中有效字符清空,不改变底层空间大小;
int main()
{
	string s("abcd");
	cout << s.size() << endl;
	cout << s.length() << endl;
	s.resize(6);
	cout << s.size() << endl;
	s.resize(10, 'c');
	cout << s << endl;
	s.reserve(20);
	cout << s.capacity() << endl;
}

访问及遍历操作函数

  • operator[],返回pos位置的字符,const string类对象调用;
  • begin+end,begin获取一个字符的迭代器,end获取最后一个字符下一个位置的迭代器;
  • rbegin+rend,rbegin获取反向第一个字符的迭代器,rend获取反向最后一个字符下一个位置的迭代器;
  • 范围for,C++11支持更简洁范围for的新遍历方式,支持迭代器就支持范围for;
int main()
{
	string s("abcdefg");
	for (int i = 0; i < s.size(); i++)
	{
		cout << s[i];
	}
	cout << endl;
	//迭代器遍历
	//string::iterator it = s.begin();
	auto it1 = s.begin();
	while (it1 != s.end())
	{
		cout << *it1;
		it1++;
	}
	cout << endl;
	//反向遍历
	auto it2 = s.rbegin();
	while (it2 != s.rend())
	{
		cout << *it2;
		it2++;
	}
	cout << endl;
	//范围for遍历
	for (auto i:s)
	{
		cout << i;
	}
	return 0;
}

修改操作函数

  • push_back,字符串尾插字符c;
  • append,字符串后追加一个字符串;
  • operator+=,字符串后追加字符串str;
  • c_str,返回c格式字符串('\0'结尾)指针,const指针,;
  • find + pos,从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置;
  • rfind,从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置;
  • substr,在str中从pos位置开始,截取n个字符,将其返回;
  • insert,插入字符串,少用,需挪动数据;
  • erase,删除指定位置后的字符,npos(无符号-1,即最大值),也少用;
int main()
{
	string s("abc");
	s.push_back('d');
	s.append("efg");
	s += "hijk";
	cout << s.c_str() << endl;
	cout << s.find('c') << endl;
	cout << s.find("hij", 2) << endl; 
	cout << s.substr(2, 3) << endl;
	cout << s.insert(0, "ab") << endl;
	s.reserve(20);
	cout << s.erase() << endl; //size为0,capacity不变,类似clear
	return 0;
}

非成员函数(即全局函数)

  • operator+,尽量少用,因传值返回导致深拷贝效率低;
  • operator>>,输入运算符重载;
  • operator<<,输出运算符重载;
  • getline,获取一行字符串;
  • relational operators(<、>、=...),大小比较;
int main()
{
	string s1;
	cin >> s1;
	cout << s1 << endl;
	getline(cin, s1);

	string s2 = "abcd";
	getline(cin, s2); //会清除和覆盖之前内容
	cout << s2 << endl;
	return 0;
}

注:

  • npos类静态成员变量,值为无符号-1,string::npos;
  • vs编译器string类大小(即sizeof)为28, 因为默认有个buff数组(char _buf[16]),所以12+16;
  • g++编译器string类大小(即sizeof)为8;

三,深/浅拷贝

经典string类问题

  • s1调用默认拷贝构造,会导致是s1、s2共用同一块空间;
  • 在释放时,会对同一块空间释放两次引起程序崩溃;
class string
{
public:
	//构造函数
	string(const char* str = "")
	{
        if(nullptr = str)
        {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	//析构函数
	~string()
	{
        if(_str)
        {
            delete[] _str;
		    _str = nullptr;
        }
	}
private:
	char* _str;
};

int main()
{
	string s1("abcd");
	string s2(s1); //调用默认拷贝构造,值拷贝或浅拷贝
	return 0;
}

浅拷贝

  • 又称位拷贝,编译器会直接将对象的值拷贝过来;
  • 如对象中管理资源,会导致多个对象共用一份资源,销毁释放时就会造成重复释放的问题;

深拷贝

  • 会给每个对象独立分配资源,保证多个对象间不会因共享资源而造成多次释放;
  • 如一个类中涉及到资源管理,其拷贝构造、赋值运算符重载、及析构函数都必须显示给出,一般按深拷贝方式提供;
//传统保守写法
//拷贝构造
string(const string& s)
	:_str(new char[strlen(s._str) + 1])
{
	strcpy(_str, s._str);
}
//赋值运算符重载
string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[strlen(s._str) + 1];
		strcpy(tmp, s._str);
		delete[]_str;
		_str = tmp;
	}
	return *this;
}
//析构函数,因为新开了空间
~string()
{
    if(_str)
    {
        delete[] _str;
		_str = nullptr;
    }
}
//现代写法
//拷贝构造
string(const string& s)
	:_str(nullptr)
{
    //利用默认构造函数,申请新空间,然后交换
	string strtmp(s._str); 
	swap(_str, strtmp._str);
}
//赋值运算符重载
string& operator=(string s)
{
    //直接传值传参调用拷贝构造,然后交换
	swap(_str, s._str);
    return *this;
}
//赋值运算符重载
//string& operator=(const string& s)
//{
//	if (this != &s)
//	{
//		string strtmp(s._str);
//		swap(_str, strtmp._str);
//	}
//	return *this;
//}

注:数组越界一般检测不出来,越界是抽查可能会检测出来;string都会被检测出来;

写时拷贝(了解)

  • 即延时拷贝,在浅拷贝的基础上增加了引用计数的方式来实现;某个对象写入时,在深拷贝对象;
  • 引用计数是用来记录资源使用者的个数,构造时计数加1,每增加一个对象使用该资源计数就在加1,当某个对象销毁时,计数就减1,如计数为1,说明该对象是资源的最后使用者,将资源释放;

注:vs是深拷贝,不是写时拷贝,g++是写时拷贝;

四,string类的模拟实现

namespace mystring
{
	class string
	{
	public:
		//迭代器iterator
		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; }

	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);
			swap(tmp);
		 }
		//赋值运算符重载
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		//析构函数
		~string()
		{
			delete[]_str;
			_str = nullptr;
		}

		//Capacity
		void reserve(size_t newcapacity)
		{
			if (_capacity < newcapacity)
			{
				char* str = new char[newcapacity + 1];
				strcpy(str, _str);
				delete[] _str;
				_str = str;
				_capacity = newcapacity;
			}
		}
		void resize(size_t newsize, char c = '\0')
		{
			if (newsize > _capacity)
				reserve(newsize);
			for (size_t i = _size; i < newsize; i++)
				_str[i] = c;
			_size = newsize;
			_str[_size] = '\0';	
		}

		//访问
		char& operator[](size_t index)
		{
			assert(index < _size);
			return _str[index];
		}
		const char& operator[](size_t index) const
		{
			assert(index < _size);
			return _str[index];
		}
		const char* c_str()const
		{
			return _str;
		}

		//修改
		void push_back(char c)
		{
			//if (_size == _capacity)
			//{
			//	size_t newcapacity = _capacity ? _capacity * 2 : 4;
			//	reserve(newcapacity);
			//}
			//_str[_size++] = c;
			//_str[_size] = '\0';

			insert(_size, c);
		}
		void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (_size + len > _capacity)
			//{
			//	reserve(_size + len);
			//}
			//strcpy(_str + _size, str);
			//_size += len;
			insert(_size, str);
		}
		string& operator+=(char c)
		{
			push_back(c);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		string& operator+=(const string& s)
		{
			*this += s._str; //append(s._str);
			return *this;
		}

		string& insert(size_t pos, char c)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity ? _capacity * 2 : 4;
				reserve(newcapacity);
			}

			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[end] = c;
			++_size;
			_str[_size] = '\0';

			return *this;	
		}
		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 + 1;
			while (end > pos)
			{
				_str[end + len - 1] = _str[end - 1];
				--end;
			}
			for (int i = 0; i < len; i++)
			{
				_str[pos + i] = str[i];
			}
			_size += len;

			return *this;
		}

		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

		//查找
		size_t find(char c, size_t pos = 0) const
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == c)
					return i;
			}
			return npos;
		}
		//kmp方法,bm方法
		size_t find(char* substr, size_t pos = 0) const
		{
			const char* p = strstr(_str + pos, substr);
			if (p == nullptr)
				return npos;
			else
				return  p - _str;
		}

		void clear()
		{
			_size = 0;
		}

		//交换
		void swap(string& s) 
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _capacity;
		}
		const static size_t npos;
	private:
		char* _str;
		size_t _size;
		size_t _capacity; //不包含'\0'
	};

	const size_t string::npos = -1;

	//非成员函数重载
	//+尽量少用
	string operator+(const string& s, const char c)
	{
		string tmp = s;
		tmp += c;
		return tmp;
	}
	string operator+(const string& s, const char* str)
	{
		string tmp = s;
		tmp += str;
		return tmp;
	}
	string operator+(const string& s1, const string& s2)
	{
		string tmp = s1;
		tmp += s2;
		return tmp;
	}

	std::istream& operator>>(std::istream& in, string& s)
	{
		//in >> ch; //获取不到' '或'\0'
		s.clear();
		char c = in.get();
		while (c != ' ' && c != '\n')
		{
			s += c;
			c = in.get();
		}
		return in;
	}
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}
		return out;
	}
	std::istream& getline(std::istream& in, string& s)
	{
		s.clear();
		char c = in.get();
		while (c != '\n')
		{
			s += c;
			c = in.get();
		}
		return in;
	}

	bool operator>(const string& s1, const string s2)
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < s1.size() && i2 < s2.size())
		{
			if (s1[i1] > s2[i2])
				return true;
			else if (s1[i1] < s2[i2])
				return false;
			else
			{
				++i1;
				++i2;
			}
		}
		if (i1 == s1.size())
			return false;
		else
			return true;
	}
	bool operator==(const string& s1, const string s2)
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < s1.size() && i2 < s2.size())
		{
			if (s1[i1] != s2[i2])
				return false;
			else
			{
				++i1;
				++i2;
			}
		}
		if (i1 == s1.size() && i2 == s2.size())
			return true;
		else
			return false;
	}
	inline bool operator!=(const string& s1, const string s2)
	{
		return !(s1 == s2);
	}
	inline bool operator>=(const string& s1, const string s2)
	{
		return (s1 > s2 || s1 == s2);
	}
	inline bool operator<(const string& s1, const string s2)
	{
		return !(s1 >= s2);
	}
	inline bool operator<=(const string& s1, const string s2)
	{
		return !(s1 > s2);
	}
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值