1,c++四种转换,,这四种转换都不会对强制转换的对象造成什么影响(static_cast<int>(b),假设b是float类型,那么返回值是int类型,但是b还是float类型)。其中static_cast const_cast reinterpret_cast是在编译的时候就能够进行转化了,而dynamic_cast是在运行的时候才进行转换的。
1.1 static_cast 对于内置数据类型来说,都是可以进行转换的,对于内置指针来说,可以找回丢到的原有的指针类型,比如a以前是指向int的指针变量,但是之后变成void*了,那么static_cast<int *>a。对于类来说,可以在基类和继承类中的指针或引用之间进行转化(对于对象来说转换没意义而且会报错),但是static_cast基于父类子类引用的转化是不安全的。对于没有关系的类不能够进行转换。注:static_cast还能转换c类型字符串和string类型如 const char *c="shi" ;string str=static_cast<string>(c)
1.2 dynamic_cast 会解决父类引用类型和子类引用类型之间转化的不安全问题,可以用。dynamic_cast<type-id>(expre),其中type-id必须是类的引用的类型(包括指针和引用),dynamic_cast是(运行时检测)的一种手段。
class B
{
public:
int m_iNum;
virtual void foo();
};
class D : public B
{
public:
char *m_szName[100];
};
void func(B *pb)
{
D *pd1 = static_cast(pb);
D *pd2 = dynamic_cast(pb);
}
假设当pb是指向D类对象的指针,那么这里都没有问题,但是如果pb是指向B类对象的指针,那么假如转化成功的话,会出问题的。用指针去访问m_szName就会出问题。
这里static_cast转化会成功的,但是dynamic_cast不会成功。因为dynamic_cast会去查看RTTI的信息,阻止从上到下的转换。当不成功的时候回返回一个空指针,所以我们要检查指针是否为空后才能用这个指针
struct B1{
virtual ~B1(){}
};
struct B2{
virtual ~B2(){}
};
struct D1 : B1, B2{};
int main()
{
D1 d;
B1* pb1 = &d;
B2* pb2 = dynamic_cast<B2*>(pb1);//L1
B2* pb22 = static_cast<B2*>(pb1); //L2
return 0;
}
L2将编译失败,static_cast并不允许两个完全不相干的类互相转换.
1.3 const_cast const_cast只能改变运算对象的底层const,而有底层const属性的只有引用类型(指针,引用),const_cast只能改变常量属性,不能改变表达式类型。如果对象本身是一个常量,使用强制类型转换获得写权限是合法的行为,如果对象是一个常量,在使用const_cast执行写操作就会产生未定义的后果。
看两个例子
const int a=3;
int *p=const_cast<int *>(a);
*p=4;
这就是未定义行为
const string &shorterString(const string &s1, const string &s2) 17 { 18 return (s1.size() <= s2.size()) ? s1 : s2; 19 } 20 21 // 目的:对两个非常量的string实参调用这个函数 22 string &shorterString(string &s1, string &s2) 23 { 24 // 实参强制转换为对const的引用,调用const版本函数得到对const string的引用 25 auto &r = shorterString(const_cast<const string &>(s1), 26 const_cast<const string &>(s2)); 27 // 转换为普通的string& 28 return const_cast<string&>(r); 29 }而这个例子就用的非常的好
1.4 reinterpret_cast 这个就和c语言的风格一样,什么都能强制转换,一般不用
2 内存对齐,内存在内存中的地址和偏移量本来是编译器所决定的。每个编译器有一个默认值,但是这个值如果是和cpu读取粒度不一样的话,我们需要人为修改它。
首先分析下内存对齐的原因,
cpu把内存当成是一块一块的,块的大小可以是2,4,8,16 个字节,因此CPU在读取内存的时候是一块一块进行读取的,块的大小称为(memory granularity)内存读取粒度。
我们再来看看为什么内存不对齐会影响读取速度?
假设CPU要读取一个4字节大小的数据到寄存器中(假设内存读取粒度是4),分两种情况讨论:
1.数据从0字节开始
2.数据从1字节开始
解析:当数据从0字节开始的时候,直接将0-3四个字节完全读取到寄存器,结算完成了。当数据从1字节开始的时候,问题很复杂,首先先将前4个字节读到寄存器,并再次读取4-7字节的数据进寄存器,接着把0字节,4,6,7字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器,对一个内存未对齐的寄存器进行了这么多额外操作,大大降低了CPU的性能。为了不出现这种情况,所以需要我们改变编译器的默认对齐大小,让其和cpu读取粒度一样 #progma pack(n) 宏处理是干这个的
对于结构体的内存对齐规则:
A:第一个变量放在偏移量为0的地方,之后的变量的偏移量是min(progma pack(n),这个成员自身的长度)的倍数。如果有数组,那么就是将数组成员看做若干个单位变量组成。对于有虚函数的,要将指向虚函数表的地址变量也计入其中。
B:整个结构体对齐完之后,按照min(progma pack(n),长度最长的数据成员)的倍数,进行结构体体长度进行补齐。
对于普通的内置变量啥的,就按照min(progma pack(n),这个成员自身的长度的倍数进行补齐就行了。
3 虚函数表:c++里面的多态分为静态绑定,和动态绑定,静态绑定在编译以及链接的时候,就能将调用函数根据类型以及函数名字替换为相应的函数入口地址(函数的代码都是放在代码段,虚函数的代码也是放在代码段)。对于动态绑定,是通过引用类型(指针和引用)去进行虚函数调用时候,编译时候不能确定指针指向的对象是基类还是派生类(赋值是运行的时候才进行的操作),从而不能确定调用是基类还是派生类中的虚函数(子类改写父类的虚函数,函数签名是一样的,并且指针既可以指向基类也可以指向继承类,所以编译 的时候才不确定)。当运行的时候根据RTTI的信息,从而可以确定指向的对象,并且根据虚函数表,进行正确调用。
#include <iostream>
using namespace std;
class Base {
public:
virtual void f() {cout<<"base::f"<<endl;}
virtual void g() {cout<<"base::g"<<endl;}
virtual void h() {cout<<"base::h"<<endl;}
};
class Derive : public Base{
public:
void g() {cout<<"derive::g"<<endl;}
};
int main () {
cout<<"size of Base: "<<sizeof(Base)<<endl;
typedef void(*Func)(void);
Base b;
Base *d = new Derive();
long* pvptr = (long*)d;
long* vptr = (long*)*pvptr;
Func f = (Func)vptr[0];
Func g = (Func)vptr[1];
Func h = (Func)vptr[2];
f();
g();
h();
return 0;
}
虚函数表,当一个类中含有虚函数的时候,数据成员中在开始的地方会存放一个指针,这个指针指向一张表,这个表中,依次存放类中各个虚函数的地址。继承类继承了基类后,也继承了这一张表(这个指向虚函数表的指针应该是静态数据类型)。当继承类改写了基类中的虚函数后,继承类中的虚函数表中对象的函数地址,就改成继承类改写的函数的入口地址,如果是新加的,依次放到函数表后面。多继承,会继承多个父类的指向虚函数表的指针。当用基类指针或者引用调用虚函数的时候,会根据实际指向的类型找到虚函数表,再根据表中的函数指针,从而调用正确的对象
Base中虚函数表结构:
多继承