类型转换(1)

学会使用私有继承

在类的分层层次中,C++将公有继承视为”是一个”的关系,Student类由Person类继承而来,为了使某个函数成功调用,编译器有必要时将Student类隐式的转换成Person类,现在将公有继承换成私有继承。
我们看下面一段代码

class Person {
public:
	void func() {};
};
class Student :private Person {
public:
	//void func() {};
	void show() {
		func();
	}
};

void date(Person* per) {};
void date1(Student* stu) {};

int main() {
    Person p1;
    Student p2;
    date(&p1);
    date1(&p2);
    date1(&p1);
    p2.show();
}

很显然我们使用date1函数使用p1对象时,会报错,因为编译器认为私有继承不是“是一个”的关系。我们可以看到给两个类中均存在func函数,而不能直接用Student对象来调用Student中的func函数,但是可以使用一个函数来调用Student中的函数。私有继承可以直接调用函数来使用基类的公有函数和保护函数。而在派生类中创建一个基类对象不能调用其保护函数。

C++四种类型转化

C中的类型转换

我们看下面一段代码

int main() {
    int a = 10;
    char ch = 'a';
    a = (int)ch;
    ch = (char)a;
    cout << "a:" << a << endl;
    cout << "ch:" << ch << endl;
    a=0x61626364;
    int* ip = &a;
    char* cp = &ch;
    cp=(char*)ip;
    cout << *cp << endl;
    return 0;
}

可以发现c语言中的类型转换就是强行转换,不管双方是什么类型,都可以进行转换,很明显的问题就是转换不明确,你只能知道他转换了却不知道为什么转换。
我们再观察下面一段代码

class Circle {
public:
    float radius;
public:
    Circle(float r = 0) :radius(r) {}
};
class Square {
public:
    int length;
public:
    Square() {}
};
int main() {
    Circle c1(12);
    Circle* cp = &c1;
    Square* sp = (Square*)cp;
    cout << sp->length << endl;
    return 0;
}

将Circle的指针强转成Square类型的指针,输出结果是什么,首先两个类的大小是相同的但是Circle中radius是float类型,Square中length是int类型,所以呢其解析方式不一样,将int类型的12解析成float类型,输出结果为1094713344。

static_cast

用法:static_cast<type_name>(expression)
该运算符把expression转换为type_name类型,static_cast在编译时使用类型信息执行转换,在转换执行必要的检测(指针越界计算,类型检查等),但没运行时类型检查来保证转换的安全性。

基于基本数据类型的转换,不能用于指针转换

可用于替代c中的基本数值类型转换,不能用于指针转化。

int main() {
    int a = 10;
    char ch = 'a';
    a = static_cast<int> (ch);
    ch = static_cast<char> (a);
    cout << "a:" << a << "ch:" << ch << endl;

    int* ip = &a;
    char* cp = &ch;
    cp = (char*)ip;
   // cp = static_cast<char*>(ip);
    return 0;
}

通过上面代码我们也就可以发现静态转换不能转换指针类型。

枚举转换

enum day
{
    a1 = 0,
    a2 = 1,
    a3 = 2,
    a4 = 3,
    a5 = 4,
    a6 = 5,
    a7 = 6
};

int main() {
    day num=a3;
    int x = 5;
    float q = 12.23;
    num = static_cast<day>(x);
    int num1 = static_cast<day>(q);
    cout << num1 << endl;
    return 0;
}

void*转换

int main() {
    int a = 10;
    int* ip = nullptr;
    double* dp = nullptr;
    void* vp = &a;
    ip = static_cast<int*>(vp);
    dp = static_cast<double*>(vp);
    return 0;
}

弃值表达式

int main() {
	int a=10,b=20;
	c=a+b;
	c=static_cast<void>(a+b);//error,弃值
}

左值,右值

我们大致可以这么理解,可以取地址的值为左值,不可以取地址的值为右值。纯右值不可以取地址也不能改变。将亡值具名就是左值,不具名就是右值。

int func() {
	int i=10;
	return i; 
}
int main() {
	int a=10;
	int b=20;//左值
	//&10,&20,右值
	int c=a+b;//此处会产生一个临时的a+b值,也就是纯右值。
	//a+b=c//err
	int tmp=func();//&func(),err,此处为将亡值,也是右值的一种。
}

右值引用,左值引用

int main() {
    int a = 10;
    const int c = 20;
    int& ap = a;
    const int& cp = c;
    
    int&& rc = 10;//右值引用
    //int tmp=10;
    //int& rc=tmp;
    rc += 10;
    int&& rd = rc;//err,rc具名之后便成了右值,
    const int& c = 20;
    //int tmp=20;
    //const int& c=tmp;
    tmp += 20;//err
    return 0;
}
void func(int&& a) {};
void func(int& b) {}
void func(const int& c) {}//万能引用
int main() {
    int x = 10;
    func(10);
    func(x);
    const int y = 20;
    func(y);
}

我们先看func(10)函数,首先默认调用右值引用,如果屏蔽了右值引用函数,则会调用常引用(万能引用)。
再看func(x),首先默认调用左值引用,如果屏蔽掉就会调用万能引用。
最后看func(y),此处只能调用万能引用,因为其值不能修改也不是右值。

右值转换

上面讲了这么多右值左值的概念,就是为了体现此处静态转换可以实现右值转换。

int main() {
    int a = 10;
    int& x = a;
    int&& cx = a;//err
    int&& cx = static_cast<int&&>(a);
    int& cy = static_cast<int&>(10);//err
    int& cz = static_cast<int&>(cx);
}

我们不能将纯右值转换成左值,因为纯右值不能改变。

move转换与static转换

class Int
{
private:
    int* pval;
public:
    Int(int x = 0) :pval(new int(x)) {}
    Int(const Int& it)
    {
        pval = new int(*it.pval);
    }
    Int(Int&& it) :pval(it.pval)
    {
        it.pval = nullptr;
    }
    Int& operator=(const Int& it)
    {
        if (this != &it)
        {
            delete []pval;
            pval = new int(*it.pval);
        }
        return *this;
    }
    Int& operator=(Int&& it)
    {
        if (this != &it)
        {
            pval = it.pval;
            it.pval = nullptr;
        }
        return*this;
    }
    ~Int()
    {
        delete pval;
        pval = nullptr;
    }
    void Set(int x) {
        *pval = x;
    }
};

执行下面代码:

int main() {
    Int a(10);
    const Int a1(20);
    
    Int&& rc1 = (Int&&)b;
    a1.Set();//error
    rc1.Set(100);
    static_cast<Int&&>(a).Set(200);
    Int&& rc2 = static_cast<Int&&>(a);
    Int&& rc3 = move(a);
    Int&& rc4 = static_cast<Int&&>(a1);//err
    Int&& rc5 = move(a);//err
}

我们可以发现在使用三种转换将左值转换成右值时,三者的地址相同,但是在将常对象转换时就会出现错误,也就说明static-cast,move这两种转换某种意义上来说是安全的,因为常性不可以进行改变。我们也可以发现在使用a1对象调用set函数时,“a1.”不会出现set函数,并且是错误的。但是可以将rc1.调用出set函数,而改变了其const特性,导致了不安全。

int main() {
    Int a(10),b(20),c(30),d(40);

    Int ra(a);
    Int rb((Int&&)b);
    Int rc(static_cast<Int&&>(c));
    Int rd(move(d));
    return 0;
}

执行上面代码我们会发现rb,rc,rd对象的都会调用移动构造,从而导致bcd对象的资源会移动到rb,rc,rd中。但是三者的区别在这

int main() {
    const Int a(10),b(20),c(30),d(40);
    Int ra(a);
    Int rb((Int&&)b);
    Int rc(static_cast<Int&&>(c));//err const_cast
    Int rd(move(d));
    return 0;
}

执行上面代码我们就会发现静态转化不能把常性的转换成右值。而其他两种可以。三者的区别:
rb强转呢,纯粹的强制转换,直接将b的资源移动到rb中。
rc静态转换呢不能把常性的转换,用const_cast转换去常性即可。
rd移动move转换呢,他可以识别常性,其内部是叠加的,常性左值叠加右值仍然是一个常性右值引用,但是没有处理常性右值引用的函数,所以调用的是常性左值引用。

继承层次中的指针与引用转换

class Object {
    int value;
public:
    Object(int x = 0) :value(x) {}
    virtual void func() { cout << "obj func" << endl; }
};
class Base :public Object {
    int num;
public:
    Base(int x = 0) :num(x), Object(x + 10) {}
    void show() { cout << num << endl; }
    void func() { cout << "base func" << endl; }
};
int main() {
    Object obj(10);
    Base base(10);
    Object* op = &obj;
    Base* bp = &base;
    op = static_cast<Object*>(&base);
    bp->show();
    bp = static_cast<Base*>(&obj);
    bp->func();
    op->func();
}

上行转换是安全的,下行转换是不安全的。其没有动态检查,所以在将obj指针转换成base指针后调用的仍然是obj的虚表。

const_cast

用法:const_cast<type_name>(expression)
用于修改只读属性。

int main() {
    const int a = 10;
    const int& b = a;
    const int* ip = &a;
    int* p = const_cast<int*>(ip);
    int& q = const_cast<int&>(b);
    int&& cp = const_cast<int&&>(a);
    return 0;
}

自定义类:

class Int {
    int value;
public:
    Int(int x = 0) :value(x) {}
    void Set(int x) { value = x; }
    int Get()const { return value; }
};

int main() {
    const Int a(10);
    cout << a.Get() << endl;
    Int* ip = const_cast<Int*>(&a);
    Int& b = const_cast<Int&>(a);
    ip->Set(100);
    cout << a.Get() << endl;
    b.Set(200);
    cout << a.Get() << endl;
    
    return 0;
}

reinterpret_cast

用法:reinpreter_cast <type_name>(expression)
type_name必须是一个指针,引用,算法类型,函数指针或者成员指针。他可以把一个指针转换成一个整数,也可以把一个证书转换成一个指针,类似于c语言强转。用于指针类型间的强制转换,整数与指针间的强制转换。

int main() {
    int a = 0x61626364;
    char* cp = reinterpret_cast<char*>(&a);
    cout << *cp << endl;
    cp += 1;
    cout << *cp << endl;
    cp+= 1;
    cout << *cp << endl;
    cp += 1;
    cout << *cp << endl;
}

有这么一道题(*(void (*)())0)();说明其表达的是什么意思
首先呢我们要知道函数指针。

int a=10,b=20;
int* ip=&a;
ip=&b;

这是很正常的,一个整型指针可以指向整型变量的地址

int add(int a,int b);
int sub(int a,int b);
int(*p)(int ,int);//函数指针
p=&add;
p=&sub;
int num=(*p)(12,13);
(*(void (*)())0)();

这就是将0强转成一个函数指针,然后进行解引用以调用这个函数。

void add() {
    cout << "hello" << endl;
}
int main(){
    cout << add << endl;
    (*(void (*)())add)();
    return 0;
}

add表示函数地址,将其强转成函数指针,进行解引用便调用了该函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*闲鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值