C++ ⑥ string类

目录​​​​​​​

一. string介绍

二、string中函数介绍

1 auto和范围for

2 string类常见构造

3 string类容量操作

3.1  size()length()capacity()

3.2  resize()和reserve()

3.3  clear()和empty()

4 有关iterator的函数 访问及遍历

4.1  begin()和end()

4.2  rbegin()和rend()

5 string类对象的修改操作

6 string类非成员函数

7 字符串操作

7.1  c_str()

7.2  copy()

7.3  find()和find_first_of()

7.4  find_last_of()、find_firsr_not_of()和find_last_not_of()

7.5  substr()

7.6  compare()

8  npos

9、其他的重载函数

operators()

swap()       

 getline()

三. string类的模拟实现

1 经典的string类问题

2 浅拷贝

3 深拷贝

3.1 传统版写法的String类

3.2 现代版写法的String类

3 写时拷贝(了解)

4** string类的模拟实现 

string.h

string.cpp

test.cpp

补充 扩容测试

四 扩展阅读


一. string介绍

        string是用来存储字符串的容器,能够储存字符。有能够扩宽容量改变长度的函数,包括构造函数等函数在内一共有45个函数。
        C语言中,字符串是以  '\0'  结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
        使用string需要包含标准库 #include <string> 以及using namespace std;

二、string中函数介绍

1 auto和范围for

auto关键字
  • 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
  • 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加  &
  • 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
  • auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
  • auto不能直接用来声明数组
#include<iostream>
using namespace std;
int func1()
{
	return 10;
}
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
	return 3;
}
int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = func1();
	// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
	auto e;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	int x = 10;
	auto y = &x;
	auto* z = &x;
	auto& m = x;
	cout << typeid(x).name() << endl;
	cout << typeid(y).name() << endl;
	cout << typeid(z).name() << endl;
	auto aa = 1, bb = 2;
	// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
	auto cc = 3, dd = 4.0;
	// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
	auto array[] = { 4, 5, 6 };
	return 0;
}
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
	"橙子" }, {"pear","梨"} };
	// auto的用武之地
	//std::map<std::string, std::string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
    return 0;
}
范围for
  • 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
  • 范围for可以作用到数组容器对象上进行遍历
  • 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	// C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2;
	}
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		cout << array[i] << endl;
	}
	// C++11的遍历
	for (auto& e : array)
		e *= 2;
	for (auto e : array)
		cout << e << " " << endl;
	string str("hello world");
	for (auto ch : str)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

作为一个容器。string拥有一个指针指向存储char类型的空间,一个容量、一个记录长度的变量。不同的编译器有不同的版本,优化之后有可能会多一个变量,记录浅拷贝构造次数。但是在VS下,容器中的内容基本上都是深拷贝,会拷贝指针指向的内容。

    class string
    {
    private:
        char* _str; // 指向存储内容的指针
        size_t _capacity; // 容量
        size_t _size; // 长度
    };

string类常见构造

string类对象的常见构造
(constructor) 函数名称
功能说明
string() (重点)
构造空的 string 类对象,即空字符串
string(const char* s) (重点)
C-string 来构造 string 类对象
string(size_t n, char c)
string 类对象中包含 n 个字符 c
string(const string&s) (重点)
拷贝构造函数
 
构造函数有以上7种构造,比较常用的是第一种、第二种、第四种构造。支持默认构造拷贝构造和“char*”构造。当然也支持控制构造的长度以及迭代器构造,只是这些不常用。

赋值重载就少一些,有3种

        分别是用string、char*和字符char,进行赋值,其实前两种有一个就行了。char*能够构造string,所以第二个能包含在第一个里面。因为参数string&前面有const修饰,构成了隐示构造。

void Teststring()
{
  string s1; // 构造空的string类对象s1
  string s2("hello bit"); // 用C格式字符串构造string类对象s2
  string s3(s2); // 拷贝构造s3
}

string类容量操作

string类对象的容量操作
函数名称
功能说明
size (重点)
返回字符串有效字符长度
length
返回字符串有效字符长度
capacity
返回空间总大小
empty (重点)
检测字符串释放为空串,是返回 true ,否则返回 false
clear (重点)
清空有效字符
reserve (重点)
为字符串预留空间**
resize (重点)
将有效字符的个数该成 n 个,多出的空间用字符 c 填充
注意:
1. size() length() 方法底层实现原理完全相同,引入 size() 的原因是为了与其他容器的接
   口保持一致,一般情况下基本都是用 size()
2. clear() 只是将 string 中有效字符清空,不改变底层空间大小
3. resize(size_t n) resize(size_t n, char c)
   都是将字符串中有效字符个数改变到 n 个,
   不同的是当字符个数增多时:
   resize(n) 0 来填充多出的元素空间,
   resize(size_t n, char  c) 用字符 c 来填充多出的元素空间。
   注意: resize 在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,     如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0) :为 string 预留空间,不改变有效元素个数
   当 reserve 的参数小于string 的底层空间总大小时, reserver 不会改变容量大小

3.1  size()length()capacity()

         前两种的用法是相同的,都会返回string中元素的个数,而“capacity()”会返回当前string的容量。“size()”和“length()”的用法其实重复了所以之后的容器只剩下第一种用法。

3.2  resize()和reserve()

resize()这个函数能够控制string中剩下的元素个数,少了就补充,多了就删除。

      使用的时候多了会默认补充“ ”,也可以自己添加补充的元素。不过这里只用第二种重载就行,给他一个默认参数就好。

        reserve()的说法就比较多了,不同编译器下的操作不同。它是控制string容量的函数,能够增加容量、部分平台下能够减少容量。因为无论是增加或者说减少都会开新的空间,所以部分编译器不支持缩减。

3.3  clear()和empty()

        “clear()“用来清理string中的数据,不改变容量。

        “empty()”用来查看string中有没有数据,如果有会返回false,未有数据会返回true。

 
void test_string6()
{
    string str1("happe ending");
 
    cout << str1 << endl;
    cout << str1.empty() << endl << endl;
    
    str1.clear();
    cout << str1 << endl;
    cout << str1.empty() << endl << endl;
}

有关iterator的函数 访问及遍历

string类对象的访问及遍历操作
函数名称
功能说明
operator[] (重点)
返回 pos 位置的字符, const string 类对象调用
begin + end
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器
rbegin + rend
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器
范围 for
C++11 支持更简洁的范围 for 的新遍历方式

4.1  begin()和end()

          这两个函数,一个返回初始位置,一个返回末尾位置。在这里空间是左闭右开的,所以begin()返回的是头,但是end()返回的是最后的字符后面一位的位置。

        对于string的iterator来说是支持“++”、“--”,“+n”、“-n”的,同时支持解引用。所以如果想访问string中的元素能够通过iterator来访问。当然也就支持迭代器和范围for。

void test_string2()
{
    string str1("happe ending");
 
    string::iterator cur = str1.begin();
    while(cur != str1.end())
    {
        cout << *cur << " ";
        ++cur;
    }
 
    for(auto ch : str1)
    {
        cout << ch ;
    }
    cout << endl;
}

4.2  rbegin()和rend()

      string支持反向迭代器,所以有rbegin()和rend(),分别返回反向的iterator。使用起来还是比较怪异的,可以顾名思义的理解,就是转了顺序读字符串。能够倒着打印字符串。

void test_string3()
{
    string str1("happe ending");
 
    string::reverse_iterator cur = str1.rbegin();
    while(cur != str1.rend())
    {
        cout << *cur << " ";
        ++cur;
    }
 
    cout << endl;
}

string类对象的修改操作

string类对象的修改操作
函数名称
功能说明
push_back
在字符串后尾插字符 c
append
在字符串后追加一个字符串
operator+= ( 重点)
在字符串后追加字符串 str
c_str ( 重点 )
返回 C 格式字符串
find + npos ( 重点)
从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的 位置
rfind
从字符串 pos 位置开始往前找字符 c ,返回该字符在字符串中的
位置
substr
str 中从 pos 位置开始,截取 n 个字符,然后将其返回
注意:
1. 在string尾部追加字符时,s.push_back(c)  /  s.append(1, c)  /  s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可
以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

6 string类非成员函数

string类非成员函数
函数
功能说明
operator+
尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点)
输入运算符重载
operator<< (重点)
输出运算符重载
getline (重点)
获取一行字符串
relational operators (重点)
大小比较

上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有

7 字符串操作

这个就相当于是给了一个适应C语言的接口:

7.1  c_str()

        比较重要的是c_str()这个是为了访问文件方便,所以弄出来的。能够返回容器中指向资源的“char*”作用和“data()”相同。

7.2  copy()

       copy()用来将string中的内容复制到字符数组中。

void test_string9()
{
    string str1("hello world");
    char arr[20];
    str1.copy(arr, 12);
    cout << arr << endl;
}

//output: helloworld

7.3  find()和find_first_of()

      find()用于从头开始找字符,直到第一次找到返回该字符在字符串中的位置,否则返回npos。

void test_string10()
{
    std::string str ("There are two needles in this haystack with needles.");
    std::string str2 ("needle");
 
    // different member versions of find in the same order as above:
    std::size_t found = str.find(str2);
    if (found!=std::string::npos)
        std::cout << "first 'needle' found at: " << found << '\n';
 
    found=str.find("needles are small",found+1,6);
    if (found!=std::string::npos)
        std::cout << "second 'needle' found at: " << found << '\n';
 
    found=str.find("haystack");
    if (found!=std::string::npos)
        std::cout << "'haystack' also found at: " << found << '\n';
 
    found=str.find('.');
    if (found!=std::string::npos)
        std::cout << "Period found at: " << found << '\n';
 
    // let's replace the first needle:
    str.replace(str.find(str2),str2.length(),"preposition");
    std::cout << str << '\n';
 
}

        和find()函数不同find_first_of()用来查找一串字符串中出现的字符。只要字符串中出现过搜索字符串中的任何一个字符就返回当前位置,find()函数如果输入的是字符串的话就需要按照顺序全部一致。

void test_string11()
{
    std::string str ("Please, replace the vowels in this sentence by asterisks.");
    std::size_t found = str.find_first_of("aeiou");
    while (found!=std::string::npos)
    {
        str[found]='*';
        found=str.find_first_of("aeiou",found+1);
    }
 
    std::cout << str << '\n';
}

7.4  find_last_of()、find_firsr_not_of()和find_last_not_of()

        这三个放在一起,和find_first_of()有相同之处,find_last_of()表示从字符串最后开始搜索相同字符,另外两个分别是从前开始向后和从后开始向前找不同的字符位置。可以直接把find_first_of()的例子直接改成find_last_of()、find_firsr_not_of()和find_last_not_of(),最后打印出来查看效果

7.5  substr()

        别被函数名称骗了,这不是从这个函数里增加字符串,而是从原来字符串的某个位置开始提取相对长度得到字符串,最后返回一个临时变量。

void test_string12()
{
    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';
 
}

7.6  compare()

        compare()函数和C语言中strcmp()函数是一样类型的函数,用来比较字符串是否相等,不过compare()的重载函数很多,功能不完全一样。能够直接比较全部,也能够单独分出来一块部分比较。

void test_string13()
{
    std::string str1 ("green apple");
    std::string str2 ("red apple");
 
    if (str1.compare(str2) != 0)
    std::cout << str1 << " is not " << str2 << '\n';
 
    if (str1.compare(6,5,"apple") == 0)
    std::cout << "still, " << str1 << " is an apple\n";
 
    if (str2.compare(str2.size()-5,5,"apple") == 0)
    std::cout << "and " << str2 << " is also an apple\n";
 
    if (str1.compare(6,5,str2,4,5) == 0)
    std::cout << "therefore, both are apples\n";
}

8  npos

npos是在string中定义的固定值,类型是size_t,实际值为-1.

9、其他的重载函数

operators()

在重载函数中除了支持流插入流提取到string中以外,还能比较。例如“!=”"=="之类的都有重载,效果和compare()是相同的,只是由于重载的是符号所以使用起来更加方便。

void test_string14()
{
    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";
 
}

swap()       

比较重要的函数swap(),能够让两个string交换容器内的内容。在重载的赋值模拟中用处更大。

void test_string15()
{
    std::string buyer ("money");
    std::string seller ("goods");
 
    std::cout << "Before the swap, buyer has " << buyer;
    std::cout << " and seller has " << seller << '\n';
 
    swap (buyer,seller);
 
    std::cout << " After the swap, buyer has " << buyer;
    std::cout << " and seller has " << seller << '\n';
}

 getline()

      和流提取不同getline()函数能够提取到“ ”和“\n”,这个时候需要自己限制结束条件,总之,用这个能够提取“ ”。

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

三. string类的模拟实现

1 经典的string类问题

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现
string类的构造拷贝构造赋值运算符重载以及析构函数
大家看下以下string类的实现是否有问题?
// 为了和标准库区分,此处使用String
class String
{
public:
	/*String()
	:_str(new char[1])
	{*_str = '\0';}
	*/
	//String(const char* str = "\0") 错误示范
	//String(const char* str = nullptr) 错误示范
	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 man!!!");
	String s2(s1);
}
说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为
浅拷贝

2 浅拷贝

浅拷贝:也称位拷贝,
编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
1.析构两次
2.一个修改会影响另外一个
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我夺,玩具损坏。
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

3 深拷贝

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

3.1 传统版写法的String类

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

3.2 现代版写法的String类

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(nullptr)
	{
		String strTmp(s._str);
		swap(_str, strTmp._str);
	}
	// 对比下和上面的赋值那个实现比较好?
	String& operator=(String s)
	{
		swap(_str, s._str);
		return *this;
	}
	/*
	String& operator=(const String& s)
	{
	if(this != &s)
	{
	String strTmp(s);
	swap(_str, strTmp._str);
	}
	return *this;
	}
	*/
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

3 写时拷贝(了解)

写时拷贝 就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数: 用来记录资源使用者的个数
在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1, 然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

4** string类的模拟实现 

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS

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

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

	public:
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s) //局部域 参数不匹配
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);

		}

		/*string& operator=(string s)
		{
			this->swap(s);
			return *this;
		}*/
		string& operator = (string tmp)
		{
			swap(tmp);
		
			return *this;
		}

		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;  //添加②
			}
		}

		const char* c_str() const
		{
			return _str;
		}

		/
		// iterator 1
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}

		/
		// modify  2
		void push_back(char c)
		{
			if (_size == _capacity)
				reserve(_capacity * 2);

			_str[_size++] = c;
			_str[_size] = '\0';
		}

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

		//void append(const char* str);
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//大于2倍,需要多少开多少,小于2倍 2倍扩
				reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);//避免频繁扩容
			}
			strcpy(_str + _size, str);
			_size += len;
			//strcat()不适合 在/0
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}

		/
		// capacity  3
		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  4
		char& operator[](size_t index)
		{
			assert(index < _size);
			return _str[index];
		}

		const char& operator[](size_t index)const
		{
			assert(index < _size);
			return _str[index];
		}

		// 返回c在string中第一次出现的位置
		size_t find(char ch, size_t pos = 0);
		// 返回子串s在string中第一次出现的位置
		size_t find(const char* str, size_t pos = 0) const;
		string substr(size_t pos = 0, size_t len = npos);

		// 在pos位置上插入字符c/字符串str,并返回该字符的位置
		void insert(size_t pos, char c);//string&
		void insert(size_t pos, const char* str); //string&

		// 删除pos位置上的元素,并返回该元素的下一个位置
		void erase(size_t pos, size_t len = npos);

	private:
		char* _str = nullptr;
		size_t _capacity = 0;
		size_t _size = 0;
		static const size_t npos; //添加①
	};

	ostream& operator<<(ostream& out, const string& s);//不可写成成员函数 加不加const 读取值
	istream& operator>>(istream& in, string& s);//

	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);
}

string.cpp

#include "string.h"

namespace bit
{
	const size_t string::npos = -1;

	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}
	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}
	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}
	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}
	
	// 返回c在string中第一次出现的位置//
	size_t string::find(char ch, size_t pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}

	size_t string::find(const char* str, size_t pos ) const
	{
		assert(pos < _size);

		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;  //下标相减
		}
	}

	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);

		//len 大于剩余字符长度,更新len
		if (len > _size - pos)
		{
			len = _size - pos;
		}

		string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}

		return sub;
	}

	// 在pos位置上插入字符c/字符串str,并返回该字符的位置//
	//插入一个字符
	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size); //走断言 =size 尾插
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//挪动数据
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];// + 1
			end--;
		}
		_str[pos] = ch;
		++_size;
	}
	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);

		size_t len = strlen(str);

		if (_size + len > _capacity)
		{
			//大于2倍,需要多少开多少,小于2倍 2倍扩
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);//避免频繁扩容
		}
		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}
		_size += len;
	}

	// 删除pos位置上的元素,并返回该元素的下一个位置///

	void string::erase(size_t pos, size_t len) //=npos
	{
		assert(pos < _size);
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;   //插入

		}
		else
		{
			for (size_t i = pos + len; i <= _size; i++)
			{
				_str[i - len] = _str[i];
			}
			_size -= len;
		}
	}

	//是否必须为友元函数 不需要
	ostream& operator<<(ostream& out, const string& s)//不可写成成员函数 加不加const 读取值
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

	//17:  优化流提取
	istream& operator>>(istream& in, string& s)//
	{
		s.clear();

		const int N = 256;
		char buff[N];
		int i = 0;

		char ch;
		//in >> ch;
		ch = in.get();//不涉及类型 一个字符一个字符读取
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;

				i = 0;
			}
			//s += ch;
			//in >> ch;
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

test.cpp

//模拟实现
#include "string.h"





namespace bit
{
	void test_string1() //测试iterator与三个遍历方式
	{
		string s1;//空 string 的实现是不符合规范的
		string s2("hello");
		cout << s1.c_str() << endl; //问题1:程序崩溃 打印不出 出现空指针的解引用
		cout << s2.c_str() << endl;

		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i] += 2;
			cout << s2.c_str() <<  endl;
		}

		//范围for遍历  报错 未找到begin
		for (auto e : s2)
		{
			cout << e << "";
		}
		cout <<endl;

		string::iterator it = s2.begin();
		while (it != s2.end())
		{
			//*it += 2;
			cout << *it << "";
			++it;
		}
		cout << endl;
	}

	void test_string2()  //测试插入 在pos位置上插入字符c/字符串str,并返回该字符的位置
	{
		string s1("hello world");
		s1 += 'x';
		s1 += '#';
		cout << s1.c_str() << endl;
		s1 += "hello bit";
		cout << s1.c_str() << endl;

		s1.insert(5, '$');
		cout << s1.c_str() << endl;

		s1.insert(0, '$');
		cout << s1.c_str() << endl;
		//C语言留下的坑 操作符两端的操作数的类型不同时 编辑器会悄悄的进行
		//类型转换 类型的提升和截断 让范围小的向范围大的提升 -1>=0; -1补码全111

		string s2("hello world");
		s2.insert(5, "$$$");
		cout << s2.c_str() << endl;//
		s2.insert(0, "&&&&&&&&&&&&");
		cout << s2.c_str() << endl;// 头插


	}
	void test_string3()   //测试删除 删除pos位置上的元素,并返回该元素的下一个位置
	{
		string s1("hello world");
		s1.erase(6, 100);
		cout << s1.c_str() << endl;

		string s2("hello world");
		s2.erase(6);
		cout << s2.c_str() << endl;

		string s3("hello world");//删一部分
		s3.erase(6, 3);
		cout << s3.c_str() << endl;
	}
	void test_string4()  //测试查找 返回c在string中第一次出现的位置  返回子串s在string中第一次出现的位置
	{
		string s("test.cpp.zip");
		size_t pos = s.find('.');
		string suffix = s.substr(pos);
		cout << suffix.c_str() << endl;

	
		s = suffix;
		cout << suffix.c_str() << endl;//赋值问题  默认赋值浅拷贝
		cout << s.c_str() << endl;
		s = s;
		cout << s.c_str() << endl;

	}
	void test_string5()   //测试重载运算符 relational operators
	{
		string s1("test.string.zip");
		string s2("test.string.cpp");
		cout << (s1 < s2) << endl;
		cout << (s1 == s2) << endl;
		cout << ("test" <= s2) << endl;
		cout << (s1 == "test") << endl;  

		cout << s1 << s2 << endl;  
		cin >> s1;
		cout << s1 << endl;

	}

	void test_string6() //测试modify
	{
		string name("John");
		string family("Smith");
		name += " K. ";        
		name += "Smith";  
		name += '\n';
		cout << name;

		string s1("test.string.zip7");
		string s2("test7");
		cout << s1 << s2 << endl;  
		//std::swap(s1, s2);
		s1.swap(s2);
		cout << s1 << s2 << endl; 
	}
	
}



int main() {
	
	bit::test_string1();//
	cout << "①测试iterator与三个遍历方式" << "\n" << endl;

	bit::test_string2();//	
	cout << "②测试插入" << "\n" << endl;

	bit::test_string3();//
	cout << "③测试删除" << "\n" << endl;

	bit::test_string4(); //
	cout << "④测试查找" << "\n" << endl;

	bit::test_string5();
	cout << "⑤测试重载运算符" << "\n" << endl;

	bit::test_string6();
	cout << "⑥测试modify" << "\n" << endl;

	return 0;
}

补充 扩容测试

#include "string.h"

void Teststring1()
{
	// 注意:string类对象支持直接用cin和cout进行输入和输出
	string s("hello, man!!!");
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.capacity() << endl;
	cout << s <<"\n"<< endl;

	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << "\n" << endl;

	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
	cout << s.size() << endl;
	cout << s.capacity() << "\n" << endl;

	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(15);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << "\n" << endl;

	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << "\n" << endl;
}

//====================================================================================
void Teststring2()
{
	string s;
	// 测试reserve是否会改变string中有效元素个数
	s.reserve(100);
	cout << s.size() << endl;
	cout << s.capacity() << "\n" << endl;

	// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
	s.reserve(50);
	cout << s.size() << endl;
	cout << s.capacity() << "\n" << endl;
}

// 利用reserve提高插入数据的效率,避免增容带来的开销
//====================================================================================
void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

// 构建vector时,如果提前已经知道string中大概要放多少个元素,可以提前将string中空间设置好
void TestPushBackReserve()
{
	string s;
	s.reserve(100);
	size_t sz = s.capacity();

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

int main() {
	Teststring1();
	Teststring2();
	TestPushBack();
	TestPushBackReserve();
	return 0;
}

四 扩展阅读

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Милашка

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值