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、模拟

<code>#include <iostream>

#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库中实现的大致相同,那么就会出现迭代器失效等等问题。提一嘴,之后遇到了细讲。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。