STL的学习代码记录

前言

本人前段时间真正学习STL的源码,学习它的代码组织形式、设计理念等等,但看源码,特别还是需要兼容平台的C++来说,这实在是令人头疼,一堆莫名其妙的宏,莫名其妙的写法,看似多余的写法,可能设计者想的比你还多。
所以在我看了几天源码后,我购买了STL源码剖析没错就是那本很老的时,但很经典,不过现在,我还没看几章,在我阅读的这几章里,我感受到了C++的神奇,同时也感受到我自身对C++的掌握能力欠缺。

关于我为什么要记录这个,原因很简单:一是记性差,二是C++太神奇了。

注意,这里并不会记录的十分详细,还很有可能存在错误,请谨慎参考。

这里我提供两个地址,一个是C++STL的源码地址,另一个是我找到了的一个可读性很高的小型STL实现项目现在好像有1.1w start。写的不错

https://github.com/gcc-mirror/gcc

https://github.com/Alinshans/MyTinySTL

  • 说明一下,本人还未看完整个STL库,以下是个人对STL库以及侯捷的书STL源码剖析继续肤浅的理解。

第一章:空间适配器(allocator)

介绍

空间适配器,简单来说,就是用来为数据分配所需的空间,比如常用的vector,当你创建一个vector<int>时,空间适配器就在默默地在背后工作,你想问我为什么要使用allocator来为每个容器来分配空间,可能你会想C语言不是可以使用malloc来对它们分配空间的吗,为什么在这些容器中,它们不用这个malloc或者说用C++的new()来开辟空间呢?希望通过本章,你能猜到这个问题的答案。

我学到了什么?

::operator new() 关键字和new()操作符的区别

很多人应该对::operator new()关键词不熟吧,但肯定见过new()这个关键字,
new()关键字的用法大概像这样MyClass* mc = newMyClass();
而::operator new()操作符大概像这样void* ptr = ::operator new(sizeof(int));
它们其实都能动态地分配空间,只不过::operator new()它可以更加灵活的内存分配方式
比如:

char buffer[sizeof(int)]; // 预留足够大小的缓冲区
int* ptr = new(buffer) int(42); // 在 buffer 中构造一个 int 对象

这种方式并不进行新的内存分配,而是在指定的地址上分配对应字节的内存,对于我们平时写的哪些程序来说,很少能用到它,通常我们用new关键字来分配空间即可
注意,我们平时重载的new运算符是::operator new(),而不是关键词new()。

在C++中,new()和delete关键字,一般分为四个过程来执行,
new() : 它分为两个过程,第一步,先分配一块合适大小的空间,也就是使用::operator new()来分配相应的空间,第二步,调用类的构造函数进行对对象的构造。
delete(): 第一步,调用类的析构函数对对象进行析构,第二步,调用::operator delete()操作符对已分配的空间进行回收。

STL里面的allocator这个模块就是对内存分配进行统一管理,也就是说所有的容器,都需要allocator来分配空间,同样地,销毁回收也是由它控制。

什么是两级配置器

那么我们分配空间是像C++那样简单地使用malloc分配对应大小的空间吗?当然没那么简单。

不直接使用malloc分配空间的理由也是很简单的,比如频繁分配空间,可能会对性能造成影响,此外,频繁地分配和释放空间,难免要产出内存碎片,导致我们实际可使用的内存空间减少,这时就需要对上述情况,做出对应的解决方案。

对于分配的大小大于128 bytes 的就正常使用malloc()和free()
对于分配的大小小于等于128 bytes的需要使用一个16个free-list,也就是链表来对对应大小的空间分配合适的区块。

但此时你可能又有个疑问,这里引入了链表,那么必然要用指针来维护它,这样看起来会是另一个负担,你考虑的的确不错,因此,区别于我们常用的结构体struct,这里STL使用联合体union来实现,这样它们共享同一块空间,就能解决刚才的顾虑了。

free-list的节点结构如下:

union obj{
	union obj* free_list_link;
	char client_data[1];
};

内存池是什么

上一节,我们说到了第二级配置器的结构,接下来,简单说说,内存池是什么。
简单来说,预先给内存池分配一块大空间,它的功能就是为了给free-list当前存有的内存,free-list从内存池里去取相应内存,以免频繁分配小空间,带来的性能损失和内存碎片。

这里我看的不是太明白就只能简单写写了,噗

第二章:迭代器(iterators)

介绍

侯捷所说,迭代器可以胶合数据容器和算法,不再使一个容器就对应一种算法模板的实现,而是多种容器使用同一个算法模板,这仅仅只需要传入对应的迭代器类型即可,复用同一个模板。而要想设计出一个好的胶合剂,往往比设计一个数据容器或算法还要困难。

迭代器是什么?它被看做是一个指针,由于它的行为表现的像指针一样,其实就是一个类实现了多种运算符,如自增,自减,->等等。

我学到了什么?

如果你有一个指针类型,那该如何推导出指针之前的类型的?

现在假设有一个这样的场景,你有一个函数,它接收一个指针类型的变量,而你的函数要做的事情是使用传进来这个指针数据类型,使用指针之前的类型来定义一个变量,比如传入了一个int*类型的数据,但此时我想要用int来定义一个变量,这该如何办?C++里面可没有判断数据类型的函数呢。

代码如下:

template <typename T>
void stl_fn(T pointer)
{
	//	我想要声明一个指针之前的类型(int)的变量该如何办?
}
int main()
{
	int a = 1;
	stl_fn(&a);
	
	return 0;
}

这里我介绍两个方法:
第一种使用利用std::remove_pointer<T>::typeT是指针类型
第二种是使用函数模板进行推导

代码如下:这里我引入了一个help_fn()它接收一个指针类型和一个指针之前的类型,这样我通过help_fn就可以实现定义一个这样的类型 U2 tmp = 2;

template <typename U1,typename U2>
void help_fn(U1 pointer,U2 value)
{
	U2 tmp = 2; // 这里U2就是int类型
	std::cout << tmp;
}
template <typename T>
void stl_fn(T pointer) // T是int*类型
{
	help_fn(pointer,*pointer);
}

迭代器是如何作为一个胶合剂的

接回上节,如果我们的函数还想要接收 指针之前类型(int)的返回值,这可咋办,现在用模板函数推导可推导不出哦,在书上提到,STL是这样解决的,它通过内嵌类型来实现这个功能,这个功能仅仅只是这个内嵌类型解决的一个小问题。

现在我们来聊聊什么是内嵌类型,比如在一个类或结构体中使用typedef或是using来对一个数据类型进行起名,这样我们就能直接使用这个新的名字了。更更重要的是,可以通过外部来用域作用符(::)来取到这个数据类型,如MyStruct<int>::value_type a = 10;这段代码就是定义了一个int类型的变量a

参考如下代码:

template<typename T>
struct MyStruct{
	using value_type = T;
	using reference = T&;
	using const_reference = const T&;
};

好了我们使用内嵌类型来实现让函数拥有指针之前的类型返回值
代码如下:

#include <iostream>
#include <vector>

template<typename T>
struct MyStruce {
	using value_type = T;
	using reference = T&;
};

template <typename T>
typename T::value_type stl_fn()
{
	using ValueType = typename T::value_type;
	ValueType num = 10;
	return num;
}

int main()
{
	std::cout << stl_fn<MyStruce<int>>();
	return 0;
}

typename T::value_type 这里我使用typename对类型说明,告诉编译器这是一个数据类型,而不是什么tm的什么错误,当然我们上面这个代码还不能很好地体现了STL中迭代器作为容器和算法的胶合剂,在这里我提供了一个详细的代码,代码功能是将一个容器的所有元素求和,并输出。

具体代码如下:

#include <iostream>
#include <vector>

template<typename T> // 模拟迭代器作为胶合剂
struct iterator_trait {
	using value_type = typename T::value_type;
	using reference = typename T::reference;
};

template <typename T> // 模拟STL中算法
typename iterator_trait<T>::value_type stl_fn(T iterBegin, T iterEnd)
{
	using ValueType = typename iterator_trait<T>::value_type;
	ValueType sum = 0;
	
	for (auto p = iterBegin; p != iterEnd; ++p) {
		sum += *p;
		std::cout << *p << std::endl;
	}
	std::cout << std::endl;
	return sum; // 返回范围内元素的总和
}

int main()
{
	std::vector<double> vec = {1.2, 2.4, 3.6}; // 这就是容器数据vector
	std::vector<double>::iterator it = vec.begin();
	
	std::cout << "总和: " << stl_fn(it, vec.end()) << std::endl;
	return 0;
}

相信这一章坚持看到这里的都是有着不错的C++基础的,如果能很快看懂的,那就能不错了,我可还是参考了gpt给的代码进行修改了的,当时写完这份代码后,我无不惊叹STL的牛逼,这设计真的巧妙。

第三章:容器(containers)

介绍

容器

我学到了什么?

xxx

第四章:算法(algorithms)

介绍

算法

我学到了什么?

xxx

第五章:仿函数(functors)

介绍

仿函数

我学到了什么?

xxx

第六章:配接器(apapters)

介绍

配接器

我学到了什么?

xxx

最后

文章会不定时更新(我也不知道什么时候)
有疑问发在评论区
也可以联系我 qq 2636427505

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值