【c++】STL--string

前言

        最开始我们学习c语言的时候,我们发现刷题或者写代码都是比较麻烦的,如果说用c语言造一辆车,那么我需要用c语言先把轮子造好--各个零件,当我们造好之后再组装。那么c++则是造好了轮子,只需要我们组装就好了。这里的的STL里有各个组件,我只要熟悉的掌握,使用即可。

网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

所以对于学习c++而言,学好STL是很有必要的。STL统称为:“Standard TemplateLibrary 标准模板库”,STL提供了有六大组件,包括了容器,算法,迭代器,仿函数,适配器以及空间适配器。这篇文章主要是讲解STL容器中的string。这些组件虽然听起来很陌生,但是熟悉过后你会发现其实就是扮演的角色一样,名字不一样而已。

文章的路程大概就是:我们先了解STL,然后再查看标准库中的string类,先对对他们的使用进行理解,最后为了更好的使用和巩固,在对string重要的接口进行模拟实现。模拟实现的时候进行分析,理解。

目录

前言

STL

什么是STL

STL的版本

STL的缺陷

标准库中的string类

了解string类

string类的常用接口说明

string类对象的容量操作

 string类对象的访问及遍历操作

string类对象的修改操作

string类非成员函数

string类的模拟实现

经典的string类问题

浅拷贝/深拷贝

string类的模拟实现


STL

什么是STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架。

STL的版本

原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意 运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使 用。 HP 版本--所有STL实现版本的始祖。

P. J. 版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低, 符号命名比较怪异。

RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好, 可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码, 主要参考的就是这个版本。

STL的缺陷

1.STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出 来已经相隔了13年,STL才进一步更新。

2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。

3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。

4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语 法本身导致的。

标准库中的string类

了解string类

1. string是表示字符串的字符串类

2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;

4. 不能操作多字节或者变长字符的序列。

在使用string类时,必须包含#include头文件以及using namespace std;

string类的常用接口说明

标准库中string有7个接口,这里我们主要讲解和使用4个,后面模拟实现也是模拟我们常用的。

default (1)
string();
copy (2)
string (const string& str);
substring (3)
string (const string& str, size_t pos, size_t len = npos);
from c-string (4)
string (const char* s);
from sequence (5)
string (const char* s, size_t n);
fill (6)
string (size_t n, char c);
range (7)
template <class InputIterator>
  string  (InputIterator first, InputIterator last);

string()

Constructs an empty string, with a length of zero characters

----构造一个长度为零个字符的空字符串。

#include<iostream>
#include <string>
using namespace std;

int main()
{
	string str;//构造空的string类对象s1

	cout << str << endl;

	return 0;
}

string(const char* s) 

Copies the null-terminated character sequence (C-string) pointed by s.

----复制s指向的以空字符结尾的字符序列(C字符串)

#include<iostream>
#include <string>
using namespace std;

int main()
{
	string str("hello world"); 

	cout << str << endl;

	return 0;
}

string(size_t n, char c) 

Fills the string with n consecutive copies of character c

----用字符c的n个连续副本填充字符串

#include<iostream>
#include <string>
using namespace std;

int main()
{
	string str(12,'x'); 

	cout << str << endl;

	return 0;
}

string(const string&s)

Constructs a copy of str.

----构造str的副本。

#include<iostream>
#include <string>
using namespace std;

int main()
{

	string str("hello world");
	string str1(str);

	cout << str1 << endl;

	return 0;
}

string类对象的容量操作

size

size_t size() const;

Return length of string--返回字符串有效字符长度

注意:

Returns the length of the string, in terms of bytes.

This is the number of actual bytes that conform the contents of the string, which is not necessarily equal to its storage capacity.

Note that string objects handle bytes without knowledge of the encoding that may eventually be used to encode the characters it contains. Therefore, the value returned may not correspond to the actual number of encoded characters in sequences of multi-byte or variable-length characters (such as UTF-8).

----返回字符串的长度,以字节为单位。这是符合字符串内容的实际字节数,不一定等于其存储容量。

请注意,字符串对象在不知道最终可能用于编码其包含的字符的编码的情况下处理字节。

因此,返回的值可能不对应于多字节或可变长度字符序列(如UTF-8)中编码字符的实际数量

这里的更深层的理解就是:我们都是熟知ASCLL表,我们打印的字符,或者符号都是被数字所表示的。因为计算机是只认识二进制,所以这些字符或者符号都会变成二进制让计算机识别。世界上有各种语言,所以对应的就有不同的电脑编码。英文对应的电脑编码是ASCLL,中文对应的电脑编码很多时候就是统一码

统一码(Unicode),也叫万国码、单一码,由统一码联盟开发,是计算机科学领域里的一项业界标准,包括字符集编码方案等。统一码是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

统一码是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。统一码用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8UTF-16UTF-32都是将数字转换到程序数据的编码方案。分别映射为8位,16位,32位长的整数。

#include<iostream>
#include <string>
using namespace std;

int main()
{
	string str("hello world");
	size_t n=str.size();

	cout << n << endl;

	return 0;
}

length

size_t length() const;

Return length of string ----返回字符串的长度

#include <iostream>
#include <string>

int main ()
{
  std::string str ("Test string");
  std::cout << "The size of str is " << str.length() << " bytes.\n";
  return 0;
}

注意:

length与size的实现时一样的,只是命名不一样,这个是历史遗留问题,就不用过多的吐槽,在后面容器中就再也没有length了,只在string中有。

capacity

size_t capacity() const;

Return size of allocated storage----返回空间总大小

#include <iostream>
#include <string>

int main ()
{
  std::string str ("Test string");
  std::cout << "The size of str is " << str.length() << " bytes.\n";
  return 0;
}

empty

bool empty() const;

Test if string is empty----检测字符串释放为空串,是返回true,否则返回false

#include<iostream>
#include <string>
using namespace std;

int main()
{
	string str("hello world");
	
	if (!str.empty())
	{
		cout << "hello everyone!" << endl;
	}
	return 0;
}

clear

void clear();

Clear string----清空有效字符

#include<iostream>
#include <string>
using namespace std;

int main()
{
	string str("hello world");
	
	str.clear();
	cout << str << endl;

	return 0;
}

注意:

clear是清空有效字符但是不清除内存,因为一般情况是下是内存足够,缺时间。一般会选择增容不会减少内存,增加内存会重新开辟一块内存,因为内存中存在这内存碎片。减少内存之后,重新写入就会增加空间,那么就更加耗时,所以一般情况下是不减少内存的。

reserve

void reserve (size_t n = 0);

Request a change in capacity-----请求更改容量

Requests that the string capacity be adapted to a planned change in size to a length of up to n characters.----请求字符串容量根据计划的大小更改调整为最多n个字符的长度

If n is greater than the current string capacity, the function causes the container to increase its capacity to n characters (or greater).

In all other cases, it is taken as a non-binding request to shrink the string capacity: the container implementation is free to optimize otherwise and leave the string with a capacity greater than n.

This function has no effect on the string length and cannot alter its content.

----如果n大于当前字符串容量,则该函数使容器将其容量增加到n个字符(或更大)。在所有其他情况下,它被视为缩小字符串容量的非绑定请求:容器实现可以自由优化,否则将字符串容量保留为大于n。此函数对字符串长度没有影响,也不能更改其内容。

 vs2013

#include<iostream>
#include <string>
using namespace std;

void TestPushBack()
{
	string s;
	s.reserve(500);
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';
	cout << s.size() << endl;
	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

int main()
{
	TestPushBack();

	return 0;
}

 我们发现在vs2013环境下,它是以1.5倍扩容的

linux下--同样的代码

那么在linux下我们发现,它是以2倍扩容的

我们知道要插入多少数据,提前用reserve开好空间,避免扩容,提高效率

resize

void resize (size_t n);

void resize (size_t n, char c);

Resize string----调整字符串大小

Resizes the string to a length of n characters.

If n is smaller than the current string length, the current value is shortened to its first n character, removing the characters beyond the nth.

If n is greater than the current string length, the current content is extended by inserting at the end as many characters as needed to reach a size of n. If c is specified, the new elements are initialized as copies of c, otherwise, they are value-initialized characters (null characters).

-----将字符串的长度调整为n个字符。如果n小于当前字符串长度,则将当前值缩短为其第一个n个字符,删除第n个字符以外的字符。如果n大于当前字符串长度,则通过在末尾插入尽可能多的字符来扩展当前内容,以达到n的大小。如果指定了c,则将新元素初始化为c的副本,否则,它们是值初始化字符(空字符)。

#include <iostream>
#include <string>

int main()
{
	std::string str("I like to code in C");
	std::cout << str << '\n';

	unsigned sz = str.size();
	std::cout << sz << '\n';

	str.resize(sz + 2, '+');//增加两个字符‘++’
	std::cout << str << '\n';

	str.resize(14);//减少5个字符
	std::cout << str << '\n';
	return 0;
}

 string类对象的访问及遍历操作

operator[]

char& operator[] (size_t pos);

const char& operator[] (size_t pos) const;

Returns a reference to the character at position pos in the string.

----返回pos位置的字符,const string类对象调用

#include <iostream>
#include <string>

int main()
{
	std::string str("Test string");
	for (int i = 0; i<str.length(); ++i)
	{
		std::cout << str[i];
	}
	return 0;
}

begin /end

iterator begin();

const_iterator begin() const;

Returns an iterator pointing to the first character of the string

----返回指向字符串第一个字符的迭代器 

iterator end();

const_iterator end() const;

Returns an iterator pointing to the past-the-end character of the string.

----返回一个迭代器,该迭代器指向字符串的结束字符。

#include <iostream>
#include <string>

int main()
{
	std::string str("Test string");
	for (std::string::iterator it = str.begin(); it != str.end(); ++it)
		std::cout << *it;
	std::cout << '\n';

	return 0;
}

rend/rbegin

reverse_iterator rbegin();
const_reverse_iterator rbegin() const;

Return reverse iterator to reverse beginning

----返回反向迭代器以反向开始

reverse_iterator rend();
const_reverse_iterator rend() const;

Return reverse iterator to reverse end

----将反向迭代器返回到反向端

#include <iostream>
#include <string>

int main ()
{
  std::string str ("now step live...");
  for (std::string::reverse_iterator rit=str.rbegin(); rit!=str.rend(); ++rit)
    std::cout << *rit;
  return 0;
}

C++中,迭代器就是一个类似于指针的对象,它能够用来遍历C++标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。

这里需要注意的是,,对于string类来说,无论是正向遍历,还是反向遍历,下标+[]都足够好用,但是对于其他容器,对于那些以链表形式连接的数据结构,如list,map/set等,就不能使用下标+[]的方式去访问容器里的元素,所以就需要采用迭代器来访问这些容器里的元素

vector--容器

	vector<int> v;
	vector<int>::iterator vit = v.begin();
	while (vit != v.end())
	{
		cout << *vit << " ";
		vit++;
	}
	cout << endl;

string类对象的修改操作

push_back

void push_back (char c);

ppends character c to the end of the string, increasing its length by one.

----将字符c追加到字符串的末尾,将其长度增加1

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
	string str;
	str.push_back('h');
	str.push_back('e');
	str.push_back('l');
	str.push_back('l');
	str.push_back('o');

	std::cout << str << '\n';
	return 0;
}

append

string (1)
string& append (const string& str);
substring (2)
string& append (const string& str, size_t subpos, size_t sublen);
c-string (3)
string& append (const char* s);
buffer (4)
string& append (const char* s, size_t n);
fill (5)
string& append (size_t n, char c);
range (6)
template <class InputIterator>
   string& append (InputIterator first, InputIterator last);

(1) string

Appends a copy of str.----追加str的副本

(2) substring

Appends a copy of a substring of str. The substring is the portion of str that begins at the character position subpos and spans sublen characters (or until the end of str, if either str is too short or if sublen is string::npos).

----追加str的子字符串的副本。该子字符串是str的一部分,从字符位置子字符串开始,跨越子字符串字符(或直到str结尾,如果str太短或子字符串为string::npos)

(3) c-string

Appends a copy of the string formed by the null-terminated character sequence (C-string) pointed by s.

----追加由s指向的以空结尾的字符序列(C字符串)形成的字符串的副本

(4) buffer

Appends a copy of the first n characters in the array of characters pointed by s.

----追加由s指向的字符数组中前n个字符的副本

(5) fill

Appends n consecutive copies of character c.

----追加n个字符c的连续副本。

(6) range

Appends a copy of the sequence of characters in the range [first,last), in the same order.

----以相同的顺序追加范围[first,last]中字符序列的副本。

#include <iostream>
#include <string>

int main()
{
	std::string str;
	std::string str2 = "Writing ";
	std::string str3 = "print 10 and then 5 more";

	
	str.append(str2);                       // 情况1
	std::cout << str << '\n';

	str.append(str3, 6, 3);                   // 情况2
	std::cout << str << '\n';

	str.append("dots are cool", 5);          // 情况3
	std::cout << str << '\n';

	str.append("here: ");                   // 情况4
	std::cout << str << '\n';

	str.append(10u, '.');                    // 情况5
	std::cout << str << '\n';

	str.append(str3.begin() + 8, str3.end());  // 情况6
	std::cout << str << '\n';

	return 0;
}

operator+=

Append to string----附加到字符串

string (1)
string& operator+= (const string& str);
c-string (2)
string& operator+= (const char* s);
character (3)
string& operator+= (char c);
#include <iostream>
#include <string>

int main()
{
	std::string name("John");
	std::string family("Smith");
	name += " K. ";         // c-string
	name += family;         // string
	name += '\n';           // character

	std::cout << name;
	return 0;
}

运行结果:John K. Smith

c_str

const char* c_str() const;

Get C string equivalent----获取等效的C字符串

Returns a pointer to an array that contains a null-terminated sequence of characters (i.e., a C-string) representing the current value of the string object.

This array includes the same sequence of characters that make up the value of the string object plus an additional terminating null-character ('\0') at the end.

----返回指向数组的指针,该数组包含表示字符串对象当前值的以空结尾的字符序列(即C字符串)。

此数组包含构成字符串对象值的相同字符序列,加上末尾的附加终止空字符(“\0”)。

#include <iostream>
#include <cstring>
#include <string>


int main()
{
	std::string str("Please split this sentence into tokens");//str是一个类

	char * cstr = new char[str.length() + 1];
	std::strcpy(cstr, str.c_str());				//将字符串返回成char *,然后进行拷贝
												//char *strcpy(char *dest, const char *src)
												//  str现在包含str的c字符串副本

	char * p = std::strtok(cstr, " ");			//char *strtok( char *strToken, const char *strDelimit );
												//分解字符串为一组字符串

	while (p != 0)
	{
		std::cout << p << '\n';
		p = std::strtok(NULL, " ");
	}

	delete[] cstr;
	return 0;
}

find + npos

string (1)
size_t find (const string& str, size_t pos = 0) const;
c-string (2)
size_t find (const char* s, size_t pos = 0) const;
buffer (3)
size_t find (const char* s, size_t pos, size_t n) const;
character (4)
size_t find (char c, size_t pos = 0) const;

Find content in string----在字符串中查找内容

static const size_t npos = -1;

npos:Maximum value for size_t----size_t的最大值

#include <iostream>      
#include <string>    
using namespace std;

int main()
{
	string str("There are two needles in this haystack with needles.");
	string str2("needle");

	// different member versions of find in the same order as above:
	size_t found = str.find(str2);
	if (found != string::npos)
		cout << "first 'needle' found at: " << found << '\n';

	found = str.find("needles are small", found + 1, 6);
	if (found != string::npos)
		cout << "second 'needle' found at: " << found << '\n';

	found = str.find("haystack");
	if (found != string::npos)
		cout << "'haystack' also found at: " << found << '\n';

	found = str.find('.');
	if (found != string::npos)
		cout << "Period found at: " << found << '\n';

	// let's replace the first needle:
	str.replace(str.find(str2), str2.length(), "preposition");
	cout << str << '\n';

	return 0;
}

rfind  

string (1)
size_t rfind (const string& str, size_t pos = npos) const;
c-string (2)
size_t rfind (const char* s, size_t pos = npos) const;
buffer (3)
size_t rfind (const char* s, size_t pos, size_t n) const;
character (4)
size_t rfind (char c, size_t pos = npos) const;

Find last occurrence of content in string----查找字符串中内容的最后一次出现

#include <iostream>
#include <string>
#include <cstddef>

int main()
{
	std::string str("The sixth sick sheik's sixth sheep's sick.");
	std::string key("sixth");

	std::size_t found = str.rfind(key);//找到sixth第二次出现的位置

	if (found != std::string::npos)
		str.replace(found, key.length(), "seventh");//seventh替换掉二次出现的sixth

	std::cout << str << '\n';

	return 0;
}

输出结果:The sixth sick sheik's seventh sheep's sick.

substr

string substr (size_t pos = 0, size_t len = npos) const;

Returns a newly constructed string object with its value initialized to a copy of a substring of this object-----返回一个新构造的字符串对象,其值初始化为此对象的子字符串的副本。

#include <iostream>
#include <string>

int main ()
{
  std::string str="We think in generalities, but we live in details.";
                                           // (quoting Alfred N. Whitehead)

  std::string str2 = str.substr (3,5);     // "think"

  std::size_t pos = str.find("live");      // position of "live" in str

  std::string str3 = str.substr (pos);     // get from "live" to the end

  std::cout << str2 << ' ' << str3 << '\n';

  return 0;
}

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好

string类非成员函数

operator+ (string)

string (1)
string operator+ (const string& lhs, const string& rhs);
c-string (2)
string operator+ (const string& lhs, const char*   rhs);
string operator+ (const char*   lhs, const string& rhs);
character (3)
string operator+ (const string& lhs, char          rhs);
string operator+ (char          lhs, const string& rhs);

Concatenate strings ----连接字符串

#include <iostream>
#include <string>

int main()
{
	std::string firstlevel("com");
	std::string secondlevel("cplusplus");
	std::string scheme("http://");
	std::string hostname;
	std::string url;

	hostname = "www. " + secondlevel + '.' + firstlevel;
	url = scheme + hostname;

	std::cout << url << '\n';

	return 0;
}

运行结果:http://www. cplusplus.com

operator>> (string) 

istream& operator>> (istream& is, string& str);

Extract string from stream----从流中提取字符串

#include <iostream>
#include <string>

int main()
{
	std::string name;

	std::cout << "Please, enter your name: ";
	std::cin >> name;
	std::cout << "Hello, " << name << "!\n";

	return 0;
}

operator<< (string)

 ostream& operator<< (ostream& os, const string& str);

Insert string into stream----将字符串插入流 

#include <iostream>
#include <string>

int main()
{
	std::string str = "Hello world!";
	std::cout << str << '\n';
	return 0;
}

getline (string)

(1)
istream& getline (istream& is, string& str, char delim);
(2)
istream& getline (istream& is, string& str);

Get line from stream into string----从流获取线到字符串 

#include <iostream>
#include <string>

int main()
{
	std::string name;

	std::cout << "Please, enter your full name: ";
	std::getline(std::cin, name);
	std::cout << "Hello, " << name << "!\n";

	return 0;
}

 relational operators (string)

(1)
bool operator== (const string& lhs, const string& rhs);
bool operator== (const char*   lhs, const string& rhs);
bool operator== (const string& lhs, const char*   rhs);
(2)
bool operator!= (const string& lhs, const string& rhs);
bool operator!= (const char*   lhs, const string& rhs);
bool operator!= (const string& lhs, const char*   rhs);
(3)
bool operator<  (const string& lhs, const string& rhs);
bool operator<  (const char*   lhs, const string& rhs);
bool operator<  (const string& lhs, const char*   rhs);
(4)
bool operator<= (const string& lhs, const string& rhs);
bool operator<= (const char*   lhs, const string& rhs);
bool operator<= (const string& lhs, const char*   rhs);
(5)
bool operator>  (const string& lhs, const string& rhs);
bool operator>  (const char*   lhs, const string& rhs);
bool operator>  (const string& lhs, const char*   rhs);
(6)
bool operator>= (const string& lhs, const string& rhs);
bool operator>= (const char*   lhs, const string& rhs);
bool operator>= (const string& lhs, const char*   rhs);

Relational operators for string----字符串的关系运算符

#include <iostream>
#include <vector>

int main()
{
	std::string foo = "alpha";
	std::string bar = "beta";

	if (foo == bar) std::cout << "foo and bar are equal\n";
	if (foo != bar) std::cout << "foo and bar are not equal\n";
	if (foo< bar) std::cout << "foo is less than bar\n";
	if (foo> bar) std::cout << "foo is greater than bar\n";
	if (foo <= bar) std::cout << "foo is less than or equal to bar\n";
	if (foo >= bar) std::cout << "foo is greater than or equal to bar\n";

	return 0;
}

string类的模拟实现

经典的string类问题

我们在模拟实现string的时候,可以先看看string类的经典问题。在实现string类的构造、拷贝构造、赋值运算符重载以及析构函数时,经常会出现深浅拷贝的问题。

构造String类对象的错误:

String(const char* str = "\0") ----错误示范,"\0"是需要内存存放的

String(const char* str = nullptr) ----错误示范,String是类,地址是不会指向nullptr

Sring(const char* str = "")----正确示范

构造String类对象时,如果传递nullptr指针,可以认为程序非法。

浅拷贝的问题:

#include <iostream>
#include <assert.h>
using namespace std;
class String
{
public:
String(const char* str = "")
{
	
	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 bit!!!");
	String s2(s1);
}

int main()
{
	TestString();

	return 0;
}

在这段代码中,我们发现测试的时候,开始s1传入字符串是没有问题的,但是将s1的地址传入s2时,就会出现错误。因为在s2调用构造函数的时候,s2中的_str存放的地址也是s1的地址。那么在调用析构函数的时候,我们就会发现调用了两次析构函数,第一将_str清除后,s2再调用析构函数的时候,此时_str已经是随机值,再对_str进行删除的时候就会报错。

 运行结果:

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块 空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝

那么什么是深拷贝?什么是浅拷贝呢?

浅拷贝/深拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享

深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

string类的模拟实现

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;
#include <assert.h>

#define npos -1

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;

	public:
		string(const char* str = "")//构造空的string类对象,即空字符串
		{
			// 构造String类对象时,如果传递nullptr指针,可以认为程序非法
			if (nullptr == str)
			{
				assert(false);
				return;
			}

			_str = new char[strlen(str) + 1];//扩容要加'/0'
			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;
			}
		}
		/
		// iterator--迭代器
		iterator begin()//返回到开始
		{
			return _str;
		}

		iterator end()//返回到结束
		{
			return _str + _size;
		}

		/
		// modify--修改

		void push_back(char c)//将字符附加到字符串
		{
			if (_size == _capacity)
				reserve(_capacity * 2);//2倍扩容

			_str[_size++] = c;
			_str[_size] = '\0';//++后添加上‘\0’
		}

		string& operator+=(char c)
		{
			push_back(c);
			return *this;
		}

		
		void append(const char* str)
		{
			size_t n = strlen(str);
			if (_size + n > _capacity )
			{
				reserve(_size + n);
			}

			strcpy(_str + _size, str);
			_size += n;

		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		void clear()
		{
			_str[0] = '\0';
			_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)
		{
			return strcmp(c_str(), s.c_str())<0;//需要接受char*
		}
		bool operator<=(const string& s)
		{
			return *this < s || *this == s;
		}
		//复用前<  ==
		bool operator>(const string& s)
		{
			return !(*this <= s);
		}

		bool operator>=(const string& s)
		{
			return !(*this < s);
		}

		bool operator==(const string& s)
		{
			return strcmp(c_str(), s.c_str()) == 0;
		}

		bool operator!=(const string& s)
		{
			return !(*this == s);
		}

		// 返回c在string中第一次出现的位置

		size_t find(char c, size_t pos = 0) const
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == c)
				{
					return pos;
				}
				pos++;
			}
			return npos;
		}

		// 返回子串s在string中第一次出现的位置
		//strstr:返回字符串中首次出现子串的地址
		size_t find(const char* s, size_t pos = 0) const
		{
			assert(pos < _size);
			const char* ptr = strstr(_str + pos, _str);

			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}

		// 在pos位置上插入字符c/字符串str,并返回该字符的位置
		string& insert(size_t pos, char c)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}

			//end--后,当减到-1时,是size_t类型就会变成一个很大的数,故此必须写成int类型,pos也将被强转
			//int end = _size;
			//while (end >= (int)pos)
			//{
			//	_str[end + 1] = _str[end];
			//	end--;
			//}
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}

			_str[pos] = c;
			++_size;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}

			size_t end = _size + len;

			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				end--;
			}

			strncpy(_str + len, str, len);
			return *this;
		}

		// 删除pos位置上的元素,并返回该元素的下一个位置
		string& erase(size_t pos, size_t len)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

	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;
	}
}

猜你喜欢

转载自blog.csdn.net/includeevey/article/details/128088562