Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別

Slides:



Advertisements
Similar presentations
第一章 C语言概述 计算机公共教学部.
Advertisements

程設一.
第4章 数组 数组是由一定数目的同类元素顺序排列而成的结构类型数据 一个数组在内存占有一片连续的存储区域 数组名是存储空间的首地址
類別與物件 Class & Object.
第15章 繼承與多重繼承 15-1 繼承的基礎 15-2 覆寫與隱藏父類別的成員 15-3 子類別的建構與解構子 15-4 多重繼承
第7单元 面向过程编程—— 继承与多态.
第八章 类和对象.
C++程序设计 王希 图书馆三楼办公室.
鄭士康 國立台灣大學 電機工程學系/電信工程研究所/ 資訊網路與多媒體研究所
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
走向C++之路 WindyWinter WindyWinter感谢诸位前来捧场。
struct 可以在同一個名稱下擁有多種資料型態。使用struct能讓資料的存取和處理更為靈活。
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
Scope & Lifetime 前言 Local Scope Global Functions & Objects
第六章 类的扩展与继承.
程式敘述執行順序的轉移 控制與重複、方法 Lecturer:曾學文.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例.
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
第六章 继承性和派生类 胡昊 南京大学计算机系软件所.
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
Object-Oriented Programming:
西安交通大学 计算机教学实验中心 大学C++程序设计教程 西安交通大学 计算机教学实验中心
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Java软件设计基础 5. 继承与多态.
Object-Oriented Programming in C++ 第一章 C++的初步知识
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
Java程序设计 第9章 继承和多态.
Object-Oriented Programming: Polymorphism
面向对象程序设计 QQ群: Object-Oriented Programming 汽车学院.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
切換Dev c++顯示語言 工具->環境選項(V)->介面->language (Chinese TW)
第16章 虛擬與多形 16-1 虛擬函數 16-2 純虛擬函數與抽象類別 16-3 多形 16-4 虛擬繼承與虛擬解構子.
10 多載函數 10.1 多載概論 多載一般函數 多載成員函數 10-3
第三章 C# 基础知识.
第7章 繼承/多型/介面 注意: 本投影片僅供本書上課教師使用,非經同意請勿上網轉載或供拷貝.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
Classes (2) Lecture 7.
第三章 链表 单链表 循环链表 多项式及其相加 双向链表 稀疏矩阵.
C++大学基础教程 第11章 多态性 北京科技大学 信息基础科学系 2019/4/8 北京科技大学.
潘爱民 C++ Overview 潘爱民
C#程序设计基础 $3 成员、变量和常量.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第三章 控制语句 第十一组 C++语言程序设计.
第二章 Java基本语法 讲师:复凡.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
Inheritance -II.
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第二章 Java语法基础.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
面向对象技术 练习 ffh.
C++程序设计 吉林大学计算机科学与技术(软件)学院.
第九章 物件導向-進階.
第1章 C++面向对象程序设计要点 1.1 函数和函数参数 1.2 输入输出   1.3 类 1.4 抽象类型和模板.
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第二章 基本数据类型与表达式 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本章主題 C++的程式結構 資料型態與宣告 算術運算 簡易的輸入輸出指令 程式編譯(Compile)的過程與原理.
第2章 Java语言基础.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
變數與資料型態  綠園.
資料結構與C++程式設計進階 C++與資料結構 講師:林業峻 CSIE, NTU 7/ 5, 2010.
Presentation transcript:

Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別 物件導向程式語言講義 2018/11/19 Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別 virtual 成員函式 RTTI (Run-time Type Information) 靜宜大學資訊管理學系 蔡奇偉 副教授

前言 C++ 提供類別繼承的機制來擴充或更改現有類別的功能。我們可以利用此機制來達到以下兩個目的: 程式碼的再利用(code reuse) 物件導向的設計(object-oriented design)

衍生類別的定義 假定 B 是一個類別。我們可以用底下的格式來定義一個 B 的衍生類別 D: class D : public B { // members of D }; 我們稱:D 繼承 B、B 是 D 的 base class(基底類別)或 superclass(父類別)、以及 D 是 B 的 derived class(衍生類別)或 subclass(子類別)。 class B class D 類別繼承的圖示

類別繼承通常是用來表達 kind-of 關係(或稱 is-a 關係):衍生類別是基底類別的一種。譬如: Manager(經理)也是 Employee(員工),所以我們先規劃好 Employee 類別,然後把 Manager 定義成 Employee 的一個衍生類別: class Employee { // other members private: string first_name, family_name; char middle_initial; Date hiring_date; short department; }; class Manager : public Employee { // other members private: set<Employee *> group; short level; };

衍生類別除了本身的資料成員以外,也具有基底類別的資料成員。譬如: Employee 的資料成員: string first_name, family_name; char middle_initial; Date hiring_date; short department; Manager 的資料成員: string first_name, family_name; char middle_initial; Date hiring_date; short department; set<Employee *> group; short level;

由於衍生類別是基底類別的子類別,因此衍生類別的物件可視為基底類別的物件;基底類別型態的指標也可用於衍生類別的物件,而不須要經過型態轉換。但反過來就不成立了。譬如: void foo (Manager mm, Employee ee) { Employee *pe = &mm; // ok Manager *pm = &ee; // error pe->level = 2; // error: Employee doesn‘t have // data member:level pm = static_cast<Manager *> (pe); // ok: explicit type casting pm->level = 2; // ok: since pe points to a // Manager object }

衍生類別及其朋友(friends)可以直接使用基底類別的 public 或 protected 成員,但是不能使用基底類別的 private 成員。譬如: class D : public B { friend void k(D); void a() { f(); … } // ok void b() { g(); … } // ok void c() { _x = 0; } // ok void d() { h(); … } // error void e() { _y = 0; } // error }; void k (D obj) { obj.g(); // ok obj.f(); // ok obj._x = 0; // ok obj._y = 0; // error } class B { public: void f(); protected: void g(); int _x; private: void h(); int _y; };

存取控制 我們用下表來總結類別成員的存取控制: 本身與其朋友 衍生類別與其朋友 外界 public protected private x

如果基底類別的成員函式不符合所需的話,我們可以在衍生類別中重新改寫(override)。譬如: class Employee { public: void print(); // other members }; void Employee::print() { cout << first_name << ‘ ’ << middle_initial << ‘ ’ << family_name; } class Manager : public Employee { public: void print(); // other members }; void Manager ::print() { Employee::print(); cout << ‘ ’ << level; } 這項改寫的機制讓 code reuse 可以很容易地達成。

衍生類別的建構函式必須呼叫基底類別的建構函式(如果它存在的話),而且前者的參數必須包含後者的參數。譬如: class Employee { public: Employee (const string & n, int d) : family_name(n), department(d) { } // other members }; class Manager : public Employee { Manager (const string & n, int d, int lvl) : Employee(n, d), level(lvl) { }

衍生類別的物件建構順序如下: 1. 執行基底類別的建構函式。 2. 執行衍生類別資料成員的建構函式。 3. 執行衍生類別的建構函式。 衍生類別的物件解構順序則恰恰相反,即: 1. 執行衍生類別的解構函式。 2. 執行衍生類別資料成員的解構函式。 3. 執行基底類別的解構函式。 資料成員和基底類別是按照宣告的順序來建構,解構則按照相反的順序。

拷貝衍生類別物件至基底類別物件時,只拷貝基底類別的資料成員。 class Employee { Employee (const Employee &); Employee& operator=(const Employee &); // … }; void f (const Manager &m) { Employee e = m; // construct e from Employee part of m e = m; // assign Employee part of m to e }

我們利用 CPoint2D 類別來定義 CPoint3D 類別。首先我們把 CPoint2D 的 private 成員改成 protected 成員,讓 CPoint3D 可以使用它們。 範 例 class CPoint2D { public: CPoint2D (int x = 0, int y = 0) : _x(x), _y(y) { } int x () { return _x; } int y () { return _y; } void setX (int x) { _x = x; } void setY (int y) { _y = y; } void set (int x, int y) { _x = x; _y = y; } bool isZero () { return _x == 0 && _y == 0; } double distance () { return sqrt(_x * _x + _y * _y); } protected: int _x, _y; };

CPoint3D 改寫(override) CPoint2D 的 isZero() 和 distance() 函式。 class CPoint3D : public CPoint2D { public: CPoint3D (int x = 0, int y = 0, int _z = 0) : CPoint2D(x, y), _z(z) { } int z() { return _z; } void setZ (int z) { _z = x; } void set (int x, int y, int z) { set(x, y); setZ(z); } bool isZero () { return _x == 0 && _y == 0 && _z == 0; } double distance () { return sqrt(_x * _x + _y * _y + _z * _z); } private: int _z; }; CPoint3D 改寫(override) CPoint2D 的 isZero() 和 distance() 函式。

我們可以為 CPoint3D 類別定義輸出運算子 << 如下: #include <iostream> using namespace std; #include “CPoint3D.h” ostream& operator<< (ostream &os, CPoint3D p) { os << ‘(‘ << p.x() << “, “ << p.y() << “, “ << p.z() << ‘)’; return os; }

測試程式 #include “CPoint3D.h” int main () { CPoint3D p(1, 2, 3); cout << p << endl; CPoint2D q(5, 6); q = p; cout << q << endl; p = q; // error } 輸出結果 (1, 2, 3) (1, 2)

我們利用標準的 string 類別來定義 big5string 類別。 big5string 類別提供了一些專門用來處理中文字串的成員函式。註:中文 Big5 碼中的中文字元是雙位元組,其中高位元組的範圍是 0x81  0xFE、低位元組的範圍是 0x40  0x7E 與 0xA1  0xFE。 範 例 typedef unsigned short big5char; class big5string : public string { public: bool is_big5char (int idx); big5char next_char(int idx); int big5_length(); private: bool in (char c, int min, int max) { return min <= c && c <= max; } bool is_big5hiByte (char c) { return in(c, 0x81, 0xFE); } bool is_big5loByte (char c) { return in(c, 0x40, 0x7E) || in(c, 0xA1, 0xFE); } };

bool big5string::is_big5char (int idx) { assert(idx >= 0 && idx < length() ); char *cp = c_str(); if (cp[idx] < 128 || idx == length() -1) return false; if (is_big5hiByte(cp[idx]) && is_big5loByte(cp[idx+1]) return true; else }

big5char big5string::next_char (int idx) { char *cp = c_str(); return is_big5char(idx)? cp[idx]*256+cp[idx] : cp[idx]; } int big5string::big5_length () { int len = 0, k = 0; while (k < length()) { k = is_big5char(k) ? k+2 : k+1; len++; } return len;

big5string 物件除了可使用 big5string 的功能以外,也可使用 string 所提供的各項功能。 測試程式 big5string 物件除了可使用 big5string 的功能以外,也可使用 string 所提供的各項功能。 #include <iostream> #include <string> #include “big5string.h” int main () { string s1(“Hi, “); big5string s2; s2 = s1 + “好久不見”; cout << s2 << endl; cout << “# of characters is: “ << s2.big5_length() << endl; return 0; } 輸出結果 Hi, 好久不見 # of characters is: 8

單一繼承 類別可隨需要而建立層層的繼承關係。譬如: class Employee { … }; Employee class Manager : public Employee { … }; class Director : public Manager { … }; Director 是 Manager 的衍生類別、 Manager 又是 Employee 的衍生類別。因此 Director 是 Manager 的子類別,也是 Employee 的子類別。之前所說 Manager 和Employee 間的關係同樣適用於 Director 和 Employee 之間。譬如: Director 包含 Employee 所有的資料成員、也可以直接使用 Employee 的 public 和 protected 成員、等等。 Employee Manager Director

衍生類別也可以作為其他類別的基底類別。如此一來,類別的繼承就形成如右圖所示的線性結構。D1, D2, …, Dn 都是基底類別 B 的衍生類別(或子類別),其中 D1 稱為 B 的「直接衍生類別」、 D2, …, Dn 稱為 B 的「間接衍生類別」。此外,D1, D2, …, Dn 型態的指標都可以轉換成 B 型態的指標。 B D1 D2 Dn

root class 有些時候,單一繼承的類別會形成如圖所示的階層狀(樹狀)的結構。其中最上層的類別稱為「根類別(root class)」。其他的類別都是根類別的子類別。

public, protected, 和 privated 基底類別 class X : public B { … }; class Y : protected B { … }; class Z : privated B { … }; 如果省略這些指定,則 class 的基底類別預設為 private、而 struct 的基底類別預設為 public 。譬如: class X : B { … }; // B is a private base struct X : B { … }; // B is a public base

這三種的差別在於以下的限制對間接衍生類別有所不同: 基底類別 public 和 protected 成員的存取; 把指標和參照從衍生類別的型態轉換成基底類別的型態。 假定 D 是 B 的一個直接衍生類別。 B 是一個 private 基底類別 B 的 protected 和 public 成員在 D 中變成 private 成員。這使得只有 D 的成員和朋友可以使用它們,而其他函式(包含 D 的衍生類別之成員和朋友)則不能使用它們。 此外,只有 D 的成員和朋友可以把 D* 轉換成 B*。

class B { public: void foo (); protected: void bar (); }; class D1 : private B { void f () { bar(); … } // ok class D2 : public D1 { void g () { bar(); … } // error void h () { foo(); … } // error void func () { B b; D1 d1; D2 d2; b.foo(); // ok b.bar(); // error d1.foo(); // error d1.bar(); // error d2.foo(); // error d2.bar(); // error B *bp = &D1; // error }

B 是一個 protected 基底類別 B 的 protected 和 public 成員在 D 中變成 protected 成員,使得只有 D 及其衍生類別的成員和朋友可以使用它們,而其他函式則不能使用它們。 此外,只有 D 及其衍生類別的成員和朋友可以把 D* 轉換成 B*。 B 是一個 public 基底類別 B 的 protected 和 public 成員在 D 中仍維持相同的存取模式。此外,任何函式都可以把 D* 轉換成 B*。

class B { public: void foo (); protected: void bar (); }; class D1 : protected B { void f () { bar(); … } // ok class D2 : public B { void g () { bar(); … } // ok void h () { foo(); … } // ok void func () { B b; D1 d1; D2 d2; b.foo(); // ok b.bar(); // error d1.foo(); // error d1.bar(); // error d2.foo(); // error d2.bar(); // error B *bp = &D1; // error }

class B { public: void foo (); protected: void bar (); }; class D1 : public B { void f () { bar(); … } // ok class D2 : public B { void g () { bar(); … } // ok void h () { foo(); … } // ok void func () { B b; D1 d1; D2 d2; b.foo(); // ok b.bar(); // error d1.foo(); // ok d1.bar(); // error d2.foo(); // ok d2.bar(); // error B *bp = &D1; // ok }

x 使用 B 的 public 和 protected 成員 D 的成員 D 及其衍生類別 其他函式 與朋友 的成員與朋友 與朋友 的成員與朋友 private B protected B public B x public but not protected

x 把 D* 轉換成 B* D 的成員 D 及其衍生類別 其他函式 與朋友 的成員與朋友 private B protected B 與朋友 的成員與朋友 private B protected B public B x

從以上的比較我們得知:對基底類別成員的存取,以 public 的限制最少、protected 次之、而 private 最多。此外,只有 public 的繼承方式允許在非成員的函式中,把衍生類別型態的指標轉換成基底類別型態的指標。由於這些原因,public 的繼承方式是最常用來定義基底類別的子類型(subtype)。如果我們想把基底類別當成 implementation 內部的一個類別,不希望外界直接地使用它,最好使用 protected 和 private 的繼承方式,其中又以 private 的隔離效果比 protected 來得大。

Virtual Functions class Employee { public: void print (); // … }; class Manager : public Employee { class Director : Manager { 假定我們有如右邊所示的類別繼承關係。由於資料成員多寡不一,每個類別因此各自定義了一個 print() 成員函式,用來列印相關的員工資料。

假定你想寫一個函式能夠列印任何一類員工的資料。由於 Manager 和 Director 都屬於 Employee 類別,因此你可能認為以下的函式就可以達到這個目的: void print_emp (Employee *e) { e->print(); } 其實不然。原因是:e 是 Employee 型態的指標,所以不論傳進來的引數型態是 Employee、 Manager、或 Director,e->print() 永遠是呼叫 Employee 的成員函式 print()。

你可以在 Employee 類別中,加入一個儲存員工類型的資料成員來解決前述的問題。譬如: class Employee { public: enum emp_type {EMPLOYEE, MANAGER, DIRECTOR}; void print (); emp_type type() { return _type; } // … private: emp_type _type; }; 並在三個類別的建構函式中,加入資料成員 type 的設定。經過這些加工之後,你就可以寫出下一頁的列印函式。

void print_emp (Employee *e) { switch (e->type()) { case Employee::EMPLOYEE: e->print(); break; case Employee::MANAGER: static_cast<Manager *>(e)->print(); // Manager’s print() case Employee::DIRECTOR: static_cast<Director *>(e)->print(); // Director’s print() }

上述的解決方案有下面兩個缺點: 就如同 print_emp() 函式所示,程式設計師必須判斷物件的類別,然後採取適當的型態轉換。這種作法不僅增加程式撰寫的負擔,也容易造成錯誤。 print_emp() 函式只能列印 Employee、Manager、和 Director 三種類別的物件。如果其他人用繼承的方式定義另一種員工的類別,如 Secretary,則已經寫死的 print_emp() 函式將無法用來列印這個新類別的物件。 class Secretary : public Employee { public: void print (); // … };

virtual return_type func_name (parameter list) 為了解決上述的問題,C++ 提供一種稱為 virtual 函式的特別成員函式。你只要在成員函式的宣告之前加上關鍵字 virtual,就可以把它變成 virtual 函式,即 virtual return_type func_name (parameter list) 當類別含有 virtual 成員函式時,C++ 編譯器會為它產生一個 virtual function table,其中包含此類別所有 virtual 成員函式的位址。此外,屬於此類別的物件,除了儲存資料成員外,會另外儲存一個指向此 virtual function table 的指標。

舉例來說,假定類別 X 的宣告如下: a class X { public: virtual void vf1 (); void f (); private: int _x, _y; }; X a, b; 則 X 類別的物件結構將如右圖所示。 _x _y _ _vptr_ _X X::vf1() virtual table for class X a b

衍生類別的 virtual 函式會覆蓋(override)基底類別的同名同參數列的 virtual 函式。 class Base { virtual void foo (int); // other members }; class Derived : public Base { override

若我們把前述 Employee 類別和它衍生類別中的 print() 成員函式都改成 virtual(如左圖所示),則函式 print_emp() 就變得簡單多了,而且也克服前述的一些缺點。 class Employee { public: virtual void print (); // … }; class Manager : public Employee { class Director : Manager {

利用 virtual 函式的 print_emp() 定義如下: void print_emp (Employee *e) { e->print(); } 則 e->print() 會呼叫傳進來物件所定義的 print() 成員函式。譬如:若傳進來 Emploee 型態的物件時,e->print() 等同於 e->Emploee::print();若是 Manager 型態的物件時,e->print() 等同於 e-> Manager ::print()。

型態相同的物件(都是 Employee)卻具有不同的行為(不同的 print() 功能),稱之為 polymorphism(多型)。具有 virtual 成員函式的類別稱為多型型態(polymorphic type)。 在 C++ 中,若要使用多型,你必須: 把一些成員函式定義成 virtual。 透過物件指標或參照來呼叫這些 virtual 成員函式。 若透過物件直接呼叫 virtual 成員函式,因為編譯時會固定呼叫的對像,所以會達不到多型的效果。譬如: Employee e; e.print(); // 一定呼叫 Employee::print()

Pure Virtual Functions 如果基底類別的 virtual 成員函式只是用來規定衍生類別應該具備的使用介面(interface),而且基底類別也不用來定義物件的話,我們可以用以下的格式把 virtual 成員函式設定無定義的函式: virtual return_type fucn_name (parameter_list) = 0 這樣的函式稱為 pure virtual function。

Abstract Classes 本身擁有或繼承但不改變 pure virtual functions 的類別稱為抽象類別(abstract class)。由於 pure virtual functions 是沒有定義的函式,因此抽象類別不可用來定義物件。譬如底下的 Abstract_Base 和Abstract_Derived 是抽象類別,而 Concrete_Derived 就不再是了: class Abstract_Base { public: virtual void foo () = 0; }; class Abstract_Derived : public Abstract_Base { /* … */ } class Concrete_Derived : public Abstract_Base { virtual void foo () {…}; // no longer pure }

抽象類別通常製訂介面(interface)用來規範衍生類別的基本功能。譬如: class Shape { public: // 所有 Shape 類別物件都必須提供下列的功能 virtual void rotate (int) = 0; virtual void draw () = 0; virtual void is_closed (int) = 0; }; class Circle : public Shape { virtual void rotate (int) { /* function definition */ } virtual void draw () { /* function definition */ } virtual void is_closed (int) { /* function definition */ } // other members }

RTTI (Run-time Type Information) C++ 的 RTTI 的機制提供下面兩個功能: dynamic_cast typeid

dynamic_cast p 如果指標 p 所指的物件型態是 T 或其基底類別是 T。 dynamic_cast<T *>(p) = p 如果指標 p 所指的物件型態是 T 或其基底類別是 T。 所有其他情形 假定 Base 是 Derived 的基底類別,Other 是與它們無任何繼續關係的類別 void f (Derived *p) { Base *q0 = p; // ok Base *q1 = dynamic_cast<Base *>( p); // ok Other *q2 = p; // compile error Other *q3 = dynamic_cast<Other *>( p); // ok: q3 is 0 }

typeid typeid 運算子可用來取得物件的型態資訊: typeid(obj); // 傳回物件 obj 的型態資訊 typeid(*objptr); // 傳回物件指標 objptr 所指物件的型態資訊 我們可以從型態資訊中取得型態的名稱,如: typeid(obj).name(); //物件 obj 所屬類別的名稱 也可以比較兩個物件的型態是否相同,如 typeid(obj1) == typeid(obj2) // true 若兩物件的類別相同 typeid(obj1) != typeid(obj2) // true 若兩物件的類別不相同