string使用及模拟

前言

        相信看过我博客的小伙伴都已经C++的接触已经很久了,也没那么多废话。stl库直接走起,最开始、最简单的就是string。string就相当于是把C语言中的字符串“char[]”给升了级,像是顺序表一样多了记录长度和容量的大小,还加了很多的函数去控制这个对象。

        本篇博客将会介绍标准库中的string,string中各种函数的作用和用法,最后会穿插自己写的string模拟。最后扩展一下string的标准。

一、string介绍

        string是用来存储字符串的容器,能够储存字符。有能够扩宽容量改变长度的函数,包括构造函数等函数在内一共有45个函数。这里不会将所有函数都模拟,但是使用方法会大致讲到。

        string出现在C++早期,当时由于没有写标准库容器先例,所以先人也就是摸着石头过河,有很多函数出现了重复功能。部分函数可以说没有存在的必要,因此string在界内讨论许多,之后标准库容器吸收了教训就会好很多。

        使用string需要包含标准库<string>。

二、string中函数介绍

        这里我会挑选出比较常用的函数进行说明,构造和拷贝构造等函数会放到之后介绍。和模拟实现放在一起会好解释更多。

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

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

1、构造、拷贝构造和赋值重载

        这些函数的使用方法都是相似的,能够放在一起讲,就直接说了。

        构造函数有以上7种构造,比较常用的是第一种、第二种、第四种构造。支持默认构造拷贝构造和“char*”构造。当然也支持控制构造的长度以及迭代器构造,只是这些不常用。

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

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

        当然,string因为只有字符,所以支持流插入流提取,用法如下:

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。

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。使用起来还是比较怪异的,可以顾名思义的理解,就是转了顺序读字符串。于是能够倒着打印字符串。

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()”的用法其实重复了所以之后的容器只剩下第一种用法。

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()对字符串长度没有影响,也不能更改其内容。

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。


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语言中的字符串一样使用就行。

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()”相同。

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中的内容复制到字符数组中。

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。

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

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()

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

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

}

6.5、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";
}

7、npos

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

8、其他的重载函数

        在重载函数中除了支持流插入流提取到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(),能够让两个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()函数能够提取到“ ”和“\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模拟实现

        string的模拟实现,我会挑选比较实用的函数进行实现,包括构造函数、析构函数、函数重载、输入输出等等。

        那么接下来给出代码,并且参考前一大节string中的函数介绍内容,实现函数的功能,所有拷贝都是深拷贝,函数的写法思路,可以参考上一次写的“Date”。

1、模拟

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

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值