C++ string类

Python爬虫实战:5分钟搞定Web of Science文献批量导出与解析(附完整代码) 本文提供了一套利用Python爬虫技术,快速实现Web of Science文献批量提取与解析的完整方案。通过解析官方导出的HTML文件,结合BeautifulSoup和pandas库,可将文献数据自动转换为结构化的Excel/CSV表格,帮助科研人员高效管理文献信息,节省大量手动整理时间。 阅读详情

目录

一.STL简介

1. 什么是STL

2.STL的版本(了解即可)

​编辑

二.String类 

1.认识string类

2.string类的常用接口说明

2.1构造函数

2.2 string类对象的容量操作

2.3string类对象的访问及遍历操作

 2.5. string类非成员函数

2.6 vs和g++下string结构的说明

2.6.1 vs下string的结构

2.6.2 g++下string的结构

3.C++11的小语法(auto关键字和范围for)

3.1auto关键字(做类型推导)

3.2范围for(遍历方便)

4.string类的实现

string.h

string.cpp

test.cpp

5.浅拷贝和深拷贝问题

5.1浅拷贝

5.1深拷贝

6.string 在线OJ题目

一.STL简介

1. 什么是STL

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

2.STL的版本(了解即可)

1.HP STL (原始版本 - 1994年):

  • 地位: 由Alexander Stepanov和Meng Lee在惠普实验室开发,是STL的原始实现和概念起源。

  • 特点: 开源、模板化、包含核心组件(容器、算法、迭代器、函数对象)。奠定了STL的设计理念和基本架构。

  • 现状: 是历史起点,但已不再是主流使用版本。

2.SGI STL (标准化前的重要实现 - 1990年代末):

  • 地位: 由Silicon Graphics公司基于HP STL进行大幅改进和扩展的实现。

  • 特点:

    • 性能优化: 做了大量性能优化,效率很高。

    • 扩展组件: 引入了重要的非标准但广泛使用的组件,如 hash_map, hash_set (后来成为标准的 unordered_map, unordered_set)、slist (单链表)、rope (长字符串处理) 以及强大的内存分配器(如 alloc)。

    • 文档优秀: 提供了非常清晰、深入的在线文档,对理解STL内部机制和设计思想影响深远。

  • 影响: 成为许多早期编译器(如GCC早期版本)STL实现的基础,其设计和扩展组件对C++标准库的发展产生了巨大影响。其文档至今仍是经典学习资料。

  • 现状: 本身作为一个独立实现已较少直接使用,但其思想和组件被广泛吸收。 

3.STLport (跨平台移植版本 - 1990年代末至2000年代初):

  • 地位: 一个旨在提供高性能、跨平台且符合标准的STL实现的开源项目。

  • 目标: 解决早期不同编译器(尤其是Windows上的VC++)自带STL实现质量、性能、可移植性和标准符合性不一致的问题。

  • 特点: 基于SGI STL,注重可移植性、调试模式支持、标准符合性(努力跟进C++98/03标准)。

  • 现状: 在早期VC++(如VC6)时代非常流行,作为替换编译器自带STL的方案。随着主流编译器(GCC, Clang, MSVC)自身STL实现质量的飞速提升和标准化,STLport的重要性已大大降低,项目基本停止活跃。

4.标准库STL实现 (主流编译器自带):

  • 地位: 目前绝对主流的STL使用方式。C++标准(C++98/03, C++11, C++14, C++17, C++20, C++23)定义了STL(严格来说是标准库中的容器、算法、迭代器、函数对象等部分)的接口和行为规范。

  • 实现者: 各大编译器厂商(或开源社区)负责根据标准规范实现自己的版本:

    • GNU (GCC): libstdc++ (通常称为GNU C++ Library)

    • LLVM/Clang: libc++

    • Microsoft (MSVC): MSVC STL (历史上曾称Dinkumware STL, 现在官方称MSVC STL)

    • 其他: Embarcadero C++ Builder, Intel C++ Compiler等也有其实现或适配。

  • 特点:

    • 标准符合性: 严格遵循(或努力追赶)最新的C++标准规范。

    • 与编译器深度集成: 紧密配合编译器的特性(如ABI、优化、错误信息)。

    • 持续演进: 不断加入对新标准特性(C++11/14/17/20/23)的支持(如移动语义、智能指针、新容器 unordered_map/set, array, forward_list, 并行算法、范围操作等)。

    • 性能优化: 各实现都在性能上进行激烈竞争和持续优化。

    • 平台适配性: 针对特定操作系统和硬件架构进行优化。

  • 现状: 是当今C++开发者直接使用的STL实现。选择哪个版本通常取决于你选择的编译器。

总结:

  • HP STL: 始祖,奠定基础。

  • SGI STL: 承前启后,性能卓越,扩展丰富,影响深远(特别是文档)。

  • STLport: 特定历史时期(跨平台/标准符合性需求强烈,编译器实现不佳时)的流行移植方案。

  • 编译器自带标准库STL (libstdc++, libc++, MSVC STL): 绝对主流,严格遵循标准,深度集成编译器,持续演进更新。开发者日常接触和使用的就是这些实现。

主要包括这些内容: 

二.string类 

        C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列 的库函数,但是这些库函数与字符串是分离开的,底层空间需要用户自己管理,稍不留神可能还会越界访问。在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。 

1.认识string类

有关string类的介绍

注意:在使用string类时,必须包含#include头文件以及using namespace std;

2.string类的常用接口说明

2.1构造函数

文档链接:cplusplus.com/reference/string/string/string/

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1;
	string s2("hello world");
	string s3(s2);

	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;

	//cin >> s1;
	//cout << s1 << endl;

	string s4(s2, 6, 15);
	cout << s4 << endl;

	string s5(s2, 6);
	cout << s5 << endl;

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

	string s7(10, 'X');
	cout << s7 << endl;

	s6[0] = 'x';
	cout << s6 << endl;

	return 0;
}

2.2 string类对象的容量操作

文档链接:string - C++ Reference

注意:
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不会改变容量大小。

2.3string类对象的访问及遍历操作

迭代器(类似于指针):

正向迭代器:

反向迭代器:

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


void test_string1()
{
	string s1;
	string s2("hello world");

	cout << s1 << s2 << endl;

	s2[0] = 'x';
	cout << s1 << s2 << endl;

	// 1、下标 + []
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}
	cout << endl;

	// 2、迭代器
	//string::iterator it = s2.begin();
	auto it = s2.begin();
	while (it != s2.end())
	{
		*it += 2;

		cout << *it << " ";
		++it;
	}
	cout << endl;

	cout << s2 << endl;

	map<string, string> dict;
	//map<string, string>::iterator mit = dict.begin();
	auto mit = dict.begin();

	/*list<int> lt = { 1,2,3,4,5,6,7 };
	list<int>::iterator lit = lt.begin();
	while (lit != lt.end())
	{
		cout << *lit << " ";
		++lit;
	}
	cout << endl;*/

	// 3、字符赋值,自动迭代,自动判断结束
	// 底层就是迭代器
	//for (auto ch : s2)
	for (auto& ch : s2)
	{
		ch -= 2;
		cout << ch << " ";
	}
	cout << endl;

	cout << s2 << endl;
}

void test_string2()
{
	string s2("hello world");
	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		*it += 2;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	string::reverse_iterator rit = s2.rbegin();
	while (rit != s2.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	const string s3("hello world");
	//string::const_iterator cit = s3.begin();
	auto cit = s3.begin();
	while (cit != s3.end())
	{
		//*cit += 2;
		cout << *cit << " ";
		++cit;
	}
	cout << endl;

	//string::const_reverse_iterator rcit = s3.rbegin();
	auto rcit = s3.rbegin();
	while (rcit != s3.rend())
	{
		// *rcit += 2;
		cout << *rcit << " ";
		++rcit;
	}
	cout << endl;
}

void TestPushBack()
{
	// reverse 反转  逆置
	// reserve 保留、预留
	string s;
	// 提前开空间,避免扩容,提高效率
	s.reserve(100);
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

void test_string3()
{
	string s2("hello world");
	cout << s2.length() << endl;
	cout << s2.size() << endl;

	cout << s2.max_size() << endl;

	cout << s2.capacity() << endl;
	TestPushBack();

	string s3("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
	//cout << sizeof(s2) << endl;
	//cout << sizeof(s3) << endl;
}
//reserve capacity 测试 不同的编译器可能不一样
void test_string4()
{
	string s2("hello worldxxxxxxxxxxxxx");
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	s2.reserve(20);
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	s2.reserve(28);
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	s2.reserve(40);
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	s2.clear();
	cout << s2.size() << endl;
	cout << s2.capacity() << endl << endl;

	cout << typeid(string::iterator).name() << endl;
	cout << typeid(string::reverse_iterator).name() << endl;

}

int main()
{
	test_string4();
	return 0;
}

 2.4string类对象的修改操作 

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

 2.5. string类非成员函数

 以上的接口介绍的没介绍的都可以自行查阅文档学习,不一一列举。

C library - C++ Reference

2.6 vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
2.6.1 vs下string的结构
string总共占 28个字节 ,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放。
当字符串长度大于等于16时,从堆上开辟空间。
class string
{
private:
	char _buff[16];
	char*  _str;

	size_t _size;
	size_t _capacity;
};
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建
好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t 字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的
容量。
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。

2.6.2 g++下string的结构
G++下,string是通过写时拷贝实现的,string对象总共占 4个字节 ,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段: 空间总大小   字符串有效长度 引用计数,指向堆空间的指针,用来存储字符串。
struct _Rep_base
{
_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};

关于写时拷贝和引用计数,有兴趣的可以阅读一下这篇大佬写的文章:

C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell

写时拷贝 就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数 :用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该
资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有
其他对象在使用该资源。

3.C++11的小语法(auto关键字和范围for)

3.1auto关键字(做类型推导)

1.在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个
不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型
指示符,而是作为一个新的类型指示符来指示编译器, auto声明的变量必须由编译器在编译时期
推导而得 。
2.用auto声明指针类型时,用auto和auto*没有任何区别,但用 auto声明引用类型时则必须加& 。
3.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
4. auto不能作为函数的参数,可以做返回值,但是建议谨慎使用 。
5. auto不能直接用来声明数组 。
#include<iostream>
using namespace std;
int func1()
{
	return 10;
}
// 不能做参数
void func2(auto a)
{}

// 可以做返回值,但是建议谨慎使用
auto func3()
{
	return 3;
}
int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = func1();
	// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
	auto e;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	int x = 10;
	auto y = &x;
	auto* z = &x;
	auto& m = x;
	cout << typeid(x).name() << endl;
	cout << typeid(y).name() << endl;
	cout << typeid(z).name() << endl;
	auto aa = 1, bb = 2;
	// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
	auto cc = 3, dd = 4.0;
	// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
	auto array[] = { 4, 5, 6 };
	return 0;
}
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
	"橙子" }, {"pear","梨"} };
	// auto的用武之地 简单方便快捷
	//std::map<std::string, std::string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	return 0;
}

3.2范围for(遍历方便)

1.对于一个 有范围的集合 而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的 for 循环。 for 循环后的括号由冒号 “ : ” 分为两部分:第一部分是范围
内用于迭代的变量,第二部分则表示被迭代的范围 ,自动迭代,自动取数据,自动判断结束。
2. 范围for可以作用到数组和容器对象上进行遍历 。
3.范围 for 的底层很简单,容器遍历实际就是替换为 迭代器 ,这个从汇编层也可以看到。
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	// C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2;
	}
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		cout << array[i] << endl;
	}
	// C++11的遍历
	for (auto& e : array)
		e *= 2;
	for (auto e : array)
		cout << e << " " << endl;
	string str("hello world");
	for (auto ch : str)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

4.string类的实现

string.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once

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

namespace sy
{
	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()
			:_str(new char[1]{'\0'})
			,_size(0)
			,_capacity(0)
		{}*/

		// 短小频繁调用的函数,可以直接定义到类里面,默认是inline
		string(const char* str = "")
		{
			_size = strlen(str);
			// _capacity不包含\0
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// 深拷贝问题
		// s2(s1)
		/*string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}*/

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

		// s2(s1)
		// 现代写法
		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
		}

		// s2 = s1
		// s1 = s1
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				delete[] _str;

				_str = new char[s._capacity + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}*/

		// s1 = s3;
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		//string tmp(s._str);
		//		string tmp(s);

		//		swap(tmp);
		//	}

		//	return *this;
		//}

		// s1 = s3;
		string& operator=(string tmp)
		{
			swap(tmp);

			return *this;
		}

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

		const char* c_str() const
		{
			return _str;
		}

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);

			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);

			return _str[pos];
		}

		/*void copy_on_write()
		{
			if (count > 1)
			{
				深拷贝
			}
		}*/

		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);

		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);
	private:
		//char _buff[16];
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		//static const size_t npos = -1;
		static const size_t npos;

		/*static const int N = 10;
		int buff[N];*/
	};

	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
}
string.cpp
#include"string.h"

namespace sy
{
	//静态成员变量⼀定要在类外进⾏初始化
	const size_t string::npos = -1;

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

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

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

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

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			// 大于2倍,需要多少开多少,小于2倍按2倍扩
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}

		strcpy(_str + _size, str);
		_size += len;
	}

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

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		// 挪动数据
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}

		_str[pos] = ch;
		++_size;
	}

	void string::insert(size_t pos, const char* s)
	{
		assert(pos <= _size);

		size_t len = strlen(s);
		if (_size + len > _capacity)
		{
			// 大于2倍,需要多少开多少,小于2倍按2倍扩
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}

		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = s[i];
		}

		_size += len;
	}

	void string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);

		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			for (size_t i = pos + len; i <= _size; i++)
			{
				_str[i - len] = _str[i];
			}

			_size -= len;
		}
	}

	size_t string::find(char ch, size_t pos)
	{
		assert(pos < _size);

		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}

		//找不到返回-1
		return npos;
	}

	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);

		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}

	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);

		// len大于剩余字符长度,更新一下len
		if (len > _size - pos)
		{
			len = _size - pos;
		}

		string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}

		return sub;
	}

	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}

	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}

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

		const int N = 256;
		char buff[N];
		int i = 0;

		char ch;
		//in >> ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;

				i = 0;
			}

			//in >> ch;
			ch = in.get();
		}

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

		return in;
	}
}
test.cpp
#include"string.h"

namespace sy
{
	void test_string1()
	{
		string s1;
		string s2("hello world");
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i] += 2;
		}

		cout << s2.c_str() << endl;

		for (auto e : s2)
		{
			cout << e << " ";
		}
		cout << endl;

		string::iterator it = s2.begin();
		while (it != s2.end())
		{
			//*it += 2;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_string2()
	{
		string s1("hello world");
		s1 += 'x';
		s1 += '#';
		cout << s1.c_str() << endl;

		s1 += "hello";
		cout << s1.c_str() << endl;

		s1.insert(5, '$');
		cout << s1.c_str() << endl;

		s1.insert(0, '$');
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;

		s2.insert(5, "$$$");
		cout << s2.c_str() << endl;

		s2.insert(0, "$$$&&&&&&&&&&&&&&&&&&&&&&&&");
		cout << s2.c_str() << endl;
	}

	void test_string3()
	{
		string s1("hello world");
		s1.erase(6, 100);
		cout << s1.c_str() << endl;

		string s2("hello world");
		s2.erase(6);
		cout << s2.c_str() << endl;

		string s3("hello world");
		s3.erase(6, 3);
		cout << s3.c_str() << endl;
	}

	void test_string4()
	{
		string s("test.cpp.zip");
		size_t pos = s.find('.');
		string suffix = s.substr(pos);
		cout << suffix.c_str() << endl;

		string copy(s);
		cout << copy.c_str() << endl;

		s = suffix;
		cout << suffix.c_str() << endl;
		cout << s.c_str() << endl;

		s = s;
		cout << s.c_str() << endl;
	}

	void test_string5()
	{
		string s1("hello world");
		string s2("hello world");

		cout << (s1 < s2) << endl;
		cout << (s1 == s2) << endl;
		cout << ("hello world" < s2) << endl;
		cout << (s1 == "hello world") << endl;
		cout << ("hello world" == "hello world") << endl;

		cout << s1 << s2 << endl;

		string s0;
		cin >> s0;
		cout << s0 << endl;
	}

	void test_string6()
	{
		string s1("hello world");
		string s2 = s1;

		cout << s1 << endl;
		cout << s2 << endl;

		string s3("xxxxxxxxxxxxxx");
		s1 = s3;

		cout << s1 << endl;
		cout << s3 << endl;
	}

	void test_string7()
	{
		string s1("hello world");
		string s2("xxxxxxxxxxxxxxxxxxxxxxxx");

		std::swap(s1, s2);
		s1.swap(s2);
	}
}
int main()
{
	sy::test_string1();
	sy::test_string2();
	sy::test_string3();
	sy::test_string4();
	sy::test_string5();
	sy::test_string6();
	sy::test_string7();
	return 0;
}

5.浅拷贝和深拷贝问题

5.1浅拷贝

        浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
说明:上述String类(自己实现的) 没有 显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为 浅拷贝。 可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。

5.1深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

6.string 在线OJ题目

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

class Solution {
public:
    string addStrings(string num1, string num2) 
    {
        string str;
        int end1=num1.size()-1,end2=num2.size()-1;
        int next=0;
        while(end1>=0 || end2>=0)
        {
            int val1 = end1 >=0 ? num1[end1--]-'0' : 0;
            int val2 = end2 >=0 ? num2[end2--]-'0' : 0;

            int ret=val1+val2+next;
            next=ret / 10;
            ret=ret % 10;

            str +=('0'+ret);
        }

        if(next==1)

        str+='1';
        reverse(str.begin(),str.end());
        return str;
    }
};

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

class Solution {
public:
        bool isLetter(char ch)
        {
        if(ch >= 'a' && ch <= 'z')
            return true;
        if(ch >= 'A' && ch <= 'Z')
            return true;
        return false;
        }
        string reverseOnlyLetters(string S) 
        {
            if(S.empty())
            return S;
            size_t begin = 0, end = S.size()-1;
            while(begin < end)
            {
                while(begin < end && !isLetter(S[begin]))
                    ++begin;
                while(begin < end && !isLetter(S[end]))
                    --end;
                swap(S[begin], S[end]);
                ++begin;
                --end;
            }
            return S;
        }
};
class Solution {

public:

//1. 分别对字符串进行正序查询和反序查询

// 2. 如果所查询的字符下标相等,说明此字符只出现了一次
  int firstUniqChar(string s) 
  {
    for(int i=0; i<s.size(); ++i)
    {
      int index = s.find(s[i]);
      int reverse_index = s.rfind(s[i]);  
      if(index == reverse_index)
        return i;
    }
    return -1;
  }

};
class Solution {
public:
    int firstUniqChar(string s) 
    {
        int a[256]={0};
        for(int i=0;i<s.size();i++)
        {
            a[s[i]]+=1;
        } 
        for(int i=0;i<s.size();i++)
        {
            if(1==a[s[i]])
            {
                return i;
            }
        }
        return -1;
    }
};

字符串最后一个单词的长度_牛客题霸_牛客网

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;
int main()
{
    string str;

    getline(cin, str);
    reverse(str.begin(), str.end());
    string::iterator it = str.begin();
    int count = 0;
    while (it != str.end() && * it != ' ')
    {
        count++;
        ++it;
    }
    cout << count << endl;


    return 0;
}
#include<iostream>
#include<string>
using namespace std;
int main()
{
    string line;
    // 不要使用cin>>line,因为会它遇到空格就结束了
    // while(cin>>line)
    while(getline(cin, line))
    {
        size_t pos = line.rfind(' ');
        cout<<line.size()-pos-1<<endl;
    }
    return 0;
}
class Solution {
public:
    bool isletter(char ch)
    {
        if((ch >= 'a' && ch <= 'z') || 
        (ch >='A' && ch <='Z') ||
         (ch >='0' && ch <= '9'))
        {
            return true;
        }
        return false;
    }
    bool isPalindrome(string s) 
    {
        //全部转成小写字母
        for(auto& ch :s)
        {
            if(ch >='A' && ch <='Z')
            {
                ch+=32;
            }
        }

        int begin=0;
        int end=s.size()-1;
        while(begin < end)
        {
            while(begin < end && !isletter(s[begin]))
            {
                ++begin;
            }
            while(begin < end && !isletter(s[end]))
            {
                --end;
            }

            if(s[begin]!=s[end])
            {
                return false;
            }
            else
            {
                ++begin;
                --end;
            }

        }
        return true;
    }
};

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

class Solution {
public:
    void Reverse(string& s, int start, int end) 
    {
        char tmp;
        end--;
        while (start < end)
        {
            tmp = s[start];
            s[start] = s[end];
            s[end] = tmp;
            start++;
            end--;
        }
    }
    string reverseStr(string s, int k)
    {
        int len = s.size();
        for (int i = 0; i < len; i += 2 * k)
        {
            if (i + k < len)
                Reverse(s, i, i + k);
            else
                Reverse(s, i, len);
        }
        return s;
    }
};

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

class Solution {
public:
    string reverseWords(string s) 
    {
        string::iterator it = s.begin();
        int count = 0;
        int end = 0;
        auto ptr = s.begin();
        while (it != s.end())
        {
            if(*it == ' ')
            {
                std::reverse(ptr, s.begin() + count);
                ptr = s.begin() + count + 1;
            }
            count++;
            it++;
        }
        std::reverse(ptr, s.end());
        return s;

    }
};

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

class Solution {
public:
void MulItem(string& tmp, string& num1, char a)
    {
        int i = 0, sign = 0;
        int mul = 0;
        while (i < num1.size())
        {
            mul = (num1[i] - '0') * (a - '0') + sign;
            if (mul >= 10)
            {
                sign = mul / 10;
                mul %= 10;
            }
            else
                sign = 0;
            tmp.push_back(mul + '0');
            i++;
        }
        if (sign > 0)
            tmp.push_back(sign + '0');
    }
    //对应位相加,sign进位采用引用传递
    int AddItem(int a, int b, int& sign)
    {
        int add = a + b + sign;
        if (add >= 10)
        {
            sign = 1;
            add -= 10;
        }
        else
            sign = 0;
        return add;
    }

    //错位相加
    void MoveAdd(string& result, string& tmp, int k)
    {
        int i, j;
        i = k;
        j = 0;
        int sign = 0;
        while (i < result.size() && j < tmp.size())
        {
            result[i] = AddItem(result[i] - '0', tmp[j] - '0', sign) + '0';
            i++;
            j++;
        }
        while (i < result.size() && sign)
        {
            result[i] = AddItem(result[i] - '0', 0, sign) + '0';
            i++;
        }
        while (j < tmp.size())
        {
            int v = AddItem(0, tmp[j] - '0', sign);
            result.push_back(v + '0');
            j++;
        }
        if (sign)
            result.push_back(sign + '0');
    }
    string multiply(string num1, string num2)

    {

        //先翻转数据,方便进位处理
        reverse(num1.begin(), num1.end());
        reverse(num2.begin(), num2.end());
        string tmp, result;

        for (int i = 0; i < num2.size(); ++i)
        {
            //使用num2的每一个数据乘以num1
            MulItem(tmp, num1, num2[i]);
            //将乘得的结果进行错位相加
            MoveAdd(result, tmp, i);
            tmp.clear();
        }
        //处理前导0
        while (result.size() != 1 && result.back() == '0')
            result.pop_back();
        //翻转数据,恢复数据
        reverse(result.begin(), result.end());
        return result;
    }
};

【实现100个unity特效之4】Unity ShaderGraph使用教程与各种特效案例(2023/12/1更新) ShaderGraph是Unity中的一个可视化工具,用于创建和编辑图形着色器。其意义在于简化编写复杂着色器的过程,减少对具体编程语言的依赖,使艺术家和设计师可以更方便地创建各种美术效果。使用ShaderGraph可以通过图形界面设置着色器属性,包括颜色、纹理、光照等,并在视窗预览中即时显示结果,从而快速迭代和调整着色器。使用ShaderGraph需要先安装Unity 2018.1或以上版本,并添加ShaderGraph插件。打开ShaderGraph面板后,可以通过拖放节点并连接它们来创建着色器。 阅读详情

相关推荐

40分钟快速入门Dart基础(上)

针对final 和const 理解的有点模糊的可以参考这篇文章: 可以参考这里Flutter 知识梳理(Dart)-Dart中static final const 区别看是不是超级好上手,好操作~其实Dart的整个学习起来还是蛮简单的,对于有基础的老司机更简单一些。分分钟敲起来~这篇文章就简单的介绍到这里如何短时间快速入门Dart,避免入坑。由于是想让大家快速入门Flutter,所以针对上面的这些章节我们讲解的只是一些在开发Flutter 过程中常用的一些方法和函数。

qq_7813180 4569

C#调用C++生成的dll时,char*与string的处理

C#调用C++的DLL时,char*与string的处理 文章转载或参考自:https://www.cnblogs.com/ligl/p/9952849.html

pcx_ny 922

逆向实战:手把手教你用Frida绕过Flutter App的SSL Pinning(附脚本修改指南)

本文详细介绍了如何利用Frida工具绕过Flutter应用的SSL Pinning保护机制,包括静态分析定位关键函数、定制Frida Hook脚本以及流量捕获的完整解决方案。通过实战案例和脚本修改指南,帮助安全研究人员有效应对Flutter应用的安全挑战,特别适用于安卓平台的逆向工程和抓包需求。

weixin_28723171的博客 402

使用std::string 通过dll接口传递时,应该注意的问题

问题描述:         在一个lib库中用到了另外一个库的dll,lib库的运行时库是“多线程DLL(/MD)”,(属性 C/C++ ->代码生成->运行库),dll工程使用的运行库是“多线程调试DLL(/MDd)”。导致向dll的接口传递std::string参数时报错。 解决方法:         将lib项目的运行库改为“多线程调试DLL(/MDd)”. 原因分析:

xiaoxiaoyusheng2012的专栏 9187

泛微E9集成登录实战:5分钟搞定第三方系统单点登录配置(附避坑指南)

本文详细解析了泛微E9集成登录的实战配置流程与核心原理。通过理解其基于信任代理的身份传递机制,文章指导用户如何在5-10分钟内完成第三方系统的单点登录对接,涵盖基础配置、核心参数设置、高级表达式应用,并提供了编码问题、账号不一致等常见避坑指南,助力企业快速构建统一身份认证门户。

weixin_29185493的博客 288

西门子S7-1200模拟量的使用

模块类型核心参数输入信号处理输出信号配置转换原理数值范围TIA Portal配置编程要点 转换公式: 输出模拟量: 五、校准与线性化 硬件校准非线性补偿抗干扰措施数字滤波温度控制压力监控流量调节信号异常精度不足实践操作参考文档项目案例借鉴博客

weixin_51020122的博客 4768

C++中简单自己实现一个string类

工具VS2017 MyString.h #pragma once #include<iostream> using namespace std; class MyString { friend ostream & operator<<(ostream &os, const MyString& s); friend istream & ...

Dxs4396的博客 4274

Pytorch transformers tokenizer 分词器词汇表添加新的词语和embedding

NLP添加新词汇

7322

【C++札记】实现C++的string类

C++有了string类使得操作字符串变得很方便。有关string类,面试过程中也经常问到的就是自己实现一个sring类。下边实现个String类供大家参考: String.h #pragma once class String { public: //构造函数 String(const char* str = nullptr); //拷贝构造函数 String(const St...

码农code之路 325

详解大模型微调数据集构建方法(持续更新)

本文详细介绍了大模型微调数据集构建方法,希望能对学习大模型的同学们有所帮助。 文章目录 1. 前言 2. 微调数据集构建方法 2.1 方法一 2.2 方法二 2.3 方法三 2.4 方法四 2.5 方法五

herosunly的博客 28万+

详细解释SigLIP的原理,为什么有些多模态模型喜欢用他?怎么应用于机器人控制呢

详细解释SigLIP的原理,为什么有些多模态模型喜欢用他?怎么应用于机器人控制呢。

具身机器人和通用AI 1225

【C++】string类

C++ string类解析摘要 本文全面解析C++ string类,重点包括: string类解决了C语言字符串的4大痛点:安全性问题、内存管理复杂、功能有限、不符合面向对象思想 string类基础用法和C++11新特性(auto关键字和范围for循环) 详细介绍了5类核心接口: 构造函数(空字符串、C字符串构造等) 容量操作(size()/length()、resize()等) 元素访问和遍历(下标操作、迭代器、范围for) 通过代码示例演示string类的实际应用,包括字符串处理和安全操作 string

凤年徐的博客 3477

【2026OD新机考】【不定滑窗】20260415-API请求日志去重分析【Py/Java/C++/C/JS/Go六种语言OD真题】【欧弟算法】全网注释最详细分类最全的华子OD真题题解

中找到所有的连续的相同元素,同时统计对应的连续相同元素对应的值的平均值。:当右指针为第一个元素(没有前一个元素),或者右指针对应的元素。某微服务系统的日志监控平台需分析API调用记录,日志含大量。相等,说明当前元素仍然属于同一个相同元素窗口,将当前元素值。中的所有元素均相等,并且计算窗口和情况即可。请实现一个函数,对给定的日志数据去重合并处理。位置,表示开启一个新的窗口,此时需要重置窗口和。,为了优化重复的请求记录,存储和后续分析需对。:对应的响应时间数组(毫秒)。重新置为当前右指针的元素值。

weixin_48157259的博客 111

cpp的string类

本篇将讲述string类中的各种重要和常用函数(string()、begin()、rbegin()、cbegin()、crbegin()、end()、rend()、cend()、crend()、范围for、size()、resize()、capacity()、reserve()、clear()、empty()、append()、push_back()、insert()、erase()、replace()、swap()、find()、rfind()、substr()、compare())。

2401_84018574的博客 978

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

CPP简单实现string类

摘要:本文展示了一个自定义字符串类string的实现,包含构造函数、拷贝构造、析构函数等基本功能,以及字符串操作(append、push_back、insert、erase等)、查找(find)和子串(substr)等功能。类中通过动态内存管理存储字符串数据,并实现了迭代器支持和运算符重载(如[]、+=、<等),是一个完整的字符串类实现示例。

weixin_39559852的博客 259

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

C++【string类,模拟实现string类】

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

qq_67578580的博客 3697

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

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

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

2301_77459845的博客 2764

C++ 之 String类详解

String小引string类常用接口常见构造容量操作访问操作修改操作string类非成员函数 小引 C语言中,字符串是以\0结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。 C++中就引入了String类,它可以看做是一个管理字符串的数据结构。...

giturtle's blog 1万+

(C++ STL)string类的简单模拟实现与源码展示

string类的简单模拟实现与源码展示

瞌睡不来的博客 1124

c++ 自己实现string类

c++ 自己实现string类

qingjiao_fa的博客 1466

【C++】string类的使用

在前面我们说过,前面的绝大部分内容都是在为了后面真正进入C++这块大门做铺垫,今天我们将正式的步入。

m0_64826370的博客 2263

C++ string 类(标准库中的string类)

本文介绍了C++中string类的基本概念和常用接口。string类是标准模板库(STL)提供的可变长度字符串处理工具,基于basic_string模板类实现。文章详细讲解了string的常见构造函数、容量操作(size/resize/capacity等)、修改操作(append/push_back等)、访问遍历方式(迭代器/下标/范围for循环)以及非成员函数(运算符重载/getline等)。同时对比了VS和g++编译器下string内部结构的差异:VS采用小字符串优化(SSO),而g++使用写时拷贝技术

aramae的博客 1500

C++初阶-string类的模拟实现

若写成两个构造函数,一个设置成无参,一个设置成带参,若调用如上的带参构造函数就会报错,将str传给_str,属于权限放大,为了解决这个问题,可以将_str改为const char*类型,但是无法修改_str所指向的内容,调用operator[]函数就会报错。为了避免频繁扩容,使用一个128的字符数组接收,若输入的数据比128小,跳出循环将数组中的数据传给string类s,若输入的数据比128大,则将字符数组整体传给string类s,再正常扩容。

sushang的博客 1330

【C++】模拟实现string类

使用C++语言模拟实现C++中的string类,包括string类中的成员变量,默认成员函数,成员函数,运算符重载以及迭代器. 是适合C++初学者的入门模拟实现string逐步详解.

C/C++领域,数据结构,linux,MySQl学习者 1733

【C++】String类

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一。增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。,说明该对象时资源的最后一个使用者,将该资源释放;的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。个字符数组的固定空间,不需要通过堆创建,效率高。,自动迭代,自动取数据,自动判断结束。

L286594的博客 1237
上一篇: 关于Sort的补充
下一篇: C++ vector类
FirstFrost --sy
博客等级 码龄3年 371粉丝 68原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值