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