【c++】继承学习(二):探索 C++ 中派生类的默认机制与静态成员共享

发布于:2024-05-08 ⋅ 阅读:(30) ⋅ 点赞:(0)

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

朋友们大家好,本篇文章我们来学习继承的第二部分

1.派生类的默认成员函数

在这里插入图片描述
来看下面的类:

class Person
{
public:
	Person(const char* name = "jason")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
protected:
	int _num; //学号
};
  1. Student对象生成的默认构造函数,对内置类型不做处理,对自定义类型调用它的默认构造函数,规则和以前一样
    在这里插入图片描述
    在这里插入图片描述

派生类里面,把父类成员当做一个整体

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用

比如的父类的构造函数修改成这种:

Person(const char* name)
	: _name(name)
{
	cout << "Person()" << endl;
}

现在父类没有默认构造,会编译报错

在这里插入图片描述

这时候需要子类来完成构造函数:

Student(const char* name, int num)
	: Person(name)
	, _num(num)
{
	cout << "Student()" << endl;
}

这段代码功能:

基类初始化:
Person(name) 调用了基类 Person 的构造函数,并传递了 name 参数,这确保了 Person 类的成员 _name 被正确初始化。写成 Person(name) 就是指示编译器使用 Person 类的接受 const char* 参数的构造函数。如果不这样做,基类成员 _nameStudent 对象构建过程中不会被初始化。

注意

这里不能这样初始化:

Student(const char* name, int num)
	: _name(name)
	, _num(num)
{
	cout << "Student()" << endl;
}

父类成员需要当做一个整体的一个自定义类型的成员,不能单独对它的成员处理

_name 是基类 Person 的一部分,派生类 Student 没有直接的权限去初始化它。应该使用基类构造函数来初始化

确保基类的构造函数被调用是继承中非常重要的一部分,因为只有基类的构造函数知道如何正确初始化基类定义的成员。上面的修改确保当创建Student 类的对象时,它会首先调用 Person 类的构造函数初始化 _name,然后初始化派生类 Student 的 _num 成员

派生类这里分成了两个部分:父类和自己,父类的调用父类构造函数初始化

成员变量的初始化顺序是根据它们在类定义中出现的顺序,而不是初始化列表中的顺序。因此,基类的构造函数总是首先被调用,再是派生类中定义的成员变量

  1. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化一般情况下默认生成的就够用,如果涉及到深拷贝,就需要自己显示实现
Student(const Student& s)
   : Person(s)
   , _num(s._num)
{
   cout << "Student(const Student& s)" << endl;
}

在这里插入图片描述

  1. 派生类的operator=必须要调用基类的operator=完成基类的复制
Student& operator = (const Student& s)
{
	cout << "Student& operator= (const Student& s)" << endl;
	if (this != &s)
	{
		Person::operator =(s);
		_num = s._num;
	}
	return *this;
}

这里同名函数构成了隐藏

  1. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
~Student()
{
	cout << "~Student()" << endl;
}

上面的函数我们都进行了显示调用,但是析构函数不可以
在这里插入图片描述
Student 的析构函数被调用并完成执行后,Person 的析构函数将随即被自动而且默认地调用。这样的设计可以防止基类成员被重复释放或者提前释放,从而导致潜在的错误和资源泄漏

  1. 派生类对象初始化:先调用基类构造再调派生类构造

  2. 派生类对象析构清理:先调用派生类析构再调基类的析构。

  3. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

所以我们想显示调用就需要这样写:

~Student()
{
	Person::~Person();
	cout << "~Student()" << endl;
}

但是这里会导致一个问题,析构多调用了一次,就是因为析构函数先调用子类再调用父类的,子类析构函数结束后会自动调用父类析构

2.继承与友元

友元关系不能继承,基类友元不能访问子类私有和保护成员

class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}

这里会编译错误:

在这里插入图片描述

基类将某些函数或类声明为友元,这个友元关系不会自动传递给从派生)。派生类需要自己明确声明哪些函数或类是它的友元

如何解决编译错误:

要解决 Display 函数不能访问 Student 类的 _stuNum 成员的问题,可以在 Student 类中也声明 Display 为友元:

class Student : public Person
{
public:
    friend void Display(const Person& p, const Student& s);
protected:
    int _stuNum; // 学号
};

现在,Display 函数是 PersonStudent 两个类的友元,可以访问两个类的保护成员

3.继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

class Graduate : public Student
{
protected:
	string _seminarCourse; // 研究科目
};
void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

静态成员是与类本身关联的,而不是与类的单个实例相关联。静态成员变量在所有实例中共享,而静态成员函数可以在没有类实例的情况下直接通过类名调用。当静态成员被继承时,派生类共享同一个静态成员副本,因为静态成员是属于类的,不属于类的任何具体对象

在上面代码中,Person 类有一个静态成员 _count,它被用来统计该类的实例数量。每当创建一个 Person 类的实例或者它的派生类的实例时,构造函数都会递增 _count,因此 StudentGraduate 的示例也会递增 _count

分析:

  1. Person::_count 是静态成员变量,并且初始化为 0。它统计 Person 及其派生类 StudentGraduate 的对象个数。

  2. Student 类继承自 Person,没有定义新的静态成员变量,因此它共享 Person 类的静态成员 _count

  3. Graduate 类继承自 Student,也没有定义新的静态成员变量,因此它同样共享 Person 类的静态成员 _count

void TestPerson()
{
    Student s1; // 在构造时, Person::_count 变为 1
    Student s2; // Person::_count 变为 2
    Student s3; // Person::_count 变为 3
    Graduate s4; // Person::_count 变为 4
    cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :4"

    Student::_count = 0; // 重置 Person::_count 为 0

    cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :0"
}

TestPerson 函数中创建了三个 Student 对象和一个 Graduate 对象,每次构造函数调用都会递增 _count,因此打印 _count 的结果为 4。

然后,将静态成员 _count 通过 Student 类重置为 0。注意,这里使用 Student::_count 访问的实际上还是 Person 类的静态成员 _count,因为 Student 并没有重新定义它。这表明无论通过类 Person 还是它的任何派生类访问静态成员 _count,结果都是相同的。因此,第二次打印 _count 的结果是 0

静态成员的继承性质:静态成员在基类及其派生类之间是共享的,而不是每个派生类都有独立的静态成员副本。因此,无论是在基类还是派生类中访问静态成员,访问的都是同一个数据。在设计类层次结构时,这一点非常重要,因为静态成员的行为可能会影响整个类族