【C++中的STL(未完成)】

news2024/11/26 8:51:28

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、pandas是什么?
  • 二、使用步骤
    • 1.引入库
    • 2.读入数据
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、【STL简介]

网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构
以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

1.1【 什么是STL】


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

1.2【 STL的版本】


原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意
运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使
用。 HP 版本--所有STL实现版本的始祖。
P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,
符号命名比较怪异。
RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,
可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,
主要参考的就是这个版本。

1.3【STL的六大组件】

1.4【STL的缺陷】


1. STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。
2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。

二、【string】

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

这里附一份优质博客以供参考:

C++面试中string类的一种正确写法 | 酷 壳 - CoolShell

2.1 【string类】

1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作
单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信
息,请参阅basic_string)。
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个
类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>
string;
4. 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;

2.2 【string类的常用接口说明】

1.【 string类对象的常见构造】

参考资料:

 http://www.cplusplus.com/reference/string/string/string/

using namespace std;
void TestString1()
{
	string s1;
	string s2("hello world");
	string s3(s2);
	cout << &s1 << endl;
	cout << s1 << endl;
	cout << &s2 << endl;
	cout << s2 << endl;
	cout << &s3 << endl;
	cout << s3 << endl;
}



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

2. 【string类对象的容量操作】

参考资料:

size:http://www.cplusplus.com/reference/string/string/size/

length:http://www.cplusplus.com/reference/string/string/length/

capacity:http://www.cplusplus.com/reference/string/string/capacity/

empty:http://www.cplusplus.com/reference/string/string/empty/

clear:http://www.cplusplus.com/reference/string/string/clear/

reserve:http://www.cplusplus.com/reference/string/string/reserve/

resize:http://www.cplusplus.com/reference/string/string/resize/

string ::size和string::length都是同义词并返回相同的值

void TestString2()
{
	string s1("hello world");
	cout << s1.max_size() << endl;
	cout << s1.capacity() << endl;
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1 << endl;
	s1.reserve(100);
	cout << s1.capacity() << endl;
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	s1.resize(20,'!');
	cout << s1.capacity() << endl;
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1 << endl;
	cout << s1.empty() << endl;
	s1.clear();
	cout << s1 << endl;
	cout << s1.empty() << endl;




}

这里我们可以看到初始容量是15,那么达到它的容量了以后会怎么办呢?会进行扩容吗?

答案是会的,会自动扩容,初始容量为15,第一次扩容为31,之后按接近1.5倍的速率增长,

// 利用reserve提高插入数据的效率,避免增容带来的开销
//====================================================================================
void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	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';
		}
	}
}

我们如果知道容量,那么就可以在需要扩容之前用reverse来控制容量,因此我们是可以是利用reverse来提高插入效率的。

void Teststring2()
{
	// 注意:string类对象支持直接用cin和cout进行输入和输出
	string s("hello, bit!!!");
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(20, 'a');
	cout << s << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(15);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
}

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

3.【 string类对象的访问及遍历操作】

参考资料:

operator [ ] :http://www.cplusplus.com/reference/string/string/operator%5B%5D/

迭代器iteraor:

begin: http://www.cplusplus.com/reference/string/string/begin/

end:http://www.cplusplus.com/reference/string/string/end/

rbegin:http://www.cplusplus.com/reference/string/string/rbegin/

rend:http://www.cplusplus.com/reference/string/string/rend/

这里可以看一下具体使用方法:
 

void TestString3()
{
	string s3("hello world");
	const string s4("aaaaa");
	cout << s3[0] << endl;//可以直接使用[]打印
	cout << s4[0] << endl;//可以直接使用[]打印
	s3[0] = 'H';//可直接修改
	//s4[0] = 'A';//此时s4类似常量字符串,不能修改。
	// 
	//1.For循环+operator[]
;	for (int i = 0; i < s3.size(); i++)
	{
		cout << s3[i];
	}
    cout << endl;
    //2.迭代器进行遍历
    string::iterator it = s3.begin();
	/// <summary>
	/// 这里也可以替换为auto it=s3.begin
	/// </summary>
	while (it != s3.end())
	{
		cout << *it;
		it++;
	}
	cout << endl;
	string::reverse_iterator  rit = s3.rbegin();
	while (rit != s3.rend())
	{
		cout << *rit;
		rit++;
	}
	cout << endl;
	//3.范围for遍历 
	for (auto ch : s3)
	{
		cout << ch ;
	}


}

int main()
{
	//TestString1();
	//TestString2();
	//TestPushBack();
	TestString3();
	return 0;
}

4. 【string类对象的修改操作】

push_back:http://www.cplusplus.com/reference/string/string/push_back/

append:http://www.cplusplus.com/reference/string/string/append/
operator+=:http://www.cplusplus.com/reference/string/string/operator+=/

c_str:http://www.cplusplus.com/reference/string/string/c_str/

find+npos:

http://www.cplusplus.com/reference/string/string/find/

http://www.cplusplus.com/reference/string/string/npos/

rfind:http://www.cplusplus.com/reference/string/string/rfind/

subster:http://www.cplusplus.com/reference/string/string/substr/

使用实例:

void TestString1()
{
	string s1("hello world");
	cout << s1 << endl;
	s1.push_back('a');
	cout << s1 << endl;
	s1.append("b");
	cout << s1 << endl;
	s1 += 'c';
	s1 += "abc";
	cout << s1 << endl;
	cout << s1.c_str() << endl;


}

rfind默认从后向前寻找,rfind找的是字符串中某个字符最后出现的位置,而find找的是某个字符首次出现的位置,而当字符串中不包含某个字符时,就会返回npos

先面来看一个示例:

void TestString2()
{
	string file("Test.cpp.pp");
	size_t pos = file.rfind('.');
	cout << pos << endl;
	string suffix(file.substr(pos, file.size() - pos));
	cout << suffix << endl;
	string url("http://www.cplusplus.com/reference/string/string/find/");
	size_t start = url.find("/");
	if (start == string::npos)
	{
		cout << "invalid url" << endl;
		return;
	}
	cout << start << endl;
}

下面是整体的使用用例:

void TestString3()
{
	    // npos是string里面的一个静态成员变量
		// static const size_t npos = -1;
		// 取出url中的域名
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == string::npos)
	{
		cout << "invalid url" << endl;
		return;
	}
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;

	// 删除url的协议前缀
	size_t pos = url.find("://");
	url.erase(0, pos + 3);
	cout << url << endl;
}

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

5.【string类非成员函数】

参考资料:

operator++:http://www.cplusplus.com/reference/string/string/operator+/

operator>>:http://www.cplusplus.com/reference/string/string/operator%3E%3E/

operator<<:http://www.cplusplus.com/reference/string/string/operator%3C%3C/

getline:http://www.cplusplus.com/reference/string/string/getline/

relational operators:http://www.cplusplus.com/reference/string/string/operators/

string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。

6.【vs和g++下string结构的说明】


【注意】:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
【vs下string的结构】:
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放当字符串长度大于等于16时,从堆上开辟空间这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。

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

但是string类中仍然有一些缺陷:

STL 的string类怎么啦?_stl里为什么没讲string-CSDN博客

2.3【string的模拟实现】

我们为了跟更加深入了解string除了了解其使用方法以外,最好能够模拟实现string:
下面是代码部分:

【String.h部分】:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <stdbool.h>
#include <iostream>
using namespace std;
namespace str
{
	class String
	{
	public:
		void swap(str::String& s);
		const static size_t npos = -1;
		typedef char* iterator;
		typedef const char* const_iterator;
		String(const char* str = "");
		~String();
		String(const String& s);
		String& operator=(const str::String& s);
		size_t capacity() const;
		size_t size() const;
		const char* c_str() const;
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;
		const char& operator[](size_t pos)const;
		void push_back(char ch);
		void append(const char* str);
		void reserve(size_t n);
		String& operator+=(char ch);
		String& operator+=(const char* str);
		void Insert(size_t pos, const char* str);
		void Insert(size_t pos, char ch);
		void erase(size_t pos, size_t len = npos);
		bool operator<(const String& s)const;
		bool operator==(const String& s)const;
		bool operator>(const String& s)const;
		bool operator<=(const String& s)const;
		bool operator!=(const String& s)const;
		bool operator>=(const String& s)const;
		void clear();
		void resize(size_t n, char ch = '\0');
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* sub, size_t pos = 0);
		String substr(size_t pos, size_t len = npos);
	private:
		char* _str;
		
		size_t _size;
		size_t _capacity;
	};
}
ostream& operator<<(ostream& out, const str::String& s);
istream& operator>>(istream& in, str::String& s);

【String.cpp部分】:

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"



//void str::String::swap(str::String& s)
//{
//	std::swap(_str, s._str);//加上std的原因是防止其与库里的swap冲突,因此指定这里的swap是std库
//	std::swap(_size, s._size);//中的swap
//	std::swap(_capacity, s._capacity);
//}

// s2(s1);
//str::String::String(const str::String& s)
//	:_str(nullptr)
//	, _size(0)
//	, _capacity(0)
//{
//	String tmp(s._str);//拷贝构造调用构造,构建出来的tmp再与this进行交换。
//	swap(tmp);
//}
// s2=s1;
//str::String& str::String::operator=(const str::String& s)
//{
//	if (this != &s)
//	{
//		String tmp(s);
//		//this->swap(tmp);
//		swap(tmp);
//	}
//
//	return *this;
//}

// s2 = s3
//str::String& str::String::operator=(str::String tmp)//s3拷贝构造tmp
//{
//	swap(tmp);//交换tmp与s2
//
//	return *this;
//}

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

}
str::String::String(const str::String& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}
str::String& str::String::operator=(const str::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;

}

str::String::~String()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}
size_t str::String::capacity() const
{
	return _capacity;
}
size_t str::String::size() const
{
	return _size;
}
const char* str::String:: c_str() const
{
	return _str;
}
str::String::iterator str::String:: begin()
{
	return _str;
}
str::String::iterator str::String::end()
{
	return _str + _size;
}
str::String::const_iterator str::String::begin() const
{
	return _str;
}
str::String::const_iterator str::String::end() const
{
	return _str + _size;
}
const char& str::String::operator[](size_t pos)const
{
	assert(pos < _size);
	return _str[pos];
}
void str::String::reserve(size_t n)//按需调整空间
{
	if (_capacity < n)//当前容量小于指定容量就进行扩容,将数组的内容扩大。
	{
		char* tmp = new char[n + 1];//多开的1个空间是为了存储‘\0’
		strcpy(tmp, _str);//之后将原字符串中的内容拷贝到新空间
		delete[] _str;//并释放原空间
		_str = tmp;//将原数组指向新开辟的数组空间
		_capacity = n;//并更新容量
	}
}
void str::String::push_back(char ch)
{

	if (_size == _capacity)在进行尾插之前,要先判断是否需要扩容
	{
		reserve((_capacity == 0) ? 4 : _capacity * 2);
	}
	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';
}
void str::String::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len >= _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}
str::String& str::String::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
str::String& str::String::operator+=(const char* str)
{
	append(str);
	return *this;
}
void str::String::Insert(size_t pos, char ch)
{
	if (_size == _capacity)//判断是否需要扩容
	{
		reserve((_capacity == 0) ? 4 : 2 * _capacity);
	}
	size_t end = _size + 1;//这里定义end位于‘\0’之后的一个位置
	while (end > pos)//将pos之后的数据全都向后移
	{
		_str[end] = _str[end-1];
		end--;
	}
	_str[pos] = ch;//将pos位置处放上需要插入的字符
	_size++;//最后将_size++即可
	/*int  end = _size;
	while (end >=(int) pos)//这里转换成int的原因是,pos为无符号整数,直接进行比较大小会导致end
	{//进行类型提升将end也当作无符号整数,当end--为-1时,由于end变为无符整形,-1=4294967295
        _str[end+1] = _str[end];//会大于0
		end--;
	}
	_str[pos] = ch;
	_size++;*/
}
bool str::String:: operator<(const String& s)const
{
	return strcmp(_str, s._str) < 0;
}

bool str::String:: operator==(const String& s)const
{
	return strcmp(_str, s._str)== 0;
}

bool str::String:: operator>(const String& s)const
{
	return strcmp(_str, s._str) > 0;
}

bool str::String:: operator<=(const String& s) const
{
	return *this < s || *this == s;
}

bool str::String::operator>=(const String& s) const
{
	return !(*this < s);
}

bool str::String::operator!=(const String& s) const
{
	return !(*this == s);
}
void str::String::clear()
{
	_str[0] = '\0';
	_size = 0;
}
ostream& operator<<(ostream& out, const str::String& s)
{
	/*for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;*/
	for (auto ch : s)//范围for遍历字符串
	{
		out << ch;
	}
	return out;
	/*str::String::const_iterator it = s.begin();
	while (it != s.end())
	{
		out << *it;
		it++;
	}
	return out;*/

}
istream& operator>>(istream& in, str::String& s)
{
	s.clear();//在流提取之前要先将字符串里之前的数据清空,不然提取到的新字符串就会尾插在旧数据
	char ch;//之后
	ch = in.get();//由于cin类似于scanf遇到空格就不会再进行读取了因此这里需要用到istream里的get
	char buffer[129];//这里定义一个数组,用来存放读取到的字符,由于是在函数中创建的,不会占用
	size_t i = 0;//字符串的空间,跟随函数的生命周期
	while (ch != ' ' && ch != '\n')
	{
		buffer[i++] = ch;
		if (i == 128)//如果读取的字符太长数组都存满了,还没有读取结束
		{//就将获取到的字符先放入到字符串中,之后重新进行读取
			buffer[i] = '\0';
			s += buffer;
			i = 0;
		}
		ch = in.get();
	}
	if (i != 0)//如果字符不长,则可以直接将其放到字符串中
	{
		buffer[i] = '\0';
		s += buffer;
	}
	return in;
}

void str::String::Insert(size_t pos, const char* str)
{
	assert(pos < _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(len + _size);
	}
	//int end = _size + len;
	//while (end > pos)
	//{
	//	_str[end] = _str[end - len];
	//	end--;
	//}
	int end = _size;
	while (end >= (int)pos)
	{
		_str[end + len] = _str[end];
		end --;
	}
	strncpy(_str + pos, str, len);
	_size += len;
}
void str::String::erase(size_t pos, size_t len )
{
	assert(pos < _size);
	if (len == npos || len + pos > _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			begin++;
		}
		_size -= len;
	}
}
void str::String::resize(size_t n, char ch)
{
	if (n <= _size)
	{
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		reserve(n);
		while (_size < n)
		{
			_str[_size] = ch;
			++_size;
		}
		_str[_size] = '\0';
	}
}
size_t str::String::find(char ch, size_t pos)
{
	assert(pos < _size);
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;

}
size_t str::String::find(const char* sub, size_t pos)
{
	assert(pos < _size);
	const char* p = strstr(_str + pos, sub);
	if (p)
	{
		return p - _str;
	}
	else
	{
		return npos;
	}
}
str::String str::String::substr(size_t pos, size_t len )
{
	assert(pos < _size);
	str::String s;
	size_t end = pos + len;
	if (len == npos || len + pos > _size)
	{
		len = _size - pos;
		end = _size;

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

	return s;

}

【Test.cpp部分】:

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"
//void test_string1()
//{
//	str::String s1("hello world");
//	cout << s1.c_str() << endl;
//
//	str::String s2;
//	cout << s2.c_str() << endl;
//
//	for (size_t i = 0; i < s1.size(); i++)
//	{
//		cout << s1[i] << " ";
//	}
//	cout << endl;
//
//	str::String::iterator it = s1.begin();
//	while (it != s1.end())
//	{
//		(*it)++;
//		cout << *it << " ";
//		++it;
//	}
//	cout << endl;
//
//	for (auto& ch : s1)
//	{
//		ch++;
//		cout << ch << " ";
//	}
//	cout << endl;
//
//	cout << s1.c_str() << endl;
//}
//
void test_string2()
{
	str::String s1("hello world");
	cout << s1.c_str() << endl;
	s1.push_back(' ');
	s1.append("hello bit hello bit");

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

	s1 += '#';
	s1 += "*********************";
	cout << s1.c_str() << endl;

	str::String s2;
	s2 += '#';
	s2 += "*********************";
	cout << s2.c_str() << endl;
}

void test_string3()
{
	str::String s1("hello world");
	cout << s1.c_str() << endl;

	s1.Insert(5, '%');
	cout << s1.c_str() << endl;

	s1.Insert(s1.size(), '%');
	cout << s1.c_str() << endl;

	s1.Insert(0, '%');
	cout << s1.c_str() << endl;
}

void test_string4()
{
	str::String s1("hello world");
	str::String s2("hello world");

	cout << (s1 >= s2) << endl;

	//s1[0] = 'z';
	cout << (s1 >= s2) << endl;

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

}


void Test_string1()
{
	str::String s1("hello world");
	str::String s2;
	//cout << s1 << endl;
	//cout << s2 << endl;
		for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
	cout << endl;

	str::String::iterator it = s1.begin();
	while (it != s1.end())
	{
		(*it)++;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto& ch : s1)
	{
		ch++;
		cout << ch << " ";
	}
	cout << endl;

	cout << s1.c_str() << endl;
}



void test_string5()
{
	str::String s1("hello world");
	s1.Insert(5, "abc");
	cout << s1 << endl;

	s1.Insert(0, "xxx");
	cout << s1 << endl;

	s1.erase(0, 3);
	cout << s1 << endl;

	s1.erase(5, 100);
	cout << s1 << endl;

	s1.erase(2);
	cout << s1 << endl;
}

void test_string6()
{
	str::String s1("hello world");
	cout << s1 << endl;

	s1.resize(5);
	cout << s1 << endl;

	s1.resize(25, 'x');
	cout << s1 << endl;
}

void test_string7()
{
	str::String s1("test.cpp.tar.zip");
	//size_t i = s1.find('.');
	//size_t i = s1.rfind('.');

	//string s2 = s1.substr(i);
	//cout << s2 << endl;

	str::String s3("https://legacy.cplusplus.com/reference/string/string/rfind/");
	//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");
	// 协议
	// 域名
	// 资源名

	str::String sub1, sub2, sub3;
	size_t i1 = s3.find(':');
	if (i1 != str::String::npos)
		sub1 = s3.substr(0, i1);
	else
		cout << "没有找到i1" << endl;

	size_t i2 = s3.find('/', i1 + 3);
	if (i2 != str::String::npos)
		sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
	else
		cout << "没有找到i2" << endl;

	sub3 = s3.substr(i2 + 1);

	cout << sub1 << endl;
	cout << sub2 << endl;
	cout << sub3 << endl;
}

void test_string8()
{
	str::String s1("hello world");
	str::String s2 = s1;
	cout << s1 << endl;
	cout << s2 << endl;

	str::String s3("xxxxxxxxxxxxxxxxxxx");
	s2 = s3;

	cout << s2 << endl;
	cout << s3 << endl;
}

void test_string9()
{
	str::String s1("hello world");
	cin >> s1;
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
}

int main()
{
	//Test_string1();
	//test_string1();
	//test_string2();
	//test_string3();
	//test_string4();
	//test_string6();
	//test_string7();
	//test_string8();
	//test_string9();
	return 0;
}

下面是老生常谈的问题了,也就是在调用拷贝构造时会出现的两个指针指向一块空间的情况,这样再调用析构函数时,就会导致同一块空间连续析构两次。

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

这里可以通过增加一个引用计数来解决,也就是说增加一个变量用来统计某块空间被多少个变量所引用。

那么什么是写时拷贝呢?

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

参考资料:

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

C++的std::string的“读时也拷贝”技术! | 酷 壳 - CoolShell

三、【vector】

3.1 【vector的介绍】

参考资料:
http://www.cplusplus.com/reference/vector/vector/
1. vector是表示可变大小数组的序列容器。
2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
3.2【vector的使用】

1.【vector的构造】

代码:

void TestConstructure()
{
	vector<int> a;//无参构造
	vector<int> arr(4, 100);//创建一个大小为4,数组元素为100的数组。
	vector<int> brr(arr.begin(), arr.end());//以arr的始迭代器和arr的末迭代器构造
	vector<int> crr(brr);//用brr拷贝构造crr
}

2.【vector中的迭代器】

void TestIterator()
{
	vector<int> arr(10, 0);
	vector<int>::iterator it = arr.begin();
	int i = 1;
	while (it != arr.end())
	{
		*it = i;
		cout << *it << " ";
		i++;
		it++;

	}
	cout << endl;
	int myints[] = { 16,2,77,29 };
	vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));//迭代器构造

	cout << "The contents of fifth are:"<<endl;
	
	for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
		cout << ' ' << *it;
	cout << '\n';

}

3.【容量操作】

void TestCapacity()
{
	int a[] = { 1,2,3,4,4,55,6,7,8,9,100 };
	int len = sizeof(a) / sizeof(int);
	vector<int> arr(a, a+len);
	cout << " arr的大小为:" << arr.size()<<endl;
	for (int i=0;i<arr.size();i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
// 将有效元素个数设置为n个,如果时增多时,增多的元素使用data进行填充
// 注意:resize在增多元素个数时可能会扩容
	arr.resize(2 * len, 0);
	cout << " arr的大小为:" << arr.size()<<endl;
	for (auto ch : arr)
	{
		cout << ch << " ";
	}
	cout << endl;
	arr.resize(100);
	cout << " arr的大小为:" << arr.size()<<endl;

// 往vecotr中插入元素时,如果大概已经知道要存放多少个元素
// 可以通过reserve方法提前将容量设置好,避免边插入边扩容效率低
	vector<int> v;
	size_t sz = v.capacity();
	cout <<"容量为:" << v.capacity() << endl;
	v.reserve(100);// 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "容量为:" << v.capacity() << endl;
	cout << "making bar grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())//相等就代表扩容,打印新的容量
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

//v.reserve(100);  // size = 0    capacity 100
//v.resize(100);     // size = 100  capacity 100

需要注意的是size和capacity的变化:

void TestChange()
{
	vector<int> v;
	cout << "v的大小为:" << v.size() << endl;
	// set some initial content:
	for (int i = 1; i < 10; i++)
		v.push_back(i);

	v.resize(5);
	cout << "v的大小为:" << v.size() << endl;
	v.resize(8, 100);
	cout << "v的大小为:" << v.size() << endl;
	v.resize(12);
	cout << "v的大小为:" << v.size() << endl;
	cout << "v contains:";
	for (size_t i = 0; i < v.size(); i++)
		cout << ' ' << v[i];
	cout << '\n';


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

capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
resize在开空间的同时还会进行初始化,影响size。

4.【增删查改】

void Add_Delete_Find_Modify()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	v.push_back(8);
	v.push_back(9);
	v.push_back(10);
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;
	auto it = v.begin();
	cout << "正向打印:";
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	// vector<int>::reverse_iterator rit = v.rbegin();
	auto rit = v.rbegin();
	cout << "反向打印:";
	while (rit != v.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
	// 在指定位置前插入值为val的元素,比如:8之前插入1000,如果没有则不插入
	// 1. 先使用find查找8所在位置
	// 注意:vector没有提供find方法,如果要查找只能使用STL提供的全局find
	it = find(v.begin(), v.end(), 8);
	v.insert(it, 1000);
	for (auto ch : v)
	{
		cout << ch<<" ";
	}
	cout << endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;
	it = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据
	v.erase(it);
	for (auto ch : v)
	{
		cout << ch<<" ";
	}
	cout << endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;
	// operator[]+index 和 C++11中vector的新式for+auto的遍历
    // vector使用这两种遍历方式是比较便捷的。
	v[0] = 10;
	cout << v[0] << endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;
	// 1. 使用for+[]小标方式遍历


	vector<int> swapv;
	swapv.swap(v);
	cout << "交换后:" << endl;
	cout << "v data:";
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	cout << "swapv data:";
	for (size_t i = 0; i < swapv.size(); ++i)
		cout << swapv[i] << " ";
	cout << endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;


	cout << "再次交换后:" << endl;
	v.swap(swapv);
	cout << "v data:";
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	cout << "swapv data:";
	for (size_t i = 0; i < swapv.size(); ++i)
		cout << swapv[i] << " ";
	cout << endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;
	v.clear();
	cout << "clear后的数据:"<<endl;
	for (auto ch : v)
	{
		cout << ch<<" ";
	}
	cout << endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;
	v.shrink_to_fit();
	cout << "shrink_to_fit后:"<<endl;
	cout << "v的大小为:" << v.size() << endl;
	cout << "v的容量为:" << v.capacity() << endl;

}


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1557315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQLServer sys.default_constraints介绍

sys.default_constraints 是 SQL Server 的系统视图&#xff0c;它包含了数据库中所有默认约束的信息。默认约束是数据库对象&#xff08;如表中的列&#xff09;的约束&#xff0c;它为列定义了一个默认值&#xff0c;当在插入新行时没有为该列提供值时&#xff0c;将使用这个…

HarmonyOS实战开发-如何实现一个支持加减乘除混合运算的计算器。

介绍 本篇Codelab基于基础组件、容器组件&#xff0c;实现一个支持加减乘除混合运算的计算器。 说明&#xff1a; 由于数字都是双精度浮点数&#xff0c;在计算机中是二进制存储数据的&#xff0c;因此小数和非安全整数&#xff08;超过整数的安全范围[-Math.pow(2, 53)&#…

【一】DDR3基础知识与IMG IP

【一】DDR3基础知识与IMG IP 一、DDR3的基本知识 1、DDR3全称为第三代双倍速率同步动态随机存储器 特点&#xff1a;掉电无法保存数据&#xff0c;需要周期性的刷新&#xff1b;时钟上升沿和下降沿都在传输数据&#xff1b;突发传输&#xff0c;突发长度burtst length一般为…

AcWing 1413. 矩形牛棚(每日一题)

原题链接&#xff1a;1413. 矩形牛棚 - AcWing题库 作为一个资本家&#xff0c;农夫约翰希望通过购买更多的奶牛来扩大他的牛奶业务。 因此&#xff0c;他需要找地方建立一个新的牛棚。 约翰购买了一大块土地&#xff0c;这个土地可以看作是一个 R 行&#xff08;编号 1∼R&…

45.跳跃游戏||

// 定义一个名为Solution的类 class Solution {// 定义一个public方法jump&#xff0c;输入参数为一个整数数组nums&#xff0c;返回值类型为整数public int jump(int[] nums) {// 初始化跳跃次数结果变量为0int result 0;// 初始化当前覆盖的最远距离下标为0int end 0;// 初…

RVM安装ruby笔记

环境 硬件&#xff1a;Macbook Pro 系统&#xff1a;macOS 14.1 安装公钥 通过gpg安装公钥失败&#xff0c;报错如下&#xff1a; 换了几个公钥地址&#xff08;hkp://subkeys.pgp.net&#xff0c;hkp://keys.gnupg.net&#xff0c;hkp://pgp.mit.edu&#xff09;&#xff0c;…

某东推荐的十大3C热榜第一名!2024随身wifi靠谱品牌推荐!2024随身wifi怎么选?

一、鼠标金榜&#xff1a;戴尔 商务办公有线鼠标 售价:19.9&#xffe5; 50万人好评 二、平板电脑金榜&#xff1a;Apple iPod 10.2英寸 售价:2939&#xffe5; 200万人好评 三、随身WiFi金榜&#xff1a;格行随身WiFi 售价:69&#xffe5; 15万人好评 四、游戏本金榜&#xff…

python爬虫-----输入输出与流程控制语句(第四天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

车辆充电桩管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 1. 前台功能…

Linux系统使用Docker部署MinIO结合内网穿透实现公网访问本地存储服务

文章目录 前言1. Docker 部署MinIO2. 本地访问MinIO3. Linux安装Cpolar4. 配置MinIO公网地址5. 远程访问MinIO管理界面6. 固定MinIO公网地址 前言 MinIO是一个开源的对象存储服务器&#xff0c;可以在各种环境中运行&#xff0c;例如本地、Docker容器、Kubernetes集群等。它兼…

车载电子与软件架构

车载电子与软件架构 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师 (Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝完再挣扎,出门靠自己,四…

网安学习笔记-day10,web服务器

web服务器的部署 Web(World Wide Web)(“万维网”) 我们一般用的网页都由web服务器提供的 使用的协议是基于TCP协议的HTTTP(80)和HTTPS(443) 常用Web服务器发布软件 微软&#xff1a;IIS(Internet Information Services) Linux&#xff1a;Apache/LAMP/Tomcat 第三方&#…

36.HarmonyOS鸿蒙系统 App(ArkUI) 创建第一个应用程序hello world

36.HarmonyOS App(ArkUI) 创建第一个应用程序helloworld 线性布局 1.鸿蒙应用程序开发app_hap开发环境搭建 3.DevEco Studio安装鸿蒙手机app本地模拟器 打开DevEco Studio,点击文件-》新建 双击打开index.ets 复制如下代码&#xff1a; import FaultLogger from ohos.fau…

【OpenGL】使用 python + Qt + OpenGL 的现代渲染

伴随资源 目录 一、说明二、 关于PyQt6.x2.1 QOpenGLWidget详细说明2.2 绘画技巧 三、PyOpenGL四、OpenGL 管线五、Python集成开发环境5.1 Emacs配置5.2 pycharm环境 六、你好&#xff0c;OpenGL&#xff01;七、QGL控件八、平截头体.svg九、定义几何9.1 立即模式与保留模式9…

如何系统的自学python?

系统地自学Python是一个循序渐进的过程&#xff0c;以下是一份详细的指南&#xff0c;帮助你从零开始逐步掌握这门语言&#xff1a; 1、了解Python及其应用场景&#xff1a; 阅读关于Python的简介&#xff0c;理解它为何流行&#xff0c;以及在哪些领域&#xff08;如Web开发…

【二叉树】Leetcode 108. 将有序数组转换为二叉搜索树【简单】

将有序数组转换为二叉搜索树 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡二叉搜索树。 示例1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null…

Linux 系统快速安装PHP环境(新手版)

Linux 系统快速安装PHP环境&#xff08;新手版&#xff09; 1、下载安装包&#xff0c;这里安装php-7.4.22.tar.gz。PHP安装包下载。 2、上传到local并解压 cd php-7.4.22 3、安装必备依赖 &#xff08;如果yum源需要更新可以运行 yum -y update&#xff09; yum -y install…

鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

随后迎来了不少互联网公司与华为鸿蒙原生应用达成了合作&#xff0c;像我们常见的阿里、京东、小红书、得物……等公司&#xff0c;还有一些银行也都与华为鸿蒙达成了合作。使得一时之间市场紧缺鸿蒙开发人才&#xff0c;不少公司不惜重金争抢人才。 据智联招聘的最新数据显示…

sadtalker学习用于风格化音频驱动单图像说话人脸动画的真实 3D 运动系数的应用

论文出处 https://arxiv.org/abs/2211.12194 使用方法 1. 打开项目的colab链接 https://colab.research.google.com/github/Winfredy/SadTalker/blob/main/quick_demo.ipynb#scrollTofAjwGmKKYl_I 在examples/source_image文件夹中添加希望动起来说话的图片&#xff0c;这…

ROS传感器图像转换

ros通过摄像头来获得图片&#xff0c;传感器数据类型为sensor_msgs中的Image&#xff0c;具体的数据类型组成&#xff1a; sensor_msgs/Image Documentationhttp://docs.ros.org/en/api/sensor_msgs/html/msg/Image.html但是我们一般使用opencv对图像进行处理&#xff0c;所以…