前面几节学的基本是两块内容:第一块是C++的一些特性,比如重载、引用、函数参数值传递与地址传递。第二块是面向对象编程的三大特性的其一:封装。封装中涉及了格式、成员变量和成员函数、访问权限、实例化对象、构造函数与析构函数、成员变量与成员函数的存储位置、this指针、友元声明、operator声明的运算符重载等知识。
现在就要接触到面向对象编程的三大特性的其二:继承。
继承的主要优势在于代码的重用性和扩展性。通过继承,子类可以直接使用父类的属性和方法,无需重新编写相同的代码。这样可以减少代码的冗余,并提高代码的可维护性。同时,子类还可以在继承的基础上添加自己的特性,以满足特定的需求。
在继承关系中,父类通常是一个更通用、抽象的类,而子类则是在父类基础上进行特化和扩展的类。子类可以通过继承获得父类的属性和方法,并且可以添加新的属性和方法,或者重写父类的方法以实现自己的逻辑。
继承还可以形成类的层次结构,使得代码的组织更加清晰和易于理解。通过继承,可以将类按照其关系进行分类和组织,形成一个类的层次结构,从而更好地组织和管理代码。
举个例子:
#include <iostream>
using namespace std;
class Animal {
protected:
string name;
public:
Animal(string name) {
this->name = name;
}
void speak() {
cout << "动物发出声音" << endl;
}
};
class Dog : public Animal {
private:
string breed;
public:
Dog(string name, string breed) : Animal(name) {
this->breed = breed;
}
void speak() {
cout << "汪汪汪!" << endl;
}
void fetch() {
cout << "狗狗正在追逐球" << endl;
}
};
int main() {
Animal animal("动物");
animal.speak(); // 输出:动物发出声音
Dog dog("旺财", "哈士奇");
dog.speak(); // 输出:汪汪汪!
dog.fetch(); // 输出:狗狗正在追逐球
return 0;
}
Animal类是父类,它有一个name属性和一个speak方法。Dog类是子类,它继承了Animal类,并且添加了一个新的属性breed(品种)和一个新的方法fetch(追逐)。
继承的语法:
class 子类: 继承方式 父类
子类也叫派生类,父类也叫基类。
使用关键字public来指定继承方式。
①子类继承了父类的公开成员和保护成员,但不继承父类的私有成员(即不能访问)。
②父类的公开接口成员函数既可以让外部调用,也可以让子类调用。
使用关键字protected来指定继承方式。
①子类继承父类的公开成员和保护成员,且在子类中都变为保护成员,父类的私有成员仍然不能访问
②父类的接口函数限制在子类和子类的派生类中,外部无法直接访问父类的成员
使用关键字private来指定继承方式。
①子类继承父类的公开成员和保护成员,且在子类中都变为私有成员,父类的私有成员仍然不能访问
②私有继承将父类的接口隐藏起来,子类不能直接访问父类的成员函数,只能通过子类自己的公有成员或者友元函数来间接访问。
子类中无法访问父类私有的权限,但是可以访问公开和受保护的权限。
有几个注意的地方:
①保护权限的成员,只能在子类内部访问,而不能在外部的主函数里访问。
子类继承父类的protected变量,不能在主函数里用对象调用吗?
保护成员可以在子类内部和子类对象中访问,但不能在类外部直接访问。
②子类继承父类后,孙子类应该继承子类的哪些成员呢?
答:当子类继承父类后,孙子类会继承子类的所有成员,包括公有成员、保护成员和私有成员。孙子类可以直接访问子类的公有成员和保护成员,但无法直接访问子类的私有成员。继承的层次可以一直延续下去,每个子类都可以成为下一个子类的父类。
儿子类继承了父类的三个不同权限的成员变量,自己有一个独特的成员变量,那sizeof(son)的结果到底是多少呢?
答案是子类的对象拥有了这四个变量的空间,即使父类有一个私有变量,子类只是不能访问,但实例化对象时也开辟了它的空间。
构造函数的继承:子类会继承父类的构造函数。当创建子类对象时,会先调用父类的构造函数,然后再调用子类自身的构造函数。这样可以确保父类的成员被正确初始化。
析构函数的继承:子类会继承父类的析构函数。当子类对象被销毁时,会先调用子类自身的析构函数,然后再调用父类的析构函数。这样可以确保父类的资源被正确释放。
class father {
private:
string name;
int age;
protected:
int money;
public:
father() {
name = "xiaogang";
age = 38;
money = 6000;
address = "nanjing";
}
string address;
};
子类:
class son :public father {
public:
son() {
money = 5000;
address = "beijing";
height = 150;
}
int height;
};
也就是说,父类的构造函数在父类写好后,子类继承时就不能用默认构造函数去实例化对象了,而是要重新写一个含参的构造函数。
一定要注意一点:
子类的对象和父类的对象是完全不同的两个对象,他们各自的成员变量的地址是不同的。构造函数里面的赋值,也只是给自己的对象的成员变量赋值而已。
int main() {
father f;
son son1;
cout << son1.address << endl;//儿子是北京
cout << f.address << endl;//父亲是南京
return 0;
}
举个例子,父类有个成员变量叫friends,子类也有一个成员变量叫friends。任何通过子类的对象去访问父类的同名成员变量呢?
使用作用域声明:
son1.father::friends
class father {
private:
string name;
int age;
protected:
int money;
public:
father() {
name = "xiaogang";
age = 38;
money = 6000;
address = "nanjing";
friends = "limiing";
}
string address;
string friends;
};
class son :public father {
public:
son() {
money = 5000;
address = "beijing";
height = 150;
friends = "xiaohong";
}
int height;
string friends;
};
int main() {
father f;
son son1;
cout << son1.father::friends << endl;
return 0;
}
对象son1调用了父亲作用域下的成员变量。
比如:class C : public A, public B
#include <iostream>
using namespace std;
// 基类A
class A {
public:
void displayA() {
cout << "This is class A" << endl;
}
};
// 基类B
class B {
public:
void displayB() {
cout << "This is class B" << endl;
}
};
// 派生类C,继承自类A和类B
class C : public A, public B {
public:
void displayC() {
cout << "This is class C" << endl;
}
};
int main() {
C c;
c.displayA(); // 调用继承自类A的成员函数
c.displayB(); // 调用继承自类B的成员函数
c.displayC(); // 调用派生类C自己的成员函数
return 0;
}
继承是C++中的一个重要特性,被广泛应用于面向对象编程中。在实际开发中,继承被广泛用于代码复用和扩展性设计。
下面是一些继承的常见应用场景:
实现类的层次结构:通过继承,可以将类组织成层次结构,从而更好地组织和管理代码。
代码复用:通过继承,可以将一个类的成员函数和成员变量复用到另一个类中,从而减少代码量,提高代码的可维护性和可重用性。
多态性:通过继承和虚函数,可以实现多态性,即同一个函数在不同的派生类中具有不同的实现。
接口设计:通过继承和抽象类,可以定义接口,从而实现代码的松耦合和可扩展性。