当前位置:网站首页>理解string类
理解string类
2022-08-03 14:04:00 【Slow Just Fast】
C++ string类
文章目录
C++关于STL的学习,主要在STL专栏里讲解,这里只讲基础,作为过渡C++的学习过程!
1.为什么要学习string类
1.1string补C语言坑
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问,因此设定了string类
string可以理解为:用来管理动态增长字符数组,这个字符串以’\0’结尾
C语言的str则有长度限制,不够需要扩容
#inlcude<string>
using namespace std;//或using std::string;
int main()
{
string s1;//构造空对象
string s2("hello world!");// 用C格式字符串构造string类对象s2
string s3(s2);//拷贝构造
string s4=s2;//拷贝构造
return 0;
}
补充:C语言检查越界是一个迷操作,随机事件。可能你越界了也查不出来,除非越界位置赋值。
2.编码表(不重要)
2.1ASCII编码表
ASCII编码:用二进制0 1表示字符
2.2Unicode编码表
Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求
Unicode源于一个很简单的想法:将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符,再也不会有乱码了。**它从0开始,为每个符号指定一个编号,这叫做"码点"(code point)**比如,码点U+0639
表示阿拉伯字母Ain
,码点U+0041
表示英语的大写字母A
,码点U+4E25
表示汉字严
我们说Unicode只是一个字符集,只规定字符的二进制代码编号,没规定字符是如何进行存储,所以这也造成了一些问题。比如:汉字严
的 Unicode 是十六进制数4E25
,转换成二进制数足足有15位(100111000100101
),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。也就是说,这个符号至少需要2个字节来表示其它更大的符号,因为需要至少2个字节来表示更大的符号,这就导致了两个问题,第一个是如何区别该编码是Unicode还是ASCII,计算机怎么知道该字符是2个字节还是3个字节甚至更多。第二个问题是,众所周知,英文字母只需要一个字节来进行编码,但是如果用2个字节3个字节甚至更多字节来表示这就会造成相应倍数的存储空间的增加,造成了存储空间上的极大浪费所以最后也出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式来表示Unicode,随着互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,同时也存在UTF-16以及UTF-32,以下我们会对其分别展开讲述。
UTF-8:UTF-8是一种变长的编码方法,字符长度从1个字节到4个字节不等。越是常用的字符,字节越短,最前面的128个字符,只使用1个字节表示,与ASCII码完全相同
UTF-16:UTF-16介于UTF-8和UTF-32之间,它同时结合了定长和边长两种编码方式的特点
UTF-32:UTF-32是最直观的编码方法,每个码点使用四个字节表示,字节内容一一对应码点。比如,码点0就用四个字节的0表示,码点597D就在前面加两个字节的0
2.3GBK编码
GBK全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称:Chinese Internal Code Specification) ,中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,国家技术监督局标准化司、电子工业部科技与质量监督司1995年12月15日联合以技监标函1995 229号文件的形式,将它确定为技术规范指导性文件。2000年已被GB18030-2000《信息交换用汉字编码字符集 基本集的扩充》国家强制标准替代。 [2] 2005年GB18030-2005发布,替代了GB18030-2000
3.标准库中的string类
3.1string类在官方文档中的理解
- string对应utf-8编码—char是一字节
- u16string对应utf-16编码—char16_t是两字节
- u32string对应utf-32编码—char32_t是四个字节
- wstring对应gbk编码—wchar宽字符是两个字节(变向说明了一个中文字符占两个字节)
1.字符串是表示字符序列的类
2.标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作
单字节字符字符串的设计特性
3.string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)
4.string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作
为basic_string的默认参数(根于更多的模板信息请参考basic_string)
5.这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个
类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作
6.在使用string类时,必须包含#include<string>头文件以及using namespace std(string在std命名空间里,std::string);
总结:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
- string在底层实际是:basic_string模板类的别名,typedef basic_string string
- 不能操作多字节或者变长字符的序列
官方文档:字符串 - C++ 参考 (cplusplus.com)
string源码定义:
class string
{
public:
//这里使用引用是为了:为了支持值修改返回对象
char& operator[](size_t pos)
{
return _str[pos];
}
const char& operator[](size_t pos) const
{
return _str[pos];
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
3.2string类常用接口
string类对象的常见构造
(constructor)函数名称 | 功能说明 |
---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char *s) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello world"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty(重点) | 检测字符串释是否空串,是返回true,否则返回false |
clear(重点) | 清空有效字符 |
reserve(重点) | 为字符串预留空间,设置阈值 |
resize(重点) | 将有效字符的个数改成n个,多出的空间用字符填充 |
// size/clear/resize
reserve:调整容量大小,多次reserve,比上次小就不调整,比它大就调整
resize:调整元素个数+初始化
clear:清零元素数据+不改变容量大小
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中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到15个,默认多出位置用缺省值'\0'进行填充
// "aaaaaaaaaa\0\0\0\0\0"
// 注意此时s中有效字符个数已经增加到15个
s.resize(15);
cout << s.size() << endl;
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';
}
}
}
补充注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
- clear()只是将string中有效字符清空,不改变底层空间大小
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用’\0’来填充多出的元素空间,resize(size_t n, char c)用字符来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小
3.3string对象的访问与遍历操作(重点)
函数名称 | 功能说明 |
---|---|
operator[] (重点) | 返回pos位置的字符,const string类对象调用 |
begin+end | 正向迭代器:begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | 反向迭代器:begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
四种迭代器:正向、const正向、反向、const反向迭代器
正向迭代器(string::iterator it):
反向迭代器(string::reverse_iterator rit):
void Teststring()
{
string s1("hello wordl");
const string s2("Hello world");
cout<<s1<<" "<<s2<<endl;
cout<<s1[0]<<" "<<s2[0]<<endl;
s1[0] = 'H'; // s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
cout<<s1<<endl;
}
void Teststring()
{
string s("hello world");
// 3种遍历方式:
// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符
// 另外以下三种方式对于string而言,第一种使用最多
// 1. for+operator[] 中括号+下标
for(size_t i = 0; i < s.size(); ++i)
//s.operator[](i)
cout<<s[i]<<endl;
// 2.迭代器
string::iterator it = s.begin();
while(it != s.end())//end是结束位置的下一个位置
{
cout<<*it<<endl;
++it;
}
string::reverse_iterator rit = s.rbegin();
while(rit != s.rend())
cout<<*rit<<endl;
补充:迭代器可以理解为:像指针一样的东西或者就是指针
// 3.范围for
for(auto ch : s)//ch自动取s的成员赋值给ch
cout<<ch<<endl;
}
3.4string类对象的修改操作(重点)
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符 |
append | 在字符串后追加一个字符串 |
**operator+= (重点) ** | 在字符串后追加字符串str |
c_str(重点) | 返回C语言格式字符串(比较的是地址) |
**find + npos(重点) ** | 从字符串pos位置开始往后找字符,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
void Teststring()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一个字符"hello"
str += 'b'; // 在str后追加一个字符'b'
str += "it"; // 在str后追加一个字符串"it"
cout<<str<<endl;
cout<<str.c_str()<<endl; // 以C语言的方式打印字符串
// 获取file的后缀
string file ("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size()-pos));
cout << suffix << endl;
// npos是string里面的一个静态成员变量
// static const size_t npos = -1;
// 取出url中的域名
string 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);
string address = url.substr(start, finish - start);
cout << address << endl;
// 删除url的协议前缀
pos = url.find("://");
url.erase(0, pos+3);
cout<<url<<endl;
}
//c_str比较
string a="hello world";
string b=a;
if (a.c_str()==b.c_str())
{
cout<<"true"<<endl;
}
else
cout<<"false"<<endl;
结果为false,因为c_str比较的是a和b存字符串位置的地址,与a和b内容相同与否无关
特别注意:
- 在string尾部追加字符时,s.push_back / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好
3.5string类的非成员函数(扩展)
函数 | 功能说明 |
---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
string类的学习主要还是熟能生巧,多刷题多写代码就熟悉了!
4.string类的模拟实现
4.1经典string类问题—浅拷贝
// 为了和标准库区分,此处使用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 world!!!");
String s2(s1);
}
上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝
4.2浅拷贝与深拷贝的理解(重点)
- 浅拷贝—按位拷贝对象
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就发生了访问违规
- 深拷贝—独立分配资源
深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供
4.3传统与现代string写法
1.传统string写法:
class String
{
public:
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _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;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
2.现代string写法:
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
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); swap(_str, strTmp._str); } return *this; } */
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
4.4写时拷贝
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源
参考:
4.5string类的模拟实现
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <assert.h>
namespace bit
{
class string
{
public:
typedef char* iterator;
public:
string(const char* str = "")
{
_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);
this->swap(tmp);
}
string& operator=(string s)
{
this->swap(s);
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
/
// iterator
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
/
// modify
void push_back(char c)
{
if (_size == _capacity)
reserve(_capacity * 2);
_str[_size++] = c;
_str[_size] = '\0';
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
// 实现
void append(const char* str);
string& operator+=(const char* str);
void clear()
{
_size = 0;
_str[_size] = '\0';
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* c_str()const
{
return _str;
}
/
// capacity
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return 0 == _size;
}
void resize(size_t newSize, char c = '\0')
{
if (newSize > _size)
{
// 如果newSize大于底层空间大小,则需要重新开辟空间
if (newSize > _capacity)
{
reserve(newSize);
}
memset(_str + _size, c, newSize - _size);
}
_size = newSize;
_str[newSize] = '\0';
}
void reserve(size_t newCapacity)
{
// 如果新容量大于旧容量,则开辟空间
if (newCapacity > _capacity)
{
char* str = new char[newCapacity + 1];
strcpy(str, _str);
// 释放原来旧空间,然后使用新空间
delete[] _str;
_str = str;
_capacity = newCapacity;
}
}
// access
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
// 作业
bool operator<(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator==(const string& s);
bool operator!=(const string& s);
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const;
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const;
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c);
string& insert(size_t pos, const char* str);
// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len);
private:
friend ostream& operator<<(ostream& _cout, const bit::string& s);
friend istream& operator>>(istream& _cin, bit::string& s);
private:
char* _str;
size_t _capacity;
size_t _size;
};
ostream& operator<<(ostream& _cout, const bit::string& s)
{
// 不能使用这个, 因为string的字符串内部可能会包含\0
// 直接cout时, 是将_str当成char*打印的,遇到内部的\0时后序内容就不打印了
//cout << s._str;
for (size_t i = 0; i < s.size(); ++i)
{
_cout << s[i];
}
return _cout;
}
}
///对自定义的string类进行测试
void TestBitstring()
{
bit::string s1("hello");
s1.push_back(' ');
s1.push_back('b');
s1.push_back('i');
s1 += 't';
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
// 利用迭代器打印string中的元素
bit::string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it;
++it;
}
cout << endl;
// 这里可以看到一个类只要支持的基本的iterator,就支持范围for
for (auto ch : s1)
cout << ch;
cout << endl;
}
5.扩展阅读
5.1扩展内容
边栏推荐
猜你喜欢
如何在 UE4 中制作一扇自动开启的大门
MySQL【存储过程与函数】
利用 NFT 释放网站的潜力
PCL 点云按时间进行分段
[A summary of the sorting and use of activation functions in deep learning]
进程通信的方式
基于.NET 6 的开源访客管理系统
苹果终于认清现实,销量成为优先考虑,iPhone14将不涨价
HCIP Fifteenth Day Notes (Three-layer Architecture of Enterprise Network, VLAN and VLAN Configuration)
利用华为云ECS服务器搭建安防视频监控平台【华为云至简致远】
随机推荐
The Chinese Embassy in Nigeria issued an emergency safety warning for the area near Zuma Rock in Abuja
Chrome browser corresponding driver_chrome mobile browser
552个元宇宙App,70个搞社交,哪款真能交到朋友?
驻冰岛使馆提醒旅冰中国公民务必加强安全防护
树莓派 USB摄像头 实现网络监控( MJPG-Streamer)
secureCRT连接开发板连接不上问题解决
Insert or Merge
用1000行代码统计西安新房价格后,我有一个惊人的发现……
中国菜刀原理与实践
W11或W10系统如何进行休眠?
使用域名注册服务 Domains配置域名【华为云至简致远】
GMapping principle analysis/easy to understand
node项目开发踩坑(一)
“芯片法案”通过后,美光承诺在美国扩产
Nanoprobes Ni-NTA-Nanogold——用于 His 标签标记和检测
[A summary of the sorting and use of activation functions in deep learning]
Tao Te: Engine or baggage?
VLAN experiment
IDO代币预售dapp开发及NFT模式
数据科学家 Agnis Liukis :在ML领域,初学者踩过的5个坑