~~~~ 串,也称为字符串,在计算机上的非数据处理的对象基本都是字符串数据。
~~~~ stl是标准模板库,是标准库的子集。std是命名空间。
介绍
~~~~ 字符串(String),是由零个或多个字符组成的有限序列。一般记为 s=a1a2…ans=a1a2…an(0≤n⪇∞0≤n⪇∞){ s=a_{1}a_{2}\dots a_{n}} s=a_{1}a_{2}\dots a_{n}( {0\leq n\lneq \infty } 0\leq n\lneq \infty )s=a1a2…ans=a1a2…an(0≤n⪇∞0≤n⪇∞)。它是编程语言中表示文本的数据类型。在程序设计中,字符串(string)为符号或数值的一个连续序列,如符号串(一串字符)或二进制数字串(一串二进制数字)。
~~~~ 字符串中任意个连续字符组成的子序列称为该串的子串。包含子串的串相应地称为主串。通常称字符在序列中的序号为改字符在字符串中的位置。
操作
~~~~ 字符串基本操作不同的语言有所不同,我们这里定义6种操作,包括:复制、附加、获取长度,比较、查找、赋值等,当然C语言对应的函数肯定不止6种,我们这里就对这六种进行简单的学习。代码会在后面实现。
字符编码
~~~~ 说到字符串,我们就必须说一下字符编码,很多人在刚刚进入程序员这个行业的时候,遇到字符串多会碰到中文乱码问题,这个就是字符编码不一致导致的。
~~~~ 字符编码(英语:Character encoding)、字集码是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。常见的例子包括将拉丁字母表编码成摩斯电码和ASCII。其中,ASCII将字母、数字和其它符号编号,并用7比特的二进制来表示这个整数。通常会额外使用一个扩充的比特,以便于以1个字节的方式存储。
~~~~ 在计算机技术发展的早期,如ASCII(1963年)和EBCDIC(1964年)这样的字符集逐渐成为标准。但这些字符集的局限很快就变得明显,于是人们开发了许多方法来扩展它们。对于支持包括东亚CJK字符家族在内的写作系统的要求能支持更大量的字符,并且需要一种系统而不是临时的方法实现这些字符的编码。
~~~~ 很多时候我们的系统使用的都是utf-8,这也是大多数人的选择,但是在中国,就会接触到国标(政府相关的项目),我们就需要了解gb2312了,还有一个比较常用的就是unicode,这就不详说,大家可以去了解一下。
实现
~~~~ 我去看过一些语言的字符串的实现,像高级语言,多是静态申请,每次用多少,申请多少,每次都不同,如果从一个开发人员的角度考虑,我们是希望空间和时间最佳利用才是最好的,下面我实现了一个string类,采用的是eager-copy。
#ifndef MYSTRING_PTR_INCLUDE
#define MYSTRING_PTR_INCLUDE
#include <stdio.h>
#include <iostream>
class MyString_ptr
{
public:
//构造函数
MyString_ptr();//
MyString_ptr(const MyString_ptr &);
MyString_ptr(const char *);
MyString_ptr(const size_t, const char *);
//析构函数
~MyString_ptr();
// 字符串长度
size_t length();
// 判断是否为空
bool isEmpty();
// 返回c风格的trr的指针
const char* c_str();
// 重载运算符
friend MyString_ptr operator+(const MyString_ptr&, const MyString_ptr&);
friend bool operator==(const MyString_ptr&, const MyString_ptr&);
// 获取对应位置的字符
char& operator[](const size_t);
const char& operator[](const size_t)const;
MyString_ptr& operator=(const MyString_ptr&);
MyString_ptr& operator+=(const MyString_ptr&);
// 比较函数
int compare(const MyString_ptr& val);
// 成员操作函数
// 获取从offset位置开始len长度的字符串
MyString_ptr substr(size_t offset, const size_t len);
// 附加字符串到末尾
MyString_ptr& append(const MyString_ptr& val);
// 插入字符串 在offset位置
MyString_ptr& insert(size_t offset, const MyString_ptr& val);
// 替换字符串
MyString_ptr& assign(MyString_ptr&, size_t offset, size_t len);
// 删除从offset位置开始len长度的字符串
MyString_ptr& erase(size_t offset, size_t len);
//find_first_of 查找某一个字符 size_t 是非符号数的,重载
// 查找在字符串中第一个与str中的某个字符匹配的字符,返回它的位置。
//搜索从index开始,如果没找到就返回npos
int find_first_of(const char* str, size_t index = 0);
int find_first_of(const char ch, size_t index = 0);
int find_first_of(const MyString_ptr &, size_t index = 0);
// 在字符串中查找第一个与str中的字符都不匹配的字符,返回它的位置。搜索从index开始。如果没找到就返回nops
int find_first_not_of(const char* str, size_t index = 0);
int find_first_not_of(const char ch, size_t index = 0);
int find_first_not_of(const MyString_ptr&, size_t index = 0);
/*
一般来说,swap操作将容器内容交换不会导致容器的指针、引用、迭代器失效。
但当容器类型为array和string时除外。
原因在于:SSO (Short String Optimization 指C++针对短字符串的优化。)
默认情况下,C++的std::string都是存储在heap中,导致访问std::string需要经过一次寻址过程,速度较慢,并且这种实现的空间局部性不好,对cache的利用较低。
很多string的字符串长度很小,这个时候,我们可以把字符串存储到栈上,从而不需要进行内存分配,优化创建速度,并且访问栈上数据的局部性很好,速度比较快。
即C++会自动把较短的字符串放到对象内部,较长的字符串放到动态内存。
假如 std::string 用 SSO 实现,而待交换的两个对象中的字符串恰好一长一短,则原先指向短字符串中的迭代器会全部失效。
*/
// 交换
void swap(MyString_ptr& val);
// 字符替换
MyString_ptr& replace_all(const char oldc, const char newc = 0);
//查找
int find(const char* str, size_t index = 0);
static constexpr auto npos{ static_cast<size_t>(-1) };
private:
char *p_str;
size_t strLength;
};
size_t _strlen(const char *_dest);
char * _strcpy(char* _dest, const char* _src, int _src_len);
char * _strcpy(char* _dest, const char* _src);
#endif // !MYSTRING_PTR_INCLUDE
源文件
#include "MyString_ptr.h"
#include <stdio.h>
#include <assert.h>
using namespace std;
size_t _strlen(const char *_dest) {
size_t len = 0;
if (!_dest) {
return len;
}
//计算
while (*_dest++) {
len++;
}
return len;
}
char * _strcpy(char* _dest, const char* _src, int _src_len) {
char * _pDest = _dest;
assert(_dest && _src);//断言,调试使用
while (_src_len--) {
*_dest++ = *_src++;
}
return _pDest;
}
char * _strcpy(char* _dest, const char* _src) {
char * _pDest = _dest;
assert(_dest && _src);
while (*_src) {
*_dest++ = *_src++;
}
return _pDest;
}
int _strcmp(const char* val1, const char* val2, int len){
int cmp = 0;
if(!val1 && !val2){
return 0;
}
if(!val1) return -1;
if(!val2) return 1;
for(int i = 0; i < len; i++){
cmp = val1[i] - val2[i];
if(cmp != 0){
return cmp;
}
}
return cmp;
}
//构造函数
MyString_ptr::MyString_ptr() :p_str(NULL), strLength(0) {}
MyString_ptr::MyString_ptr(const MyString_ptr &val) : p_str(NULL), strLength(0) {
//第一件事就是判断是否为空
assert(val.length > 0 && val.p_str);
strLength = val.strLength;
//申请空间
p_str = new char[strLength];
size_t i = strLength;
//拷贝
_strcpy(p_str, val.p_str, strLength);
}
MyString_ptr::MyString_ptr(const char *_src) : p_str(NULL), strLength(0) {
if (!_src) {
return;
}
strLength = _strlen(_src);
//申请空间
p_str = new char[strLength];
//拷贝
_strcpy(p_str, _src);
}
MyString_ptr::MyString_ptr(const size_t len, const char *_src) : p_str(NULL), strLength(0) {
char* _p_src = NULL;
size_t i = 0;
if (!_src || !len) {
return;
}
//申请空间
p_str = new char[len];
strLength = len;
i = len;
_p_src = p_str;
//赋值
while (i--) {
*_p_src++ = *_src++;
}
_p_src = NULL;
}
//析构函数
MyString_ptr::~MyString_ptr() {
if (p_str) {
delete p_str;
strLength = 0;
}
}
// 字符串长度
size_t MyString_ptr::length() {
return strLength;
}
//判断是否为空
bool MyString_ptr::isEmpty() {
return strLength ? false : true;
}
//返回c风格的trr的指针
const char* MyString_ptr::c_str() {
return (const char*)p_str;
}
//重载运算符 a = b + c
MyString_ptr operator+(const MyString_ptr& val1, const MyString_ptr& val2) {
//先判断长度是否正常
if (!val2.p_str || !val2.strLength) {
return val1;
}
if (!val1.p_str || !val1.strLength) {
return val2;
}
MyString_ptr ret;
char *pStr = NULL;
ret.strLength = val1.strLength + val2.strLength;
ret.p_str = new char[ret.strLength];
pStr = ret.p_str;
_strcpy(pStr, val1.p_str, val1.strLength);
pStr += val1.strLength;
_strcpy(pStr, val2.p_str, val2.strLength);
pStr = NULL;
return ret;
}
bool operator==(const MyString_ptr& val1, const MyString_ptr& val2) {
if(val1.strLength != val2.strLength){
return false;
}
if(_strcmp(val1.p_str, val2.p_str, val1.strLength) == 0){
return true;
}
return false;
}
//获取对应位置的字符
char& MyString_ptr::operator[](const size_t val) {
return p_str[val];
}
const char& MyString_ptr::operator[](const size_t val)const {
return (const char&)p_str[val];
}
MyString_ptr& MyString_ptr::operator=(const MyString_ptr& val) {
if(strLength != val.strLength){
if(p_str){
delete p_str;
p_str = NULL;
strLength = 0;
}
if(!val.strLength){
return *this;
}
strLength = val.strLength;
p_str = new char[strLength];
_strcpy(p_str, val.p_str, strLength);
return *this;
}
if(!val.strLength){
return *this;
}
_strcpy(p_str, val.p_str, strLength);
return *this;
}
MyString_ptr& MyString_ptr::operator+=(const MyString_ptr& val) {
if (!val.p_str || !val.strLength) {
return *this;
}
char *pStr = NULL;
int len = strLength + val.strLength;
pStr = new char[len];
_strcpy(pStr, p_str, strLength);
if(p_str){
delete p_str;
p_str = NULL;
}
p_str = pStr;
pStr += strLength;
_strcpy(pStr, val.p_str, val.strLength);
pStr = NULL;
strLength = len;
return *this;
}
//比较函数
int MyString_ptr::compare(const MyString_ptr& val){
if(strLength > val.strLength){
return 1;
}
if(strLength < val.strLength){
return -1;
}
return _strcmp(p_str, val.p_str, strLength);
}
// 成员操作函数
// 获取从offset位置开始len长度的字符串
MyString_ptr MyString_ptr::substr(size_t offset, const size_t len) {
return MyString_ptr(len, p_str + offset);
}
// 附加字符串到末尾
MyString_ptr& MyString_ptr::append(const MyString_ptr& val) {
if(!val.strLength){
return *this;
}
char * pStr = NULL;
int len = strLength + val.strLength;
pStr = new char[len];
_strcpy(pStr, p_str, strLength);
if(p_str){
delete p_str;
p_str = NULL;
}
p_str = pStr;
_strcpy(pStr + strLength, val.p_str, val.strLength);
strLength = len;
return *this;
}
// 插入字符串 在offset位置
MyString_ptr& MyString_ptr::insert(size_t offset, const MyString_ptr& val) {
if(!val.strLength){
return *this;
}
if(strLength < offset || !strLength){
return this->append(val);
}
char * pStr = NULL;
int len = strLength + val.strLength;
pStr = new char[len];
_strcpy(pStr, p_str, offset);
_strcpy(pStr + offset, val.p_str, val.strLength);
_strcpy(pStr + offset + val.strLength, p_str + offset, strLength - offset);
if(p_str){
delete p_str;
p_str = NULL;
}
p_str = pStr;
pStr = NULL;
strLength = len;
return *this;
}
// 替换字符串
MyString_ptr& MyString_ptr::assign(MyString_ptr& val, size_t offset, size_t len) {
if(!len || offset > strLength || offset < 0){
return *this;
}
int newlen = len + strLength;
char *pnewStr = new char[newlen];
_strcpy(pnewStr, p_str, offset);
_strcpy(pnewStr + offset, val.p_str, len);
_strcpy(pnewStr + offset + len, p_str + offset, strLength - offset);
if(p_str){
delete p_str;
p_str = NULL;
}
p_str = pnewStr;
pnewStr = NULL;
strLength = newlen;
return *this;
}
// 删除从offset位置开始len长度的字符串
MyString_ptr& MyString_ptr::erase(size_t offset, size_t len) {
if(!len || offset < 0 || offset > strLength || offset + len > strLength){
return *this;
}
int newlen = strLength - len;
char* pnewStr = new char[newlen];
_strcpy(pnewStr, p_str, offset);
_strcpy(pnewStr, p_str + offset + len, strLength - len - offset);
if(p_str){
delete p_str;
p_str = NULL;
}
p_str = pnewStr;
pnewStr = NULL;
strLength = newlen;
return *this;
}
//find_first_of 查找某一个字符 size_t 是非符号数的,重载
// 查找在字符串中第一个与str中的某个字符匹配的字符,返回它的位置。
//搜索从index开始,如果没找到就返回npos
int MyString_ptr::find_first_of(const char* str, size_t index = 0) {
if(!p_str || !str || index > strLength){
return -1;
}
int len_str = _strlen(str);
if(!len_str || len_str > strLength || len_str + index > strLength){
return -1;
}
int j = 0;
for(int i = index; i < strLength; i++){
for(j = 0; p_str[i + j] == str[j] && j < len_str; j++);
if(j == len_str){
return i;
}
}
return npos;
}
int MyString_ptr::find_first_of(const char ch, size_t index = 0) {
if(!p_str || index > strLength){
return -1;
}
for(int i = 0; i < strLength; i++){
if(*p_str == ch){
return i;
}
}
return -1;
}
int MyString_ptr::find_first_of(const MyString_ptr &val, size_t index = 0) {
if(!p_str || !val.strLength || index > strLength || val.strLength > strLength
|| val.strLength + index > strLength){
return -1;
}
int j = 0;
for(int i = index; i < strLength; i++){
for(j = 0; p_str[i + j] == val.p_str[j] && j < val.strLength; j++);
if(j == val.strLength){
return i;
}
}
return -1;
}
// 在字符串中查找第一个与str中的字符都不匹配的字符,返回它的位置。搜索从index开始。如果没找到就返回nops
int MyString_ptr::find_first_not_of(const char* str, size_t index = 0) {
if(!str || strLength == 0 || index < strLength){
return npos;
}
const char* p = str;
bool isSuccess = false;
for(int i = index; i < strLength; i++){
p = str;
isSuccess = false;
while(*p){
if(p_str[i] == *p){
isSuccess = true;
break;
}
p++;
}
if(!isSuccess){
return i;
}
}
return npos;
}
int MyString_ptr::find_first_not_of(const char ch, size_t index = 0) {
if(strLength == 0 || index < strLength){
return npos;
}
for(int i = index; i < strLength; i++){
if(p_str[i] != ch){
return i;
}
}
return npos;
}
int MyString_ptr::find_first_not_of(const MyString_ptr& val, size_t index = 0) {
if(val.strLength == 0 || strLength == 0 || index < strLength){
return npos;
}
bool isSuccess = false;
for(int i = index; i < strLength; i++){
isSuccess = false;
for(int j = 0; j < val.strLength; j++){
if(val.p_str[j] == p_str[i]){
isSuccess = true;
break;
}
}
if(!isSuccess){
return i;
}
}
return npos;
}
// 交换
void MyString_ptr::swap(MyString_ptr& val) {
val.strLength ^= strLength;
strLength ^= val.strLength;
val.strLength ^= strLength;
char* p = p_str;
p_str = val.p_str;
val.p_str = p_str;
}
// 字符替换
MyString_ptr& MyString_ptr::replace_all(const char oldc, const char newc) {
for(int i =0; i< strLength; i++){
if(p_str[i] == oldc){
p_str[i] = newc;
}
}
}
//查找
int MyString_ptr::find(const char* str, size_t index) {
if(!str || strLength == 0 || index > strLength){
return npos;
}
int _len = _strlen(str);
if(index + _len > strLength){
return npos;
}
int j = 0;
for(int i = index; i < strLength; i++){
for(j = 0; p_str[i + j] == str[j] && j < _len; j++);
if(j == _len)return i;
}
return npos;
}
(上面的类,没有做过多的测试,有问题可以找到我,-_-)
研究过的人就应该知道,string的主要优化方案还有cow(早期)和sso,对于这两个的区别我们很多时候还是需要了解一下的。
同时在实现string类的时候,就考虑了一下c如何和sso,这应该是现在主要的对string类拷贝的优化方法了,这里我就记录一下对于cow和sso的理解,上面第一篇string的实现可以发现,使用的直接就是深拷贝,就是无论如果都是重新new一个对象直接复制数据
对于cow和sso,我写了一个例子,大家可以自己运行看一下,在gcc5.0一起,个之后看结果如果:
#include <iostream>
#include <deque>
#include <string>
int main(){
std::string str1 = "hello world!";
std::string str2 = str1;
if(str1.c_str() == str2.c_str()){
std::cout << "true" << std::endl;
}
else{
std::cout << "false" << std::endl;
}
std::string str3 = str2;
str2 = "";
if(str3.c_str() == str1.c_str()){
std::cout << "true" << std::endl;
}
else{
std::cout << "false" << std::endl;
}
str1 = "";
if(str1.c_str() == str2.c_str()){
std::cout << "true" << std::endl;
}
else{
std::cout << "false" << std::endl;
}
return 1;
}
在两个版本的gcc编译的结果是不同的,一个true,true,true;一个是false,false,false,而这就是两个不同的对于string的优化,也就是我们这里要学习的cow和sso优化。
cow(copy or write),字面意思理解就是复制或者写,就是当我们平时使用拷贝的时候,或者是需要拷贝的时候,如果两个字符串是一样的,我们只会进行地址的复制,而不是申请新的内存,进行拷贝;
而只有当两个字符串内容不一样的时候,才会重新申请内存进行复制,这就会导致某些时候资源占用会比较高,这有优点,也有缺点。当然缺点更大,在我们平时使用多线程的时候,就会有安全问题。
sso(short string optimization)
SSO策略中,拷贝均使用立即复制内存的方法,也就是深拷贝的基本定义,其优化在于,当字符串较短时,直接将其数据存在栈中,而不去堆中动态申请空间,这就避免了申请堆空间所需的开销。
使用以下代码来验证一下:
int main() {
string a = "aaaa";
string b = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
printf("%p ------- %p\n", &a, a.c_str());
printf("%p ------- %p\n", &b, b.c_str());
return 0;
}
某次运行的输出结果为:
1
2
0136F7D0 ------- 0136F7D4
0136F7AC ------- 016F67F0
可以看到,a.c_str()的地址与a、b本身的地址较为接近,他们都位于函数的栈空间中,而b.c_str()则离得较远,其位于堆中。
SSO是目前STL库的实现方式,其优点就在于,对程序中经常用到的短字符串来说,运行效率较高。
下面我们就可以查看一下stl中的string的实现
_GLIBCXX_BEGIN_NAMESPACE_CXX11
template<typename _CharT, typename _Traits = char_traits<_CharT>,
typename _Alloc = allocator<_CharT> >
class basic_string;
/// A string of @c char
typedef basic_string<char> string;
#ifdef _GLIBCXX_USE_WCHAR_T
/// A string of @c wchar_t
typedef basic_string<wchar_t> wstring;
#endif
#if ((__cplusplus >= 201103L) \
&& defined(_GLIBCXX_USE_C99_STDINT_TR1))
/// A string of @c char16_t
typedef basic_string<char16_t> u16string;
/// A string of @c char32_t
typedef basic_string<char32_t> u32string;
#endif
先看上面我们可以发现,string的基本长度不只是char(一个字节),也可以是两个或者三个到四个,而不同长度的字节,都可以使用basic_string,我们下面就是看basic_string类的实现。
代码过多,我就不复制了,主要看它的存储的方式:
template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string
...
typedef typename _Alloc_traits::pointer pointer;
...
struct __sv_wrapper
{
explicit __sv_wrapper(__sv_type __sv) noexcept : _M_sv(__sv) { }
__sv_type _M_sv;
};
#endif
// Use empty-base optimization: http://www.cantrip.org/emptyopt.html
struct _Alloc_hider : allocator_type // TODO check __is_final
{
#if __cplusplus < 201103L
_Alloc_hider(pointer __dat, const _Alloc& __a = _Alloc())
: allocator_type(__a), _M_p(__dat) { }
#else
_Alloc_hider(pointer __dat, const _Alloc& __a)
: allocator_type(__a), _M_p(__dat) { }
_Alloc_hider(pointer __dat, _Alloc&& __a = _Alloc())
: allocator_type(std::move(__a)), _M_p(__dat) { }
#endif
pointer _M_p; // The actual data.
};
_Alloc_hider _M_dataplus;
size_type _M_string_length;
enum { _S_local_capacity = 15 / sizeof(_CharT) };
union
{
_CharT _M_local_buf[_S_local_capacity + 1];
size_type _M_allocated_capacity;
};
看上面我们就发现了,它有一个16个字节的字符数组,这就是之前说的sso,然后当我们使用字符串较短的时候,就会直接使用者个联合体,而上面同样有一个指针类型,用于字符串过长的时候,去堆中申请空间。
本文资料第三方资料主要来自维基百科。
找时间重写一个sso的string类,到时候加到后面吧!如果想学sso,可以直接看std::string。