C++ のクラスは、キーワード、、または(最初の 2 つはまとめて非共用体クラスと呼ばれます)のいずれかで宣言されたユーザー定義の型またはデータ構造であり、メンバーとしてデータと関数 (メンバー変数とメンバー関数とも呼ばれます) を持ち、そのアクセスは、3 つのアクセス指定子private、protected 、またはpublicによって制御されます。デフォルトでは、キーワードで宣言された C++ クラスのメンバーへのアクセスはprivateです。private メンバーはクラスの外部からはアクセスできません。クラスのメンバー関数を介してのみアクセスできます。public メンバーはクラスへのインターフェースを形成し、クラスの外部からもアクセスできます。classstructunionclass
クラスデータ型のインスタンスはオブジェクトと呼ばれ、プログラマによって定義されたメンバ変数、定数、メンバ関数、およびオーバーロードされた演算子を含むことができます。
structとaの違いclassC++ では、キーワードで定義されたクラスは、デフォルトでプライベートclassメンバーと基底クラスを持ちます。構造体は、キーワードで定義されたクラスです。[ 1 ]そのメンバーと基底クラスは、デフォルトでパブリックです。実際には、構造体は通常、関数を持たないデータ用に予約されています。クラス/構造体から構造体を派生する場合、基底クラス/構造体のデフォルトのアクセス指定子は public です。また、クラスを派生する場合、デフォルトのアクセス指定子は private です。struct
集約クラスとは、ユーザーが宣言したコンストラクタ、プライベートまたは保護された非静的データメンバ、基底クラス、仮想関数を持たないクラスのことです。[ 2 ]このようなクラスは、波括弧で囲まれたカンマ区切りの初期化句のリストで初期化できます。[ 3 ]次のコードは、C と C++ の両方で同じ意味を持ちます。C ++20以降、集約型はC99スタイルの指定初期化子でも初期化できますが、C とは異なり、これらのフィールドは宣言で並べ替えることはできません。[ 4 ]
std :: stringを使用します。struct Address { string city ; int zip ; };struct Person { string name ; int age ; Address addr ; };// 通常の初期化リストAddress addr1 = { "Los Angeles" , 12345 }; Address addr2 { "Seattle" , 10001 };// 指定初期化子アドレスaddr3 = { . city = "Toronto" , . zip = 13853 };// 指定初期化子は、メンバーとして別の集約でも機能します: Person john { . name = "John" , . age = 20 , . address = { . city = "New York" , . zip = 54321 } };C言語と同様に、セミコロンはstructスラッシュの定義の後に記述する必要があります。スラッシュclassの定義とセミコロンの間には、セミコロンのインスタンスを宣言することができます。structclass
// a と b という名前の 2 つの Point を宣言します// a = {1, 2}、b = {3, 4} struct Point { int x ; int y ; } a { 1 , 2 }, b { 3 , 4 };POD構造体(Plain Old Data Structure)は、非POD構造体、非POD共用体(またはそのような型の配列)、または参照の非静的データメンバを持たず、ユーザー定義の代入演算子やユーザー定義のデストラクタを持たない非共用体集約クラスです。[ 1 ] POD構造体は、C++におけるCの構造体に相当するものと言えますstruct。ほとんどの場合、POD構造体は、Cで宣言された対応する構造体と同じメモリレイアウトを持ちます。[ 5 ]このため、POD構造体は、口語的に「Cスタイルの構造体」と呼ばれることもあります。[ 6 ]
C++ クラスには独自のメンバがあります。これらのメンバには、変数(他の構造体やクラスを含む)、メンバ関数と呼ばれる関数(特定の識別子またはオーバーロードされた演算子)、コンストラクタ、デストラクタが含まれます。メンバは、それぞれアクセス指定子を使用して、公開アクセスまたは非公開アクセスとして宣言されますpublic:。private:指定子の後に出現するメンバは、別の指定子が出現するまで、関連付けられたアクセス権限を持ちます。クラス間には継承もあり、protected:指定子を使用できます。
thisクラスが自身を参照できるようにするために、C++ ではthisすべてのメンバ関数にキーワードが実装されています。キーワードthisは現在のオブジェクトへのポインタとして機能します。[ 11 ]その型は現在のオブジェクトへのポインタです。
このthisキーワードは、クラス自体を戻り値とするメンバ関数にとって特に重要です。
class Point { private : double x ; double y ; public : // ...Point & operator += ( const Point & rhs ) noexcept { this -> x += rhs . x ; this -> y += rhs . y ; return * this ; } };上記のように、thisはポインタなので、返される参照に変換するにはアスタリスク(*) を使用する必要があります。
すべての関数の外側で定義されたクラスは、そのオブジェクトをプログラム内のどこからでも作成できるため、グローバルクラスと呼ばれます。関数本体内で定義されたクラスは、そのクラスのオブジェクトが関数スコープ内でのみ存在するため、ローカルクラスと呼ばれます。
非組合クラスは、キーワードclassまたはで宣言されます。メンバーの宣言はこの宣言内に記述されます。struct
上記の定義は機能的に同等です。どちらのコードでも、型のオブジェクトはと というPerson2 つのパブリックデータメンバーを持つものとして定義されます。閉じ括弧の後のセミコロンは必須です。nameage
これらの宣言のうちいずれか一方(両方ではない)の後、Person次のようにしてデータ型の新しい定義済み変数を作成できますPerson。
import std ;std :: stringを使用します。struct Person { string name ; int age ; };int main ( ) { Person a ; Person b ; a.name = " Calvin " ; b.name = "Hobbes" ; a.age = 30 ; b.age = 20 ; std :: println ( " { } : {} " , a.name , a.age ) ; std :: println ( " { } : { } " , b.name , b.age ) ; }上記のコードを実行すると、次の出力が得られます。
カルビン:30 ホッブズ:20
C++ クラスの重要な機能の一つにメンバ関数があります。各データ型には、そのデータ型のすべての (パブリックおよびプライベート) メンバにアクセスできる組み込み関数 (メンバ関数と呼ばれます) があります。これらの非静的メンバ関数の本体では、キーワードをthis使用して、関数が呼び出されるオブジェクトを参照できます。これは通常、オブジェクトのアドレスを関数への暗黙的な最初の引数として渡すことで実現されます。[ 12 ]上記のPerson型をもう一度例にとります。
import std ;std :: stringを使用します。class Person { private : string name ; int age = 5 ; public : // "name" と "age" はメンバ変数です。"this" キーワードは、 //メンバが呼び出されたオブジェクトのアドレスを値とする式です。 // 関数が const で宣言されているため、型は "const Person*" です。void printData () const { std :: println ( "{}: {}" , name , age ); } };上記の例では、printDataメソッドはクラスの本体で宣言され、クラス名に続けて修飾することで定義されています::。 とnameはどちらもageプライベート(クラスのデフォルト)ですが、 はprintDataパブリックとして宣言されています。これは、クラスの外部から使用する場合に必要なことです。
メンバー関数を使用するとprintData、印刷を次のように簡略化できます。
。 printData (); b 。printData ();ここでa、およびb上記は送信者と呼ばれ、関数が実行されるときにそれぞれが自身のメンバ変数を参照しますprintData()。
モジュール(つまりヘッダーファイル)が導入される以前は、クラスや構造体の宣言(インターフェースと呼ばれる)と定義(実装と呼ばれる)を別々の単位に分離するのが一般的でした。ユーザーが必要とするインターフェースはヘッダーファイルに格納され、実装はソースコードまたはコンパイル済みのファイルとして別々に格納されていました。
C++標準では、POD以外のクラスのメモリ上のレイアウトは規定されていません。例えば、多くの一般的なC++コンパイラは、親クラスのフィールドと子クラスのフィールドを連結することで単一継承を実装していますが、これは標準で要求されているものではありません。このようなレイアウトを採用することで、親クラスの型へのポインタを介して派生クラスを参照することが非常に容易になります。
例えば、
struct Scalar { int x ; };struct Vec2 : public Scalar { int y ; };Scalarを指すインスタンスは、Scalar* pメモリ上では次のようになるかもしれません。
┏━━━━━━━━━┓ ┃スカラー::x┃ ┗━━━━━━━━━┛ ↑ p
Vec2それを指すインスタンスはScalar* p次のようになります。
┏━━━━━━━━━┳━━━━━━━┓ ┃スカラー::x┃Vec2::y┃ ┗━━━━━━━━━┻━━━━━━━┛ ↑ p
したがって、オブジェクトのフィールドを操作するコードは、オブジェクトのフィールドの定義について何も考慮することなく、オブジェクト内のフィールドをScalar操作できます。適切に記述された C++ プログラムは、いずれの場合も、継承されたフィールドのレイアウトについていかなる仮定もすべきではありません。static_castまたはdynamic_cast型変換演算子を使用することで、ポインタが1つの型から別の型に正しく変換されることが保証されます。ScalarVec2Vec2
多重継承はそれほど単純ではありません。クラスがZクラスとを継承する場合X、Y両方の親クラスのフィールドを何らかの順序で格納する必要がありますが、派生クラスの先頭には(最大で)親クラスの1つしか配置できません。コンパイラがポインタを型からまたはに変換する必要がある場合はいつでも、Z派生クラスのアドレスから基底クラスのフィールドのアドレスへの自動変換が提供されます(通常、これは単純なオフセット計算です)。XY
キーワードfinalは、クラスをサブクラス化する方法を制限する。[ 13 ]クラスのサブクラスは、final親クラスによってマークされたメソッドをオーバーライドできない。[ 14 ] [ 15 ] final クラスは継承できない。[ 15 ]これにより、デ仮想化、つまりメソッド検索のためのvtableの使用の削除が可能になり、 final クラスのメソッド呼び出しのインライン化が可能になる。 [ 16 ] [ 17 ]
// クラス宣言の final は、そのクラスを拡張できないことを宣言しますclass Z final : public X , public Y { public : // メソッドシグネチャの final は、そのメソッドをそれ以上オーバーライドできないことを宣言しますvoid someOperation () override final { // ここで何かを実行します} };finalC++ では予約語ではなく、既存のコードベースでの識別子 'final' の使用と競合しないように、コンテキストキーワードとして定義されています。 [ 18 ] [ 19 ]
クラスは匿名である、つまり識別子や名前を持たない場合がある。[ 20 ]
struct { int x ; int y ; } coords ;座標. x = 5 ;C++では、匿名クラスも同様の方法で定義できます。ただし、匿名クラスには名前がないため、コンストラクタを持つことはできません。コンパイラによって生成される型は、`decltype`を使用して取得できます。
class { private : int value ; public : int getValue () { return value ; }void setValue ( int x ) { value = x ; }void display () { std :: println ( "値: {}" , value ); } } obj ;obj.setValue ( 15 ) ; int x = obj.getValue ( ); obj.display ( ) ; //値: 15Javaスタイルの匿名サブクラスも同様にエミュレートできます。
std :: chrono :: system_clockを使用します。// インターフェースのようなクラスclass Speaking { public : virtual void speak () = 0 ; };// Speaking "インターフェース"クラスを実装します。public Speaking { public : void speak () override { std :: println ( "現在の時刻は {}" , system_clock :: now ()); } } timeTeller ;C++ のラムダは匿名クラスに似ています。実際には関数オブジェクトであり、コンパイラによって生成された名前を持ち、 を実装しますoperator()。[ 21 ]
C++では、演算子(例+ - * /:)はプログラマのニーズに合わせてオーバーロードできます。これらの演算子はオーバーロード可能な演算子と呼ばれます。
慣例として、オーバーロードされた演算子は組み込みデータ型(int、floatなど)の場合とほぼ同じように動作するはずですが、これは必須ではありません。Integer変数に実際に整数が格納されているという構造体を宣言できますが、Integer * Integerを呼び出すことで、整数の積ではなく和が返される可能性があります。
struct Integer { int i = 0 ;Integer () = default ; Integer ( int i ) : i { i } {}整数演算子* ( const Integer & rhs ) const { return Integer ( i + rhs . i ); } };上記のコードでは、コンストラクタを使用して戻り値を「構築」していました。より分かりやすくするために(ただし、コンパイラがこのステートメントを上記の同等のステートメントに最適化できない場合、プログラムの効率が低下する可能性があります)、上記のコードは次のように書き換えることができます。
struct Integer { // ...整数演算子* ( const Integer & rhs ) const { Integer m ; m . i = i + rhs . i ; return m ; } };プログラマーは、宣言時に演算子のプロトタイプを記述しstruct、グローバルスコープで演算子の関数を定義することもできます。
struct Integer { int i = 0 ;Integer () = default ; Integer ( int i ) : i { i } {}Integer operator * ( const Integer & rhs ) const ; }; Integer Integer :: operator * ( const Integer & rhs ) const { return Integer ( i * rhs . i ); }i上記は送信者自身のメンバ変数を表し、一方はrhs.i引数変数からのメンバ変数を表しますrhs。
上記のコードでは、キーワードconstが2回出現します。最初の出現箇所である引数はconst integer& rhs、引数変数が関数によって変更されないことを示しています。宣言の最後にある2回目の出現箇所は、関数実行によって送信元が変更されないことをコンパイラに約束するものです。
ではconst integer& rhs、アンパサンド(&)は「参照渡し」を意味します。関数が呼び出されると、変数の値ではなく、変数への参照が関数に渡されます。
演算子の結合規則と優先順位は変更できないことに注意してください。
二項演算子(2つの引数を持つ演算子)は、「識別子」演算子(何らかの値)を持つ関数を宣言することでオーバーロードされます。この演算子は、1つの引数を呼び出すように指定されます。演算子の左側の変数は送信元、右側の変数は引数となります。
整数i = 1 ;// 構造体変数は、最初の引数のみを指定してコンストラクタを呼び出す場合と同様に、このように初期化できます。 Integer j = 3 ;// 変数名は構造体のメンバ変数の名前とは独立しています。 Integer k = i * j ; std :: println ( "{}" , k . i );「3」が印刷されます。
以下は、バイナリオーバーロード可能な演算子の一覧です。
同じ構造体型の2つの変数間の代入演算子「= 」は、デフォルトでは一方の変数の内容全体を他方の変数にコピーするようにオーバーロードされています。必要に応じて、別の演算子で上書きすることも可能です。
演算子は一つずつオーバーロードする必要があります。つまり、オーバーロード同士は関連付けられません。たとえば、<は必ずしもの反対ではありません>。
上記のように、一部の演算子は左側に送信者、右側に引数という2つの項を取りますが、一部の演算子は送信者という1つの引数しか取らず、それらは「単項演算子」と呼ばれます。例としては、負の符号(左側に何も置かれていない場合)や「論理否定」(感嘆符、!)などがあります。
単項演算子の送信元は、演算子の左側または右側のどちらにでも指定できます。以下は、オーバーロード可能な単項演算子の一覧です。
送信元が右側にある単項演算子のオーバーロードの構文は次のとおりです。
T operatorX()送信者が左側にいる場合、宣言は次のようになります。
T operatorX(int)X上記は、オーバーロードされる演算子を表します。T戻り値のデータ型(int、、bool構造体など)に置き換えてください。
このintパラメータは、送信者が演算子の左側にあることを示すための慣例に過ぎず、本質的には何の意味もありません。
const必要に応じて、引数を宣言の末尾に追加できます。
C++クラスでは、角括弧[]と丸括弧を()オーバーロードできます。角括弧には、引数、初期化子リスト(C++11以降)、または任意のパラメータパック(C++23以降)が必ず1つ含まれていなければなりませんが、丸括弧には任意の数の引数、または引数なしの引数を含めることができます。
以下の宣言は角括弧をオーバーロードします。
T operator[] (x1, x2, ...))括弧内の内容はargument部品に指定されています。
丸括弧も同様の方法でオーバーロードされます。
T operator() (x1, x2, ...)演算子呼び出しの括弧内の内容は、2番目の括弧で指定されます。
上記で指定した演算子に加えて、矢印演算子()、アスタリスク付き矢印()、キーワード、およびキーワードもオーバーロードできます。これらのメモリまたはポインタ関連の演算子は、オーバーロード後にメモリ割り当て関数を処理する必要があります。代入演算子()と同様に、特別な宣言がない場合はデフォルトでオーバーロードされます。->->*newdelete=
プログラマーは、変数を宣言時にデフォルト値または特定の値に設定したい場合があります。これは、コンストラクタを宣言することで実現できます。
std :: stringを使用します。class Person { private : string name ; int age ; public : Person ( string name , int age ) { this- > name = name ; this- > age = age ; } };メンバー変数は、以下の例のようにコロンを使用して初期化子リストで初期化できます。これは、代入演算子を使用するのではなく、(コンストラクタを使用して)初期化するという点で上記とは異なります。クラス型の場合は、直接構築するだけで済むため、この方法の方が効率的です。一方、代入の場合は、まずデフォルトコンストラクタを使用して初期化し、次に別の値を代入する必要があります。したがって、可能な限り、コンストラクタ本体の代入よりもメンバーの初期化が一般的に推奨されます。[ 22 ]また、一部の型(参照やconst型など)は代入できないため、初期化子リストで初期化する必要があります。
std :: stringを使用します。class Person { private : string name ; int age ; public : Person ( string name , int age ) : name { name }, age { age } {} };中括弧は、たとえ空であっても省略できないことに注意してください。
デフォルト値を初期化する際に、最後の引数にデフォルト値を指定できます。
std :: stringを使用します。class Person { // ...Person ( string name = "" , int age = 0 ) : name { name }, age { age } {} };上記の例でコンストラクタに引数が指定されていない場合、それは引数なしで次のコンストラクタを呼び出すこと(デフォルトコンストラクタ)と同等です。
std :: stringを使用します。class Person { // ...Person () :名前{ "" }、年齢{ 0 } {} };コンストラクタの宣言は、データ型と同じ名前の関数のように見えます。実際、コンストラクタの呼び出しは関数呼び出しの形式をとることができます。その場合、初期化されたPerson型変数は戻り値と考えることができます。
int main () { Person r = Person ( "Wales" , 40 ) ; r.print ( ); }上記の例と同じことを行う別の構文は次のとおりです。
int main () { Person r ( "Wales" , 40 ); r.printData ( ) ; }変数に関連する場合も関連しない場合も含め、特定のプログラム動作をコンストラクタ本体内に追加することができます。
std :: stringを使用します。class Person { // ...Person ( string name , int age ) : name { name }, age { age } { std :: println ( "こんにちは!私の名前は{}で、{}歳です。" , name , age ); }; }上記のコンストラクタを使用すると、デフォルトPersonコンストラクタが呼び出されたときに「Hello!」と表示されます。
デフォルトコンストラクタは、クラスにコンストラクタが定義されていない場合に呼び出されます。
struct Scalar { int i ; };// 括弧を使用してオブジェクトを作成します。Scalar * a = new Scalar (); // デフォルトコンストラクタを呼び出し、b は '0' で初期化されます。// 括弧を使用せずにオブジェクトを作成します。Scalar * b = new Scalar ; // メモリを割り当ててからデフォルトコンストラクタを呼び出すと、b の値は '0' になります。// new なしでオブジェクトを作成します。Scalar c ; // スタック上に a の領域を確保し、b には未知のガベージ値が格納されます。// 割り当てられたオブジェクトは解放する必要がありますdelete a ; delete b ;ただし、クラスにユーザー定義コンストラクタが定義されている場合、上記の2つの宣言はどちらもこのユーザー定義コンストラクタを呼び出し、定義されたコードが実行されますが、変数bにはデフォルト値は割り当てられません。
コンパイラが提供するデフォルトコンストラクタは、 を使用することで保持できますが、 を使用することで明示的に無効にすることもできます。これは、コンパイラによって生成される他の特殊メンバ関数についても同様です。=default;=delete;
デストラクタはコンストラクタの逆です。クラスのインスタンスが破棄されるときに呼び出されます。たとえば、ブロック(中括弧「{}」のセット)内で作成されたクラスのオブジェクトが閉じ括弧の後に削除されると、デストラクタが自動的に呼び出されます。変数が格納されているメモリ位置が空になると、デストラクタが呼び出されます。デストラクタは、クラスのインスタンスが破棄されるときに、ヒープに割り当てられたメモリや開いているファイルなどのリソースを解放するために使用できます。
デストラクタを宣言する構文は、コンストラクタの場合と似ています。戻り値はなく、関数名はクラス名の先頭にチルダ(~)を付けたものです。
class Person { // ...~ Person () { std :: println ( "年齢{}の{}を削除します" , name , age ); } }C++では、クラス宣言はクラステンプレートから生成できます。このようなクラステンプレートは、クラスの集合を表します。実際のクラス宣言は、 1つ以上のテンプレート引数を使用してテンプレートをインスタンス化することによって得られます。特定の引数セットを使用してインスタンス化されたテンプレートは、テンプレート特殊化と呼ばれます。
C++ の構文は、クラスのあらゆる側面を基本データ型のそれと似たものにしようとしています。そのため、オーバーロードされた演算子によって、クラスを整数や浮動小数点数と同じように操作でき、クラスの配列は角括弧構文 ( 、ここではクラス名、は配列名、はその長さ) で宣言でき、クラスへのポインタは組み込みデータ型へのポインタと同じ方法で逆参照できます。Xa[N]={/* ... */};XaN
構造体のメモリ使用量は、構成要素となる変数のメモリサイズの合計以上になります。Point以下の構造体を例に挙げます。
struct Point { int x ; int y ; };この構造体は2つの整数で構成されています。多くの最新のC++コンパイラでは、整数は デフォルトで32ビット整数であるため、各メンバ変数は4バイトのメモリを消費します。したがって、構造体全体では、少なくとも(または正確に)8バイトのメモリを消費します。
+----+----+ | x | y | +----+----+
ただし、コンパイラは、特定のコンピュータアーキテクチャで適切なデータアライメントを確保するために、変数間または構造体の末尾にパディングを追加する場合があります。多くの場合、変数を32ビットアライメントするためにパディングが追加されます。たとえば、構造体
struct Data { char p ; char q ; char r ; short s ; int i ; double d ; };次のように表現できます
+-+-+-+-+--+--+----+--------+ |p|q|r|X|s |XX| i | d | +-+-+-+-+--+--+----+--------+
メモリ内では、Xは4バイトアライメントに基づくパディングされたバイトを表します。
構造体はメンバ変数の宣言と初期化にポインタや配列を使用する可能性があるため、構造体のメモリ消費量は必ずしも一定ではありません。メモリサイズが一定でないもう一つの例は、テンプレート構造体です。
ビットフィールドは、整数型よりも少ないストレージ容量でクラスメンバーを定義するために使用されます。このフィールドは、整数型(、、、など)および列挙型(例えば、生の数値データを表す非代数的な列挙型)にのみ適用され、intfloat型やdouble型は除外されます。charshortlongstd::byte
struct Data { uint32_t a : 2 ; // 可能な値は 0..3、int の最初の 2 ビットを占有しますuint32_t b : 3 ; // 可能な値は 0..7、int の次の 3 ビットを占有しますuint32_t : 0 ; // 次の整数型の末尾に移動しますuint32_t c : 2 ; uint32_t : 4 ; // c と d の間に 4 ビットをパディングしますuint32_t d : 1 ; uint32_t e : 3 ; };4バイト整数 4バイト整数 [1][2][3][4][5][6][7][8] [1] [2] [3] [4] [a][a][b][b][b][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [5] [6] [7] [8] [c][c][ ][ ][ ][ ][ ][d][e] [e][e][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ] [ ][ ][ ][ ][ ][ ][ ][ ]
共用体にはビットフィールドのメンバーを含めることもできます。
union Data { uint32_t a : 2 ; uint32_t b : 3 ; uint32_t : 0 ; // 何もしませんuint32_t c : 2 ; uint32_t : 4 ; // この場合は何もしません。ビットフィールドの幅が十分に大きければ、共用体のサイズがそれに合わせて変更されますuint32_t d : 1 ; uint32_t e : 3 ; };多くのプログラマーは、構造体を含む関数の引数を宣言する際に、アンパサンド(& )を使用することを好みます。これは、アンパサンドを使用することで、関数に渡す必要があるのは1ワード(通常、32ビットマシンでは4バイト、64ビットマシンでは8バイト)のみ、つまり変数のメモリ位置だけとなるためです。値渡しを使用する場合、関数が呼び出されるたびに引数をコピーする必要があり、大きな構造体ではコストがかかります。
参照渡しでは、関数によって変更される元の構造が公開されるため、意図しない場合は、const関数がパラメータを変更しないことを保証するためにキーワードを使用する必要があります(const-correctnessを参照)。
一般的な参考文献: