(切割/切片)
Person为父类,Student为子类
构造函数
在C++中,子类和父类的构造函数之间有一种特殊的关系,称为构造函数的继承和调用。
当创建子类对象时,子类的构造函数会自动调用父类的构造函数。这是因为子类继承了父类的成员变量和方法,所以在创建子类对象时,必须先初始化父类的成员。
子类可以通过在子类的构造函数初始化列表中调用父类的构造函数来实现对父类的构造函数的调用。在初始化列表中,使用父类的名称和参数列表来调用父类的构造函数。
以下是一个示例,展示了子类和父类构造函数的关系:
#include <iostream>
using namespace std;
class Parent {
public:
int data;
Parent(int data) : data(data) {
cout << "Parent constructor called" << endl;
}
};
class Child : public Parent {
public:
Child(int data) : Parent(data) {
cout << "Child constructor called" << endl;
}
};
int main() {
Child child(10);
return 0;
}
在上面的示例中,我们定义了一个父类Parent
和一个子类Child
。在子类的构造函数Child(int data)
中,我们调用了父类的构造函数Parent(data)
来初始化父类的成员变量(显示调用)。当创建子类对象child
时,会先调用父类的构造函数,然后再调用子类的构造函数。
需要注意的是,如果子类没有显式地调用父类的构造函数,编译器会默认调用父类的默认构造函数(无参数构造函数)。如果父类没有默认构造函数或默认构造函数不可访问,那么子类的构造函数必须显式地调用父类的构造函数。
此外,子类的构造函数可以在初始化列表中调用父类的任意构造函数,包括父类的其他构造函数。这样可以根据需要选择合适的父类构造函数来初始化父类的成员变量。
析构
子类的析构在调用完成后,默认调用一次父类的。这样保证先析构子类再析构父类
若子类要调用父类的析构函数 父类::父类析构函数
继承与友元
友元关系不能继承
上述程序 Display不能访问_stuNum
在Student类中加入 friend void Display(const Person& p, const Student& s)即可
static
基类中定义了一个static静态成员,则整个继承体系中只有一个这样的成员实例
是的,当基类中定义了一个静态成员变量时,在整个继承体系中只有一个这样的成员实例。
静态成员变量是属于类而不是类的实例的,它在内存中只有一个实例。当基类被继承时,派生类会继承基类的静态成员变量,但是它们共享同一个静态成员变量的实例。无论创建多少个派生类的对象,它们都共享同一个静态成员变量的值。
以下是一个示例,展示了基类中定义的静态成员变量在整个继承体系中只有一个实例的情况:
#include <iostream>
using namespace std;
class Parent {
public:
static int staticData;
};
int Parent::staticData = 1;
class Child : public Parent {
};
int main() {
cout << "Parent static data: " << Parent::staticData << endl; // 输出: Parent static data: 1
cout << "Child static data: " << Child::staticData << endl; // 输出: Child static data: 1
Child::staticData = 2;
cout << "Parent static data: " << Parent::staticData << endl; // 输出: Parent static data: 2
cout << "Child static data: " << Child::staticData << endl; // 输出: Child static data: 2
Parent::staticData = 3;
cout << "Parent static data: " << Parent::staticData << endl; // 输出: Parent static data: 3
cout << "Child static data: " << Child::staticData << endl; // 输出: Child static data: 3
return 0;
}
在上面的示例中,我们定义了一个基类Parent
和一个派生类Child
。基类中定义了一个静态成员变量staticData
并初始化为1。
在main
函数中,我们通过类名来访问基类和派生类的静态成员变量staticData
。可以看到,无论是通过基类还是派生类来访问静态成员变量,它们的值都是一样的,因为它们共享同一个静态成员变量的实例。
需要注意的是,静态成员变量的访问权限是在编译时就确定的,所以在继承中,派生类可以访问基类的静态成员变量,即使它们的访问权限是私有的。
单继承、多继承
如要父类保持私密 将父类构造函数私有化即可
单继承和多继承是面向对象编程中的两种继承方式。
单继承是指一个派生类只能从一个基类继承,即一个派生类只有一个直接基类。这种继承方式使得类之间的关系更加简单和清晰。在单继承中,派生类继承了基类的成员变量和成员函数,并且可以通过派生类对象来访问这些继承的成员。
以下是一个单继承的示例:
#include <iostream>
using namespace std;
class Parent {
public:
void parentFunction() {
cout << "This is a function in parent class." << endl;
}
};
class Child : public Parent {
public:
void childFunction() {
cout << "This is a function in child class." << endl;
}
};
int main() {
Child child;
child.parentFunction(); // 输出: This is a function in parent class.
child.childFunction(); // 输出: This is a function in child class.
return 0;
}
多继承是指一个派生类可以从多个基类继承,即一个派生类可以有多个直接基类。这种继承方式可以在一个派生类中获得来自多个基类的成员变量和成员函数。在多继承中,如果多个基类中有同名的成员函数或成员变量,派生类需要通过作用域限定符来指明要访问的成员。
以下是一个多继承的示例:
#include <iostream>
using namespace std;
class Parent1 {
public:
void parent1Function() {
cout << "This is a function in parent1 class." << endl;
}
};
class Parent2 {
public:
void parent2Function() {
cout << "This is a function in parent2 class." << endl;
}
};
class Child : public Parent1, public Parent2 {
public:
void childFunction() {
cout << "This is a function in child class." << endl;
}
};
int main() {
Child child;
child.parent1Function(); // 输出: This is a function in parent1 class.
child.parent2Function(); // 输出: This is a function in parent2 class.
child.childFunction(); // 输出: This is a function in child class.
return 0;
}
在多继承的示例中,派生类Child
同时继承了Parent1
和Parent2
两个基类的成员函数。通过派生类对象可以分别访问来自不同基类的成员函数。
需要注意的是,多继承可能会引发一些问题,例如菱形继承问题和命名冲突问题。在使用多继承时需要谨慎设计和处理这些问题。
菱形继承
菱形继承是多继承中的一种特殊情况,指的是一个派生类同时继承了两个直接基类,而这两个基类又共同继承自同一个间接基类,形成了一个菱形的继承关系。
下面是一个菱形继承的示例:
#include <iostream>
using namespace std;
class Grandparent {
public:
int data;
};
class Parent1 : public Grandparent {
public:
void setData(int value) {
data = value;
}
};
class Parent2 : public Grandparent {
public:
void printData() {
cout << "Data: " << data << endl;
}
};
class Child : public Parent1, public Parent2 {
public:
void setDataAndPrint(int value) {
setData(value);
printData();
}
};
int main() {
Child child;
child.setDataAndPrint(42); // 输出: Data: 42
return 0;
}
在这个示例中,Child
类同时继承了Parent1
和Parent2
两个基类,而这两个基类又共同继承自Grandparent
基类。所以,Child
类实际上有两个data
成员变量,一个来自Parent1
,一个来自Parent2
,这就是数据冗余问题。
菱形继承会导致两个问题:
-
二义性(Ambiguity):当派生类中调用一个在两个基类中都有的成员函数或成员变量时,编译器无法确定应该使用哪个基类中的成员,从而导致二义性。在上面的示例中,
Child
类中的setDataAndPrint
函数调用了setData
和printData
函数,这两个函数都在Parent1
和Parent2
中都有定义,所以在调用时会引发二义性。 -
数据冗余(Data Redundancy):由于派生类继承了两个基类,而这两个基类又都继承了同一个间接基类,所以派生类中会包含两份相同的成员变量。在上面的示例中,
Child
类中有两个data
成员变量,即Parent1
中的data
和Parent2
中的data
,这就造成了数据冗余。
为了解决菱形继承带来的二义性和数据冗余问题,C++提供了虚继承(Virtual Inheritance)机制。通过在继承关系中使用关键字virtual
,可以确保只有一份间接基类的实例被派生类继承,从而解决了菱形继承带来的问题。具体的虚继承用法如下:
class Parent1 : virtual public Grandparent {
// ...
};
class Parent2 : virtual public Grandparent {
// ...
};
通过添加virtual
关键字,Parent1
和Parent2
类就可以使用虚继承,从而解决了菱形继承的二义性和数据冗余问题。
如果在上述示例中使用了虚继承,即将Parent1
和Parent2
的继承方式改为virtual public Grandparent
,那么Child
类中只会有一个data
成员变量,而不会出现数据冗余的情况。
在使用虚继承后,Parent1
和Parent2
类可以通过作用域解析运算符::
来访问data
成员变量,如下所示:
class Parent1 : virtual public Grandparent {
public:
void setData(int value) {
data = value;
}
};
class Parent2 : virtual public Grandparent {
public:
void printData() {
cout << "Data: " << data << endl;
}
};
class Child : public Parent1, public Parent2 {
public:
void setDataAndPrint(int value) {
Parent1::setData(value);
Parent2::printData();
}
};
在Child
类中的setDataAndPrint
函数中,通过使用作用域解析运算符::
,可以指定调用Parent1
和Parent2
类中的成员函数,从而访问data
成员变量。
需要注意的是,在使用虚继承后,派生类中的成员函数如果需要访问data
成员变量,必须通过指定基类的名字来访问,以消除二义性。
在使用虚继承时,派生类中的成员函数访问虚继承的基类成员时,会通过指针来进行访问。
当使用虚继承时,派生类中会包含一个虚基类指针(vptr),该指针指向虚基类的实例。通过这个虚基类指针,派生类可以访问虚基类的成员。
在上述示例中,Child
类继承了Parent1
和Parent2
,而这两个类都使用了虚继承。因此,Child
类中会包含一个虚基类指针,指向Grandparent
的实例。通过这个虚基类指针,Child
类可以访问Grandparent
类的成员变量data
。
具体来说,在Child
类的成员函数中访问data
成员变量时,会通过虚基类指针来访问。例如,在Child
类的setDataAndPrint
函数中,Parent1::setData
函数通过虚基类指针来访问data
成员变量,Parent2::printData
函数也是通过虚基类指针来访问data
成员变量。
虚继承通过使用虚基类指针来解决菱形继承的数据冗余和二义性问题,确保只有一份虚基类的实例被派生类继承,并且通过指针来访问虚基类的成员。