【C++】三、string类

一、string类的文档介绍

二、string类的常用接口说明

1.string类对象的常见构造

2.string类对象的容量操作

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

4. string类对象的修改操作

5. string类非成员函数

6.string的使用        

三、练习

1.反转字母

2.字符串中第一个唯一字符

3.验证一个字符串是否是回文

4.字符串相加

四、string类的模拟实现

1.实现string类的构造、拷贝构造、赋值运算符重载以及析构函数

2.浅拷贝

3.深拷贝

 4.传统版本和现代版本的string类

5.模拟实现

一、string类的文档介绍

1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>
string;
4. 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;

 

二、string类的常用接口说明

1.string类对象的常见构造

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

2.string类对象的容量操作

 

//1.string类对象支持cin cout进行输入输出
// size/clear/resize
void Teststring1()
{
 // 注意:string类对象支持直接用cin和cout进行输入和输出
 string s("hello, world!!!");
 cout << s.size() << endl;
 cout << s.length() << endl;
 cout << s.capacity() << endl;
 cout << s << endl;
 
 s.clear();// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
 cout << s.size() << endl;
 cout << s.capacity() << endl;

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

 s.resize(15); // 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
 cout << s.size() << endl; // "aaaaaaaaaa\0\0\0\0\0" 注意此时s中有效字符个数已经增加到15个
 cout << s.capacity() << endl;
 cout << s << endl;
 // 将s中有效字符个数缩小到5个
 s.resize(5);
 cout << s.size() << endl;
 cout << s.capacity() << endl;
 cout << s << endl; }
void Teststring2()
{
 string s;
 // 测试reserve是否会改变string中有效元素个数
 s.reserve(100);
 cout << s.size() << endl;
 cout << s.capacity() << endl;
 // 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
 s.reserve(50);
 cout << s.size() << endl;
 cout << s.capacity() << endl; }
// 利用reserve提高插入数据的效率,避免增容带来的开销
void TestPushBack()
{
 string s;
 size_t sz = s.capacity();
 cout << "making s grow:\n";
 for (int i = 0; i < 100; ++i)
 {
 s.push_back('c');
 if (sz != s.capacity())
 {
 sz = s.capacity();
 cout << "capacity changed: " << sz << '\n';
 }
 }
}


void TestPushBackReserve()
{
 string s;
 s.reserve(100);
 size_t sz = s.capacity();
 
 cout << "making s grow:\n";
 for (int i = 0; i < 100; ++i)
 {
 s.push_back('c');
 if (sz != s.capacity())
 {
 sz = s.capacity();
 cout << "capacity changed: " << sz << '\n';
 }
 }
}

注意:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

2. clear()只是将string中有效字符清空,不改变底层空间大小。

3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

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

void Teststring()
{
 string s1("hello World");
 const string s2("Hello World");
 cout<<s1<<" "<<s2<<endl;
 cout<<s1[0]<<" "<<s2[0]<<endl;
 
 s1[0] = 'H';
 cout<<s1<<endl;
 
 // s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
void Teststring3()
{
 string s("hello World");
 // 3种遍历方式
 // 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
 // 另外以下三种方式对于string而言,第一种使用最多
 // 1. for + operator[]
 for(size_t i = 0; i < s.size(); ++i)
 cout<<s[i]<<endl;
 
 // 2.迭代器
 string::iterator it = s.begin();
 while(it != s.end())
 {
 cout<<*it<<endl;
 ++it;
 }
 
 string::reverse_iterator rit = s.rbegin();
 while(rit != s.rend())
 cout<<*rit<<endl;
 
 // 3.范围for
 for(auto ch : s)
{
    cout<<ch<<endl; 
}

4. string类对象的修改操作

void Teststring()
{
 string str;
 str.push_back(' '); // 在str后插入空格
 str.append("hello"); // 在str后追加一个字符"hello"
 str += 'w'; // 在str后追加一个字符'w' 
 str += "orld"; // 在str后追加一串字符串"orld"
 cout<< str <<endl;
 cout<< str.c_str() <<endl; // 以C语言的方式打印字符串
 
 // 获取file的后缀
 string file1("string.cpp");
 size_t pos = file.rfind('.');
 string suffix(file.substr(pos, file.size()-pos));//substr取出字符串的子串
 cout << suffix << endl;
 
 // npos是string里面的一个静态成员变量
 // static const size_t npos = -1; //相当于特别大的值 代表一直到结束
 
 // 取出url中的域名
 sring url("http://www.cplusplus.com/reference/string/string/find/");
 cout << url << endl;
 size_t start = url.find("://"); //返回:前的位置
 if (start == string::npos)
 {
 cout << "invalid url" << endl;
 return;
 }
 start += 3;
 size_t finish = url.find('/', start);//从某个位置开始找 这里是从start的位置开始找 /   
 string address = url.substr(start, finish - start);
 cout << address << endl;
 // 删除url的协议前缀
 pos = url.find("://");
 url.erase(0, pos+3);
 cout<<url<<endl; 
}

5. string类非成员函数

6.string的使用
        1.构造/拷贝
        2.增->operator+= /insert
        3.删->earse
        4.查->find/rfind
        5.改->operator[] 
        6.遍历-> size() + operator[]    迭代器遍历     范围for
        7.输入输出 cin>>  getline(当输入字符串包含空格时用它)  cout<<
        8.容量,增容规则 -> VS基本1.5倍增  g++基本2倍增  reserve+resize
        9.c_str()会返回对象中指向字符数组的指针
      10.重载了 > < == 的比较 按ascll码去比较

三、练习

1.反转字母

class Solution {
public:
    string reverseOnlyLetters(string s) 
    {
        //类似快排的单趟排序
        int left = 0;
        int right = s.size()-1; //因为right代表下标 所以要-1
        while(left < right)     //相遇之前结束
        {
            while(left < right  && !isalpha(s[left]))    //isalpha是判断谁不是一个字符的接口
            {
                ++left;   //不为字符的时候 往后继续走
            }
            while(left < right && !isalpha(s[right]))
            {
                --right;  //不为字符的时候 往后继续走
            }
            swap(s[left] ,s[right] );
            ++left;
            --right;

        }
          return s;
    }

  
};

2.字符串中第一个唯一字符

class Solution {
public:
    int firstUniqChar(string s)
    {   //时间复杂度为O(N) 空间复杂度尾O(1)
        //统计字符出现的次数
        int CountArray[26] = {0} ; //记录字符出现的次数
        int len = s.size();
        for(size_t i = 0 ; i < len ; i++ )
        {
            CountArray[s[i] - 'a']++; //s[i] - 'a'的意思是找到字符在数组中的的相对位置
          
        }
        for(size_t i = 0 ; i < len ; i++ )
        {
            if(CountArray[s[i] - 'a'] == 1)//找第一个只出现一次的字符
            {
                return i ;
            }
            
        }
        return -1;//表示没有找到
    }
};

3.验证一个字符串是否是回文

class Solution {
public:
 bool isLetterOrNumber(char ch)
 {
 return (ch >= '0' && ch <= '9')
 || (ch >= 'a' && ch <= 'z')
 || (ch >= 'A' && ch <= 'Z');
 }
 
 bool isPalindrome(string s) {
 // 先小写字母转换成大写,再进行判断
 for(auto& ch : s)
 {
 if(ch >= 'a' && ch <= 'z')
 ch -= 32;
 }
 
 int begin = 0, end = s.size()-1;
 while(begin < end)
 {
 while(begin < end && !isLetterOrNumber(s[begin]))
 ++begin;
 
 while(begin < end && !isLetterOrNumber(s[end]))
 --end;
 
 if(s[begin] != s[end])
 {
 return false;
 }
 else
 {
 
 ++begin;
 --end;
 }
 }
 
 return true;
 }
};

4.字符串相加

class Solution {
public:
    string addStrings(string num1, string num2)
    {
        string retStr;
         int end1 = num1.size()-1;
         int end2 = num2.size()-1;
         int next = 0;  // 进位
         while(end1 >= 0 || end2 >= 0)
         {
             int val1= 0 ; 
             if(end1 >= 0)
             val1 = num1[end1] - '0';//原本这个位置上的这个值时一个字符中的0-9 会对应相应的 
                                     //ASCLL  要转换成数组中的0-9所以-‘0’
            int val2 = 0;
            if(end2 >= 0 )
            val2 = num2[end2] - '0';
            int ret = val1 + val2 + next;
            if(ret > 9)
            {
                ret -= 10 ;
                next = 1;
            }
            else
            {
                next = 0;
            }
            retStr += ('0'+ret);
            //retStr.insert (retStr.begin() , '0' + ret  );
            --end1;
            --end2;


         }
         if(next == 1)
         retStr += '1';
         reverse(retStr.begin(),retStr.end());
            //retStr.insert (retStr.begin() , '1' );
         return retStr;
    }
};

四、string类的模拟实现

1.实现string类的构造、拷贝构造、赋值运算符重载以及析构函数

class string
{
public:
 /*string()
 :_str(new char[1])
 {*_str = '\0';}
 */
 //string(const char* str = "\0") 错误示范
 //string(const char* str = nullptr) 错误示范
 string(const char* str = "") //注意这里要给上缺省值 很重要
 {
 // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
 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;
};
// 测试
void Teststring()
{
 string s1("hello !!!");
 string s2(s1);
}

这里引入了深浅拷贝的概念

2.浅拷贝:

string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构

造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块

空间被释放多次而引起程序崩溃
 

3.深拷贝

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

 4.传统版本和现代版本的string类

class string
{
public:
 string(const char* str = "")   //const的构造函数
 { 
         // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
    if(nullptr == str)
     {
     assert(false);
     return;
     }
 
     _str = new char[strlen(str) + 1];
     strcpy(_str, str);
 }
 
 string(const string& s)        //非const的构造函数
 : _str(new char[strlen(s._str)+1])
 {
 strcpy(_str, s._str);
 }
 
 string& operator=(const string& s)
 {
     if(this != &s)
     {
     char* pStr = new char[strlen(s._str) + 1];
     strcpy(pStr, s._str);
     delete[] _str;
     _str = pStr;
     }
 
   return *this;
 }
 
char& operator[](size_t pos)
		{
		return _str[pos];
		}


 ~string()
{
     if(_str)
     {
     delete[] _str;
     _str = nullptr;
     }
 }
 
private:
     char* _str;
};
class string
{
public:
 string(const char* str = "")
 {
 if(nullptr == str)
 str = "";
_str = new char[strlen(str) + 1];
 strcpy(_str, str);
 }
 
 string(const string& s)
 : _str(nullptr)
 {
 string strTmp(s._str);
 swap(_str, strTmp);
 }
 
 // 对比下和上面的赋值那个实现比较好?
 string& operator=(string s)
 {
 swap(_str, s._str); 
 return *this;
 }
 
 /*
 string& operator=(const string& s)
 {
 if(this != &s)
 {
 string strTmp(s);
 swap(_str, strTmp._str);
 }
 
 return *this;
 }
 */
 
 ~string()
 {
 if(_str)
 {
 delete[] _str;
 _str = nullptr;
 }
 }
 
private:
 char* _str;
};

5.string类增删查改的模拟实现

namespace hdzc
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

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

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

		// s1(s2)
		string(const string& s)
		{
			// +1是开空间时永远保持给\0多开一个
			_str = new char[s._size + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._size;

			/*_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;*/
		}

		// s1 = s2
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				delete[] _str;
				_str = new char[s._size + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._size;
			}

			return *this;
		}

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

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

			return _str[pos];
		}

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

			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* newstr = new char[n + 1];
				strcpy(newstr, _str);
				delete[] _str;
				_str = newstr;

				_capacity = n;
			}
		}

		void resize(size_t n, char ch = '\0')
		{
			if (n < _size) // 删除数据
			{
				_size = n;
				_str[_size] = '\0';
			}
			else // 插入数据
			{
				// 空间不够先增容
				if (n > _capacity)
					reserve(n);

				for (size_t i = _size; i < n; ++i)
					_str[i] = ch;
				_size = n;
				_str[_size] = '\0';
			}
		}

		// s1.push_back('x');
		void push_back(char ch)
		{
			// 增容
			if (_size == _capacity)
			{
				if (_capacity == 0)
				{
					reserve(2);
				}
				else
				{
					reserve(_capacity * 2);
				}
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		// s1.append("xxxxxxxxxx");
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
				reserve(_size + len);

			strcpy(_str + _size, str);
			_size += len;
		}

		// s1 += 'x'
		string& operator+=(char ch)
		{
			this->push_back(ch);
			return *this;
		}

		// s1 += "xxxxx"
		string& operator+=(const char* str)
		{
			this->append(str);
			return *this;
		}

		string& insert(size_t pos, char ch)
		{
			assert(pos < _size);
			if (_size == _capacity)
			{
				reserve(_capacity * 2);
			}

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

			_str[pos] = ch;
			++_size;

			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;
			while (end >= pos)
			{
				_str[end + len] = _str[end];
				--end;
			}

			strncpy(_str + pos, str, len);
			_size += len;

			return *this;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			if (_size - pos <= len)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

		size_t find(char ch, size_t pos = 0)//找单个字符
		{
			for (size_t i = pos; i < _size; ++i)
			{
				if (_str[i] == ch)
					return i;
			}

			return npos;
		}

		size_t find(const char* str, size_t pos = 0)//找一个字符串
		{
			const char* p = strstr(_str + pos, str);
			if (p == nullptr)
				return npos;
			else
				return p - _str;
		}

		// s1 < s2
		// 实现这两个,其他的比较复用实现
		bool operator<(const string& s)
		{
			return strcmp(_str, s._str) < 0;//比较当前字符的ASCLL码
		}

		bool operator==(const string& s)
		{
			return strcmp(_str, s._str) == 0;//比较当前字符的ASCLL码
		}

		bool operator<=(const string& s)
		{
			return *this < s || *this == s;//比较当前字符的ASCLL码
		}

		bool operator>(const string& s)
		{
			return !(*this <= s);//比较当前字符的ASCLL码
		}

		bool operator>=(const string& s)
		{
			return !(*this < s);
		}

		bool operator!=(const string& s)
		{
			return !(*this == s);
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static size_t npos;
	};

	size_t string::npos = -1;

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

		return out;
	}

	istream& operator >> (istream& in, string& s)
	{
		while (1)
		{
			char ch = in.get();
			if (ch == ' ' || ch == '\n')
				break;
			else
				s += ch;
		}

		return in;
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值