記事からの主な引用:
Axiu の学習ノート (interviewguide.cn)
Nuke.com - 求人検索ツール | 筆記試験質問バンク | 面接体験談 | おすすめインターンシップ募集、就活・就職のワンストップソリューション_Nuke.com (nowcoder.com)
何の本を読みましたか(百度)
C++ Primer Plus、Code Random Notes、清華大学の教科書。
コンピュータ ネットワーク: コンピュータ ネットワーク - トップダウン アプローチ、HTTP 図、TCP/IP 図。
オペレーティング システム: 最新のオペレーティング システム、コンピュータ システムについての深い理解
1. C++の機能構成
1.1 特徴
C++言語の特徴を簡単に説明します
参考回答
-
C++ は、C 言語に基づいたオブジェクト指向メカニズムを導入しており、 C 言語とも互換性があります。
-
C++ には 3 つの主要な機能があります (1) カプセル化。(2) 継承。(3) 多態性。
-
C++言語で書かれたプログラムは構造が明確で拡張しやすく、プログラムの可読性も良好です。
-
C++ によって生成されたコードは高品質で効率的であり、アセンブリ言語よりもわずか 10% ~ 20% 遅いだけです。
-
C++はより安全であり、const 定数、参照、4 種類のキャスト変換 (static_cast、dynamic_cast、const_cast、reinterpret_cast)、スマート ポインター、try-catch などを追加します。
-
C++は再利用性が高く、C++ ではテンプレートの概念が導入され、これに基づいて開発を容易にするために標準テンプレート ライブラリ STL (Standard Template Library) が実装されました。
-
同時に、C++ は常に進化している言語です。C++ の後続のバージョンでは、nullptr、自動変数、Lambda 匿名関数、右辺値参照、C++11 で導入されたスマート ポインターなど、多くの新機能が開発されました。
C言語とC++の違いについて話します
参考回答
-
C 言語は C++ のサブセットであり、C++ は C 言語と非常に互換性があります。ただし、C++ には、参照、スマート ポインター、自動変数など、多くの新機能があります。
-
C++ はオブジェクト指向プログラミング言語であり、C 言語はプロセス指向プログラミング言語です。
-
C 言語には、ポインタ使用の潜在的な危険性、キャストの不確実性、メモリ リークなど、安全ではない言語機能がいくつかあります。C++ では、セキュリティを向上させるために、const 定数、参照、キャスト変換、スマート ポインタ、try-catch などの多くの新機能が追加されています。
-
C++は再利用性が高く、C++ ではテンプレートの概念が導入され、これに基づいて開発を容易にする標準テンプレート ライブラリ STL が実装されました。C++ STL ライブラリは、C 言語関数ライブラリよりも柔軟で多用途です。
ASCLL
数字 0 ~ 9 は ASCII コード 48 ~ 57 に対応します。
大文字 AZ は ASCII コード 65 ~ 90 に対応します。
小文字の az は ASCII コード 97 ~ 122 に対応します。
1.2 構成
インクルードヘッダーファイルの順序と、二重引用符 "" と山括弧 <> の違いについて説明します。
参考回答
-
違い:
(1) 山括弧 <> 内のヘッダー ファイルはシステム ファイル、二重引用符 "" 内のヘッダー ファイルはカスタム ファイルです。
(2) コンパイラの前処理段階でヘッダー ファイルを検索するために使用されるパスが異なります。
-
パスを検索:
(1) 山括弧を使用したヘッダー ファイルのパスの検索 <>: コンパイラによって設定されたヘッダー ファイルのパス --> システム変数。
(2) ダブルクォート "" を使用したヘッダー ファイルの検索パス: 現在のヘッダー ファイル ディレクトリ --> コンパイラによって設定されたヘッダー ファイル パス --> システム変数。
C++ヘッダファイルの詳細説明 <bits/c++std.h> - ユニバーサルヘッダファイル
アドバンテージ:
-
ヘッダー ファイルの作成と検索にかかる時間を大幅に節約できます。
-
非常に簡潔なので、コードは 1 行だけで十分です。
欠点:
-
含まれているヘッダー ファイルが多すぎるため、コンパイルに非常に時間がかかります。
-
このヘッダー ファイルは、一部の古いバージョンのコンパイル ソフトウェアには含まれていない場合があります。
2. C++ 構文
2.1 構造体とクラス
C++ における構造体とクラスの違いについて話しましょう
参考回答
-
Struct は通常、データ構造のコレクションを記述するために使用されますが、class はオブジェクト データのカプセル化です。
-
struct のデフォルトのアクセス権限は public (アクセス権限の設定がないとも言える) ですが、class のデフォルトのアクセス制御権限は private です。
-
typename と同様に、class キーワードを使用してテンプレート パラメーターを定義できますが、 struct を使用してテンプレート パラメーターを定義することはできません。
回答分析
-
C++ の構造体は、C の構造体を拡張したものです。宣言における両者の違いは次のとおりです。
C C++ メンバー関数 禁じられている できる 静的メンバー 禁じられている できる アクセス制御 デフォルトはパブリックであり、変更できません。 パブリック/プライベート/保護された 相続関係 継承できない クラスまたは他の構造から継承可能 初期化 データメンバーを直接初期化することはできません できる -
使用上の違い: C で構造体を使用するには、struct キーワードを追加するか、typedef を使用して構造体のエイリアスを取得する必要がありますが、C++ では、struct キーワードを省略して直接使用できます。次に例を示します。
struct Student{ int iAgeNum; string strName; } typedef struct Student Student2; //C でのエイリアス struct Student stu1; //C stu2 では通常 Student2 を使用します; // C ではエイリアスで Student stu3 を使用します ; //C++ では使用します
構造体に値を直接割り当てることはできますか?
できる。
を宣言するときに直接初期化でき、同じ構造体の異なるオブジェクトを直接割り当てることもできます。
注: 同じメモリを指しているポインタが複数ある場合、1 つのポインタによってこのメモリを解放すると、他のポインタの不正な操作が発生する可能性があります。したがって、このメモリ空間を解放する前に、他のポインタがこのメモリ空間を使用していないことを必ず確認してください。
2.2 コードの実行
C++ のコードから実行可能バイナリ ファイルまでのプロセスを簡単に説明します。
参考回答
C++ は C 言語に似ており、C++ プログラムにはソース コードから実行ファイルへのプリコンパイル、コンパイル、アセンブリ、リンクの4 つのプロセスがあります。
回答分析
-
プリコンパイル: このプロセスの主な処理操作は次のとおりです。
(1) #defineをすべて削除し、マクロ定義をすべて展開します。
(2) #if、#ifdef などのすべての条件付きプリコンパイル命令を処理します。
(3) #include プリコンパイルディレクティブを処理し、インクルードされたファイルをプリコンパイル ディレクティブの場所に挿入します。
(4) コメントのフィルタリング
(5)行番号とファイル名の識別を追加します。
-
コンパイル: このプロセスの主な処理操作は次のとおりです。
(1) 字句解析: ソースコードの文字列を一連のトークンに分割します。
(2) 文法解析: トークンに対して文法解析を実行し、構文ツリーを生成します。
(3) 意味分析: 表現が意味があるかどうかを判断します。
(4) コードの最適化:
(5) ターゲットコード生成:アセンブリコードを生成します。
(6) ターゲットコードの最適化:
-
アセンブリ: このプロセスは主にアセンブリコードをマシンが実行できる命令に変換します。
-
リンク:異なるソース ファイルによって生成されたターゲット ファイルをリンクして、実行可能プログラムを形成します。
リンクは静的リンクと動的リンクに分けられます。
静的リンクと動的リンク
静的リンクとは、呼び出される関数やプロセスが、リンク時に生成された実行可能ファイルにリンクされていることを意味します。静的ライブラリを削除しても、実行可能プログラムの実行には影響しません。生成された静的リンク ライブラリのサフィックスは、Windows では .lib、Linux ではサフィックスは .a です。
利点: プログラムはリリース時にライブラリに依存する必要がなく、独立して実行できます。
欠点: プログラムのサイズは比較的大きくなり、メモリ内に同じターゲット ファイルのコピーが複数存在します。また、静的ライブラリが更新された場合は、すべての実行可能ファイルを再リンクする必要があります。
動的リンクとは、リンク処理中に呼び出された関数コードがリンクされるのではなく、実行処理中にリンクされる関数が見つかることを意味します。生成される実行可能ファイルには関数コードは含まれず、関数の再配置情報のみが含まれます。したがって、ダイナミック ライブラリを削除すると、実行可能プログラムは実行できなくなります。生成されたダイナミック リンク ライブラリには、Windows では .dll 接尾辞が付けられ、Linux では .so 接尾辞が付きます。
利点: ディスクに複数のコピーを保存する必要がなく、複数のプログラムで同じコードを共有できる 更新が便利: 更新時に元のターゲット ファイルを置き換えるだけで済みます。
短所: 実行時の読み込みにより、プログラムの初期実行パフォーマンスに影響を与える可能性があります。
DLLで何を使用するか、インターフェイスと関数の違い
DLL は、複数のプログラムで同時に使用できるコードとデータを含むライブラリです。すべてのプログラムは、DLL に含まれる機能を使用して特定の操作を実行できます。これは、コードの再利用とメモリの効率的な使用を促進するのに役立ちます。利点は、プログラムの実行開始時にすべてのコードをロードする必要がなく、プログラムが特定の機能を必要とするときにのみ DLL から取り出されるということです。
関数はステートメント シーケンスのパッケージであり、
メソッドは関数によって実装されるオブジェクト メンバーに対する操作であり、
インターフェイスはメソッドの抽象化と一般化であり、メソッドは特定のインターフェイスを実装します。
条件付きコンパイル
#ifdef、#else、#endif、#ifndef
通常の状況では、コンパイル時にソース プログラム内のすべての行をコンパイルする必要があります。ただし、特定の条件が満たされた場合にのみプログラムの特定の部分をコンパイルする、つまりプログラムの一部をコンパイルするための条件を指定したい場合があります。この条件が満たされない場合、コンテンツのこの部分はコンパイルされません。これが「条件付きコンパイル」です。
#ifdef および #endif を使用してプログラム関数モジュールを組み込み、この関数を特定のユーザーに提供します。ユーザーは、必要のない場合にそれらを簡単にブロックできます。
この要件は、条件付きコンパイル コマンドを使用せずに if ステートメントを直接使用することで実現できますが、ターゲット プログラムは長くなり(すべてのif ステートメントがコンパイルされるため)、実行時間も長くなります (プログラムの実行中に if ステートメントがテストされるため)。条件付きコンパイルを使用すると、コンパイルされるステートメントの数が減り、それによってターゲット プログラムの長さが短縮され、実行時間が短縮されます。
2.3 文法
浮動小数点数を保存する方法 (Nova)
1000.101
この種の 2 進数は標準化された形式で表現されます 1.000101 x 2^3
。その中で最も重要なものは 000101 と 3 で、この 2 進数のすべての情報を含めることができます。
000101
これは仮数と呼ばれ、小数点の後の数値です。3
これは指数 と呼ばれ、データ内の小数点の位置を指定します。
現在、ほとんどのコンピュータで使用されている浮動小数点数は、IEEE が制定した国際標準を採用するのが一般的であり、この標準の形式は次のとおりです。
これら 3 つの重要な部分の意味は次のとおりです。
- 符号ビット: 数値が正か負かを示します。0 は正の数を示し、1 は負の数を示します。
- 指数ビット: データの小数点の位置を指定します。指数には負の数または正の数を指定できます。指数ビットの長さが長いほど、値の表現範囲が広くなります。
- 仮数位置: 小数点の右側の数値 (小数部分) たとえば、2 進数1.0011
32
ビットで表される浮動 小数点数は、プログラミング言語の 変数 である単精度浮動小数点数と呼ばれ、 ビットで表される浮動小数点数は、 変数である倍精度浮動小数点数と呼ばれます。その構造は次のとおりです。次のように:float
64
double
見られます:
- double の仮数部は 52 ビット、float の仮数部は 23 ビットですが、どちらも固定の暗黙ビット (これについては後述) を持っているため、double の 2 進数の有効ビット数は 53、float の 2 進数の有効ビット数は 24 です。 , それらの精度は、それぞれ 10 進数 のビット
log10(2^53)
とほぼ同じです 。15.95
したがって、double の有効数字は ビットであり、float の有効数字は ビットです。これらの有効数字には、整数部分と小数部分が含まれます。log10(2^24)
7.22
15~16
7~8
- double の指数部は 11 ビットですが、float の指数部は 8 ビットです。つまり、double は float よりも広い範囲の値を表現できます。
sizeof と strlen の違い
-
sizeof は演算子、strlen はライブラリ関数です。
-
コンパイラはコンパイル時にsizeof の結果を計算しますが、 strlen 関数は実行時に計算する必要があります。また、sizeof はデータ型のメモリ サイズを計算し、strlen は文字列の実際の長さを計算します。
-
sizeof のパラメータはデータ型または変数にすることができますが、strlen はパラメータとして '\0' で終わる文字列のみを取ることができます。
C 関数をインポートするためのキーワードは何ですか?また、コンパイル時の C++ と C の違いは何ですか?
参考回答
-
キーワード: C++ では、C 関数をインポートするためのキーワードはexternで、式形式はextern "C"です。 extern "C" の主な機能は、C++ コードを正しく実装して他の C 言語コードを呼び出すことです。extern "C" を追加すると、コードのこの部分を C++ ではなくC 言語でコンパイルするようにコンパイラーに指示されます。
-
コンパイルの違い: C++ は関数のオーバーロードをサポートしているため、コンパイラーが関数をコンパイルすると、関数名だけでなく関数のパラメーターの型もコンパイルされたコードに追加されますが、C 言語は関数のオーバーロードをサポートしていません。 C 言語コードで関数をコンパイルすると、関数のパラメーターの型は含まれず、通常は関数名のみが含まれます。
回答分析
//extern example//この関数を C++ プログラムで宣言すると、C 言語に従ってコードのこの部分をコンパイルするようにコンパイラーに指示されます extern "C" int strcmp(const char *s1, const char *s2); //In C++ プログラム 関数 extern "C" を宣言します { #include <string.h>//string.h には呼び出される C 関数の宣言が含まれます} //2 つの異なる言語には、異なるコンパイル ルールがあります。 function fun 、おそらく C 言語でコンパイルすると _fun、C++ では __fun__ になります。
変数の宣言と定義の違い
変数の定義では変数のアドレスと記憶領域が割り当てられますが、変数の宣言ではアドレスは割り当てられません。変数は複数の場所で宣言できますが、定義できるのは 1 か所のみです。
注: extern を追加すると、変数の宣言が変更され、変数がファイルの外で定義されるか、ファイルの後半で定義されることが示されます。
通常の関数とメンバー関数の違いは何ですか?
通常の関数はクラスの外部で定義されますが、メンバー関数はクラスの内部で定義されます。
通常の関数はクラスのプライベート メンバーと保護されたメンバーに直接アクセスできませんが、メンバー関数はプライベート メンバーと保護されたメンバーを含むクラスのすべてのメンバーにアクセスできます。
通常の関数は直接呼び出すことができますが、メンバー関数はクラスのオブジェクトを通じて呼び出す必要があります。
メンバー関数には特別なポインター this があり、メンバー関数を呼び出すオブジェクトを指します。通常の関数にはこのポインタがありません。
このポインタは何のためにあるのでしょうか?
this ポインタは、現在のオブジェクトを指すアドレスです。これは主に、クラスのメンバー関数内の現在のオブジェクトのメンバー変数およびメンバー関数にアクセスするために使用されます。
オブジェクトが独自のメンバー関数を呼び出すと、コンパイラはthis を通じて暗黙的にオブジェクトのアドレスをメンバー関数に渡します。このポインターを介して、メンバー関数は現在のオブジェクトのメンバー変数およびメンバー関数にアクセスし、操作できます。
静的メンバー関数にはこのポインターがなく、特定のオブジェクトに属していないため、このポインターは非静的メンバー関数でのみ使用できます。
配列とポインタの違いについて話す
参考回答
-
コンセプト:
(1) 配列: 配列は、同じ種類のデータを複数格納するために使用される集合です。配列名は最初の要素のアドレスです。
(2) ポインタ: ポインタは変数に相当しますが、他の変数とは異なり、メモリ上に他の変数のアドレスを格納します。ポインタ名はメモリの最初のアドレスを指します。
-
違い:
(1)代入: 同じ型のポインタ変数同士は代入できますが、配列への代入はできず、1 つの要素のみ代入またはコピーが可能です。
(2)保管方法:
配列: 配列はメモリに継続的に保存され、連続したメモリ空間を開きます。配列は配列の底部に従ってアクセスされ、配列の記憶領域は静的領域またはスタック上にあります。
ポインター: ポインターは非常に柔軟で、あらゆる種類のデータを指すことができます。ポインタのタイプは、それが指すアドレス空間内のメモリを指定します。ポインタ自体は変数であり、格納するものも変数であるため、ポインタの格納領域を決定することはできません。
(3) sizeof を求める:
配列が占有する記憶領域のメモリサイズ: sizeof (配列名)/sizeof (データ型)
32 ビット プラットフォームでは、ポインターの種類に関係なく、sizeof (ポインター名) は 4 になります。64 ビット プラットフォームでは、ポインターの種類に関係なく、sizeof (ポインター名) は 8 になります。
(4)初期化:
// 配列 int a[5] = { 0 }; char b[] = "Hello"; // 文字列で初期化され、サイズは 6 char c[] = { 'H','e','l', ' l','o','\0' }; // 初期化 int* arr = new int[10]; // 1 次元配列を動的に作成 // Pointer // オブジェクトへのポインタ int* p = new int( 0); delete p; // 配列へのポインタ int* p1 = new int[10]; delete[] p1; // クラスへのポインタ: string* p2 = new string; delete p2; // ポインタへのポインタ Pointer (2 次)ポインタ) int** pp = &p; **pp = 10;
(5) ポインタ操作:
配列名に対するポインタ操作
int a[3][4]; int (*p)[4]; //このステートメントは、4 つの要素を含む 1 次元配列 p = a を指す配列ポインタを定義します; //2 つの要素の先頭を変更します次元配列 アドレスは p、つまり a[0] または &a[0][0] に割り当てられます p++; //ステートメントの実行後、つまり p=p+1; p は線 a[0 と交差します][] そして行を指します a [1][] //したがって、配列ポインタは 1 次元配列へのポインタとも呼ばれ、行ポインタとも呼ばれます。//配列内の行 i と列 j の要素にアクセスするには、いくつかの方法があります: //*(p[i]+j)、*(*(p+i)+j)、(*(p+i) ))[j]、p[i][j]。そのうちの優先順位: ()>[]>*。//これらの操作はすべて合法です。
ポインター変数のデータ操作:
char *str = "hello,douya!"; str[2] = 'a'; *(str+2) = 'b'; //どちらの操作も正当です。
関数ポインターとは何か、関数ポインターを定義する方法、およびその使用シナリオについて説明します。
参考回答
-
概念:関数ポインターは、関数を指すポインター変数です。各関数にはエントリ アドレスがあり、これは関数ポインタが指すアドレスです。
-
定義は次のとおりです。
int func(int a); int (*f)(int a); f = &func ;
-
関数ポインターのアプリケーション シナリオ:コールバック。他人が提供する API 関数 (Application Programming Interface) を呼び出すことを「コール」といい、他人のライブラリが自分の関数を呼び出すことを「コールバック」といいます。
回答分析
//ライブラリ関数 qsort ソート関数を例に挙げます。そのプロトタイプは次のとおりです: void qsort(void *base,//void* 型は元の配列を表します size_t nmemb, //2 番目の型は size_t 型で、データの数 size_t size, //3 番目は size_t 型で、単一のデータが占める領域を表します int(*compar)(const void *,const void *)//4 番目のパラメータは関数ポインタ ); // 4 番目のパラメーターは、要素の比較にどの関数を使用するかを qsort に指示します。つまり、qsort にサイズ比較のルールを指示する限り、あらゆるデータ型の配列をソートするのに役立ちます。ライブラリ関数 qsort は、コールバックのアプリケーションであるカスタム比較関数を呼び出します。 // 例 int num[100]; int cmp_int(const void* _a, const void* _b){//パラメータ形式固定 int* a = (int*)_a; //強制型変換 int* b = (int *)_b; return *a - *b; } qsort(num,100,sizeof(num[0]),cmp_int); // コールバック
関数ポインターと C++ のポインター関数の違い。
C++ では、関数ポインターと関数へのポインターは、意味と用途が異なる 2 つの異なる概念です。
1. 関数ポインタ:関数ポインタは関数へのポインタで、関数のアドレスを保存したり呼び出したりするために使用できます。関数ポインタの宣言形式は次のとおりです。return_type (*pointer_name)(parameter_types)
はreturn_type
関数の戻り値の型、parameter_types
は関数のパラメータ型のリストです。関数ポインターを介して、実行時に呼び出すさまざまな関数を動的に選択できます。
// 声明一个函数指针
int (*funcPtr)(int, int);
// 初始化函数指针
funcPtr = add; // add 是一个函数名
// 使用函数指针调用函数
int result = funcPtr(3, 5); // 调用 add 函数
2. 関数へのポインタ:ポインタ関数はポインタを返す関数であり、戻り値は特定のデータ型を指すポインタです。ポインタ関数の宣言形式は次のとおりです。return_type* function_name(parameters)
はreturn_type
ポインタが指すデータ型、parameters
は関数のパラメータ リストです。ポインタ関数は、関数の外部に割り当てられたメモリまたはその他のデータを指すことができるポインタを返します。
// 声明一个指针函数
int* createIntPointer();
// 定义指针函数
int* createIntPointer() {
int* ptr = new int(10);
return ptr;
}
// 使用指针函数
int* ptr = createIntPointer(); // 返回一个指向动态分配的整数的指针
違いを要約すると、次のようになります。
- 関数ポインタは、呼び出すことができる関数へのポインタです。ポインタ関数は、ポインタを返す関数であり、特定のデータ型へのポインタを返します。
- 関数ポインターを宣言するときに構文を使用し
(*ptr)
、return_type*
ポインター関数を宣言するときに構文を使用します。 - 関数ポインタは関数アドレスを格納し、ポインタ関数はポインタ値を返します。
- 関数ポインタはさまざまな関数を動的に呼び出すために使用され、ポインタ関数は外部使用のためにポインタ値を返します。
nullptr はメンバー関数を呼び出すことができますか? なぜ?
参考回答
できる。
理由:オブジェクトはコンパイル時に関数アドレスにバインドされるため、ポインターが空かどうかは関係ありません。
回答分析
//出实例 class Animal{ public: void sleep(){ cout << "animal sleep" << endl; } void Breath(){ cout << "動物の呼吸ははは" << endl; } }; クラスフィッシュ:パブリックアニマル{ パブリック: void Breath(){ cout << "フィッシュバブル" << endl; } }; int main(){ 動物 *pAn=nullptr; pAn->breathe(); // 输出:動物の呼吸ははは 魚 *pFish = nullptr; pFish->breathe(); // 输出:fish bubble return 0; }
理由:オブジェクトはコンパイル時に関数アドレスにバインドされるため、ポインターが空かどうかは関係ありません。pAn->breathe(); コンパイル時に、関数のアドレスはポインタ pAn にバインドされ、breath(*this) が呼び出されるとき、これは pAn と等しくなります。関数内でこれを逆参照する必要がないため、関数実行時にエラーは発生しませんが、これを使用すると this=nullptr となるためエラーが発生します。
演算子 i++ と ++i の違いについて話しましょう
参考回答
まず実装コードを見てみましょう。
#include <stdio.h> int main(){ int i = 2; int j = 2; j += i++; //最初に値を割り当ててから、 printf("i= %d, j= %d\n") を追加します,i , j); //i= 3, j= 4 i = 2; j = 2; j += ++i; //最初に加算してから代入 printf("i= %d, j= %d" ,i, j); //i= 3, j= 5 }
-
割り当ての順序は異なります: ++ i が最初に追加されてから割り当てられ、i ++ が最初に割り当てられてから追加され、++i と i++ は両方とも 2 つのステップで完了します。
-
効率が異なります。post++ は prefix よりも実行が遅くなります。
-
i++ は左辺値として使用できませんが、++i は次のことができます。
int i = 0; int* p1 = &(++i);//正しい // int* p2 = &(i++);//間違った ++i = 1;//正しい // i++ = 1;// エラー
-
どちらもアトミック操作ではありません。
((void ()( ) )0)( ) の意味
このコードは、関数ポインターを使用して関数を呼び出そうとしているように見えます。この式をステップごとに説明してみましょう。
-
((void ()( ) )0)
: これは関数ポインタへの呼び出しです。内側から外側まで説明しましょう。void ()( )
関数ポインターの型を表します。 は( )
関数パラメーター リスト (空のパラメーター) で、最後の( )
は関数の戻り値の型が空 (void
) であることを示します。((void ()( ) )0)
関数ポインター型全体が関数ポインターにキャストされ、 0 (null ポインター) に初期化されることを示します。
-
((void ()( ) )0)( )
: ここでは、((void ()( ) )0)
これは関数ポインターを表しており、その後に続くのは( )
実際の関数呼び出しです。これは、この関数ポインターが指す関数を呼び出すことを意味します。以前に関数ポインタを 0 (null ポインタ) に初期化したため、これは実際には null ポインタが指す関数を呼び出そうとしています。
ほとんどの場合、null ポインターを介して関数を呼び出そうとすると未定義の動作が発生するため、このようなコードは間違っています。null ポインタは有効な関数コードを指していないため、null ポインタを呼び出すとプログラムのクラッシュまたは例外が発生します。
C++ のいくつかの値渡しメソッドを簡単に説明します。それらの違いは何ですか?
参考回答
パラメーターを渡すには、値の受け渡し、参照の受け渡し、ポインターの受け渡しの 3 つの方法があります。
-
値の転送: 関数本体内で仮パラメータの値が変更された場合でも、実パラメータの値には影響しません。
-
参照渡し: 関数本体内で仮パラメータの値が変更されると、実際のパラメータの値に影響します。
-
ポインターの受け渡し: ポインターの指す位置が変わらない限り、関数本体の仮パラメーターの値が変更され、実際のパラメーターの値に影響します。
回答分析
値による受け渡しをオブジェクトに使用すると、オブジェクト全体がコピーされるため非効率的ですが、参照による受け渡しをオブジェクトに使用すると、コピー動作は発生せず、オブジェクトがバインドされるだけなので効率的です。ポインターの受け渡しには当てはまりますが、参照による受け渡しほど安全ではありません。
コード例
//名前空間 std を使用したコード例 #include <iostream> void testfunc(int a, int *b, int &c){//仮パラメータ a の値は変更されましたが、実際のパラメータの値には影響しませんでした。パラメータ i; ただし、仮パラメータ *b と c の値が変更され、実際のパラメータ *j と k の値に影響を与えます a += 1; (*b) += 1; c += 1; printf("a= %d, b= %d , c= %d\n",a,*b,c);//a= 2, b= 2, c= 2 } int main(){ int i = 1; int a = 1 ; int * j = &a; int k = 1; testfunc(i, j, k); printf("i= %d, j= %d, k= %d\n",i ,*j,k);//i= 1 , j= 2, k= 2 return 0; }
形式パラメータ int arr[] 和vector<int> vec、vector<int>& vec、vector<int>* vec
- Int arr[] は配列の最初のアドレスが関数に渡されるため、スワップなどの操作を実行する場合は、アドレス & arr[i] を取得する必要があります。
- function1(std::vector<std::vector > vec),传值
- function2(std::vector<std::vector>& vec)、参照渡し
- function3(std::vector<std::vector>* vec)、ポインタを渡す
const (アスタリスク) と (アスタリスク) const の違いを簡単に説明します。
参考回答
//const* は定数ポインタ、*const はポインタ定数 int const *a; //a ポインタが指すメモリ内の値は変更されません、つまり (*a) は変更されません int *const a; // a ポインタが指す値 メモリアドレスは変化しない、つまり a は変化しない
定数がアドレスを取得しますか?
メモリに格納されている変数または定数のみが、それらに関連付けられたメモリ番号 (アドレス)を持ちます。
1. コードには、リテラル定数と制限付き定数の 2 種類の定数があります。リテラル定数には名前がありません。たとえば、cout<<8、この 8 はリテラル定数です。これはメモリの一部を占有し、メモリ アドレスを持つデータですが、名前がないため、そのアドレスを取得することができません。アドレス指定記号は & ですが、その後にデータの名前を続ける必要があります。たとえば、&a。&8 を書き込んでも機能しません。たとえうまくいったとしても(達成するのが難しくても)意味はありません。名前のないデータ(または他の名前の助けを借りて見つけることができる)は、連絡先情報がないことを意味するため、文字通りの現在の場所を離れた後は、そのデータを合法的に使用できなくなることを意味します。したがって、コード内のデータには、名前があれば連絡先も存在します。または、名前で呼ばず、連絡先として扱うだけです。
2.const int b = 10; bのアドレスを取得できますか? できる。const 数量の定義は実際には変数です。const は変更できないことを制限するだけです。すべての変数は (プログラムの実行中に) アドレスを取得できます。
3. enum 型の列挙子はenum 型宣言の一部にすぎず、定義された変数ではないため、値を取ることはできません。
4.#define PI 3.14 はマクロとして出てきますが、これは前処理のものです。コンパイラはすぐに PI を消去し、すべてリテラルの 3.14 に置き換えます。前処理後のコンパイル段階はもう存在しないため、入手することは不可能です。. マクロのアドレス。
ポインタと参照の違い
同じ点:
-
参照は参照される変数のエイリアスであり、参照自体が占める空間には参照される変数のアドレスが格納され、独自のメモリ アドレスはありません。これはポインタ変数と同じメモリです。それはすべてアドレスによって行われます。
-
これらを仮パラメータとして使用すると、両方向に渡されるため、値のコピーが回避され、関数呼び出し時のデータ転送のオーバーヘッドが軽減されます。
違い:
-
通常のポインタは複数回割り当てることができます。つまり、それが指すオブジェクトは複数回変更できます。参照では、初期化中に参照先オブジェクトのみを指定でき、その後は変更できません。
-
ポインタは低レベルのメカニズムであり、参照は高レベルのメカニズムです。
参照を使用して実現できる関数はすべて、ポインタを使用しても実現できると断言できます。参照の本質は依然としてポインタ定数です
パラメーターを渡す場合は、ポインターよりも参照を使用する方が簡潔で安全です。ただし、多くの場合、ポインタを置き換えることはできません。
たとえば、指すオブジェクトを途中で変更したり、特定の意味を表現するために null ポインタを使用したりしますが、null 参照、関数ポインタ、関数参照などというものは存在しません。
ランダムは0か1か?
C言語(0,1) javaとpython[0, 1);
シリアル化とデシリアル化?
簡単に言えば、シリアル化は、オブジェクト インスタンスの状態を、維持または送信できる形式に変換するプロセスです。シリアル化の反対は逆シリアル化で、ストリームに基づいてオブジェクトを再構築します。これら 2 つのプロセスを組み合わせることで、データの保存と転送が容易になります。たとえば、オブジェクトをシリアル化してから、HTTP を使用して、インターネット経由でクライアントとサーバーの間でオブジェクトを転送できます。
シリアル化: オブジェクトをバイト ストリームに送信します。
逆シリアル化: バイト ストリームから元のオブジェクトを復元します。
シリアル化の目的: (1) 後で逆シリアル化して使用できるようにオブジェクトをハードディスクに保存する; (2) オブジェクトのバイト シーケンスをネットワーク上に送信する
利点: ネットワーク送信の利便性と柔軟性により、次のような大規模プロジェクトの時間を節約できます。
データ構造があり、そこに保存されているデータは、他の多くのデータを介して非常に複雑なアルゴリズムによって生成されています。データの量が非常に多く、アルゴリズムが複雑であるため、非常に長い時間がかかる場合があります (おそらく数時間程度)データ構造の生成に使用されるデータを生成するのに数時間 (数時間、場合によっては数日)、データ構造が生成され、他の計算に使用されます。その後、デバッグ段階で、プログラムを実行するたびに、非常に長い時間がかかります。データ構造を生成するだけで時間がかかり、そのコストは間違いなく非常に膨大です。データ構造を生成するアルゴリズムが変更されない、または頻繁に変更されないことが確実な場合は、シリアル化テクノロジを使用してデータ構造データを生成し、ディスクに保存できます。次回プログラムを実行するときは、読み取るだけで済みます。ディスクからのオブジェクト データです。はい、かかる時間はファイルの読み取り時間です。それがどれほど速く、開発時間を節約できるか想像できるでしょう。
大きな数値を乗算したり、並べ替えたり、結合したりするときに剰余を取る必要があるのはなぜですか?
-
(10 ^ 9 + 7) 1000000007 は素数 (素数) です。素数の余りを取ることで、結果の矛盾や重複を最大限に回避できます。
-
int32 ビットの最大値は 2147483647 であるため、1000000007 は int32 ビットには十分な大きさです。
-
int64 ビットの最大値は 2^63-1 です。最大値のモジュロ 1000000007 の結果を使用して平方を求めますが、int64 ではオーバーフローしません。
-
したがって、大きな数の乗算の問題では、(a*b)%c=((a%c)*(b%c))%c であるため、乗算するとき、両側は 1000000007 を法として、int64 に格納されません。オーバーフロー。
2.4 キーワード
volatile キーワードは何をするのでしょうか?
volatileキーワードは、この変数を最適化しないようにコンパイラーに指示します。つまり、レジスターからではなく、毎回絶対アドレスから値をフェッチする必要があります。
効果:
-
マルチスレッド アプリケーションの複数のタスクで共有される変数。マルチスレッドで変数を変更すると、変数がレジスタにロードされることがなくなり、各スレッドがメモリ内の一貫した変数を読み取ることができるようになります。
パラメータは const と volatile の両方にすることができますか?
はい、const と volatile を使用して変数を同時に変更できます。つまり、この変数はプログラム内では読み取り専用であり、変更できません。変更されるのはプログラムの外部の条件が変化した場合のみであり、コンパイラは最適化されません。この変数。この変数を使用するたびに、レジスタに行ってバックアップを読み取るのではなく、メモリに行ってこの変数の値を読み取るように注意してください。
volatile [ˈvɑːlətl] adj. 不安定; 揮発性; 変わりやすい; 不定; 永続的;
静的キーワードの機能について話しましょう
参考回答
-
グローバル静的変数: グローバル変数は、スコープを変更するためにstatic で変更されますが、有効期間は変更されません。通常のグローバル変数は他の .c ファイルから参照できますが、静的に変更すると、グローバル変数が定義されている .c ファイルからのみ参照できるようになり、グローバル変数のスコープが縮小されます。 機能:グローバル変数が他の .c ファイルによって参照されたくない場合は、静的に変更して、他のファイルが extern 経由でアクセスできないようにすることができます。これは主にデータ セキュリティのためです。概要: スコープを変更しても変更されません。ライフサイクル。
-
ローカル静的変数:ローカル変数は関数内で定義された変数です。関数が終了するとライフサイクルも終了し、最後の値は保持されません。static で変更すると、ローカル変数の有効期間はプログラムの終了時に終了し、最後の値が保持されます。
応用: 関数内で、いくつかの変数の最後の値を保持したい場合は、static を使用して変数を変更できます。例: 関数の実行回数をカウントしたい場合は、static で変更された int 変数を定義すると、実行されるたびに変数が ++ になります。
概要: 静的に変更されたローカル変数はライフサイクルを変更しますが、スコープは変更しません。有効期間を変更する理由は、通常のローカル変数がスタックに格納されるのに対し、static によって変更されたローカル変数は .bss セグメントまたは .data セグメントに格納されるためです。 -
初期化された静的変数はデータ セグメントにメモリを割り当て、初期化されていない静的変数は BSS セグメントにメモリを割り当てます。静的変数は、プログラムが終了するまで常に以前の値を維持します。初期値を指定しない静的なライフタイム変数は値 0 で初期化されますが、動的なライフタイム変数の場合、初期値を指定しないことは初期値が不確実であることを意味します。グローバル静的変数とローカル静的変数のスコープが異なるだけです。
-
静的関数を定義する: 関数の戻り値の型の前に static キーワードを追加すると、関数は静的関数として定義され、スコープが変更されます。静的関数は、このソース ファイル内でのみ使用できます。通常の関数は、ヘッダー ファイル宣言を通じて他のファイルから呼び出すことができます。一部の関数は外部に提供したくないため、このファイル内にのみ存在する必要があります。
-
C++ では、static キーワードを使用してクラス内の静的メンバー変数を定義できます。静的データ メンバーを使用すると、グローバル変数として格納できますが、クラス内に隠蔽されます。クラス内の静的データ メンバーには、作成されるクラスのオブジェクトの数に関係なく、別の記憶域があります。これらすべてのオブジェクトの静的データ メンバーは、この静的記憶領域を共有します。サブオブジェクトを含む、クラスのすべてのオブジェクトによって共有されます。クラスの外部で初期化する必要があり、コンストラクター内で初期化することはできません。
-
C++ では、static キーワードを使用して、クラスで静的メンバー関数を定義できます。静的メンバー変数と同様に、静的メンバー関数もクラスで定義できます。関数の前にキーワード static を追加するだけです。たとえば、静的メンバー関数もオブジェクトの一部ではなくクラスの一部です。これらすべてのオブジェクトの静的データ メンバーは、この静的記憶領域を共有します。この関数はすべてのオブジェクトで共有され、このポインターは含まれず、クラス内の非静的メンバーは使用できません。
回答分析
オブジェクトの非静的メンバー関数が呼び出されると、システムはオブジェクトの開始アドレスをメンバー関数のthis ポインターに割り当てます。静的メンバー関数はどの object にも属さないため、 C++ では静的メンバー関数がこのポインターを持たないことを規定しています(強調は追加されており、面接の質問でよくテストされます)。オブジェクトを指していないため、オブジェクトの非静的メンバーにアクセスできません。
静的変数を使用する場合
プログラミングでは、複数のオブジェクト間の通信を実現するため、または作成されたオブジェクトの数を記録するために、クラスの複数のインスタンス化されたオブジェクトによって変数が共有される場合、この場合には静的変数が使用されます。
静的を使用してグローバル変数と関数を変更します。上記のデータ セキュリティと誤参照の防止に加えて、もう 1 つの機能は重複名の問題を解決することです。グローバル変数と関数を static で変更した後、他のファイルに同じ名前のグローバル変数と関数を定義することもできます。一般に、関数とグローバル変数が外部に提供されない場合は、静的変更を使用するのが最善です。
静的に変更された関数を他のファイルから呼び出すことはできますか?
関数: 関数は static で変更され、スコープが変更されます。通常の関数はヘッダー ファイル宣言を通じて他のファイルから呼び出すことができますが、静的に変更した後は、このファイル内でのみ呼び出すことができます。これはデータ セキュリティのためです。概要: スコープを変更しても、そのライフサイクルは変わりません。
これは通常の関数とは異なりますが、他の方法でも呼び出すことができます。
-
間接呼び出し:外部から提供された関数をファイルに定義し、その関数が内部で静的修飾関数を呼び出すことで、静的修飾関数の間接呼び出しを実現します。
-
直接呼び出し:静的に変更された関数の関数ポインターを渡し、関数ポインターを通じて他のファイルを呼び出すことができます。
静的変数がいつ初期化されるかについて話してください。
参考回答
C言語のグローバル変数と静的変数の場合、初期化はコードが実行される前に発生し、コンパイル時の初期化に属します。
C ++標準では、グローバル オブジェクトまたは静的オブジェクトは、オブジェクトが初めて使用されるときのみ構築されると規定されています。
回答分析
-
スコープ: C++ のスコープは、グローバル、ローカル、クラス、ステートメント、名前空間、ファイル スコープの 6 種類に分類できます。
静的グローバル変数: グローバル スコープ + ファイル スコープなので、他のファイルでは使用できません。
静的ローカル変数: ローカル スコープ。プログラムの終了までに 1 回だけ初期化されます。
クラスの静的メンバー変数: クラスのスコープ。
-
場所: すべて静的ストレージ領域内。静的変数は静的記憶領域にあるため、次回関数を呼び出したときにも元の値を取得できます。
-
ライフサイクル: 静的グローバル変数と静的ローカル変数は静的記憶領域にあり、プログラムが終了するまでメモリは再利用されません。クラスの静的メンバー変数は静的記憶領域にあり、クラスのスコープを超えるとメモリがリサイクルされます。
静的ローカル変数、グローバル変数、ローカル変数の特徴、使用シナリオについて話しましょう
参考回答
-
まず、スコープについて考えてみましょう。C++ のスコープは、グローバル、ローカル、クラス、ステートメント、名前空間、ファイル スコープの 6 つのタイプに分類できます。
グローバル変数: グローバル スコープ。extern を介して他の未定義のソース ファイルで使用できます。
静的グローバル変数: グローバル スコープ + ファイル スコープなので、他のファイルでは使用できません。
ローカル変数: 関数パラメーター、関数内のローカル変数などのローカル スコープ。
静的ローカル変数: ローカル スコープ。プログラムの終了までに 1 回だけ初期化されます。
-
スペースを考慮してください。スタック上のローカル変数を除いて、他のすべては静的ストレージ領域にあります。静的変数は静的記憶領域にあるため、次回関数を呼び出したときにも元の値を取得できます。
-
ライフサイクル: ローカル変数はスタック上にあり、スコープ外に出るとメモリが再利用されます。グローバル変数、静的グローバル変数、および静的ローカル変数はすべて静的ストレージ領域にあり、最後までメモリは再利用されません。プログラムの。
-
利用シナリオ:それぞれの特徴からそれぞれの適用シナリオが見えてくるので、詳細は割愛します。
new と malloc の違いと、それぞれの基本的な実装原則について話しましょう。
参考回答
-
new は演算子、malloc は関数です。
-
new は呼び出されたときに最初にメモリを割り当て、コンストラクターを呼び出し、解放されるときにデストラクターを呼び出しますが、malloc にはコンストラクターとデストラクターがありません。
-
malloc は要求されたメモリのサイズを指定する必要があり、返されるポインタを強制する必要があります。new はメモリのサイズを指定せずにコンストラクタを呼び出し、戻りポインタを強制する必要はありません。
-
new はオーバーロードできますが、malloc はオーバーロードできません
-
new はメモリをより直接的かつ安全に割り当てます。
-
new でエラーが発生して例外がスローされ、malloc は null を返します。
回答分析
malloc の基礎となる実装:割り当てられたスペースが 128K 未満の場合は brk() 関数が呼び出され、割り当てられたスペースが 128K を超える場合は mmap() が呼び出されます。Malloc はメモリ プール管理方法を使用してメモリの断片化を軽減します。まず大きなメモリブロックをヒープ領域として申請し、次にヒープ領域を複数のメモリブロックに分割します。ユーザーがメモリを申請すると、ヒープ領域から適切な空きブロックが直接割り当てられます。すべての空きブロックをリストするために暗黙的なリンク リストが使用され、各空きブロックは未割り当ての連続メモリ アドレスを記録します。
新しい基礎となる実装: new キーワードは、コンストラクターを呼び出すときに実際に次の手順を実行します。
-
新しいオブジェクトを作成する
-
コンストラクターのスコープをこの新しいオブジェクトに割り当てます (つまり、 this はこの新しいオブジェクトを指します)
-
コンストラクターでコードを実行します (この新しいオブジェクトにプロパティを追加します)。
-
新しいオブジェクトを返す
constとdefineの違いについて話しましょう。
参考回答
const は定数の定義に使用され、define はマクロの定義に使用され、マクロは定数の定義にも使用できます。両方を定数定義で使用する場合、その違いは次のとおりです。
-
const はコンパイル段階で有効になり、define は前処理段階で有効になります。
-
const で定義された定数は C 言語のメモリに格納され、追加のメモリ領域が必要になります。define で定義された定数は実行時の直接オペランドであり、メモリには格納されません。
-
const で定義された定数は型指定されますが、define で定義された定数は型指定されません。したがって、define で定義された定数は型チェックに役立ちません。
-
ドメインは異なります。
#define
マクロはドメインによって制限されませんが、const
定数はドメイン内でのみ有効です。
インライン関数とマクロ関数の違いについて話しましょう
参考回答
違い:
-
マクロ定義は関数ではありませんが、関数のように動作します。プリプロセッサは、マクロ コードをコピーすることで関数呼び出しを置き換え、関数のプッシュおよびスタック解除のプロセスを排除し、効率を向上させます。インライン関数は本質的には関数であり、インライン関数は通常、関数本体のコードが比較的単純な場合に使用されます。関数には複雑なものを含めることはできません制御ステートメント、while、switch、および inline 関数自体は、それ自体を直接呼び出すことができません。
-
マクロ関数は、プリコンパイル中にすべてのマクロ名をマクロ本体に置き換えますが、これは単純な文字列置換です。インライン関数はコンパイル中にコードを挿入し、コンパイラはあらゆる場所でインラインを呼び出します。インライン関数の内容を関数内で直接展開するため、コストを節約できます。関数呼び出しの効率を向上させます。
-
マクロ定義には型チェックがなく、マクロ定義が正しいか間違っているかに関係なく直接置換されます。一方、インライン関数はコンパイル中に型チェックされ、インライン関数は戻り値、パラメータ リストなどの関数のプロパティを満たします。 。
回答分析
//マクロ定義例 #define MAX(a, b) ((a)>(b)?(a):(b)) MAX(a, "Hello"); //int と string の比較が間違っており、パラメータがありません型チェック // インライン関数の例 #include <stdio.h> inline int add(int a, int b) { return (a + b); } int main(void) { int a; a = add(1 , 2) ; printf("a+b=%d\n", a); return 0; } //上記の a = add(1, 2); は次のように展開されます: a = (a + b) ;
1. 使用時の注意事項:
-
マクロ定義関数には型チェックがなく、かっこが使用されます。マクロを定義するときは、マクロ パラメータを慎重に扱う必要があります。通常、マクロ パラメータはかっこで囲みます。そうしないと、あいまいさが生じやすくなります。
-
インライン関数は通常、比較的小規模で頻繁に呼び出される関数に使用され、関数呼び出しによって生じるオーバーヘッドを軽減できます。関数をインライン関数として指定するには、関数の戻り値の型の前にキーワード inline を追加するだけです。
-
他の関数とは異なり、インライン関数は単に宣言するのではなく、ヘッダー ファイルで定義することをお勧めします。コンパイラがインライン関数を処理するとき、呼び出しポイントで関数をインライン展開する必要があるため、関数の宣言だけを行う必要があります。十分ではありません。
2. インライン関数の使用条件:
-
インライン化は、コードの拡張(重複) を犠牲にして関数呼び出しのコストを節約するだけであり、それによって関数の実行効率が向上します。関数本体内のコードの実行時間が関数呼び出しのオーバーヘッドよりも長い場合、効率の向上は非常に小さくなります。一方、インライン関数呼び出しごとにコードをコピーする必要があるため、プログラムの合計コード サイズが増加し、より多くのメモリ領域を消費します。インライン化は、次の状況では使用しないでください。
-
(1) 関数本体のコードが比較的長い場合、インライン化を使用するとメモリ消費コストが高くなります。
-
(2) 関数本体でループが発生した場合、関数本体内のコードの実行に費やされる時間は、関数呼び出しのコストよりも大きくなります。
-
インライン展開はいつでも展開できるわけではなく、優れたコンパイラであれば、関数の定義に基づいて要件を満たさないインライン展開を自動的にキャンセルします。
インライン関数インライン
頻繁に使用される短い関数の場合は、inline
インライン関数を使用する必要があります。つまり、コンパイラは、inline
インライン関数内のコードを関数が呼び出される場所に置き換えます。
利点: (1)関数呼び出しの時間が節約され、プログラムの実行効率が向上します。(2) マクロ関数と比較して、コードがインライン関数用に展開されるときにコンパイラが構文セキュリティ チェックやデータ型変換を実行するため、より安全に使用できます。 . ;
欠点: (1) コードが拡張されるため、オーバーヘッドが増加します; (2) インライン関数内のコード ブロックの実行時間が呼び出し時間よりはるかに長い場合、効率の向上はそれほど大きくありません。
インライン宣言はあくまで提案であり、インライン化するかどうかはコンパイラが決めるので実際には制御できません。
インライン関数と関数の違い、およびインライン関数の役割について説明します。
参考回答
-
インライン関数には通常の関数よりも多くのキーワードがあります。
-
インライン関数は関数呼び出しのオーバーヘッドを回避しますが、通常の関数には呼び出しのオーバーヘッドがあります
-
通常の関数を呼び出す場合は、アドレス指定 (関数エントリ アドレス) が必要ですが、インライン関数の場合はアドレス指定する必要はありません。
-
インライン関数には特定の制限があります。インライン関数本体には単純なコードが必要であり、複雑な構造制御ステートメントを含めることはできません。通常の関数にはこの要件はありません。
インライン関数の役割: インライン関数が呼び出されると、呼び出し側の式がインライン関数の本体に置き換えられます。関数呼び出しのオーバーヘッドを回避します。
回答分析
インライン関数を使用する場合は、次の点に注意する必要があります。
-
インライン関数内では、Loop ステートメントと switch ステートメントは使用できません。インライン関数にこれらのステートメントがある場合、コンパイラはその関数を通常の関数として扱い、関数呼び出しコードを生成します。再帰関数はインライン関数として使用できません。インライン関数は、1 ~ 5 行のみの小さな関数にのみ適しています。多くのステートメントを含む大規模な関数の場合、関数の呼び出しと戻りのオーバーヘッドは比較的小さいため、インライン関数を使用する必要はありません。
-
インライン関数の定義は、インライン関数が最初に呼び出される前に指定する必要があります。
インライン関数の欠点は何ですか?
インライン関数の主な欠点は次のとおりです。
-
コードの肥大化: インライン関数を使用すると、呼び出されるすべての場所でコードの置換が発生し、コードの肥大化につながる可能性があります。インライン関数本体が非常に大きい場合、または頻繁に呼び出される場合、実行可能ファイルのサイズが大きくなり、キャッシュ ミスが発生し、パフォーマンスに影響を与える可能性があります。
-
コンパイル時間の増加: インライン関数では各呼び出しポイントでコードを置換する必要があり、コンパイル時間が増加します。特にインライン関数が広範囲に使用される場合、コンパイル時間が大幅に増加する可能性があります。
-
可読性の低下: インライン関数は関数本体を呼び出しサイトに埋め込むため、コードの可読性が低下する可能性があります。関数本体が複数の場所に散在しているため、コードの理解と保守が困難になる可能性があります。
typedefとdefineの違い
-
別の使用法: typedef は、プログラムの可読性を高めるためにデータ型の別名を定義するために使用されます。定義は主に定数を定義し、複雑で頻繁に使用されるマクロを記述するために使用されます。
-
実行時間は異なります。typedef はコンパイルプロセスの一部であり、型チェック機能を備えています。定義はマクロ定義であり、プリコンパイルされた部分であり、コンパイル前に発生し、型チェックを実行せずに文字列を単純に置き換えます。
-
スコープが異なります。typedef はスコープ付きです。define ステートメントの後の参照が正しい限り、define はスコープの制約を受けません。
注: typedef 定義は、文の最後にセミコロンが追加されるため、ステートメントになります。Defineはステートメントではないため、文の最後にセミコロンを追加してはなりません。
オーバーライドと最終
-
オーバーライド: 派生クラスで宣言された関数が、基本クラスの仮想関数と同じシグネチャを持つことを確認します。
-
Final: クラスのさらなる派生と仮想関数のさらなる書き換えを防ぎます。
例: override を追加すると、派生クラスの仮想関数が基本クラスをオーバーライドすることが明確に示されます。派生クラスと基本クラスの仮想関数の署名が一致しない場合、コンパイラはエラーを報告します。
class Base { public: virtual void Show(int x); // 仮想関数 }; class Derived : public Base { public: virtual void Show(int x) const override; // const プロパティは異なり、新しい仮想関数 } ; / /エラーが報告されるため、プログラム実行時のエラーを軽減するために、書き換えた仮想関数にオーバーライドを追加することをお勧めします。
例: クラスを継承したくない場合、または仮想関数をオーバーライドしたくない場合は、クラス名と仮想関数の後にfinalキーワードを追加し、継承される前にfinalキーワードを追加します。コンパイラはエラーを報告します。
class Base { public: virtual void Show(int x) Final; // 仮想関数 }; class Derived : public Base { public: virtual void Show(int x) override; // オーバーライド エラー }; // したがって、一度仮想関数関数はfinalと宣言されているため、派生クラスはそれをオーバーライドできません。
2.5 クラッシュ
ワイルド ポインターとは何なのか、どのように生成されるのか、そしてそれらを回避する方法について話しましょう。
参考回答
-
概念:ワイルド ポインターとは、ポインターが指す位置が認識できない(ランダム、不正確、明確な制限がない)ことを意味します。
-
原因: メモリが解放された後、ポインタが時間内にクリアされず (ワイルド ポインタ)、依然としてメモリを指している場合、不正アクセス エラーが発生する可能性があります。これらを避けるためには注意が必要です。
-
回避方法:
(1) NULLに初期化する
(2) メモリを申請した後、メモリが空であると判断されます。
(3) ポインタ解放後は NULL が設定されます。
(4) スマートポインタを利用する
回答分析
原因: メモリが解放された後、ポインタが時間内にクリアされず (ワイルド ポインタ)、依然としてメモリを指している場合、不正アクセス エラーが発生する可能性があります。これらを避けるためには注意が必要です。のように:
char *p = (char *)malloc(sizeof(char)*100); strcpy(p, "Douya"); free(p);//p が指すメモリは解放されますが、p が指すアドレスはまだ変更されません ... if (p != NULL){//エラー防止の役割を果たしません strcpy(p, "hello, Douya!");//Error }
回避方法:
(1) NULLに初期化する
(2) メモリを申請した後、メモリが空であると判断されます。
(3) ポインタ解放後は NULL が設定されます。
int *p = NULL; //NULL に初期化 p = (int *)malloc(sizeof(int)*n); //n 個の int メモリ空間のアプリケーション assert(p != NULL); //NULL 判定、エラー防止Design p = (int *) realloc(p, 25); //メモリを再割り当てし、p が指すメモリ ブロックが解放され、新しいメモリ アドレスが割り当てられます free(p); p = NULL; //ポストを解放し ます Empty int *p1 = NULL; //NULL に初期化 p1 = (int *)calloc(n, sizeof(int)); //n 個の int メモリ空間を適用し、同時に 0 に初期化する assert(p1 != NULL) ; / /Null検出、誤り防止設計 free(p1); p1 = NULL; //空きスペースを解放 int *p2 = NULL; //NULLに初期化 p2 = new int[n]; //nに適用 intメモリ空間 assert(p2 != NULL); //Null判定、エラー防止設計 delete []p2; p2 = nullptr; //解放後 Null
ハンギング ポインタとワイルド ポインタの違いは何ですか?
ダングリング ポインタ:ダングリング ポインタは、ポインタがまだ存在しているが、それが指すメモリが解放されているか無効であることを意味します。プログラム内でメモリ ブロックを解放しても、そのメモリ ブロックへのポインタがまだ存在する場合、そのポインタはダングリング ポインタになります。ダングリング ポインタを使用すると、すでに解放されているメモリ領域にアクセスしようとしているため、未定義の動作が発生する可能性があります。
int* danglingPtr() {
int x = 10;
int* ptr = &x; // ptr指向了一个局部变量
return ptr;
}
int main() {
int* p = danglingPtr();
// 此时p成为了悬挂指针,因为它指向了已经被释放的内存区域
// 在访问*p时会导致未定义行为
return 0;
}
ワイルド ポインタ:ワイルド ポインタは、初期化されていない、または解放されたポインタを指し、未知のメモリ アドレスを指します。ワイルド ポインタを使用すると、未知または無効なメモリ領域にアクセスすることになり、未定義の動作が発生する可能性があります。ワイルド ポインタは通常、ポインタが適切に初期化されていないか、メモリを解放した後にポインタが null または正当なメモリ アドレスに設定されていないことが原因で発生します。
ワイルド ポインタを避ける:
-
ポインタ変数は宣言時に初期化されません。解決策: 宣言時にポインタを初期化します。ポインタは、特定のアドレス値または NULL を指すことができます。
-
ポインタ p は解放または削除された後、NULL には設定されません。解決策: ポインタが指すメモリ空間が解放された後、ポインタは NULL を指す必要があります。
-
ポインター操作は変数の範囲を超えます。解決策: 変数のスコープが終了する前に、変数のアドレス空間を解放し、ポインタが NULL を指すようにします。
int* wildPtr() {
int* ptr; // 未初始化的指针
*ptr = 5; // 这里会导致未定义行为,因为ptr指向未知的内存地址
return ptr;
}
int main() {
int* p = wildPtr();
// p成为了野指针,因为ptr未初始化,指向未知的内存地址
return 0;
}
ポインタを使用する際の注意点を教えてください。
参考回答
-
ポインタを定義するときは、最初にポインタを NULL に初期化します。
-
malloc または new でメモリを申請した後は、ポインタ値が NULL かどうかをすぐに確認する必要があります。ポインタ値が NULL のメモリの使用を防止します。
-
配列と動的メモリを初期化することを忘れないでください。初期化されていないメモリが右辺値として使用されるのを防ぎます。
-
数値やポインターの範囲外の添え字は避けてください。特に「1 多い」または「1 少ない」演算には注意してください。
-
メモリ リークを防ぐために、動的メモリの適用と解放をペアにする必要があります
-
free または delete でメモリを解放した後、「ワイルド ポインタ」を防ぐために、すぐにポインタを NULL に設定します。
回答分析
(1) NULLに初期化する
(2) メモリを申請した後、メモリが空であると判断されます。
(3) ポインタ解放後は NULL が設定されます。
int *p = NULL; //NULL に初期化 p = (int *)malloc(sizeof(int)*n); //n 個の int メモリ空間のアプリケーション assert(p != NULL); //NULL 判定、エラー防止Design p = (int *) realloc(p, 25); //メモリを再割り当てし、p が指すメモリ ブロックが解放され、新しいメモリ アドレスが割り当てられます free(p); p = NULL; //ポストを解放し ます Empty int *p1 = NULL; //NULL に初期化 p1 = (int *)calloc(n, sizeof(int)); //n 個の int メモリ空間を適用し、同時に 0 に初期化する assert(p1 != NULL) ; / /Null検出、誤り防止設計 free(p1); p1 = NULL; //空きスペースを解放 int *p2 = NULL; //NULLに初期化 p2 = new int[n]; //nに適用 intメモリ空間 assert(p2 != NULL); //Null判定、エラー防止設計 delete []p2; p2 = nullptr; //解放後 Null