第十六章 使用继承扩展类
16.1什么是继承
如果一个类在现有类的基础上添加了新功能,那么这个类就被称为从原来的类派生而来的派生类(子类),而原来的类称为新类的基类(父类)。
基类可以有多个派生类
在c++中,要从一个类派生出另一个类,可在类声明中的类名后加上冒号,再指定类的访问控制符(public、protected或private)以及基类。
程序清单16.1 Mammal1.cpp
#include <iostream>
enum BREED
{
YORKIE,
CAIRN,
DANDIE,
SHETLAND,
DOBERMAN,
LAB
};
class Mammal
{
protected:
int age;
int weight;
public:
Mammal();
~Mammal();
int getAge() const;
void setAge(int);
int getWeight() const;
void setWeight();
void speak();
void sleep();
};
class Dog : public Mammal
{
protected:
BREED itsBreed;
public:
Dog();
~Dog();
BREED getBreed() const;
void setBreed(BREED);
void wagTail();
void begForBreed();
};
int main()
{
return 0;
}
这个程序能够通过编译,但是没有任何输出。它只包含类声明,而没有实现。
若希望数据对当前类和它的派生类可见,为此可使用protected。受保护的数据成员和函数对派生类来说是可见的,但其他方面与私有成员完全相同。
有三个访问限定符:public、protected和private。只要有类的对象,其成员函数就能够访问该类的所有成员数据和成员函数。成员函数可访问基类的所有私有数据和函数。
16.2 私有和保护
程序清单16.2 Mammal2.cpp
#include <iostream>
enum BREED
{
YORKIE,
CAIRN,
DANDIE,
SHETLAND,
DOBERMAN,
LAB
};
class Mammal
{
protected:
int age;
int weight;
public:
Mammal() : age(2), weight(5) {}
~Mammal() {}
int getAge() const { return age; }
void setAge(int newAge) { age = newAge; }
int getWeight() const { return weight; }
void setWeight(int newWeight) { weight = newWeight; }
void speak() const { std::cout << "Mammal sound!\n"; }
void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};
class Dog : public Mammal
{
private:
BREED breed;
public:
Dog() : breed(YORKIE) {}
~Dog() {}
BREED getBreed() const { return breed; }
void setBreed(BREED newBreed) { breed = newBreed; }
void wagTail() { std::cout << "Tail wagging ...\n"; }
void begForBreed() { std::cout << "Begging for food ...\n"; }
};
int main()
{
Dog fido;
fido.speak();
fido.wagTail();
std::cout << "Fido is " << fido.getAge() << " years oid\n";
return 0;
}
16.3构造函数和析构函数
创建派生类对象时,将调用多个构造函数。
所以创建一个子类对象时,将先调用基类的构造函数,再调用子类的构造函数;销毁对象时,将先调用子类的析构函数,再调用基类的析构函数。
程序清单16.3 Mammal3.cpp
#include <iostream>
enum BREED
{
YORKIE,
CAIRN,
DANDIE,
SHETLAND,
DOBERMAN,
LAB
};
class Mammal
{
protected:
int age;
int weight;
public:
Mammal() : age(2), weight(5) { std::cout << "Mammal constructor ..."; }
~Mammal() { std::cout << "Mammal destructor ..."; }
int getAge() const { return age; }
void setAge(int newAge) { age = newAge; }
int getWeight() const { return weight; }
void setWeight(int newWeight) { weight = newWeight; }
void speak() const { std::cout << "Mammal sound!\n"; }
void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};
class Dog : public Mammal
{
private:
BREED breed;
public:
Dog() : breed(YORKIE) { std::cout << "Dog constructor ..."; }
~Dog() { std::cout << "Dog destructor ..."; }
BREED getBreed() const { return breed; }
void setBreed(BREED newBreed) { breed = newBreed; }
void wagTail() { std::cout << "Tail wagging ...\n"; }
void begForBreed() { std::cout << "Begging for food ...\n"; }
};
int main()
{
Dog fido;
fido.speak();
fido.wagTail();
std::cout << "Fido is " << fido.getAge() << " years oid\n";
return 0;
}
16.4将参数传递给基类构造函数
要在派生类的初始化阶段进行基类初始化,可指定基类名称,并在后面跟基类构造函数需要的参数。
程序清单16.4 Mammal4.cpp
#include <iostream>
enum BREED
{
YORKIE,
CAIRN,
DANDIE,
SHETLAND,
DOBERMAN,
LAB
};
class Mammal
{
protected:
int age;
int weight;
public:
Mammal() : age(1), weight(5) { std::cout << "Mammal constructor ...\n"; }
Mammal(int age) : age(age), weight(5) { std::cout << "Mammal(int) constructor ...\n"; }
~Mammal() { std::cout << "Mammal destructor ...\n"; }
int getAge() const { return age; }
void setAge(int newAge) { age = newAge; }
int getWeight() const { return weight; }
void setWeight(int newWeight) { weight = newWeight; }
void speak() const { std::cout << "Mammal sound!\n"; }
void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};
class Dog : public Mammal
{
private:
BREED breed;
public:
Dog() : Mammal(), breed(YORKIE) { std::cout << "Dog constructor ...\n"; }
Dog(int age) : Mammal(age), breed(YORKIE) { std::cout << "Dog(int) constructor ...\n"; }
Dog(int age, int newWeight) : Mammal(age), breed(YORKIE)
{
weight = newWeight;
std::cout << "Dog(int,int) constructor ...\n";
}
Dog(int age, int newWeight, BREED breed) : Mammal(age), breed(breed)
{
weight = newWeight;
std::cout << "Dog(int,int,BREED) constructor ...\n";
}
Dog(int age, BREED newBreed) : Mammal(age), breed(newBreed) { std::cout << "Dog(int,BREED) constructor ...\n"; }
~Dog() { std::cout << "Dog destructor ...\n"; }
BREED getBreed() const { return breed; }
void setBreed(BREED newBreed) { breed = newBreed; }
void wagTail() { std::cout << "Tail wagging ...\n"; }
void begForBreed() { std::cout << "Begging for food ...\n"; }
};
int main()
{
Dog fido;
Dog rover(5);
Dog buster(6, 8);
Dog yorkie(3, YORKIE);
Dog dobbie(4, 20, DOBERMAN);
fido.speak();
rover.wagTail();
std::cout << "Yorkie is " << yorkie.getAge() << " years old\n";
std::cout << "Dobbie weights: " << dobbie.getWeight() << " pounds\n";
return 0;
}
16.5重写函数
如果派生类创建了一个返回类型和签名都与基类成员函数相同的函数,但是提供了新的实现,就称之为重写(覆盖)该函数。
重写函数时,返回类型和签名必须与基类函数相同。签名指的是除返回类型外的函数原型,这包括函数名、参数列表及关键字const(如果被重写的函数使用了const)
函数的签名由其名称以及参数的数量和类型组成,但不包括返回类型。
程序清单16.5 Mammal5.cpp
#include <iostream>
enum BREED
{
YORKIE,
CAIRN,
DANDIE,
SHETLAND,
DOBERMAN,
LAB
};
class Mammal
{
protected:
int age;
int weight;
public:
Mammal() : age(2), weight(5) { std::cout << "Mammal constructor ...\n"; }
~Mammal() { std::cout << "Mammal destructor ...\n"; }
void speak() const { std::cout << "Mammal sound!\n"; }
void sleep() const { std::cout << "Shhh. I'm sleeping.\n"; }
};
class Dog : public Mammal
{
private:
BREED breed;
public:
Dog() : breed(YORKIE) { std::cout << "Dog constructor ...\n"; }
~Dog() { std::cout << "Dog destructor ...\n"; }
void wagTail() { std::cout << "Tail wagging ...\n"; }
void begForBreed() { std::cout << "Begging for food ...\n"; }
void speak() const { std::cout << "Woof!\n"; }
};
int main()
{
Mammal mammal;
Dog dog;
mammal.speak();
dog.speak();
return 0;
}
重写与重载不同,重载成员函数实际上是创建了多个名称相同但签名不同的函数。但重写成员函数时,在派生类中创建的是一个名称与签名都与基类函数相同的函数。
如果Mammal有三个move()的重载版本,一个不接受任何参数,一个接受一个int型参数,一个接受int型和其他参数,而Dog只重写了无参版本的函数,那么使用Dog对象将难以访问其他两个版本。
程序清单16.6 Mammal6.cpp
#include <iostream>
class Mammal
{
protected:
int age;
int weight;
public:
void move() const { std::cout << "Mammal moves one step\n"; }
void move(int distance) const { std::cout << "Mammal moves " << distance << " steps\n"; }
};
class Dog : public Mammal
{
public:
void move() const { std::cout << "Dog moves 5 steps\n"; }
};
int main()
{
Mammal mammal;
Dog dog;
mammal.move();
mammal.move(2);
dog.move();
//dog.move(10);//报错
return 0;
}
即便重写了基类的成员函数,仍可使用全限定名来调用它。为此,可指定基类名、冒号和函数名
程序清单16.7 Mammal7.cpp
#include <iostream>
class Mammal
{
protected:
int age;
int weight;
public:
void move() const { std::cout << "Mammal moves one step\n"; }
void move(int distance) const { std::cout << "Mammal moves " << distance << " steps\n"; }
};
class Dog : public Mammal
{
public:
void move() const
{
std::cout << "Dog moves ...\n";
Mammal::move(3);
}
};
int main()
{
Mammal mammal;
Dog dog;
mammal.move(2);
dog.move();
dog.Mammal::move(6); //显式调用
return 0;
}