C++String类使用笔记|详细版

目录

string类的接口使用

string类的模拟实现


考试当中,很多字符串的题目基本以string类的形式出现,工作当中,为了方便,也基本都使用了string类,熟悉它的使用是很必要的

string类的接口使用

string类对象的构造

//构造函数初始化
void test_string1()
{
  string s1;//无参构造 里面有一个\0

  //string (const char* s);
  string s2("hello,word");//用常量字符串初始化

  //string(const string& s) (重点)拷贝构造
  string s3(s2);//拷贝构造
  string s4 = s2;//拷贝构造

  //string(const char* s, size_t n);//
  string s5("abcdef", 4);//用前四个初始化

  //string (size_t n, char c);
  string s6(10, 'x');//用10个字符x初始化

  //string (const string& str, size_t pos, size_t len = npos);
  string s7(s2, 6, 3);//从第pos位置拷贝n个数据

  cout << s1 << endl;//有一个\0
  cout << s2 << endl;//hello,word
  cout << s3 << endl;//hello,word
  cout << s4 << endl;//hello,word
  cout << s5 << endl;//打印abcd
  cout << s6 << endl;//打印xxxxxxxxxx
  cout << s7 << endl;//打印wor

}

string类对象的访问

//赋值string::operator=
void test_string2()
{
  string s1("hello");
  string s2("word");
  s1 = s2;
  cout << s1 << endl;//word
  s1 = "word";
  cout << s1 << endl;//word
  s1 = 'y';
  cout << s1 << endl;//y
}

//赋值char& operator[] (size_t pos);
//const char& operator[] (size_t pos) const;
void test_string4()
{
  string s1("hello");
  s1[0] = 'H';//此时还能修改
  cout << s1 << endl;//打印Hello
  //s1[6];//越界会自动断言报错误	
}

string类对象的遍历

//数组和范围for遍历
void test_string3()
{
  //遍历string的每一个字符
  //1 下标+方括号string::operator[]
  string s1("hello");
  for (size_t i = 0; i < s1.size(); i++)//s1.size()表示大小
  {
    //s1.operator[](i)
    cout << s1[i] << "";//打印hello	
  }
    cout << endl;

  //2.范围for (c++11)//语法糖 底层替换成迭代器 汇编
  for (auto ch : s1)//自动取字符 自动++
  {
    cout << ch << "";
  }
    cout << endl;	 
}

迭代器的遍历(正反向)

//正反向迭代器
void test_string5()
{
  //正向迭代器,用来访问数据结构的
  string s1("ABCDEF");
  string::iterator it = s1.begin();
  while (it != s1.end())//end指向最后一个数据的下一个位置
  {
   (*it) += 1;//可读可写
   cout << *it << "";//BCDEFG
   ++it;
  }
  cout << endl;
	
  //反向迭代器
  string::reverse_iterator rit = s1.rbegin();
  while (rit != s1.rend())//end指向最后一个数据的下一个位置
  {
   (*rit) -= 1;//可读可写
   cout << *rit << "";//打印FEDCBA
   ++rit;//注意还是++
  }
  cout << endl;

  //const_iterator begin() const;
  //注意还有const的 只能读
  //长可以使用auto
}

string类对象的容量操作(reserve和resize)

//测试尾部插数据空间如何扩容
void test_string6()
{
  string s;
  /*s.reserve(1000);*///可以提前默认开辟1000
  size_t sz = s.capacity();
  cout << "making s grow:\n";
  cout << "capacity changed: " << sz << '\n';
  for (int i = 0; i < 1000; ++i)//扩容有消耗
  {
   s.push_back('c');//尾部插数据
   if (sz != s.capacity())
   {
    sz = s.capacity();//容量大小
    cout << "capacity changed: " << sz << '\n';//结果为1.5倍左右
   }
  }
  /*s.reserve(1000);*///可以提前默认开辟1000
  //s.resize(1000);//可以提前默认开辟1000并默认初始化0
  //s.resize(100,x)//也可以默认初始化其他数据
  //都不会缩容数据
}

string类对象resize的使用

//注意resize 分三种考虑 
void test_string8()
{
  //resize再扩容时可以指定初始化
  //分下面三种情况

  string s3("hello world");

  s3.resize(20, 'x');
  cout << s3 << endl;//打印hello worldxxxxxxxxx

  s3.resize(14, 'x');
  cout << s3 << endl;//打印hello worldxxx

  s3.resize(5, 'x');
  cout << s3 << endl;//打印hello 注意容量小 会筛数据

}

string类对象的数据增加操作

//指定位置(头部)插入字符串
void test_string9()
{
  //不高效 连续空间 插入要挪数据 
  string s("hello");
  //头部插入一个字符 迭代器
  //iterator insert (iterator p, char c);
  s.insert(s.begin(), 'x');
  cout << s<< endl;

  //头部位置插入字符串
  // string& insert (size_t pos, const char* s);
  s.insert(0, "word");
  cout << s<< endl;

  //第三个位置插入一个字符 迭代器
  //iterator insert (iterator p, char c);
  s.insert(s.begin()+3, '0');
  cout << s << endl;
}

string类对象的数据删除操作

//删除数据 erase

void test_string10()
{
  //iterator erase(iterator p);
  string s("helloword");
  cout << s << endl;//打印elloword

  s.erase(s.begin());//头删 
  cout << s << endl;//打印elloword

  s.erase(s.begin()+4);//删除第4个
  cout << s << endl;//打印elloord

  s.erase(4, 2);//从第4个位置删除2个
  cout << s << endl;//打印ellod

  s.erase(2);//从第2个位置删除到最后
  cout << s << endl;//打印el
	
}

string类对象的交换操作

void test_string11()
{
  string s1("hello word");
  string s2("string");

  //c++98
  s1.swap(s2);//效率高 直接交换指向位置

  swap(s1, s2);//效率低 用到了拷贝构造和赋值 是深拷贝
}

string类对象的查找操作

//string::c_str   返回C形式的字符串
//string::find    指定位置查找函数
//string::substr  返回字串 从pos位置返回len个字符
void test_string12()
{
  string s1("hello word");
  cout << s1.c_str() << endl;//和C的接口进行兼容

  //查找返回文件后缀
  string file("strng.cpp");
  size_t pos = file.find('.');//还有rfind
  if (pos != string::npos)
  {
   string suffix = file.substr(pos);//有多少取多少
   cout << file << "后缀为" << suffix << endl;
  }
  else
  {
   cout << "没有找到后缀" << endl;
  }
}

操作实践:分别取出网址的协议、域名、资源名

void test_string13() 
{
  //网址:协议、域名、uri(资源名)
  //取出url网址域名
  string url1("https://m.cplusplus.com/reference/string/string/substr/");

  string& ur1 = url1;//有另外的协议可以直接换

  //取协议名
  string protocol;
  size_t pos1 = ur1.find(":");
  if (pos1 != string::npos)
  {
   protocol = ur1.substr(0, pos1);//去字符串
   cout << "协议名protocol为:" << protocol << endl;
  }
  else
  {
   cout << "非法网址" << endl;
  }
  //取域名
  string domin;
  size_t pos2 = ur1.find("/",pos1+3);//从指定位置找
  if (pos1 != string::npos)
  {
   domin = ur1.substr(pos1 + 3, pos2 - (pos1 + 3));
   cout << "域名domin为:" << domin << endl;
  }
  else
  {
   cout << "非法" << endl;
  }
  //取uri
  string uri = ur1.substr(pos2+1);//取到最后
  cout << "资源名uri为:" << uri << endl;

}

string类的模拟实现

模拟实现string类可以帮助我们更好的理解底层是如何实现具体的函数实现

我们分两个文件来写 string.h 头文件中

//string.h 头文件中

namespace mys//自己的命名空间
{
//先实现一个简单的string,
//考虑管理深浅拷贝问题,考虑增删查改
//全考虑增删查改

class string
{
public:

	//迭代器模拟
	typedef char* iterator;//普通迭代器可修改
	typedef char* const_iterator;//const迭代器 前面加const不可修改

	iterator begin()
	{
		return _str;
	}

	iterator end()//最后一个字符的下一个
	{
		return _str+_size;
	}

	const_iterator begin()const//注意前后都要加const
	{
		return _str;
	}

	const_iterator end()const//最后一个字符的下一个
	{
		return _str + _size;
	}
		
	//全缺省构造
	//:_str(nullptr)//标准库不是给空指针,给的空串 
	//  "\0" ""  '\0'
	string(const char* str="")
		:_size(strlen(str))
		,_capacity(_size)
	{
		cout << "调用构造" << endl;
		_str = new char[_capacity + 1];//写到里面先和执行初始化列表的
		strcpy(_str, str);//会拷贝\0过去
	}

	//拷贝构造 现代写法
	void swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}
	string(const string &s)
		:_str(nullptr)
		,_size(0)
		,_capacity(0)
	{
		cout << "调用拷贝" << endl;
		string tmp(s._str);
		swap(tmp);
	}
		
	//赋值(现代写法)
	//s1=s3
	string& operator=(const string& s)
	{
		if (this != &s)
		{
			cout << "调用赋值" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		return *this;
	}

	//析构
	~string()
	{
		if (_str)
		{
			cout << "调用析构" << endl;
			delete[]_str;
			_str = nullptr;
			_size = _capacity = 0;
		}
	}
	//成员函数c_str
	const char* c_str()const //获取C形式的字符串
	{
		return _str;
	}
		
	//运算符重载 修改pos位置字符 也可以遍历
	char& operator[](size_t pos)//返回字符位置的引用 可读可写
	{
		assert(pos < _size);//底层这里形成string类自动检查
		return _str[pos];
	}
		
	//返回const 字符 不能修改 
	const char& operator[](size_t pos)const//
	{
		assert(pos < _size);//
		return _str[pos];
	}

	//空间大小
	size_t size()const//
	{
		return _size;
	}
	//容量大小
	size_t capcacity()const
	{
		return _capacity;
	}

	//赋值重载(常用
	string& operator+=(char ch)//添加字符使用+=返回值
	{
		cout << "调用赋值重载push_back" << endl;
		push_back(ch);//+=底层复用push_back
		return *this;
	}
	string& operator+=(const char* str)//添加字符使用+=返回值
	{
		cout << "调用赋值重载append" << endl;
		append(str);//+=底层复用push_back
		return *this;
	}

	//注意分三种考虑
	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';
		}
	}

	//扩容函数
	void reserve(size_t n)
	{
		//判断
		if (n > _capacity)
		{
			//1.先开辟新空间
			char* tmp = new char[n+ 1];
			//2.旧空间到新空间
			strcpy(tmp, _str);
			//3.释放旧空间
			delete[]_str;
			//4.让指针指向新空间
			_str = tmp;
			//5.更新容量
			_capacity = n;
		}
	}

	void push_back(char ch)//添加单个字符
	{
		//先看满没满 满了就扩容
		if (_size == _capacity)
		{
			reserve(_capacity==0?4:_capacity*2);//扩容2倍
		}
		//到这里空间足够 尾部(_size位置)添加数据
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';
		//insert(_size, ch);//还可以复用insert
	}

	//添加字符串
	void append(const char* str)//
	{
		//扩大2倍不一定够 
		//len 表示实际实际需要长度:现在有的数量+新的字符数量
		size_t len = _size + strlen(str);//
		if (len > _capacity)//如果超过容量
		{
			reserve(len);//扩大实际需要的长度
		}
		strcpy(_str + _size, str);
		_size = len;//更新_size

		//insert(_size, str); 还可以复用insert
	}
		
	//指定位置插入字符
	string& insert(size_t pos,  char ch)//字符
	{
		//判断合法性
		assert(pos<_size);
		//从\0开始挪
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);//扩容2倍
		}
			
		//size_t end = _size;//这种头插会越界#
		size_t end = _size+1;

		//while (end >= pos)#
		while (end > pos)//这时等于0就停止 
		{
			//_str[end + 1] = _str[end];#
			_str[end] = _str[end-1];
			--end;
		}
		_str[pos] = ch;
		_size++;
		return *this;

	}

	//指定位置插入字符串
	//void insert(size_t pos, const char* str)
	string& insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
			
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}

		//strcpy(_str + _size, str);//尽量不用 会拷贝\0 就停了
		size_t end = _size + len;//往后挪动len个位置

		while (end > pos+len-1)//这时等于pos+len就停止 
		{
			//_str[end + 1] = _str[end];
			_str[end] = _str[end-len];//避开
			--end;
		}
		strncpy(_str + pos, str, len);
		_size+=len;
		return *this;
	}

	//指定位置删除
	string& earse(size_t pos,size_t len=npos)//
	{
		assert(pos < _size);
		//
		if (len == npos || pos + len >= _size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		//挪数据覆盖
		else
		{
			size_t begin = pos + len;
			while (begin <= _size)
			{
				_str[begin - len] = _str[begin];
				++begin;
			}
			_size -= len;
		}
		return *this;
	}

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

	//查找字符串
	size_t find(const char* str,size_t pos=0)
	{
		const char* p=strstr(_str + pos, str);
		if (p == nullptr)
		{
			return npos;
		}
		//返回下标
		return p - _str;
	}

private:
	char* _str;//字符串的指针 
	size_t _size;//有效字符 增删查改 要扩容
	size_t _capacity;//有效字符空间 不含\0
	const static size_t npos;//
};
	
const size_t string:: npos = -1;

//流插入 输出
ostream& operator<<(ostream& out,const string& s)
{
	cout << "调用流(输出)" << endl;
	for (auto ch : s)//有多少打印多少
	{
		out << ch;
	}
	return out;//连续流输出
}

//流提取 输入
istream& operator>>(istream& in, string& s)
{
	cout << "调用流提取(输入)" << endl;
	char ch;
	ch=in.get();
	while (ch != ' ' && ch != '\n')
	{
		s += ch;//存在连续扩容 
		ch = in.get();
	}
	return in;//连续流输入
}

//运算符重载 
//放在类外 在类内考虑this
//在外面复用c语言库
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str())<0;
}

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

bool operator<=(const string& s1, const string& s2)
{
	return s1=(const string& s1, const string& s2)
{
	return !(s1<=s2);
}

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

}

test.cpp实现文件中

//模拟实现 
void test_string11()
{
  mys::string s1("hello word");
  cout << s1.c_str() << endl;//打印hello word
  s1[0] = 'x';//char& operator[](size_t pos)
  cout << s1.c_str() << endl;//打印xello word

  //遍历
  for (size_t i = 0; i < s1.size(); i++)
  {
   cout << s1[i] << "";打印xello word
  }
  cout << endl;
  }

//拷贝构造
void test_string22()
{
  //此时没有写拷贝构造
  //析构两次 赋值会相互改 
  mys::string s1("hello word");
  mys::string s2(s1);
  cout << s1.c_str() << endl;
  cout << s2.c_str() << endl;
  s1[0] = 'x';
  cout << s1.c_str() << endl;//深拷贝其中一个修改不影响另一个
  cout << s2.c_str() << endl;
}

//赋值
void test_string33()
{
  //s1 s3是两个存在的对象 
  mys::string s1("hello word");
  mys::string s2(s1);//拷贝
  mys::string s3("1111111");
  s1 = s3;//两个存在的对象赋值 不写赋值也会报错 
  cout << s1.c_str() << endl;
  cout << s2.c_str() << endl;
  cout << s3.c_str() << endl;

  s1 = s1;//考虑自己给自己赋值 
  cout << s1.c_str() << endl;
}

//尾部添加单个字符
void test_string55()
{
  mys::string s1("hello word");
  s1.push_back('!');
  s1.push_back('!');
  cout << s1.c_str() << endl;//打印hello word!!

  s1 += '@';//
  s1 += '@';
  s1 += '@';
  cout << s1.c_str() << endl;

  s1 += "myfriend";
  cout << s1.c_str() << endl;
}

//遍历字符
//调用const版本的遍历 
void func(const mys::string& s)//传参不改变 尽量加const 注意权限
{

  for (size_t i = 0; i < s.size(); ++i)
  {
   cout << s[i] << "";
  }
  mys::string::const_iterator it = s.begin();
  while (it != s.end())
  {
   cout << *it << "";
   ++it;
  }
  cout << endl;

}

//普通遍历
void test_string66()
{
  mys::string s1("hello world");
  // 下标遍历 char& operator[](size_t pos)
  for (size_t i = 0; i < s1.size(); ++i)
  {
    cout << s1[i] << "";
  }
  cout << endl;

  //迭代器遍历
  mys::string::iterator it = s1.begin();
  while (it != s1.end())
  {
    cout << *it << "";
    ++it;
  }
  cout << endl;

  //范围for
  for (auto& ch : s1)//底层就是被替换成迭代器
  {
   //ch -= 1;//传引用还可以修改
   cout << ch << "";
  }
  cout << endl;

  func(s1);
}

//指定位置插入和删除
void test_string77()
{
  mys::string s1("hello world");
  s1.insert(6, '@');
  cout << s1.c_str() << endl;//打印hello @world
  s1.insert(0, '@');
  cout << s1.c_str() << endl;//打印@hello @world
  s1.insert(0, "xxxx");
  cout << s1.c_str() << endl;//打印xxxx@hello @world
  s1.earse(0, 4);//删除
  cout << s1.c_str() << endl;//打印@hello @world
  s1.earse(5);
  cout << s1.c_str() << endl;//打印@hell
}

//流插入
void test_string88()
{
  /*mys::string s1("hello world");
  cout << s1 << endl;*/
  mys::string s2;
  cin >> s2;
  cout << s2 << endl;
}

//注意resize的使用 
void test_string99()
{
  mys::string s3("hello word");
  s3.resize(20, 'x');
  cout << s3.c_str() << endl;//打印hello worldxxxxxxxxx

  s3.resize(14, 'x');
  cout << s3.c_str() << endl;//打印hello worldxxx

  s3.resize(5, 'x');
  cout << s3.c_str() << endl;//打印hello 注意容量小 会筛数据
}

总结:

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

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

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

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

5.string类,本质是一个模板,是管理动态增长的字符数组,以 \0 结尾

希望这篇文章大家有所收获,我们下篇见

你可能感兴趣的:(C++编程,c++,java,开发语言)