STL

STL:C++标准模板库

A、HP版本------SGI(所有的SGI版本都是继承自HP版本

1、常用的数据结构的封装

2、通用的算法:模板(让处理的数据与类型无关)+仿函数(函数指针)

B、六大组件:

容器:

a、线性结构(序列式容器)

string:管理字符串(字符串类型的动态顺序表)

array:静态顺序表(C++11)

vector:动态顺序表

list:带头节点的双向循环链表

foward_list:带头节点的循环单链表

deque:双向队列,动态二位数组结构

b、特殊的容器(适配器):只是把双向队列的接口进行了封装形成了一种新的结构。

stack:用迭代器把栈中的内容从前到后打印(栈的这种数据结构是一种后进先出的线性结构,只是在栈顶进行操作,操作的只是尾端,所以这里的遍历一遍是错误的。这个问题就是一个有问题的问题)

queue:

priority_queue:优先级队列(堆)把堆算法重新封装,元素放到vector中,空间也就没有浪费。

c、关联式容器:容器中存放的是一个一个的键值对<key,value>是一一对应的关系。

有序序列:搜索树结构

二叉搜索树:1、概念(一棵二叉搜索树可能为空;根节点比左子树中任何节点都大,比右子树中任何节点都小,并且他们的左右子树都是二叉搜索树二叉搜索树最左边的节点是最小的,最右边的节点是最大的,通过中序遍历可以得到一个有序序列(删除*)(关联式容器没有使用二叉搜索树,就是因为二叉搜索树的性能比较差,虽然这种树的原理简单,但是入果给定的序列是一个有序序列,就是一棵单只树,单只树中查找某个元素时间复杂度是O(n),所以不使用二叉搜索树。

效率高:哈希桶结构

 

 

STL的应用

类型转化:

(1)隐式类型转化:对于赋值操作符,左操作数和右操作数类型相同就会赋值,若是类型不同就会进行隐式类型转化。不能转化就会报错。

# include<iostream>
using namespace std;

int main()
{
	int a = 20;
	char c = '0';
	c = a;
	return 0;
}

引用变量和引用实体的类型必须相同,若是不同,也需要进行引用,则在前面加上const:相当于编译器此时创建了一个临时变量,引用的就是这个临时变量。

int main()
{
	 
	double d = 12.33;
	const int& rd = d;
	return 0;
}

按照某一种次序重新解析空间:

int main()
{ 
	double d = 12.33;
	const int& rd = d;
	int *pd = (int*)&d;
	return 0;
}

(2)显式类型转化:把要转化的类型前面加上要转化的类型

二、c++中的类型转化:不同的场景有不同的转换方式,把不同的转换方式区分开。

1、static_cast:用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不可用于两个不相关的类型进行转换。

(1)以下方式会出现类型的丢失,把整型的高字节丢掉

int main()
{ 
	int a = 10;
	char c = '0';
	c = a;
	//会出现类型的丢失,把整型的高字节丢掉
	return 0;
}

(2)采用static_cast后:

int main()
{ 
	int a = 10;
	char c = '0';
	c = static_cast<char>(a);
 	return 0;
}

(3)static_cast不可用于显式类型的转化,以下代码会出现错误:

int main()
{ 
	double d = 34.55;
	int *pd = static_cast<int*>(&d);
 	return 0;
}

2、reinterpret_cast:

(1)可用用于显示类型的转化:被转换的类型是完全不相关的两个类型(把不想管的两个类型从一个类型转换为另一个类型)

int main()
{ 
	double d = 34.55;
	int *pd = reinterpret_cast<int*>(&d);
 	return 0;
}

(2)用于将一个种类型转换为另一种不同的类型

int DoDomething(int a)
{
	cout << "DoDomething()" << endl;
	return;
}
typedef void(*FUNC)();
int main()
{ 
	FUNC f = (FUNC)(DoDomething);
	double d = 34.55;
	int *pd = reinterpret_cast<int*>(&d);
 	return 0;
}

也可以使用reinterpret_cast:

FUNC f = reinterpret_cast<FUNC>(DoDomething);

3、const_cast:删除变量的const属性,方便赋值

int main()
{
	const int a = 33;
	//int *p = a;//会发生错误
	int *p = const_cast<int*>(&a);
	return 0;
}

4、dynamic_cast:用于将一个父类对象的指针转化为子类对象的指针或者引用(动态转换):会先检查是否能转化成功,能成功则转换,不能则返回0

class A
{
public:
	virtual void f()
	{}
};
class B :public A
{
public:
	virtual void f()//重写A中的虚函数
	{}
};
void fun(A* pa)
{
	//dynamic_cast会先检查是否能转换成功,能成功则转换
	B* pb1 = static_cast<B*>(pa);
	B* pb2 = dynamic_cast<B*>(pa);
	cout << "pb1:" << pb1 << endl;
	cout << "pb2:" << pb1 << endl;
}
int main()
{
	A a;
	B b;
	fun(&a);
	fun(&b); 
	return 0;
}

一般面试会问到:这四种类型分别是什么类型;这四种类型转换所用到的场景分别是什么场景。

应用场景:构造函数【构造对象,进行隐式类型转换】:构造函数发生类型转换的前提就是构造函数体里面一定是单参的。单参构造函数具有类型转换的作用。

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
	int _b;
};
int main()
{
	A a(10);
	a = 22;
	return 0;
}

但是这种方法容易造成歧义,加上关键字explicit:对于单参构造函数禁止这种类型的转换。

总结:STL:c++标准模板库:

封装应遵循的两个特性:通用(把库里的代码写成模板,使通用性更高,和数据类型没有关系),效率高

A、将常用的数据结构封装

B、常用算法的封装:模板类型(可以处理不同类型的数据,但不是通用)----》数据和类型无关

                                     ------》仿函数(函数对象)

C、STL的六大组件:

(1)容器:数据的容器,底层就是对于常见数据结构(线性:链表(list:底层使用带头节点的双向循环(为了使头插变简单、为了实现迭代器)链表【八种结构,不带头节点的单链表是最主要的,不适用因为遍历时只能从前朝后走,若要从尾向前走则不可行,不带头节点,进行插入也不方便】;带头节点循环单链表:slist在c++11中引入)、顺序表、特殊的线性结构(vector:动态顺序表;array:静态顺序表,在c++11阶段加入),二叉树结构,哈希)的封装。双向队列:deque底层是一段假想的连续空间。string:特殊的线性序列(这种线性结构放到元素类型已经具体化了,只能管理字符)。

vector可以实例化为任何类型,也可以实例化为字符类型,那么为什么要独立的把字符类型给出来?【vector可以看成是字符数组,string既可以看成是字符数组,又可以看成是字符串。一般情况下把string都称作字符串,字符串和字符串组不是同一个东西。字符串有特定的结尾标志,并且有自己特定的操作函数,vector可以实例化为各种类型,string的操作都是针对string类的,因此把string类的内容单独列出来了。

两种特殊的线性结构:容器的适配器:把双向队列的接口进行重新封装,从而形成新的容器

                                   stack:(一种适配器而不是容器)【栈在底层使用vector结构,后进先出,pushback,popback

                                   queue:用list进行封装, 

                                   priority_queue:把堆算法进行了重新封装(底层搭建的是堆 算法,数据放到vector容器中)

适配器:把另外一种结构里的方法进行重新包装成为一种新的方法,把这种结构称为适配器。

二、list:底层是一个带头节点的双向从循环链表,因为有的情况下需要朝前遍历,有的情况下需要朝后遍历容器、获取容器里面的元素,带头节点的原因:使尾插变得简单。

list迭代器,迭代器类似于一个元素的指针:list底层是一个链表的结构,链表的结构不能取指针的++,所以需要将该元素的值进行封装。指针里面有那些操作,此时我们需要实现哪些操作:++,--,*,->等操作。既可以从前往后走,也可以从后往前走。

链表不支持随机访问的操作,对于链表的遍历只能通过迭代器进行遍历。删除只会导致某一个节点失效;但是在vector中一个节点失效会导致所有的节点失效。

进行排序时,调用sort默认使用小于的方式进行比较“void sort()";但是采用”template<class Compare>       void sort(Compare comp)"的这种方式,需要给出排序的方式,不是直接使用小于的排序方式,此时若要使用小于的方式排序,需要对操作符进行重载。

三、双向队列:deque:既可以在头部进行插入和删除,也可以在尾部进行插入和删除,要求时间复杂度都为O(1)。连续空间在头部进行插入和删除,需要搬移元素,保证时间复杂度为O(1),所以底层不是真正的连续空间,而是假想的连续空间。

posted on 2018-08-23 14:44  众里寻他2018  阅读(80)  评论(0)    收藏  举报

导航