C++ の基本概念
C++ の基本概念 1
- Linux 3 の静的ライブラリと動的ライブラリの類似点と相違点
- 動的計画法を理解していますか 11
- STL ソースコードでのマップ実装 (赤黒ツリーの 5 つの要件) 12
B+ ツリー 13 - map と unorder_map (hash_map) の違い 14
- 新しいオブジェクトには何ステップが必要ですか? 新しい演算子をオーバーロードすることで変更できるステップはどれですか 15
- malloc は一度にどれくらいのメモリ空間を適用できますか? 16
- new と malloc 17 の違い
- 削除と解放の違い19
- いつ削除する必要があるか[] 20
- 静的ポリモーフィズム 動的ポリモーフィズム 21
- 動的バインディング (遅延バインディング、実行時バインディング)、静的バインディング (早期バインディング、コンパイル時バインディング) 21
- デザインパターンを学習しましたか? ステートマシンモード、シンプルファクトリー、ストラテジーモード、フライウェイトモード、テンプレートモード 1回24
- ポインターと参照の違い: 24
- C 言語コードのメモリ レイアウトの詳細 26
- メモリ内のヒープとスタックの違い 29
- ユーザープロセスメモリ空間 30
- 型変換とは何ですか? (4種類の変換、それぞれ例付き) 30
- メモリ調整の原則 33
- インライン関数(インライン関数の利点とマクロ定義との違いについての話) 35
- typedef と #define の使用法と違い 37
- const とマクロ定義の違い #define 39
- リンクディレクティブ: extern "C" 40
- 外部と含める 44
- 継承メカニズムでオブジェクト、参照、ポインタの間で変換する方法 45
- クラスオブジェクトのみを動的に割り当てることができ、クラスオブジェクトを定義することはできないことを理解する方法 46
- テンプレートの長所と短所 47
- テンプレートの特化 48
- 明示的な機能: プライマー P264 50
- strcpy 関数 53
- memcpy 55の欠陥
- メモリオーバーフロー、メモリリーク 56
- メモリリークをチェックするソフトウェアは何ですか 58
- 初期化リスト 59
- パブリック、プロテクト、プライベートのアクセス属性と継承 60
- スマートポインター61
- 仮想関数と純粋仮想関数 64
- コンストラクターを仮想関数にできないのはなぜですか。65 に使用される仮想デストラクターは何ですか?
- pthread_create()関数(Linux環境におけるスレッド作成関数)のプロトタイプを書き出す 66
- C 67 の volatile キーワード
- 親クラスのポインタはサブクラスを指すことができるのに、その逆はできないのはなぜですか?
- stlのリストのsize関数はどのように実装されていますか? それはトラバーサルですか、それとも保存する変数を設定していますか? 達成しましょう。どちらが好みですか、またその理由は何ですか? 69
- サブクラス a は b と c を複数継承しており、b と c の両方に仮想関数があります。a には仮想関数テーブルがいくつありますか? 仮想関数ポインタはいくつありますか? b と c に属する仮想関数が順番に呼び出されるとき、仮想関数ポインタはどのように変化しますか? 70
- 浅いコピーまたは深いコピー 72
- コピーコンストラクターと代入演算子の違い 76
- システム コールには sleep () 関数と usleep () 関数があります。usleep () 関数はマイクロ秒レベルであると主張していますが、実際にそれほど高速であると信じていますか? それともシステムコールはマイクロ秒の速度に達するのでしょうか? 77
- オーバーロード、オーバーライド、非表示 79
- this、const メンバー関数、可変型 80
- STL メモリ管理 81
- 静的グローバル変数と非静的グローバル変数の違い 83
- メイクファイルの書き込み 84
- コアダンプ 85 によって生成されるいくつかの考えられる状況
- ネットワーク接続における長いリンクと短いリンク。86
- 同期 IO と非同期 IO、ブロッキングとノンブロッキング 87
- スタックとキューの実装 89
- ビッグエンディアン リトルエンディアン 90
- コンパイラはどのような最適化を行いますか 92
- オブジェクトのメンバー関数をコールバックしますが、このオブジェクトはもう存在しない可能性があります。どうすればよいですか? 93
- C++11 の機能を紹介する 94
- ハードディスクコピー速度、メモリコピー速度 95
- Gdb デバッグ、コア ダンプ ファイルのデバッグ 96
- コンストラクターは仮想関数を呼び出すことができますか98
- Dynamic_cast が失敗するとどうなりますか? 100
- デストラクターが例外 101 をスローできないのはなぜですか
23.externとinclude
1. extern を変数または関数の前に置くと、変数または関数の定義が別のファイルにあることを示し、コンパイラがこの変数または関数に遭遇したときに他のモジュールでその定義を見つけるように指示できます。これはコンパイルできますが、他の場所で定義されていない場合、リンクは通過しません。もう 1 つの機能は外部「C」です。
2.Include は、プリコンパイル中の単純なテキスト置換です。つまり、.h コンテンツを常に .c に置き換えます。
一般に、extern と include の違いは、「小売」と「卸売」の違いと考えることができます。include は卸売、extern は小売です。
プログラムがより明確になり、保守が容易になるため、include を使用することをお勧めします。
大量の extern が必要な場合は、それらをヘッダー ファイルに入れてから #include を検討できます。
24. 継承メカニズムでオブジェクト、参照、ポインタの間で変換する方法
派生クラスは基本クラスに転送され、自動的に完了します。それ以外の場合は、強制変換が必要です。
クラスオブジェクトのみを動的に割り当てることができ、クラスオブジェクトを定義することはできないことを理解する方法
26. テンプレートの長所と短所
利点:
コードの再利用が実現され、プログラマーの時間と労力が節約されます。これが標準ライブラリの出現の理由でもあります。
欠点:
- テンプレートはコンパイル中にコードを生成する動作であり、ブレークポイントを使用してデバッグできないため、バグが発生しやすくなります。
- テンプレートを多用するとコード領域が拡大し、コンパイル時間が大幅に長くなります。(テンプレートは .h ファイル内でのみ定義できます。プロジェクトが大きい場合、コンパイル時間は非常に異常です。)
27. テンプレートの特化
C++ におけるテンプレートの特殊化は、テンプレートのインスタンス化とは異なり、特定の型でのテンプレート パラメーターの特定の実現は、テンプレートの特殊化と呼ばれます。テンプレートの特殊化はテンプレートの特殊化とも呼ばれ、それぞれ関数テンプレートの特殊化とクラス テンプレートの特殊化があります。
別の例:
Vector は Vector の特殊バージョンです。一般に、ブール値をビット単位で保存しますが、実際にブール値を保存するわけではありません。この特化版で解決すべき問題は、記憶容量の問題である。ここから、スペース効率を向上させるためにビットを使用すると、時間効率が問題になる可能性があることがわかります (時間効率は低いが、スペースは少ない)。
28. 明示的な関数: プライマー P264
1) Explicit キーワードは、クラス内のコンストラクター宣言でのみ使用できます。
2) 明示的なキーワードは、単一パラメーターのコンストラクターに作用します (一致する限り、(int x, int y=0) も問題ありません)。
3) C++ では、explicit キーワードはクラスのコンストラクターを変更するために使用されますが、変更されたコンストラクターのクラスは、対応する暗黙的な型変換を受けることができません。
29. strcpy関数
strcpy() はデータのソースを気にせず、文字列の長さもチェックしません。停止できるのは文字列終端文字 '\0' だけです。ただし、このターミネータが見つからない場合は、strSrc の内容がバイト単位でコピーされ、strDest プリセット領域を埋めた後、オーバーフローした文字がバッファの後ろのデータを置き換えます (メモリ オーバーフロー)。これらの溢れたデータが後続の strcpy 関数の戻りアドレスをカバーするだけの場合、関数呼び出し後、プログラムは攻撃者が設定した「戻りアドレス」に転送され、あらかじめ設定されたトラップに素直に入ります。このような罠に陥らないようにするには、加害者に隙を与えておきましょう。strncpy の使用を検討してください。strcat と strncat についても同様です。
3 30. memcpy の欠陥
memcpy は、順方向コピー (つまり、上位アドレスから下位アドレスへのデータのコピー) は実現できますが、逆方向コピー (つまり、下位アドレスから上位アドレスへのデータのコピー) は実現できません。
その実装は前から後ろにコピーされるため、2 つが重複すると、元のアドレスの先頭が宛先アドレスの末尾で上書きされます。
自分で実装する場合は、送信元アドレス < 宛先アドレス、前から後ろにコピーできます
。
送信元アドレス > 宛先アドレス、後ろから前にコピーします。
31. メモリオーバーフロー、メモリリーク
オーバーフローは、表現されるデータが、コンピューターがそのデータに割り当てたスペースの範囲を超えるときに発生します。
メモリ オーバーフローの原因は数多くありますが、一般的な理由は次のとおりです:
1. データベースから一度に取得するデータが多すぎるなど、メモリにロードされるデータの量が多すぎる、2.
オブジェクトへの参照があるコレクション クラス内でクリアされていないため、リサイクルできません。
3. コード内に無限ループがあるか、ループによって生成される反復オブジェクト エンティティが多すぎます。
4. 起動パラメータのメモリ値の設定が小さすぎます。
理解を容易にするために、たとえ話をするとよいでしょう。バッファー オーバーフローは、5 ポンドしか入らない容器に 10 ポンドの砂糖を入れるようなものです。容器がいっぱいになると、残りがカウンターや床にこぼれてしまい、めちゃくちゃになってしまいます。コンピュータ プログラムの作成者はコードを作成しましたが、そのコードでは、宛先領域またはバッファ ゾーン (5 ポンドのコンテナ) が新しいコンテンツを完全に保持するのに十分な大きさであるかどうかを適切にチェックしませんでした。砂糖が含まれており、その結果バッファオーバーフローが発生する可能性があります。新しい場所に置く予定だったデータがサイズに合わず、あちこちにこぼれてしまうと、大きな問題が発生する可能性があります。ただし、これはバッファが単にオーバーフローした場合にのみ問題になります。今のところ、破壊的なものではありません。砂糖がこぼれたとき、カウンターは覆われました。砂糖を拭き取るか、掃除機で取り除くと、カウンターの元の外観を復元できます。対照的に、バッファがオーバーフローすると、余分な情報がコンピュータのメモリの以前の内容を上書きします。これらの上書きされた内容は、保存するか復元できない限り、永久に失われます。
オーバーフローしたデータがたまたま関数の背後の戻りアドレスをカバーしていた場合、関数が呼び出された後、プログラムは攻撃者が設定した(オーバーフローによってカバーされた)「戻りアドレス」に転送し、あらかじめ設定されたトラップに素直に入ります。
メモリ オーバーフローを引き起こす可能性のある関数:
strcpy()、strcat()、sprintf()、scanf()、sscanf() は、宛先アドレスのサイズが入力のサイズよりも大きいかどうかをチェックしません。例外に。strncpy、strncat()、snprintf() などの n バージョンを使用できます。
メモリ不足によるメモリ オーバーフローとは、プログラムがメモリを申請するときに、使用するのに十分なメモリ領域がなく、メモリ不足が発生することを意味します。たとえば、整数が申請されますが、long 型に格納できる数値は次のとおりです。そこに保存される、つまりメモリオーバーフローです。
メモリ リークとは、プログラムがメモリを申請した後、要求されたメモリ領域を解放できないことを意味します。メモリ リークの害は無視できますが、メモリ リークの蓄積による影響は非常に深刻です。メモリがどれだけ割り当てられていても、メモリは使用されます。遅かれ早かれ起きます(ファイナルハザード)。
メモリ リークは最終的にメモリ不足につながります。
メモリ オーバーフローとは、割り当てを要求したメモリがシステムが提供できる量を超え、システムが要求を満たすことができず、オーバーフローが発生することを意味します。
メモリ リークとは、使用するメモリの割り当て (新規) をシステムに適用するが、使用後にメモリを返さない (削除) ことを意味します。失われた場合、システムはそれを必要とするプログラムにメモリを再割り当てできません。皿にはどうやってもフルーツが4個しか入らないのに、5個フルーツを入れても地面に落ちて食べられない。これはオーバーフローです!たとえば、スタックがいっぱいの場合、それをスタックにプッシュすると、必然的にスペースのオーバーフローが発生します。これはオーバーフローと呼ばれます。つまり、割り当てられたメモリはデータ項目のシーケンスを保存するのに十分ではありません。これをメモリ オーバーフローと呼びます。
メモリ リークは発生方法によって分類され、次の 4 つのカテゴリに分類できます。
- メモリリークが頻繁に発生します。メモリ リークのあるコードは複数回実行され、実行されるたびにメモリ リークが発生します。
- 時々メモリリークが発生します。メモリ リークを引き起こすコードは、特定の状況または操作手順でのみ発生します。頻繁と散発は相対的なものです。特定の環境では、散発的な状態が定期的な状態になる場合があります。したがって、メモリ リークを検出するには、テスト環境とテスト方法が重要です。
- 1 回限りのメモリ リーク。メモリ リークのあるコードは 1 回だけ実行されるか、アルゴリズムの欠陥により常に 1 つのメモリ リークのみが発生します。たとえば、メモリはクラスのコンストラクターで割り当てられますが、デストラクターでは解放されないため、メモリ リークは 1 回だけ発生します。
- 暗黙的なメモリ リーク。プログラムはプロセスの実行中に継続的にメモリを割り当てますが、最後までメモリは解放されません。厳密に言えば、最終的にプログラムは割り当てられたメモリをすべて解放するため、ここでメモリ リークは発生しません。しかし、数日、数週間、さらには数か月にわたって実行する必要があるサーバー プログラムの場合、メモリを期限内に解放しないと、最終的にシステムのメモリがすべて使い果たされてしまう可能性があります。したがって、このタイプのメモリ リークを暗黙的メモリ リークと呼びます。
プログラムを使用するユーザーからすれば、メモリリーク自体は何ら害を及ぼすものではなく、一般ユーザーとしてはメモリリークの存在を全く感じません。本当の危険はメモリ リークの蓄積であり、最終的にはシステム内のすべてのメモリを消費する可能性があります。この観点から見ると、1 回限りのメモリ リークは累積しないため有害ではありませんが、暗黙的メモリ リークは頻繁で散発的なメモリ リークよりも検出が難しいため、非常に有害です。
バッファオーバーフロー攻撃の原理は
ここで詳しく説明されています
32. メモリリークをチェックするソフトウェアは何ですか?
1.ccmalloc - Linux および Solaris 上の C および C++ プログラム用の単純なメモリ リークおよび malloc のデバッグ ライブラリ。
2.Dmalloc - デバッグ Malloc ライブラリ
3.Electric Fence - Linux ディストリビューションで Bruce Perens によって作成された malloc() デバッグ ライブラリ。
4. Leaky - Linux でのメモリ リークを検出するプログラム。
5. LeakTracer - Linux、Solaris、および HP-UX 上の C++ プログラムのメモリ リークを追跡および分析します。
Visual C++ のデバッグ バージョンの C ランタイム ライブラリ (C ランタイム ライブラリ)。コードを診断し、メモリ リークを追跡するのに役立ついくつかの機能がすでに提供されています。Visual Leak Detector は、Visual C++ 用の無料のメモリ リーク検出ツールです。
6. MEMWATCH - Johan Lindh によって書かれた、オープンソースの C 言語メモリ エラー検出ツールで、主に gcc プロセッサを使用します。
7.Valgrind - C および C++ で書かれたプログラムを対象とした Linux プログラムのデバッグとプロファイリング
Grind
8.KCachegrind - Cachegrind と Calltree によって生成されたプロファイリング データの視覚化ツール。
9.IBM Rational PurifyPlus - 開発者が C/C++、マネージド .NET、Java、および VB6 コードのパフォーマンスと信頼性のエラーを特定するのに役立ちます。PurifyPlus は、メモリ エラーとリークの検出、アプリケーション パフォーマンス プロファイリング、コード カバレッジ分析などを 1 つの完全なツールキットに組み合わせています。
10. ParasoftInsure++ - C/C++ アプリケーション用のランタイム エラー自動検出ツール C/C++ プログラムを自動的に監視し、メモリ破損、メモリ リーク、ポインタ エラー、I/O などのエラーを検出できます。また、一連の独自のテクノロジー (SCI テクノロジーや突然変異テストなど) を使用して、コードを徹底的に検査およびテストし、エラーの正確な位置を特定し、詳細な診断情報を提供します。MicrosoftVisual C++ のプラグインとして実行できます。
11.Compuware DevPartner for Visual C++ BoundsChecker Suite は、C++ 開発者向けに設計されたエラー検出およびデバッグ ツール ソフトウェアを実行します。Microsoft Visual Studio および C++ 6.0 のプラグインとして実行します。
12. Electric Software GlowCode - メモリ リーク チェック、コード プロファイラー、関数呼び出し追跡、その他の機能を含みます。C++ および .Net 開発者に、完全なエラー診断およびランタイム パフォーマンス分析ツールキットを提供します。
33. 初期化リスト
初期化リスト (イニシャライザー) に含める必要があるもの:
定数メンバー。定数は初期化のみが可能で割り当てができないため、初期化リストに含める必要があります。 参照
型、参照は定義時に初期化する必要があります。再割り当てできないため、初期化リストにも記述する必要があります。
デフォルト コンストラクターのないクラス型。初期化リストを使用すると、デフォルト コンストラクターを呼び出して初期化することはできませんが、コピー コンストラクターを直接呼び出して初期化することができるためです。
初期化リストを使用する理由
クラスのメンバーを初期化するには 2 つの方法があります。1 つは初期化リストを使用する方法、もう 1 つはコンストラクター本体で代入操作を実行する方法です。
初期化リストの使用は、主にパフォーマンスの問題に基づいています。
1) int、float などの組み込み型の場合、初期化リストの使用とコンストラクター本体での初期化に大きな違いはありません。
2) しかし、クラス型の場合は初期化子リストを使用する方が良いのですが、なぜですか? 初期化リストを使用すると、デフォルト コンストラクターを 1 回呼び出すプロセスが削減されます (コンストラクター本体では、最初にデフォルト コンストラクターを呼び出す必要があります (いずれの場合も、関数本体に入る前にデフォルト コンストラクターを呼び出す必要があります)。その後、コンストラクターをコピーします (「 28. ディープ コピーまたはシャロー コピー))、これはデータ集約型のクラスに非常に効率的です。したがって、初期化リストを使用できる場合は、可能な限り初期化リストを使用するのが良い原則です。
上記のコードは最初に m_y=I を実行し、次に m_x=m_y を実行し、最後にそれらは同じ値になると考えるかもしれません。ただし、コンパイラは最初に m_x を初期化し、次に m_y を初期化します。これは、これらがこの順序で宣言されているためです。その結果、m_x は予測できない値になります。これを回避するには 2 つの方法があります。1 つは、常に初期化したい順序でメンバーを宣言することです。2 つ目は、初期化子リストを使用する場合は、常に宣言された順序でメンバーをリストすることです。これは混乱を取り除くのに役立ちます。
34. パブリック、プロテクト、プライベートのアクセス属性と継承
35.
スマート ポインタ
auto_ptr 98
C++11 には unique_ptr、shared_ptr、weak_ptr があります
詳細については、ここを参照してください
2. スマート ポインタを使用すると、この問題を防ぐことができます
変数はすべてスタック メモリから自動的に削除されるため、ポインタ ps が占有するメモリはが解放され、ps が指すメモリも自動的に解放されると便利です。
デストラクターにはこの機能があることがわかっています。ps にデストラクターがある場合、そのデストラクターは ps の有効期限が切れると、それが指すメモリを自動的に解放します。しかし、ps の問題は、それが単なる通常のポインタであり、デストラクタ関数を備えたクラス オブジェクト ポインタではないことです。それがオブジェクトを指している場合、そのデストラクターは、オブジェクトの有効期限が切れたときに、指されたメモリを削除できます。
これは、スマート ポインター auto_ptr、unique_ptr、shared_ptr の背後にある設計思想です。私の簡単な要約は次のとおりです。基本型ポインタをクラス オブジェクト ポインタとしてカプセル化し(このクラスは、さまざまな基本型のニーズを満たすテンプレートである必要があります)、デストラクタに削除ステートメントを記述して、ポインタが指すメモリ空間を削除します。 。
3. unique_ptr と auto_ptr
上の例から、 unique_ptr と auto_ptr は非常に似ていることがわかります. 実際、非常に単純に理解できると思いますが、 auto_ptr は任意に値を割り当てることができますが、割り当て後は元の値が返されます。オブジェクトは無意識のうちに破棄されます。困惑させられます。また、unique では、自由に値をコピーして代入することはできません (コピー コンストラクターと = 演算子、=delete を無効にすることでより安全です)。本当に渡したい場合は、値の場合、メモリ転送 std: 移動するという明示的な説明があります。その後、この方法で値を渡した後、前のオブジェクトも破棄されます。移動全体によって、以前の unique_ptr オブジェクトがこの後無効になることが明らかになるだけです。 4. unique_ptr unique_ptr 「ユニークに
」
所有する それが参照するオブジェクトは、同時に指定されたオブジェクトを指す unique_ptr を 1 つだけ持つことができます (コピー セマンティクスを禁止し、移動セマンティクスのみを使用することで実現されます)。
unique_ptr ポインター自体のライフサイクル: unique_ptr ポインターが作成されてからスコープを出るまで。スコープを離れるときに、それがオブジェクトを指している場合、そのオブジェクトが指しているオブジェクトは破棄されます (デフォルトでは削除演算子が使用され、ユーザーは他の操作を指定できます)。
unique_ptr ポインタとそれが指すオブジェクトの関係: スマート ポインタのライフサイクル中に、スマート ポインタが指すオブジェクトは、スマート ポインタの作成時にコンストラクタを通じて指定したり、リセットを通じて再指定したりするなど、変更される可能性があります。メソッド、release メソッドによる所有権の解放、および move セマンティクスによる所有権の譲渡。
詳細については
、この5.shared_ptr
複数のスマート ポインターの共通メモリ ブロックを参照してください。
1. ポインターが指すメモリー ブロックを現在共有しているスマート ポインター オブジェクトの数を示すためにカウンターshared_count が導入されました (共有する必要があるため、unique_ptr よりも多くのカウンターがあります) 2. デストラクターはメモリー ブロックを直接解放しません
。 pointer に対応し、shared_count が 1 より大きい場合、メモリは解放されませんが、参照カウントは 1 減らされ、カウントが 1 になった場合にのみメモリが解放されます (unique_ptr はメモリ ブロックを独占します) 3. コピー構築および代入演算子は一般的な
意味を提供するだけです。コピー関数は、参照カウントに 1 を加算します
(スマート ポインター オブジェクトが作成されると、カウント用のスペースが作成されます)。
6.weak_ptrweak_ptrは、
shared_ptr と連携するために導入されたスマート ポインタです。通常のポインタの動作を持たず、operator* や - >をオーバーロードしないため、スマート ポインタよりもshared_ptrのアシスタントです最大の役割は、shared_ptrが傍観者のようにリソースの使用状況を観察できるように支援することです
。
は、shared_ptr と連携して動作するように設計されており、(複雑なメモリ管理ではなく) リソース監視権限を取得するには、shared_ptr または別のweak_ptr オブジェクトから構築する必要があります。ただし、weak_ptr はリソースを共有しないため、その構築によってポインター参照カウントが増加することはありません。
weak_ptr のメンバー関数 use_count() はリソースの参照カウントを監視することができ、別のメンバー関数expired() (期限切れ、無効) は管理オブジェクトが解放されたかどうかを検出するために使用されます。weak_ptr は、リソースを操作するために、非常に重要なメンバー関数 lock() を使用して、監視されたshared_ptr から利用可能なshared_ptr オブジェクトを取得できます。ただし、expired()==true の場合、lock() 関数は null ポインタを格納するshared_ptr を返します。
weak_ptr は循環参照の問題を解決します
。32 行目が作成されたとき、父親の参照数は 1 で、36 行目の父親の参照数は 2 に加算され、スコープ外の -1 の参照数は 1 であり、解決できませんでした。解放されます。
修正後:
元の36行目は、weak_ptrは父親の参照数を増加させないため範囲外となり、父親の参照数は0で解放されます。Father のson を指すスマート ポインタを破壊すると、son の参照カウントが -1 から 1 になり、範囲外になると -1 から 0 になるため、son も解放できます。
36. 仮想関数と純粋仮想関数
(1) 抽象クラスの定義:純粋仮想関数を持つクラスを抽象クラスと呼びます。
(2) 抽象クラスの役割:
抽象クラスの主な機能は、関連する操作を継承階層内の結果インターフェイスとして編成することであり、これにより派生クラスに共通のルートが提供され、派生クラスは次のように実装されます。インターフェイスとしての操作の基本クラス。したがって、派生クラスは、実際には、サブクラスのグループの操作インターフェイスの一般的なセマンティクスを記述し、これらのセマンティクスもサブクラスに渡されます。サブクラスは、これらのセマンティクスを具体的に実装したり、これらのセマンティクスを独自のサブクラスに渡すこともできます。
(3) 抽象クラスを使用する場合の注意:
抽象クラスは基底クラスとしてのみ使用でき、その純粋仮想関数の実装は派生クラスによって提供されます。派生クラスが純粋仮想関数を再定義せず、基本クラスの純粋仮想関数のみを継承する場合、派生クラスは依然として抽象クラスです。基本クラスの純粋仮想関数の実装が派生クラスで与えられる場合、派生クラスは抽象クラスではなく、オブジェクトを作成できる具象クラスになります。
抽象クラスはオブジェクトを定義できません。
概要:
1. 純粋仮想関数の宣言は次のとおりです: virtual void function1()=0; 純粋仮想関数を定義することはできません (関数本体はなく、ステートメントのみです)。純粋仮想関数が使用されます。派生クラス、つまりインターフェイスの動作を制御します。純粋仮想関数を含むクラスは抽象クラスです。抽象クラスはインスタンスを定義できませんが、抽象クラスを実装する具象クラスへのポインタまたは参照を宣言できます。
2. 仮想関数の宣言は次のとおりです: virtual ReturnType FunctionName(Parameter); 仮想関数は実装する必要があります。実装されていない場合、コンパイラはエラーを報告し、エラー メッセージは次のとおりです: error LNK****: 未解決の外部シンボル“
public: virtual void __thiscall ClassName: :virtualFunctionName(void)”
3. 仮想関数の場合、親クラスとサブクラスの両方に独自のバージョンがあります。ポリモーフィック モードで呼び出された場合の動的バインディング。
4. 純粋仮想関数のサブクラスが実装されます。純粋仮想関数はサブクラス内の仮想関数になります。サブクラスのサブクラス、つまり孫クラスは仮想関数をオーバーライドでき、呼び出されたときに動的にバインドされます。多態性による。
5. 仮想関数は、C++ でポリモーフィズムを実装するために使用されるメカニズムです。中心となるアイデアは、基本クラスを介して派生クラスによって定義された関数にアクセスすることです。
6. ヒープ上にメモリが動的に割り当てられている場合、デストラクタは仮想関数である必要がありますが、純粋な仮想関数である必要はありません。
7. フレンドはメンバー関数ではありません。仮想関数にできるのはメンバー関数だけです。そのため、フレンドを仮想関数にすることはできません。ただし、フレンド関数に仮想メンバー関数を呼び出すことで、フレンドの仮想問題を解決できます。
8. デストラクタは仮想関数である必要があり、対応するオブジェクト型のデストラクタが呼び出されます。したがって、ポインタがサブクラス オブジェクトを指している場合は、サブクラスのデストラクタが呼び出され、次に基本クラスのデストラクタが呼び出されます。自動的に呼び出されます。
9. 仮想関数と純粋仮想関数の定義に静的識別子を使用することはできません。理由は非常に簡単です。静的に変更された関数はコンパイル時に事前バインドが必要ですが、仮想関数は動的にバインドされます (実行時バインド)。両者によって修正される機能のライフサイクル(ライフリサイクル)も異なります。
純粋仮想関数を持つクラスは抽象クラスであり、オブジェクトを生成できず、派生のみが可能です。派生クラスの純粋仮想関数は書き換えられていないため、派生クラスは依然として抽象クラスです。
純粋仮想関数を定義する目的は、基本クラスをインスタンス化不可能にすることです。
そのような抽象データ構造自体をインスタンス化することは意味がありません。あるいは、実装を与えることも意味がありません。
37. コンストラクターを仮想関数にできないのはなぜですか? 仮想デストラクターは何に使用されますか?
仮想関数が vtable に対応することは誰もが知っていますが、この vtable は実際にはオブジェクトのメモリ空間に格納されます。問題が発生します。コンストラクターが仮想の場合、vtable を介して呼び出す必要がありますが、オブジェクトがインスタンス化されていない、つまりメモリ空間がなく、vtable が見つからないため、コンストラクターを仮想にすることができません。関数。
仮想関数テーブルポインタはいつ初期化されますか? もちろんコンストラクターです。new を使用してオブジェクトを作成する場合、最初のステップは必要なメモリを適用することであり、2 番目のステップはコンストラクターを呼び出すことです。想像してみてください。コンストラクターが仮想関数の場合、vtbl を通じて仮想コンストラクターのエントリ アドレスを見つける必要があります。明らかに、適用したメモリは初期化されていないため、vtbl は存在しません。したがって、コンストラクターを仮想にすることはできません。
ポリモーフィズムでは、親クラスのデストラクターを仮想関数として設定できます。これは、そのようなことが起こらないようにするためであり、その結果、親クラス ポインターを新しいサブクラス オブジェクトに使用し、親クラス オブジェクトを解放すると
、サブクラス オブジェクトを解放しないとメモリ リークが発生します。
多くの場合、基本クラスのポインタを介してオブジェクトを破棄します。このとき、デストラクタが仮想関数でない場合、オブジェクトの型を正しく識別できず、デストラクタを正しく呼び出すことができません。
38. pthread_create()関数(Linux環境におけるスレッド作成関数)のプロトタイプを書き出す
39.
C における volatile キーワード volatileの元の意味
は「揮発性」であり、関連する最適化を実行しないようにコンパイラーに指示します。
1. volatile 変数は、2 つの操作の間のレジスターにキャッシュされません。マルチタスク環境、割り込み環境、さらには setjmp 環境では、変数が他のプログラムによって変更される可能性がありますが、コンパイラ自体はそれを知ることができません。volatile はこの状況をコンパイラに伝えます。
2. 定数のマージや定数の伝播などの最適化は実行しないので、次のコードを実行します。
if の条件は無条件に true として扱われません。
3. 揮発性変数の読み取りと書き込みは最適化されません。変数に値を代入しても後で使用しない場合、コンパイラーは多くの場合その代入を省略できます。
親クラスのポインタはサブクラスを指すことができるのに、その逆はできないのはなぜですか?
41. stl のリストの size 関数はどのように実装されていますか? それはトラバーサルですか、それとも保存する変数を設定していますか? 達成しましょう。どちらが好みですか、またその理由は何ですか?
作者が他の機能のパフォーマンスを考慮して妥協したため、トラバーサルを使用して実装されています。たとえば、size() を格納するためのトラバーサルが必要ない場合は、次の関数の実装が高速になります。リンク リストを変更するときにサイズを維持するのに時間を費やす必要はありません。
splice(iteratorposition, list& x, iterator first, iterator last);メソッド
の妥協案は、その実現のためにサイズメソッドを O(N) として設計することです。
スプライス方法は、リンク リスト A のいくつかの要素をリンク リスト B に直接連結することです。size() が O(1) の複雑さになるように設計されている場合、スプライスを行うときは、最初と最後の間の長さをトラバースする必要があります。 (リンク リスト A リンク リストの長さから最初と最後 (移動する要素) の間の長さを引いたものを保存します)! したがって、著者は、size メソッドが O(N) になるように設計されている、つまり、size 変数が保存されないため、splice メソッドの実行時にサイズを維持するためにトラバースする必要がなく、必要なのは次のことだけであると考えています。いくつかのポインタを変更してください。
同様に、サイズを保存するために変数を設定した場合、リンク リストの分割操作 (リンク リストを分割するための中間ノードを与えるなど) を実行するときに、どのようにしてサイズを実現しますか? サイズを更新するにはどうすればよいですか? (走査と更新しかできないため、以前に設定した変数は役に立たないのと同じです)
この 2 つの方法にはそれぞれ利点があります。size 変数を保存して size() 関数を実行するほうが高速です。ただし、リンク リストのサイズを変更する操作がある場合は、size() を更新する必要があり、何かを更新する必要がある場合に走査されます。サイズ変数は保存されないため、size() 関数をトラバースする必要がありますが、リンク リストのサイズを変更する操作があるときにサイズを受動的に更新する必要はありません。
size() の代わりに empty() を使用できる場合は、O(1) を保証できる empty を使用できます。stl のリストなど、size() の一部のコンテナーは O(n) です。
詳しくはこちらをご覧ください:詳しくはこちらをご覧ください
42. サブクラス a は b と c を多重継承しており、b と c の両方に仮想関数があります。a には仮想関数テーブルがいくつありますか? 仮想関数ポインタはいくつありますか? b と c に属する仮想関数が順番に呼び出されるとき、仮想関数ポインタはどのように変化しますか?
1. 仮想関数テーブル:
単一継承と同様に、すべての仮想関数が仮想関数テーブルに含まれます。違いは、多重継承には複数の仮想関数テーブルがあることです。サブクラスが親クラスの仮想関数をオーバーライドする場合、サブクラスの関数は、サブクラスは、対応する仮想関数の位置にある親クラスの関数をオーバーライドします。サブクラスに新しい仮想関数がある場合、これらの仮想関数は最初の仮想関数テーブルの後に追加されます。
具体的には:
2. 親クラス ポインタは、
多重継承からサブクラス ポインタのメモリ レイアウトを強化します. サブクラスによって新たに追加された仮想関数が、最初の基本クラスの仮想関数テーブルに追加されることがわかります。 subclass 最初の基底クラスのアドレスと同じなのでポインタを移動する必要はありませんが、他の親クラスにdynamic_castingする場合は対応するポインタを移動する必要があります。
A a;
B b;
ここで、B は A から継承します。
a=b;
a のデータ メンバーなど、プログラマに表示されるすべてのデータ メンバーは b の値に書き換えられますが、仮想関数ポインターなど、コンパイラによって隠蔽されたデータ メンバーは引き続き a に格納されます。 . a のポインタは b によってカバーされません。
b のポインタが a に割り当てられる場合、これはポリモーフィズムであり、a の仮想関数ポインタもそれに応じて変更されます。
派生クラス オブジェクトのみを基本クラス オブジェクトに割り当てることができ、その逆も同様です。
基本クラスへのポインターは派生クラス オブジェクトを指すことはできますが、その逆はできません。派生クラスへのポインターは基本クラスへのポインターを指すことはできません。どうしてこれなの?これは、派生クラスのオブジェクトが占有する記憶領域は、通常、基本クラスのオブジェクトの記憶領域よりも大きく、派生クラスは基本クラスのメンバーを継承するだけでなく、独自のメンバーも持つためです。 , したがって、基底クラスのポインタは派生クラスのオブジェクトを操作します。 , 基底クラスのポインタは基底クラスのオブジェクトと同じ方法で派生クラスのオブジェクトを操作し、基底クラスのオブジェクトが占有するメモリ空間は通常基底クラスのポインタは派生クラスのオブジェクトよりも小さいため、基底クラスのポインタが派生クラスのオブジェクトを超えてデータを操作することはありません。
同様に、基本クラスへの参照は、派生クラス オブジェクトのエイリアスとして使用できますが、その逆はできません。派生クラスへの参照を、基本クラス オブジェクトのエイリアスとして使用することはできません。例:
上記のプロセスを説明するには、 を使用します。
43. 浅いコピーまたは深いコピー
C++で文字列の代入関数を実装するにはどうすればよいですか? 浅いコピーか深いコピーか? 浅いコピーの何が問題なのでしょうか? (ある文字列が削除されると、別の文字列が保持していたデータもクリアされます) ディープコピーの何が問題になっているのでしょうか? (反復的でメモリを消費します) では、より良い実装は何でしょうか? (浅いコピーを実行し、参照カウントを設定し、カウントが 0 になったら削除します) この方法では、スレッドの安全性の問題は発生しますか? どうやって対処すればいいのでしょうか?
浅いコピーはポインタの単なるコピーです。コピー後、2 つのポインタは同じメモリ空間を指します。ディープ コピーはポインタをコピーするだけでなく、ポインタが指す内容もコピーします。ディープ コピー後のポインタは 2 つを指します。異なるアドレスのポインタ。
浅いコピーはメンバーデータ間の1対1の代入であり、コピーされる値に1つずつ値を代入します。しかし、そのような状況が存在する可能性があります。オブジェクトにはリソースも含まれており、そのリソースはヒープ リソースまたはファイルである可能性があります。。値がコピーされると、2 つのオブジェクトが共通のリソースを持ち、そのリソースに同時にアクセスできるため、問題が発生します。このような問題を解決するのがディープコピーであり、一度リソースを割り当てることで、オブジェクトに異なるリソースを持たせても、リソースの内容は同じになります。ヒープ リソースの場合、ヒープ メモリの一部を開き、元の内容をコピーします。
Ps: クラスなしでコピー コンストラクターを設計し、それを提供しない場合、このクラスのオブジェクトを使用してオブジェクトに値を割り当てるときに実行されるプロセスは浅いコピーになります。
文字列の代入関数を実装するにはどうすればよいですか? 浅いコピーですか? 深いコピーですか?
次のように、これは深いコピーです (ポインターはなく、システム リソースが関与し、深さと深さによって領域が割り当てられるため、効果は同じです)。