C++中string类的用法介绍及底层实现

C++标准库中string的三种底层实现方式 引言 我们都知道, std::string的一些基本功能和用法,但它底层到底是如何实现的呢? 其实在std::string的历史中,出现过几种不同的方式。 可以从一个简单的问题来探索,一个std::string对象占据的内存空间有多大,即 sizeof(std::string)的值为多大?如果我们在不同的编译器(VC++, GNU, Clang++)上去测试,可能会发现其值并不相同;即使是GNU... 阅读详情

1.概述

首先谈谈为什么要使用和学习string?

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
string是C++标准库的一个重要组成部分,主要用于字符串处理,可以使用输入输出流的方式直接进行string操作,也可以通过文件等手段进行string操作,同时,C++的算法库堆string类也有着很好的支持,并且string类还和C语言的字符串之间有着良好的接口。


2.用法介绍
文档中一些单词的含义(希望能帮助初学者)

size_t pos:表示无符号string对象的下标

size_t npos:表示无符号-1,表示2^32

(InputIterator first, InputIterator last):迭代器:表示一个区间

2.1string的构造函数(构造和拷贝构造)
 //构造函数constructor
void test_string1()
{
string s1;//无参的构造函数
string s2("hello world");//用一个字符串构造s2
string s3("happy", 3);//使用字符串的前三个字符构造s3
string s4(4, 'x');//用4个x构造s4
string s5(s2);//拷贝构造
string s6(s2, 6, 5);//表示用s2的第6个字符开始的3个字符构造
string s7(s2.begin(), s2.end());//用迭代器构造
cout << "s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
cout << "s3:" << s3 << endl;
cout << "s4:" << s4 << endl;
cout << "s5:" << s5 << endl;
cout << "s6:" << s6 << endl;
cout << "s7:" << s7 << endl;
}

 2.2用于string对象一些容量操作

  size返回字符串的有效字符个数
  length返回字符串的有效字符长度
  capacity返回字符串的有效空间容量大小
  empty检测字符串是否为空串,true表示空,false表示非空
  clear清理有效字符
  reserve为字符串提前开辟空间
  resize将有效字符的个数改成n个,多出的空间用字符c补充
void test_string2()
{
//size,length
string s1("happy newyear");
cout << "有效字符数据个数:" << s1.size() << endl;
//s1.length()和s1.size()用法相同,功能类似
cout << "空间容量大小:" << s1.capacity() << endl;
string s2;
//empty
cout << "s1是否为空:" << s1.empty() << endl;
cout << "s2是否为空:" << s2.empty() << endl;
//clear
s1.clear();
cout <<"clear:"<< s1 << endl;
}

使用一段代码来观察reserve的功能:

//vs2022环境下
void test_string3()
{
//构建string时,如果提前已经知道string中大概需要
//放多少个元素,可以提前将string中的空间设置好
string s;
//s.reserve(100);
size_t sz = s.capacity();//计算没插入元素之前s的容量大小
cout << "makeing s grow:\n";
for (int i = 0; i < 100; i++)
{
	s.push_back('c');
	if (sz != s.capacity())
	{
		sz = s.capacity();//扩容后重置sz
		cout << "capacity changeed:" << sz << endl;
	}
}
}

没有提前开空间:

 提前开空间:

resize功能:

a. resize(size_t n) resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
b. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于
string的底层空间总大小时,reserver不会改变容量大小。
//1.resize表示有效数据的大小,会开空间
string s1("hello world");
cout <<"s1的容量:"<< s1.capacity() << endl;
s1.resize(100);//vs下编译器会根据内存对齐原则,适当开空间(>=100)
cout << "s1的容量:" << s1.capacity() << endl;
//vs下编译结果:
//s1的容量:15 
//s1的容量:111

//2.默认情况下会有效字符后面加'\0',这个字符可以自己选定
string s2("xxxxx");
s2.resize(32, 'y');
cout << s2 << endl;
//s2.resize(32);
//默认情况下:xxxxx'\0'.......
//编译结果:xxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyy

2.3 string的插入接口函数(push_back,append,operator+=,insert)

//push_back尾插一个字符
string s1("helllo world");
s1.push_back('x');
s1.push_back('y');
s1.push_back('z');
cout << "s1:" << s1 << endl;
//append尾插一个字符串
string s2("I have a lovely");
s2.append("cat");
cout << "s2:" << s2 << endl;
//operator+=尾插一个字符/字符串
string s3("hello dog");
s3 += "ll";
s3 += 'w';
cout <<"s3:" << s3 << endl;
//insert在任意一个位置插入数据
string s4("hello world");
s4.insert(5, "djhk");
cout <<"s4:"<< s4 << endl;

2.4string的遍历和打印:下标访问operator[]和迭代器iterator

void test_string5()
{
//下标访问 运算符重载
string s1("hello world");
cout << "下标访问:";
for (int i = 0; i < s1.size(); i++)
{
	cout << s1[i] ;
}
cout << endl;
//迭代器
string s2("quick and slow");
string::iterator it = s2.begin();
cout << "迭代器访问:";
while (it != s2.end())
{
	cout << *it ;
	it++;
}
cout << endl;
//范围for原理->调用迭代器
cout << "范围for: ";
for (auto e : s2)
{
	cout << e ;
}

}

2.5 string的删除接口函数:erase()

void test_string6()
{
string s1("hello world");
s1.erase(1, 3);//删除下标3到下标的字符
cout << "s1:" << s1 << endl;
string s2("i have a pig");
s2.erase(2);//从下标为2的位置一直删完
cout << "s2:" << s2 << endl;
string s3("you are right");
s3.erase(s3.begin());//删除迭代器位置的字符
cout << "s3:"<<s3 << endl;
//删除迭代器区间的字符
s3.erase(s3.begin(), s3.end() - 3);
cout << "s3:" << s3 << endl;
}

 

 2.6string的查找接口:find() rfind()(常用)

//从下标为pos位置开始找string对象str
size_t find(const string& str, size_t pos = 0) const;
//从下标为pos位置开始找字符串s
size_t find(const char* s, size_t pos = 0) const;
//从下标为pos位置开始找字符串s前n个字符串
size_t find(const char* s, size_t pos, size_t n) const;
//从下标为pos位置找字符c
size_t find(char c, size_t pos = 0) const;

//rfind倒着找
size_t rfind(const string & str, size_t pos = npos) const;
size_t rfind(const char* s, size_t pos = npos) const;
size_t rfind(const char* s, size_t pos, size_t n) const;
size_t rfind(char c, size_t pos = npos) const;
void test_string7()
{
string s1("Since ancient times, deep feeling can not stay");
string s2("time");
size_t len1 = s1.find(s2, 0);//运行结果:len1: 14
cout << "len1: " << len1 << endl;
size_t len2 = s1.find("deep", 3);//运行结果:len2: 21
cout << "len2: " << len2 << endl;
size_t len3 = s1.find('a', 0);
cout << "len3: " << len3 << endl;//运行结果:len3: 6
size_t len4 = s1.find("not stay", 4, 4);
cout << "len4: " << len4 << endl;//运行结果:len4: 38
}

find()函数和substr()函数的一个小的运用场景:

//取出协议 域名 uri
string url("https://cplusplus.com/reference/string/string/erase/");
string protocol;
size_t pos1 = url.find("://");
if (pos1 != string::npos)
{
	protocol = url.substr(0, pos1);
	cout << "协议为:" << protocol << endl;
}
else
{
	cout << "非法url" << endl;
	return;
}
string domain;
size_t pos2 = url.find('/', pos1 + 3);
if (pos2 != string::npos)
{
	domain = url.substr(pos1+3, pos2-pos1-3);
	cout << "域名为:" << domain << endl;
}
string uri;
size_t pos3 = pos2 + 1;
if (pos3 != string::npos)
{
	domain = url.substr(pos3);
	cout << "uri:" << domain << endl;
}

 2.7string分割:strtok() ,substr()

void test_string8()
{
char str[80] = "sort - fail - mail";
//字符串里可以是一个分隔符,也可以是分隔符的集合
const char s[2] = "-";  
/* 获取第一个子字符串 */
char* token = strtok(str, s);
/* 继续获取其他的子字符串 */
while (token != NULL) {
	printf("%s\\n", token);
		token = strtok(NULL, s);
}
}
//运行结果:sort \n fail \n mail\n
cout << endl;
//substr切割字符串
//string substr(size_t pos = 0, size_t len = npos) const;
string s1("hello world");
s1.substr(2, 7);
cout << s1 << endl;
string s2("pig and monkey");
s2.substr(3);
cout << s2 << endl;
//运行结果:hello world
//        pig and monkey

3.整个string类底层实现

#include <iostream>
using namespace std;
#include <assert.h>
#include <string>



namespace yht
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		//有参构造函数
		//string(const char* str)
		//	:_str(new char[strlen(str)+1])
		//	,_capacity(strlen(str))
		//	,_size(strlen(str))
		//{
		//	strcpy(_str, str);
		//}
		////无参构造
		//string()
		//	:_str(new char[1])
		//	,_capacity(0)
		//	,_size(0)
		//{
		//	_str[0] = '\0';
		//}
		//全缺省构造函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str=new char[_capacity + 1];
			strcpy(_str, str);
		}
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_capacity = _size = 0;
		}
		const char* c_str() const
		{
			return _str;
		}
		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		char& operator[](size_t pos)
		{
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			return _str[pos];
		}
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const iterator begin() const
		{
			return _str;
		}
		const iterator end() const
		{
			return _str + _size;
		}
		//拷贝构造函数
		//打工人
		/*string(const string& s)
			:_str(new char[strlen(s._str)+1])
			,_size(s._size)
			,_capacity(s._capacity)
		{
			strcpy(_str, s._str);
		}*/
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		//老板
		string(const string& s)
			:_str(nullptr)
			,_capacity(0)
			,_size(0)
		{
			string tmp(s._str);
			swap(tmp);
		}
		//赋值运算符重载
		//打工人
		//string operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		delete[] _str;
		//		_str = new char[strlen(s._str) + 1];
		//		strcpy(_str, s._str);
		//		_capacity = s._capacity;
		//		_size = _size;
		//	}
		//	return *this;
		//}
		////老板
		//string operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s._str);
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_capacity = n;
				_str = tmp;
			}
		}
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}
			strcpy(_str + _size, str);
			_size += len;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		string& insert(size_t pos, char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			int end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size++;
			return *this;
		}
		string& insert(size_t pos, const char* str)
		{
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}
			int end = _size + len;
			while (end>=pos+len)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
		}
		string& erase(size_t pos, size_t len = npos)
		{
			if (len == npos || len + pos >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}
		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}
		size_t find(char ch, size_t pos = 0) const
		{
			for (; pos <= size(); pos++)
			{
				if (_str[pos] == ch)
					return pos;
			}
			return npos;
		}
		size_t find(const char* sub, size_t pos = 0) const
		{
			char* str = strstr(_str + pos, sub);
			if (str != NULL)
			{
				return str - _str;
			}
			else
			{
				return npos;
			}
		}
		string substr(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			size_t realLen = len;
			string s;
			if (len == npos || len + pos > _size)
			{
				realLen = _size - pos;
			}
			for (size_t i = 0; i < realLen; i++)
			{
				s += _str[pos + i];
			}
			return s;
		}
		bool operator>(const string& s) const
		{
			return strcmp(_str,s._str) > 0;
		}
		bool operator==(const string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator!=(const string& s) const
		{
			return !(*this == s);
		}
		bool operator>=(const string& s) const
		{
			return (*this == s) && (*this > s);
		}
		bool operator<(const string& s) const
		{
			return !(*this >= s);
		}
		bool operator<=(const string& s) const
		{
			return !(*this > s);
		}
		void resize(size_t n, char ch = '\0')
		{
			if (n > _capacity)
			{
				reserve(n);
				for (; _size < n; _size++)
				{
					_str[_size] = ch;
				}
				_str[_size] = '\0';
			}
			else
			{
				_str[n] = '\0';
				_size = n;
			}
		}


	private:
		char* _str;
		size_t _capacity;
		size_t _size;
		const static size_t npos = -1;
	};

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			cout << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();
		char buff[32];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 31)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		buff[i] = '\0';
		s += buff;
		return in;
	}
}

C++字符串拼接效率比较(+=、append、stringstream、spintf) 事情起因很简单,自己的代码中使用了stringstream对象进行字符串的拼接,然后被老同事质疑效率低下。借着这个机会了解下为什么? 一、+=、append、stringsteam、sprintf四种字符串拼接方法比较 C/C++中字符串拼接的使用场景非常多,字符串拼接的方法也非常多,这里简单的比对下上述四种方法的效率。 测试方法:分别哟经+=、append、stringstream、sprintf的方式来拼接字符串。s1=“aaaaa”,s2=“bbbbb”,s3=“ccccc”。内层循环将这三个字 阅读详情

相关推荐

C++ string 超详讲解!string的特点、相关函数及用法与应用,逐一分析!

string a;”,这是定义了一个string型的变量,一个string型的变量有它的。char型的变量只能存储一个字符(如:a),而string型的变量可以存储。求a中有多少个不同的子串与b的子串相等(非空串)。下面,我们用a来代表string变量名,所有的操作就是针对a的。本文章纯原创,若要转载一定一定要私信我(我平时可能没时间看)。其它还有许多函数,比较少用,上面的7个函数几乎是我。,接在变量后面,引出为它量身定制的函数。string型的变量末尾会有一个。

m0_70379664的博客 1864

C++string中find()和rfind()函数

C++string中find()和rfind()函数 1、find()函数 查字符或字符串,若查找成功,则返回正向查到的第一个字符下标或第一个字符串首字符的下标;若查找失败,无法返回正确的下标。find()函数的返回值为无符号整数型。 2、 rfind()函数 逆向查字符或字符串,若查找成功,则返回逆向查到的第一个字符下标或第一个字符串首字符的下标;若查找失败,无法返回正确的下标。逆向查到的第一...

信小海 1万+

C++ STL】深入理解string底层实现

深入理解string底层实现,自己动手实现一个简易版的string

2301_80176226的博客 1662

C++ string的+与+=运算符

来说一说C++ string的运算符+ 与 运算符 += 一、string对象的运算符+和运算符+=效率差很多 string str,a; str=str+a; str+=a; 以上两句str=str+a; 与str+=a;看起来差不多,其实效率差很多。 今天在力扣上写代码,同一道题,同样的代码,差别就是我把str+=a;改成了str=str+a;运行时间翻了倍,如图: 虽说力扣上的运行时间会有波动,但也不至于差别这么大,然后我去搜了好些博客看,有些博主实际运行验证了在string的使用中,使用+=比

WYuan的博客 1万+

C++STL(string)

废话少说,不那么繁琐的介绍了!就是跟字符有关!在使用string时,必须包含#include头文件以及using namespace std;string是表示字符串的字符串的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。string底层实际是:basic_string模板的别名,typedef basic_string<char, char_traits, allocator>string;不能操作多字节或者变长字符的序列。

qq_57425280的博客 613

string的各个功能函数的底层实现

找单个字符很简单,只要遍历并返回下标即可,传参pos是让在pos位置后的子字符串中查找目标字符,字符串查找也一样,在字符串查找函数中,我们用到了c中的strstr函数,他的返回值是相同字符串的开头的位置的指针,但是我们的find函数返回值是其下标,这里就用到了指针的减法运算的意义,只需要让这个指针减去字符串的首地址就得到了其中相差的字符数,也就得到了下标。下一个,运算符+=重载,由于涉及到单个字符和字符串,我们写两个函数进行函数重载,函数的思路也很简单,运用刚才的push_back和append即可。

czt230610的博客 2119

C++string

STL(standard template library标准模版库),是c++标准库的重要组成部分,是一个包罗数据结构与算法的软件框架。STL有很多版本,我们学习STL要阅读部分源代码,主要参考SGI版本。STL的六大组件网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层 的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

2302_81442125的博客 2002

C++String

本篇博客主要梳理了C++标准库中string用法和常用接口,搭配对string重要功能的模拟实现,旨在更好地帮助读者理解string的上层用法底层实现

NeeEk0的博客 256

C++string的使用及其模拟实现

STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

悟已往之不谏 知来者之可追 1312

C++string介绍及常用函数用法总结

一.strng介绍   二.string常用函数及用法    1.string对象常见的构造    2.string对象的容量操作    3. string对象的访问及遍历操作    4.string对象的修改操作

2301_77509762的博客 2962

C++string,模拟实现string

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一 不想分享就你争我夺,玩具损坏。可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父 母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

qq_67578580的博客 3684

string介绍与使用【C++

string是编程语言中用于表示和操作字符串的基本数据型或。它提供了一系列方法和操作,允许开发者对字符串进行创建、修改、查找、比较、转换等。string通常具有不可变性,意味着一旦创建了字符串对象,其内容就不能被修改,但可以创建新的字符串对象来表示修改后的内容。这种特性有助于在多线程环境中保持数据的安全性。string还提供了各种构造函数、操作符重载和格式化功能,使字符串操作更加灵活和高效。

qq_74013365的博客 3495

C++学习之旅【C++String介绍

摘要: 本文探讨C++string的优势与必要性,对比C语言字符数组的缺陷。C语言字符串需手动管理内存和\0,操作繁琐且易出错;而string自动处理内存、动态扩容,支持运算符重载和丰富方法,杜绝缓冲区溢出,兼容STL容器。文章还介绍C++11的auto关键字和范围for循环,简化代码编写。通过示例展示auto的用法及限制,强调string在安全性、易用性和扩展性上的显著优势,是C++字符串处理的理想选择。

承渊政道的博客 1377

C++String的语法及常用接口的底层实现详解

C++中,string 是一个标准库(class),用于处理字符串。它提供了一种更高级、更便捷的字符串操作方式,string 提供了一系列成员函数和重载运算符,以便于对字符串进行操作和处理。本篇文章列举出了string中常用的语法和接口底层底层实现,这些都是我们应该熟练掌握的内容

weixin_67596609的博客 1167

C++初阶】学习string的模拟实现

前面已经学习了string用法,这篇文章将更深入的学习string,了解string底层是怎么实现的。当然,这里只是模拟一些常用的,不常用的可以看文档学习。

2301_77459845的博客 2748

C++string用法详细介绍&&string模拟实现解析】

C++string用法详细介绍&&string模拟实现解析】

m0_59292239的博客 1470

C++ String(带你一篇文章搞定C++中的string

我们前面对C++的泛型编程做了了解,然后我们继续来说,C++标准库里stringstringC++标准库里的一个,为什么学习string,因为C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。其实在标准库还有很多这里内容,下次在一一讲解。

2301_80344616的博客 1341

C++】——string介绍及模拟实现

本文主要介绍C++string的概念及常用接口,最后简单模拟实现string

_Fiora的博客 1651
上一篇: C++模板介绍(简洁易懂)
下一篇: 图解创建堆和堆排序(超详细)
疯狂的小码农
博客等级 码龄6年 30粉丝 · 29原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

疯狂的小码农

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

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

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

打赏作者

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

抵扣说明:

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

余额充值