类型转换-----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),所以底层不是真正的连续空间,而是假想的连续空间。

猜你喜欢

转载自blog.csdn.net/xuruhua/article/details/81291606