c++四种转换以及内存对齐和虚函数

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;
}

上述定义中可以看到,B1和B2是不相关的类,从L1可以看到,dynamic_cast允许这种转换:只要B1存在多态方法.
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中虚函数表结构:

多继承



 









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值