程序员成长之旅——string简介(深浅拷贝、写时拷贝)
string简介
string的初始化
首先,string要使用的话,必须包含其头文件。
#incldue<string>
还要知道的是string类是一个模范类,位于名字空间std中,因此还要有
using namespace std;
那么如何声明一个字符串变量呢?
string str;
string的比较等操作
string中可以使用 ==、>、<、>=、<=、和!=比较字符串,也可以使用+或者+=操作符来连接两个字符串,并且可以使用[]获取特定的字符。
string的一些特性
可以用一些函数来说明string的特性
int capacity()const; //返回当前容量(即string中不必增加内存即可存放的元素个数)
int max_size()const; //返回string对象中可存放最大字符串的长度
int size()const; //返回当前字符串的大小
int length()const; //返回当前字符串的长度
bool empty()const; //当前字符串是否为空
void resize(int len,char c) //把字符串当前大小置为len,多去少补,多出的字符c填充不足的部分
string的查找
由于查找是使用最为频繁的功能之一,string提供了非常丰富的查找函数:(注:string::npos)
size_type find(const basic_string &str, size_type index);//返回str在字符串中第一次出现的位置(从index开始查找),如果没找到则返回string::npos
size_type find( const char *str, size_type index ); // 同上
size_type find( const char *str, size_type index, size_type length ); //返回str在字符串中第一次出现的位置(从index开始查找,长度为length),如果没找到就返回string::npos
size_type find( char ch, size_type index ); // 返回字符ch在字符串中第一次出现的位置(从index开始查找),如果没找到就返回string::npos
**注意:**查找字符串a是否包含子串b,不是用 strA.find(strB) > 0 而是 strA.find(strB) != string:npos (初学者比较容易犯的一个错误)
参考1
参考2
string其它常用的函数
string &insert(int p,const string &s);//在p位置插入字符串s
string &replace(int p, int n,const char *s); //删除从p开始的n个字符,然后在p处插入串s
string &erase(int p, int n); //删除p开始的n个字符,返回修改后的字符串
string substr(int pos = 0,int n = npos) const; //返回pos开始的n个字符组成的字符串
void swap(string &s2); //交换当前字符串与s2的值
string &append(const char *s); //把字符串s连接到当前字符串结尾
void push_back(char c) //当前字符串尾部加一个字符c
浅拷贝
我们先来看一段代码
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
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()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
void TestString()
{
String s1("hello world!!!");
String s2(s1);
}
int main()
{
TestString();
return 0;
}
根据调试我们会发现:由于没有写拷贝构造函数,编译器会默认生成一个拷贝构造函数,最终导致的问题是,它两会同时用一块内存空间,析构的时候会析构多次,从而会导致程序崩溃,我们把这种情况叫做浅拷贝。赋值运算符重载同上。
深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
为了解决这个问题,我们引入了深拷贝,而深拷贝有两种写的方式。
传统版
namespace lpf
{
class string
{
public:
string(const char* str = "")
{
if (nullptr == str)
str = "";
_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;
};
}
现代版
namespace lpf
{
class string
{
public:
string(const char* str = "")
{
if (nullptr == str)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s)
:_str(nullptr)//防止崩溃 this指向的有可能是一个无效空间,因此要将其初始化
{
string strTemp(s._str);
swap(_str, strTemp._str);
}
string& operator=(string s)
{
swap(_str, s._str);
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
}
写时拷贝
除了深拷贝,还有一种方法可以解决这个问题,就是写时拷贝。
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
namespace lpf
{
class string
{
public:
string(const char* str = "")
{
if (nullptr == str)
{
str = "";
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
_pCount = new int(1);
}
string(const string& s)
:_str(s._str)
,_pCount(s._pCount)
{
++(*_pCount);
}
string& operator=(const string& s)
{
if (this != &s)
{
Release();
_str = s._str;
_pCount = s._pCount;
++(*_pCount);
}
}
char& operator[](size_t index)
{
if (*_pCount > 1)
{
string strTemp(_str);
Swap(strTemp);
}
return _str[index];
}
const char& operator[](size_t index)const
{
return _str[index];
}
~string()
{
Release();
}
void Swap(string& s)
{
swap(_str, s._str);
swap(_pCount, s._pCount);
}
private:
void Release()
{
if (_str && 0 == --(*_pCount))
{
delete[] _str;
delete _pCount;
_str = nullptr;
_pCount = nullptr;
}
}
private:
char* _str;
int* _pCount;
};
}
void TestString()
{
lpf::string s1("hello");
lpf::string s2(s1);
lpf::string s3("world");
lpf::string s4(s3);
s1 = s3;//s1 s3 s4共用一份空间 s2共用一份
s2 = s4;//s1 s2 s3 s4共用同一份
s1[0] = 'H';
}
int main()
{
TestString();
return 0;
}