介绍常用的几个string接口函数,如果需要学习更多的请参考官方文档: string构造函数.
函数名称 | 功能说明 |
---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
string (const string& str, size_t pos, size_t len = npos) | 从str对象中由pos位置开始截取len个长度的字符,len > str长度就结束 |
string (const char* s, size_t n) | 从s指向的字符数组中复制前n个字符。 |
void functest()
{
//函数原型:string()
string s1;
//调用默认构造函数创建一个string对象,对象的内容是空字符串
//类似于这种初始化方式:string s1("")
//函数原型:string(const char* s)
string s2("hello cpp");
//调用string的构造函数用字符串去初始化一个string对象
//函数原型:string(const string&s)
string s3(s2);
//调用string类的拷贝构造函数创建s3对象
//函数原型:string (const string& str, size_t pos, size_t len = npos)
string s4(s3, 0, 5);
//从s3对象中由0位置开始截取5个长度的字符
char arr[] = "https://blog.csdn.net/m0_53421868?spm=1000.2115.3001.5343";
//函数原型:string (const char* s, size_t n)
string s5(arr, 5);
//截取字符数组的n个长度字符
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
}
函数名称 | 功能说明 |
---|---|
operator[] (重点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持 |
使用正向迭代器遍历string对象
void functest()
{
string s1;
string s2("hello cpp");
//返回字符串首字符的迭代器给sc,让sc指向这个字符
string::iterator sc = s2.begin();
while (sc != s2.end())
{
cout << *sc << " ";
sc++;
}
}
使用反向迭代器遍历string对象
void functest()
{
string s1;
string s2("abcdefg");
//返回最后一个字符的迭代器让sc指向
string::reverse_iterator sc = s2.rbegin();
while (sc != s2.rend())
{
cout << *sc << " ";
++sc;
}
}
而以上两种迭代器都是可以去修改对象的值的,还有一种只读的迭代器不允许修改对象的值
void functest()
{
string s1;
string s2("abcdefg");
string::const_iterator sc = s2.begin();
while (sc != s2.end())
{
//可读但不能修改
cout << *sc++ ;
}
}
反向只读迭代器遍历string对象
void functest()
{
string s1;
string s2("abcdefg");
//返回const_reverse_iterator 的迭代器支持反向遍历操作,
//但不支持修改
string::const_reverse_iterator sc = s2.rbegin();
while (sc != s2.rend())
{
cout << *sc++;
}
}
总结:以上几种迭代器的操作比较简单,读者可以结合文档自己去使用
链接: 做题.
题目描述:
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
找出字符串中只出现一次的字符可以采用计数的方法,记录每个字符在数组中出现的位置统计他出现的次数,最后只出现一次的字符就是我们要找的字符
范围for + operator[]
class Solution {
public:
int firstUniqChar(string s) {
int count[26] = {0};
for(auto e : s)
{
//记录每个字符在count数组中出现的次数(相对映射)
count[e - 'a']++;
}
for(size_t i = 0; i < s.size(); i++)
{
//检查某个字符映射在数组中出现的次数,
//如果等于1就表示只出现一次
if(count[s[i] - 'a'] == 1)
{
return i;
}
}
return -1;
}
};
使用迭代器遍历对象的源字符串中的字符再通过count数组计数
class Solution {
public:
int firstUniqChar(string s) {
int count[26] = { 0 };
string::iterator sc = s.begin();
//sc接受s.begin返回的迭代器
while (sc != s.end())
{
count[*sc - 'a']++;
sc++;
}
for (size_t i = 0; i < s.size(); i++)
{
if (count[s[i] - 'a'] == 1)
{
return i;
}
}
return -1;
}
};
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 在字符串中搜索由参数指定的序列的最后一次出现时的下标位置。 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
insert | 指定位置pos处插入一个字符串 |
erase | 从指定pos位置开始删除len个长度的字符 |
插入操作:
void functest2()
{
string s;
s.push_back('h');
s.push_back('e');
s.push_back('l');
s.push_back('l');
s.push_back('o');
cout << s << endl;// hello
s.append("word");// helloword
cout << s << endl;
//append还可以指定一个迭代器区间,
//将这段区间的字符串追加到s对象
// 函数原型:string& append (const string& str, size_t subpos, size_t sublen);
string s3("defgh");
s.append(s3, 2,3);
//从字符串中下标位置为2的字符起开始截取3个字符追加到s对象
cout << s << endl;//hellowordfgh
//string成员函数operator+=同样也支持追加字符串的操作
s += "abc";
cout << s << endl; //hellowordfghabc
//指定位置插入
//函数原型:string& insert (size_t pos, const char* s);
string s("abcdefg");
s.insert(0,'c');//err,参数是字符串,这样使用不对
s.insert(0, "x");
//0位置处插入一个x,整体字符串往后挪动,不推荐使用,挪动数据的效率太低
}
需要注意的一些string成员函数c_str
string s("hello word");
cout << s << endl;
//自定义类型的s在输出的时候会
//调用重载的 operator<< (ostream& os, const string& str);
cout << s.c_str() << endl;
//s.c_str()函数返回的是一个c风格的字符串他是内置类型,
//使用的是全局的operator<<(cout, const char *)
cout << "------------------" << endl;
//所以会在输出的时候各不相同
s.resize(20);
s += "!!!";
cout << s << endl;
cout << s.c_str() << endl;
效果:
1、调用内置类型的operator<<(cout, const char *) 输出字符串的时候遇到‘\0’就会停止
2、调用自定义类型的operator<< (ostream& os, const string& str)即使遇到‘\0’也不会停止,只有把字符串给完全遍历完了才会停止遍历
删除操作:
void functest3()
{
string s("abcdefg");
//函数原型:string& erase (size_t pos = 0, size_t len = npos);
//函数功能:指定pos位置处开始,删除len个长度的字符
s.erase(1,2);
cout << s << endl;//adefg
}
查找操作:
从前往后
//string拷贝构造函数原型: 这里会给缺省值
//string (const string& str, size_t pos, size_t len = npos);
string buff1("test.cpp");
int pos = buff1.find('.');
if (pos != string::npos)
//npos值是-1,类型是无符号整形,所以是整形的最大值,字符串并不会存储这么长
{
string buff2(buff1,pos, buff1.size() - pos);
//调用拷贝构造函数创建buff2对象,
//从pos位置开始截取len个长度的字符串创建一个string对象
//也可以使用这种写法去截取后缀:
//即使使用了函数提供的缺省值也并不用过于担心,npos是最大值也好
//只会截取有效内容,超过不管
string buff3(buff1,pos);
//不调用拷贝构造函数同样也能做到, substr截取
//函数原型:string substr (size_t pos = 0, size_t len = npos) const;
string buff4 = buff1.substr(pos);
cout << buff2 << endl;//.cpp
}
从后往前
//函数原型:
//size_t rfind (const string& str, size_t pos = npos) const;
//函数功能是返回指定参数在字符串中最后一次出现的位置
string buff1("test.cpp.zip");
int pos = buff1.rfind('.');
if (pos != string::npos)
{
string buff2(buff1, pos);
cout << buff2 << endl;
}
使用string成员函数查找网络域名跟协议使用举例,详细解释请看注释
//返回协议名
string GetAgreeMent(const string& s)
{
//查找"://",找到返回该字符串的起始下标位置
size_t pos = s.find("://");
if (pos != string::npos)
{
//由于是左闭右开区间【0,5),所以只需要pos - 0就能计算出协议名的长度
return s.substr(0, pos - 0);
}
else
{
//找不到返回空串
return string();
}
}
//返回域名
string GetDomain(const string& s)
{
//查找"://",找到返回该字符串的起始下标位置
size_t pos = s.find("://");
if (pos != string::npos)
{
//计算出域名的起始下标位置,从这个位置开始查找"/"
size_t start = pos + 3;
size_t end = s.find("/", start);
if (end != string::npos)
{
//同样的左闭右开区间,开区间的位置减去闭区间的位置就是字符串的长度
return s.substr(start, end - start);
}
else
{
//找不到返回空串
return string();
}
}
else
{
//找不到返回空串
return string();
}
}
void functest4()
{
string url1 = "https://blog.csdn.net/m0_53421868?spm=1000.2115.3001.5343";
string url2 = "https://bbs.csdn.net/forums/mzt";
cout << GetDomain(url1) << endl;
cout << GetAgreeMent(url1) << endl;
cout << GetDomain(url2) << endl;
cout << GetAgreeMent(url2) << endl;
}
string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size(重点) | 统计字符个数 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符,将size置零 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
测试:
string s;
s.resize(10);//插入10个'\0',默认以'\0'填充
cout << s << endl;
string s1;
s1.resize(10,'x');//指定插入10个字符,以'x'填充
cout << s1 << endl;
string s3("hello word");
s3.resize(20,'x'); //将空间扩容到20,多出来的空间用'x'填充,并且是尾插的方式
cout << s3 << endl;
需要注意的一些增容函数
与resize函数相似的有reserve 他并不会改变空间的内容,只做增容
浅拷贝一般都是在拷贝构造一个对象的时候完成值拷贝的一个过程,因为我们不写编译器默认的生成的拷贝构造函数完成的是浅拷贝,这样对象出了作用域开始调用析构函数的时候会导致同一块空间被释放两次,就会引发程序崩溃
namespace mzt
{
class string
{
public:
//重载operator<<
friend ostream &operator<< (ostream &out, string &str)
{
out << str._str << endl;
return out;
}
string(const char* str = "")
: _str(new char[strlen(str) + 1])
, _size(0)
,_capacity(0)
{
strcpy(_str, str);
}
//我们不写编译器会默认生成一个拷贝构造函数完成浅拷贝
/* string(const string & str)
: _str(new char[strlen(str._str) + 1])
{
strcpy(_str, str._str);
}*/
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
size_t _capacity;
size_t _size;
};
void stringtest()
{
mzt::string s("hello world");
mzt::string s1(s);
cout << s;
cout << s1;
}
}
即使打印出了hello world,但是这个程序还是存在问题,因为两个对象的_str指针指向的是同一块空间所以当被delete的时候就会被析构两次,这是不被允许的,解决办法深拷贝,重新创建一个空间,并把值存过去,让两个对象之间的内容互不影响
namespace mzt
{
class string
{
public:
friend ostream &operator<< (ostream &out, string &str)
{
out << str._str << endl;
return out;
}
string(const char* str = "")
: _str(new char[strlen(str) + 1])
, _size(0)
,_capacity(0)
{
strcpy(_str, str);
}
//我们不写编译器会默认生成一个拷贝构造函数完成浅拷贝
//解决办法深拷贝,开辟一块新的空间,把值拷贝过去
string(const string & str)
: _str(new char[strlen(str._str) + 1])
{
strcpy(_str, str._str);
}
//重载operator=也是一样的做法,
//开空间拷贝值避免出现浅拷贝的问题
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
size_t _capacity;
size_t _size;
};
void stringtest()
{
mzt::string s("hello world");
mzt::string s1(s);
cout << s;
cout << s1;
}
}
推荐使用现代深拷贝的方法:
原因1: 代码简洁
原因2:可读性强
//我们不写编译器会默认生成一个拷贝构造函数完成浅拷贝
//解决办法做深拷贝
string(const string &s)
: _str(nullptr)
//_str必须初始化为nullptr,才去交换tmp指针,
//否则_str就是野指针了,当tmp出了作用域析构野指针会有非法内存访问
{
//调用构造函数利用s._str做参数构造临时对象
string tmp(s._str);
//将临时对象的指针_str和this._str一交换
swap(tmp._str, _str);
//临时对象出了作用域就销毁了,会自动调用它的析构函数
}
//拷贝赋值运算符现代写法
string& operator=(string s)
//s通过调用拷贝构造函数完成的深拷贝
{
//还是一样的思路,由于拷贝构造函数已经被我们实现了,
//所以就不会存在浅拷贝的问题,所以通过值传递,即使
//栈帧被销毁了,两个对象也互不影响,这也是一种复用的方法
//直接上手交换指针this._str和s._str
swap(_str, s._str);
return *this;
}
注意:想要复用拷贝构造函数,这里的_str必须初始化为nullptr,否则程序是会有隐患的
string(const string &s)
:_str(nullptr)
//_str必须初始化为nullptr,才去交换tmp对象的_str指针,
//否则_str就是野指针了,
//当tmp出了作用域析构野指针会有非法内存访问
{
//利用s._str构造临时对象
string tmp(s._str);
swap(tmp._str, _str);
}
//构造函数
string(const char* str)
:_str(new char[strlen(str) + 1])
,_size(strlen(str))
,_capacity(_size)
//_capacity建议给strlen(str),
//即使给strlen(str) + 1也可以
{
//初始化新对象的_str
strcpy(_str, str);
}
//拷贝构造
string(const string& s)
:_str(nullptr)
//指针初始化给nullptr,为了交换给tmp对象,
//即使tmp对象出了作用域析构的时候也不受影响
,_size(0)
,_capacity(0)
{
//构造一个tmp对象,交换tmp对象和对象的引用的成员属性
string tmp(s._str);
//在string中自定义的交换函数,详细看下面
Swap(tmp);
}
//交换
void Swap(string& tmp)
{
//调用全局的模板交换函数,交换对象的成员变量
::swap(tmp._size,_size);
::swap(tmp._capacity, _capacity);
::swap(tmp._str, _str);
}
在string定义一个局部的交换函数,调用C++模板库的全局函数
从官网的文档可以看出swap是一个模板函数,如果直接交换两个对象会涉及到三个深拷贝的问题,所以使用交换对象的成员这种方式会更好,同样的拷贝赋值运算符也是一样的要交换成员变量就调用swap函数就行
//拷贝赋值运算符
string& operator=(string s)
//调用拷贝构造函数创建的s对象,这里是值传递并不会影响到外面对象
{
//交换s和this对象的成员变量
Swap(s);
return *this;
}
加上const,即使来调用这个成员函数的this的是属于const权限也可以,非const权限也可以
//返回对象的大小
int size() const
{
return _size;
}
//返回C风格的字符串
char* C_str() const
{
return _str;
}
//可读operator[]
const char& operator[](size_t i) const
{
assert(i < _size);
return _str[i];
//返回下标为i位置的字符,const修饰的是this,
//所以this对象是不能修改的,并且返回引用的时候也要加const
}
//可读可写operator[],简单粗暴
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
typedef char* iterator;
//定义begin迭代器
iterator begin()
{
return _str;
}
//定义end迭代器
iterator end()
{
return _str + _size;
}
范围for会被编译器替换成迭代器形式,范围for的底层是有迭代器支持的,这里为了让读者更醒目的看到现象,那么就用范围for遍历一次对象证明
开始遍历:
for (auto ch : s)
{
cout<< ch <<" ";
}
效果:
迭代器没去掉前可以跑出结果,如果当迭代器去掉后,程序立马跑出一连串的错误信息
void push_back(char ch)
{
//空间满了调用reverse扩容
if (_size == _capacity)
{
//原来空间上扩容二倍
reverse(_capacity * 2);
}
//扩容后在字符串后面加一个‘\0’
_str[_size] = ch;
_str[_size + 1] = '\0';
_size++;
}
//追加一个字符串
void append(const char * str)
{
//统计需要增容多少的空间
size_t len = _size + strlen(str);
if (len > _capacity)
{
//扩容len个长度
reverse(len);
}
//将字符串str追加在_str + _size的位置处
strcpy(_str + _size, str);
//更新_size的位置
_size = len;
}
1、reserve (重点) 为字符串预留空间,空间不够就扩容,够就不需要处理
2、resize (重点) 将有效字符的个数该成n个,多出的空间用字符c填充
//开空间并初始化,size也要动
void resize(size_t n, char ch = '\0')
{
//如果n要小于size,那么就将_str的大小控制在n,有效字符也控制在n
if (n < _size)
{
int end = _size - n;
_str[n] = '\0';
_size -= end;
}
else
{
//n大于实际空间的大小,从_size位置开始初始化后面的空间
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size += n;
_str[_size] = '\0'
}
}
//扩容不修值
void reverse(size_t size)
{
//空间不够,扩容至size大小
if (size > _capacity)
{
//多开一个空间是为了保存‘\0’
char* tmp = new char[size + 1];
//释放旧的空间
delete[]_str;
//让_str指向新的空间,并把值给拷贝过去
strcpy(tmp,_str);
_str = tmp;
}
else
{
//空间够不做扩容处理
return;
}
}
//在pos之前插入数据
string& insert(size_t pos,char ch)
{
assert(pos <= _size);
//空间满了就扩容
if (_size == _capacity)
{
reverse(_capacity == 0 ? 4 : _capacity * 2);
}
//整体字符从‘\0’位置开始往后挪动1个位置
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
//在第一个位置放入ch
_str[end] = ch;
_size++;
return *this;
}
//pos之前插入字符串
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (len + _size > _capacity)
{
reverse(len + _size);
}
//【end,pos】区间的字符整体往后挪动len个长度,
char* end = _str + _size;
while (end >= _str + pos)
{
*(end + len) = *end;
end--;
}
//从pos位置开始将str的字符串覆盖在这个位置处,一直到走到len的位置
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
//指定删除pos位置的len个长度的空间
string& Erese(size_t pos, size_t n = npos)
{
assert(pos <= _size);
size_t len = _size - pos;
//剩余的长度小于要删除的长度,则后面的字符全部删除
if (n >= len)
{
_str[pos] = '\0';
_size = pos;
}
//剩余长度大于删除的长度,只将后面的字符往前覆盖掉前面len个长度的字符
else
{
strcpy(_str + pos, _str + pos + n);
_size -= n;
}
return *this;
}
//查找字符
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
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)
{
assert(pos < _size);
const char* ret = strstr(_str + pos, str);
if (ret)
return ret - _str;
else
return npos;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
friend istream& operator>>(istream& in,string &s)
{
s.clear();
char ch = in.get();
//cin遇到 ' '和‘\n’不再读取
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
//返回对象可以连续输入
return in;
}
friend ostream& operator<<(ostream& out, string& s)
{
for (auto ch : s)
{
cout << ch;
}
//返回对象可以连续输出
return out;
}