回顾 赋值兼容性原则:
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
1、类型识别
- 在面向对象中可能出现下面的情况
— 父类指针指向子类对象
— 父类引用直接引用子类对象,成为子类对象的别名
期望的类型是静态类型,指向的对象是动态类型。 - 静态类型 — 变量(对象)自身的类型
- 动态类型 — 指针(引用)所指向对象的实际类型
分析:这一节主要是讲
void test(Base* b)
{
/* 危险的转换方式 */
Derived* d = static_cast<Derived*>(b);
}
因为我们不知道 b 到底是指向父类对象还是子类对象,
如果 b 指向的是子类对象,那么这个强制类型转换就没有问题;
如果 b 指向的是父类对象,那么这个强制类型转换就有问题,会有隐藏的 Bug 出现。
- 解决方案 — 利用多态
1、在基类中定义虚函数返回具体的类型信息
2、所有的派生类都必须实现类型相关的虚函数
3、每个类中的类型虚函数都需要不同的实现
我们看这个程序:
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
virtual string type()
{
return "Base";
}
};
class Derived : public Base
{
public:
string type()
{
return "Derived";
}
};
class Child : public Base
{
public:
string type()
{
return "Child";
}
};
void test(Base* b)
{
//Derived *d = static_cast<Derived *>(b); //如果是 b 指向的是动态类型那么这一句才没问题
cout << b->type() << endl; //利用多态可以打印类型名
}
int main()
{
Base b;
Derived d;
Child c;
test(&b);
test(&d);
test(&c);
return 0;
}
程序没报错,并且运行正常,static_cast
用于有继承关系类对象之间的转换和类指针之间的转换,所以在上面的程序类型转换基本都能通过static_cast
转换成功。但是比如这个程序既有虚函数,又有继承,我们可以用 dynamic_cast
强制类型转换,就会发现dynamic_cast
这个类型转换的局限性,尽量用dynamic_cast
的时候,把子类指针转成父类指针,也就是说父类指针指向子类对象,这是它设计的目的。
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
virtual string type()
{
return "Base";
}
};
class Derived : public Base
{
public:
string type()
{
return "Derived";
}
};
class Child : public Base
{
public:
string type()
{
return "Child";
}
};
void test(Base* b)
{
cout << dynamic_cast<Derived *>(b) << endl; //利用多态可以打印类型名
}
int main()
{
Base b;
Derived d;
Child c;
test(&b);
test(&d);
test(&c);
return 0;
}
输出结果为 0 说明是有出错的地方,也直接说明了只有父类指针指向的子类对象可以强制类型转换成子类对象,其余的都不行。
void test(Base* b)
{
/* 危险的转换方式 */
Derived* d = static_cast<Derived*>(b);
}
为什么说这个是危险的转换方式,因为我们不知道 b 到底是指向父类对象还是子类对象,如果 b 指向的是子类对象,那么这个强制类型转换就没有问题,所以,基类(父类)指针是否可以强制类型转换为子类指针取决于动态类型。
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
virtual string type()
{
return "Base";
}
};
class Derived : public Base
{
public:
string type()
{
return "Derived";
}
};
class Child : public Base
{
public:
string type()
{
return "Child";
}
};
void test(Base* b)
{
if (b->type() == "Derived")
{
Derived *d = static_cast<Derived *>(b); //如果是 b 指向的是动态类型那么这一句才没问题
cout << d->type() << endl; //利用多态可以打印类型名
}
//又有继承,又有虚函数,可以用dynamic_cast,可以告诉我们转换是否成功,但是我们真正需要知道动态类型具体是什么还得靠static_cast。所以上面的语句我们用static_cast,而下面判断的我们用dynamic_cast。
cout << dynamic_cast<Derived *>(b) << endl;
}
int main()
{
Base b;
Derived d;
Child c;
test(&b);
test(&d);
test(&c);
return 0;
}
2、缺陷
- 多态解决方案的缺陷
— 必须从基类开始提供类型虚函数
— 所有的派生类都必须重写类型虚函数
— 每个派生类的类型名必须唯一
3、类型识别关键字
-
C++提供了 typeid 关键字用于获取类型信息:头文件
#include <typeinfo>
— typeid 关键字返回对应参数的类型信息
— typeid 返回一个 type_info 类对象
— 当 typeid 的参数为 NULL 是将抛出异常 -
typeid 关键字的使用
int i = 0;
const type_info& tiv = typeid(i);
const type_info& tii = typeid(int);
cout<< (tiv == tii) <<endl;
- typeid 的注意事项
— 当参数为类型时:返回静态类型信息
— 当参数为变量时:
1、不存在虚函数表 — 返回静态类型信息
2、存在虚函数表 — 返回动态类型信息
情况1:不存在虚函数表
#include <iostream>
using namespace std;
class Base
{
public:
string type()
{
return "Base";
}
};
class Derived : public Base
{
public:
string type()
{
return "Derived";
}
};
class Child : public Base
{
public:
string type()
{
return "Child";
}
};
void print(Base* b)
{
const type_info& tt = typeid (*b);
cout << tt.name() << endl;
}
int main()
{
Base b;
Derived d;
Child c;
print(&b);
print(&d);
print(&c);
return 0;
}
情况二、存在虚函数表
#include <iostream>
using namespace std;
class Base
{
public:
virtual string type()
{
return "Base";
}
};
class Derived : public Base
{
public:
string type()
{
return "Derived";
}
};
class Child : public Base
{
public:
string type()
{
return "Child";
}
};
void print(Base* b)
{
const type_info& tt = typeid (*b);
cout << tt.name() << endl;
}
int main()
{
Base b;
Derived d;
Child c;
print(&b);
print(&d);
print(&c);
return 0;
}
这就印证了上面的结论:
不存在虚函数表 — 返回静态类型信息
存在虚函数表 — 返回动态类型信息
小结:
- C++中有静态类型和动态类型的概念
- 利用多态能够实现对象的动态类型识别
- typeid 是专用于类型识别的关键字
- typeid 能够返回对象的动态类型信息