首页 > 编程语言 >C++:STL:String类常用函数介绍(附加部分重要函数的模拟实现)

C++:STL:String类常用函数介绍(附加部分重要函数的模拟实现)

时间:2024-09-24 14:22:00浏览次数:3  
标签:capacity 函数 STL char String str const string size

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列
的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问。如果我们经常在leetcode上刷OJ题,我们会发现字符串类型的题目基本都是以string的形式给初始字符串。

cplusplus.com/reference/string/string/?kw=string

上面的网址可以帮助我们了解string类,同时需要记得在使用string类的时候需要包含头文件以及展开命名空间,下面我们来介绍些常用的string类函数。蓝字部分点击即可直接跳转至介绍该函数的文档,我们这里只做较为简单的介绍。

一,std::string::string(初始化函数)

string::string - C++ Reference (cplusplus.com)

​ 

(以上是从文档中截取的图片,基本上是我们定义对象时常用的)单看这里我们便可以大致推出:

1.不传任何字符给字符串,定义一个空对象。

2.用一个现有对象通过拷贝构造初始化我们要定义的对象。 

3.取现有对象中的一部分用来初始化我们即将定义的对象。

4.使用一个字符串来初始化我们的对象。 

5.取字符串的前N个字符来初始化我们的对象。

6.以N个字符C来初始化我们即将定义的对象。

 我们平时使用1,2,4居多,以下是我们对其的模拟实现:

	string::string()
		:_str(new char[15] {'\0'}),
		_size(0),
		_capacity(14)
	{}

	string::string(const char* s)
		:_size(strlen(s)),
		_capacity(strlen(s))
	{
		_str = new char[_capacity + 1];
		strcpy(_str, s);
	}

	string::string(const string& s)
		:_size(s._size),
		_capacity(s._capacity)
	{
		_str = new char[_capacity + 1];
		strcpy(_str, s._str);
    }
	class string
	{
	public:
		string();
		string(const char* s);
		string(const string& s);
		string& operator=(const string& s);
		string& operator=(const char* s);
		~string();
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static const size_t npos = -1;
	};

由于库中已经有string类,这里我们是另外开了一块命名空间去定义我们的构造和拷贝构造,当然strcpy函数不安全会报警告,不过我们这里只是简单实现,目的是为了大致了解它们的底层结构。需要注意的是,由于我们的字符串实际上结尾还有个\0字符,所以我们需要多开一个空间来储存。 

当然,成对才是最舒服的,这里我们也顺便的模拟实现下赋值运算符重载,以及析构函数:

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

string& string::operator=(const char* s)
{
	while (strlen(s) > _capacity)
	{
		char* tmp = new char[(_capacity + 1) * 2] {'\0'};
		delete[] _str;
		_str = tmp;
		_capacity = (_capacity + 1) * 2 - 1;
	}
	strcpy(_str, s);
	_size = strlen(s);
	return *this;
}

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

再次提醒new与delete必须要匹配使用,同时为了避免或不小心造成的自己给自己初始化的无趣行为, 我们需要判断传入的对象是否与this指针解引用一样在上图第一个实现的重载中。

二,std::string::(r)begin与std::string::(r)end(范围函数)

虽然我们简单的去模拟实现这两者不难,但在此之前,我们需要去了解什么是迭代器:

iterator - C++ Reference (cplusplus.com)

C++标准库中的迭代器是一种用于访问容器(如数组、链表、集合等)元素的对象,类似于指针。它们提供了一种统一的方法来遍历不同类型的容器,而无需关心容器的具体实现。 

而我们这里所介绍的这两个函数也是基于迭代器的,先来简要的介绍下string中的与迭代器相关的一些函数用法:

  • begin():返回指向字符串第一个字符的迭代器。
  • end():返回指向字符串末尾后一个位置的迭代器(不指向任何有效字符)。
  • rbegin():返回指向字符串最后一个字符的反向迭代器。
  • rend():返回指向字符串首个字符前一个位置的反向迭代器。
  • cbegin():返回常量迭代器,指向第一个字符。
  • cend():返回常量迭代器,指向末尾后一个位置。
  • crbegin():返回常量反向迭代器,指向最后一个字符。
  • crend():返回常量反向迭代器,指向首个字符前一个位置 。 

这里我们无法同时模拟这么多,因为一迭代器遍历数据的方式是通用与我们别的一些STL的,所以需要结合后面的STL的使用来学习,二是毕竟我们的迭代器类似与指针,我们要简单模拟实现只需要实现最开始两个,后面便可迎刃而解:
 

using iterator = char*;
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}
const iterator begin() const
{
	return _str;
}
const iterator end() const
{
	return _str + _size;
}

这里我们模拟实现迭代器iterator之后,便可以使用范围for来遍历我们的字符串(只要我们定义了迭代器,并有begin与end函数便可以在我们模拟实现的string中使用范围for,下面是范围for的使用实例(注意范围for是在C++11中才使用的):

当我们使用范围for循环遍历std::string时,element_declaration可以是一个字符类型,如char或者auto

当我们使用范围for循环遍历std::string时,我们的元素可以是一个字符类型,如char或者auto(auto不能作为函数的参数,可以做返回值,但是建议谨慎使用,auto不能直接用来声明数组)。

#include <iostream>
#include <string>

int main() {
    std::string str = "Hello, World!";

    // 使用范围 for 遍历字符串
    for (char c : str) {
        std::cout << c << " ";  // 输出每个字符
    }
    std::cout << std::endl;

    return 0;
}
/
#include <iostream>
#include <string>

int main() {
    std::string str = "Hello, C++";

    // 使用 auto 自动推导类型
    for (auto c : str) {
        std::cout << c << " ";
    }
    std::cout << std::endl;

    return 0;
}

#include <iostream>
#include <string>

int main() {
    std::string str = "Range for loop!";

    // 使用 const 引用遍历字符串,保证字符串不会被修改
    for (const char& c : str) {
        std::cout << c << " ";
    }
    std::cout << std::endl;

    return 0;
}

补充一个[]操作符的重载的模拟实现,虽然简单但它可以让我们像访问字串中的数据一样去访问我们string类对象。

char& operator[](size_t n)
{
	assert(n >= 0 && n < _size - 1);
	return _str[n];
}
const char& operator[](size_t n)const
{
	assert(n >= 0 && n < _size);
	return _str[n];
}

三,std::string::size/length/capacity/empty/clear/reserve/resize(容量操作函数)

以下是它们的英文介绍文档(按照分割符顺序):

cplusplus.com/reference/string/string/size/重要

cplusplus.com/reference/string/string/length/ 

 cplusplus.com/reference/string/string/capacity/

cplusplus.com/reference/string/string/empty/ 重要

cplusplus.com/reference/string/string/clear/ 重要

cplusplus.com/reference/string/string/reserve/ 重要

cplusplus.com/reference/string/string/resize/ 重要

注意:

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接
口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不
同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char
c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数
增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参
数小于string的底层空间总大小时,reserver不会改变容量大小。 

以下是这些函数的模拟实现(只模拟实现重要的函数):

size_t size()const
{
	return _size;
}
size_t capacity()const
{
	return _capacity;
}
bool empty()const
{
	assert(_str);
	return (_size > 0);
}
void string::resize(size_t n, char c)
{
	if (n > _capacity)
	{
		reserve(n);
		for (size_t i = _size; i < _capacity; i++)
		{
			_str[i] = c;
		}
		_str[_capacity] = '\0';
		_size = _capacity;
	}
	else if (n >= _size && n <= _capacity)
	{
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = c;
			_size++;
		}
	}
	else if (n < _size)
	{
		_str[n] = '\0';
		_size = n;
	}
}

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

四,std::string::push_back/append/operator+=/c_str/find + npos/rfind/substr(对象修改函数) 

 cplusplus.com/reference/string/string/push_back/

cplusplus.com/reference/string/string/append/ 

cplusplus.com/reference/string/string/operator+=/ 

cplusplus.com/reference/string/string/c_str/ 

cplusplus.com/reference/string/string/find/ 

cplusplus.com/reference/string/string/npos/ 

cplusplus.com/reference/string/string/rfind/ 

cplusplus.com/reference/string/string/substr/ 

 

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差
不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可
以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留
好。 (表格中的+应为+=,创表格的时候少输入了)。

 以下是模拟实现代码:

	void string::push_back(char c)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : (_capacity+1) * 2 - 1);
		}

		_str[_size] = c;
		_size++;
		_str[_size] = '\0';
	}

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		while(len + _size > _capacity)
		{
			char* tmp = new char[(_capacity + 1) * 2] {'\0'};
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = (_capacity + 1) * 2 - 1;
		}
		strcpy(_str + _size, str);
		_size += len;
	}
	size_t string::find(char c, size_t pos) const
	{
		assert(pos < _size);
		for (size_t i = pos; _str[i]; i++)
		{
			if (_str[i] == c)
				return i;
		}
		return npos;
	}

	size_t string::find(const char* s, size_t pos) const
	{
		assert(pos < _size);
		const char* ptr = strstr(_str + pos, s);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
    }
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		void clear()
		{
			memset(_str, '\0', _size + 1);
			_size = 0;
		}
		const char* c_str()const
		{
			return _str;
		}

下面是几道练习题:

917. 仅仅反转字母 - 力扣(LeetCode) 

387. 字符串中的第一个唯一字符 - 力扣(LeetCode) 

字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com) 

125. 验证回文串 - 力扣(LeetCode) 

415. 字符串相加 - 力扣(LeetCode)

541. 反转字符串 II - 力扣(LeetCode) 

557. 反转字符串中的单词 III - 力扣(LeetCode) 

43. 字符串相乘 - 力扣(LeetCode) 

找出字符串中第一个只出现一次的字符_牛客题霸_牛客网 (nowcoder.com) 

 

标签:capacity,函数,STL,char,String,str,const,string,size
From: https://blog.csdn.net/xiuwoaiailixiya/article/details/142479731

相关文章

  • Python中,你可以使用`scipy.stats`库中的`entropy`函数来计算两个连续变量之间的KL散度
    在Python中,你可以使用`scipy.stats`库中的`entropy`函数来计算两个连续变量之间的KL散度。这个函数计算的是两个概率分布之间的熵,即KL散度。以下是一个使用`scipy`计算KL散度的示例:首先,你需要安装`scipy`库(如果还未安装的话):```bashpipinstallscipy```然后,你可以使用以下代码......
  • Anaconda创建虚拟环境失败Malformed version string ‘~‘: invalid character(s)
     notepad%USERPROFILE%\.condarccondacreate-npy38python=3.8TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChineseSimplifiedHungarianRussianChineseTraditionalIndonesian......
  • MySQL零基础入门教程-5 单行处理函数、分组函数、mysql关键字执行顺序,基础+实战
    教程来源:B站视频BV1Vy4y1z7EX001-数据库概述_哔哩哔哩_bilibili我听课整理的课程的完整笔记,供大家学习交流下载:夸克网盘分享本文内容为完整笔记的第五篇17、单行数据处理函数P30-36&分组函数17.1、数据处理函数又被称为单行处理函数单行处理函数的特点:一个输入对应一个输出。和单行......
  • Python函数艺术:掌握编程中的“乐高积木”
    引言函数是程序设计的基本单元之一,它使得代码模块化,提高了重用性和可读性。无论是处理数据、操作文件还是实现特定业务逻辑,掌握好函数的设计与使用都是至关重要的技能。在Python中,定义一个函数非常直观且强大,这使得即使是初学者也能快速上手,并随着经验积累不断发掘其深层价......
  • 日期函数(sql)
    SQL标量函数----->日期函数day()、month()、year()、2009年02月23日星期一11:30SQL标量函数----->日期函数day()、month()、year()、DATEADD()、ATEDIFF()、DATENAME()、DATEPART()GETDATE()执行实例(表:life_unite_product有createtime时间字段)selectday(crea......
  • go基础-7.函数和指针
    函数是一段封装了特定功能的可重用代码块,用于执行特定的任务或计算函数接受输入(参数)并产生输出(返回值)函数定义packagemainimport"fmt"//使用func关键字定义一个函数funcsayHello(){fmt.Println("hello")}funcmain(){//函数()调用函数sayHello()}......
  • go基础-8.init函数和defer函数
    init函数init()函数是一个特殊的函数,存在以下特性:不能被其他函数调用,而是在main函数执行之前,自动被调用init函数不能作为参数传入不能有传入参数和返回值一个go文件可以有多个init函数,谁在前面谁就先执行packagemainimport"fmt"funcinit(){fmt.Println("init1"......
  • Lambda函数
    C++中的Lambda函数在C++11及以后版本中,Lambda函数(或称为匿名函数)是一种内联函数,可以在函数内部或任何其他地方定义,甚至可以立即传递给其他函数。Lambda函数的语法更加灵活,允许捕获变量和处理复杂的行为,非常适合在需要简单函数的场景中使用。语法[capture](parameters)->r......
  • C++学习笔记(三)-----【内联函数】
    1内联函数1.1为什么要有内联函数答:还是为了补C语言的坑,补宏的坑#defineN10//实现一个ADD的宏函数//错误写法#defineADD(intx,inty){returnx+y;}#defineADD(x,y){returnx+y;}#defineADD(x,y)returnx+y;#defineADD(x,y)x+y;//宏不需......
  • 凸函数的等价定义及其证明
    Preface    我非常记得罗翔老师说过一句话,"我们登上并非我们所选择的舞台,演绎并非我们所选择的剧本,但是没有谁的剧本值得羡慕,我们唯一能做的就是尽力演好自己的角色,打好自己手中的牌"。我们所作的每一个选择都可看做是一个优化问题中的一次迭代,在一次一次迭代过程中趋向我们......