目录
- 一. STL概括
- 二. string
- 2.1 编码
- 2.2 string接口函数
- 2.3 string部分接口模拟实现
- *总览:*
- 成员变量:
- 构造,拷贝构造,析构函数:
- 赋值重载: string& operator=(const string& s)
- []重载: char& operator[](size_t pos)
- 迭代器: iterator
- 尾插: push_back()和append()
- 尾插: string& operator+=(const char* str)
- 关系运算符重载: < == > != <= >=
- 扩容: reserve()和resize():
- pos位置插入: insert()
- 删除pos位置字符: erase()
- 查找: find()
- 交换: swap()
- 输出c语言字符串: c_str
- 其他接口: size(),capacity(),clear()
- *全局函数(非成员函数)*
- 2.4 其他
注:这里是初学者的笔记,可能会出错,如果有错,希望你能提醒我.
一. STL概括
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且
是一个包罗数据结构与算法的软件框架。
1.1 STL的六大组件
二. string
string是一种对字符串管理的类.
2.1 编码
1.ASCII编码
大小为一个char,从0~127;
2.unicode
为了解决ASCII只能编码英文字符的问题;
编码方式
统一码是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。统一码用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
UTF-8兼容ASCII
字符或者汉字存储在计算机中是以二进制的形式存储,存入的编码格式和读取的编码格式要对应才能显示原来信息,否则就会出现乱码.
2.2 string接口函数
2.3 string部分接口模拟实现
总览:
#pragma once
#include <iostream>
#include <string>
#include <assert.h>
using namespace std;
namespace kozen
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
string(const char* str = "") //"" 默认带有\0 ==>等价"\0"
:_size(strlen(str))
{
//_capacity = _size;
_capacity = _size == 0 ? 3 : _size; //防止_capacity = 0 2倍数扩容问题
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& s)
:_size(s._size)
, _capacity(s._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
~string()
{
//assert(_str != nullptr);
delete[] _str;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
char& operator[](size_t pos)
{
assert(pos >= 0 && pos < _size); //左闭右开,_size对应/0
return _str[pos];
}
const char& operator[](size_t pos) const //const string
{
assert(pos >= 0 && pos < _size);
return _str[pos];
}
//赋值,s1 = s2 ,已存在
string& operator=(const string& s)
{
//防止s1 = s1,空间释放导致数据丢失
//防止new失败
if (this != &s)
{
char* tmp = new char[s._capacity + 1]; //new失败,抛异常
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
iterator begin()
{
//cout << "iterator begin()" << endl;
return _str;
}
iterator end()
{
return _str + _size;
}
//需要const版本和非const版本
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
void push_back(char ch)
{
//if (_size + 1 > _capacity)
//{
// reserve(2 * _capacity);
//}
//_str[_size++] = ch;
//_str[_size] = '\0';
insert(_size, ch);
}
void append(const char* str)
{
//size_t len = strlen(str);
检查,扩容
//if (_size + len > _capacity)
//{
// reserve(_size + len); //这里不能2倍扩容,_size + len可能大于2*_capacity
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
bool operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool operator>=(const string& s) const
{
return (*this == s) || (*this > s);
}
bool operator<(const string& s) const
{
return !(*this >= s);
}
bool operator<=(const string& s) const
{
return !(*this > s);
}
bool operator!=(const string& s) const
{
return !(s == *this);
}
int compare(const string& s) const
{
return strcmp(_str, s._str);
}
string& insert(size_t pos, char ch)
{
if (_size + 1 > _capacity)
{
reserve(2 * _capacity);
}
//2.0 当pos=0时
int end = _size + 1;
while (pos < end)
{
_str[end] = _str[end - 1];
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);
}
//挪动数据 2.0
int end = _size + 1;
while (pos < end)
{
_str[end - 1 + len] = _str[end - 1]; //当len=1,即插入一个字符,_str[end]=_str[end-1];
end--;
}
拷贝 2.0
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
void erase(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
//len = (len == npos ? _size : len);
if (len == npos || pos + len >= _size) //防止等于npos时溢出
{
_str[pos] = '\0';
_size = pos;
}
else
{
//2.0
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
size_t find(char ch, size_t pos = 0) const
{
assert(pos < _size);
for (int i = pos; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0) const
{
assert(pos < _size);
char* p = strstr(_str + pos, str);
if (nullptr == p)
{
return npos;
}
return p - _str;
}
void reserve(size_t n)
{
if (n > _capacity) //不轻易缩容
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//2.0
void resize(size_t n, char ch = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else if (n > _size)
{
if (n > _capacity)
{
reserve(n);
}
while (_size < n)
{
_str[_size++] = ch;
}
_str[_size] = '\0';
}
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
void clear()
{
_size = 0;
_str[_size] = '\0';
}
public:
const static size_t npos;
private:
char* _str;
size_t _size;
size_t _capacity;
//另一种写法,不推荐(语法奇怪)
// 只支持int
//static const size_t n = -1;
// 不支持别的类型
//static const double dn = 1.1;
//应用
//static const size_t N = 10;
//int arr[N];
};
const size_t string::npos = -1;
//流插入和流提取不一定都是友元函数,这里调用接口同样可以间接访问私有成员
ostream& operator<<(ostream& out, const string& s)
{
//out << s.c_str(); //cout<<string 遇到'\0'也会打印
for (auto e : s)
{
out << e;
}
return out;
}
//2.0 空间换时间
istream& operator>>(istream& in, string& s)
{
//清空之前的
s.clear();
char ch = in.get();
char buff[16]; //减少每次+=扩容
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 15) //0,1,2...,14,15 ==> 16
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get(); //.get()可以从缓冲区读取' '和'\n'
}
//剩余的
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
---------------分割线--------------------------
成员变量:
public:
const static size_t npos;
private:
char* _str;
size_t _size;
size_t _capacity;
const size_t string::npos = -1;
主要是一个字符串的数组.
_capacity是有效容量,不包括’\0’,所以实际容量是_capacity+1
关于npos初始化:
static成员不属于某个具体对象,是这种类共有的,在类里只能声明,不能再类中初始化(定义).
const static size_t npos;
//另一种写法,不推荐(语法奇怪)
// 只支持int
//static const size_t n = -1;
// 不支持别的类型
//static const double dn = 1.1;
//应用
//static const size_t N = 10;
//int arr[N];
};
const size_t string::npos = -1;
构造,拷贝构造,析构函数:
string(const char* str = "") //"" 默认带有\0 ==>等价"\0"
:_size(strlen(str))
{
//_capacity = _size;
_capacity = _size == 0 ? 3 : _size; //防止_capacity = 0 2倍数扩容问题
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& s)
:_size(s._size)
, _capacity(s._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
~string()
{
//assert(_str != nullptr);
delete[] _str;
_size = _capacity = 0;
}
注意:
string(const char* str = nullptr) 不可以,对空指针解引用会报错
string(const char* str = '\0') '\0'类型转化为0,0类型转化为nullptr
string(const char* str = "\0") "\0" 等价于 ""
赋值重载: string& operator=(const string& s)
//赋值,s1 = s2 ,已存在
string& operator=(const string& s)
{
防止s1 = s1,空间释放
//if (this != &s)
//{
// delete[] _str;
// _size = s._size;
// _capacity = s._capacity;
// _str = new char[_capacity + 1];
// strcpy(_str, s._str);
//}
//防止s1 = s1,空间释放导致数据丢失
//防止new失败
if (this != &s)
{
char* tmp = new char[s._capacity + 1]; //new失败,抛异常
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
[]重载: char& operator[](size_t pos)
注意:需要const和非const两种函数重载
char& operator[](size_t pos)
{
assert(pos >= 0 && pos < _size); //左闭右开,_size对应/0
return _str[pos];
}
const char& operator[](size_t pos) const //const string
{
assert(pos >= 0 && pos < _size);
return _str[pos];
}
迭代器: iterator
迭代器都是左闭右开的区间,[begin,end),end指向’\0’,访问不到;
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
//cout << "iterator begin()" << endl;
return _str;
}
iterator end()
{
return _str + _size;
}
//需要const版本和非const版本
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
使用:
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : s2) //本质是替换为迭代器 s2.begin(); s2.end();
{
cout << e << " ";
}
cout << endl;
熟练后可以使用auto自动判断类型
auto it = s2.begin();
尾插: push_back()和append()
当实现insert()后,可以复用insert(),提高代码利用率
void push_back(char ch)
{
//if (_size + 1 > _capacity)
//{
// reserve(2 * _capacity);
//}
//_str[_size++] = ch;
//_str[_size] = '\0';
insert(_size, ch);
}
void append(const char* str)
{
//size_t len = strlen(str);
检查,扩容
//if (_size + len > _capacity)
//{
// reserve(_size + len); //这里不能2倍扩容,_size + len可能大于2*_capacity
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
尾插: string& operator+=(const char* str)
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
---------------分割线--------------------------
关系运算符重载: < == > != <= >=
字符串比较大小是比较对应位置的ASCII码值,对应位相同则比较下一位;
bool operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool operator>=(const string& s) const
{
return (*this == s) || (*this > s);
}
bool operator<(const string& s) const
{
return !(*this >= s);
}
bool operator<=(const string& s) const
{
return !(*this > s);
}
bool operator!=(const string& s) const
{
return !(s == *this);
}
STL库中string还有个成员函数compare(),但是功能可以使用关系运算符替代;
int compare(const string& s) const
{
return strcmp(_str, s._str);
}
扩容: reserve()和resize():
缩容一般需要销毁原空间,再将数据拷贝到新的空间,代价比较大,所以一般不缩容;
void reserve(size_t n)
{
if (n > _capacity) //不轻易缩容
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
resize()一种写法
//1.0
void resize(size_t n, char ch = '\0')
{
if (n >= _size)
{
if (n > _capacity)
{
reserve(n);
}
while (_size < n)
{
_str[_size++] = ch;
}
}
_size = n;
_str[_size] = '\0';
}
resize()另一种写法,本质没差别
//2.0
void resize(size_t n, char ch = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else if(n > _size)
{
if (n > _capacity)
{
reserve(n);
}
while (_size < n)
{
_str[_size++] = ch;
}
_str[_size] = '\0';
}
}
string在不同编译器平台扩容方式不同,例如:
g++和vs2013
pos位置插入: insert()
不建议经常使用,数组pos位置插入需要挪动之后字符的位置,有代价;
string& insert(size_t pos, char ch)
{
if (_size + 1 > _capacity)
{
reserve(2 * _capacity);
}
1.0 当pos=0时
//int end = _size; //end不能是size_t,不然end--为-1的时候变为2^32-1;访问越界
//while ((int)pos <= end) //隐式类型转化 size_t 和 int 比较,默认转化为size_t
//{
// _str[end + 1] = _str[end];
// end--;
//}
//2.0 避免当pos=0时,end等于-1 (2^32-1)
size_t end = _size + 1;
while (pos < end)
{
_str[end] = _str[end - 1];
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);
}
挪动数据 1.0
//int end = _size;
while (pos <= end && end != -1)//防止end变为-1,即2^32-1 但是这种写法不好,不便于理解
//while ((int)pos <= end)
//{
// _str[end + len] = _str[end];
// end--;
//}
//挪动数据 2.0
int end = _size + 1;
while (pos < end)
{
_str[end - 1 + len] = _str[end - 1]; //当len=1,即插入一个字符,_str[end]=_str[end-1];
end--;
}
拷贝 1.0
//int i = 0;
//while (str[i] != '\0')
//{
// _str[pos + i] = str[i];
// i++;
//}
拷贝 2.0
strncpy(_str + pos, str, len);
_size += len;
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 + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
//挪动数据
/*size_t end = _size;
for (size_t i = 0; i < _size + 1; ++i) //理解不直观,不建议写
{
_str[end + len] = _str[end];
--end;
}*/
// 拷贝插入
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
删除pos位置字符: erase()
不建议经常使用,数组pos位置删除需要挪动之后字符的位置,有代价;
void erase(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
//len = (len == npos ? _size : len);
if (len == npos || pos + len >= _size) //防止等于npos时溢出
{
_str[pos] = '\0';
_size = pos;
}
else
{
1.0
//size_t begin = pos;
//size_t end = pos + len;
//while (end <= _size)
//{
// _str[begin] = _str[end];
// begin++;
// end++;
//}
2.0
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
查找: find()
size_t find(char ch,size_t pos = 0) const
{
assert(pos < _size);
for (int i = pos; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0) const
{
assert(pos < _size);
char* p = strstr(_str + pos, str);
if (nullptr == p)
{
return npos;
}
return p - _str;
}
交换: swap()
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string s1 = "123";
string s2 = "hello;
std::swap(s1,s2);
s1.swap(s2);
这里没有使用std::swap(s1,s2)交换;原因是标准库中的swap()是内容的拷贝交换,而string类中的swap()主要是交换s1,s2的指针,效率更高;
输出c语言字符串: c_str
返回c语言类型字符串;
const char* c_str() const
{
return _str;
}
使用场景,调用c语言的一些接口需要c的字符串,比如fopen();
//.c_str()
string s2("string.txt");
FILE* fin = fopen(s2.c_str(), "w");
fprintf(fin, "%s\n", s1.c_str());
fclose(fin);
其他接口: size(),capacity(),clear()
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
//清空
void clear()
{
_size = 0;
_str[_size] = '\0';
}
---------------分割线--------------------------
全局函数(非成员函数)
流提取(流输出): ostream& operator<<(ostream& out,const string& s)
注意:
cout<<string 遇到’\0’也会打印之后的
//流插入和流提取不一定都是友元函数,这里调用接口同样可以间接访问私有成员
ostream& operator<<(ostream& out,const string& s)
{
//out << s.c_str(); //cout<<string 遇到'\0'也会打印
for (auto e : s)
{
out << e;
}
return out;
}
流插入(流输入): istream& operator>>(istream& in, string& s)
注意:
’ ‘和’\n’会存入缓冲区,但是它作为>>流提取的分割符,不能被流提取
.get()可以从缓冲区读取’ ‘和’\n’
//1.0
istream& operator>>(istream& in, string& s)
{
//清空之前的
s.clear();
char ch = in.get();
//in >> ch;
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch; //' '和'\n'会存入缓冲区,但是它作为>>流提取的分割符,不能被流提取
ch = in.get(); //.get()可以从缓冲区读取' '和'\n'
}
return in;
}
代码优化:(空间换时间)
//2.0 空间换时间
istream& operator>>(istream& in, string& s)
{
//清空之前的
s.clear();
char ch = in.get();
char buff[16]; //减少每次+=扩容
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 15) //0,1,2...,14,15 ==> 16
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get(); //.get()可以从缓冲区读取' '和'\n'
}
//剩余的
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
当然,也可以用.get()模拟实现getline()的效果:
void getline(istream& in, string& s)
{
//清空之前的
s.clear();
char ch = in.get();
while (ch != '\n')
{
s += ch;
ch = in.get(); //.get()可以从缓冲区读取' '和'\n'
}
}
注意:
C++没有了gets,取而代之的是getline
string s1 = "132456431345370";
char s[50];
gets(s); //C++没有了gets
getline(cin, s1); //取而代之的是getline
cout << s1 << endl;
---------------分割线--------------------------
2.4 其他
使用try和catch可以捕获异常(待学习)
#include "string.h"
int main()
{
try
{
kozen::string_test8();
}
catch (const std::exception& e)
{
cout << e.what() << endl;
}
return 0;
}