目次
1. クラスの宣言と定義
1.1 クラス宣言
クラスは、新しい型と新しいスコープを定義することです. クラス (クラス), 構造体 (構造体), 共用体 (共用体) はユーザー定義型であり, クラス指定子で定義されます.宣言構文が表示されます。
/*类关键词 属性 类头名 基类子句 { 成员说明 }*/
//demo
class X {public: int val;};
class Y : public X {public: void setval();};
struct A { int id; };
struct B : public A { double val;};
union U{ int a; char b; };
/*
类关键词 - class 或 struct或union 之一。除了默认成员访问和默认基类访问之外,两个关键词是等同的。
属性 - (C++11 起) 可选的任任意数量属性序列,可以包含 alignas 指定符
类头名 - 所定义的类的名字。可以有限定,也可以后随关键词 final。名字可以省略,此时类是无名的(注意无名类不能是 final 的)
基类子句 - 一或多个基类以及各自所用的继承模型的可选列表
成员说明 - 访问说明符、成员对象及成员函数的声明和定义的列表
如果 类关键词 是 union,那么声明引入一个联合体类型。
*/
各クラスは、メンバーを持たないことも、データ、関数、または型のエイリアスなどの複数のメンバーを定義することもできます。クラスには、いくつかのパブリック (パブリック)、プライベート (プライベート)、および保護 (保護) 部分を含めることができます。public セクションで定義されたメンバーは、型を使用するすべてのコードからアクセスできます。private セクションで定義されたメンバーは、クラス メンバーまたはフレンドからアクセスできます。protected で定義されたメンバーは、派生クラスまたはフレンドからアクセスできます。
すべてのメンバーはクラス内で宣言する必要があります。クラス定義が完了すると、メンバーを追加する方法はありません。
1.2 クラスの主な特徴
クラスの背後にある基本的な考え方は、データの抽象化とカプセル化です。クラスはカプセル化されたエンティティです。クラスはメンバーのコレクションを表し、ほとんどの (適切に設計された) クラス型は、その型を実装するメンバーを隠します。クラスは使用法に関して抽象的です。そのインターフェース、つまり実行できる操作を考慮してください。また、コンシューマーは型がどのように表現されているかの詳細を知らず、その実装アーティファクトにもアクセスできないという点でカプセル化されています。
class Base
{
public:
int ival;
void func1(void);
protected:
float fval;
void func2(void);
private:
double dval;
void func3(void);
};
通常、データ (プライベート、メンバー変数または関数、内部使用のためのメンバー関数) を非表示にし、外部からアクセス可能なインターフェイス (パブリック、保護、メンバー関数、および場合によってはメンバー変数を公開) を提供します。クラス オブジェクトを操作するユーザー関数は、データがプライベートであり、メンバー関数のインターフェイスが変更されない場合、変更する必要はありません。クラスの設計者は、クラスの実装方法に注意を払う必要がありますが、クラスを使用するプログラマーは、型のインターフェイス機能を理解するだけでよく、型がどのように機能するかを具体的に考える必要なく、型が何をするかを抽象的に考えることができます。
PS、すべての型が抽象である必要はありません。使用法に応じて、一部の具象クラスは実装の詳細を非表示にするのではなく公開します。クラス設計者は、ユーザーのニーズの観点から優れた実用的なクラスを設計する必要があります。
クラスのもう 1 つの重要な機能は、オブジェクト指向プログラミングによって示されるポリモーフィズム (ポリモーフィズム) です。つまり、派生型または基本型は、多くの場合、交換可能に使用できるため、継承によってクラス型に関連する型はポリモーフィック型です。 」タイプの。C++ では、ポリモーフィズムは、継承によって関連付けられた型の参照またはポインターに対してのみ使用されます。継承を通じて、型間の関係をモデル化するクラスを定義し、共通のものを共有し、本質的に異なるものだけを特殊化することができます。派生クラス (派生クラス) は、基底クラス (baseclass) で定義されたメンバーを継承できます. 派生クラスは、派生型の特定の特性に関連しない操作を変更せずに使用できます. 派生クラスは、それらのメンバー関数を再定義できます.派生型に関連する 派生型の特性を考慮した関数の特殊化。最後に、派生クラスは、基本クラスから継承されたメンバーに加えて、さらにメンバーを定義できます。継承によって関連付けられたクラスは、継承階層を形成します。これらのクラスの 1 つがルートと呼ばれ、他のクラスはルート クラスから直接的または間接的に継承します。
1.3 クラス定義
クラスを定義するとき、右中括弧に遭遇した場合、それは定義の終わりを意味します. クラス定義ステートメントを最後に追加する必要があることに注意してください. したがって、コンパイラは、すべてのクラス メンバーと、このクラスのオブジェクトを格納するために必要なストレージ領域を決定できます。クラスは、特定のソース ファイルで 1 回だけ定義できます。クラスが複数のファイルで定義されている場合、定義は各ファイルで同一でなければなりません。通常、クラスはヘッダー ファイルで定義されます。これにより、クラスを使用する各ファイルで同じ方法でクラスが定義され、メンバー関数、組み込みオブジェクト、動的オブジェクトなどの実装が定義されます。ソースファイルで。クラス定義本体の外側でメンバー関数などを定義することは、カプセル化を実現するもう 1 つの方法です.たとえば、ソース コードではなく、ヘッダー ファイルと静的または動的ライブラリのみをユーザーに提供します.
//a.h
#ifndef _A_H_
#define _A_H_
class A {
public:
void setval(int val_);
private:
int val;
};
#endif //_A_H_
//a.cpp
void A::setval(int val_)
{
//code
};
各クラスは、独自の新しいスコープと一意の型を定義します。クラス定義の本体内でクラス メンバーを宣言すると、メンバー名がクラスのスコープに含まれます。2 つの異なるクラスには、2 つのクラス スコープがあります。2 つのクラスがまったく同じメンバー リストを持っていても、それらは同じ型ではありません。各クラスのメンバーは、他のクラス (または他のスコープ) のメンバーとは異なります。
class A { private: int x; double y; };
class B { private: int x; double y; };
//
A a;
B b = a; //error
メンバ関数などはクラスの定義本体外で定義されることが多いが、あたかもクラスのスコープ内にあるかのように定義されており、クラス定義本体でのメンバ関数の定義との違いは、 inline プロパティは定義の外で定義され、inline プロパティであることを明示的に指定する必要があります。
class X
{
public:
void func1(){ val=1;}; //inline
void func2();
void func3();
private:
int val;
};
//
void X::func2() { val=2;};//普通成员函数
void X::func3() { val=3;};//inline成员函数
第二に、クラスの組み合わせ
2.1 前方宣言参照
一般に、クラスを定義せずにクラスを宣言することができます。
class ATest;
この宣言は、前方宣言 (前方宣言) または前方宣言と呼ばれることもあり、クラス型の ATest をプログラムに導入します。宣言の後、定義の前では、クラス ATest は不完全な型 (不完全な型) です。つまり、ATest が型であることはわかっていますが、どのメンバーが含まれているかはわかりません。不完全型は限られた方法でしか使用できません。このタイプのオブジェクトは定義できません。不完全な型は、その型へのポインターと参照を定義するため、またはその型をパラメーターの型または戻り値の型として使用する関数を宣言する (定義はしない) ためにのみ使用できます。
//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
class ATest;
class CTest{};
class BTest
{
public:
BTest();
~BTest();
void doit();
// ATest a; //error,这是一个不完全类型
ATest *pa; //OK,不完全类型能用于定义指向该类型的指针
CTest c; //OK
};
#endif //_TEST_1_H_
//test1.cpp
class ATest
{
public:
int val;
};
BTest::BTest(){
pa = new ATest;
};
BTest::~BTest(){
delete pa; pa=nullptr;
}
void BTest::doit()
{
// ++a.val; //error
++(pa->val);//OK
};
事前宣言により、一部のクラス型定義をソース ファイルに実装するのを遅らせることができるため、実装の詳細をより適切に隠すことができます。また、クラス ユーザーが気にしない一部のクラス型もシールドすることができ、ユーザーにより明確なアプリケーション インターフェイス。
標準ライブラリには、前方宣言をサポートするヘッダー ファイル <*fwd> も含まれています。特定のソース ファイルがこのクラスのポインターと参照のみを使用している場合、前方宣言を使用すると #include の依存関係を減らすこともできます。
#include <iosfwd> // 含有 std::ostream 的前置声明
class PTest
{
private:
/* data */
std::string *pstr; //<iosfwd>中#include <bits/stringfwd.h>
// std::string str; //error
public:
friend std::ostream& operator<<(std::ostream& os, const PTest& s);//<iosfwd> // 含有 std::ostream 的前置声明
};
クラスは、クラス定義本体が完成した後にのみ定義できるため、クラスは独自の型のデータ メンバーを持つことはできません. クラス型がそれ自体を使用する場合、事前に宣言されたクラス、つまりデータ メンバーを使用することと同じです。クラスの は、それ自体の型または Quote へのポインターにすることができます。
class DTest
{
private:
/* data */
public:
DTest *prt;
};
前方宣言がローカルスコープで発生した場合、以前に宣言されたクラス、変数、関数、およびそれを囲むスコープに表示される可能性のある同じ名前の他のすべての宣言が非表示になります。
//示例1
struct s { int a; };
struct s; // 不做任何事(s 已经在此作用域定义)
void g()
{
struct s; // 新的局部结构体“s”的前置声明,它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
*(p->p) = 'a';//OK,使用局部声明
//p->a = 1; //error
};
//或示例2
struct ts{
struct s; // 新的局部结构体“s”的前置声明,它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
void doit()
{
*(p->p) = 'a'; //OK
// p->a = 1; //error
};
};
前方宣言は、他の宣言の一部である精巧な型指定子を介して、新しいクラス名を導入することもできますが、名前のルックアップでその名前を持つ以前に宣言されたクラスが見つからない場合に限ります。
#ifndef _TEST_2_H_
#define _TEST_2_H_
class K;
namespace ns{
class Y f1(class T p); // 声明函数 ns::f1 并前置声明类 ns::T 与 ns::Y
class K f2(); // K 指代 ::K,和class Y是不同的
};
void nstest(void);
#endif //_TEST_2_H_
//test2.cpp
#include "test2.h"
class K
{
private:
/* data */
int val;
public:
};
class ns::Y{};
class ns::T{};
ns::Y ns::f1(ns::T p)
{
return ns::Y();
};
::K ns::f2()
{
return ::K();
};
void nstest(void)
{
ns::T p;
ns::Y y = ns::f1(p);
::K k = ns::f2();
};
2.2 友情
場合によっては、非メンバー関数またはクラスをそのクラスのフレンドとして宣言することにより、一般的なアクセスを防止しながら、特定の非メンバー関数またはクラスがクラスのプライベート メンバーにアクセスできるようにすると便利です。
たとえば、入力演算子や出力演算子などのオーバーロードされた演算子は、多くの場合、クラスのプライベート データ メンバーにアクセスする必要があります。これらの演算子をクラスのメンバーにすることはできません。ただし、それらはクラスのメンバーではありませんが、それでもクラスの「インターフェイスの一部」です。
#include <ostream>
class Empty{};
class PClass
{
private:
int val;
void g(){val = 10; };
public:
void func(){ val = 100; };
// protected:
// private: //可以public protected private
friend class FClass;//友元类前置声明
friend Empty; //友元类声明(简单类型说明符) (C++11 起)
friend std::ostream& operator<<(std::ostream &os,const PClass &obj);
};
class FClass
{
private:
/* data */
public:
PClass pc;
void func()
{
pc.val = 22; 直接使用私有成员
pc.g(); 直接使用私有成员
pc.func(); //
};
};
//test2.cpp
#include "test2.h"
std::ostream& operator<<(std::ostream &os,const PClass &obj)
{
os << obj.val; //直接使用私有成员
return os;
}
フレンド メカニズムにより、クラスは非パブリック メンバーに指定された関数またはクラスへのアクセスを許可できます。フレンド宣言は、キーワード フレンドで始まります。クラス定義内でのみ使用できます。フレンド宣言は、クラス内のどこにでも表示できます。フレンドは、フレンドシップ関係を付与したクラスのメンバーではないため、宣言が表示されるアクセス制御の影響を受けません。通常、friend 宣言は、クラス定義の最初または最後にグループで配置されます。
フレンドは、通常の非メンバー関数、別のクラスの以前に定義されたメンバー関数、またはクラス全体にすることができます。上記の例では、クラスと非メンバー関数を事前に宣言して、それらを friends にする必要はありません。フレンド宣言は、名前付きクラスまたは非メンバー関数を外側のスコープに導入します。
クラスをフレンドとして設定すると、フレンド クラスのすべてのメンバー関数が、フレンドシップ関係を付与されたクラスの非パブリック メンバーにアクセスできるようになり、クラスのカプセル化が弱まります。通常は、カプセル化をできるだけ維持できるだけでなく、使用の利便性を満たすことができる特定のメンバー関数に友人を与えることをお勧めします。メンバー関数をフレンドとして宣言する場合、関数名は関数が属するクラスの名前で修飾できます。
#include <ostream>
class FClass
{
private:
/* data */
public:
void func();
};
class PClass
{
private:
int val;
void g(){val = 10; };
public:
void func(){ val = 100; };
// protected:
// private:
// friend class FClass; //
friend void FClass::func(); //直接面向成员函数
friend std::ostream& operator<<(std::ostream &os,const PClass &obj);
};
//test2.cpp
#include "test2.h"
std::ostream& operator<<(std::ostream &os,const PClass &obj)
{
os << obj.val; //直接使用私有成员
return os;
}
void FClass::func()
{
PClass pc;
pc.val = 22; 直接使用私有成员
pc.g(); 直接使用私有成员
pc.func(); //
};
上記のコードからわかるように、メンバー関数を friends として設定する前に、メンバー関数を含むクラスを定義する必要があります。
非メンバー関数を定義すると同時に、このクラスのフレンドにすることができます。そのような非メンバー関数は、名前付きモジュールにアタッチされていない限り、常にインラインです (C++20以上)。
class PClass
{
private:
int val;
public:
friend void func1(PClass& obj) { obj.val = 100; };//内联友元,非成员函数
};
//main.cpp
PClass pc;
func1(pc);//直接使用内部私有成员
フレンドとして宣言された後、ネストされた型にもアクセスできます。
class PClass
{
private:
enum { a = 10 }; // 私有枚举
class DeriveA { }; // 私有嵌套类型
public:
friend class FDerive;
};
class FDerive : PClass::DeriveA // OK:友元能访问 PClass::DeriveA
{
PClass::DeriveA mx; // OK:友元的成员能访问 PClass::DeriveA
class DeriveB {
PClass::DeriveA my; // OK:友元的嵌套成员能访问 PClass::DeriveA
};
int v[PClass::a]; // OK:友元的成员能访问其他私有成员 PClass::a
};
フレンドを使用する場合は、次の制約に注意する必要があります。
- 友達関係は推移的ではありません (友達の友達はあなたの友達ではありません)。
- 友達関係は継承されません (友達の子供はあなたの友達ではありません)。
- フレンド関数の宣言では、ストレージ クラス指定子は使用できません。フレンド宣言で定義された関数には外部リンケージがあり、以前に定義された関数は、定義されたときのリンケージを保持します。
- アクセス指定子は、フレンド宣言の意味には影響しません (それらは private: または public: セクションに表示できますが、違いはありません)。
- フレンド クラス宣言では、新しいクラスを定義できません (フレンド クラス X {}; は誤りです)。
class TF1
{
private:
/* data */
int val;
public:
friend class TF2;
// friend class NewTF{}; //error,不能像普通函数一样在类内定义主体
};
class TF2
{
public:
void func(){
TF1 tf1;
tf1.val = 10; //OK
}
friend class TF4;
};
class TF3 : public TF2
{
private:
/* data */
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能继承
}
};
class TF4
{
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能传递
}
};
2.3 クラスの継承または派生
クラス型は基本クラスとして他の型に継承でき、継承されたクラスはこのクラスの派生クラスです。基本クラスは、そのインターフェイスを定義し、継承保護によって派生クラスに渡されるデータと関数メンバーを実装します. 派生クラスは、基本クラスの特性を継承し、これに基づいて独自の特性を拡張します.
class Base
{
public:
int ival;
};
class Child : public Base
{
public:
char cval;
};
クラス継承には、基底クラス メンバーに対する派生クラスのアクセス制御を決定する、パブリック、プロテクト、およびプライベートの 3 つの制限付き継承メソッドがあります。
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base
{
public:
int ival;
protected:
float fval;
private:
double dval;
};
class Child1 : public Base
{
public:
char cval;
void doit_public();
};
class Child2 : protected Base
{
public:
char cval;
void doit_public();
};
class Child3 : private Base
{
public:
char cval;
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
#include "test3.h"
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void Child2::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void Child3::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void test_derive(void)
{
Child1 c1;
c1.ival = 1;
// c1.fval = 1.0; //error
c1.doit_public();
Child2 c2;
// c2.ival = 1; //error
// c2.fval = 1.0; //error
c2.doit_public();
Child3 c3;
// c3.ival = 1; //error
// c3.fval = 1.0; //error
c3.doit_public();
};
継承がない場合、クラスには、クラス自体のメンバーとそのクラスのユーザーの 2 種類のユーザーしかありません。クラスのプライベート アクセス レベルとパブリック アクセス レベルへの分割は、このユーザー カテゴリの分離を反映しています。ユーザーはパブリック インターフェイスにのみアクセスでき、クラス メンバーと友人はパブリック メンバーとプライベート メンバーの両方にアクセスできます。
継承では、クラスの 3 番目の種類のユーザーが存在します。それらから派生して新しいクラスを定義するプログラマーです。派生クラスのプロバイダーは通常 (常にではありませんが) (通常はプライベートな) 基本クラスの実装にアクセスする必要があり、実装への一般的なアクセスを禁止しながらこのアクセスを許可するために、追加の保護アクセス ラベルが提供されます。クラスの保護された部分は、通常のプログラムからはアクセスできませんが、派生クラスからはアクセスできます。基本クラスのプライベート部分にアクセスできるのは、クラス自体とその仲間だけであり、派生クラスは基本クラスのプライベート メンバーにアクセスできません。
基本クラスとして機能するクラスを定義する場合、メンバーをパブリックにする基準は変更されていません。インターフェイス関数は引き続きパブリックにする必要があり、データは一般にパブリックにするべきではありません。継承されたクラスは、実装のどの部分を保護と宣言し、どの部分を非公開と宣言するかを決定する必要があります。派生クラスがアクセスできないようにするメンバーは private に設定し、派生クラスが実装するために必要な操作またはデータを提供するメンバーは protected に設定する必要があります。派生クラスを介した基本クラス メンバーへのアクセスは、パブリック継承である必要があります。
クラス派生リストでは、複数の基底クラスを指定できます。単一の基底クラスからの継承が最も一般的ですが、複数の継承、つまり複数の直接基底クラスからクラスを派生させる機能をサポートする必要がある場合があり、多重継承の派生クラスはそのすべてのプロパティを継承します。親クラス。クラスは、複数の基底クラスを直接継承する派生クラスにすることができ、各基底クラスの継承制限 (public、protected、private) を個別に定式化することができます. 複数の基底クラスを統合する場合は、メンバーの競合に注意する必要があります.名前. 適用時に、メンバー名の競合によりあいまいさが生じ、コンパイラは解決できず、コンパイルに失敗します。
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Base2
{
public:
int ival;
int ival2;
protected:
float fval2;
private:
double dval2;
};
class Childn : public Base1, protected Base2
{
public:
void doit_public();
};
//test3.cpp
void Childn::doit_public()
{
// ival = 1; //继承冲突
ival2 = 1; //
fval = 1.0;
fval2 = 1.0;
// dval = 1.0; //error
// dval2 = 1.0; //error
};
void test_derive(void)
{
Childn cn;
// cn.ival = 1; //error,变量名冲突
cn.ival1 = 1; //OK
// cn.ival2 = 1; //error,protected继承
cn.doit_public();
};
また、派生クラスは引き続き他のクラスの基底クラスとして継承されます。
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Child1 : public Base1
{
public:
char cval;
void doit_public();
};
class CChild1 : public Child1
{
public:
void doit_public();
};
//test3.cpp
#include "test3.h"
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void CChild1::doit_public()
{
ival = 1; //Base1的成员
fval = 1.0; //Base1的成员
cval = 'a'; //Child1的成员
// dval = 1.0; //error
}
void test_derive(void)
{
CChild1 cc1;
cc1.ival = 1; //Base1的成员
cc1.cval = 'b'; //Child1的成员
cc1.doit_public();
};
コンストラクター初期化子は、基本クラスが構築される順序ではなく、基本クラスの初期化に使用される値のみを制御できます。基本クラスのコンストラクターは、クラス派生リストに表示される順序で呼び出されます。CChild1 の場合、基本クラスの初期化の順序は次のとおりです。
- Base1、CChild1 の直接の基本クラス Base1 から上位の最終基本クラス。
- Child1、直接の基本クラス。
- CChild1 を作成し、CChild1 自体のメンバーを初期化してから、そのコンストラクターの関数本体を実行します。
破棄の順序は、常にコンストラクターが実行される順序と逆の順序でデストラクタを呼び出します。この例では、デストラクタを呼び出す順序は ~CChild1、~Child1、~Base1 です。
基本クラスのポインターまたは参照を使用すると、基本クラスで定義された (または継承された) メンバーにのみアクセスでき、派生クラスで導入されたメンバーにはアクセスできません。クラスが複数の基本クラスから継承する場合、それらの基本クラス間に暗黙的な関係はなく、1 つの基本クラスのポインターを使用して他の基本クラスのメンバーにアクセスすることは許可されません。
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Base2
{
public:
int ival;
int ival2;
protected:
float fval2;
private:
double dval2;
};
class Child1 : public Base1
{
public:
char cval;
void doit_public();
};
class CChild1 : public Child1
{
public:
void doit_public();
};
class CChild2 : public Child1 , public Base2
{
public:
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
//test3.cpp
#include "test3.h"
#include <iostream>
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child1::doit_public\n";
}
void CChild1::doit_public()
{
ival = 1;
fval = 1.0;
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild1::doit_public\n";
}
void CChild2::doit_public()
{
// ival = 1; //error,冲突
ival1 = 1; //Base1
ival2 = 1; //Base2
fval = 1.0; //Base1
fval2 = 1.0; //Base2
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild2::doit_public\n";
}
void dfunc(Child1 &c)
{
c.doit_public();
};
void test_derive(void)
{
CChild1 cc1;
cc1.ival = 1;
cc1.cval = 'b';
cc1.doit_public(); //CChild1::doit_public
Child1 *pc1 = &cc1;
pc1->doit_public(); //Child1::doit_public
dfunc(c1); //Child1::doit_public
dfunc(cc1); //Child1::doit_public
CChild2 cc2;
cc2.doit_public(); //CChild2::doit_public
pc1 = &cc2;
pc1->doit_public(); //Child1::doit_public
};
多重継承では、基本クラスは派生階層に複数回出現する可能性があり、多重継承クラスはその親クラスのそれぞれから状態とアクションを継承します。、特定の型が慣例により 2 つの親クラスを継承し、これら 2 つの親クラスが他の基本クラスを共有する場合、この型の各オブジェクト インスタンスには 2 つの基本サブオブジェクトが含まれる場合があります。設計の観点からすると、この実装はまったく間違っています。
C++ では、この種の問題は仮想継承を使用して解決されます。仮想継承は、クラスがその仮想基本クラスの状態を共有したいことを示すメカニズムです。仮想継承では、特定の仮想基本クラスについて、そのクラスが派生階層で仮想基本クラスとして何回出現しても、継承される共有基本クラス サブオブジェクトは 1 つだけです。共有基底クラス サブオブジェクトは、仮想基底クラスと呼ばれます。
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Child1 : virtual public Base1 //虚继承,确保Base1仅一个副本存在
{
public:
char cval;
void doit_public();
};
class Child2 : protected virtual Base1 //虚继承,确保Base1仅一个副本存在
{
public:
char cval;
void doit_public();
};
class Child12 : public Child1 , public Child2 //虚继承,确保Base1仅一个副本存在
{
public:
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
//test3.cpp
#include "test3.h"
#include <iostream>
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child1::doit_public\n";
}
void Child2::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child2::doit_public\n";
}
void Child12::doit_public()
{
ival = 1; //OK,虚继承,冲突消除
fval = 1.0; //OK,虚继承,冲突消除
// dval = 1.0; //error
std::cout << "Child12::doit_public\n";
}
void test_derive(void)
{
//
Child12 c12;
c12.doit_public(); //Child12::doit_public
Child1 *pc1 = &c12;
pc1->doit_public(); //Child1::doit_public
Child2 *pc2 = &c12;
pc2->doit_public(); //Child2::doit_public
};
2.4 ネストされたクラス
A class can be defined within another class, つまり, class/struct または union の宣言が別のクラスに現れる. このようなクラスは、ネストされたクラスであり、ネストされた型とも呼ばれます. ネストされたクラスは独立したクラスであり、基本的にそれらの外側のクラスとは無関係であるため、外側のクラスとネストされたクラスのオブジェクトは互いに独立しています。入れ子になった型のオブジェクトには、囲んでいるクラスによって定義されたメンバーがなく、囲んでいるクラスのメンバーには、入れ子になったクラスによって定義されたメンバーがありません。
入れ子になったクラスの名前は、それを囲むクラスのスコープでは表示されますが、他のクラスのスコープや、外側のクラスが定義されたスコープでは表示されません。ネストされたクラス名は、別のスコープで宣言された名前と競合しません。ネストされたクラスは、外部クラスの静的メンバー、型名、および列挙メンバーを直接参照できます. もちろん、外部クラスのスコープ外の型名または静的メンバーを参照するには、スコープ決定演算子が必要です.
//
int x = 0; int y = 0;
class Test1
{
private:
/* data */
int val;
int x;
static int si;
enum eu{ a=1,b=2};
public:
int pival;
class Test2
{
private:
/* data */
int val;
// Test1 t1; //error
Test1 *pt1; //OK
public:
class Test3
{
private:
/* data */
int val;
public:
int pival;
};
Test3 t3;
// Test4 t4; //error;
class Test4 *pt4; //OK
void func(int val_)
{
val = val_; //local Test2变量
// x = val_; //error,外围类成员变量和全局变量同名
::x = val_; //OK,显式全局变量
y = val_; //OK,隐式匹配到全局变量
si = val_; //OK,静态成员
val = eu::a; //OK
// pival = val_;//error,外围类的public成员
t3.pival = val_; //OK
}
// void g1(Test4 *pt)
// {
// pt->pival = val; // error,Test4为不完整类
// };
class Test4
{
private:
/* data */
int val;
public:
int pival;
};
//
void g2(Test4 *pt)
{
pt->pival = val; // OK
};
};
void foo()
{
Test2 t2;
t2.func(10);
};
};
int Test1::si = 10;
void nestfunc(void)
{
Test1 t1;
t1.foo();
};
入れ子になったクラスの名前は、それを囲むクラスのスコープに存在し、入れ子になったクラスのメンバー関数からの名前検索は、入れ子になったクラスのスコープを調べた後に、囲んでいるクラスのスコープにアクセスします。ネストされたクラスは、ネストされていないクラスと同じ種類のメンバーを持つことができ、他のクラスと同様に、ネストされたクラスはアクセス ラベルを使用して独自のメンバーへのアクセスを制御します。メンバーは、パブリック、プライベート、または保護されていると宣言できます。外側のクラスは入れ子になったクラスのメンバーへの特別なアクセス権を持っておらず、入れ子になったクラスはその外側のクラスのメンバーへの特別なアクセス権を持っていません。
#include <iostream>
class Test1
{
private:
/* data */
int val;
public:
int pival;
class Test2
{
private:
/* data */
int val;
// Test1 t1; //error
Test1 *pt1; //OK,定义外围类指针
public:
Test2(){pt1 = new Test1();};
~Test2(){delete pt1; pt1 = nullptr;};
class Test3
{
private:
/* data */
int val;
public:
int pival;
};
Test3 t3; //定义内嵌类对象
void func(int val_)
{
t3.pival = val_; //OK
//确保指针有效
pt1->pival = val_; //OK
pt1->val = val_; //OK,直接访问了私有成员,宽松编译器
std::cout << "pt1->val = " << pt1->val << "\n";
}
};
};
入れ子になったクラスは、それを囲むクラスで型メンバーを定義します。他のメンバーと同様に、外側のクラスがこの型へのアクセスを決定します。外側のクラスのパブリック部分で定義されたネストされたクラスは、どこでも使用できる型を定義し、外側のクラスの保護された部分で定義されたネストされたクラスは、外側のクラス、フレンド、または派生クラスによってのみアクセスできる型を定義します。クラス クラスのプライベート セクションで定義されたネストされたクラスは、囲んでいるクラスまたはそのフレンドによってのみアクセスできる型を定義します。
class Test4
{
private:
/* data */
int val;
class Test5
{
public:
void func(int val_){;};
friend Test4;
private:
void func1(int val_){;};
};
public:
Test5 pt5; //OK
class Test6
{
public:
void func(int val_){;};
friend void nestfunc2(void);
private:
void func1(int val_){;};
};
Test5 pt6; //OK
};
void nestfunc1(void)
{
Test4 t4;
t4.pt5.func(10); //OK
// t4.pt5.func1(10); //error
t4.pt6.func(10); //OK
// t4.pt6.func1(10); //error
};
void nestfunc2(void)
{
// Test4::Test5 t5;//error
Test4::Test6 t6;
t6.func1(10); //OK,友元函数调用私有成员
}
入れ子になったクラスは、通常、囲んでいるクラスの実装の詳細をサポートします。囲んでいるクラスのユーザーが、ネストされたクラスの実装コードを見ないようにしたい場合があります。
入れ子になったクラスを宣言して、囲んでいるクラスの本体で定義できます。たとえば、Test8 クラスの定義を独自のファイルに入れたい場合、このファイルを Test7 クラスとそのメンバーの実装ファイルに含めることができます。
class Test7
{
private:
struct Test8;
Test8 *pt8;
};
//*.cpp
struct Test7::Test8//为了在外围类的外部定义类体,必须用外围类的名字限定嵌套类的名字
{
int val;
};
ネストされたクラスが外部クラスと同じ名前の場合、ネストは内部使用で最初に使用され、外部クラスでいつ使用されるかを確認するために明示的に指定する必要があります。
struct Test9;
class Test7
{
private:
struct Test9; //采用作用域内内嵌的
Test9 *pt9;
public:
struct Test10;
};
struct Test9 //外围的
{
int val;
};
struct Test7::Test9//内嵌的
{
double val;
};
struct Test7::Test10
{
char val;
};
void nestfunc3(void)
{
::Test9 t9; //使用外围的
Test7::Test10 t10; //使用内嵌的
}
2.5 部分クラス
クラスは関数の本体内で定義でき、そのようなクラスはローカル クラスと呼ばれます。ローカル クラスは、それが定義されているローカル スコープでのみ表示される型を定義します。ローカル クラスのメンバーは厳密に制限されています. ローカル クラスは、外側のスコープで定義された型名、静的変数、および列挙メンバーにのみアクセスでき、クラスを定義する関数で変数を使用することはできません.
#include <vector>
#include <algorithm>
#include <iostream>
void localFunc(void)
{
std::vector<int> v{1,2,3};
struct Local { //局部类
bool operator()(int n, int m) {
return n > m;
}
};
std::sort(v.begin(), v.end(), Local()); // C++11 起
for(int n: v) std::cout << n << ' ';
};
クラス宣言は関数の本体内で行われ、その時点でローカル クラスが定義されます。このクラスの名前は関数のスコープ内にのみ存在し、関数の外からアクセスすることはできません。
- ローカル クラスは静的データ メンバーを持つことはできません
- ローカル クラスのメンバー関数にはリンケージがありません
- ローカル クラスのメンバー関数は、クラス本体内で完全に定義する必要があります
- (C++14以上) クロージャー型以外のローカルクラスはメンバーテンプレートを持つことができません
- ローカル クラスにフレンド テンプレートを含めることはできません
- ローカル クラスは、クラス定義内でフレンド関数を定義できません
- 関数内のローカルクラスの周辺関数がアクセスできる名前(メンバー関数を含む)は、ローカルクラスからもアクセスできます。
- ローカル クラスはテンプレート引数として使用できません (C++11 まで)
- ローカル クラスは関数スコープで変数を使用できません
int a=1, val=2;
void foo(int val)
{
static int si = 10;
enum Loc { a = 1024, b };
// Bar bar_f; //error,未定义
// Bar is local to foo
class Bar {
public:
// static int lsi; //error
Loc locVal; // ok: uses local type name
int barVal;
void call_test() //OK
{
fooBar(locVal);
};
// void fooBar(Loc l = a); // ok: default argument is
void fooBar(Loc l = a){};//OK
// template <typename T> void template_func(T t){}; //error
// friend void frtest(int a); //error
void doit()
{
// barVal = val; // error: val is local to foo
barVal = ::val; // ok: uses global object
barVal = si; // ok: uses static local object
locVal = b; // ok: uses enumerator
}
private:
int pval; //通常是不必要的
};
// Bar::fooBar(Loc l){}; //error
Bar bar;
int tval = bar.locVal; // OK
// ...
};
ローカル クラス定義の本体での名前検索は、他のクラスと同じように機能します。ローカル クラス メンバー宣言で使用される名前は、その名前が使用される前にスコープ内に出現する必要があり、メンバー定義で使用される名前は、ローカル クラス スコープ内のどこにでも出現できます。クラス メンバーとして識別されない名前は、最初に外側のローカル スコープで検索され、次に関数自体を囲むスコープで検索されます。
周辺関数は, ローカルクラスのprivateメンバへの特別なアクセス権を持たない. 実際, 関数本体で定義されているので, ローカルクラスはローカルスコープにカプセル化されている. ローカルクラスのprivateメンバはほとんど不要. 通常, ローカルクラスのすべてのメンバパブリック会員です。
クラスは、部分クラス内にネストできます。この場合、ネストされたクラス定義は、ローカル クラス定義の本体の外に表示できますが、ネストされたクラスは、ローカル クラスが定義されているのと同じスコープ内で定義する必要があります。いつものように、入れ子になったクラス名は囲んでいるクラス名で修飾する必要があり、入れ子になったクラスの宣言はローカル クラス定義に含まれている必要があります。
void foo2()
{
class Bar {
public:
// ...
class Nested; // declares class Nested
};
// definition of Nested
class Bar::Nested {
// ...
};
}
部分クラス内にネストされたクラスは、それ自体がすべての追加制限を持つローカル クラスです。ネストされたクラスのすべてのメンバーは、ネストされたクラス自体の本体内で定義する必要があります。
3. ソースコード補足
コンパイル手順 g++ main.cpp test*.cpp -o test.exe -std=c++11
main.cpp
#include "test1.h"
#include "test2.h"
#include "test3.h"
class X {public: int val;};
class Y : public X {public: void setval();};
struct A { int id; };
struct B : public A { double val;};
union U{ int a; char b; };
struct s { int a; };
struct s; // 不做任何事(s 已经在此作用域定义)
void g()
{
struct s; // 新的局部结构体“s”的前置声明
// 它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
*(p->p) = 'a';
//p->a = 1; //error
// void doit()
// {
// *(p->p) = 'a';
// }
};
struct ts{
struct s; // 新的局部结构体“s”的前置声明,它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
void doit()
{
*(p->p) = 'a'; //OK
// p->a = 1; //error
};
};
int main(int argc, char* argv[])
{
nestfunc();
nstest();
localFunc();
test_derive();
PClass pc;
func1(pc);
return 0;
}
test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
class ATest;
class CTest{};
class BTest
{
public:
BTest();
~BTest();
void doit();
// ATest a; //error
ATest *pa; //OK
CTest c; //OK
};
#include <iosfwd> // 含有 std::ostream 的前置声明
class PTest
{
private:
/* data */
std::string *pstr;
// std::string str; //error
public:
friend std::ostream& operator<<(std::ostream& os, const PTest& s);
};
class DTest
{
private:
/* data */
public:
DTest *prt;
};
#endif //
test1.cpp
#include "test1.h"
class ATest
{
public:
int val;
};
BTest::BTest(){
pa = new ATest;
};
BTest::~BTest(){
delete pa; pa=nullptr;
}
void BTest::doit()
{
// ++a.val; //error
++(pa->val);//OK
};
test2.h
#ifndef _TEST_2_H_
#define _TEST_2_H_
#include <ostream>
class FClass
{
private:
/* data */
public:
void func();
};
class PClass
{
private:
int val;
void g(){val = 10; };
enum { a = 10 }; // 私有枚举项
class DeriveA { }; // 私有嵌套类型
public:
void func(){ val = 100; };
// protected:
// private:
friend class FClass2;
friend void FClass::func();
friend std::ostream& operator<<(std::ostream &os,const PClass &obj);
friend void func1(PClass& obj) { obj.val = 100; };
friend class FDerive;
};
class FClass2
{
public:
};
class FDerive : PClass::DeriveA // OK:友元能访问 PClass::DeriveA
{
PClass::DeriveA mx; // OK:友元的成员能访问 PClass::DeriveA
class DeriveB {
PClass::DeriveA my; // OK:友元的嵌套成员能访问 PClass::DeriveA
};
int v[PClass::a]; // OK:友元的成员能访问 PClass::a
};
class TF1
{
private:
/* data */
int val;
public:
friend class TF2;
// friend class NewTF{}; //error,不能像普通函数一样在类内定义主体
};
class TF2
{
public:
void func(){
TF1 tf1;
tf1.val = 10; //OK
}
friend class TF4;
};
class TF3 : public TF2
{
private:
/* data */
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能继承
}
};
class TF4
{
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能传递
}
};
class K;
namespace ns{
class Y f1(class T p); // 声明函数 ns::f1 并前置声明类 ns::T 与 ns::Y
class K f2(); // K 指代 ::K
};
void nestfunc(void);
void nstest(void);
void localFunc(void);
void foo(int val);
#endif //_TEST_2_H_
test2.cpp
#include "test2.h"
std::ostream& operator<<(std::ostream &os,const PClass &obj)
{
os << obj.val; //直接使用私有成员
return os;
}
void FClass::func()
{
PClass pc;
pc.val = 22; 直接使用私有成员
pc.g(); 直接使用私有成员
pc.func(); //
};
class K
{
private:
/* data */
int val;
public:
};
class ns::Y{};
class ns::T{};
ns::Y ns::f1(ns::T p)
{
return ns::Y();
};
::K ns::f2()
{
return ::K();
};
void nstest(void)
{
ns::T p;
ns::Y y = ns::f1(p);
::K k = ns::f2();
};
//
#include <iostream>
int x = 0; int y = 0;
class Test1
{
private:
/* data */
int val;
int x;
static int si;
enum eu{ a=1,b=2};
public:
int pival;
class Test2
{
private:
/* data */
int val;
// Test1 t1; //error
Test1 *pt1; //OK
public:
Test2(){pt1 = new Test1();};
~Test2(){delete pt1; pt1 = nullptr;};
class Test3
{
private:
/* data */
int val;
public:
int pival;
};
Test3 t3;
// Test4 t4; //error;
class Test4 *pt4; //OK
void func(int val_)
{
val = val_; //local Test2变量
// x = val_; //error,外围类成员变量和全局变量同名
::x = val_; //OK,显式全局变量
y = val_; //OK,隐式匹配到全局变量
si = val_; //OK,静态成员
val = eu::a; //OK
// pival = val_;//error,外围类的public成员
t3.pival = val_; //OK
//确保指针有效
pt1->pival = val_; //OK
pt1->val = val_; //OK
std::cout << "pt1->val = " << pt1->val << "\n";
}
// void g1(Test4 *pt)
// {
// pt->pival = val; // error,Test4为不完整类
// };
class Test4
{
private:
/* data */
int val;
public:
int pival;
};
//
void g2(Test4 *pt)
{
pt->pival = val; // OK
};
};
void foo()
{
Test2 t2;
t2.func(10);
};
};
int Test1::si = 10;
void nestfunc(void)
{
Test1 t1;
t1.foo();
};
class Test4
{
private:
/* data */
int val;
class Test5
{
public:
void func(int val_){;};
friend Test4;
private:
void func1(int val_){;};
};
public:
Test5 pt5; //OK
class Test6
{
public:
void func(int val_){;};
friend void nestfunc2(void);
private:
void func1(int val_){;};
};
Test5 pt6; //OK
};
void nestfunc1(void)
{
Test4 t4;
t4.pt5.func(10); //OK
// t4.pt5.func1(10); //error
t4.pt6.func(10); //OK
// t4.pt6.func1(10); //error
};
void nestfunc2(void)
{
// Test4::Test5 t5;//error
Test4::Test6 t6;
t6.func1(10); //OK
}
struct Test9;
class Test7
{
private:
struct Test8;
struct Test9; //采用局部的
Test8 *pt8;
Test9 *pt9;
public:
struct Test10;
};
struct Test7::Test8
{
int val;
};
struct Test9
{
int val;
};
struct Test7::Test9
{
double val;
};
struct Test7::Test10
{
char val;
};
void nestfunc3(void)
{
::Test9 t9;
Test7::Test10 t10;
}
//
#include <vector>
#include <algorithm>
#include <iostream>
void localFunc(void)
{
std::vector<int> v{1,2,3};
struct Local {
bool operator()(int n, int m) {
return n > m;
}
};
std::sort(v.begin(), v.end(), Local()); // C++11 起
for(int n: v) std::cout << n << ' ';
};
int a=1, val=2;
void foo(int val)
{
static int si = 10;
enum Loc { a = 1024, b };
// Bar bar_f; //error,未定义
// Bar is local to foo
class Bar {
public:
// static int lsi; //error
Loc locVal; // ok: uses local type name
int barVal;
// void fooBar(Loc l = a); // ok: default argument is
void call_test() //OK
{
fooBar(locVal);
};
void fooBar(Loc l = a){};//OK
// template <typename T> void template_func(T t){}; //error
// friend void frtest(int a); //error
void doit()
{
// barVal = val; // error: val is local to foo
barVal = ::val; // ok: uses global object
barVal = si; // ok: uses static local object
locVal = b; // ok: uses enumerator
}
private:
int pval; //通常是不必要的
};
// Bar::fooBar(Loc l){}; //error
Bar bar;
int tval = bar.locVal; // OK
// ...
};
void foo2()
{
class Bar {
public:
// ...
class Nested; // declares class Nested
};
// definition of Nested
class Bar::Nested {
// ...
};
}
test3.h
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Base2
{
public:
int ival;
int ival2;
protected:
float fval2;
private:
double dval2;
};
class Childn : public Base1, protected Base2
{
public:
void doit_public();
};
class Child1 : virtual public Base1
{
public:
char cval;
void doit_public();
};
class Child2 : protected virtual Base1
{
public:
char cval;
void doit_public();
};
class Child3 : private Base1
{
public:
char cval;
void doit_public();
};
class CChild1 : public Child1
{
public:
void doit_public();
};
class CChild2 : public Child1 , public Base2
{
public:
void doit_public();
};
class Child12 : public Child1 , public Child2
{
public:
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
test3.cpp
#include "test3.h"
#include <iostream>
void Childn::doit_public()
{
// ival = 1; //继承冲突
ival2 = 1; //
fval = 1.0;
fval2 = 1.0;
// dval = 1.0; //error
// dval2 = 1.0; //error
std::cout << "Childn::doit_public\n";
};
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child1::doit_public\n";
}
void Child2::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child2::doit_public\n";
}
void Child3::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child3::doit_public\n";
}
void CChild1::doit_public()
{
ival = 1;
fval = 1.0;
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild1::doit_public\n";
}
void CChild2::doit_public()
{
// ival = 1; //error,冲突
ival1 = 1; //Base1
ival2 = 1; //Base2
fval = 1.0; //Base1
fval2 = 1.0; //Base2
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild2::doit_public\n";
}
void dfunc(Child1 &c)
{
c.doit_public();
};
void Child12::doit_public()
{
ival = 1; //OK,虚继承,冲突消除
fval = 1.0; //OK,虚继承,冲突消除
// dval = 1.0; //error
std::cout << "Child12::doit_public\n";
}
void test_derive(void)
{
Child1 c1;
c1.ival = 1;
// c1.fval = 1.0; //error
c1.doit_public();
Child2 c2;
// c2.ival = 1; //error
// c2.fval = 1.0; //error
c2.doit_public();
Child3 c3;
// c3.ival = 1; //error
// c3.fval = 1.0; //error
c3.doit_public();
Childn cn;
// cn.ival = 1; //error,变量名冲突
cn.ival1 = 1; //OK
// cn.ival2 = 1; //error,protected继承
cn.doit_public();
CChild1 cc1;
cc1.ival = 1;
cc1.cval = 'b';
cc1.doit_public(); //CChild1::doit_public
Child1 *pc1 = &cc1;
pc1->doit_public(); //Child1::doit_public
dfunc(c1); //Child1::doit_public
dfunc(cc1); //Child1::doit_public
CChild2 cc2;
cc2.doit_public(); //CChild2::doit_public
pc1 = &cc2;
pc1->doit_public(); //Child1::doit_public
//
Child12 c12;
c12.doit_public(); //Child12::doit_public
// Child1 *pc1 = &c12;
pc1 = &c12;
pc1->doit_public(); //Child1::doit_public
Child2 *pc2 = &c12;
pc2->doit_public(); //Child2::doit_public
};