目录
7.4 find_last_of()、find_firsr_not_of()和find_last_not_of()
一. string介绍
string是用来存储字符串的容器,能够储存字符。有能够扩宽容量改变长度的函数,包括构造函数等函数在内一共有45个函数。C语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。使用string需要包含标准库 #include <string> 以及using namespace std;
二、string中函数介绍
1 auto和范围for
- 在早期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;
}
- 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此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; // 长度
};
2 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) (重点) 拷贝构造函数
![](https://i-blog.csdnimg.cn/direct/665edc257ddd45bb952f3341a55771ff.png)
赋值重载就少一些,有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
}
3 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; }
4 有关iterator的函数 访问及遍历
string类对象的访问及遍历操作 函数名称 功能说明 operator[] (重点) 返回 pos 位置的字符, const string 类对象调用 begin + end begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器 rbegin + rend begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器 范围 for C++11 支持更简洁的范围 for 的新遍历方式
4.1 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;
}
5 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 深拷贝
![](https://i-blog.csdnimg.cn/direct/20a77ad163c7476aa1c30e766aa81d9e.png)
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;
}