目录
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.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 结尾
希望这篇文章大家有所收获,我们下篇见