1、C++有哪四个类型转换的关键字?各有什么特点?分别在什么场景下使用?
**c语言的强制类型转换:**可以对基本数据类型进行类型转换,窄数据类型向宽数据类型转换是安全的,宽数据类型向窄数据类型转换是不安全的,可能损失精度。格式是如下:
TYPE1 a=(TYPE2) b;
**static_cast:**除了可以实现C语言强制数据类型转换相同的基本数据类型的转换外,还可以实现对象类型的转换,其中转换的两个类型必须是继承关系,上行转换(子类向基类转化)是安全的,下行转换时不安全的。static_cast不能修改原变量的const,volatile属性。格式如下:
TYPE1 a=static_cast<TYPE2>(b);
**const_cast:**用于修改变量的const和volatile属性,常量指针被转化成非常量的指针,并且仍然指向原来的对象;常量引用被转换成非常量的引用,并且仍然指向原来的对象;const_cast一般用于修改底指针,如const char *p形式。格式如下:
const TYPE b;
TYPE *a=const_cast<TYPE*>(&b);
TYPE &c=const_cast<TYPE&>(b);//如果是类类型,对a和c的改变就是对b的改变,如果是基本数据
//类型,对a和c的改变不影响b的值(亲测是这样)。
//一个实例:
#include <iostream>
class A{
public:
A(int t) :i(t){};
int i;
};
int main(){
const A a(10);
A *b = const_cast<A*>(&a);
A &c = const_cast<A&>(a);
//a.a = 100; //不能进行修改,因为A是const
std::cout << a.i << std::endl;
b->i = 11; //同时也改变了a的成员
std::cout << a.i <<" "<<b->i<< std::endl;
c.i = 12;
std::cout << a.i <<" "<<c.i<< std::endl;
}
//输出为:
//10
//11 11
//12 12
**dynamic_cast:**在子类和父类之间转换,可以在继承关系、兄弟关系或对象与void*之间进行转换,上行转换(子类向基类转化)是安全的,这个与static_cast一样,下行转换static_cast是不安全的,但是dynamic_cast实在运行时进行动态转换,同时会进行安全检查,下行转换将返回一个空指针。dynamic_cast还支持交叉转换,即在兄弟类型间进行转换,转换个结果为空指针。值得注意的是,dynamic_cast转换的父类必须含有虚函数,因为运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中。格式如下:
//上下行转换
classB
{
public:
int m_iNum;
virtual void foo();
};
classD:public B
{
public:
char* m_szName[100];
};
void func(B* pb,D* ptr)
{
//上行转换是安全的
D* pd1=static_cast<D*>(pb);
D* pd2=dynamic_cast<D*>(pb);
//下行转换static_cast是不安全的,dynamic_cast是相对安全的
B* ptr1=static_cast<B*>(ptr); //不安全
B* ptr2=dynamic_cast<B*>(ptr); //null pointer
}
//交叉转换
classA
{
public:
intm_iNum;
virtual void f(){}
};
class B:public A
{
};
class D:public A
{
};
void foo()
{
B*pb=newB;
pb->m_iNum=100;
//D*pd1=static_cast<D*>(pb);//compile error
D*pd2=dynamic_cast<D*>(pb);//pd2 is NULL
delete pb;
}
**reinterpret_cast:**可以在指针、引用、算术类型、函数指针或者成员指针和整数间进行相互转换,它只是将底层的比特模型重新解释成目标类型,不会对比特进行改变,所以是不安全的,只是为了故弄玄虚和其他目的。
class A {
public:
int m_a;
};
class B {
public:
int m_b;
};
class C : public A, public B {};
//那么对于以下代码:
C c;
printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));
//前两个的输出值是相同的,最后一个则会在原基础上偏移4个字节,这是因为static_cast计算了父子
//类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),
//而reinterpret_cast却不会做这一层转换。
2、关于类类型的sizeof
① 问:定义一个空类型,没有成员变量和成员函数,它的sizeof是多少?
struct A{};
std::cout<<sizeof(A);
答:1,空类型的实例中不包含任何信息,本来求sizeof应该是0,但是当我们声明该类型的实例的时候,它必须在内存中占有一定空间,否则无法使用这些实例,至于占用多少内存,由编译器决定,Visual Studio每个空类型的实例占一个字节空间。
② 问:如果在类型中添加一个构造函数和一个析构函数,它的sizeof是多少?
struct A{
public:
A(){}
~A(){}
};
std::cout<<sizeof(A);
答:和前面一样,是1,调用构造函数和析构函数只需要知道函数的地址即可,而这些函数的地址只与类型相关,而与类型的实例无关,编译器也不会因为这两个函数而在实例内添加任何额外信息。
③ 问:如果把析构函数标记为虚函数呢?
struct A{
public:
A(){}
virtual ~A(){}
};
std::cout<<sizeof(A);
答:是一个指针的大小(32位系统上是4,64位系统上是8)。C++编译器发现一个类型中有虚函数,就会为该类型生成虚表,并在该类型的每一个实例中添加一个指向虚表的指针(**注意:**只有一个指针,而不是每个虚函数一个指针,因为虚表是一个数组,只需要指向数组首元素,就可以遍历所有元素;**补充:**虚函数是为了实现多态而出现的,而虚函数的实现在C++中没有定义,实现方式由编译器决定,一般都是用虚表实现的,虚表存放的位置可参考vicness的专栏)。