C++
基本的な考え方
インライン関数
- インライン関数は、マクロ展開と同様に、関数のコンパイル時に自動的に展開され、コンパイル後には存在しません。
- インライン関数はヘッダーファイルに定義することができ、複数インクルードしても繰り返し定義エラーが発生しません。
- インライン関数はコンパイル時のマクロのようなもので、コンパイル後のリンク時には存在しません。
- inline キーワードは関数定義時に追加する必要があり、関数宣言時に使用した場合は無効となります。
- インライン関数には宣言を含めるべきではありませんが、宣言によってエラーは報告されません。
- インライン関数の定義と宣言を別のファイルに置くとエラーが発生します。正確には、リンク時にエラーが報告されます。したがって、インライン関数の定義は通常、ヘッダー ファイルに配置されます。
- インライン関数には 2 つの関数があります。1 つ目は次のとおりです。関数呼び出しのオーバーヘッドを排除する、2つ目はマクロをパラメータに置き換える。
デフォルトパラメータ
関数を作成するとき、仮パラメータのデフォルト パラメータを設定できます。デフォルト パラメータには式を使用することもできます。デフォルト パラメータは、仮パラメータ リスト の最後にのみ配置できます。特定の仮パラメータにデフォルト値が設定されると、その場合、その背後にある仮パラメータもデフォルト値が存在する必要があります。C++ では、特定のスコープ内でデフォルト パラメーターを 1 回のみ指定できると規定しています。
定義関数と宣言関数のデフォルト パラメータが異なる場合は、現在のスコープのデフォルト パラメータがコンパイル時に使用されます。同じ関数を複数回宣言できます。
関数のオーバーロード
C++ では、パラメーター リストが異なる限り、複数の関数に同じ名前を付けることができます。関数のオーバーロード(関数のオーバーロード)パラメータリストパラメータ シグネチャとも呼ばれ、パラメータの種類、パラメータの数、パラメータの順序など、相違点がある限り、異なるパラメータ リストと呼ばれます。
関数のオーバーロードのルール:
- 関数名は同じである必要があります
- 引数リストは異なっていなければなりません
- 関数の戻り値の型は同じでも異なっていてもよい
- 戻り値の型が異なるだけでは、関数のオーバーロードの基礎として使用できません。
C++ は、コンパイル時にパラメーター リストに従って関数の名前を変更します。たとえば、関数のvoid Swap(int a, int b)
名前は に変更されSwap_int_int
、void Swap(float x, float y)
関数の名前は に変更されますSwap_float_float
。関数呼び出しが発生すると、コンパイラは入力された実パラメータを 1 つずつ照合し、対応する関数を選択します。照合に失敗した場合、コンパイラはオーバーロード解決と呼ばれるエラーを報告します。
関数のオーバーロードは文法レベルでのみ行われますが、本質的に異なる関数は異なるメモリを占有します。関数のオーバーロード、パラメーターの型が少なすぎる、または多すぎると、曖昧さエラーが発生する可能性があります。
外部「C」使い方は大きく分けて2つあります。
- C++ コードの文のみを変更する場合は、コード行の先頭に直接追加します。
- C++ コードの一部を装飾するために使用する場合は、extern "C" に中括弧 {} を追加し、装飾するコードを括弧で囲みます。
クラスとオブジェクト
コンセプト
オブジェクトを作成するためのテンプレートと同様に、クラスは複数のオブジェクトを作成でき、各オブジェクトはクラス型の変数です。オブジェクトを作成するプロセスは、クラスのインスタンス化とも呼ばれます。各オブジェクトはクラスの具体的なインスタンスであり、クラスのメンバー変数とメンバー関数を備えています。
構造体と同様、クラスは複雑なデータ型の宣言にすぎず、メモリ空間を占有しません。
クラスを定義するには、 class キーワードを使用します。通常、クラス名の最初の文字は区別するために大文字で表記されます。{} 内にはクラスのメンバー変数とメンバー関数があり、これらを総称して クラス のメンバーと呼びます。クラス定義の後にはセミコロンが続きます。
クラスは単なるテンプレートであり、コンパイル後にメモリ領域を占有しないため、クラス定義時にメンバー変数を初期化できないデータを保存する場所がないためです。オブジェクトの作成後にのみメモリがメンバー変数に割り当てられ、値を割り当てることができます。
クラス内で直接関数を定義する場合は関数名の前にクラス名を付ける必要はありませんが、クラス外で関数を定義する場合は関数名にクラス名を付ける必要があります。ドメインリゾルバー::
。
クラス本体で定義されたメンバー関数は自動的にインライン関数になりますが、クラスの外で定義されたメンバー関数はそうではありません。インライン関数は、コンパイル時に関数呼び出しを関数本体に自動的に置き換えますが、これは期待どおりではないため、関数をクラス本体内で宣言し、クラス本体の外で定義することをお勧めします。
クラスのアクセス権
C++ パス公共、保護された、プライベートメンバー変数とメンバー関数へのアクセスを制御する 3 つのキーワード。これらはそれぞれパブリック、プロテクト、プライベートであり、メンバー アクセス修飾子と呼ばれます。いわゆるアクセス権は、このクラスのメンバーを使用できるかどうかです。
クラス内では、すべてのメンバー関数とメンバー変数は、アクセス権の制限なしに相互にアクセスできます。
クラスの外部では、メンバーは object を通じてのみアクセスでき、パブリック プロパティのメンバーのみが object を通じてアクセスできます。
メンバ変数はm_
慣例的に で始まる記述が多く、m_
メンバ変数の先頭が一目で分かり、メンバ関数内の仮引数の名前が区別できます。メンバー変数への代入には通常、パブリック関数である set および get が使用され、変数名の後に関数を続けることができます。public と private が記述されていない場合、デフォルトは private です。クラス本体では、private と public は複数回出現することができ、別のアクセス修飾子が出現するかクラス本体の終わりまで有効なスコープはありません。
set関数やget関数以外にも、オブジェクト作成時にコンストラクタを呼び出して各メンバ変数を初期化することもできますが、コンストラクタは各メンバ変数の値を初期化することしかできず、メンバ変数に値を代入することはできません。
クラスはオブジェクトを作成するためのテンプレートであり、メモリ領域を占有せず、コンパイルされた実行可能ファイルには存在しませんが、オブジェクトは格納するためにメモリを必要とする実際のデータです。オブジェクトが作成されると、メモリがスタックまたはヒープに割り当てられます。異なるオブジェクトのメンバー変数の値は異なる場合があるため、保存には別のメモリが必要です。
== 異なるオブジェクトのメンバー関数のコードは同じであり、コードの断片を 1 つのコピーに圧縮できます。==コンパイラはオブジェクトのメンバー変数とメンバー関数を別々に保存します。メンバー変数は個別にメモリを割り当てますが、メンバー関数は関数コードの一部を共有します。メンバ変数はスタック領域またはヒープ領域にメモリを確保し、メンバー関数はコード領域にメモリを割り当てます。オブジェクトのサイズ値はメンバー変数の影響のみを受け、メンバー関数とは関係ありません。クラスのメモリ分散にもメモリ アラインメントの問題が発生します。
C++ 関数のコンパイル
C言語の機能はコンパイル時にアンダースコアを追加するだけです(コンパイラの実装が異なると異なります)。
C++ がコンパイルされると、関数の名前は、名前空間、関数が属するクラス、パラメーター リスト(パラメーター シグネチャ) およびその他の情報に従って変更されます。新しい関数名を形成します。この新しい関数名はコンパイラだけが知っています。関数の名前を変更するこのプロセスは呼び出されます。ネームコード。名前のエンコード プロセスは可逆的であり、一方の名前がわかれば、もう一方の名前を推測できます。
注: コンパイラによって生成された新しい関数名を確認したい場合は、関数を定義せずに関数を宣言することしかできません。そのため、関数呼び出し時にエラーが表示され、エラー メッセージから新しい関数名を確認できます。 。
メンバー関数呼び出し
メンバ関数は最終的にオブジェクトとは関係のない関数にコンパイルされますが、関数内でメンバ変数が使用されていない場合は直接呼び出すことができます。
メンバー変数がメンバー関数で使用される場合、メンバー関数のコンパイル時にパラメーターを追加する必要があり、現在のオブジェクトがポインターとして渡され、メンバー変数はポインターを介してアクセスされます。これは、オブジェクトを使用して関数を検索するときに想像していたものとは異なり、実際には関数を通じてオブジェクトを検索します。
コンストラクタ
クラス名と同じ名前で、戻り値がなく、ユーザーが明示的に呼び出す必要がなく、ユーザーが呼び出すことはできませんが、クラス名と同じ名前の特殊なメンバー関数があり、ユーザーが呼び出すと自動的に実行される関数です。オブジェクトが作成されます。この特別なメンバー関数はコンストラクタ(コンストラクタ)。
コンストラクターを呼び出す場合は、オブジェクトの作成時に実際のパラメーターを渡す必要があります。実際のパラメーターは で囲まれます()
。これは通常の関数呼び出しと非常によく似ています。スタック上にオブジェクトを作成する場合、実際のパラメータはオブジェクト名の後に配置されますStudent stu("小明", 15, 92.5f);
。オブジェクトがヒープ上に作成されると、実装はクラス名の後に配置されますnew Student("李华", 16, 96)
。コンストラクターは public 属性です。それ以外の場合、オブジェクトの作成時に呼び出されません。
戻り値を受け取る変数がないため、コンストラクターには戻り値がありません。そのため、定義されているか宣言されているかにかかわらず、戻り値の型を関数名の前に指定することはできず、関数本体で return ステートメントを使用することもできません。コンストラクターはオーバーロード可能です。クラスには複数のオーバーロードされたコンストラクターを含めることができます。オブジェクトの作成時に、渡されたパラメーターに従ってどのコンストラクターを呼び出すかが判断されます。
コンストラクターの呼び出しは、必須はい、クラスでコンストラクターが定義されると、オブジェクトの作成時に呼び出されます。複数のオーバーロードされたコンストラクターがある場合、提供される実際のパラメーターは、オブジェクトの作成時にコンストラクターの 1 つと一致する必要があります。オブジェクトの作成時に呼び出されるコンストラクターは 1 つだけです。コンストラクターは通常、プロジェクトで初期化作業を行うために使用されます。コンストラクターが定義されていない場合、コンパイラーはデフォルトのコンストラクター内で空の コンストラクター を自動的に生成します。クラスにはコンストラクターが必要です。コンストラクターはユーザーによって定義されるか、コンパイラーによって自動的に生成されます。ユーザーがコンストラクターを定義する場合、コンパイラーはコンストラクターを自動的に生成しません。パラメータを指定せずにコンストラクタを呼び出す場合は、括弧を省略できます。
コンストラクターの初期化を使用すると、初期化リストつまり、コンストラクターを定義する場合、関数本体の変数に値を代入する必要はありませんが、関数ヘッダーと関数本体の間にコロンを使用し、その後に内部に相当するステートメントを使用します。m_name(name), m_age(age), m_score(score)
関数本体m_name = name; m_age = age; m_score = score
の。例えば:
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
//TODO:
}
初期化子リストを使用することに効率上の利点はなく、書き込みと読み取りの利便性のみを目的としています。メンバー変数の初期化順序は、初期化リストにリストされている変数の順序とは関係がなく、メンバー変数がクラス内で宣言される順序のみに関係します。スタックにメモリを割り当てる場合、初期値は不定ですが、ヒープにメモリを割り当てる場合、初期値は 0 です。
コンストラクター初期化リストのもう 1 つの重要な機能は、const メンバー変数を初期化することです。これが const 変数を初期化する唯一の方法です。。
デストラクター
オブジェクトを破棄すると、システムはメモリを解放したり、開いているファイルを閉じたりするなど、クリーンアップする関数を自動的に呼び出します。この関数はデストラクター。デストラクター(デストラクター) も特別なメンバー関数であり、戻り値がなく、明示的に呼び出す必要がなく、呼び出すこともできません。オブジェクトが破棄されると自動的に呼び出されます。
コンストラクターの名前はクラス名と同じで、デストラクターの名前がクラス名の前に追加されます~
。デストラクターにはパラメーターがなく、オーバーロードできないため、クラスはデストラクターを 1 つだけ持つことができます。
C++ では、newとdelete を使用してメモリの割り当てと解放を行います。mallocや free との最大の違いは、new を使用してメモリを割り当てた場合はコンストラクタが呼び出され、delete を使用してメモリを解放した場合はデストラクタが呼び出される点です。
グローバルオブジェクトメモリ領域に格納されるグローバル データ領域、これらのオブジェクトのデストラクタはプログラム実行の最後に呼び出されます。ローカルオブジェクトメモリに格納されたスタック領域、これらのオブジェクトのデストラクタは、関数の実行終了時に呼び出されます。newで作成したオブジェクトはメモリ上のヒープ領域に配置されており、deleteを呼び出すとデストラクタが呼び出されますが、deleteが無い場合はデストラクタは実行されません。
C++ オブジェクトの配列
配列の各要素はオブジェクトであり、そのような配列はオブジェクト配列と呼ばれます。コンストラクターに複数の要素がある場合、配列初期化子リストにはコンストラクターへの呼び出しが明示的に含まれます。
C++ メンバー オブジェクト
クラスのメンバ変数がクラスのオブジェクトである場合、それはメンバ オブジェクトと呼ばれます。メンバー オブジェクトを含むクラスは、囲みクラスと呼ばれます。閉じたクラスのオブジェクトを作成するときは、それに含まれるメンバー オブジェクトも作成する必要があります。これにより、メンバー オブジェクト コンストラクターの呼び出しがトリガーされます。
閉じたクラス オブジェクトが生成されると、すべてのメンバー オブジェクトのコンストラクターが最初に実行され、次に閉じたクラス自体のコンストラクターが実行されます。メンバー オブジェクト コンストラクターの実行順序は、コンストラクターの初期化リストに表示される順序に関係なく、クラス定義内のメンバー オブジェクトの順序と一致します。
クローズドクラスのオブジェクトが終了すると、クローズドクラスのデストラクタが最初に実行され、次にオブジェクトメンバのデストラクタが実行されます。メンバオブジェクトのデストラクタの実行順序は、デストラクタの実行順序と逆になります。つまり、最初に構築され、次に実行された破壊です。
このポインタ
これこれは C++ のキーワードであり、 const ポインタでもあります。現在のオブジェクトを指すこれを通じて、現在のオブジェクトのすべてのメンバーにアクセスできます。これはクラス内でのみ使用できます、public、protected、private 属性を含むクラスのすべてのメンバーに、これを通じてアクセスできます。
これはクラス内にありますが、オブジェクトの作成後にのみ値が割り当てられ、この割り当てプロセスはコンパイラによって自動的に完了します。これは実際にはメンバー関数の仮パラメータです。メンバー関数を呼び出すとき、オブジェクトのアドレスが実パラメータとして this に渡されますが、これはコンパイル段階でコンパイラ自体によってパラメータ リストに追加されます。
パラメーターとしてのこれは本質的にメンバー関数のローカル変数であり、メンバー関数内でのみ使用でき、オブジェクトを通じて関数が呼び出された場合にのみ割り当てられます。。
メンバー関数は、コンパイル中にオブジェクトとは関係のない通常の関数にコンパイルされます。メンバー変数を除いて、すべての情報が失われます。そのため、コンパイル時に、現在のオブジェクトを渡すために追加のパラメーターをメンバー関数に追加する必要があります。メンバー変数とメンバー関数を関連付けるために、この追加パラメーターは this です。
静的メンバー変数
複数のオブジェクト間でデータを共有したい場合は、静的メンバー変数を使用して、複数のオブジェクトがデータを共有するという目標を達成できます。静的メンバー変数は、キーワード static で変更される特別なメンバー変数です。静的メンバ変数はクラスに属し、特定のオブジェクトに属するものではなく、複数のオブジェクトを作成した場合でもメモリが確保され、すべてのオブジェクトがそのメモリ上のデータを使用します。。
静的メンバー変数は、クラス宣言の外で初期化する必要があります。静的メンバー変数は初期化時に静的に追加できませんが、データ型が必要です。
静的メンバー変数のメモリは、クラスの宣言時やオブジェクトの作成時に割り当てられず、クラス外で初期化されるときに割り当てられます。。したがって、クラスの外部で初期化されていない静的メンバー変数は使用できません。静的メンバー変数には、クラスまたはメンバー変数を通じてアクセスできます。。静的メンバー変数はオブジェクトのメモリを占有しませんが、すべてのオブジェクトの外部にメモリを開放し、オブジェクトが作成されていなくてもアクセスできます。
通常の静的変数と同様に、静的メンバー変数はメモリ パーティションのグローバル データ領域にメモリが割り当てられ、プログラムが終了するまで解放されません。静的メンバー変数は初期化する必要があり、クラスの外部で初期化する必要があります。初期化は割り当てても割り当てなくてもよく、デフォルトの割り当ては 0 です。
静的メンバー変数には、オブジェクト名またはクラス名のいずれかによってアクセスできます。キーワードのアクセス制限が必要です。
静的メンバー関数
通常のメンバー関数はすべてのメンバーにアクセスできますが、静的メンバー関数は静的メンバーのみにアクセスできます。コンパイラは通常のメンバー関数をコンパイルするときに、仮パラメータ this を関数に追加し、現在のオブジェクトのアドレスを this に渡します。そのため、通常のメンバー関数は、オブジェクトの作成後にオブジェクトを通じてのみ呼び出すことができます。
静的メンバー関数はクラスを通じて直接呼び出すことができ、コンパイラーは this パラメーターを静的メンバー関数に追加せず、現在のオブジェクトのアドレスを必要としません。通常のメンバ変数はオブジェクトのメモリを占有しますが、静的メンバにはこのポインタがないため、それがどのオブジェクトを指しているのかがわからず、オブジェクトのメンバ変数にアクセスできません。したがって、静的メンバーは通常のメンバー変数にはアクセスできず、静的メンバー変数にのみアクセスできます。
C++ では、静的メンバー関数の主な機能は静的メンバーにアクセスすることです。静的メンバー関数はオブジェクトおよびクラスを通じて呼び出すことができますが、通常はクラスを通じて呼び出されます。
const メンバー関数とメンバー変数
const メンバー変数初期化は、コンストラクターの初期化子リストを通じてのみ行われます。
const メンバー関数クラス内のすべてのメンバー変数を使用できますが、その値は変更できません。const メンバー関数も呼び出されますconst メンバー関数、通常は get 関数を const 関数に設定します。
const メンバー関数と const メンバー変数では、定義と宣言に const キーワードを追加する必要があります。例: int getage() const
。
関数の先頭の const は、関数の戻り値を変更するために使用され、戻り値が const 型であることを示します。関数ヘッダーの最後の const は定数メンバー関数を意味します。この関数はメンバー変数の値を読み取ることのみができますが、メンバー変数の値を変更することはできません。。
constオブジェクト
const で変更されたオブジェクトが呼び出されます。定数オブジェクト、定数オブジェクトはクラスの const メンバーのみを呼び出すことができます。
フレンド機能とフレンドクラス
の助けを借りて友元(友人)、他のクラスのメンバー関数やグローバル スコープの関数が現在のクラスのプライベート メンバーにアクセスできるようにします。
フレンド機能、現在のクラスの外で定義され、現在のクラスに属さない関数も現在のクラスで宣言できますが、その前にキーワード キーワード friends を追加する必要があります。これにより、フレンド関数が形成されます。この関数は、現在のクラスに属することはできません。任意のクラス 非メンバー関数は、他のクラスのメンバー関数になることもできます。
Friend 関数は、public、protected、private 属性を含む、現在のクラスのすべてのメンバーにアクセスできます。フレンド関数はクラスのメンバー関数とは異なり、クラスのメンバーに直接アクセスすることはできず、オブジェクトを使用してアクセスする必要があります。
場合によっては、早期宣言が行われている限り、クラスを最初に試行できますが、クラスの早期宣言の範囲は制限されており、正式な宣言の後にオブジェクトを作成するためにのみ使用できます。オブジェクトの作成にはメモリの割り当てが必要ですが、正式な宣言がないため、コンパイラはどのくらいのメモリを割り当てればよいのかわかりません。
関数は複数のクラスによってフレンド関数として宣言できるため、複数のクラスのプライベート メンバーにアクセスできます。
フレンドクラスのすべてのメンバー関数は、別のクラスのフレンド関数です。
注記:友人関係は双方向ではなく一方向です。友人関係は継承できません。
クラスもスコープの一種です。各クラスは独自のスコープを定義します。クラスのスコープ外では、通常のメンバーはオブジェクトを通じてのみアクセスできます。静的メンバーはオブジェクトとクラスの両方を通じてアクセスできます。ただし、typedef は、クラスの型を定義します。クラスを通じてのみアクセスできます。
構造体とクラスの違い
- クラス内のメンバーはデフォルトでプライベートであり、構造体のメンバーはパブリックです。
- クラス継承のデフォルトはプライベート継承、構造体継承のデフォルトはパブリック継承です。
- クラスはテンプレートを使用できますが、構造体はテンプレートを使用できません
文字列文字列
String は C++ のクラスであり、使用する場合はヘッダー ファイルをインクルードする必要があります<string>
。定義文字列が初期化されていない場合、コンパイラはデフォルトで空の文字列を設定します。
C++では文字列の末尾に終了記号「\0」がありません。文字列の長さを知りたい場合は、 string で提供されるものを使用できますlength()
。文字列は length+1 の代わりに正式な長さを返します。
実際のプログラミングでは、C スタイルの文字列 (ファイルを開くときのパスなど) を使用する必要がある場合があります。文字列クラスを私たちに提供します変換関数c_str()
、この関数は、文字列 string を C スタイルの文字列に変換し、string の const ポインタを返すことができます。
string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");
文字列クラスは入力演算子と出力演算子をオーバーロードします。を使用すると、文字列変数を通常の変数と同様に扱うことができます。つまり、>>
入力に使用したり、入力に使用したりできます<<
。+
また、文字列クラスの場合、 and演算子を使用して文字列を直接連結できます。これは非常に便利であり、文字列を連結するために, ,+=
を使用する必要はありません。strcat()
strcpy()
malloc()
文字列文字列の追加、削除、変更、問い合わせ
入れる:関数は、 string :insert()
で指定された位置に別の文字列を挿入string& insert(size_t pos, const string& str);
できます。
- pos は挿入される位置、つまり添え字を示し、str は挿入される文字列を示します。
消去:erase()
関数は、 string: 内の部分文字列を削除できますstring& erase(size_t pos = 0, size_t len = npos);
。
- pos は削除する部分文字列の開始添え字を示し、len は削除する部分文字列の長さを示します。
抽出する:substr()
関数は文字列 string: から部分文字列を抽出できますstring substr (size_t pos = 0, size_t len = npos) const;
。
- posは抽出される文字列の実際の添え字、lenは抽出される文字列の長さです
見上げる:find()
この関数は、文字列 string: 内で部分文字列が出現する位置を見つけることができますSize_t find (const string& str, size_t pos = 0) const;
。
- 最初のパラメータは検索する文字列で、2 番目のパラメータは検索を開始する添字です。
find
この関数は最終的に、文字列内で部分文字列が最初に出現する実際の添え字を返します。見つからない場合は無限数を返します。
rfind
関数とfind
関数の違いはrfind
、関数は 2 番目のパラメーターの位置のみを検索することです。find_first_of()
部分文字列と文字列の両方に共通する文字の文字列内で最初に出現する文字を検索する関数。
C 言語では、文字列を表現する方法が 2 つあります。
- 文字配列を使用して文字列を保持します。このような文字列は読み取りおよび書き込み可能です。
- 文字列定数で表されるこのような文字列は読み取りのみが可能であり、書き込みはできません。
C++ では、string はメモリと容量に関連する情報を内部的にカプセル化するため、string はメモリ内の開始位置、文字列に含まれる文字列シーケンス、および文字シーケンスの長さを知っています。メモリ容量が不足している場合、文字列もメモリサイズを自動的に調整します。
引用
コンセプト
char、int、float などの型が呼び出されます。基本タイプ、データ、構造体、クラスなどが呼び出されます。集計タイプ。
C++ には、ポインターよりも高速に集合型データを渡す方法があります。引用。参照はdata のエイリアスとみなすことができ、データはこのエイリアスと元の名前を通じて見つけることができます。
参照は、記号 & :を使用してポインターと同様に定義されますtype &name = data;
。
- type は参照されるデータ型です。
- name は参照の名前です。
- データは参照されるデータです。
参照は定義時に初期化する必要があります、将来的には一貫性があり、他のデータを参照することはできません。これは定数 (const 変数) に少し似ています。
参照を定義する場合は & を追加する必要がありますが、使用する場合は& を追加することはできません。& を追加すると、使用時にアドレスを取得することになります。参照と元の変数は両方とも同じアドレスを指しているため、参照によって元の変数に格納されているデータが変更される可能性があります。
元のデータを参照によって変更したくない場合は、定義時に const を追加するか、const type &name = value
このtype const &name = value
参照メソッドを使用します。よく引用される。
関数を定義または宣言するときに、関数の仮パラメータを参照として指定できます。これにより、関数が呼び出されるときに、実パラメータと仮パラメータがバインドされ、すべてが同じデータ部分を参照します。関数本体内で仮引数のデータを変更すると、実引数のデータも変更され、関数内部の関数外のデータに影響を与えます。パラメーターを参照によって渡すことは、使用の点でポインターよりも直感的であり、ポインターの代わりに参照を使用することをお勧めします。
関数の戻り値として参照を使用する場合に注意すべき問題は、ローカル データは関数終了後に破棄されるため、ローカル データを返すことができないことです。
参照は単にポインタをカプセル化するだけであり、その最下層は依然としてポインタを通じて実装されます。参照によって占有されるメモリは、ポインタによって占有されるメモリと同じであり、32 ビット環境では 4 バイト、環境では 8 バイトです。 64 ビット環境の bytes で参照のアドレスが取得できないのは、コンパイラが内部変換を行っているためです。それ以外の場合、コンパイラは参照のアドレスを取得しますが、参照は依然としてメモリを占有します。
参照とポインタの違い:
- 参照を定義するときは、初期化する必要があり、後で変更することはできません (つまり、他のデータを指すことはできません)。ポインターにはこの制限はありません。
- ポインターは複数のレベルを持つことができますが、参照は 1 つのレベルのみを持つことができます。
ポインタはメモリ内のデータまたはコードのアドレスであり、ポインタ変数はメモリ内のデータまたはコードを指します。レジスタやハードディスクはアドレス指定できないため、ポインタはメモリのみを指すことができ、レジスタやハードディスクを指すことはできません。式の結果、関数の戻り値などの一部のデータはレジスタに格納される場合があります。レジスタに格納されると、そのアドレスには & を介してアクセスできなくなります。そのため、ポインタを使用してレジスタを指すことはできません。彼ら。
ポインターの型は、ポインターが指すデータの型に厳密に対応します。
定数式
変数を含まない式が呼び出されます。定数式, 定数式には変数が含まれていないため、コンパイル段階で評価できます。コンパイラは、定数式の値を保存するために別のメモリを割り当てませんが、定数式の値をコードと組み合わせて、仮想アドレス空間内のコード領域。したがって、定数式はアセンブリの観点からは即値であり、命令にハードコーディングされるため、アドレス指定できません。
したがって、定数式はメモリに格納されていますが、アドレス指定することはできないため、& を使用してそのアドレスを取得することはできず、ましてやそれを指すポインタを使用することもできません。
コンパイラは const 参照の一時変数を作成し、参照をより柔軟で多用途なものにします。参照に const 修飾を追加すると、参照を一時データにバインドできるだけでなく、同様のタイプのデータにもバインドできるため、参照がより柔軟かつ汎用的になり、その背後にあるメカニズムはすべて一時変数になります。 。したがって、参照型の関数引数は可能な限り coonst を使用する必要があり、関数引数として参照を使用する場合、関数内で参照の値を変更しない場合には const 制限を使用するようにしてください。
要約すると、参照型のパラメーターに const 制限型を追加する理由は 3 つあります。
- const を使用すると、不用意にデータを変更するプログラミング エラーを回避できます。
- const を使用すると、関数は const 型および非 const 型の引数を受け取ることができます。それ以外の場合は、非 const 型の引数のみを受け取ります。
- const 参照を使用すると、関数が一時変数を正しく生成して使用できるようになります。
継承と派生
コンセプト
継承する(継承) は、クラスが別のクラスからメンバー変数とメンバー関数を取得するプロセスです。
派生(派生する)のコンセプトを、立場を変えてのみ継承しています。
継承されたクラスは親クラスまたは基本クラスと呼ばれ、継承されたクラスはサブクラスまたは派生クラスと呼ばれます。派生クラスには基本クラスのメンバーがあり、独自の新しいメンバーを定義してクラスの機能を強化することもできます。継承されたメンバーには、独自のメンバーと同様に、サブクラス オブジェクトを通じてアクセスできます。
継承の一般的な構文: class 派生类 : 继承方式 基类{派生类新增加的成员};
。継承メソッドにはpublic
、private
、 が含まれますprotected
。記述されていない場合、デフォルトは private です。
保護されたメンバーはプライベート関数に似ており、オブジェクトを通じてアクセスすることはできません。ただし、継承関係がある場合、基底クラスの protected メンバーは派生クラスで使用できますが、基底クラスの private メンバーは使用できません。
派生クラスのさまざまな継承メソッドのアクセス権
パブリック継承
- 派生クラスの基本クラスのpublicのメンバーもパブリック プロパティです。
- 基本クラスの保護されたメンバーのプロパティは、派生クラスでも保護されます。
- 基本クラスのプライベートメンバーは派生クラスでは使用できません。
保護された継承
- 基本クラスのパブリックメンバーは、派生クラス属性で保護されます。
- 基本クラスの保護されたメンバーには、派生クラスで保護されたプロパティがあります。
- 基本クラスのプライベートメンバーはすべて、派生クラスでは使用できません。
私的継承
- 基本クラスのすべてのパブリックメンバーは、派生クラスのプライベートプロパティです。
- 基本クラス内のすべての保護されたメンバーは、派生クラスではプライベートになります。
- 基本クラスのプライベート メンバーはすべて、派生クラスでは使用できません。
基本クラスのメンバーは、派生クラスにおいて Inheritance で指定されたアクセス権よりも高いアクセス権を持ってはなりません。継承方法に関係なく、基本クラスのプライベート メンバーが派生クラスで常に使用できるとは限りません。基本クラスのプライベート メンバーは、継承できないことは言うまでもなく、派生クラスで使用することはできません。開発では、一般にパブリック継承が使用されます。
派生クラスの基本クラスのプライベート メンバーにアクセスする唯一の方法は、基本クラスの非プライベート メンバー関数を使用することです。。
using キーワードを使用すると、基本クラスのパブリック メンバーと保護されたメンバーのアクセス権を変更できますが、プライベート メンバーのアクセス権は変更できません。
//派生类Student
class Student : public People {
public:
void learning();
public:
using People::m_name; //将protected改为public
using People::m_age; //将protected改为public
float m_score;
private:
using People::show; //将public改为private
};
派生クラスのメンバーと基本クラスのメンバーは同じ名前を持ち、基本クラスから継承されたメンバーはシャドウされます。基底クラスのメンバー関数は、パラメーターに関係なく名前が同じであればシャドウイングが発生するため、基底クラスのメンバー関数と派生クラスのメンバー関数はオーバーロードにはなりません。
派生クラスのスコープは、基本クラスのスコープ内にネストされています。派生クラスのスコープ内で名前が見つからない場合、コンパイラは外側の基本クラスのスコープ内で名前の定義を探し続けます。名前が外側のスコープで定義または宣言されている場合、内側のスコープはこの名前にアクセスできます。名前の検索は、内層から外層に向かって徐々に検索されます。内層が見つかった場合は検索されません。このプロセスはと呼ばれます。名前の検索。
コンパイラは、同じスコープ内の同じ名前の関数のみを、オーバーロードされた関数のオプションとして使用します。。
継承時のメモリモデル
継承する場合、派生クラスのメモリは、基本クラスのメンバーと新しく追加されたメンバー変数の合計とみなすことができます。メンバー関数は引き続きコード領域に保存され、すべてのオブジェクトで共有されます。。派生クラスのオブジェクト モデルには、すべての基本クラスのメンバー変数が含まれます。このメソッドは、複数の層の間接計算を経由せずに、基本クラスのメンバー変数に直接アクセスできます。
基本クラスと派生クラスのコンストラクター
クラスのコンストラクターは継承できません。また、派生クラスによって継承されたメンバー関数の初期化も、派生クラスのコンストラクターが完了する必要がありますが、基本クラスのプライベート変数は派生クラスのコンストラクターで初期化できません。派生クラスにアクセスできないためです。
派生クラスの初期化リストでは、基本クラスのコンストラクターを呼び出して変数を初期化できます。派生クラスは常に最初に基本クラスのコンストラクターを呼び出し、その後、他のコードを実行します。次のコードは、student のコンストラクタが people のコンストラクタを呼び出して初期化するコードです。
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
コンストラクタは継承されないため、基本クラスのコンストラクタは関数の先頭にのみ配置でき、関数本体には配置できず、通常の関数として使用できないことに注意してください。ここで、関数ヘッダーは宣言ではなくコンストラクターへの呼び出しであるため、ここで渡されるのは実際のパラメーターであるため、ここで渡されるのは、派生クラス コンストラクターのパラメーター リスト内のパラメーターだけでなく、ローカルのパラメーターである場合もあります。変数、定数など。
コンストラクターが呼び出される順序は、継承の階層に従って、基本クラスから派生クラスへ上から下へです。派生クラスは、間接基本クラスではなく、直接基本クラスのコンストラクターのみを呼び出すことができます。
派生クラスからオブジェクトを作成する場合は、基本クラスのコンストラクターを呼び出す必要があります。
基本クラスと派生クラスのデストラクター
各クラスにはデストラクターがあり、コンパイラーはその選択方法を知っているため、派生クラスのデストラクターでは、基本クラスのデストラクターを明示的に呼び出す必要はありません。デストラクターはコンストラクターとは逆の順序で実行されます。。
多重継承
複数の基本クラスを持つ派生クラスは、多重継承と呼ばれます。多重継承はコード ロジックを複雑にしやすいため、Java、C#、および PHP では多重継承がキャンセルされます。
class D: public A, private B, protected C {
//类D新增加的成员
}
基本クラスのコンストラクターが呼び出される順序は、それらが派生クラスのコンストラクターに現れる順序とは関係がありませんが、派生クラスが宣言されたときに基本クラスが現れる順序とは関係ありません。
複数の基本クラスに同じ名前のメンバーが存在する場合、直接アクセスすると名前の競合が発生し、コンパイラーは基本クラスのどのメンバーを使用するかを認識できません。この場合、クラス名とドメイン リゾルバーを追加する必要があります。::
多重継承におけるオブジェクトメモリモデル
基本クラス オブジェクトは、継承時に宣言されたのと同じ順序でリストされます。
ポインタを使用してプライベート メンバーにアクセスする
C++ では、オブジェクト変数またはオブジェクト ポインターを介して、オブジェクトを介してプライベート メンバーにアクセスすることはできません。ただし、この制限は文法レベルのものであり、プライベート メンバー変数にはポインターを介して引き続きアクセスできます。
オフセットを使用する
メンバ変数とオブジェクトの先頭との間には一定の距離があるため、オブジェクトの先頭からのメンバ変数のオフセットがわかれば、ポインタ経由でアクセスできます。したがって、オブジェクトの開始アドレスがわかっていて、変数のオフセットを追加すると、変数のアドレスがわかり、メンバー変数のアドレスと型がわかっていれば、その値がわかります。
実際、オブジェクト ポインターを介してメンバー変数にアクセスする場合、次のコードに示すように、コンパイラーは実際にこの方法で値を取得します。
class A{
public:
A(int a, int b, int c);
public:
int m_a;
int m_b;
int m_c;
};
int b = p->m_b;
//上一句在编译器内部会转换为下面这样
int b = *(int*)( (int)p + sizeof(int) );
//其中p 是对象 obj 的指针,(int)p将指针转换为一个整数,这样才能进行加法运算;sizeof(int)用来计算 m_b 的偏移;(int)p + sizeof(int)得到的就是 m_b 的地址,不过因为此时是int类型,所以还需要强制转换为int *类型;开头的*用来获取地址上的数据
//如果使用对象指针访问变量m_a的话如下
int a = p -> m_a;
//在编译器中转换为
int a = * (int*) ( (int)p + 0 );
アクセス権の制限を突破するには、コンパイラを使用して自動的に変換するのではなく、手動で変換する必要があります。オフセットが正しく計算できる限り。
次のコードは、プライベート変数にアクセスするためのポインターのオフセットです。
#include <iostream>
using namespace std;
class A{
public:
A(int a, int b, int c);
private:
int m_a;
int m_b;
int m_c;
};
A::A(int a, int b, int c): m_a(a), m_b(b), m_c(c){ }
int main(){
A obj(10, 20, 30);
int a1 = *(int*)&obj;
int b = *(int*)( (int)&obj + sizeof(int) );
A *p = new A(40, 50, 60);
int a2 = *(int*)p;
int c = *(int*)( (int)p + sizeof(int)*2 );
cout<<"a1="<<a1<<", a2="<<a2<<", b="<<b<<", c="<<c<<endl;
return 0;
}
操作の結果、 C++ では文法レベルでのみプライベート変数へのアクセスが許可されていないことがわかります。これは、アクセス権がa1=10, a2=40, b=20, c=60
メンバー演算子の合計に対してのみ.
機能することを意味します->
。しかし、ポインタを介した直接アクセスを防ぐ方法はありません。
仮想継承と仮想基本クラス
多重継承における名前の競合と冗長データの問題を解決するために、仮想継承, そのため、派生クラスでは間接的な基本クラスのメンバーが 1 つだけ予約されます。継承メソッドの前にキーワードを追加することは、virtual
仮想継承です。
class B: virtual public A{ //虚继承
//todo
};
仮想継承の目的は、クラスが基本クラスを共有する意思があることを宣言できるようにすることです。共有基本クラスは、仮想基本クラスと呼ばれます。仮想基本クラスが継承システムに何度出現しても、派生クラスには仮想基本クラスのメンバーが 1 つだけ含まれます。
したがって、仮想導出の実際の需要が現れる前に、仮想導出の操作を完了する必要があります。仮想派生は、仮想基本クラスの派生クラスからさらに派生したクラスにのみ影響し、派生クラス自体には影響しません。
仮想メンバーの可視性
仮想基本クラスのメンバーは 1 つだけ仮想継承の最終派生クラスに保存されるため、このメンバーには曖昧さなく直接アクセスできます。仮想基本クラスのメンバーが 1 つの派生パスのみでカバーされている場合でも、カバーされているメンバーには引き続きアクセスできます。メンバが複数のパスにまたがる場合、長時間直接アクセスできなくなるため、メンバがどのクラスに属しているかを示す必要がある。
多重継承の使用は推奨されません。
仮想継承を備えたコンストラクター
仮想継承では、仮想基本クラスは最終派生クラスによって初期化されるため、最終派生クラスのコンストラクターは仮想基本クラスのコンストラクターを呼び出す必要があります。究極の派生クラスの場合、仮想基本クラスは直接基本クラスではなく間接基本クラスです。通常の継承では、派生クラスのコンストラクターは直接の基本クラスのコンストラクターのみを呼び出すことができ、間接的な基本クラスのコンストラクターを呼び出すことはできません。
仮想継承コンストラクターの実行順序は通常の継承とは異なり、最終的な派生クラスのコンストラクター呼び出しリストでは、コンストラクターの出現順序に関係なく、コンパイラは常に仮想基底クラスのコンストラクターを最初に呼び出します。その後、他のコンストラクターを順番に呼び出します。通常の継承では、コンストラクターが呼び出される順序は、クラスが宣言されたときに基本クラスが出現する順序に従います。
仮想継承時のメモリモデル
通常の継承の場合、基本クラス オブジェクトは常に派生クラス オブジェクトの反対側に配置されます。継承階層がどれほど深くても、派生クラス オブジェクトの最上位に対する相対的なオフセットは固定されます。
仮想継承の場合、通常の継承とは対照的に、ほとんどのコンパイラーは基底クラスのメンバー変数を派生クラスのメンバー変数の後ろに配置します。そのため、継承レベルが増加するにつれて、基底クラスのメンバー変数のオフセットが変化するため、オフセットは変更されなくなります。固定オフセットを使用して基本クラスのメンバーにアクセスすることが可能です。
仮想継承では、派生クラスを固定部分と共有部分に分割し、共有部分を最後に置きます。
派生クラスを基本クラスに代入する (アップキャスト)
クラスもデータ構造であり、データ型変換が発生する可能性がありますが、この変換は基本クラスと派生クラスの間でのみ意味があり、派生クラスのみを基本クラスに割り当てることができます。派生クラス オブジェクトの基本クラス オブジェクトへの代入、意思派生クラス ポインタの基本クラス ポインタへの代入、意思派生クラス参照の基本クラス参照への代入、C++ ではこう呼ばれます上向きの変革。もちろん、基底クラスを派生クラスに代入することは、下方変革。
アップキャストは非常に安全であり、コンパイラによって自動的に実行できますが、ダウンキャストはリスクがあり、手動介入が必要です。
代入の本質は、割り当てられたメモリに既存のデータを書き込むことですが、オブジェクトのメモリにはメンバ変数のみが含まれるため、オブジェクト間の代入はメンバ変数の代入となり、メンバ関数における代入の問題は発生しません。
派生クラス オブジェクトを基底クラス オブジェクトに割り当てると、派生クラスの新しいメンバーが破棄されます。この変換関係は元に戻せません。基底クラス オブジェクトに値を割り当てるために使用できるのは派生クラス オブジェクトのみであり、基底クラス オブジェクトを割り当てることはできません。派生クラスのオブジェクトに。基本クラス オブジェクトには、派生クラス オブジェクトに新しく追加されたメンバーが含まれていないためです。
派生クラスのポインタを基本クラスのポインタに代入する
派生クラス ポインターは、基本クラス ポインターに割り当てることができます。オブジェクト変数の割り当てとは異なり、オブジェクト ポインターの割り当てでは、オブジェクトのメンバー変数をコピーしたり、オブジェクトのデータを変更したりする必要はなく、ポインターのポインティングが変更されるだけです。
基本クラス ポインターを介して派生クラス メンバーにアクセスする
派生クラス ポインターが基底クラス ポインターに割り当てられている場合、基底クラス ポインターを介して派生クラスのメンバー変数のみが使用でき、派生クラスのメンバー関数は使用できません。使用されるメンバー関数は引き続き使用されます。基本クラス。
これは、派生クラスのポインタが基本クラスのポインタに代入された後、基本クラスのポインタが派生クラスのオブジェクトを指すため、 this ポインタが変更され、派生クラスのオブジェクトを指すようになるためです。 , したがって、アクセスされるメンバー変数は派生カテゴリです。ただし、コンパイラは、メンバー関数へのポインターではなく、基本クラス ポインターを通じてメンバー変数にアクセスします。コンパイラは、ポインタの型を介してメンバー関数にアクセスします。。したがって、派生クラスのポインタを基底クラスのポインタに代入しても、基底クラスのポインタの型は変わりません。
要約すると次のようになります。コンパイラはポインタを介してメンバ変数にアクセスし、ポインタがオブジェクトを指す場合はそのオブジェクトのデータが使用され、コンパイラはポインタの型を介してメンバ関数にアクセスし、クラスのメンバ変数はポインタの型に使用されます。ポインタが属するクラス。
派生クラス参照を基本クラス参照に代入する
参照は基本的にポインターを使用して実装されるため、ポインターと参照の効果は同じです。
派生クラス ポインタを基本クラス ポインタ プロシージャに代入する
派生クラス ポインターを基本クラス ポインターに代入すると、それらの値が等しい場合もあれば、等しくない場合もあります。
コンパイラは、代入前に既存の値に対して何らかの処理 (double 型データを int 型変数に代入するなど) を実行する場合があり、コンパイラは小数部分を消去します。派生クラスのポインタを基底クラスのポインタに代入する場合も同様で、代入前にコンパイラが値に対して何らかの処理を行う場合があります。
ポリモーフィズムと仮想関数
コンセプト
これは、基本クラス ポインターがアクセスできるのは派生クラスのメンバー変数のみであり、派生クラスのメンバー関数にはアクセスできないためです。C++ が追加されました仮想関数これにより、基本クラス ポインターが派生クラスのメンバー関数にアクセスできるようになり、仮想関数は関数宣言の前にキーワードを追加するだけで済みますvirtual
。
仮想関数を使用すると、基本クラス ポインターが基本クラス オブジェクトを指すときに基本クラスのメンバーが使用されます。つまり、メンバー変数とメンバー関数が使用できます。派生クラスを指す場合、派生クラスのメンバーが使用されます。したがって、基底クラス ポインターは、基底クラスの方法で物事を行うことができますが、派生クラスの方法で物事を行うこともできます。多くの形式または表現方法があります。この現象は、と呼ばれます。多態性。
仮想関数の唯一の役割は、ポリモーフィズムを形成することです。
ポリモーフィズムを提供する目的は、基本クラス ポインターを通じて、派生クラス (直接派生および間接派生) のメンバー変数とメンバー関数に全方向からアクセスできるようにすることです。ポリモーフィズムがない場合は、派生クラスのメンバー変数のみにアクセスできます。仮想関数はポインタの位置に従って呼び出され、ポインタがクラスのオブジェクトを指す場合にどのクラスの関数が呼び出されます。
参照は本質的にポインタであるため、参照でも多態性を実現できますが、参照は初期化後に変更できないため、多態性の観点からは表現力に欠けます。
複雑な継承関係を持つ大規模および中規模のプログラムの場合、ポリモーフィズムによって柔軟性が向上し、コードの表現力が向上します。プログラム内に多数の派生クラスが存在する場合、ポリモーフィズムを使用しない場合は複数のポインターを定義する必要がありますが、ポリモーフィズムを使用すると、すべての派生クラスの仮想関数を呼び出すのに必要なポインター変数は 1 つだけです。
仮想関数
仮想関数キーワードを追加する必要があるのは、関数が宣言されているvirtual
場合のみであり、関数の定義時に追加してもしなくても構いません。基本クラス内の関数は仮想関数としてのみ宣言できるため、シャドウ関係を持つ派生クラス内の同じ名前を持つすべての関数が自動的に仮想関数になります。
仮想関数が基本クラスで定義されている場合、派生クラスがこの関数をシャドウする新しい仮想関数を定義していない場合は、基本クラスの仮想関数が使用されます。派生クラスの仮想関数が基本クラスの仮想関数をオーバーライドする場合にのみ、ポリモーフィズムを形成できます。つまり、派生クラス関数は、基本クラス ポインターを介してアクセスされます。
コンストラクターを仮想にすることはできません。
デストラクターは仮想として宣言でき、場合によっては仮想として宣言する必要があります。
カプセル化、継承、ポリモーフィズム - オブジェクト指向の 3 つの主要な特徴。
多態性を構成する条件
- 継承関係が存在する必要があります。
- 継承関係には同名の仮想関数が存在し、それらの間に重複関係が存在する必要があります。
- 基本クラスへのポインターがあり、仮想関数はポインターを通じて呼び出されます。
- 基本クラス ポインターは、基本クラスから継承されたメンバーにのみアクセスでき、派生クラスの新しく追加されたメンバーにはアクセスできません。
- クラスのメンバー関数が継承後にその関数を変更したい場合、通常は仮想関数として宣言されます。
仮想デストラクタ
次の理由により、コンストラクターを仮想にすることはできません。
- 派生クラスは基本クラスのコンストラクターを継承できず、コンストラクターを仮想関数として宣言しても意味がありません。
- C++ のコンストラクターは、オブジェクトの作成時にオブジェクトを初期化するために使用されますが、コンストラクターが実行される前にはオブジェクトは作成されておらず、仮想関数テーブルは存在しません。
デストラクターは通常、デフォルトで仮想として宣言されます。
デストラクタをデフォルトで記述する場合、仮想関数として定義されていないとメモリリークが発生しやすくなります。なぜなら、オブジェクト ポインターを削除するときに、それが基本クラス ポインターによって指される派生クラスである場合、派生クラスを削除するときにデストラクターが呼び出されるからです。ポインターを介してメンバー関数を呼び出す場合、コンパイラーは、ポインターが指すオブジェクトではなく、ポインターの型に従ってメンバー関数を呼び出します。したがって、これにより、派生クラスのデストラクターではなく、基本クラスのデストラクターが呼び出されます。派生クラスのコンストラクターでメモリが適用された場合、メモリが解放されず、メモリ リークが発生します。
派生クラスのデストラクターは常に基本クラスのデストラクターを呼び出します。基本クラスのデストラクターを仮想関数として宣言すると、派生クラスのデストラクターは自動的に仮想になります。このとき、コンパイラはポインタの型に応じてメンバ関数を選択するのではなく、ポインタが指すオブジェクトに応じてメンバ関数を選択します。
ほとんどの場合、基本クラスのデストラクターは仮想関数として宣言されます。
純粋仮想関数と抽象クラス
仮想関数は次のように宣言できます。純粋仮想関数:
virtual 返回值类型 函数名 (函数参数) = 0;
=0
純粋仮想関数には関数本体がなく、純粋仮想関数であることを示すために仮想関数宣言の最後に追加される関数宣言のみがあります。。
最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。
純粋仮想関数を含むクラスが呼び出されます。抽象クラス、抽象クラスと呼ばれる理由は次のとおりです。インスタンス化できません、 あれはオブジェクトを作成できませんでした。純粋仮想関数には関数本体がなく、完全な関数ではなく、呼び出すことができず、メモリ領域を割り当てることもできません。
抽象クラスは通常、派生クラスが純粋仮想関数を実装できるようにするための基本クラスとして使用され、派生クラスはインスタンス化する前に純粋仮想関数を実装する必要があります。関数の一部のみを完了する抽象基本クラスを定義し、未完成の関数は実装のために派生クラスに渡されます。これらの関数は、多くの場合、基本クラスでは必要とされないか、基本クラスでは実装できません。
抽象クラスによって宣言された純粋仮想関数は実装されていませんが、派生クラスでは実装することが必須であり、そうしないとインスタンス化を完了できません。
派生クラスの機能を制限することに加えて、抽象クラスはポリモーフィズムを実現することもできます。基底クラスに宣言がないと、基底クラスのポインターを使用して派生クラスのメンバー関数を呼び出すときにエラーが発生するためです。
純粋仮想として宣言できるのは仮想関数のみです。
ポリモーフィックな実装メカニズム
関数が仮想関数であり、派生クラスの同じ名前の関数によってカバーされている場合、コンパイラーはポインターに従って関数を見つけます。つまり、オブジェクトが呼び出されたときにそのクラスの関数が呼び出されます。ポインタの指す先がどのクラスに属するか、これがポリモーフィズムです。
コンパイラがポインタが指すオブジェクトを通じて仮想関数を見つけることができる理由は、追加の仮想関数テーブル。
クラスに仮想関数が含まれている場合、このクラスのオブジェクトの作成時に追加の配列が追加され、配列の各要素が仮想関数のエントリ アドレスになります。ただし、配列とオブジェクトは別々に格納されるため、オブジェクトを配列に関連付けるために、コンパイラは配列の開始アドレスを指すポインタをオブジェクトに挿入する必要もあります。この配列は仮想関数テーブルであり、vtableと省略されます。
オブジェクトの先頭には、仮想関数テーブルを指すポインタがあり、このポインタは常にオブジェクトの先頭に位置します。
仮想関数テーブルでは、vtable 内の基底クラスの仮想関数の添字が固定されており、継承レベルが上がっても変更されず、派生クラスの新しい仮想関数が vtable の最後に配置されます。同じ名前の仮想関数が基本クラスの仮想関数をシャドウする場合、派生クラスの仮想関数は、基本クラスの仮想関数を置き換えるために使用されます。シャドウ関係を持つこのような仮想関数は、 vtable 内に 1 回だけ出現します。
ポインタを介して仮想関数を呼び出す場合、まずポインタ vfptr に従って仮想関数のエントリ アドレスを見つけます。
p -> display()
( *( *(p+0) + 0 ) )(p);//上面的调用在编译器中会转换成下面这样
変換された式は固定されており、仮想関数が呼び出されている限り、値がどのクラスにあるかに関係なく、この式が使用されることがわかります。言い換えれば、コンパイラは、基本クラスのポインタがどこを指しているのかを気にしません。常に同じ式に変換します。
変換された式は p の型に関する情報を使用せず、ポインタがわかっていれば関数を呼び出すことができます。これは名前エンコード アルゴリズムとは根本的に異なります。
typeid 演算子
typeid は、式の型情報を取得するために使用されます。基本的なデータ型の場合、型情報は比較的単純で、主にデータの型を指します。クラス型データ、つまりオブジェクトの場合、型情報は、オブジェクトが属するクラス、オブジェクトに含まれるメンバー、オブジェクトの継承関係などを指します。
型情報はデータを作成するためのテンプレートであり、データがどれだけのメモリを占有し、どのような操作が実行できるかはすべて型情報によって決まります。typeidの操作オブジェクトは、式またはデータ型のいずれかになります。
typeid( dataType )
typeid( expression )
typeid は括弧で囲む必要があり、typeid は取得した型情報をtype_info型のオブジェクトに保存します。オブジェクトへの定数参照を返します。返されたtype_infoクラスのいくつかのメンバー関数:
name()
型を返すために使用される名前。raw_name()
名前エンコーディング アルゴリズムによって生成された新しい名前を返すために使用されます。hash_code()
現在の型に対応するハッシュ値を返すために使用されます。
C++ 標準では、type_info クラスには少なくとも次の 4 つのパブリック関数が必要であると規定されています。
const char* name() const;
: 型名を表す文字列を返します。bool before (const type_info& rhs) const;
注: ある型が別の型の前にあるかどうかを判断するには、rhs パラメーターは type_info オブジェクトへの参照です。bool operator== (const type_info& rhs) const;
:==
2 つの型が同じかどうかを判断するオーバーロードされた演算子。rhs パラメーターは type_info オブジェクトへの参照です。bool operator!= (const type_info& rhs) const;
:!=
2 つの型が異なるかどうかを判断するオーバーロードされた演算子。rhs パラメーターは type_info オブジェクトへの参照です。
typeid 演算子は、2 つの型が等しいかどうかを判断するためによく使用されます。型が何度使用されても、コンパイラはその型のオブジェクトを作成するだけであり、すべての typeid はこのオブジェクトへの参照を返します。
ただし、コンパイルされたファイルのサイズを減らすために、コンパイラはすべての型に対して type_info オブジェクトを作成するのではなく、typeid 演算子を使用する型に対してのみ作成します。ただし、仮想関数を持つクラスの場合、typeid 演算子が使用されるかどうかに関係なく、コンパイラは仮想関数を持つクラスの type_info オブジェクトを作成します。
タイプ認識メカニズム
一般に、式の型はコンパイル時に決定できますが、ポリモーフィズムがある場合、一部の式はコンパイル時に型を決定できず、プログラム実行後に実際の環境に応じて型を決定する必要があります。
オブジェクトメモリモデル:
- 仮想関数も仮想継承もない場合、オブジェクト メモリ モデルにはメンバー変数のみが存在します。
- クラスに仮想関数が含まれる場合、追加の仮想関数テーブルが追加され、この仮想関数テーブルを指すポインタがオブジェクト メモリに挿入されます。同時に、このクラスのオブジェクト メモリに追加の型情報、つまり type_info オブジェクトも追加されます。
- クラスに仮想継承が含まれる場合、追加の仮想基本クラス テーブルが追加され、この仮想基本クラス テーブルを指すポインタがオブジェクト メモリに挿入されます。
クラスに仮想関数が含まれている場合、コンパイラはオブジェクトの先頭に仮想関数テーブルを作成するだけでなく、仮想関数テーブルの先頭にポインタを挿入して、現在のテーブル内の対応する type_info オブジェクトを指します。現在のプログラムが実行段階で型情報を取得する場合、オブジェクト ポインタを通じて仮想関数テーブル ポインタを見つけ、次に仮想関数テーブル ポインタを通じて type_info オブジェクトのポインタを見つけて、型情報を取得します。
コンパイラは、コンパイル フェーズ中に基底クラス ポインタがどのオブジェクトを指しているのかを判断できないため、ポインタが指すオブジェクトの型情報を取得できませんが、コンパイラはコンパイル フェーズ中にさまざまな準備を行うことができるため、プログラムは次のことを行うことができます。実行後にこれらの準備されたデータを使用して、次のような型情報を取得します。
- type_info オブジェクトを作成し、仮想関数テーブルの先頭に type_info オブジェクトへのポインターを挿入します。
- 型情報を取得する操作をポインターのようなステートメントに変換します。
**(p->vfptr - 1)
。
プログラムの実行後にオブジェクトの型情報を決定するこのメカニズムは、と呼ばれます。実行時の型認識(RTTI)。C++ では、RTTI メカニズムは仮想関数が含まれる場合にのみ使用され、それ以外の場合はすべてコンパイル段階で型情報を決定できます。
ポリモーフィズムはオブジェクト指向プログラミングの重要な機能であり、プログラムの柔軟性が大幅に向上しますが、ポリモーフィズムをサポートするためのコストも非常に高くなります。一部の情報はコンパイル段階で事前に決定できないため、より多くのメモリと CPU を消費します。リソース。
CPUがメモリにアクセスするために必要なのはアドレスであり、変数名、関数名、アドレスを一致させる操作を呼びます。シンボルバインディング。通常、コンパイル中に関数名に対応するアドレスが見つかり、関数のバインドが完了し、実行後すぐにプログラムを使用できるようになります。静的バインディング。コンパイル中にどの関数を呼び出すか決定できない場合があり、プログラム実行後の特定の環境や動作に応じて決定する必要があります。動的バインディング。
静的言語: 型は定義時に明示的に指定され、型を指定した後は変更できないため、コンパイラはコンパイル中にほとんどの式の型を知ることができます。この言語は静的言語と呼ばれます。
動的言語: 変数を定義するときに型を指定する必要はなく、変数の型はいつでも変更できます。コンパイラはコンパイル中に式の型情報を決定できません。プログラムの実行後にのみ取得できます。 . 動的言語と呼ばれます。
柔軟性と導入を容易にするために、動的言語は通常、コンパイルと実行を同時に行うため、従来のコンパイルと実行のプロセスが曖昧になります。
演算子のオーバーロード
コンセプト
演算子のオーバーロードそれは、同じ演算子に異なる機能を持たせることです。実際のアプリケーションでは、+
さまざまなタイプのデータの加算演算を<<
シフト演算子として、または出力する cout のシンボルとして使用できます。これらは演算子のオーバーロードです。
演算子のオーバーロードとは、関数を定義し、関数本体内で目的の関数を実現することで、演算子が必要な場合、コンパイラが自動的にこの関数を呼び出します。それで演算子のオーバーロードは関数を通じて実現されますこれは本質的に関数のオーバーロードです。演算子のオーバーロードの形式は次のとおりです。
返回值类型 operator 运算符名称(形参列表){
//TO DO
}
operator是关键字,专门用于定义重载运算符的函数。
演算子オーバーロード関数は、関数名が特定の形式であることを除いて、通常の関数と変わりません。演算子オーバーロード関数は、クラスのメンバー関数としてだけでなく、グローバル関数としても使用できます。
- すべての演算子をオーバーロードできるわけではありません。長さ演算子
sizeof
、条件演算子:?
、メンバー演算子.
、ドメイン解決演算子::
はオーバーロードできません。 - オーバーロードによって演算子の優先順位と結合性を変更することはできません。
- オーバーロードしても、演算子の使用法、オペランドの数、オペランドが左側にあるか右側にあるか、これらは変わりません。
- 演算子のオーバーロード関数にはデフォルトのパラメーターを含めることはできません。そうしないと、演算子のオペランドの数が変更されます。
- 演算子オーバーロード関数は、クラスのメンバー関数またはグローバル関数として使用できます。
- 矢印演算子
->
、添字演算子[]
、関数呼び出し演算子()
、および代入演算子は、=
メンバー関数としてのみオーバーロードできます。
演算子オーバーロード関数がクラスのメンバー関数として使用される場合、二項演算子にはパラメーターが 1 つだけあり、単項演算子にはパラメーターは必要ありません。クラスのメンバー関数として使用される場合、1 つのパラメーターが暗黙的になるためです。たとえば、オーバーロードされると+
、c3 = c1 + c2
コンパイル中にc3 = c1.operator+(c2)
this ポインタを介して暗黙的に c1 のメンバ変数にアクセスするように変換されます。
演算子オーバーロード関数をグローバル関数として使用する場合、二項演算子には 2 つのパラメーターが必要で、単項演算子には 1 つのパラメーターが必要です。また、パラメーターの 1 つはオブジェクトである必要があるため、コンパイラーはこれが関数によって定義された演算子であることを認識します。プログラマは、組み込み型に使用される演算子を変更しないようにします。
演算子オーバーロード関数をグローバル関数として使用する場合、通常、その関数をクラス内でフレンド関数として宣言する必要があります。
過負荷<<
と>>
左シフト演算子と右シフト演算子は、さまざまなデータの入出力に使用できるように、標準ライブラリでオーバーロードされています。ただし、入力および出力オブジェクトとして使用できるのは、C++ 組み込みデータ型および標準ライブラリに含まれるデータ型のみです。<<
データ型を自分で定義する場合、と を使用したい場合は、自分でデータ型をオーバーロードする必要があります>>
。
cout は ostream クラスのオブジェクトであり、cin は istream クラスのオブジェクトです<<
。 andをオーバーロードする場合は、 および をグローバル関数の形式でオーバーロードする>>
必要があります。それ以外の場合は、標準ライブラリのクラスを変更する必要があります。<<
>>
入力演算子のオーバーロード>>
:
istream & operator>>(istream &in, complex &A){
in >> A.m_real >> A.m_imag;
return in;
}
このうち、istreamは入力ストリームを表し、cinはistreamクラスのオブジェクトですが、このオブジェクトは標準ライブラリで定義されています。参照を返す目的は複素数を連続して読み取れるようにすることですが、参照を返さない場合は 1 つずつしか読み取れません。。また、演算子のオーバーロード関数は、複合体内のプライベート変数を使用します。これは、複合体内でフレンド関数として宣言する必要があります。
オーバーロードされた出力演算子<<
:
ostream & operator<<(ostream &out, complex &A){
out << A.m_real << " + " << A.m_imag << "i";
return out;
}
ostream は出力ストリームを表し、cout は ostream クラスのオブジェクトです。
オーバーロード[]`
C++ では、添字演算子をメンバー関数の形式でオーバーロードする必要があると規定しています。
1.返回值类型 & operator[](参数);
或者是:
2.const 返回值类型 & operator[](参数) const;
最初の宣言では、[]
要素にアクセスできるだけでなく、要素を変更することもできます。
2 番目の方法では、[]
要素はアクセスのみ可能で、変更はできません。実際のアプリケーションでは、const オブジェクトに適応する 2 つの形式を提供する必要があります。これは、const オブジェクトを通じて呼び出すことができるのは const メンバー関数のみであり、2 番目の形式が提供されていない場合は、const オブジェクトの要素にアクセスできないためです。
過負荷++
と--
自己インクリメント演算子++
と自己デクリメント–
演算子は単項演算子であり、その前形式と後形式の両方をオーバーロードできます。
1.返回值类型 operator++(); //++i,前置形式
2.返回值类型 operator++(int i); //i++,后置形式
operator++();
関数は自動インクリメントを実装しますフロントフォルム、実行結果を単独で返します。
operator++(int i);
関数は自動インクリメントを実装します投稿フォーム、戻り値はオブジェクト自体ですが、後でオブジェクトが再度使用されると、オブジェクトは自己インクリメントされるため、関数の関数本体で、最初にオブジェクトを保存し、次に run() 関数を 1 回呼び出してから、返された最初の保存オブジェクトを保存します。この関数では、パラメータ i には意味はなく、前置詞か後置詞かを区別するだけです。
過負荷new
とdelete
メモリ演算子new
、new[]
、もオーバーロードでき、オーバーロードされdelete[]
た形式はメンバー関数またはグローバル関数のいずれかになります。delete
new をメンバー関数としてオーバーロードする:
void* classname::operator new(size_t size){
//todo;
}
new をグローバル関数としてオーバーロードする:
void* operator new(size_t size){
//todo;
}
new と new[] をメンバー関数またはグローバル関数としてオーバーロードする場合、最初のパラメータは size_t 型でなければなりません。size_t は、割り当てられるスペースのサイズを表します。new[] のオーバーロードされた関数の場合、size_t は割り当てる必要があるすべてのスペースの合計を表します。。
size_t 在头文件<cstdio>中被定义为 typedef unsigned int size_t;就是无符号整形
もちろん、オーバーロードされた関数は他のパラメーターを持つことができますが、それらはすべてデフォルト値を持つ必要があり、最初のパラメーターの型は size_t である必要があります。
メンバー関数としてのオーバーロード削除:
void* classname::operator delete(void *ptr){
//todo;
}
グローバル関数としてのオーバーロード削除:
void* operator delete(void *ptr){
//todo;
}
キャスト演算子のオーバーロード()
C++ では、型の名前 (クラスの名前を含む) 自体も演算子、つまり型キャスト演算子です。
型強制演算子は単項演算子であり、オーバーロードすることもできますが、オーバーロードできるのはグローバル関数ではなくメンバー関数としてのみです。オーバーロード後、(类型名)对象
オブジェクトをキャストする式は と同等对象.operator 类型名()
、つまり演算子関数の呼び出しになります。
キャスト演算子をオーバーロードする場合、戻り値の型は演算子自体によって表される型によって決定されるため、戻り値の型を指定する必要はありません。
予防
- オーバーロード後の演算子の意味は、元の使用習慣に準拠する必要があります。
- 演算子のオーバーロード演算子の優先順位を変更しません。
.
、、、、などの演算子.*
_::
?:
sizeof
過負荷にすることはできません。- 演算子
()
、[]
、->
、をオーバーロードする=
場合メンバー関数としてのみオーバーロードできます、グローバル関数としてオーバーロードすることはできません。 - 演算子のオーバーロードは、実際には演算子を関数にオーバーロードすることであり、演算子を使用する式はオーバーロードされた関数の呼び出しとして解釈されます。
- 演算子はグローバル関数としてオーバーロードすることができ、このとき関数の引数の数は演算子のオペランドの数となり、演算子のオペランドが関数の実引数となります。
- 演算子はメンバー関数としてオーバーロードできます。このとき、関数のパラメータの数は演算子のオペランドから 1 を引いた数になります。演算子のオペランドの 1 つが動作の対象となり、残りが演算子の実際のパラメータになります。関数。
- 型の名前はキャスト演算子として使用したり、クラスのメンバー関数としてオーバーロードしたりして、オブジェクトが特定の型に自動的に変換されるようにすることができます。
- 自己インクリメント演算子と自己デクリメント演算子にはそれぞれ 2 つのオーバーロード モードがあり、使用前と使用後を区別するために使用されます。
テンプレート
コンセプト
C++ では、データの型をパラメータを通じて渡すこともできます。関数を定義するときに、特定のデータ型に名前を付けることはできません。関数が呼び出されるとき、コンパイラは、渡された実際のパラメータに従ってデータ型を自動的に推測できます。 。 これは型のパラメータ化。値と型はデータの 2 つの主な特性であり、どちらも C++ でパラメータ化できます。
関数テンプレートを作成することですユニバーサル機能では、使用するデータ型(戻り値の型、仮引数の型、ローカル変数の型を含む)を具体的に指定することはできませんが、代わりに仮想型が使用され、関数呼び出しが発生した場合は、渡される実パラメータ 実数型を取り出します。
関数テンプレートを定義すると、関数定義および関数宣言で型パラメーターを使用できるようになります。int
、などの組み込み型が元々使用されていた場合、代わりに関数パラメータfloat
をchar
使用できます。
テンプレートこれは、関数テンプレートを定義するためのキーワードであり、その後に山括弧が続き<>
、山括弧は型パラメータを囲みます。typenameはtemplate<typename T>
、特定の型パラメータを宣言するために使用されるもう 1 つのキーワードです。テンプレートヘッダー。
template <typename 类型参数1, typename 类型参数2, ...> 返回值类型 函数名(形参列表){
//在函数体中可以使用类型参数
}
多くの型パラメータを指定できますが、それらはコンマで区切る必要があり、型パラメータは で囲まれ<>
、仮パラメータは()
で囲まれます。初期の C++ では新しいキーワードが導入されず、type パラメーターを示すために class キーワードが使用されていたため、typename キーワードは class キーワードで置き換えることができます。
関数テンプレートは事前に宣言できますが、宣言にはテンプレート ヘッダーを付ける必要があり、テンプレート ヘッダーと関数定義 (宣言) は分離できない全体であり、ラップすることはできますが、セミコロンを追加することはできません。
関数テンプレートに加えて、クラス テンプレートもサポートされています。関数テンプレートで定義された型パラメータは関数宣言と関数定義で使用でき、クラス テンプレートで定義された型パラメータはクラス宣言とクラス実装で使用できます。クラス テンプレートの目的は、データ型をパラメータ化することでもあります。
template <typename 类型参数1, typename 类型参数2,...> class 类名{
//todo
};
クラス テンプレートは関数テンプレートと同じであり、型パラメーターを空にすることはできず、複数の型パラメーターはカンマで区切られます。
クラス テンプレートを宣言すると、クラスのメンバー関数とメンバー変数で型パラメーターを使用できるようになります。クラス宣言にテンプレート ヘッダーを追加するだけでなく、クラス外のメンバー関数を定義するときにもテンプレート ヘッダーを追加する必要があります。
template <typename 类型参数1, typename 类型参数2, ...>
返回值类型 类名<类型参数1, 类型参数2, ...>::函数名(形参列表){
//todo
}
- 注: テンプレート キーワードの後に type パラメーターを指定する必要があることに加えて、クラス名ポイントの後にも type パラメーターを指定する必要がありますが、typename キーワードは追加されません。
関数テンプレートとは異なり、クラス テンプレートは をインスタンス化するときにデータ型を指定する必要があり、コンパイラは指定されたデータに基づいてデータ型を推測できません。インスタンス化にオブジェクト ポインターを使用する場合は、両側で特定のデータ型を指定し、それらの一貫性を保つことも必要です。
テンプレートでサポートされる型は幅広く無制限であり、任意の型に置き換えることができます。このプログラミングはと呼ばれます。汎用プログラミング。パラメータ T はジェネリック型、int、char などは特定型とみなすことができます。
C++ では、関数テンプレートのオーバーロードが可能です。
- 注: プログラミング言語は、変数を定義するときに言語がデータ型を明示的に示す必要があるかどうかに応じて、強く型指定された言語と弱く型指定された言語に分類できます。厳密に型指定された言語では、変数を定義するときにデータ型を指定する必要があり、変数に特定のデータ型が指定されると、必須の型変換と暗黙的な変換が行われない限り、将来その変数に他の型のデータを割り当てることはできません。
int a = 100; //不转换
a = 12.34; //隐式转换(直接舍去小数部分,得到12)
a = (int)"http://c.biancheng.net"; //强制转换(得到字符串的地址)
弱い型指定言語では、変数を定義するときにデータ型を明示的に指定する必要はありません。コンパイラは、変数に割り当てられたデータに従って自動的に型を推測し、変数に異なる型のデータを割り当てたり、異なる型を割り当てることができます。データを変数に代入します。
var a = 100; //赋给整数
a = 12.34; //赋给小数
a = "http://c.biancheng.net"; //赋给字符串
a = new Array("JavaScript","React","JSON"); //赋给数组
強く型指定された言語であっても、弱く型指定された言語であっても、変数に関するさまざまな情報を維持するための型システムがコンパイラー内にあります。
厳密に型指定された言語の場合、コンパイラはコンパイル中に変数の操作が正しいかどうかを検出できるため、プログラムの実行時に型情報のセットを維持する必要がなく、メモリ使用量が削減され、プログラムの実行が高速化されます。プログラム。ただし、厳密に型指定された言語には、C++ のポリモーフィズムなど、コンパイル中に決定できない型もいくつかあります。コンパイラは、完全な継承チェーンを維持するために、コンパイル中に仮想関数テーブル、type_info、およびその他の補助情報をオブジェクト メモリ モデルに追加します。 、プログラムが実行されるまで待って、どの関数を呼び出すかを決定します。弱い型付け言語ではコンパイルはあまり意味がありません。コンパイルされても判断できないことがたくさんあるからです。
弱い型指定言語は一般に 1 回コンパイルして実行されるため、コンテキストに応じて多くの有用な情報を導き出すことができます。この種の 1 回コンパイルして実行される言語はインタプリタ言語と呼ばれますが、従来の言語はインタープリタ言語と呼ばれます。最初にコンパイルされてから実行される言語をコンパイル言語と呼びます。
強く型付けされた言語はより厳密であり、コンパイル中に多くのエラーが見つかる可能性があり、大規模なシステムレベルおよび産業レベルのプロジェクトの開発に適していますが、弱い型付け言語はより柔軟で、高度なコーディングが必要です。効率性、導入の容易さ、学習コストの低さなど、あなたのスキルを発揮してください。さらに、強く型付けされた言語の IDE は一般的により強力で、優れたコード認識と豊富なプロンプト情報を備えていますが、弱く型付けされた言語のコードは一般にエディターで直接記述されます。
引数の推論
クラステンプレートを使用してオブジェクトを作成する場合、実際のパラメータを明示的に指定する必要があります。たとえば、以下のオブジェクトを作成する場合、実際のパラメータの型を指定する必要があります。このようにすると、コンパイル時にコンパイラーが独自に推論する必要がなく、直接使用できます。
template<typename T1, typename T2> class Point;
Point<int, int> p1(10, 20); //在栈上创建对象
Point<char*, char*> *p = new Point<char*, char*>("东京180度", "北纬210度"); //在堆上创建对象
関数テンプレートの場合、関数を呼び出すときに実際のパラメータを明示的に指定することはできません。。
//函数声明
template<typename T> void Swap(T &a, T &b);
//函数调用
int n1 = 100, n2 = 200;
Swap(n1, n2);
float f1 = 12.5, f2 = 56.93;
Swap(f1, f2);
コンパイラは、実際のパラメータの型に従って T の型を自動的に推測します。関数の引数を通じてテンプレート引数を決定するこのプロセスは、と呼ばれます。テンプレート引数の推論。
関数テンプレートの実パラメータ推論とは、関数呼び出し中に実パラメータの型に従って型パラメータの特定の型を見つけるプロセスを指します。これはほとんどの場合機能しますが、型パラメータが多数ある場合は、個々の型を推論することはできないため、実際のパラメータを明示的に指定する必要があります。つまり、コンパイラが実際のパラメータに基づいてテンプレート内のすべての型を推測できない場合、呼び出しエラーが発生します。
関数テンプレートとクラス テンプレートの実パラメータの指定方法は同じで、<>
特定の型も含め、すべて関数名の後に追加されます。明示的に指定されたテンプレート引数は、対応するテンプレート引数と左から右の順序で照合されます。
func<int, int>(10);
非型パラメータ
テンプレートは汎用テクノロジーであり、データの型をパラメータ化し、言語の柔軟性を高めることを目的としています。型パラメータのサポートに加えて、テンプレートは非型パラメータもサポートします。非型パラメータは、型ではなくパラメータを渡すために使用されます。通常の仮パラメータと同様に、特定の型を指定する必要があります。関数テンプレートを呼び出すとき、またはクラス テンプレートを通じてオブジェクトを作成するとき、非型パラメータはユーザーまたはユーザーによって提供されます。コンパイラが推論します。
型以外のパラメーターの型は任意に指定できません。整数、またはオブジェクトまたは関数へのポインターのみにすることができます。非型パラメータが整数の場合、それに渡される実際のパラメータ、またはコンパイラによって推定される実際のパラメータは定数式である必要があります。
非型パラメータがポインタの場合、ポインタにバインドされる実パラメータは静的な有効期間を持つ必要があるため、実パラメータは仮想アドレス空間の静的データ領域に格納される必要があります。ローカル変数はスタック領域に配置され、動的に作成されたオブジェクトはヒープ領域に配置されます。
インスタンス化する
テンプレートはメモリを消費しません、最終的に生成された関数またはクラスはメモリを占有します。テンプレートから関数またはクラスを生成するプロセスは、テンプレートのインスタンス化と呼ばれます。、型に対して生成された関数またはクラスの特定のバージョンは、テンプレートのインスタンスと呼ばれます。
テンプレートは、必要なコードを生成するようにコンパイラーに指示する、コンパイラーの一連の命令として見ることができます。。テンプレートのインスタンス化はオンデマンドで実行され、どのタイプが使用されても、そのタイプの関数またはクラスが生成され、事前に大量のコードが生成されることはありません。コンパイラは、型パラメータに渡された実際のパラメータに基づいて関数またはクラスの特定のバージョンを生成します。同じ型は 1 回だけ生成されます。
クラス テンプレートのインスタンス化では、クラス テンプレートを使用してオブジェクトを作成するときにすべてのメンバー関数がインスタンス化されるわけではありません。実際に呼び出されたときにのみインスタンス化されます。メンバー関数が呼び出されない場合、インスタンス化されることはありません。したがって、インスタンス化は遅延ローカルです。
テンプレートリファレンスマルチファイルプログラミング
クラスでも関数でも、宣言と定義の分離は同じで、関数の定義は別のファイルに置くので、最終的に解決すべき問題は関数呼び出しとの対応付けだけです。関数定義 (関数定義のアドレスを検索し、関数呼び出しに埋め込みます)、この作業が完了するとコネクタが作成されます。
しかし、テンプレートでは、テンプレートの宣言と定義を別々の関心事に分割するのは正しくありません。テンプレートは実際の関数やクラスではなく、関数やクラスを生成するための単なる図面です。
- テンプレートのインスタンス化はオンデマンドで実行され、その型の関数またはクラスが使用される型に生成され、コードは事前に生成されません。
- テンプレートのインスタンス化は、リンカーではなくコンパイラーによって行われます。
- インスタンス化プロセス中は、宣言や定義などのテンプレートの詳細を知っておく必要があります。
したがって、テンプレートは単なるテンプレートであり、メモリを占有しません。コンパイル時に、コンパイラは必要に応じて対応するタイプのコードを生成するため、テンプレートのインスタンス化はコンパイラによって行われます。2 つのファイルに分かれている場合は、リンクされています。コンパイラーは関数の充填作業を完了します。これにより、リンク中に対応するインスタンスが見つからなくなる可能性があります。したがって、テンプレートの定義と宣言は通常、ヘッダー ファイルに配置されます。
次のように明示的なインスタンス化を使用して、定義と宣言を 2 つのファイルに配置できます。テンプレートは function の後に追加されず<>
、関数プロトタイプが直接続きます。つまり、テンプレートは関数プロトタイプに対応する特定のバージョンにインスタンス化されます。
template void Swap(double &a, double &a);
クラス テンプレートを明示的にインスタンス化するのと同じ方法で、クラスを追加する必要があります。クラス テンプレートを明示的にインスタンス化すると、メンバー関数やメンバー変数を含むクラスのすべてのメンバーが一度にインスタンス化されます。
クラステンプレートと継承
- クラス テンプレートはクラス テンプレートから派生します
- クラス テンプレートはテンプレート クラスから派生します
- クラステンプレートは通常のクラスから派生します
- 通常のクラスはテンプレート クラスから派生します
クラステンプレートと友達
- クラステンプレートの友達としての関数、クラス、クラスのメンバー関数
- クラステンプレートの友達としての関数テンプレート
- クラスの友達としての関数テンプレート
- クラス テンプレートの友達としてのクラス テンプレート
例外処理
プログラムエラーは主に、構文エラー、論理エラー、実行時エラーの3 種類に分類されます。例外メカニズムは、実行時に発生するエラーを捕捉し、何が起こったのかをユーザーに通知してプログラムを終了できるようにするものです。
例外をキャッチするための構文は次のとおりです。
try{
//可能抛出异常的语句
}catch(exceptionType variable){
//处理异常的语句
}
tryとcatch はキーワードであり、その後にステートメント ブロックが続きます。catchキーワードの背後にある例外タイプ変数は、catch が処理できる例外のタイプを示します。変数variableは例外情報を受け取り、プログラムが例外をスローした際にデータが作成されますが、このデータにはエラー情報が含まれており、プログラマはこの情報を基に例外を判断することができます。
例外が検出されるとすぐにスローされ、すぐに try によって検出され、例外後のステートメントは実行されません。つまり、例外が検出されるとキャッチ位置にジャンプし、例外ポイント以降のステートメントは再度実行されません。throwキーワードは例外をスローするために使用されます。例外は try によって検出され、catch によってキャッチされます。
例外の型には、基本型または集約型があり、C++ 自体または標準ライブラリによってスローされる例外は、例外クラスの例外です。したがって、例外がスローされると、例外クラスまたはそのサブクラスのオブジェクトが作成されます。
例外は実行フェーズ中に生成されます。例外はどのようなタイプであってもよく、事前に予測することはできません。したがって、コンパイルフェーズ中にタイプが正しいかどうかを判断することはできません。プログラムが実行されて例外がスローされた後でのみ、タイプが正しいかどうかを判断できます。スローされた例外の型は、catch によって処理される型と一致します。トライの後に複数のキャッチが続くこともあります。一致する catch タイプが見つかると、他の catch ステートメントは実行されなくなります。
例外は検出される前に明示的にスローされる必要があり、 throw を使用して例外をスローできます。
throw exceptionData;
例外は異常なデータであり、あらゆる情報を含むことができます。throw キーワードは、関数本体で例外をスローするだけでなく、現在の関数が関数ヘッダーと関数本体の間でスローできる例外のタイプを示すこともできます。例外仕様。
double func (char param) throw (int);
関数 func の戻り値の型が double で、char 型のパラメータを持ち、int 型の例外のみをスローできることを示します。関数が複数の例外をスローする必要がある場合は、コンマを使用して例外を区別できます。
double func (char param) throw (int, char, exception);
例外クラスは標準例外と呼ばれ、例外クラスはヘッダー ファイル内にあり、次のように宣言されます。
class exception{
public:
exception () throw(); //构造函数
exception (const exception&) throw(); //拷贝构造函数
exception& operator= (const exception&) throw(); //运算符重载
virtual ~exception() throw(); //虚析构函数
virtual const char* what() const throw(); //虚函数
}
オブジェクト指向
コピーコンストラクター
オブジェクトの作成は2 つの部分で構成されます。最初の部分は次のとおりです。スペースを割り当てる、次に初期化。
copy によってオブジェクトを初期化するとき、特別なコンストラクターが呼び出されます。コピーコンストラクター。コピー コンストラクターにはパラメーターが 1 つだけあり、型は通常、現在のクラスへの参照であり、通常は const 参照です。
クラスには同時に 2 つのコピー コンストラクターを含めることができます。1 つはパラメーターが const 参照であり、もう 1 つはパラメーターが非 const 参照です。
コピー コンストラクターが明示的に定義されていない場合、コンパイラーはデフォルトのコピー コンストラクターを自動的に生成します。このコンストラクターは、古いオブジェクトのメンバー変数を使用して、新しいオブジェクトのメンバー変数に値を割り当てます。ただし、クラスが動的に割り当てられたメモリ、開いているファイル、他のデータへのポインタ、ネットワーク接続などの他のリソースを保持している場合、デフォルトのコピー コンストラクターはこれらのリソースをコピーできません。すべてのリソースを完全にコピーするには、コピー コンストラクターを明示的に定義する必要があります。オブジェクトのデータ。
コピー コンストラクターは、オブジェクトがコピー初期化されるときに呼び出されます。オブジェクトの初期化とは、オブジェクトにメモリを割り当てた後、初めてメモリをデータで埋めることを指します。このプロセスではコンストラクタが呼び出され、オブジェクトは作成直後に初期化される必要があります。
初期化と代入の両方で、データがメモリに書き込まれます。定義と同時に代入することを初期化といいます,定義後の代入を代入といいます、初期化は 1 つだけであり、多くの代入が可能です。基本型の場合、初期化と代入に違いはありませんが、クラスの場合は違いがあります。これは、オブジェクトが初期化されるときにコンストラクターが呼び出されるからです (コピーによってオブジェクトが初期化されるときにコピー コンストラクターが呼び出されます)。値が割り当てられると、再割り当て関数が呼び出されます。オーバーロードされた代入演算子。
オブジェクトが初期化されるとコンストラクターが呼び出され、異なる初期化メソッドは異なるコンストラクターを呼び出します。渡された実際のパラメータが初期化用の場合は通常のコンストラクタが呼び出され、オブジェクトが他のオブジェクトのデータで初期化されている場合はコピー コンストラクタが呼び出され、コピーによって初期化が完了します。
浅いコピーと深いコピー
プリミティブ型と単純なオブジェクト データの間のコピーはメモリをビットごとにコピーすることです。このメソッドは呼び出されます。浅いコピーこれは、memcpy 関数を呼び出した場合の効果と似ています。
クラスが動的に割り当てられたメモリや他のデータへのポインタなどのリソースを保持している場合、デフォルトのコピー関数ではこれらのリソースをコピーできないため、コピー コンストラクターを明示的に定義する必要があります。コピー コンストラクターを明示的に定義すると、元のオブジェクトのメンバー変数を新しいオブジェクトにコピーするだけでなく、新しいオブジェクトにメモリのブロックが割り当てられ、元のオブジェクトが保持するメモリもコピーされます。このように、元のオブジェクトと新しいオブジェクトは関連付けられておらず、互いに独立しており、一方のオブジェクトのデータを変更しても、もう一方のオブジェクトには影響しません。オブジェクトが保持するリソースをコピーするこの動作は、ディープコピーの場合、コピー コンストラクターを明示的に定義する必要があります。
明示的なコピー コンストラクターを使用する代わりに、デフォルトのコピー コンストラクターを使用してポインターなどのリソースを使用してオブジェクトを初期化すると、コピーされたオブジェクトのポインターが元のオブジェクトと同じメモリ部分を指すようになります。したがって、ポインタ型のメンバ変数を持つ場合はディープコピーを使用し、元のオブジェクトと新しいオブジェクトを独立させる必要があります。
オブジェクトがコピーによって初期化されると、コピー コンストラクターが呼び出され、オブジェクトに値が割り当てられると、オーバーロードされた代入演算子が呼び出されます。代入演算子の明示的なオーバーロードがない場合でも、エディターはデフォルトの方法で代入演算子をオーバーロードします。演算子をオーバーロードするデフォルトの方法は、元のオブジェクトのすべてのメンバー変数を新しいオブジェクトに代入することです。これは、代入演算子のオーバーロードとは異なります。デフォルト コピー コンストラクターも同様に機能します。
コンストラクターと型変換関数の変換
ユーザーが変換方法を指定しなくても、異なるデータ型を相互に変換できます。自動型変換、ユーザーが明示的に指定する必要があるキャスト。型変換ルールはクラスのメンバー関数の形式でのみ現れるため、型変換はクラスにのみ適用されます。
変換コンストラクターこれは、他の型を現在のクラス型に変換するコンストラクターであり、変換コンストラクターにはパラメーターが 1 つだけあります。変換コンストラクターは、他の型を現在のクラス型に変換するために使用できるコンストラクターでもあり、コンストラクターの本来の意味であるオブジェクトの初期化にも使用できます。
オブジェクトをコピー初期化する場合、コンパイラはデータを変数にコピーする前に変換コンストラクターを呼び出します。コンストラクターは、オブジェクトの作成時にオブジェクトを初期化するもので、コンパイラーは渡されるさまざまな実際のパラメーターに応じてさまざまなコンストラクターを照合します。
- デフォルトのコンストラクター: コンパイラーによって自動的に生成されるコンストラクター。
- 通常のコンストラクタ: ユーザー定義のコンストラクタ。
- コピー コンストラクター: コピーによってオブジェクトが初期化されるときに呼び出されます。
- 変換コンストラクター: 他の型を現在のクラス型に変換するときに呼び出されます。
どのようなコンストラクターであっても、オブジェクトの初期化に使用されます。オブジェクトの作成時にオブジェクトを初期化するだけでなく、他の場合にもコンストラクターが呼び出されます。たとえば、オブジェクトをコピーして初期化する場合は、コピー コンストラクターが呼び出されます。変換コンストラクターは、他の型を現在のクラス型に変換するときに呼び出されます。
変換コンストラクターは、他の型を現在の型に変換できますが、その逆はできません。型変換関数現在のクラス型は他の型に変換できます。型変換関数はクラス内にメンバー関数としてのみ出現できます。
operator type(){
//todo
return data;
}
Operator は C++ キーワード、type は変換対象の型、data は返される型データです。
type のデータが返されることがわかっているため、戻り値 type を指定する必要はありません。型変換関数には戻り値の型がないように見えますが、実は暗黙的に戻り値の型を示しています。型変換関数にはパラメーターもありません。現在のクラスのオブジェクトを別の型に変換するために、コンパイラーは現在のオブジェクトのアドレスを this ポインターに割り当て、現在のオブジェクトを関数本体で操作できるようにします。
型変換関数は演算子のオーバーロードに似ており、どちらも演算子キーワードを使用するため、型変換関数は型変換演算子とも呼ばれます。
型変換関数と変換コンストラクターは逆に動作します。: 変換コンストラクターは他の型を現在のクラス型に変換し、型変換関数は現在のクラス型を他の型に変換します。これら 2 つの関数がないと、型変換と演算を実装するために多数の演算子オーバーロード関数が作成されます。
型変換
ユーザーが変換方法を指定しなくても、異なるデータ型を相互に変換できます。自動型変換(暗黙的な型変換)。ユーザーは変換方法を明示的に指定する必要があります。キャスト。
暗黙的な型変換では、コンパイラの内部変換規則、またはユーザー定義の変換コンストラクターと型変換関数が使用されます。
データはバイナリ形式でメモリに保存されます。データ型とは、データが解釈される方法を指します。、そのようなデータをどのように解釈するかは、使用前に決定する必要があります。この解釈方法はデータの種類によって決まります。データ型は、データの種類を記述し、データがどのように解釈されるかを決定するために使用されます。データ型には、組み込み型とユーザー定義型が含まれます。
データ型変換とは、データが占めるバイナリ ビットを再解釈し、必要に応じて再解釈中にデータを変更することです。暗黙的な型変換の場合、コンパイラは既知の変換規則に従ってデータのバイナリ ビットを変更するかどうかを決定できます。また、必須の型変換の場合、対応する変換規則がないため、できることはデータのバイナリ ビットを再解釈することだけです。 data ですが、データのバイナリ ビットは修正できません。暗黙的な型変換と必須の型変換の最も基本的な違い。
データのバイナリ ビットを変更することは、変換されたデータを正しい値に調整できるようにするために非常に重要です。
暗黙的な型変換では既知の変換ルールを使用する必要があり、柔軟性は限られていますが、データを適切に調整できるため安全です (リスクはほとんどありません)。キャストでは、さまざまな型のポインター (参照) 間の変換、const から非 const への変換、int からポインターへの変換 (一部のコンパイラでは逆も可能) など、より広範囲のデータ型の間で変換できます。柔軟性が高い反面、データを適切に調整できないためリスクも多く、プログラマーは慎重に使用する必要があります。
強制は万能薬ではありません。型変換は関連する型または類似の型の間でのみ発生します。強制を使用した場合でも、無関係な 2 つの型を相互に変換することはできません。継承関係のない2つのクラスの相互変換、基底クラスから派生クラスへの変換(ダウンキャスト)、クラス型から基本型への変換、ポインタとクラス型の相互変換はできません。 。
I/Oストリーム
C++ には多くの io クラスが含まれており、総称して と呼ばれます。ストリームクラス。
- istream: キーボードからのデータ入力を受け取るためによく使用されます。
- ostream: データを画面に出力するためによく使用されます。
- ifstream: ファイル内のデータを読み取るために使用されます。
- ofstream: ファイルにデータを書き込むために使用されます。
- iostream: このクラスの関数は両方とも 1 つにまとめられており、入力と出力の両方に使用できるため、istream クラスと ostream クラスから継承されます。
- fstream: ifstream と ofstream の機能を組み合わせたもので、ファイル内のデータを読み取るだけでなく、ファイルにデータを書き込むこともできます。
実際、cinは istream クラスのオブジェクトであり、coutは ostream のオブジェクトです。これらはすべて で宣言されており<iostream>
、さらに、このヘッダー ファイルでは、ostream クラス オブジェクト (それぞれ cerr と clog) も宣言されています。cerrは警告メッセージやエラーメッセージを出力するために使用され、clogはプログラム実行時のログ情報を出力するために使用されます。
cout はリダイレクトをサポートしますが、clog と cerr はリダイレクトをサポートせず、データを画面に出力することしかできません。このようにC++であらかじめ作成しておいたオブジェクトをこう呼びます。組み込みオブジェクト、直接使用できます。
入力および出力のリダイレクト
リダイレクトには 3 つの方法があります
freopen()
関数実装リダイレクト:freopen()
で定義されており<stdio.h>
、標準ライブラリ内の関数であり、入力ストリームと出力ストリームをリダイレクトするために特別に使用されます。
string name, url;
//将标准输入流重定向到 in.txt 文件
freopen("in.txt", "r", stdin);
cin >> name >> url;
//将标准输出重定向到 out.txt文件
freopen("out.txt", "w", stdout);
cout << name << "\n" << url;
rdbuf()
リダイレクトを実現する関数:rdbuf()
この関数は<ios>
ヘッダー ファイルに定義されており、入出力ストリームのリダイレクトを実現するために使用されます。ios は istream と ostream の基本クラスであるため、cin と cout はこの関数を直接呼び出してリダイレクトを実現できます。
streambuf * rdbuf() const;//仅是返回一个指向当前流缓冲区的指针
streambuf * rdbuf(streambuf * sb);//用于将 sb 指向的缓冲区设置为当前流的新缓冲区,并返回一个指向旧缓冲区的对象
ifstream fin("in.txt");//打开 in.txt 文件,等待读取
ofstream fout("out.txt");//打开 out.txt 文件,等待写入
oldcin = cin.rdbuf(fin.rdbuf());//用 rdbuf() 重新定向,返回旧输入流缓冲区指针
oldcout = cout.rdbuf(fout.rdbuf());//用 rdbuf() 重新定向,返回旧输出流缓冲区指针
- コンソールを介したリダイレクトの実現: プログラム add の実行時に
<in.txt >out.txt
、このパラメーターは<in.txt
プログラム内の cin 入力ストリームをリダイレクトし、>out.txt
プログラム内の cout 出力ストリームもリダイレクトします。
出力バッファ
各ストリームはバッファを管理します、プログラムによって読み書きされたデータを保存するために使用されます。バッファリング メカニズムを導入すると、オペレーティング システムはプログラムからの複数の出力を単一のシステム レベルの書き込み操作に結合できます。書き込み操作には時間がかかるため、オペレーティング システムが複数の出力操作を 1 つのデバイス書き込み操作として結合できるようにすると、パフォーマンスが大幅に向上します。
バッファ (データが実際に出力デバイスまたはファイルに書き込まれる場所) をフラッシュする理由は次のとおりです。
- プログラムは正常に終了し、メイン関数の戻り動作の一部としてバッファのフラッシュが実行されます。
- バッファがいっぱいになった場合、新しいデータをバッファに書き込み続ける前に、バッファをフラッシュする必要があります。
- endlなどの演算子を使用してバッファをフラッシュします。
- 各出力操作の後に、演算子unitbufを使用してストリームの内部状態を設定し、バッファをクリアできます。デフォルトでは、cerr はunitbuf に設定されているため、cerr はすぐにフラッシュされます。
- 出力ストリームは別のストリームに関連付けられる場合があり、その場合、関連付けられたストリームの読み取りおよび書き込み時に、関連付けられたストリームのバッファがフラッシュされます。
バッファをフラッシュできる endl 演算子に加えて、flush 演算子と end 演算子があります。flash はバッファをフラッシュしますが、文字は出力しません。end は、バッファに NULL 文字を挿入し、バッファをフラッシュします。
入力ストリームが出力ストリームに関連付けられている場合、出力ストリームからデータを読み取ろうとすると、まず関連付けられた出力ストリームがフラッシュされます。標準ライブラリは cout と cin を関連付けます。tie()
機能できる出力ストリームをバインドするために使用されます、オーバーロードされたバージョンが 2 つあります。
ostream* tie() const;//返回指向绑定的输出流的指针
ostream* tie(ostream* os);//将os指向的输出流绑定在该对象上,并返回上一个绑定的输出流指针
cin.tie(&cout); //仅仅是用来展示,标准库已经将 cin 和 cout 关联在一起
//old_tie 指向当前关联到 cin 的流(如果有的话)
ostream *old_tie = cin.tie(nullptr); // cin 不再与其他流关联
//将 cin 与 cerr 关联,这不是一个好主意,因为 cin 应该关联到 cout
cin.tie(&cerr); //读取 cin 会刷新 cerr 而不是 cout
cin.tie(old_tie); //重建 cin 和 cout 间的正常关联
一文字を読む: get()
istream クラスのメンバー関数で、出力ストリームから文字を読み取り、入力の最後に到達すると戻り値が返されます。終了後。EOF は、iostream クラスで定義された整数定数で、値は -1 です。
文字列の行を読み取る:get()
は istream クラスのメンバー関数であり、オーバーロードされたバージョンが 2 つあります。
istream & getline(char* buf, int bufSize);
istream & getline(char* buf, int bufSize, char delim);
最初のバージョンでは、入力ストリームから bufsize-1 の文字をバッファ buf に読み取ります。または、改行文字が検出されるまで、関数は buf に読み取られたデータの末尾に自動的に追加します\0
。
2 番目のバージョンと最初のバージョンの違いは、最初のバージョンは\0
まで読み込むのに対し、2 番目のバージョンは delim 文字まで読み込むことです。\n
または delim は buf に読み込まれませんが、出力ストリームから取得されます。
delim 前の入力ストリームの文字数が\n
bufsize に達するかそれを超えると、読み取りエラーが発生し、この読み取りは完了しますが、その後の読み取りは失敗します。
指定した文字を無視する方法:ignore()
これは istream クラスのメンバー関数であり、関数プロトタイプは ですistream & ignore(int n =1, int delim = EOF);
。この関数は指定された入力ストリーム内の n 文字をスキップし、デフォルトでは 1 文字をスキップします。
入力ストリーム内の次の文字を見てください:peek()
これは、istream クラスのメンバー関数です。関数のプロトタイプは、int peek()
peek 関数は入力ストリーム内の次の文字を返しますが、入力ストリームから文字を取得しません。入力ストリームが終了した場合、peek は EOF を返します。
判定入力終了: 標準入力をファイルにリダイレクトした後、cin はファイルからデータを読み取ることができます。入力データの量が不明で、終了マークがない場合は、コンソールに特殊文字を入力して終了を示すことができます。Linux では ctrl+D を入力して終了を示し、Windows では ctrl+Z を入力して Enter を押して終了を示します。
cin が入力の終わりを判断する方法: コンソール読み取りの終わりを判断: ファイルの終わりであるか、ctrl+z または ctrl+D であるか、それは終了記号です。cin は通常の読み取り時に true を返します。終了記号に遭遇すると false を返すので、 cin の戻り値によって読み込みが終了したかどうかを判断できます。
ファイル操作
ファイルストリーム
データ ストレージの観点から見ると、すべてのファイルの本質は同じであり、すべてバイトで構成されています。プレーンテキストファイルに加えて、画像、ビデオ、実行可能ファイルなども一般的に呼ばれます。バイナリーファイル。
ファイルストリームクラスは、ファイル操作を実現するために標準ライブラリで提供されている主に3つのクラスです。ifstream
、ofstream
、fstream
、ifstream
および はおよびofstream
から派生しているため、これら 3 つのクラスにはそれぞれおよびのすべてのメンバー メソッドがあります。istream
ostream
istream
ostream
開閉
ファイルを操作する前に、まずファイルを開く必要があります。これにより、ファイル名を指定することでファイルとファイル ストリーム オブジェクトの関連付けが確立され、後でファイルを操作するときに、関連付けられたストリーム オブジェクトを通じて実行できるようになります。2 つ目は、ファイルを開くときにその開き方を指定できることです。
ファイルを開くには 2 つの方法があります。
- ストリーム オブジェクトの open メンバー関数を使用してファイルを開きます。
void open(const char* szFileName, int mode)
、最初のパラメータはファイル名へのポインタ、2 番目のパラメータはファイルを開く方法です。openメンバ関数でファイルをオープンした場合は戻り値で判定でき、成功となります。 - ファイル ストリーム オブジェクトを定義する場合、ファイルはコンストラクターを通じて開かれます。
ifstream::ifstream (const char* szFileName, int mode = ios::in, int);
、最初のパラメータはファイル名へのポインタ、2 番目のパラメータはファイルを開く方法、3 番目のパラメータは通常は使用されません。
ファイル テキストとバイナリ テキストの違い: 物理的な観点からは、バイナリ ファイルと文字ファイルの間に違いはなく、すべてバイナリでディスクに保存されます。テキスト ファイルには ASCII や UTF-8 などの文字エンコーディングが使用されており、テキスト エディタはこれらのエンコーディング形式を認識し、エンコーディング値を表示用の文字に変換できます。テキスト モードとバイナリ モードの間に本質的な違いはありませんが、改行の処理が異なります。Linux プラットフォームでは、バイナリ ファイルとテキスト ファイルを開くことに違いはありません。Windows プラットフォームでは、テキスト ファイルは改行として結合されます。ファイルをテキスト ファイルとして開いてファイルを読み取る場合、プログラムは\r\n
ファイル内のすべてを\r\n
削除します\r
。
open を使用してファイルを開くことは、ファイル ストリーム オブジェクトとファイルの間の関連付けを確立するプロセスであり、クローズすることは、ファイル ストリーム オブジェクトとファイルの間の関連付けを切断することですが、ファイル ストリームは破棄されません。
開いているファイルは close で閉じる必要があります。バッファは、バッファがいっぱいになるかファイルが閉じられた場合にのみデータをファイルに書き込むため、flush を使用するとバッファをリフレッシュできますが、flush を使用すると、出力ストリーム バッファをフラッシュしてファイルにデータを書き込むことができます。
読み書き
バイナリでデータを読み書きすると、スペースを節約でき、各データが同じサイズのスペースを占有するため、検索も簡単になります。
バイナリでデータを読み書きするには、 と は使用できなくなりました<<
。>>
読み取りと書き込みを使用する必要があります。読み取りメソッドはバイナリ形式でファイルからデータを読み取るために使用され、書き込みメソッドは使用されます。データをバイナリ形式でファイルに書き込みます。
ostream & write(char* buffer, int count);
,
istream & read(char* buffer, int count);
,
取得(&P)
ファイルに格納されている文字を 1 つずつ読み取るか、ファイルに 1 つずつ格納するには、get と put を使用します。
ファイルはハードディスクに保存されるため、ハードディスクのアクセス速度はメモリのアクセス速度に比べて非常に遅く、ファイルを書き込むたびにハードディスクにアクセスしなければならない場合、読み書き速度が非常に遅くなります。したがって、オペレーティング システムは put または get リクエストを受け取ると、まず指定された文字をメモリに格納するか、ハードディスクからデータを読み取ってメモリ (ファイル ストリーム出力バッファ、ファイル) に格納します。ストリーム入力バッファ)、バッファがリフレッシュされると、データはまとめてハードディスクに書き込まれるか、次回のデータ取得時にファイルストリーム入力バッファから直接取得されます。
この使用getline()
方法では、cin 入力ストリーム バッファから文字列を 1 行読み取ることができ、また、指定されたファイル内のデータを 1 行読み取ることもできます。
ファイル読み取りポインタを移動して取得する
ファイルの読み取りおよび書き込みを行うときに、ファイル内の特定の場所に直接ジャンプして読み取りおよび書き込みを開始したい場合があります。その場合は、ファイルの読み取りおよび書き込みポインターを移動してから、読み取りおよび書き込み操作を実行する必要があります。
ostream & seekp (int offset, int mode);//设置文件读指针的位置
istream & seekg (int offset, int mode);//设置文件写指针的位置
モードには 3 つのオプションがあります。
- ios::beg は、ファイルの先頭の後のオフセット バイトを指します。オフセットが 0 に等しい場合は、ファイルの先頭からであることを意味します。この場合、オフセットは負ではない数値である必要があります。
- ios::cur は、現在位置のオフセットバイトを指します。
- ios::end は、ファイルの末尾からのオフセットバイトを指します。
tellg()
そしてtellp()
現在のポインタの位置を取得することができます。