当前位置: 首页 > article >正文

【STL】string类用法介绍及部分接口的模拟实现

1.前言

​ string即字符串,我们知道在C语言中字符串是以‘\0’结尾的字符合集,同时C标准库中也提供了str系列如strlen、strcpy等一系列库函数,但它们与字符串分离,这与C++面向对象编程思想不合,因此在C++库中添加了string类以供使用者使用,下面我们就来介绍一下。

2.string类的介绍及使用

​ 注:本文在此仅介绍部分经常使用的接口函数,若想查看所有用法请参考

cplusplus

2.1 string类对象的构造

函数名功能
string()构造空的string类对象
string(const char s)*用C-string来构造string类对象
string(size_t n,char c)string类对象中包含n个c字符
string(const string&s)通过s拷贝构造

eg:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1;//注意不要用成string s1();哦,注意区分
	string s2("hello world");
	string s3(5, 'x');
	string s4(s2);
	
    //打印s1~s4
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述

2.2 string类对象的容量操作

2.2.1 size()

1.作用:返回字符串的有效长度,不包括’\0’,且不会改变string中有效元素个数

2.使用:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1("hello world");//不算'\0'共11个字符
	cout << s1.size() << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述

上面提到size()的返回值不包括’\0’,那么在string类对象中其字符串结尾是否存在’\0’呢?我们通过下面代码调试查看一下:

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

	char temp = s1[s1.size()];
	return 0;
}

通过调试窗口我们发现是存在’\0’的,但其不算在size大小之中:
在这里插入图片描述

2.2.2 reserve()

1.作用:为string类对象至少预留指定大小的内存空间。

2.使用:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1("hello world");
	cout << "before reserve's capacity:" << s1.capacity() << endl;//打印reserve前s1对象的空间大小

	s1.reserve(50);
	cout << "before reserve's capacity:" << s1.capacity() << endl;//打印reserve后s1对象的空间大小

	return 0;
}

结果如下:

在这里插入图片描述

扩展:有人会好奇为什么reserve后s1空间大小不是50,这是因为在VS下其扩容是以1.5倍左右扩容:

int main()
{
	string s1("x");
	int capacity = s1.capacity();
	cout << capacity << endl;
	while (capacity < 10000)
	{
		s1.append("xxxxxxxxxx");//append函数作用是在字符串后添加10个x,当空间大小不够时底层会使用reserve扩容
		//每次扩容后查看扩容后的空间大小
		if (s1.capacity() != capacity)
		{
			capacity = s1.capacity();
			cout << capacity << endl;
		}
	}
	return 0;
}
输出结果:
15
31
47
70
105
157
235
352
528
792
1188
1782
2673
4009
6013
9019
13528

可以发现每次扩容大约是上次的1.5倍

在VS2022下当指示预留空间的大小小于之前已有的空间大小时不会进行缩容:

int main()
{
	string s1("hello world");
	s1.reserve(100);
	cout << "reserve(100) capacity:" << s1.capacity() << endl;
	s1.reserve(50);
	cout << "reserve(50) capacity:" << s1.capacity() << endl;
	return 0;
}

结果如下:

在这里插入图片描述

注:在不同情况下reserve的扩容情况不同,比如在Linux g++下reserve每次扩容的大小近似2倍。

2.2.3 resize()

1.作用:调整string类对象的长度。若新长度大于当前长度,会在原字符串末尾添加指定字符(默认为空字符’\0’)来达到新长度;若新长度小于当前长度,则会截断字符串。

2.使用:

#include <iostream>
#include <string>
using namespace std;
int main() {
    string str = "abc";
    cout << "原始字符串: " << str << endl;
    cout << "原始capacity:" << str.capacity() << endl;
    
    // 增大字符串长度,默认用 '\0' 填充,指定大小小于capacity时capacity不变
    str.resize(10);
    cout << "增大长度后的字符串: " << str << endl;
    cout << "增大长度后capacity:" << str.capacity() << endl;
    
    // 增大字符串长度,用 'x' 填充,指定大小大于capacity时capacity变大
    str.resize(20, 'x');
    cout << "再次增大长度后的字符串: " << str << endl;
    cout << "再次增大长度后capacity:" << str.capacity() << endl;

    //减小字符串长度,指定大小小于capacity时capacity不变,相当于尾删
    str.resize(5);
    cout << "缩小长度后的字符串: " << str << endl;
    cout << "缩小长度后capacity:" << str.capacity() << endl;
    return 0;
}

结果如下:

在这里插入图片描述

注意:关于为什么再次增大后打印出来的x数量不是17个以及缩小长度后打印出来的字符串只有abc的问题,原因是第一次增大字符串长度时是用’\0’填充,其要占用字符串的空间大小,但其不作为结束符的标志(即不像C语言字符串一样遇到’\0’就结束)。

2.2.4 clear()

1.移除string类对象中的所有字符,使字符串长度变为 0。调用该方法后,字符串就为空了,但它所占的空间通常不会改变。

2.使用:

#include <iostream>
#include <string>
using namespace std;
int main() {
    string str = "Hello, World!";
    cout << "原始字符串: " << str << endl;
    cout << "原始字符串长度: " << str.size() << endl;
    cout << "原始字符串容量: " << str.capacity() << endl;

    // 清空字符串
    str.clear();

    cout << "清空后的字符串: " << str << endl;
    cout << "清空后的字符串长度: " << str.size() << endl;
    cout << "清空后的字符串容量: " << str.capacity() << endl;
    return 0;
}

结果如下:

在这里插入图片描述

2.2.5 其它容量操作

函数名功能
length()与size()功能相同
capacity()返回空间大小
empty()检测字符串是否为空串,是返回true,否则返回false

2.3 string类对象的访问及遍历

2.3.1 operator[]

1.作用:重载[],返回pos位置的字符

2.使用:

#include <iostream>
#include <string>
using namespace std;
int main() {
    string str = "Hello, World!";
    cout << "修改pos=4前:" << str[4] << endl;//通过pos位置打印出pos位置的字符

    str[4] = 'x';//修改pos位置的值
    cout << "修改pos=4后:" << str[4] << endl;
    return 0;
}

2.3.2 正向迭代器begin()+end()和反向迭代器rbegin()+rend()

1.作用:begin()、end()、 rbegin()、rend()都是迭代器相关的成员函数,在string类中用于标记字符串的起始结束位置,通过使用迭代器可以进行string类对象的遍历

2.使用:

#include <iostream>
#include <string>
using namespace std;
int main() {
    string str = "Hello, World!";
    cout << "正向遍历begin()+end():";
    string::iterator it1 = str.begin();//正向
    while (it1 != str.end())
    {
        cout << *it1;
        ++it1;
    }
    cout << endl;
    cout << "反向遍历begin()+end():";
    string::reverse_iterator it2 = str.rbegin();//反向
    while (it2 != str.rend())
    {
        cout << *it2;
        ++it2;
    }
    return 0;
}

结果如下:

在这里插入图片描述

2.3.3 范围for的使用

使用:

#include <iostream>
#include <string>
using namespace std;
int main() 
{
    string str = "Hello, World!";
    //auto自动识别类型
    for (auto ch : str) 
    {
        cout << ch;
    }
    return 0;
}

输出结果:

在这里插入图片描述

2.4 string类的增删查改操作

2.4.1 push_back()

1.作用:将一个字符添加到string类对象的末尾,使字符串的长度增加1(size+1)

2.使用:

#include <iostream>
#include <string>
using namespace std;
int main() 
{
    string str = "Hello, World!";
    cout << "before push_back:" << str << endl;

    str.push_back('x');
    cout << "after push_back('x'):" << str << endl;

    char ch = 'y';
    str.push_back(ch);
    cout << "after push_back(ch):" << str << endl;
    return 0;
}

输出结果如下:

在这里插入图片描述

2.4.2 append()

1.作用:在字符串后追加一段字符串

2.使用:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    //尾插一段字符串
	string s1("hello ");
	cout << "s1 before append:" << s1 << endl;
	s1.append("world");
	cout << "s1 after append:" << s1 << endl;
	
    //尾插另一段字符串下标i~j的内容,j不写时默认从i位置到该字符串结束位置
	string s2("hello");
	string s3("hello world");
	cout << "s2 before append:" << s2 << endl;
	s2.append(s3,5,10);
	cout << "s2 after append:" << s2 << endl;
	return 0;
}

结果如下:

在这里插入图片描述

2.4.3 operator+=

1.作用:重载+=,在字符串后追加字符串

2.使用:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1("hello ");
	string s2("world");
	char ch = 'x';
	cout << s1 << endl;
    //尾插一段字符串
	s1 += "world";
	cout << s1 << endl;
    //尾插另一个string类的str内容
	s1 += s2;
	cout << s1 << endl;
    //尾插一个字符
	s1 += ch;
	cout << s1 << endl;
	return 0;
}

结果如下:
在这里插入图片描述

2.4.4 insert()

1.作用:在 string 类对象的任意位置插入新的内容

2.使用:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1("hello");
	string s2("world ");
	const char* ch = "xxxxxxxx";
	cout << s1 << endl;
	s1.insert(2, 3, 'n');//在下标2位置前插入3个x字符
	cout << s1 << endl;
	s1.insert(0, s2);//在下标0位置前插入s2字符串(头插s2)
	cout << s1 << endl;
	s1.insert(10, ch);//在下标10为之前插入一个C风格字符串
	cout << s1 << endl;
	s1.insert(s1.begin(), 'x');//可以结合迭代器使用
	cout << s1 << endl;
	return 0;
}

结果如下:

在这里插入图片描述

2.4.5 erase()

1.作用:从字符串中语出指定位置为字符或一段字符序列

2.使用:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1 = "hello world";
	s1.erase(2, 5); //删除s1下标从2开始的5个字符
	cout << s1 << endl;

	string s2 = "hello world";
	s2.erase(2); //删除s1下标从2开始的剩余字符
	cout << s2 << endl; 

	string s3 = "hello world";
	s3.erase(); //删除整个字符串
	cout << s3 << endl;

	string s4 = "hello, world";
	//结合迭代器使用
	//s4.erase(s4.begin());//头删
	s4.erase(s4.begin(), s4.begin()+4);// 删除指定区域的内容 
	cout << s4 << endl;
	return 0;
}

结果如下:
在这里插入图片描述

2.4.6 find()

1.作用:在string类对象中搜索指定的内容,并返回该内容首次出现的起始位置(索引)。如果未找到指定内容,则返回 string::npos,这是一个静态常量,表示无效的位置。

2.使用:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1("hello world");
	string s2("world");
	
	cout << "w:" << s1.find('o',5) << endl;//查找字符类型o,从下标为5的位置开始找
	cout << "rld:" << s1.find("world") << endl;//查找C类型字符串,可在后面指定起始区域
	cout << "s2:" << s1.find(s2) << endl;//查找string类型字符串,可在后面指定起始区域
	cout << "npos:" << s1.find('x') << endl;//找不到返回npos
	return 0;
}

结果如下:

在这里插入图片描述

2.4.7 substr()

1.作用:从调用它的string对象里提取一部分字符,生成一个新的string对象。

2.使用:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s1 = "Hello, World!";
    string subStr1 = s1.substr(7);//从下标为7的位置开始提取后面的字符串给subStr1
    cout << "从下标7开始的子字符串: " << subStr1 << endl;

    string subStr2 = s1.substr(0, 5);// 从下标0位置开始,提取长度为5的子字符串给subStr2
    cout << "从下标0开始,长度为5的子字符串: " << subStr2 << endl;

    return 0;
}

结果如下:
在这里插入图片描述

2.4.8 c_str()

1.作用:返回一个指向以 '\0' 结尾的字符数组(C 风格字符串)的指针,该字符数组包含了string对象中的数据。

2.使用:在 C++ 程序中,有时需要调用一些使用 C 风格字符串的 C 语言函数,例如 printf()、fopen() 等。由于这些函数只能处理以 '\0结尾的字符数组,此时就可以使用 c_str()方法将 string对象转换为 C 风格字符串。示例代码如下:

#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
int main() {
    string filename = "example.txt";
    // 使用 c_str() 将 string 转换为 C 风格字符串
    FILE* file = fopen(filename.c_str(), "r");
    if (file) {
        cout << "文件打开成功!" << endl;
        fclose(file);
    }
    else {
        cout << "文件打开失败!" << endl;
    }
    return 0;
}

关于C++string类的介绍暂时就到这里,其余用法可参考
cplusplus

3.string类部分模拟实现

​ 对于string类的模拟实现这里不做详细原理介绍,仅提供一份个人写的参考代码。有任何问题欢迎讨论区提出,谢谢。

3.1 string.h

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace mystring
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		const_iterator begin() const
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];

			strcpy(_str, str);
		}

		//传统写法
		/*string(const string& s)
		{
			_str = new char[s._capacity+1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		string& operator=(string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;

				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/
		//会发生浅拷贝
		/*string& operator=(string& s)
		{
			_str = s._str;
			_size = s._size;
			_capacity = s._capacity;
			
			return *this;
		}*/

		//现代写法
		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
		}
		
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}

		~string() 
		{
			delete[] _str;
			_size = 0;
			_capacity = 0;
		}
		size_t size()
		{
			return _size;
		}
		const char* c_str() const
		{
			return _str;
		}

		const char& operator[](size_t pos) const;
		char& operator[](size_t pos);

		string& operator+=(char ch);
		string& operator+=(const char* str);

		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* s);
		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);
		void swap(string& s);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		string substr(size_t pos = 0, size_t len = npos);
		void clear();

		/*void Print_addr(int n = 0)
		{
			for (int i = 0; i < _size; i++)
			{
				printf("a[%d]%p\n", i, &_str[i]);
			}
		}*/

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		static const size_t npos = -1;
	};
	istream& operator>>(istream& in, string& str);
	ostream& operator<<(ostream& out, string& str);
}

3.2 string.cpp

#define _CRT_SECURE_NO_WARNINGS
#include"string.h"
istream& mystring::operator>>(istream& in, string& str)
{
	str.clear();
	char buff[128];//一串一串写入
	char ch = in.get();
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[i] = '\0';
			str += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		str += buff;
	}
	return in;
}

ostream& mystring::operator<<(ostream& out, string& str)
{
	for (auto e : str)
	{
		out << e;
	}
	return out;
}

const char& mystring::string::operator[](size_t pos) const
{
	assert(pos >= 0 && pos < _size);
	return *(_str + pos);
}
char& mystring::string::operator[](size_t pos)
{
	assert(pos >= 0 && pos < _size);
	return *(_str + pos);
}

void mystring::string::reserve(size_t n)
{
	assert(n > 0);
	if (n > _capacity)
	{
		char* tmp = new char[n];
		strcpy(tmp, _str);
		free(_str);
		_str = tmp;
		_capacity = n;
	}
}

void mystring::string::push_back(char ch)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 1.5 * _capacity);
	}
	_str[_size] = ch;
	_size++;
}

void mystring::string::append(const char* s)
{
	size_t s_len = strlen(s);
	while (_size + s_len > _capacity)
	{
		reserve(_capacity == 0 ? 4 : 1.5 * _capacity);
	}
	strcpy(_str + _size, s);
	_size += s_len;
}

void mystring::string::insert(size_t pos, char ch)
{
	assert(pos >= 0&&pos<=_size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 1.5 * _capacity);
	}
	for (int i = _size; i > pos; i--)
	{
		_str[i] = _str[i-1];
	}
	_str[pos] = ch;
	_size += 1;
}

void mystring::string::insert(size_t pos, const char* str)
{
	assert(pos >= 0 && pos <= _size);
	int str_len = strlen(str);
	while (_size + str_len > _capacity)
	{
		reserve(_capacity + str_len);
	}
	for (int i = _size + str_len -1; i > pos + str_len - 1; i--)
	{
		_str[i] = _str[i - str_len];
	}
	strncpy(_str + pos, str,str_len);
	_size += str_len;
}

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

mystring::string& mystring::string::operator+=(const char* str)
{
	append(str);
	/*int str_len = strlen(str);
	for (int i = 0; i < str_len; i++)
	{
		*this += str[i];
	}*/
	return *this;
}

void mystring::string::erase(size_t pos, size_t len)
{
	assert(pos >= 0 && pos < _size);
	if (pos + len > _size)
	{
		_size = pos;
	}
	else
	{
		for (int i = pos; i < _size - len; i++)
		{
			_str[i] = _str[i + len];
		}
		_size -= len;
	}
}

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

size_t mystring::string::find(char ch, size_t pos)
{
	while (pos < _size)
	{
		if (_str[pos] == ch)
			return pos;
		pos++;
	}	
	return npos;
}

size_t mystring::string::find(const char* str, size_t pos)
{
	const char* judge = strstr(_str + pos, str);
	if (judge == nullptr)
		return npos;
	else
		return judge - _str;
}

mystring::string mystring::string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	size_t end = pos + len;
	if (len == npos || end >= _size)
	{
		end = _size;
	}
	string str;
	str.reserve(end - pos);
	for (size_t i = pos; i < end; i++)
	{
		str += _str[i];
	}
	return str;

}

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

3.3 test.cpp(个人部分测试用例)

#define _CRT_SECURE_NO_WARNINGS
#include "string.h"
void test1()
{
	mystring::string s1;
	s1.push_back('a');
	s1.reserve(20);
}

void test2()
{
	mystring::string s1("123456");
	s1.append("hello world");
}

void test3()
{
	mystring::string s("hello world");
	mystring::string s1 = s;
	mystring::string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	for (auto& e : s1)
	{
		cout << e;
	}

	const mystring::string s2("const test");
	mystring::string::const_iterator it1 = s2.begin();	
	while (it1 != s2.end())
	{
		cout << *it1;
		++it1;
	}
}

void test4()
{
	mystring::string s("hello world!");
	s.insert(0, "xxxxx");
	cout << s << endl;
}

void test5()
{
	mystring::string s("hello world!");
	s += "xxxxxxxxxxx";
	cout << s << endl;
	cout <<s.size();
}


void test6()
{
	mystring::string s("hello world!");
	cout << s << endl;
	s.erase(0);
	cout << s;
}

void test7()
{
	mystring::string s1("hello world!");
	mystring::string s2;
	s2 = s1;
	cout << s2 << endl;
	cout << s1;
}

void test8()
{
	mystring::string s1("hello world!");
	mystring::string s2(s1);

	cout << &s2 << endl;
	cout << s2 << endl;

	cout << &s1 <<endl;
	cout << s1;
}

void test9()
{
	mystring::string s1("hello worlld!");
	//s1.Print_addr();

	size_t found = s1.find('o',5);
	cout << found << endl;
	size_t fond = s1.find("ll",5);
	cout << fond;
}

void test10()
{
	mystring::string s1("hello worlld!");
	mystring::string s2 = s1.substr();
	cout << s2;
}

void test11()
{
	mystring::string s1;
	cin >> s1;
	cout << s1;
}

void test12()
{
	mystring::string s1("hello worlld!");
	s1 += 'a';
	s1 += " nihao shijie";
	cout << s1;
}


int main()
{  
 	//test12();
	/*string s("hello world!");
	s.erase(0);*/

	/*string s("hello world!");
	s.insert(0, "xxxxx");
	cout << s << endl;*/

	
	return 0;
}

本文到此结束,有任何问题包括有想要作者介绍一下的string接口都欢迎私信、评论区提出,看到后会抽空对文章进行添加、修改,谢谢。


http://www.kler.cn/a/592158.html

相关文章:

  • pnpm创建vite
  • 蓝桥杯第13届真题2
  • C++项目:高并发内存池_上
  • 【云原生之kubernetes实战】在k8s环境中高效部署minio对象存储(详细教程)
  • pytorch 笔记:张量索引的维度扩展规则
  • python二级每日十题
  • JS逆向_腾讯点选_VMP补环境
  • (五)Reactor核心-前置知识4
  • (六)Reactive-Stream 响应式流
  • 霍尔传感器与电流互感器的区别
  • 男女搭配(数学思维)
  • 如何实现一个bind函数?
  • electron桌面应用多种快速创建方法
  • PyTorch入门指南:环境配置与张量初探
  • 3.19学习总结 题+java面向对象
  • 程序化广告行业(28/89):基于用户旅程的广告策略解析
  • 第三:go 操作mysql
  • 前端iView面试题及参考答案
  • PMP项目管理—相关方管理篇—补充内容
  • 【系统架构设计师】操作系统 - 特殊操作系统 ③ ( 微内核操作系统 | 单体内核 操作系统 | 内核态 | 用户态 | 单体内核 与 微内核 对比 )