string使用及模拟
随缘与奇迹 2024-08-25 08:05:02 阅读 87
前言
相信看过我博客的小伙伴都已经C++的接触已经很久了,也没那么多废话。stl库直接走起,最开始、最简单的就是string。string就相当于是把C语言中的字符串“char[]”给升了级,像是顺序表一样多了记录长度和容量的大小,还加了很多的函数去控制这个对象。
本篇博客将会介绍标准库中的string,string中各种函数的作用和用法,最后会穿插自己写的string模拟。最后扩展一下string的标准。
一、string介绍
string是用来存储字符串的容器,能够储存字符。有能够扩宽容量改变长度的函数,包括构造函数等函数在内一共有45个函数。这里不会将所有函数都模拟,但是使用方法会大致讲到。
string出现在C++早期,当时由于没有写标准库容器先例,所以先人也就是摸着石头过河,有很多函数出现了重复功能。部分函数可以说没有存在的必要,因此string在界内讨论许多,之后标准库容器吸收了教训就会好很多。
使用string需要包含标准库<string>。
二、string中函数介绍
这里我会挑选出比较常用的函数进行说明,构造和拷贝构造等函数会放到之后介绍。和模拟实现放在一起会好解释更多。
但是基础还是需要介绍的,作为一个容器。string拥有一个指针指向存储char类型的空间,一个容量、一个记录长度的变量。不同的编译器有不同的版本,优化之后有可能会多一个变量,记录浅拷贝构造次数。但是在VS下,容器中的内容基本上都是深拷贝,会拷贝指针指向的内容。
<code> class string
{
private:
char* _str; // 指向存储内容的指针
size_t _capacity; // 容量
size_t _size; // 长度
};
1、构造、拷贝构造和赋值重载
这些函数的使用方法都是相似的,能够放在一起讲,就直接说了。
构造函数有以上7种构造,比较常用的是第一种、第二种、第四种构造。支持默认构造拷贝构造和“char*”构造。当然也支持控制构造的长度以及迭代器构造,只是这些不常用。
赋值重载就少一些,有3种:
分别是用string、char*和字符char,进行赋值,其实前两种有一个就行了。char*能够构造string,所以第二个能包含在第一个里面。因为参数string&前面有const修饰,构成了隐示构造。
当然,string因为只有字符,所以支持流插入流提取,用法如下:
<code>void test_string1()
{
// 构造
string str1("nice");
// 拷贝构造
string str2(str1);
// 默认构造
string str3;
// 赋值重载
str3 = str2;
cout << str2 << endl;
cout << str3 << endl;
cin >> str1;
cout << str1 << endl;
}
2、析构函数
析构函数释放申请的空间,自动调用,不做演示。
3、有关iterator的函数
这个类的函数比较多,但是都可以顾名思义。作为string的迭代器,它的iterator就能直接是“char*”这里分为两块讲就行了。
3.1、begin()和end()
这两个函数,一个返回初始位置,一个返回末尾位置。在这里空间是左闭右开的,所以begin()返回的是头,但是end()返回的是最后的字符后面一位的位置。
对于string的iterator来说是支持“++”、“--”,“+n”、“-n”的,同时支持解引用。所以如果想访问string中的元素能够通过iterator来访问。当然也就支持迭代器和范围for。
<code>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;
}
3.2、rbegin()和rend()
string支持反向迭代器,所以有rbegin()和rend(),分别返回反向的iterator。使用起来还是比较怪异的,可以顾名思义的理解,就是转了顺序读字符串。于是能够倒着打印字符串。
<code>void test_string3()
{
string str1("happe ending");
string::reverse_iterator cur = str1.rbegin();
while(cur != str1.rend())
{
cout << *cur << " ";
++cur;
}
cout << endl;
}
3.3、其他
这里还有其他的四个函数就不做讲解了,有兴趣的小伙伴可以去官网查。效果和前面4种一样,不如说就是改了个名。
4、有关容量的函数
这一类的函数比较多,需要将的也比较多。有的函数在不同编译器下效果还会不同,这里都会提到。
4.1、size()、length()和capacity()
前两种的用法是相同的,都会返回string中元素的个数,而“capacity()”会返回当前string的容量。“size()”和“length()”的用法其实重复了所以之后的容器只剩下第一种用法。
<code>void test_string4()
{
string str1("happe ending");
cout << str1.size() << endl;
cout << str1.length() << endl;
cout << str1.capacity() << endl;
}
4.2、resize()和reserve()
resize()这个函数能够控制string中剩下的元素个数,少了就补充,多了就删除。
使用的时候多了会默认补充“ ”,也可以自己添加补充的元素。不过这里只用第二种重载就行,给他一个默认参数就好。
reserve()的说法就比较多了,不同编译器下的操作不同。它是控制string容量的函数,能够增加容量、部分平台下能够减少容量。因为无论是增加或者说减少都会开新的空间,所以部分编译器不支持缩减。
resize()对字符串长度没有影响,也不能更改其内容。
<code>void test_string5()
{
string str1;
cout << str1.size() << endl;
str1.resize(20);
cout << str1.size() << endl;
str1.resize(10);
cout << str1.size() << endl;
cout << str1.capacity() << endl;
str1.reserve(100);
cout << str1.capacity() << endl;
str1.reserve(50);
cout << str1.capacity() << endl;
str1.reserve(10);
cout << str1.capacity() << endl;
}
不同编译器下结果不同,望周知。
4.3、clear()和empty()
“clear()“用来清理string中的数据,不改变容量。
“empty()”用来查看string中有没有数据,如果有会返回false,未有数据会返回true。
<code>
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.4、其他
另外两个函数用处较少所以不做讲解。
5、元素访问
元素访问最简单的就是“[]”访问了,就和C语言中的字符串一样使用就行。
<code>void test_string7()
{
string str1("happe ending");
size_t size1 = str1.size();
for(size_t i = 0; i < size1; ++i)
{
cout << str1[i];
}
}
也能够通过一下函数访问:
但是剩下的访问没这个方便,所以就只讲这一个常用的就足够了。
6、字符串操作
这个就相当于是给了一个适应C语言的接口:
函数很多,但是用处多的不多。
6.1、c_str()
比较重要的是c_str()这个是为了访问文件方便,所以弄出来的。能够返回容器中指向资源的“char*”作用和“data()”相同。
<code>void test_string8()
{
string file("test.txt");
// 这里需要用到char*所以需要取出来指针
FILE* fin = fopen(file.c_str(), "w");
string str1("over again");
for(auto ch : str1)
{
fprintf(fin, "%c", ch);
}
fclose(fin);
}
6.2、copy()
copy()用来将string中的内容复制到字符数组中。
<code>void test_string9()
{
string str1("hello world");
char arr[20];
str1.copy(arr, 12);
cout << arr << endl;
}
6.3、find()和find_first_of()
find()用于从头开始找字符,直到第一次找到返回该字符在字符串中的位置,否则返回npos。
<code>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()函数如果输入的是字符串的话就需要按照顺序全部一致。
<code>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';
}
6.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(),最后打印出来查看效果,这里不做演示,详情查看上方代码。
6.5、substr()
别被函数名称骗了,这不是从这个函数里增加字符串,而是从原来字符串的某个位置开始提取相对长度得到字符串,最后返回一个临时变量。
<code>void test_string12()
{
std::string str="We think in generalities, but we live in details.";code>
// (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';
}
6.5、compare()
compare()函数和C语言中strcmp()函数是一样类型的函数,用来比较字符串是否相等,不过compare()的重载函数很多,功能不完全一样。能够直接比较全部,也能够单独分出来一块部分比较。
<code>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";
}
7、npos
npos是在string中定义的固定值,类型是size_t,实际值为-1.
8、其他的重载函数
在重载函数中除了支持流插入流提取到string中以外,还能比较。例如“!=”"=="之类的都有重载,效果和compare()是相同的,只是由于重载的是符号所以使用起来更加方便。
<code>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(),能够让两个string交换容器内的内容。在重载的赋值模拟中用处更大。
<code>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()函数能够提取到“ ”和“\n”,这个时候需要自己限制结束条件,总之,用这个能够提取“ ”。
<code>void test_string16()
{
std::string name;
std::cout << "Please, enter your full name: ";
std::getline (std::cin,name);
std::cout << "Hello, " << name << "!\n";
}
三、string模拟实现
string的模拟实现,我会挑选比较实用的函数进行实现,包括构造函数、析构函数、函数重载、输入输出等等。
那么接下来给出代码,并且参考前一大节string中的函数介绍内容,实现函数的功能,所有拷贝都是深拷贝,函数的写法思路,可以参考上一次写的“Date”。
1、模拟
#include <string.h>
#include <assert.h>
using namespace std;
namespace lcs
{
class string
{
friend ostream& operator<<(ostream& _cout, const lcs::string& s);
friend istream& operator>>(istream& _cin, lcs::string& s);
public:
typedef char* iterator;
public:
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size + 1;
_str = new char[_capacity];
strcpy(_str, str);
}
string(const string& s)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity];
strcpy(_str, s._str);
}
string& operator=(const string &s)
{
string tmp(s);
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//
// iterator
iterator begin()
{
assert(_str);
return _str;
}
iterator end()
{
assert(_str);
return _str + _size;
}
/
// modify
void push_back(char c)
{
assert(_str);
if(_size + 1 == _capacity) // 满了扩容
{
_capacity = _capacity > 3 ? _capacity * 2 : 4;
char* tmp = new char[_capacity];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
}
_str[_size++] = c;
}
string& operator+=(char c)
{
assert(_str);
push_back(c);
return *this;
}
void append(const char* str)
{
assert(_str);
int len = strlen(str);
for(int i = 0; i < len; ++i)
{
push_back(str[i]);
}
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void clear()
{
_size = 0;
_str[_size] = '\0';
}
void swap(string& s)
{
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
std::swap(_str, s._str);
}
const char* c_str()const
{
assert(_str);
return _str;
}
/
// capacity
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
void resize(size_t n, char c = '\0')
{
assert(_str);
if(n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
for(int i = _size; i < n; ++i)
{
push_back(c);
}
}
}
void reserve(size_t n)
{
assert(_str);
if(_capacity <= n)
{
_capacity = n > 2 * _capacity ? n : 2 * _capacity;
char* tmp = new char[_capacity];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
}
}
/
// 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];
}
/
//relational operators
bool operator<(const string& s)
{
return strcmp(_str, s._str) < 0;
}
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(_str, s._str) == 0;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
// 返回c在string中第一次出现的位置
size_t find (char c, size_t pos = 0) const
{
for(int i = pos; i < _size; ++i)
{
if(c == _str[i])
{
return i;
}
}
return -1;
}
// 返回子串s在string中第一次出现的位置
size_t find (const char* s, size_t pos = 0) const
{
char* tmp = strstr(_str + pos, s);
if(tmp == nullptr)
{
return -1;
}
return tmp - _str;
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if(_size + 1 == _capacity)
{
reserve(_size + 2);
}
for(size_t i = _size; i > pos; --i)
{
_str[i] = _str[i - 1];
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if(len == 0)
{
return *this;
}
if(_size + len >= _capacity)
{
reserve(_size + len + 1);
}
for(size_t i = _size + len - 1; i > pos + len - 1; --i)
{
_str[i] = _str[i - len];
}
for(size_t i = 0; i < len; ++i)
{
_str[i + pos] = str[i];
}
_size += len;
return *this;
}
// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos = 0, size_t len = -1)
{
if(pos >= _size)
{
return *this;
}
if(pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
int n = _size - pos - len;
for(size_t i = 0; i < n; ++i)
{
_str[pos + i] = _str[pos + i + len];
}
_str[pos + n] = '\0';
_size = pos + n;
}
return *this;
}
private:
char* _str;
size_t _capacity;
size_t _size;
};
ostream& operator<<(ostream& _cout, const lcs::string& s)
{
size_t size = s.size();
for(size_t i = 0; i < size; ++i)
{
_cout << s[i];
}
return _cout;
}
istream& operator>>(istream& _cin, lcs::string& s)
{
s.clear();
char ch = _cin.get();
while(ch != ' ' && ch != '\n')
{
s += ch;
ch = _cin.get();
}
return _cin;
}
}
2、测试
测试之中所写的,代码分为5个模块测试。如代码注释:
#include "string.hpp"
// 测试赋值、构造、流插入流输出
void test_string1()
{
lcs::string str1("hello world hahah");
lcs::string str2(str1);
cout << str1 << " " << str2 << endl;
lcs::string str3("ku");
str3 = str1;
cout << str3 << endl;
cin >> str3;
cout << str3 << endl;
}
// 测试迭代器,+=重载
void test_string2()
{
lcs::string str1("hello world");
for(auto ch : str1)
{
cout << ch << " ";
}
cout << endl;
str1 += "overcome your self";
cout << str1 << endl;
}
// 测试和容量相关的函数size()/capacity(),resize()/reserve()
void test_string3()
{
lcs::string str1;
cout << str1.capacity() << endl;
str1.reserve(81);
cout << str1.capacity() << endl;
str1.reserve(66);
cout << str1.capacity() << endl << endl;
cout << str1.size() << endl;
str1.resize(20);
cout << str1.size() << endl;
str1.resize(10);
cout << str1.size() << endl;
}
// 测试比较函数的重载
void test_string4()
{
lcs::string str1("call");
lcs::string str2("call");
lcs::string str3("cell");
cout << (str1 == str2) << endl;
cout << (str1 < str2) << endl;
cout << (str1 > str2) << endl;
cout << (str1 != str2) << endl;
cout << (str1 >= str2) << endl;
cout << (str1 <= str2) << endl;
cout << (str1 == str3) << endl;
cout << (str1 == "call") << endl;
}
// 测试不规则插入、输出函数
void test_string5()
{
lcs::string str1("hallo");
str1.insert(5, " ###");
str1.insert(0, "$$$ ");
cout << str1 << endl;
str1.erase(8, 1);
cout << str1 << endl;
str1.erase(8, 10);
cout << str1 << endl;
}
int main()
{
test_string1();
test_string2();
test_string3();
test_string4();
test_string5();
return 0;
}
结语
本来模拟打算多讲一点的,但是感觉有点废话就省掉了,主要是实现出来的内容其实和之前介绍的讲的差不多,有一些函数没有模拟出来,其实是因为不是很常用。
下一篇就是将vector了,vector和string模拟其实挺像的,但是为了和stl库中实现的大致相同,那么就会出现迭代器失效等等问题。提一嘴,之后遇到了细讲。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。