讨论字符串

语言是由一句一句字符串组成的,编程中,我们想要输出一句话也少不了字符串的加入。

C语言中的字符串

C语言中没有字符串数据类型,但是存在字符串,使用""括起来的即字符串。字符串是以\0结尾的字符的集合。

// C语言中字符串的表现形式。
"abcd"; // 字符串常量
char str[] = "abcd";
char* strs = "abcd"; // str指向字符串首位置地址

字符串结束标志

C++中的字符串

C++标准库中的string类,就是字符串类。

string类文档

  • 根据文档介绍我们可以得知,字符串是表示字符序列的类。
  • string在底层实际是basic_string模板类的别名,typedef basic_string<char> string;
  • 由于string类在C++标准库中,因此在使用时需要使用std::string或者using namespace std;

string类的构造函数

string类的构造函数

string类的构造函数众多,可以通过字符串构造,可以通过一部分字符串构造,还可以使用迭代器区间构造。

#include <iostream>

using namespace std;

int main()
{
	string s; // 调用默认构造函数
	cout << s << endl;

	string s1("hello world");
	cout << s1 << endl;

	string s2("hello world", 2, 5);
	cout << s2 << endl;

	const char* str = "welcome";

	string s3(str);
	cout << s3 << endl;

	string s4(str, 2);
	cout << s4 << endl;

	string s5(10, 'x');
	cout << s5 << endl;

	return 0;
}

string构造函数结果

string类对象的容量操作

函数名功能
size返回字符串长度。
length返回字符串长度,和size作用一样。
capacity返回容器容量。
empty判断字符串是否为空。
clear清空字符串中数据。
reserve扩容/为字符串预留空间。
resize将有效字符个数设为n个,多出的空间用字符c填充。
max_size返回字符串最大长度。(基本没用)
shrink_to_fit缩容。(轻易不会选择缩容)

size()底层和length()完全相同,size()只是为了和其他容器接口保持一致。

int main()
{
	string s("hello world");
	cout << s.size() << endl;
	cout << s.length() << endl;
	return 0;
}

字符串长度

vs编译器下,string对象的初始capacity是15,以接近1.5倍的方式扩容。

int main()
{
	string s;
	int capacity = s.capacity();
	cout << s.capacity() << endl;
	for (int i = 0; i < 1000; i++)
	{
		int newCap = s.capacity();
		if (capacity != newCap)
		{
			cout << s.capacity() << endl;
			capacity = newCap;
		}
		s.push_back(i);
	}
	return 0;
}

扩容规则

在g++13编译器下,初始容量为15,后面以2倍方式扩容。

扩容

clear()清空内容,但不会影响容量。

int main()
{
	string s("hello worldxxxxxxxxxxx");
	cout << s.capacity() << endl;
	s.clear();
	cout << s.capacity() << endl;

	return 0;
}

clear清空数据

reserve(size_t n)可以指定字符串对象的初始容量,若n比初始容量小,则初始容量还是默认值。若比初始容量大,则字符串对象预先开出指定大小容量。

int main()
{
	string s;
	s.reserve(1);
	cout << s.capacity() << endl;

	s.reserve(100);
	cout << s.capacity() << endl;

	return 0;
}

reserve函数

resize(size_t n)/resize(size_t n, char c)n如果比现字符串长度小,则发生截断,若比现字符串长度长 ,则用字符c填充。

int main()
{
	string s1("hello world");
	s1.resize(4);
	cout << s1 << endl;

	string s2("no");
	s2.resize(10, '!');
	cout << s2 << endl;
	

	return 0;
}

resize函数

string类对象元素访问及遍历

函数名功能
operator[]返回pos位置的字符。
迭代器beginendbegin获取第一个字符的迭代器,end获取最后一个字符下一个位置的迭代器。
反向迭代器rbeginrend逆向遍历。
范围forC++11支持的语法,底层基于迭代器。

运算符重载operator[],也可以使用const修饰的对象使用。

void print_string(const string &s) {
    for (int i = 0; i < s.size(); ++i) {
        cout << s[i] << " ";
    }
    cout << endl;
}

int main() {
    string s("hello world");
    for (int i = 0; i < s.size(); ++i) {
        s[i] += 1;
        cout << s[i] << " ";
    }
    cout << endl;

    print_string(s);
    return 0;
}

operator[]

迭代器

begin迭代器

end迭代器

根据手册,begin迭代器返回指向第一个字符的位置,end迭代器返回指向末尾字符后面的位置。

int main() {
    string s("hello world");

    string::iterator it = s.begin();
    while (it != s.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

迭代器遍历

C++11实现范围for遍历方式,底层基于迭代器。只要实现迭代器,就可以使用范围for

范围for遍历

string类对象的修改操作

函数名功能
push_back在字符串末尾处插入字符。
append在字符串末尾处插入字符串。
operator+=在字符串末尾处插入字符串。
c_str返回C语言格式字符串。
findpos位置开始查找字符,返回该位置下标。
rfind反向查找。
substrpos位置开始,截取n个字符,返回截取后的字符串。

find()的返回值,若没有找到字符,则返回string::nposstring::nposstring类的静态成员变量,值是-1。

int main() {
    string s("hello world");

    size_t pos = s.find('n');
    if (pos != string::npos) {
        cout << "找到了" << endl;
    } else {
        cout << "没找到" << endl;
    }
    return 0;
}

npos

string类的模拟实现

成员变量

模拟实现的string类的成员变量,由三部分组成,分别是存放字符串的字符指针、字符串长度变量以及字符串容量变量。

char *_str;
size_t _size;
size_t _capacity;

构造函数

编写构造函数时,需要思考深浅拷贝问题,如果是浅拷贝,则在调用析构函数时会出现问题。因此,成员变量_str一定要申请自己的空间,不能直接拷贝传参的地址。

string(const char *str = "")
        : _size(strlen(str))
        , _capacity(_size) {
    _str = new char[_capacity + 1]; // 进行深拷贝
    for (size_t i = 0; i < _size; i++) {
        _str[i] = str[i];
    }
}

析构函数

_str是通过动态开辟内存空间,因此在析构函数中需要释放空间。

~string() {
    delete[] _str;
    _str = nullptr;
    _size = _capacity = 0;
}

扩容/预留空间

传入数字n,如果n比容量_capacity大,则扩容。

void reserve(size_t n) {
    if(n > _capacity) {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str); // 拷贝原有字符
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}

push_back

先需要判断容量是否满足条件,判断条件是_size == _capacity代表容量满了。

void push_back(char ch) {
    // 先判断容量
    if(_size == _capacity) {
        int newCapacity = _capacity == 0 ? 5 : 2 * _capacity;
        reserve(newCapacity);
    }
    _str[_size] = ch;
    _size++;
  	_str[_size] = '\0'; // 确保末尾位置是结束位
}

遍历

operator[]

只需要返回指定下标元素即可。

char &operator[](size_t pos) {
    return _str[pos];
}

const char &operator[](size_t pos) const {
    return _str[pos];
}

迭代器

此处迭代器采用指针方式,将char*作别名iterator

typedef char *iterator;
typedef const char *const_iterator;
  • 接下来只需要返回指定位置指针地址即可。
iterator begin() {
    return _str;
}

iterator end() {
    return _str + _size;
}

const_iterator begin() const {
    return _str;
}

const_iterator end() const {
    return _str + _size;
}
  • 只要迭代器实现,范围for就可以调用。

append

  • 首先还是需要考虑容量问题,但这次不是简单的扩容2倍了,需要按照传入的字符串来判断扩容容量。
string& append(const char* str) {
    int size = strlen(str);
    reserve(size + _size);
    strcpy(_str + _size, str);
    return *this;
}
  • 因此,operator+=可以直接复用。
string &operator+=(char ch) {
    push_back(ch);
    return *this;
}

string &operator+=(char *str) {
    append(str);
    return *this;
}

string &operator+=(const string &s) {
    int size = s._size;
    reserve(size + _size);
    strcpy(_str + _size, s._str);
    return *this;
}

任意位置插入数据和删除数据

  • 插入数据还是需要优先判断扩容,和前面差别不大,只是需要考虑数据挪动。
void insert(size_t pos, char ch) {
    assert(pos <= _size);
    if (_size == _capacity) {
        int newCapacity = _capacity == 0 ? 5 : 2 * _capacity;
        reserve(newCapacity);
    }

    size_t end = _size + 1;
    while (end > pos) {
        _str[end] = _str[end - 1];
        --end;
    }
    _str[pos] = ch;
    _size++;
    _str[_size] = '\0';
}

void insert(size_t pos, const char *str) {
    assert(pos <= _size);
    int size = strlen(str);
    reserve(_size + size);

    size_t end = _size + size;
    while (end > pos) {
        _str[end] = _str[end - size];
        --end;
    }
    strncpy(_str + pos, str, size);
    _size += size;
    _str[_size] = '\0';
}

void erase(size_t pos) {
    assert(pos <= _size);

    size_t begin = pos;
    while(begin < _size - 1) {
        _str[begin] = _str[begin + 1];
        ++begin;
    }
    _size--;
    _str[_size] = '\0';
}

查找元素

创建string类的静态成员函数npos,并设定值为-1。

static const size_t npos; // 声明
const size_t string::npos = -1;
size_t find(char ch, size_t pos = 0) const {
    for (size_t i = pos; i < _size; ++i) {
        if(_str[i] == ch) {
            return i;
        }
    }
    return npos;
}

size_t find(const char* str, size_t pos = 0) const {
    const char* ret = strstr(_str + pos, str);
    if(ret) {
        return ret - _str;
    } else {
        return npos;
    }
}

拷贝构造

比较容易想到的方法就是将char*一个字符一个字符拷贝。

string(const string &s)
        : _size(strlen(s._str)), _capacity(_size) {
    _str = new char[_capacity + 1];
    for (size_t i = 0; i < _size; i++) {
        _str[i] = s._str[i];
    }
}

也可以复用构造函数,构建一个临时的string对象,再交换this和临时对象,达到拷贝构造的效果。

string(const string &s)
        : _str(nullptr), _size(0), _capacity(_size) {
    string tmp(s._str);
    swap(tmp);
}
  • 同理,operator=()也可以通过这种方式实现。
string &operator=(string s) {
    swap(s);
    return *this;
}

切割字符串

通过起始位置和结束位置来切割字符串。

string substr(size_t pos = 0, size_t len = npos) {
    string s;
    size_t end = pos + len;
    if(len == npos || end >= _size) {
        len = _size - pos;
        end = _size;
    }

    s.reserve(len);
    for(size_t i = pos; i < end; i++) {
        s += _str[i];
    }
    return s;
}

完整实现


#ifndef BLOG_CODE_STRING_H
#define BLOG_CODE_STRING_H

#include <iostream>
#include <cstring>
#include <cassert>

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

        iterator begin() {
            return _str;
        }

        iterator end() {
            return _str + _size;
        }

        const_iterator begin() const {
            return _str;
        }

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

        string(const char *str = "")
                : _size(strlen(str)), _capacity(_size) {
            _str = new char[_capacity + 1];
            for (size_t i = 0; i < _size; i++) {
                _str[i] = str[i];
            }
        }

        void swap(string &s) {
            std::swap(_str, s._str);
            std::swap(_size, s._size);
            std::swap(_capacity, s._capacity);
        }

        string(const string &s)
                : _str(nullptr), _size(0), _capacity(_size) {
            string tmp(s._str);
            swap(tmp);
        }

        string &operator=(string s) {
            swap(s);
            return *this;
        }

        ~string() {
            delete[] _str;
            _str = nullptr;
            _size = _capacity = 0;
        }

        char *c_str() {
            return _str;
        }

        size_t size() {
            return _size;
        }

        size_t capacity() {
            return _capacity;
        }

        void resize(size_t n, char c = '\0') {
            if (n < _size) {
                _str[n] = '\0';
                _size = n;
            } else {
                reserve(n);
                while (_size < n) {
                    _str[_size++] = c;
                }
                _str[_size] = '\0';
            }
        }

        void reserve(size_t n) {
            if (n > _capacity) {
                char *tmp = new char[n + 1];
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;
                _capacity = n;
            }
        }

        void push_back(char ch) {
            // 先判断容量
            if (_size == _capacity) {
                int newCapacity = _capacity == 0 ? 5 : 2 * _capacity;
                reserve(newCapacity);
            }
            _str[_size] = ch;
            _size++;
            _str[_size] = '\0';
        }

        char &operator[](size_t pos) {
            return _str[pos];
        }

        const char &operator[](size_t pos) const {
            return _str[pos];
        }

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

        bool empty() {
            return _size == 0;
        }

        string &append(const char *str) {
            int size = strlen(str);
            reserve(size + _size);
            strcpy(_str + _size, str);
            return *this;
        }

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

        string &operator+=(char *str) {
            append(str);
            return *this;
        }

        string &operator+=(const string &s) {
            int size = s._size;
            reserve(size + _size);
            strcpy(_str + _size, s._str);
            return *this;
        }

        void insert(size_t pos, char ch) {
            assert(pos <= _size);
            if (_size == _capacity) {
                int newCapacity = _capacity == 0 ? 5 : 2 * _capacity;
                reserve(newCapacity);
            }

            size_t end = _size + 1;
            while (end > pos) {
                _str[end] = _str[end - 1];
                --end;
            }
            _str[pos] = ch;
            _size++;
            _str[_size] = '\0';
        }

        void insert(size_t pos, const char *str) {
            assert(pos <= _size);
            int size = strlen(str);
            reserve(_size + size);

            size_t end = _size + size;
            while (end > pos) {
                _str[end] = _str[end - size];
                --end;
            }
            strncpy(_str + pos, str, size);
            _size += size;
            _str[_size] = '\0';
        }

        void erase(size_t pos) {
            assert(pos <= _size);

            size_t begin = pos;
            while (begin < _size - 1) {
                _str[begin] = _str[begin + 1];
                ++begin;
            }
            _size--;
            _str[_size] = '\0';
        }

        size_t find(char ch, size_t pos = 0) const {
            for (size_t i = pos; i < _size; ++i) {
                if (_str[i] == ch) {
                    return i;
                }
            }
            return npos;
        }

        size_t find(const char *str, size_t pos = 0) const {
            const char *ret = strstr(_str + pos, str);
            if (ret) {
                return ret - _str;
            } else {
                return npos;
            }
        }

        string substr(size_t pos = 0, size_t len = npos) {
            string s;
            size_t end = pos + len;
            if (len == npos || end >= _size) {
                len = _size - pos;
                end = _size;
            }

            s.reserve(len);
            for (size_t i = pos; i < end; i++) {
                s += _str[i];
            }
            return s;
        }

    private:
        char *_str;
        size_t _size;
        size_t _capacity;
    public:
        static const size_t npos;
    };

    const size_t string::npos = -1;

    std::ostream &operator<<(std::ostream &out, const string &s) {
        for(char c : s) {
            out << c;
        }
        return out;
    }

    std::istream &operator>>(std::istream &in, string &s) {
        s.clear();

        char buff[129];
        size_t i = 0;

        char c;
        c = in.get();
        while(c != ' ' && c != '\n') {
            buff[i++] = c;
            if(i == 128) {
                buff[i] = '\0';
                s += buff;
                i = 0;
            }
            c = in.get();
        }

        if(i != 0) {
            buff[i] = '\0';
            s += buff;
        }
        return in;
    }
}


#endif //BLOG_CODE_STRING_H

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

烛九_阴

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

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

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

打赏作者

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

抵扣说明:

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

余额充值