C++ における継承/仮想継承の原則

C++ における継承

1. 継承の概念と定義

継承は、コードの再利用率を向上させる重要な方法です。継承により、プログラマは、元のクラスの特性を維持しながら、他の機能を追加できます。そのようなクラスは、派生クラスと呼ばれます。継承は、クラス設計レベルの再利用です

class Person
{
public:
	void Print()
	{
		cout << "name: " << _name << endl;
		cout << "age: " << _age << endl;
	}
	
protected:
	string _name = "牡丹";
	int _age = 18;
};

class Student :public Person
{
protected:
	int _stuid;
};

class Teacher :public Person
{
protected:
	int _jobid;
};

int main(void)
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}

操作の結果は次のようになります。

明らかにStudent和Teacher機能はありませんがPrint()、なぜまだ正常に実行されているのでしょうか? なぜなら、それらはすべてPersonクラスを継承しているからです。

1.1 継承の定義

以下では、 Person が親クラスであり、基本クラスとしても知られていることがわかります。Student はサブクラスであり、派生クラスとも呼ばれます。

1.12 継承関係とアクセス修飾子

クラスメンバー/継承 パブリック継承 保護された継承 私的継承
基本クラスのパブリックメンバー 派生クラスのパブリック メンバー 派生クラスの保護されたメンバー 派生クラスのプライベート メンバー
基本クラスの保護されたメンバー 派生クラスの保護されたメンバー 派生クラスの保護されたメンバー 派生クラスのプライベート メンバー
基本クラスのプライベートメンバー 派生クラスでは表示されません (非表示) 派生クラスでは表示されません (非表示) 派生クラスでは表示されません (非表示)

要約する

1. 基底クラスのメンバは、private継承方法に関わらず、派生クラス内では非表示・非表示となり、クラス外ではprivate派生クラスでは呼び出すことができない非表示のメンバとなります。

2.private派生クラス内ではメンバーにアクセスできませんが、クラス外からはアクセスできないようにしたい場合は、派生クラス内でアクセスすればメンバーを使用できますprotected

3.public > protected > privateサブクラス内の基本クラスの他のメンバーのアクセス方法 == 権限の低いもの

4. キーワード class を使用する場合のデフォルトの継承メソッドは ですprivate。キーワード classstructを使用する場合のデフォルトの継承メソッドは ですpublicただし、

継承を書き出す

5. 実際には、継承が一般的に使用され、継承はpublicbめったに使用されず、その使用は推奨されていません。protetced/private

protetced/private継承。protetced/private継承されたメンバーは派生クラスのクラス内でのみ使用できるため、実際には

延長メンテナンスは強くありません。

2. 基底クラスと派生クラスのオブジェクトのコピー変換

  • 派生クラスのオブジェクトは、基本クラスのオブジェクト/基本クラスのポインター/基本クラスの参照に割り当てることができますスライスまたはカットという非常に鮮やかな格言があります。親クラスの値を切り取って派生クラスに代入するというものです。

  • 基本クラスのオブジェクトを派生クラスに割り当てることはできません

  • 基底クラスへのポインタは、キャストによって派生クラスへのポインタに割り当てることができます。ただし、基本クラスのポインタが派生クラス オブジェクトを指している場合に限ります。

    安全です。ここで、基底クラスがポリモーフィック型の場合、RTTI(実行時型情報)のdynamic_castを使用して、

    セキュリティ移行は、識別が実行された後に実行されます。

class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};
void Test()
{
	Student sobj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;//Person = Student
	Person* pp = &sobj;//Person = Student
	Person& rp = sobj;//Person = Student

	//2.基类对象不能赋值给派生类对象
	sobj = pobj;//Student = Person

	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	pp = &sobj;
	Student * ps1 = (Student*)pp; // 这种情况转换时可以的。Student* = (Student*)Person*
	ps1->_No = 10;

	pp = &pobj;//Person =Person
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
	ps2->_No = 10;
}

3. 継承の範囲

1. 継承方式では基底クラス派生クラスが独立したスコープを持つ

2. サブクラスと親クラスに同じ名前のメンバーが存在します。サブクラスのメンバーと親クラスのメンバー間の関係は非表示と呼ばれます。親クラスのメンバーにアクセスする必要がある場合は、アクセス修飾子を追加できます。アクセスを表示するには

3.隠し関数を形成するには、関数が同じ関数名を持つことだけが必要であり、パラメーターは無関係です。

4. 同じ名前のメンバーを定義しないことをお勧めします

class Person
{
public:

protected:
	string _name="牡丹";
	int _num=18;
};

class Student :public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "学号:" << _num << endl;
		cout << "学号:" << Person::_num << endl;
	}
protected:
	int _num = 520;
};

int main(void)
{
	Student s1;
	s1.Print();
	return 0;
}

操作結果:

class Person
{
public:
	void Print(int a)
	{
		cout << "姓名:" << _name << endl;
		cout << "学号:" << _num << endl;
	}
protected:
	string _name="牡丹";
	int _num=18;
};

class Student :public Person
{
public:
	void Print()
	{
		Person::Print(1);
		cout << "姓名:" << _name << endl;
		cout << "学号:" << _num << endl;
		cout << "学号:" << Person::_num << endl;
	}
protected:
	int _num = 520;
};

int main(void)
{
	Student s1;
	s1.Print();
	return 0;
}

ここで構成されているのは、同じスコープ内にないため、オーバーロードではなく関数の非表示です。

4. 派生クラスのデフォルトのメンバー関数

6 つのデフォルトのメンバー関数。デフォルトは記述しないことを意味します。コンパイラーは、メンバー関数を自動的に生成するように変更します。その後、派生クラスでこれらの関数が生成されます。

メンバー関数はどのように生成されるのでしょうか?

1. 派生クラスのコンストラクターは、基本クラスのコンストラクターを呼び出して、基本クラスの一部を初期化する必要があります。基本クラスにデフォルト コンストラクターがない場合は、派生クラスの初期化リスト フェーズで明示的に呼び出す必要があります。コンストラクタ。

2. 派生クラスのコピー構築は、基本クラスのコピー構築を呼び出して、基本クラスの初期化を完了する必要があります。

3. 派生クラスは、operator=基本クラスを呼び出してoperator=、基本クラスのコピーを完了する必要があります。

4. 派生クラスのデストラクターは、呼び出された後に基本クラスのメンバーをクリーンアップするために、基本クラスのデストラクターを自動的に呼び出しますこれにより、派生クラスが確実に

オブジェクトが最初に派生クラスのメンバーをクリーンアップし、次に基本クラスのメンバーをクリーンアップする順序。

5. 派生クラスのオブジェクトの初期化では、最初に基本クラスの構築を呼び出し、次に派生クラスの構築を呼び出します。

6. 派生クラスのオブジェクト デストラクターのクリーンアップは、最初に派生クラスのデストラクターを呼び出し、次に基本クラスのデストラクターを調整します。

class Person
{
public:
	Person(const char* name = "牡丹")
		:_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:
	Student(const char* name, int num)
		: Person(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		: Person(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);
			_num = s._num;
		}
		return *this;
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};
void Test()
{
	Student s1("jack", 18);
	Student s2(s1);
	Student s3("rose", 17);
	s1 = s3;
}

int main(void)
{
	Test();
	return 0;
}

このコード:

实例化对象s1的时候会先去调用Student的构造函数,然后再Student的构造函数当中显示的调用了父类的构造函数。
实例化对象s2的时候先调用Student的拷贝构造,在拷贝构造的初始化列表当中显示的调用父类的拷贝构造
实例化s3的时候和s1一样
s1赋值给s3的时候先去调用子类的operator=,但是在子类当中会调用父类的operator=

相続と友人

フレンド関係は継承できません。つまり、基本クラスのフレンドはサブクラスのプライベートおよび保護されたメンバーにアクセスできません。

Display は基本クラス person の友人です。Student も Person で友人関係を継承している場合、ここの s._stuNum にはアクセスできますが、結果にはアクセスできないため、友人関係は継承できません。

6.継承と静的メンバー

**基本クラスが静的静的メンバーを定義している場合、そのようなメンバーは継承システム全体で 1 つだけ存在します。** サブクラスはいくつ派生しても、存在するのは 1 つだけです

静的メンバーインスタンス

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;
}

複雑なダイヤモンド継承とダイヤモンド仮想継承

単一継承: サブクラスに直接の親クラスが 1 つだけある場合、その継承関係は単一継承と呼ばれます。

多重継承: サブクラスに 2 つ以上の直接の親クラスがある場合、その継承関係は多重継承と呼ばれます。

ダイヤモンド継承: ダイヤモンド継承は、多重継承の特殊なケースです。

ダイヤモンド継承の問題: 次のオブジェクト メンバー モデルの構築から、ダイヤモンド継承にはデータの冗長性と曖昧さの問題があることがわかります。Assistant オブジェクトには Person メンバーのコピーが 2 つあります。

class Person
{
public:
	string _name; // 姓名
};

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

class Teacher : public Person
{
protected:
	int _id; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";

	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

仮想継承は、ダイヤモンド継承のあいまいさとデータの冗長性の問題を解決できます。


class Person
{
public:
	string _name; // 姓名
};

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

class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";

	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

int main(void)
{
	Test();
	return 0;
}

このように、それらはすべて_name同じものを指します_name

7. データの冗長性と曖昧性を解決するための仮想継承の原則

仮想継承のメンバーはすべてパブリック領域に配置されていることがわかりますが、ここにはさらに 2 つのアドレスがあり、2 つのポインターのように見えます。

14ここに合計0cがあり、14 の 16 進数は 20、c は 12 であることがわかります。これは、現在のクラスからパブリック エリアまでの距離を記録しているだけです

おすすめ

転載: blog.csdn.net/AkieMo/article/details/131625705