C++ 继承与多态全面解析
1. 继承基础
1.1 继承概念与公共继承
继承是 C++ 和面向对象编程的重要特性,它允许从现有类创建新类,新类称为派生类,现有类称为基类。派生类继承基类的特性,还可添加新特性或修改现有特性。
公共继承的一般形式为:
class name_of_derived_class : access_type name_of_base_class
当访问类型为公共时,派生类的函数和友元函数可访问基类的公共成员,但不能直接访问私有成员,只能通过基类的公共和受保护函数访问。派生类的函数和友元函数可直接访问基类的受保护成员,但其对象不能。
1.2 数据隐藏与类层次结构
数据隐藏方面,只有私有成员不能从类外部直接访问,提供了数据隐藏。派生类可作为基类创建新的派生类,形成类的层次结构。
基类包含实体的一般属性,派生类是基类的特化。例如:
#include <iostream>
#include <string>
using std::cout;
using std::cin;
using std::string;
class Product
{
public:
string code;
float prc;
Product() {cout << "Base Constructor\n";}
~Product() {cout << "Base Destructor\n";}
void show() const {cout << "\nC:" << code << '\n';}
};
class Book : public Product
{
public:
string title;
string auth;
Book() {cout << "Derived Constructor\n";}
~Book() {cout << "Derived Destructor\n";}
void display() const {cout << "T:" << title << " A:" << auth << " P:" << prc << '\n';}
};
int main()
{
Book b;
cout << "Product Details: ";
cin >> b.code >> b.prc;
cout << "Book Details: ";
cin >> b.title >> b.auth;
b.show();
b.display();
return 0;
}
此例中,
Book
类继承了
Product
类的成员和函数,创建
Book
对象时,先调用基类构造函数,再调用派生类构造函数;销毁对象时,顺序相反。
1.3 继承的优势
继承的主要优势是代码复用,避免重写代码。同时,简化了程序的维护和升级,若基类添加新特性或修复错误,派生类会自动继承。
1.4 Is - a 和 Has - a 关系
公共继承通常模拟 “is - a” 关系,即派生类对象也是基类对象,如
Book
是
Product
。而 “has - a” 关系适用于一个类包含另一个类的对象,如书店包含书籍。
1.5 受保护成员与继承
若类可能作为基类,可将部分成员声明为受保护,派生类可直接访问。但这会降低内部安全性,可能导致基类操作难以控制。例如:
class A
{
protected:
int p;
};
class B : public A
{
public:
void f();
};
void B::f()
{
p = 10; // 允许,访问基类子对象的受保护成员
A a;
a.p = 10; // 错误,普通基类对象的受保护成员不可访问
}
1.6 作用域与继承
每个类定义自己的作用域,派生类的作用域包含在基类作用域内。使用名称时,编译器先在类作用域查找,若未找到,继续在基类作用域查找。
若不同类使用相同名称,内部作用域的名称会隐藏外部作用域的名称。可使用作用域解析运算符
::
引用其他类的成员。
1.7 派生类与构造函数
创建派生类对象时,需先创建基类子对象,派生类构造函数要调用基类构造函数初始化基类子对象。例如:
#include <iostream>
#include <string>
using std::cout;
using std::string;
class Product
{
private:
string code;
float prc;
public:
Product(const string& c = "No", float p = 0);
void show() const;
};
class Book : public Product
{
private:
string title;
string auth;
public:
Book(const string& c, float p, const string& t, const string& a);
void display() const;
};
Product::Product(const string& c, float p)
{
code = c;
prc = p;
}
void Product::show() const
{
cout << "C:" << code << " P:" << prc << '\n';
}
Book::Book(const string& c, float p, const string& t, const string& a) : Product(c, p)
{
title = t;
auth = a;
}
void Book::display() const
{
show();
cout << "T:" << title << " A:" << auth << '\n';
}
int main()
{
Book b("AB25", 8.5, "Nice", "Many");
b.display();
return 0;
}
成员初始化按在类中声明的顺序进行,而非初始化列表的顺序。
1.8 类层次结构
在类层次结构中,派生对象包含直接基类和间接基类的子对象。构造时,从最基类开始调用构造函数;析构时,顺序相反。
若不想让类被继承,可使用 C++11 的
final
关键字:
class A final {...}; // 类 A 不能作为基类
2. 基类与派生类的特殊关系
2.1 从派生类到基类的转换
基类的指针或引用可指向或引用派生类对象,无需类型转换,这称为派生到基类的转换。例如:
Book b("AB25", 8.5, "Nice", "Many");
Product *p = &b;
Product &r = b;
p->show();
r.show();
但基类的指针或引用不能访问派生类的成员。
派生类对象可用于需要基类对象的表达式,函数接受基类对象的指针或引用时,也可接受派生类对象的指针或引用。
2.2 从基类到派生类的转换
基类到派生类的转换不能自动进行,若要转换需类型转换,但这不安全,因为基类对象不包含派生类的子对象。
若要将基类对象赋值给派生类对象,可重载赋值运算符或定义转换构造函数。
2.3 派生类的复制操作
2.3.1 定义派生类的复制构造函数
简单继承示例中,默认复制构造函数会复制基类子对象的成员。复杂示例如下:
#include <iostream>
class A
{
public:
int a;
A(int i) : a(i) {}
A(const A& r) : a(r.a + 1) {}
};
class B : public A
{
private:
int t;
public:
B(int i) : A(i), t(i+20) {}
B(const B& b) : A(b), t(b.t + 1) {}
void show() const {std::cout << a << ' ' << t << '\n';}
};
int main()
{
B b1(10);
B b2 = b1;
b2.show();
return 0;
}
派生类的复制构造函数需调用基类的复制构造函数处理基类子对象。
2.3.2 定义派生类的复制赋值函数
复制赋值函数也需复制基类子对象的成员。例如:
#include <iostream>
class A
{
public:
int v;
A(int i) : v(i) {}
};
class B : public A
{
public:
int r;
B(int i) : A(i+5), r(i) {}
B& operator=(const B& b);
};
B& B::operator=(const B & b)
{
A::operator=(b);
r = b.r;
return *this;
}
int main()
{
B b1(10), b2(20);
b2 = b1;
std::cout << b2.v << ' ' << b2.r << '\n';
return 0;
}
3. 函数重定义
3.1 相同签名的函数重定义
当派生类重定义基类的函数且签名相同时,派生类函数会隐藏基类函数。例如:
void Book::show() const
{
cout << "T:" << title << " A:" << auth << '\n';
}
若要调用基类的函数,可使用作用域解析运算符:
void Book::show()
{
cout << "T:" << title << " A:" << auth << '\n';
Product::show();
}
使用指针或引用调用函数时,若指针或引用的类型为基类,调用的是基类的函数。
3.2 不同签名的函数重定义
当基类函数在派生类中声明不同时,重定义的函数会隐藏基类中同名的所有函数。例如:
#include <iostream>
class A
{
public:
void show() const {std::cout << "A\n";}
};
class B : public A
{
public:
void show(int k) const {std::cout << k << '\n';}
};
class C : public B
{
public:
void show(const char *s) const {std::cout << s << '\n';}
};
int main()
{
B b;
b.show(10);
b.show(); // 错误
C c;
c.show("Test");
c.show(10); // 错误
c.show(); // 错误
return 0;
}
若要在派生类中拥有基类函数的重载版本,可使用
using
声明:
class C : public B
{
public:
using B::A::show;
using B::show;
void show(const char *s) const {std::cout << s << '\n';}
};
3.3 函数重定义的流程图
graph TD;
A[调用函数] --> B{函数签名是否相同};
B -- 是 --> C{是否为虚函数};
C -- 是 --> D[动态绑定,根据对象类型调用];
C -- 否 --> E[静态绑定,根据指针或引用类型调用];
B -- 否 --> F[新函数,隐藏基类同名函数];
3.4 函数重定义的表格总结
| 情况 | 处理方式 |
|---|---|
| 相同签名,非虚函数 | 派生类函数隐藏基类函数,根据指针或引用类型调用 |
| 相同签名,虚函数 | 动态绑定,根据对象类型调用 |
| 不同签名 | 新函数,隐藏基类同名函数 |
4. 虚函数
4.1 虚函数的基本概念
当使用对象调用函数时,调用的是对象所属类的函数。但使用指针或引用调用重定义的基类函数时,若未声明为虚函数,调用的是指针或引用类型对应的函数,这称为静态绑定。例如:
Product p("A", 1);
Book b("B", 2, "Nice", "Many");
Product *ptr1 = &p;
ptr1->show();
Product *ptr2 = &b;
ptr2->show();
Product& ref = b;
ref.show();
上述代码中,
ptr2
和
ref
虽指向或引用
Book
对象,但调用的是
Product
类的
show()
函数。
若在基类中将函数声明为虚函数,情况会不同。例如:
class Product
{
...
virtual void show() const;
};
class Book : public Product
{
...
virtual void show() const;
};
此时,当通过指针或引用调用虚函数时,会根据对象的实际类型调用相应函数,这称为动态绑定或运行时多态。
4.2 函数重写
当派生类的函数重定义基类的虚函数且签名相同时,称为函数重写。重写时,派生类函数的返回类型通常应与基类相同,或为更特化的类型。若签名不匹配,派生类函数不会重写基类函数,而是隐藏基类函数。
4.3 静态和动态类型
指针或引用有静态类型和动态类型。静态类型是声明时的类型,动态类型是指向或引用对象的实际类型。例如:
Product *ptr2 = &b;
ptr2
的静态类型是
Product*
,动态类型是
Book*
。使用虚函数时,编译器根据动态类型选择要调用的函数。
4.4 动态多态
使用虚函数时,函数的选择在程序运行时决定,实现了动态多态。例如:
int a;
Product *ptr;
cin >> a;
if(a == 1)
ptr = new Product("A", 1);
else
ptr = new Book("B", 2, "Nice", "Many");
ptr->show();
编译时,编译器不知道
ptr
指向的对象类型,运行时根据实际对象类型绑定
show()
函数。
编译器通常为每个类创建一个虚函数表(vtbl),存储虚函数的内存地址。调用虚函数时,编译器根据对象类型在 vtbl 中查找函数地址并执行。
4.5 虚函数的示例
#include <iostream>
using std::cout;
class A
{
public:
void f() const {cout << "A ";}
virtual void g() const {cout << "A ";}
void v() const {cout << "A ";}
};
class B : public A
{
public:
void f() const {cout << "B ";}
virtual void g() const {cout << "B ";}
virtual void v() const {cout << "B ";}
};
int main()
{
B b;
A &r = b;
r.f();
r.g();
r.v();
return 0;
}
上述代码中,
f()
不是虚函数,调用
A.f()
;
g()
是虚函数,调用
B.g()
;
v()
在基类未声明为虚函数,调用
A.v()
。
4.6 虚函数的表格总结
| 函数类型 | 绑定方式 | 调用依据 |
|---|---|---|
| 非虚函数 | 静态绑定 | 指针或引用的类型 |
| 虚函数 | 动态绑定 | 对象的实际类型 |
4.7 虚函数的流程图
graph TD;
A[调用函数] --> B{是否为虚函数};
B -- 是 --> C[根据对象实际类型调用];
B -- 否 --> D[根据指针或引用类型调用];
5. 使用
override
和
final
说明符
5.1
override
说明符
C++11 引入
override
说明符,用于确保派生类正确重写基类的虚函数。例如:
class Book : public Product
{
...
void show() override;
};
使用
override
可避免因签名不匹配导致的错误。例如:
#include <iostream>
using std::cout;
class A
{
public:
void f() const {cout << "A ";}
virtual void g() const {cout << "A ";}
};
class B : public A
{
public:
virtual void f() const {cout << "B ";}
virtual void g(int i) const {cout << i;}
};
int main()
{
B b;
A &r = b;
r.f();
r.g();
return 0;
}
上述代码中,由于
f()
在
A
类未声明为虚函数,
r.f()
调用
A.f()
;
g()
签名不匹配,未重写基类函数。使用
override
可让编译器检测此类错误。
5.2
final
说明符
final
说明符可用于防止类被继承或虚函数被重写。例如:
class Book : public Product
{
...
virtual void show() override final;
};
class Technical_Book : public Book
{
...
virtual void show(); // 错误
};
override
和
final
不是保留关键字,但为避免混淆,不建议用作变量名。
6. 虚函数的使用
6.1 接受基类引用的函数
虚函数常用于接受基类对象引用的函数中。例如:
#include <iostream>
class Shape
{
public:
virtual void draw() const {std::cout << "Shape ";}
};
class Circle : public Shape
{
public:
virtual void draw() const override {std::cout << "Circle ";}
};
void f(const Shape& s)
{
s.draw();
}
int main()
{
Circle c;
f(c);
return 0;
}
上述代码中,
f()
接受
Shape
类的引用,可传入
Circle
对象,根据对象实际类型调用
Circle::draw()
函数。
6.2 管理不同类型对象
可使用基类指针数组或智能指针向量管理不同类型的对象。例如:
int main()
{
const int SIZE = 10;
int i, ch;
float prc;
string code, title, auth;
Product *p[SIZE];
for(i = 0; i < SIZE; i++)
{
cout << "Enter choice (1 for Product): ";
cin >> ch;
cout << "Enter code: ";
cin >> code;
cout << "Enter price: ";
cin >> prc;
if(ch == 1)
p[i] = new Product(code, prc);
else
{
cout << "Enter title: ";
cin >> title;
cout << "Enter auth: ";
cin >> auth;
p[i] = new Book(code, prc, title, auth);
}
}
for(i = 0; i < SIZE; i++)
{
p[i]->show();
delete p[i];
}
return 0;
}
通过虚函数,可根据指针指向的实际对象类型调用相应的
show()
函数。
6.3 调用特定虚函数
若要强制调用特定虚函数,可使用作用域解析运算符。例如:
#include <iostream>
class A
{
public:
virtual void f() const {std::cout << "A";}
};
class B : public A
{
public:
virtual void f() const override;
};
void B::f() const
{
A::f();
}
int main()
{
B b;
A &r = b;
A *p = &b;
b.f();
r.A::f();
p->A::f();
return 0;
}
上述代码中,通过作用域解析运算符可静态解析调用
A
类的
f()
函数。
6.4 虚函数与访问说明符
虚函数在派生类中的访问说明符可与基类不同,但使用基类指针或引用调用时,以基类的访问说明符为准。例如:
#include <iostream>
class A
{
public:
virtual void f() const {std::cout << "A" << '\n';}
};
class B : public A
{
private:
virtual void f() const override {std::cout << "B" << '\n';}
};
int main()
{
B b;
A *p = &b;
b.f(); // 错误
p->f(); // 输出 B
return 0;
}
6.5 虚函数与预定义参数
虚函数可使用预定义参数,但通过基类指针或引用调用时,使用基类的预定义参数。例如:
#include <iostream>
class A
{
public:
virtual void f(int i = 10) const {std::cout << "A:" << i << '\n';}
};
class B : public A
{
public:
virtual void f(int i = 20) const override {std::cout << "B:" << i << '\n';}
};
int main()
{
B b;
A &r = b;
b.f(); // 输出 B:20
r.f(); // 输出 B:10
return 0;
}
为避免问题,不建议在派生类中更改虚函数的默认参数。
6.6 虚析构函数
析构函数可声明为虚函数。若基类可能指向派生类对象,应将基类析构函数声明为虚函数,以确保正确释放内存。例如:
class Book : public Product
{
...
char *n;
~Book();
...
};
Book::Book(const string &c, float p, const string &t, const string &a) : Product(c, p)
{
n = new char[100];
...
}
Book::~Book()
{
delete[] n;
}
int main()
{
Product *p = new Book("B", 2, "Nice", "Many");
delete p; // 应调用 Book 析构函数
return 0;
}
若
Product
析构函数未声明为虚函数,
delete p
只会调用
Product
析构函数,导致内存泄漏。
6.7 构造函数和析构函数中调用虚函数
在基类构造函数或析构函数中调用虚函数时,调用的是当前类的函数,因为此时对象的构造或析构未完成。例如:
#include <iostream>
class A
{
public:
A() {f();}
~A() {f();}
virtual void f() const {std::cout << "A" << '\n';}
void test() const {f();}
};
class B : public A
{
public:
virtual void f() const override {std::cout << "B" << '\n';}
};
int main()
{
B b;
b.test();
return 0;
}
上述代码中,程序输出
A B A
。
6.8 虚函数使用的表格总结
| 情况 | 处理方式 |
|---|---|
| 调用特定虚函数 | 使用作用域解析运算符 |
| 虚函数与访问说明符 | 使用基类的访问说明符 |
| 虚函数与预定义参数 | 使用基类的预定义参数 |
| 虚析构函数 | 基类析构函数声明为虚函数 |
| 构造和析构中调用虚函数 | 调用当前类的函数 |
6.9 虚函数使用的流程图
graph TD;
A[使用虚函数] --> B{是否为构造或析构函数中调用};
B -- 是 --> C[调用当前类的函数];
B -- 否 --> D{是否使用基类指针或引用};
D -- 是 --> E{是否为虚析构函数};
E -- 是 --> F[根据对象实际类型调用析构函数];
E -- 否 --> G{是否指定特定类};
G -- 是 --> H[调用指定类的函数];
G -- 否 --> I[根据对象实际类型调用函数];
D -- 否 --> J[根据对象类型调用函数];
7. 注意事项
7.1 转换错误示例
在使用数组传递对象时,要注意类大小不同可能导致的错误。例如:
#include <iostream>
class A
{
public:
virtual void show() const {std::cout << "A\n";}
};
class B : public A
{
public:
int a;
int b;
virtual void show() const override {std::cout << "B\n";}
};
void f(A* p, int s)
{
for(int i = 0; i < s; i++)
p[i].show();
}
int main()
{
A a[5];
f(a, 5);
B b[3];
f(b, 3);
return 0;
}
上述代码中,由于
B
类大小大于
A
类,
f(b, 3)
调用可能导致程序崩溃。可使用
vector
替代数组解决此问题:
template <typename T> void f(const vector<T>& v)
{
for(int i = 0; i < v.size(); i++)
v[i].show();
}
int main()
{
vector<A> a(5);
f(a);
vector<B> b(3);
f(b);
return 0;
}
7.2 实践建议
学习 C++ 继承和多态的概念时,要多实践,通过实验书中的示例和练习,编写自己的程序,从错误中学习,逐渐熟悉这些概念。
8. 练习题解答
8.1 练习题 1
实现 TCP/IP 模型的层次类结构,代码如下:
#include <iostream>
#include <string>
using std::cout;
using std::cin;
using std::string;
class Physical
{
protected:
int speed;
};
class Data_Link : public Physical
{
protected:
string MAC_addr;
};
class Network : public Data_Link
{
protected:
string IP_addr;
};
class Transport : public Network
{
protected:
string prtcl;
};
class Application : public Transport
{
protected:
int port;
public:
void get();
void show() const;
};
void Application::get()
{
cout << "Enter speed: ";
cin >> speed;
cout << "Enter MAC_addr: ";
cin >> MAC_addr;
cout << "Enter IP_addr: ";
cin >> IP_addr;
cout << "Enter transport protocol: ";
cin >> prtcl;
cout << "Enter application port: ";
cin >> port;
}
void Application::show() const
{
cout << "\nS:" << speed << " MAC:" << MAC_addr << " IP:" << IP_addr << " T:" << prtcl << " P:" << port << '\n';
}
int main()
{
Application app;
app.get();
app.show();
return 0;
}
8.2 练习题 2
定义类层次结构,代码如下:
#include <iostream>
#include <string>
using std::cout;
using std::string;
class A
{
private:
string name;
public:
A(const string& s) : name(s) {}
virtual void show() const {cout << name << '\n';}
};
class B : public A
{
private:
string name;
public:
B(const string& s1, const string& s2) : A(s2), name(s1) {}
virtual void show() const {cout << name << '\n';}
};
class C : public B
{
private:
string name;
public:
C(const string& s1, const string& s2, const string& s3) : B(s2, s3), name(s1) {}
virtual void show() const {cout << name << '\n';}
};
int main()
{
C c("Last", "Sec", "First");
A *p = &c;
p->show();
return 0;
}
8.3 练习题 3
找出程序中的错误,错误分析如下:
-
show()
函数无法直接访问
p
,因为
p
是
A
类的私有成员。
-
B
类的构造函数未调用
A
类的构造函数,无法创建
A
对象。
-
A
类没有
show()
函数,
r.show(20)
调用错误。
8.4 练习题 4
定义
Product
和
Book
类,代码如下:
#include <iostream>
#include <string>
using std::cout;
using std::string;
class Product
{
protected:
int code;
public:
Product(int c);
virtual ~Product() {};
virtual void show() const;
};
class Book : public Product
{
private:
string title;
public:
Book(string t, int c);
virtual void show() const override;
};
void f(const Product *p1, const Product *p2);
Product::Product(int c)
{
code = c;
}
void Product::show() const
{
cout << "C:" << code << '\n';
}
Book::Book(string t, int c) : Product(c)
{
title = t;
}
void Book::show() const
{
cout << "C:" << code << " T:" << title << '\n';
}
int main()
{
Product prod(100);
Book b("C++", 200);
Product *p1 = ∏
Product *p2 = &b;
f(p1, p2);
return 0;
}
void f(const Product *p1, const Product *p2)
{
p1->show();
p2->show();
}
8.5 练习题 5
程序输出为
1 A.v() 1 B.v()
。分析如下:
-
t1(b)
调用时,仅传递
b
对象的
A
部分,
f()
显示
1
,
v()
显示
A.v()
。
-
t2(b)
调用时,
r
引用
b
对象,
f()
非虚函数,调用
A.f()
显示
1
;
v()
是虚函数,调用
B.v()
。
8.6 练习题 6
定义
Fruit
和
Apple
类,代码如下:
#include <iostream>
#include <string>
using std::cout;
using std::string;
class Fruit
{
private:
string name;
int cal;
public:
Fruit(const string& s, int c) : name(s), cal(c) {}
virtual ~Fruit() {};
virtual int operator+(int num);
};
class Apple : public Fruit
{
private:
int antiox;
public:
Apple(int ant, const string& name, int cal) : Fruit(name, cal), antiox(ant) {}
virtual int operator+(int num) override;
};
int Fruit::operator+(int num)
{
cal += num;
return cal;
}
void f(Fruit& p, int num);
int Apple::operator+(int num)
{
antiox += num;
return antiox;
}
int main()
{
Fruit fr("F1", 10);
Apple ap(20, "F2", 30);
f(fr, 5);
f(ap, 5);
return 0;
}
void f(Fruit& p, int num)
{
cout << p+num << '\n';
}
8.7 练习题 7
程序输出为
A C C.f() A.v() ~B ~A
。分析如下:
- 创建
C
对象时,调用
A
和
C
的构造函数,输出
A C
。
-
p->f()
调用
C.f()
,因为
f()
是虚函数。
-
p->v()
调用
A.v()
,因为
v()
不是虚函数。
- 销毁对象时,由于
B
的析构函数不是虚函数,不调用
C
的析构函数,输出
~B ~A
。
8.8 练习题 8
为类添加适当函数,代码如下:
#include <iostream>
#include <cstring>
class A
{
private:
char *s;
public:
explicit A(const char str[]);
A();
virtual ~A();
A(const A& a);
A& operator=(const A& a);
void show() const;
};
A::A(const char str[])
{
s = new char[strlen(str)+1];
strcpy(s, str);
}
A::A()
{
s = nullptr;
}
A::~A()
{
delete[] s;
}
A& A::operator=(const A& a)
{
if(this == &a)
return *this;
delete[] s;
s = new char[strlen(a.s)+1];
strcpy(s, a.s);
return *this;
}
A::A(const A& a)
{
s = new char[strlen(a.s)+1];
strcpy(s, a.s);
}
void A::show() const
{
std::cout << s << '\n';
}
class B : public A
{
private:
char *s;
public:
explicit B(const char str[]);
B();
virtual ~B();
B(const B& b);
B& operator=(const B& b);
void show() const;
};
B::B(const char str[]) : A(str)
{
s = new char[strlen(str)+1];
strcpy(s, str);
}
B::B()
{
s = nullptr;
}
B::~B()
{
delete[] s;
}
B& B::operator=(const B& b)
{
if(this == &b)
return *this;
A::operator=(b);
delete[] s;
s = new char[strlen(b.s)+1];
strcpy(s, b.s);
return *this;
}
B::B(const B& b) : A(b)
{
s = new char[strlen(b.s)+1];
strcpy(s, b.s);
}
void B::show() const
{
std::cout << s << '\n';
A::show();
}
int main()
{
B b1("abc");
B b2(b1);
B b3;
b3 = b2;
b3.show();
return 0;
}
8.9 练习题 9
定义
Company
相关类,代码如下:
#include <iostream>
#include <string>
#include <vector>
using std::cout;
using std::string;
using std::vector;
class Company
{
private:
string cmp_name;
string tax_num;
int empl;
public:
Company(const string& cmp_name, const string& tax_num, int empl);
Company() {cmp_name = "None"; tax_num = empl = -1;}
virtual ~Company() {};
virtual void show() const;
};
class Software_Cmp : public Company
{
private:
string prod;
int prgr;
public:
Software_Cmp(const string& prod, int prgr);
virtual void show() const override;
};
class Finance_Cmp : public Company
{
private:
string srvc;
int acnt;
public:
Finance_Cmp(const string& srvc, int acnt);
virtual void show() const override;
};
Company::Company(const string& cmp_name, const string& tax_num, int empl)
{
this->cmp_name = cmp_name;
this->tax_num = tax_num;
this->empl = empl;
}
void Company::show() const
{
cout << "N:" << cmp_name << " E:" << empl << '\n';
}
Software_Cmp::Software_Cmp(const string& prod, int prgr)
{
this->prod = prod;
this->prgr = prgr;
}
void Software_Cmp::show() const
{
cout << "N:" << prod << " E:" << prgr << '\n';
}
Finance_Cmp::Finance_Cmp(const string& srvc, int acnt)
{
this->srvc = srvc;
this->acnt = acnt;
}
void Finance_Cmp::show() const
{
cout << "N:" << srvc << " E:" << acnt << '\n';
}
int main()
{
vector<Company*> v;
Company cmp("Alpha", "12345", 100);
Software_Cmp sw_cmp("Software_Product", 20);
Finance_Cmp fin_cmp("Finance_Service", 10);
v.push_back(&cmp);
v.push_back(&sw_cmp);
v.push_back(&fin_cmp);
for(int i = 0; i < v.size(); i++)
v[i]->show();
return 0;
}
8. 未解决的练习题
8.1 练习题 1
使用公共继承定义类层次结构
A
、
B
和
C
,每个类包含一个整数私有成员和公共函数
get()
和
show()
。要求程序读取三个整数并存储在相应类成员中,然后依次显示这些值。
8.2 练习题 2
定义
Circle
和
Sphere
类,使程序能正确初始化对象并显示
Sphere
对象的颜色和体积。
8.3 练习题 3
在之前的类层次结构中,将每个数据成员的访问类型改为私有,
Application
类不包含
get()
和
show()
函数,添加构造函数使程序能正确存储和显示数据。
8.4 练习题 4
分析程序的输出结果,涉及类的构造、虚函数调用等知识。
8.5 练习题 5
定义
Shape
和
Rect
类,使程序能读取数据并进行相应计算和显示。
8.6 练习题 6
定义
Person
、
Student
和
Employee
类,实现
f()
函数,使程序能正确显示对象的私有成员值。
8.7 练习题 7
定义
Person
、
Student
和
Phd_Student
类,添加适当函数使程序能正确显示对象的私有成员。
通过对这些练习题的分析和解答,能更好地掌握 C++ 继承和多态的知识。同时,要注意实践和总结,不断提高编程能力。
超级会员免费看
11万+

被折叠的 条评论
为什么被折叠?



