类型转换(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=⊂
int num=(*p)(12,13);
(*(void (*)())0)();
这就是将0强转成一个函数指针,然后进行解引用以调用这个函数。
void add() {
cout << "hello" << endl;
}
int main(){
cout << add << endl;
(*(void (*)())add)();
return 0;
}
add表示函数地址,将其强转成函数指针,进行解引用便调用了该函数。