C++ 字符串 string

本文详细介绍了字符串的概念、基本操作及字符编码等内容,并深入探讨了字符串在不同编程语言中的实现方式,特别是C++中的string类实现,包括eager-copy、cow和sso优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    ~~~~    串,也称为字符串,在计算机上的非数据处理的对象基本都是字符串数据。
    ~~~~    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=a1a2ans=a1a2an0n0n。它是编程语言中表示文本的数据类型。在程序设计中,字符串(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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值