【C++】C言語の基礎知識まとめ(ポインタ、関数、メモリ、キーワード、前処理など)(秋の裏技)

記事ディレクトリ


序文

秋採用ノートのまとめでC言語の基礎をchatgptを使って書いたので、一部の解答には方法があるかもしれません(GPTの3.5バージョンにはいくつかの欠陥があります)、書き終えた後にほとんどの解答は修正されています、矛盾がある場合は、クラスメイトの皆さん

2023.8.7 初回更新

32 ビット システムで一般的に使用されるデータ型のバイト サイズについて説明します (例として stm32f103)。

1) 8 ビットのデータ型:

uint8_t (符号なし 8 ビット整数): 1 バイト

int8_t (符号付き 8 ビット整数): 1 バイト

文字:1バイト

2) 16 ビットのデータ型:

uint16_t (符号なし 16 ビット整数): 2 バイト

int16_t (符号付き 16 ビット整数): 2 バイト

uint_fast16_t: 2バイト

int_fast16_t: 2バイト

3) 32 ビットのデータ型:

uint32_t (符号なし 32 ビット整数): 4 バイト

int32_t (符号付き 32 ビット整数): 4 バイト

uint_fast32_t: 4バイト

int_fast32_t: 4バイト

浮動小数点数:4バイト

4) 64 ビット データ型 (通常、STM32F103 には適用されません):

uint64_t (符号なし 64 ビット整数): 8 バイト

int64_t (符号付き 64 ビット整数): 8 バイト

uint_fast64_t: 8バイト

int_fast64_t: 8バイト

倍精度:8バイト

C/C++ の一般的なライブラリについて話します。

stdio.h: printf や scanf などの標準入出力関数を提供します。

stdlib.h: メモリ割り当ておよび解放関数 malloc および free など、一般的に使用される関数をいくつか提供します。

unistd.h: プロセス制御、ファイル操作などを含む POSIX オペレーティング システム API へのアクセスを提供します。

string.h: 文字列コピー関数 strcpy や文字列比較関数 strcmp などの文字列操作関数を提供します。

arpa/inet.h: IP アドレス変換関数 inet_pton や inet_ntop など、ネットワーク プログラミングに関連するいくつかの関数とデータ構造を提供します。
(Windows プラットフォームでは、ネットワーク プログラミングに関連する機能は通常、winsock2.h ライブラリによって提供されます)

揮発性変数とは何ですか?

「揮発性変数」は、プログラムの実行中に変数が予期せず変化する可能性があることをコンパイラに伝えるためにプログラミングで使用される修飾子です。そのため、コンパイラは変数へのアクセスと操作の正確性を確保するために特定の最適化を実行すべきではありません。
C および C++ では、コンパイラは通常、コードの実行効率を向上させるために変数を最適化します。ただし、プログラムの実行中に外部要因によって変数の値が変更される場合があり、その場合、コンパイラの最適化によって問題が発生する可能性があります。例えば:

1) ハードウェア レジスタ アクセス: 変数が実際にハードウェア レジスタに関連付けられている場合、プログラムの実行中にレジスタの値が変化する可能性がありますが、コンパイラはこれを認識しない可能性があります。

2) マルチスレッド環境: マルチスレッド環境では、あるスレッドが別のスレッドで使用される変数を変更する可能性がありますが、コンパイラはこの可能性を検出できない場合があります。

3) 割り込み処理: 組み込みシステムでは、割り込みによって変数の値がいつでも変更される可能性がありますが、コンパイラーはこの状況を認識できない場合があります。

変数を volatile として宣言すると、コンパイラはこの変数の値が予告なく変更される可能性があることを認識するため、変数の値をメモリから読み取るのではなくレジスタにキャッシュするなど、特定の最適化を実行しなくなります。
C 言語では、volatile 変数は volatile キーワードを使用して宣言されます。次に例を示します。

volatile int sensorValue;

C++ では、 volatile キーワードも使用できますが、C++11 以降では、std::atomic クラスまたはその他のマルチスレッド プログラミング メカニズムを使用して、マルチスレッド環境で揮発性データを処理することをお勧めします。
volatile はコンパイラーに特定の最適化を実行しないように指示できますが、マルチスレッドまたは同時プログラミングの問題を完全に解決するわけではないことに注意してください。マルチスレッド環境では、volatile の使用に加えて、データの正確性と一貫性を確保するために適切な同期メカニズムを使用することも必要です。

コードの変換と構築は通常どのような手順を経ますか: (前処理、コンパイル、アセンブル、リンク)

C++ プログラムの開発中、コードの変換と構築は通常、次の手順を経ます。

1) 前処理 (Preprocessing): これは最初の段階で、プリプロセッサはソース コードの前処理命令 (# で始まる命令) を処理します。たとえば、#include ディレクティブはヘッダー ファイルの内容をソース コードに挿入します。マクロ展開はマクロをその定義内容に置き換えます。条件付きコンパイルは条件に従って特定のコードをコンパイルするかどうかを判断します。などです。前処理後、前処理されたソース コード ファイルが生成されます。

2) コンパイル: この段階では、コンパイラは前処理されたソース コードをアセンブリ言語に変換します。アセンブリ言語は、ターゲット マシン アーキテクチャに固有の中間表現です。コンパイラは構文エラー、型エラー、その他の静的エラーをチェックし、コードをアセンブリ命令に変換します。

3) アセンブリ: アセンブラは、アセンブリ コードをマシン コード命令に変換します。マシン コード命令は、コンピュータ ハードウェアによって直接実行できる命令です。生成されるのは、マシン コード命令やシンボル テーブルなどの情報を含むオブジェクト ファイル (通常は拡張子 .obj または .o が付きます) です。

4) リンク: マルチファイル プロジェクトでは、異なるソース ファイルが複数のターゲット ファイルを生成する場合があります。リンカーは、これらのオブジェクト ファイルと場合によってはライブラリ ファイルを結合し、シンボル参照 (関数、変数など) を解決し、実行可能ファイルを生成します。このプロセスには、アドレス割り当て、再配置、シンボル解決などのステップが含まれます。

実行可能ファイルの生成: 最終段階では、リンカーはオペレーティング システム上で実行し、プログラムの機能を実行できる実行可能ファイルを生成します。

C/C++でよく使われるキーワードを紹介します。

C言語でよく使われるキーワード:

Break: ループまたは switch ステートメントから抜け出します。

case: switch ステートメントでさまざまなケースをマークします。

char: 文字変数またはデータ型を宣言します。

const: 定数を宣言します。

continue: 現在のループ内の残りのステートメントをスキップし、次のループに進みます。

default: switch ステートメントでデフォルトのケースを定義します。

do: do-while ループを開始します。

double: 倍精度浮動小数点変数またはデータ型を宣言します。

else: 条件が満たされない場合に実行する分岐。

enum: 列挙型を宣言します。

extern: 外部変数を宣言します。

float: 浮動小数点変数またはデータ型を宣言します。

for: for ループを開始します。

goto: 指定されたラベルのコードの位置にジャンプします。

if: 条件判定文。

int: 整数変数またはデータ型を宣言します。

long: 長整数変数またはデータ型を宣言します。

register: 変数をレジスタに格納する要求 (必ずしも有効になるわけではありません)。

return: 関数から値を返します。

short: short 整数変数またはデータ型を宣言します。

signed: signed 型を宣言します。

sizeof: データ型または変数のサイズをバイト単位で返します。

static: 静的な変数または関数を宣言し、スコープを制限します。

struct: 構造体の型を宣言します。

switch: switch ステートメントを開始します。

typedef: 型の別名を定義します。

unsigned: unsigned 型を宣言します。

void: 型を宣言しません。通常は関数の戻り値の型に使用されます。

volatile: オプティマイザによって最適化される可能性のある変数に使用される、揮発性変数を宣言します。

while: while ループを開始します。


C++ 言語の一般的なキーワード:

C++ は C 言語のキーワードを継承し、オブジェクト指向プログラミングやその他の機能をサポートするためにいくつかの新しいキーワードを導入しています。

class: クラスを宣言します。

delete: 動的に割り当てられたオブジェクトを削除します。

明示的: 暗黙的な型変換を禁止するコンストラクターを宣言します。

friends: フレンド関数またはクラスを宣言します。

inline: インライン関数を宣言します。

名前空間: 名前空間を宣言します。

new: メモリを動的に割り当てます。

演算子: オーバーロードされた演算子関数を宣言します。

private: クラス内のプライベートメンバーのアクセス修飾子。

protected: クラス内の保護されたメンバーのアクセス修飾子。

public: クラス内の public メンバーのアクセス修飾子。

template: テンプレートを宣言します。

this: 現在のオブジェクトへのポインター。

throw: 例外をスローします。

try: 例外処理の開始ブロック。

catch: 例外をキャッチして処理します。

typeid: 式の型情報を返します。

virtual: 仮想関数または仮想継承を宣言します。

wchar_t: ワイド文字データ型。

constの使い方をいくつか紹介します

C および C++ では、const は定数を定義するために使用されるキーワードであり、変数、関数パラメーター、関数戻り値などに適用して、データが変更不可能であることを示します。

定数変数の宣言: const キーワードを使用して、宣言後に値を変更できない定数変数を宣言します。

const int num = 10;

定数ポインター: ポインターは const を使用して定数として宣言できます。これは、ポインターが指す値をポインターを通じて変更できないことを意味します。

const int *ptr; // 常量指针,指向的值不可修改
int const *ptr; // 同样是常量指针的声明方式

ポインタ定数: const を使用すると、ポインタ自体を定数として宣言できます。つまり、ポインタ自体は変更できませんが、ポインタが指す値はポインタを介して変更できます。

int value = 5;
int *const ptr = &value; // 指针常量,指针本身不可修改,但指向的值可修改

定数ポインター定数: ポインターもポインターが指す値も変更できません。

const int *const ptr = &value; // 常量指针常量,指针和指向的值都不可修改

関数パラメータの const: 関数パラメータを const として宣言することは、パラメータの値を関数内で変更できないことを意味します。

void printValue(const int num) {
    
    
    // num 只能被读取,不能被修改
}

関数の戻り値に const を使用する: 関数の戻り値を const として宣言することは、呼び出し元が戻り値を変更できないことを意味します。

const int getValue() {
    
    
    return 42;
}

const キーワードを使用すると、コンパイラがデータの不必要な変更を検出して防止できるため、プログラムの信頼性とセキュリティが向上します。const は、型変換などの手段を通じて const データを変更する可能性があるため、絶対的な保護ではないことに注意してください。

#define const typedef 3 つの違い

define、const、typedef は、C および C++ でエイリアスを定義、宣言、作成するために使用されるキーワードであり、それぞれに異なる機能と使用法があります。

1)define (マクロ定義):
#define は、マクロ定義を作成するための C および C++ の前処理ディレクティブです。マクロ定義を使用してコード内のテキストを置換し、コードの再利用と簡素化を実現できます。マクロ定義はメモリを割り当てず、コンパイル前に指定されたテキストをマクロの内容に置き換えるだけです。

#define PI 3.14159

この例では、すべての PI が 3.14159 に置き換えられます。

2) const (定数):
const は変更不可能な変数、つまり定数を宣言するために使用されるキーワードです。変数、ポインター、関数パラメーターなどを変更して、それらの値が変更されないようにするために使用できます。

const int MAX_VALUE = 100;

この例では、MAX_VALUE は値を変更できない定数として宣言されています。

3) typedef (型定義):
typedef は型エイリアスを作成するために使用されるキーワードです。これを使用して既存の型のエイリアスを定義すると、コードが読みやすく、保守しやすくなります。通常、構造体、ポインターなどの複合型を定義するために使用されます。

typedef int MyInt;

この例では、MyInt が int のエイリアスとして定義されており、後で int の代わりに MyInt を使用できます。


定義、const、typedef はさまざまな点で目的と特性が異なります。さまざまな点での違いは次のとおりです。

1) 行動範囲:

define は、コンパイル前に指定されたテキストをマクロの内容に置き換える前処理ディレクティブです。その動作範囲はグローバルであり、ファイル内でマクロが使用されているすべての場所に影響します。

const は、変数、関数パラメータなどをスコープとする定数を宣言するために使用されるキーワードです。プログラムの実行中に定数を変更することはできません。

typedef は型エイリアスの作成に使用されるキーワードで、ローカルまたはグローバル スコープで型エイリアスを定義できます。

2) メモリ割り当て:

定義にはメモリの割り当ては含まれず、コンパイル前にテキストの置換が行われるだけです。

const を使用して、メモリ内にストレージを割り当てる定数を宣言できます。

typedef にはメモリ割り当ては含まれず、既存の型のエイリアスを作成するだけです。

3) 型チェック:

define は型チェックを実行せず、単純なテキスト置換を行うだけです。

const は定数の宣言に使用できますが、型チェックには影響しません。

typedef は型エイリアスの作成に使用できますが、型チェックには影響しません。

4) コードの可読性:

定義を使用してマクロを作成できますが、マクロを展開するとコードが読みにくくなり、理解しにくくなる可能性があります。

const を使用して定数を宣言できるため、コードの可読性と保守性が向上します。

typedef を使用すると、コード内でよりわかりやすい型名を使用するのに役立つ型エイリアスを作成できます。

5) エラー処理:

これは、デバッグが難しい問題を引き起こす可能性のある単純なテキストの置換にすぎないため、define にはエラー処理のメカニズムがありません。

const で宣言された定数は、コンパイル時の型チェックおよびエラー処理メカニズムによって保護できます。

typedef により、コードの明瞭さが向上し、型エラーによって引き起こされる問題が軽減されます。
6) 開始時間

#define は前処理フェーズで動作し、残りの 2 つはコンパイルフェーズにあります。

リンクの静的リンクと動的リンクとは何ですか?

リンクとは、複数のオブジェクト ファイルを 1 つの実行可能ファイルまたは共有ライブラリに結合するプロセスです。リンク プロセスには、静的リンクと動的リンクという 2 つの一般的なリンク方法があります。

1) 静的リンク (静的リンク) :
静的リンクとは、すべてのオブジェクト ファイルとライブラリ ファイルのコードとデータを 1 つの実行可能ファイルに結合することです。静的リンクでは、リンカーは、プログラムで使用されるすべてのライブラリ関数のコードを、最終的に生成される実行可能ファイルにコピーします。これは、実行可能ファイル自体に必要なコードがすべて含まれているため、外部ライブラリに依存せずに独立して実行できることを意味します。静的リンクの利点はシンプルでシステム環境に依存しないことですが、欠点は実行ファイルが大きくなりディスク容量を多く消費することです。

2) 動的リンク (Dynamic Linking) :
動的リンクとは、プログラムが必要とするライブラリファイルの参照情報をコンパイル時やリンク時に実行ファイルに埋め込むことですが、実際のライブラリ関数のコードは実行ファイル上の共有ライブラリに残ります。ディスク ファイル ( .dll や .so など)。プログラムの実行中、オペレーティング システムはこれらの共有ライブラリをメモリにロードし、ライブラリ関数コードをプログラムのアドレス空間にマップします。動的リンクの利点は、実行可能ファイルのサイズを削減でき、複数のプログラムが同じライブラリ インスタンスを共有してメモリを節約できることです。同時に、ライブラリの更新では、再コンパイルせずに共有ライブラリ ファイルを置き換えるだけで済みます。プログラム全体。ただし、動的リンクには、システム内に対応するバージョンの共有ライブラリが存在する必要があります。存在しない場合、プログラムは実行できません。

つまり、静的リンクには実行可能ファイル内のすべてのコードとデータが含まれますが、動的リンクは実行時に必要なライブラリ ファイルのみをロードするため、プログラムがより柔軟になり、リソースが節約されます。静的リンクと動的リンクの選択は、プロジェクトのニーズと最適化戦略によって異なります。

一般的な前処理ディレクティブとは何ですか

前処理ディレクティブは、ソース コードをコンパイルする前にプリプロセッサによって処理される特別なコマンドです。これらは、コンパイル中のマクロの置換、条件付きコンパイル、ファイルのインクルードなどに使用されます。以下は、一般的な前処理ディレクティブの一部です。

#define: マクロを定義するために使用されます。マクロは、単純なテキスト置換またはパラメーターを含むマクロにすることができます。

#include: 他のファイル (通常はヘッダー ファイル) をインクルードするために使用され、他のファイルの内容を現在のファイルに挿入できます。

#ifdef および #ifndef: 条件付きコンパイルに使用され、マクロが定義されているかどうかに応じてコードの一部をコンパイルするかどうかを決定します。

#else および #elif: 条件付きコンパイルで使用され、条件が満たされない場合、または他の条件が満たされた場合にコンパイルされるコードを指定します。

#endif: 条件付きコンパイル ブロックを終了するために使用されます。

#undef: 定義されたマクロをキャンセルするために使用されます。

#pragma: コンパイラ固有のディレクティブを発行してコンパイラの動作を制御するために使用されます。

#error: 前処理中にコンパイル エラー メッセージを生成するために使用されます。

#warning: 前処理中にコンパイル警告メッセージを生成するために使用されます。

定義済み() 関数スタイルのマクロ: マクロが定義されているかどうかを確認するために使用されます。

前処理命令は基本的に命令チェックを行いません。

型チェックとは何ですか?

型チェックはプログラミング言語における重要な概念であり、変数、式、関数パラメータなどのデータ型がコンパイル時または実行時に期待どおりであることを検証することが含まれます型チェックは、コード内で発生する可能性のある型エラーを検出するのに役立ち、コードの堅牢性と信頼性が向上します。
プログラミング言語は多くの場合、静的型付け言語と動的型付け言語に分けられ、型チェックの動作が異なります。

静的型付け:
静的型付け言語では、コンパイル中に型チェックが行われますコードを記述するときは、変数のデータ型を明示的に宣言する必要があります。コンパイラは、コンパイル中に変数のデータ型と式が一致しているかどうかをチェックします。型の不一致エラーが見つかった場合、コンパイラーはコンパイル時にそれを報告し、実行時に発生する可能性のある型関連の問題を回避します。
たとえば、C++ では次のようになります。

int x = 5;
double y = 3.14;
int result = x + y; // 编译时会报类型不匹配的错误

動的型付け:
動的型付け言語では、通常、型チェックは実行時に行われます。変数のデータ型は、実行時に代入または式から推測できます。したがって、型エラーは通常、コードが実際に実行されるまで検出されません。
たとえば、Python では次のようになります。

x = 5
y = 3.14
result = x + y  # 在运行时才会发现类型错误

型チェックの利点は次のとおりです。

型安全性: データ型をチェックすることで、誤った型操作を防止し、潜在的なエラーを回避できます。

エラーの早期検出: 静的型チェックにより、コンパイル時に型エラーを検出し、実行時の未定義の動作を回避できます。

コードの可読性の向上: 明示的な型宣言によりコードが理解しやすくなり、誤解やエラーの可能性が減ります。

ただし、型チェックを厳密に行うと柔軟性がある程度失われる可能性があるため、プログラミング言語を選択して型チェックを処理するときは、プロジェクトのニーズや開発チームの好みに応じてトレードオフを考慮する必要があります。

前処理ディレクティブの定義の何が問題なのでしょうか?

#define は、C および C++ でマクロを定義するために使用される前処理ディレクティブで、コード内のテキスト置換を可能にします。マクロ定義は状況によっては便利ですが、いくつかの欠点もあります。

型チェックなし:マクロは、型チェック機構のない単純なテキスト置換です。マクロのパラメーターが正しく使用されない場合、型エラーが発生する可能性がありますが、これらのエラーはコンパイル時または実行時にのみ検出できます。


たとえば、
マクロのパラメータが間違って使用されると、型エラーが発生する可能性がありますが、これらのエラーはコンパイル時または実行時にのみ発見されます。これを説明する例を次に示します。
2 つの数値の 2 乗を計算する単純なマクロ定義があるとします。

#define SQUARE(x) (x) * (x)

然后在代码中使用这个宏进行计算:
int main() {
    
    
    int result = SQUARE(5 + 3); // 预期计算结果是 64 (8 * 8)
    return 0;
}

この例では、コードはマクロ呼び出しで式 5 + 3 を使用します。マクロの展開規則に従って、マクロ定義は単純にテキスト置換、つまり (5 + 3) * (5 + 3) を実行します。ただし、これは期待された結果ではありません。
このマクロには型チェックがなく、単純なテキスト置換が行われるだけであるため、上記のコードは実際には次のように展開されます。

int result = (5 + 3) * (5 + 3);

(5 + 3) * (5 + 3) は実際には 5 + 3 * 5 + 3 であり、期待される二乗演算ではないため、これにより不正確な結果が生じます。このバグは実行時にのみ検出され、予期しない計算結果が発生しました。
対照的に、インライン関数を使用して同じ機能を実現すると、次の型エラーを回避できます。

inline int square(int x) {
    
    
    return x * x;
}

int main() {
    
    
    int result = square(5 + 3); // 正确计算结果为 64
    return 0;
}

この例では、インライン関数 square を使用して二乗計算を実装しているため、コンパイル時に型チェックを実行して、前述の型エラーを回避できます。これは、タイプチェックメカニズムがないというマクロの潜在的な問題を浮き彫りにしており、それが見つけにくいバグにつながる可能性があります。


可読性が低い: マクロ定義が複雑であると、コードが読みにくく、理解しにくくなる可能性があります。マクロの置換後、コードが冗長になり、コードのロジックに従うことが困難になる場合があります。

デバッグが難しい: マクロ展開されたコードは、デバッグ中に混乱を招く可能性があります。デバッガーにはマクロ定義自体ではなく、展開されたコードが表示されるため、問題の特定が複雑になる可能性があります。

名前空間の汚染: マクロはグローバル名前空間で展開されるため、マクロ名が他の変数、関数などと衝突し、不要な問題が発生する可能性があります。

デバッグに不便: マクロは前処理の段階で展開されるため、エラー メッセージや警告で元のマクロ定義の場所を追跡することが難しく、デバッグが困難になる可能性があります。

保守が難しい: マクロ定義の変更には、マクロが使用される複数の場所が関係する場合があるため、マクロを変更する必要がある場合は、複数のコードを変更する必要があり、保守が複雑になります。

考えられる副作用: 一部のマクロは展開時に引数を複数回評価する場合があり、これにより予期しない副作用が発生する可能性があります。

制限事項: マクロ定義は単純なテキスト置換のみを実行でき、関数呼び出しやスコープなどの一部の複雑なプログラミング概念を実装できません。

上記の問題を回避するために、最新の C++ では通常、マクロ定義の代わりにインライン関数、定数、列挙などを使用することをお勧めします。これらの方がタイプセーフで読みやすく、マクロによって引き起こされる多くの問題を回避できるためです。もちろん、状況によってはマクロが有効なツールであることもありますが、マクロを使用する場合は潜在的な欠点を慎重に考慮する必要があります。

#面接での質問を定義します (標準的なマクロ関数をいくつか書きましょう)

面接では、前処理命令とマクロの理解をテストするためにいくつかの標準マクロ関数を作成することを求める質問に遭遇することがよくあります。一般的な標準マクロ関数の例をいくつか示します。

最大および最小マクロ関数:

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

2 つの変数の値を交換します。

#define SWAP(a, b) do {
      
       \
    typeof(a) temp = (a); \
    (a) = (b); \
    (b) = temp; \
} while (0)

ここでは do { … } while (0) 構文を使用して、マクロが予期しない問題なく使用されることを保証します。

配列の長さを計算します。

#define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof((arr)[0]))

定数を定義します。

#define PI 3.14159265359

マクロの文字列化 (マクロ引数を文字列に変換):

#define STRINGIFY(x) #x

たとえば、STRINGIFY(test) は「test」に置き換えられます。

連結マクロ (2 つのマクロ引数を 1 つの識別子に連結):

#define CONCAT(a, b) a ## b

たとえば、CONCAT(prefix, _value) は prefix_value に置き換えられます。

ヘッダー ファイル内の一般的な #ifndef #endif について説明します。

C および C++ では、ヘッダー ファイル (header file) は宣言と定義を格納するために使用されるファイルで、通常は関数、変数、クラスなどの宣言と定数の定義を格納するために使用されます。ヘッダー ファイルが繰り返しインクルードされることを避け、重複定義やコンパイル エラーを防ぐために、ヘッダー ファイルが 1 回だけインクルードされるようにするために、前処理ディレクティブがよく使用されます。その中で、一般的な前処理ディレクティブには #ifndef、#define、#endif が含まれており、これらはいわゆる「ヘッダー ガード」を作成するために一緒に使用されます。
ヘッダー ファイル保護の役割は、同じヘッダー ファイルが繰り返しインクルードされることを防ぎ、定義エラーの繰り返しを回避することです。その使用法と説明は次のとおりです。

#ifndef: このディレクティブは「if not Definition」の略で、識別子がすでに定義されているかどうかを確認するために使用されます。識別子が定義されていない場合、プリプロセッサは次のコードを実行します。それ以外の場合はスキップされます。

#define: このディレクティブは識別子を定義するために使用されます。通常、識別子が存在しない場合に識別子が定義されていることを確認するために #ifndef の後に使用されます。この識別子は通常、ヘッダー ファイルに関連付けられた一意の識別子であり、ヘッダー ファイルの保護に使用されます。

#endif: このディレクティブは、#ifndef に対応する条件付きコンパイル ブロックを終了するために使用されます。このディレクティブの後のコードは正常に処理されます。

たとえば、内容が次のようなヘッダー ファイル myheader.h があるとします。

#ifndef MYHEADER_H
#define MYHEADER_H

// 此处放置头文件的声明和定义

#endif

ここで、MYHEADER_H は、このヘッダー ファイルを一意に識別するために使用されるカスタム識別子です。myheader.h が最初にインクルードされるときは、MYHEADER_H がまだ定義されていないため、#ifndef が通過し、#define MYHEADER_H がこの識別子を定義します。myheader.h が別の場所に再度インクルードされた場合、MYHEADER_H は既に定義されているため、#ifndef は渡されず、ヘッダー ファイルの内容は再度インクルードされません。
そうすることで、ヘッダー ファイルを繰り返しインクルードすることによって発生する重複定義エラーを防止し、各ヘッダー ファイルが 1 回だけインクルードされるようにすることができますこれはコード構造と依存関係の管理に役立つため、大規模なプロジェクトでは特に重要です。

前処理ディレクティブ #pragma を導入する例を示します。

#pragma は、コンパイラの動作を制御するために特定の指令やコマンドをコンパイラに発行するために使用される前処理指令です。これは、コンパイラ固有の機能や最適化を実装したり、特定の状況で設定したりするためによく使用されます。ただし、#pragma ディレクティブの動作はコンパイラによって異なる場合があるため、クロスプラットフォームまたはクロスコンパイラのプロジェクトでは注意して使用してください。
一般的な使用シナリオは、ネットワーク関連のコードをコンパイルするとき、特に Winsock2 ライブラリ (Windows ソケット ライブラリ) を使用するときです。Winsock2 は、Windows オペレーティング システム上のネットワーク プログラミング用のライブラリであり、特定のセットアップと初期化が必要です。
Winsock2 を使用する場合に #pragma ディレクティブを使用する方法を示す例を次に示します。

#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib") // 告诉编译器链接 ws2_32.lib 库

int main() {
    
    
    WSADATA wsaData;
    
    // 初始化 Winsock2 库
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
    
    
        printf("Failed to initialize Winsock2.\n");
        return 1;
    }

    // 在这里进行网络编程操作

    // 清理并释放 Winsock2 库资源
    WSACleanup();

    return 0;
}

この例では、#pragma comment(lib, “ws2_32.lib”) を使用して、Winsock2 を使用するために必要な ws2_32.lib ライブラリをリンクするようにコンパイラーに指示します。このディレクティブは、リンク オプションを手動で追加することなく、コンパイル中に必要なライブラリを結果の実行可能ファイルに自動的にリンクします。
#pragma ディレクティブの動作はコンパイラによって異なる場合があることに注意してください。移植性が重要な場合は、特定の #pragma ディレクティブに過度に依存しないようにするのが最善です。コードを記述するときは、他の開発者がコードの意味を理解できるように、#pragma ディレクティブの目的と目的を説明する適切なコメントを提供することをお勧めします。
この命令をここに記述せず、ビジュアル標準出力のリンカーに直接リンクすることもできません。

前処理ディレクティブ #error の例を示します。

#error は、コンパイル中にエラー メッセージを生成し、コンパイルを停止するための C および C++ プリプロセッサのディレクティブです。条件付きコンパイルの場合に、特定の条件が満たされたときにエラー メッセージを出力するためによく使用されます。
#error ディレクティブを使用した例を次に示します。

#include <stdio.h>

#define DEBUG_MODE 0

#if DEBUG_MODE
    // 执行一些调试操作
#else
    #error "DEBUG_MODE is not defined! Please define it to enable debugging."
#endif

int main() {
    
    
    // 其他代码
    return 0;
}

この例では、DEBUG_MODE というマクロを定義し、その値に基づいて条件付きでコンパイルします。DEBUG_MODE の値がゼロ以外 (つまり true) の場合、一部のデバッグ操作が実行されます。DEBUG_MODE が未定義であるか、値が 0 の場合、#error ディレクティブを使用してエラー メッセージが生成され、コンパイルが停止します。
上記のコードをコンパイルするときに DEBUG_MODE が定義されていない場合、コンパイラは次のエラー メッセージを生成します:
エラー: 「DEBUG_MODE が定義されていません! デバッグを有効にするために定義してください。」

こうすることで、開発者はコンパイル時に明確なエラー メッセージを受け取り、デバッグ モードを有効にするために DEBUG_MODE マクロを定義する必要があることを思い出させます。
#error ディレクティブは前処理段階でエラー メッセージを生成するだけであり、コンパイルによって生成されたオブジェクト コードには影響を与えないことに注意してください。したがって、前処理中に状態をチェックし、必要に応じて役立つエラーのヒントを提供するためによく使用されます。他に何かお手伝いできることはありますか?

静的変数と静的関数について話す

静的変数と静的関数は、C++ プログラムにおける 2 つの特殊なタイプです。それらの有効範囲は現在のファイル内、つまり同じソース ファイル内です。

1) 静的変数 (静的変数):
静的変数は、関数内または static キーワードを使用してグローバル スコープ内で宣言された変数です。静的変数はプログラムの存続期間全体にわたって存在しますが、そのスコープは、それが宣言されている関数 (関数内で宣言されている場合) または現在のソース ファイル内のグローバル スコープ (グローバル スコープで宣言されている場合) に制限されます。

関数内で宣言された静的変数は、関数が呼び出されたときに初期化されますが、関数に最初に入ったときに 1 回だけ初期化されます。各関数呼び出しの後、変数の値は最後の関数呼び出しの終了時の値を維持します。これは、状態の保存が必要な場合に役立ちます。
グローバル スコープで宣言された静的変数は、プログラムの実行時に初期化され、現在のソース ファイル内でのみ使用できます。これは、他のソース ファイルがその静的変数に直接アクセスできないことを意味します。

void foo() {
    
    
    static int counter = 0; // 静态局部变量
    counter++;
}

static int globalStaticVar = 5; // 静态全局变量,只在当前源文件内可见

2) 静的関数 (Static Functions):
静的関数は、関数宣言の前に static キーワードが追加された関数です。静的関数は静的変数に似ており、現在のソース ファイルでのみ表示されます。静的関数のスコープは現在のソース ファイルに制限されており、他のソース ファイルから直接呼び出すことはできません。

静的関数を使用する主な利点は、関数のスコープを現在のソース ファイルに制限し、他のファイル内の関数との名前の競合を回避し、コードのモジュール性とカプセル化を改善できることです。

static void staticFunction() {
    
    
    // 这是一个静态函数,只在当前源文件内可见
}

つまり、静的変数と静的関数は両方ともスコープが制限されており、現在のファイル内でのみ有効です。これらの機能は、コードを整理し、範囲をある程度制御するのに役立ちます。


変数と関数を静的として宣言し、その範囲を現在のファイル (同じソース ファイル内) に制限すると、いくつかの利点と用途がもたらされます。

1) モジュール性とカプセル化: 単一のソース ファイル内の変数と関数の範囲を制限することにより、より優れたモジュール性とカプセル化を実現できます。各ソース ファイルは独立したモジュールと見なすことができ、必要なインターフェイスのみが他のモジュールに公開されるため、異なるモジュール間の直接の依存関係が減り、コードの保守性とスケーラビリティが向上します。

2) 名前の競合を避ける: 静的変数と静的関数のスコープは現在のファイルに制限されます。つまり、名前が競合することなく、異なるファイルで同じ変数名または関数名を使用できます。各ファイル内の命名は比較的独立しているため、グローバル名前空間での潜在的な競合が軽減されます。

3) 情報の隠蔽: 静的変数と静的関数は現在のファイルでのみ表示され、他のファイルから直接アクセスされることはありません。このメカニズムにより、実装の詳細が隠蔽され、外部コードが内部状態を直接操作するのを防ぐことができるため、コードのセキュリティと安定性が向上します。

4) コードの最適化: コンパイラは、これらの変数や関数が他のファイルから参照されないことを認識しているため、最適化時にローカル スコープ内のコードをより簡単に分析および最適化でき、それによってコードの実行効率が向上します。

5) アクセス権の制御: 静的変数と静的関数は、プライベート クラスまたは名前空間で宣言することにより、外部アクセス制御を実現できます。これにより、オブジェクト指向プログラミングにおけるカプセル化と情報の隠蔽が容易になります。

静的変数と静的関数は現在のファイルでは有効ですが、複数のソース ファイル間の通信には適していないことに注意してください。異なるソース ファイル間で変数または関数を共有する必要がある場合は、 extern キーワードを使用して外部変数または関数を宣言する必要があります。静的変数と静的関数は主に、コードを編成し、単一のソース ファイル内のスコープを管理するために使用されます。

const は静的変数と静的関数を組み合わせます

const を静的変数および静的関数と組み合わせて使用​​すると、それらのプロパティと動作に対する制約を表現できます。C や C++ では、静的変数と静的関数は文脈によって意味が異なりますが、const を使ってそれらを組み合わせる方法を以下に説明します。

1) 静的変数 + 定数:

void func() {
    
    
    static const int value = 10;
    // value 是一个静态常量变量,在多次调用 func() 时值不会改变
}

ここで、 value は静的定数変数です。つまり、 func() への最初の呼び出し時にのみ初期化され、その後の呼び出しでは定数のままになります。const は、関数の実行中にその値が変更されないことを保証します。宣言の範囲内でのみ表示されます

2) 静的関数 + const:

static int add(int a, int b) {
    
    
    return a + b;
}

static int multiply(int a, int b) {
    
    
    return a * b;
}

static const int (*operation)(int, int) = add;
// operation 是一个指向静态函数的指针,指向 add 函数

この例では、operation は静的関数へのポインターであり、それを関数へのポインターとして宣言することで、実行時にどの関数を呼び出すかを選択できます。ここでの const は、関数の内容を操作ポインタを通じて変更できないことを意味します。
ここでの const キーワードの役割は、関数ポインタを介した関数コードの変更を制限することであり、呼び出し関数自体を制限することではありません。

extern キーワードについて話します

extern は、外部変数および関数を宣言するために使用される C++ プログラミング言語のキーワードです。これは、特定の変数または関数が別のファイルで定義されており、現在のファイルでは単なる宣言であり、実際の定義は別の場所にあることをコンパイラに伝えます。extern キーワードはリンク段階で機能し、コンパイラとリンカーがさまざまなファイルで定義および参照されている識別子を正しく処理して、プログラムが正しく実行およびリンクできるようにします。
主な目的は次の 2 つです。

1) 外部変数の宣言:
別のソース ファイルで定義されたグローバル変数を 1 つのソース ファイルで参照する必要がある場合、 extern キーワードを使用して現在のファイルで宣言し、その変数が別の場所で定義されていることをコンパイラに認識させることができます。こうすることで、コンパイラは変数が未定義であるというエラーを報告しなくなります。

// File1.cpp
int globalVar = 42;

// File2.cpp
extern int globalVar; // 声明外部变量
int main() {
    
    
    // 在这里可以使用 globalVar
    return 0;
}

2) 外部関数の宣言:
別のソース ファイルで定義された関数を 1 つのソース ファイルで呼び出す必要がある場合、 extern キーワードを使用して現在のファイルで関数を宣言できます。これにより、コンパイラは関数が別の場所で定義されていることを認識します。

// MathFunctions.cpp
int add(int a, int b) {
    
    
    return a + b;
}

// Main.cpp
extern int add(int a, int b); // 声明外部函数
int main() {
    
    
    int result = add(3, 5); // 调用另一个文件中的函数
    return 0;
}

つまり、 extern キーワードは、ファイル内の別の場所で定義された変数または関数を宣言するために使用されます。これにより、異なるソース ファイルが連携して動作し、定義の繰り返しや宣言の繰り返しの問題を回避できます。これは、C++ プログラム内の異なるソース ファイル間のリンクとして機能します。

ポインタのサイズはどれくらいですか?

最新のコンピュータ アーキテクチャでは、ポインタのサイズ (つまり、ポインタが占めるバイト数) は通常、コンピュータのアーキテクチャとオペレーティング システムのビット数によって異なります。
一般的な 32 ビット オペレーティング システムでは、ポインタ サイズは通常4 バイト (32 ビット)です。これは、ポインタが 32 ビットのメモリ アドレスを格納できることを意味し、メモリ内のデータにアクセスするために使用されます。
64 ビット オペレーティング システムでは、ポインターのサイズは通常8 バイト (64 ビット) です。このようなポインタは 64 ビットのメモリ アドレスを格納できるため、コンピュータはより広い範囲のメモリをアドレス指定できるようになります。

ポインター p++、その値はどのように変化しますか

int 型の配列があるとします。ポインタ p で配列の最初の要素を指し、p++ 演算を実行してアドレス値の変化を観察します。
配列の開始アドレスが 0x1000 で、int 型が 4 バイトを占めるとします。


初期状態:
配列開始アドレス: 0x1000
p アドレス: 0x1000
*p 値: 42 (ここでは配列の最初の要素の値が 42 であると仮定します)


p++ 演算実行後:
配列開始アドレス: 0x1000
p アドレス: 0x1004 (4 バイト追加、つまり int 型のサイズ)
*p 値: 57 (ここでは、配列の 2 番目の要素の値が57)


ポインター間の加算と減算を導入する

2 つのポインター ptr1 と ptr2 があると仮定すると、次の操作を実行できます。

int arr[] = {
    
    1, 2, 3, 4, 5};
int *ptr1 = &arr[1]; // ptr1指向数组的第二个元素(值为2)
int *ptr2 = &arr[4]; // ptr2指向数组的最后一个元素(值为5)

ptrdiff_t diff = ptr2 - ptr1; // 计算ptr2和ptr1之间的偏移量
printf("Offset between ptr2 and ptr1: %td\n", diff);

上記の例では、ptrdiff_t はポインター間のオフセットを保持するために使用される整数型であり、負のオフセットを確実に表現できるように符号付き整数型にすることもできます。
ポインタ間の加算および減算演算のための 2 つのポインタは、同じ配列 (または配列の最後の要素の次の位置) を指している必要があることに注意してください。そうでない場合、結果は未定義になります。この操作は、配列内の要素間の距離を計算したり、動的に割り当てられたメモリ ブロック内の要素のオフセットを計算したりするためによく使用されます。
C および C++ では、ポインターと別のポインターには定義された意味がないため、ポインター間の加算は許可されません。ポインタの追加は整数値でのみ実行でき、別のポインタでは実行できません。

ポインタ定数と定数ポインタの違いについて説明します。

C/C++ プログラミングでは、ポインター定数と定数ポインターは 2 つの異なる概念です。これらはそれぞれ、ポインター自体の不変性とポインターが指すものの不変性に関係します。

1) 定数へのポインタ:
ポインタ定数は、その値 (つまり、ポインタが指すメモリ アドレス) が不変であるポインタです。これは、ポインターが初期化されると、そのポインターは常に同じアドレスを指し、指す値はこのポインターを通じて変更できないことを意味します。

const int *ptr;  // ptr 是一个指向常量的指针

この例では、 ptr は整数定数へのポインタです。これは、ptr が指す整数の値は変更できませんが、ptr が指す位置は変更できることを意味します。

2) 定数ポインタ (定数ポインタ):
定数ポインタは、その値 (つまり、格納されたメモリ アドレス) が不変であるポインタです。これは、ポインタが初期化されると、ポインタは常に同じアドレスを指し、このポインタを介してポインタが指す場所を変更する方法がないことを意味します。

int x = 10;

int *const ptr = &x;  // ptr 是一个常量指针

この例では、 ptr は整数 x への定数ポインタです。これは、ptr が指す位置は変更できませんが、x の値は変更できることを意味します。
要約すると、ポインター定数はポインターが指す値の不変性に焦点を当てますが、定数ポインターはポインター自体の不変性に焦点を当てます。これら 2 つの概念は組み合わせて使用​​できます。たとえば、 const int *const ptr は、ポインター自体やポインターが指す値を変更できないポインターを表します。

ワイルド ポインターと null ポインターについて話します。それらはどのようにして起こるのか

ダングリング ポインターと Null ポインターは、異なる状態のポインターの 2 つの概念です。

1) ワイルド ポインタ (ダングリング ポインタ):
ワイルド ポインタは、まだ存在しているが、それが指すメモリ アドレスが無効であるか解放されているポインタを指します。これはいくつかの状況で発生する可能性があります。

ポインタが指すメモリは解放されますが、ポインタ自体は NULL に設定されません。

ポインターが指すローカル変数はスコープの終了後に破棄されましたが、ポインターは時間内に NULL 化または再割り当てされませんでした。

ワイルド ポインターを使用すると、プログラムのクラッシュや誤ったデータ アクセスなど、予期しない結果が発生する可能性があります。ワイルド ポインタの問題を回避するには、メモリを解放した後、ポインタを NULL に設定することをお勧めします。

2) ヌル ポインタ (ヌル ポインタ):
ヌル ポインタは、明らかにヌル アドレスを指すポインタ変数です。つまり、関連付けられた有効なメモリ アドレスがありません。C/C++ では、通常、NULL ポインターを表すためにマクロ定義 NULL またはキーワード nullptr が使用されます。Null ポインターは、特に次のようないくつかの状況で役立ちます。

ワイルド ポインターの問題を防ぐためにポインターを初期化します。

ポインタが初期化されているかどうかを確認し、初期化されていない状態での使用を避けてください。

C では、NULL ポインターを表すために NULL を使用します。

int *ptr = NULL;  // ptr 是一个空指针

C++11 以降の標準では、nullptr を使用して null ポインターを表します。

int *ptr = nullptr;  // ptr 是一个空指针

要約:

浮遊ポインタは、予期しない動作を引き起こす可能性がある無効なメモリへのポインタです。

null ポインターは、null アドレスを明示的に指すポインターであり、初期化されていないポインターの問題を回避するために使用できます。


1) ワイルド ポインターの発生:

ポインタは解放されたメモリを指します。メモリの一部を解放したが、メモリへのポインタを NULL に設定するのを忘れた場合、ポインタはワイルド ポインタになります。

スコープ外になったローカル変数へのポインタ: ポインタがスコープ外になったローカル変数を指している場合、そのポインタにアクセスしようとすると、ローカル変数のメモリが失われているため、そのポインタはワイルド ポインタになります。有効。

ポインターが一時変数を参照する: ポインターが一時変数 (関数によって返される一時オブジェクトなど) を参照する場合、一時変数がスコープ外になると、ポインターはワイルド ポインターになります。

不適切なポインター操作: ポインター操作を実行するときに、ポインターの値が正しく更新されないと、ポインターがワイルド ポインターになる可能性があります。

2) NULL ポインタの発生:

未初期化ポインタ: ポインタが初期化されていない場合、その値はランダムであり、任意のメモリ アドレスを指す可能性があり、これはヌル ポインタです。

NULL ポインタに明示的に設定する: ポインタが有効なメモリを指さないようにするために、ポインタを明示的に NULL または nullptr に設定することがあります。

関数ポインターの導入

関数ポインタは、C および C++ の特別なタイプのポインタで、関数のアドレスを保存および操作できるようにします関数ポインターを使用すると、関数をパラメーターとして他の関数に渡したり、実行時に呼び出す関数を選択したり、コールバック メカニズムを実装したりできます。以下に、関数ポインターに関する基本的な概念と使用法をいくつか示します。

関数ポインタの宣言と定義:
関数ポインタの宣言形式は関数プロトタイプと似ていますが、関数名がポインタ変数名に置き換えられ、関数のパラメータリストと戻り値の型を指定する必要があります。
returnType (*pointerName)(parameterType1、parameterType2、...);

たとえば、整数を返す関数へのポインタを宣言するには、次のようにします。

int (*funcPtr)(int, int);

関数ポインターの初期化:
関数ポインターは、特定の関数を指すように初期化できます。これには、関数のシグネチャが関数ポインターの宣言された型と一致する必要があることに注意する必要があります。

int add(int a, int b) {
    
    
    return a + b;
}

int (*funcPtr)(int, int) = add;  // 初始化函数指针指向 add 函数

関数ポインターを使用した関数の呼び出し:
関数自体を呼び出すのと同じように、関数ポインターを介して、指定された関数を呼び出すことができます。関数を呼び出しているかのようにポインター名を使用し、必要なパラメーターを渡すだけです。

int result = funcPtr(3, 5);  // 调用 add 函数,返回 8

関数パラメータとしての関数ポインタ:
関数ポインタは、関数をパラメータとして他の関数に渡すためによく使用されます。これは、コールバック メカニズムを実装するときに非常に便利です。

void performOperation(int (*operation)(int, int), int a, int b) {
    
    
    int result = operation(a, b);
    printf("Result: %d\n", result);
}

performOperation(add, 3, 5);  // 调用 performOperation,传递 add 函数

関数ポインタは、柔軟性の高いコードの作成、プラグイン システムの実装、関数の動的選択などを行う場合に非常に役立ちます。これにより、実行時に必要に応じてさまざまな関数実装を選択できるようになり、コードのスケーラビリティと適応性が向上します。

セカンダリ ポインターとアプリケーション シナリオの紹介

ダブル ポインターは、ポインターへのポインターを指し、ポインターへのポインターとも呼ばれます。これは、特に動的メモリ割り当てやマルチレベル ポインタが関係する場合に、複雑なデータ構造と操作を処理するために一部のプログラミング言語で使用されます。
簡単な例を通してセカンダリ ポインターの概念を理解しましょう。整数変数を指す整数ポインター p があり、ポインター p を指す 2 次ポインター pp があるとします。これはセカンダリ ポインタの例です。

int x = 42;
int *p = &x;
int **pp = &p;

この例では:

x は、値 42 を格納する整変数です。

p は、変数 x のアドレスを指す整数へのポインタです。

pp は、二次ポインタであるポインタ p へのポインタです。ポインタ p のアドレスを指します。

2 次ポインターを介してポインター p の値にアクセスして変更し、次に変数 x の値にアクセスして変更することができます。例は次のとおりです。

printf("x = %d\n", **pp);  // 输出 x 的值,即 42

**pp = 57;  // 修改 x 的值为 57

printf("x = %d\n", *p);   // 输出修改后的 x 的值,即 57

実際のプログラミングでは、セカンダリ ポインタは次のような状況でよく使用されます。

リンクされたリストの先頭ポインタ、ツリーのノードなどのマルチレベル データ構造。
動的なメモリの割り当てと解放。たとえば、関数内でメモリを割り当て、割り当てられたメモリをセカンダリ ポインタを介して呼び出し元に渡します。
関数内のポインター変数の値を変更して、変更されたポインター値が関数の外に保持されるようにします。

文字列と文字配列の関係

文字列 (String) と文字配列 (Character Array) は、C と C++ では密接に関連した概念ですが、いくつかの違いがあります。

文字列: 文字列は、ヌル文字 '\0' (ASCII コード 0) で終わる一連の文字です。C および C++ の文字列は、最後の文字が文字列の終わりをマークするために使用される null 文字であることを除いて、実際には文字配列です。文字列は C 言語のネイティブ データ型としては存在しませんが、文字配列によって表されます。

文字配列: 文字配列は、メモリ内の連続した場所に格納される、連続した文字要素のセットです。文字配列を使用すると、最後の文字が文字列の終わりを示す NULL 文字である文字列を格納できます。

以下は、文字列と文字配列の関係を示す簡単な例です。

#include <stdio.h>

int main() {
    
    
    // 字符数组的初始化
    char charArray[] = {
    
    'H', 'e', 'l', 'l', 'o', '\0'};
    
    // 字符串的初始化
    char str[] = "Hello";
    
    printf("charArray: %s\n", charArray);
    printf("str: %s\n", str);

    return 0;
}

この例では、charArray と str はどちらも同じ文字列「Hello」を格納する文字配列ですが、使用方法が少し異なります。str が文字列リテラルで初期化される場合、コンパイラは最後に null 文字を自動的に追加しますが、charArray は文字列の終わりを示すために null 文字を手動で追加する必要があります。
文字列は null 文字で終了するため、文字列を操作する場合は null 文字の存在に注意してください。C および C++ には、strlen、strcpy、strcmp など、文字列を操作するための標準ライブラリ関数が多数あります。これらの関数を使用して文字配列を処理し、文字列に対する操作を実装できます。
文字列は本質的に文字配列です。文字列を扱うとき、実際には文字の配列を操作します。ただし、文字列の終わりを認識できるように配列が null で終了している点が異なります。

C/C++ でパラメータを渡す方法は何通りありますか?

パラメーターを渡すには 2 つの方法があります:
1) 値による受け渡し
2) アドレスによる受け渡し: 参照による受け渡し (参照による) とポインターによる受け渡し (ポインターによる)、配列の名前の受け渡しなど。
長所と短所: データ構造からの参照青島大学
ここに画像の説明を挿入

関数のパラメータとして配列を記述する方法は何通りありますか?

ほとんどの場合、配列名は配列の最初の要素へのポインターとして扱われます。関数パラメータとして配列を渡す場合、それを記述する方法がいくつかあります。

1) 配列のポインタを渡す:
関数パラメータでは、配列名をポインタとして渡すことができ、関数は配列の最初の要素へのポインタを受け取ります。これは最も一般的な方法ですが、関数は配列のサイズを知ることができないため、追加のパラメーターとして配列のサイズを渡す必要があります。


void function(int arr[], int size) {
    
    
    // 使用 arr 来访问数组元素
}

int main() {
    
    
    int myArray[5] = {
    
    1, 2, 3, 4, 5};
    function(myArray, 5);
    return 0;
}

2) ポインターを使用して配列を表す:
配列をポインターの形式で使用して渡すことができるため、サイズ情報を渡す必要がありません。この場合、関数は、C 文字列で使用される NULL ターミネータなど、配列の終わりを認識する必要があります。

void function(int *arr) {
    
    
    // 使用 arr 来访问数组元素,直到遇到某个终止条件
}

int main() {
    
    
    int myArray[5] = {
    
    1, 2, 3, 4, 5};
    function(myArray);
    return 0;
}

配列を参照渡し (&) で直接渡すと、配列のサイズ情報が失われる可能性があり、関数内で配列の要素にアクセスするのが不便になります。
ただし、LeetCode などのプログラミング コンテストや面接では、ベクトルは関数のパラメーターとして参照によって渡されます。

void modifyVector(std::vector<int>& vec) {
    
    
    for (int i = 0; i < vec.size(); ++i) {
    
    
        vec[i] *= 2; // 修改向量元素的值
    }
}

do while(0) の利点は何ですか?

do while(0) は一般的なプログラミング手法であり、その主な利点は、コード構造を明確で読みやすい状態に保ちながら、1 つのステートメントで複数の操作を実行できるようにするコード ブロックを作成することです。
do while(0) の利点をいくつか示します。

複数の操作を 1 つのステートメントで実行できます。 do while(0) は、コード ブロック内の複数のステートメントを組み合わせることができるため、追加の関数や条件ステートメントを使用せずに、複数の操作を 1 か所で実行できます。

コード構造を明確で読みやすいものに保つ: do while(0) を使用すると、コード ブロックを作成するために中括弧を使用することがなくなり、より簡潔で明確なコード構造になります。これにより、コードの可読性が向上し、バグやデバッグの困難さが軽減されます。

マクロ定義の便利な使い方: do while(0) は通常、マクロ定義と一緒に使用され、構文エラーを引き起こすことなく複雑なマクロを簡単に定義できます。そうしないと、マクロ展開時に問題が発生する可能性があります。do while(0) を使用してマクロ定義の正確さを確認します。マクロ定義では複数のステートメントを使用できます。

例えば:

#include <stdio.h>

#define PRINT_NUM(x) printf("The number is: %d\n", x) \
                     printf("This is a complex macro.\n")

int main() {
    
    
    int num = 10;
    PRINT_NUM(num);
    
    return 0;
}

この例では、2 つの printf ステートメントを含む PRINT_NUM というマクロを定義します。ただし、このコードをコンパイルしようとすると、次のエラーが発生します: エラー:
'printf' の前に ';' が必要です
printf("これは複雑なマクロです。\n")

これは、マクロ定義がコード ブロック内の複数のステートメントを結合していないため、マクロを展開するとセミコロンが欠落することになります。do while(0) を使用すると、この問題を解決し、マクロ定義の正確さを保証できます。

#include <stdio.h>

#define PRINT_NUM(x) do {
      
       \
                        printf("The number is: %d\n", x); \
                        printf("This is a complex macro.\n"); \
                    } while(0)

int main() {
    
    
    int num = 10;
    PRINT_NUM(num);
    
    return 0;
}

swap(a,b) と swap(&a,&b) の違い

swap(a, b) と swap(&a, &b) の違いは、引数の受け渡し方法です。

swap(a, b): このメソッドは変数 a と b の値をパラメータとして渡します。関数内では、パラメーターのコピーが使用されます。つまり、関数は a と b のコピーを作成し、そのコピーに対して操作します。このように、関数内のスワップ操作は、元の変数 a と b に影響を与えません。

swap(&a, &b): このメソッドは変数 a と b のアドレスをパラメータとして渡します。関数内では、ポインターを使用して受信アドレスを操作し、それによって元の変数 a および b の値を直接変更します。このように、関数内のスワップ操作は元の変数 a と b に影響を与えます

要約すると、swap(a, b) は値の転送であり、関数の内部操作はパラメータのコピーです。一方、swap(&a, &b) はアドレス転送であり、関数はパラメータのアドレスを直接操作します。元の変数。したがって、swap(&a, &b) は元の変数を直接変更できますが、swap(a, b) は元の変数の値を変更しません。

i++ と ++i の違い

i++ と ++i はどちらも i の値を 1 増やしますが、主な違いは、「副作用」がいつ発生するか、そして何を返すかです。
i++ は後置インクリメント演算子です。まず i の現在の値を返し、次に i の値を 1 ずつ増分します。たとえば、i の初期値が 5 の場合、式 j = i++ は j の値を 5 に設定し、i の値を 1 ずつ増分します。

int i = 5;
int j = i++;  // j becomes 5, i becomes 6

++i は前置インクリメント演算子です。まず i の値を 1 ずつ増分し、次に i の新しい値を返します。たとえば、i の初期値が 5 の場合、式 j = ++i は i の値を 1 つ増やし、j の値を 6 に設定します。

**int i = 5;
int j = ++i;  // i and j both become 6**

つまり、i の値をインクリメントするだけで、インクリメント操作の前後の値を気にしない場合、i++ と ++i は同じ効果があります。ただし、インクリメント演算の実行時間に依存する式でこれらを使用する場合は、この違いに注意する必要があります。
また、i++ は元の i 値を格納する一時変数を作成する必要があるため、場合によっては ++i の方が i++ より効率的である可能性があります。ただし、最新のコンパイラーは通常、このケースを最適化するため、実際にはこの違いは目立たない可能性があります。

if、if、if と if、else if、else の違い

if if if と if else if else の間には、次のようなロジックの明確な違いがあります。

if if if:
複数の連続した独立した if 文であり、それぞれの if が独立して条件判定と実行を行います。これは、前の if が実行されたかどうかに関係なく、次の if が試行されることを意味します各 if ステートメントは独立した条件分岐です。

例えば:

int x = 10;

if (x > 5)
    cout << "x is greater than 5" << endl;

if (x > 7)
    cout << "x is greater than 7" << endl;

if (x > 9)
    cout << "x is greater than 9" << endl;

x の値が 10 の場合、上記のコードは次のように出力します。
x は 5 より大きい
x は 7 より大きい
x は 9 より大きい

if else if else:
これは、複数の連続する条件文で構成される連鎖条件構造です。各条件は順番にチェックされ、条件を満たす最初の分岐のみが実行され、他の分岐は無視されます。この構造により、複数の条件の中から満たされる分岐を選択することができます。

例えば:

int x = 10;

if (x > 9)
    cout << "x is greater than 9" << endl;
else if (x > 7)
    cout << "x is greater than 7" << endl;
else if (x > 5)
    cout << "x is greater than 5" << endl;

如果 x 的值是 10,以上代码将输出:
x is greater than 9

これは、最初の条件 x > 9 のみが true であるため、最初の分岐のみが実行され、後続の else if 分岐は考慮されないためです。
要約すると、if if が独立した複数の条件分岐であるのに対し、if else if else が連鎖条件構造の場合、最初に満たされた条件分岐のみが実行されます。特定の論理ニーズに基づいて、使用する構造を選択できます。

インライン関数とマクロ関数を紹介し、それらの違いについて説明します

インライン関数:

インライン関数は、コードの実行効率を最適化するためにプログラミングで使用される手法です。関数呼び出しのコードを呼び出し位置に直接埋め込むことで、関数呼び出しのオーバーヘッドを回避します。コードの肥大化により読みやすさやメンテナンスの問題が発生する可能性があるため、アドレス指定なしのインライン関数の直接展開は、関数本体が短い状況に通常適しています。 . セックスの問題。
C++ では、inline キーワードを使用してインライン関数を宣言できます。例えば:

inline int add(int a, int b) {
    
    
    return a + b;
}

この例では、add 関数はインライン関数として宣言されています。コンパイラが add 関数を呼び出すと、関数本体のコードが呼び出し位置に直接挿入されます。これにより、関数呼び出しのオーバーヘッドが削減され、実行効率が向上します。
マクロ機能:

マクロ関数は、前処理フェーズ中に展開される置換テキストの一種です。マクロを使用すると、コード内でいくつかの単純なコード フラグメントを定義し、コードがコンパイルされる前にそれらを対応するテキストに置き換えることができます。マクロ関数はコード展開中に型チェックされないため、潜在的なエラーを避けるために特別な注意が必要です。
C/C++ では、#define を使用してマクロ関数を定義できます。例えば:

#define ADD(a, b) (a + b)

この例では、ADD はコード展開時に (a + b) を実引数の加算に置き換えるマクロ関数として定義されています。マクロ関数は型チェックを行わずにテキスト置換を実行するだけであることに注意してください。
違いの概要:

1) インライン関数はコンパイル時にコンパイラによって展開されますが、マクロ関数は前処理段階で展開されます。

2) インライン関数については型チェックが実行され、マクロ関数については型チェックが実行されません。

3) インライン関数はデバッグ可能ですが、マクロ関数は展開するとデバッグが困難です。

4) インライン関数はコードの肥大化を引き起こす可能性があり、マクロ関数もテキストの置換によってコードの肥大化を引き起こす可能性があります。

5) インライン関数は短い関数本体に適しており、マクロ関数は単純なコードの断片に適しています。

インライン関数は本質的に関数です マクロ関数は本質的にマクロ定義です

C言語とC++でのIOストリームの機能を紹介します。

C 言語および C++ では、プログラムから入力を取得し、ユーザーまたは他のデバイスに出力を表示するための I/O (入出力) 操作が非常に重要です。C および C++ では、I/O 操作は通常、関数を使用して実装されます。以下に、C 言語および C++ でよく使用される入出力関数をいくつか紹介します。
C 言語の I/O 関数:

printf(): 出力データを標準出力 (通常は端末ウィンドウ) にフォーマットするために使用されます。

scanf(): 標準入力 (通常はキーボード) からフォーマットされたデータを読み取るために使用されます。

getchar() および putchar(): それぞれ、標準入力から文字を読み取り、標準出力に文字を出力するために使用されます。

gets() および put(): それぞれ、標準入力から文字列を 1 行読み取り、標準出力に文字列を出力するために使用されます。gets() にはセキュリティ上の問題があるため、使用しないでください。

C++ の I/O ストリーム関数 (iostream ライブラリを使用):

C++ では、入出力操作に iostream ライブラリを使用する、より柔軟なオブジェクト指向の I/O メカニズムが導入されています。よく使用される関数をいくつか示します。

cin: 標準入力からデータを読み取るために使用されます。

cout: データを標準出力に出力するために使用されます。

cerr: バッファリングせずに、標準エラー出力 (通常はターミナル ウィンドウも) にエラー メッセージを出力するために使用されます。

clog: cerr に似ていますが、出力をバッファリングします。

getline(): 入力ストリームから文字列の行を読み取ります。

setw(): 出力フィールドの幅を設定するために使用されます。

setprecision(): 浮動小数点出力の精度を設定するために使用されます。

ifstream: ファイルの読み取りに使用されます。

ofstream: ファイルの書き込みに使用されます。

fstream: ファイルの読み取りと書き込みを同時に行うために使用されます。

これらの関数とストリームは、より高いレベルの抽象化を提供し、より簡単にフォーマットされた出力と入力を可能にし、オブジェクト指向プログラミングに役立ちます。
これは概要にすぎず、実際に使用する際の詳細については関連ドキュメントを参照してください。

typeid 関数についての話

typeid は、式の型情報を取得するために使用される C++ の演算子です。通常、実行時に型チェックやその他の操作のためにオブジェクトまたは式の型を取得するために使用されます。typeid は主に、実行時の型の識別と型の比較に使用されます。
簡単な使用例を次に示します。

#include <iostream>
#include <typeinfo>

class Base {
    
    
    virtual void foo() {
    
    } // 为了演示多态
};

class Derived : public Base {
    
    
};

int main() {
    
    
    int i = 42;
    double d = 3.14;
    std::string str = "Hello";
    Base base;
    Derived derived;

    std::cout << "i的类型: " << typeid(i).name() << std::endl;
    std::cout << "d的类型: " << typeid(d).name() << std::endl;
    std::cout << "str的类型: " << typeid(str).name() << std::endl;
    std::cout << "base的类型: " << typeid(base).name() << std::endl;
    std::cout << "derived的类型: " << typeid(derived).name() << std::endl;

    return 0;
}

この例では、typeid を使用して、さまざまな変数やオブジェクトの型情報を取得しました。typeid によって返される型情報は、コンパイラの実装に依存するため、人間が判読できる名前ではない場合があることに注意してください。コンパイラやプラットフォームが異なると、型名の表現が異なる場合があります。
typeid は、型情報を取得するだけでなく、2 つの型が同じかどうかを比較するためにも使用できます。例えば:

if (typeid(obj1) == typeid(obj2)) {
    
    
    // obj1和obj2的类型相同
}

typeid は基本クラスと派生クラスの間の型の比較に特に役立つため、通常はポリモーフィックな状況でのみ使用されることに注意してください。

C++ の 4 つのメモリ領域について話します。

C++ でメモリの 4 つの領域について話すとき、通常、ヒープ、スタック、グローバル領域 (データ領域または BSS セグメントとも呼ばれます)、およびコード領域 (テキスト領域またはコード セグメントとも呼ばれます) を指します。これらの領域は、プログラムの実行とメモリ管理においてさまざまな役割を果たします。具体的な説明は次のとおりです。

1) ヒープ:

機能:実行時にオブジェクトストレージを管理するためにメモリを動的に割り当てるために使用されます。

メモリ割り当て: new や mallocなどのランタイム メモリ割り当て関数を通じてヒープにメモリを割り当てます。

メモリの解放: 手動で delete と free を呼び出して、割り当てられたヒープ メモリを解放します。解放しないとメモリ リークが発生します。

特徴: ヒープメモリの割り当てと解放は比較的遅い場合があり、サイズやライフサイクルの柔軟な管理が必要なデータに適しています。

2) スタック (スタック):

機能:ローカル変数、関数パラメータ、関数呼び出しの戻りアドレスなどの情報を管理するために使用されます。

メモリ割り当て: 関数呼び出し時に自動的に割り当てられ、関数呼び出しが終了すると自動的に解放されます。

特徴: スタック メモリの割り当てと解放は非常に効率的で、高速な関数呼び出しやローカル変数に適しています。

3) グローバル/静的メモリ (グローバル/静的メモリ):

機能: グローバル変数、静的変数、およびゼロに初期化された、または初期化されていない静的データを格納します。

メモリ割り当て: プログラムの起動時に割り当てられ、グローバル変数と静的変数はプログラムの存続期間全体にわたって存在します。

特徴: グローバル変数と静的変数のスコープとライフサイクルは長く、グローバル アクセスが必要なデータに適しています。

一部のシステムでは、グローバル変数と静的変数を、「BSS セグメント」(初期化されていないグローバル変数と静的変数を格納する) と「データ セグメント」(初期化されたグローバル変数と静的変数を格納する) などの異なるセグメントに分離する場合があります。

4) コード領域/コードセグメント (コード領域/テキストセグメント):

機能: コードを実行するプログラムのマシンコード命令を保存します。

メモリ割り当て: プログラムがメモリにロードされるときにオペレーティング システムによって割り当てられます。

特徴: コード領域は読み取り専用で、プログラムの実行ロジックが含まれていますが、変更することはできません。関数のマシンコード命令は、プログラム実行のためにここに保存されます。

このパーティション方法は、ダークホース プログラマーのビデオに基づいています。

new関数とmalloc関数の使い方の紹介(関数プロトタイプや戻り値など)

new と malloc は、それぞれ C++ と Cで動的にメモリを割り当てるために使用される関数ですが、いくつかの違いがあります。以下は、その使用法の概要、関数のプロトタイプ、および戻り値です。
1) 新しい演算子:

new は、ヒープ上にメモリを動的に割り当て、新しく割り当てられたメモリへのポインタを返す C++ の演算子です。また、コンストラクターを自動的に呼び出してオブジェクトを初期化することもできます。
使用例:

int *ptr = new int; // 分配一个整数大小的内存,并将指针指向它
double *arr = new double[10]; // 分配一个包含10个双精度浮点数的数组

関数プロトタイプ:

new type;         // 分配一个 type 类型的内存并返回指向它的指针
new type[size];   // 分配一个包含 size 个 type 类型元素的数组并返回指针

注: new で割り当てられたメモリは、メモリ リークを避けるために、delete 演算子を使用して手動で解放する必要があります。
2) malloc 関数:

malloc は C 言語のライブラリ関数であり、C++ でも使用してヒープ上にメモリを動的に割り当てることができます。コンストラクターは呼び出されず、返されるポインターは初期化されていないメモリを指します。
使用例:

int *ptr = (int *)malloc(sizeof(int)); // 分配一个整数大小的内存,并将指针指向它
double *arr = (double *)malloc(10 * sizeof(double)); // 分配一个包含10个双精度浮点数的数组

前の括弧は型強制です

関数プロトタイプ:

void *malloc(size_t size); // 分配 size 大小的内存并返回指向它的指针

注: malloc によって割り当てられたメモリは、メモリ リークを避けるために、free 関数を使用して手動で解放する必要があります。
要約:

new 演算子は C++ の一部であり、コンストラクターを自動的に呼び出すことができます。また、割り当てられたメモリは delete 演算子を使用して解放する必要があります。

malloc 関数は C++ で使用できる C 言語のライブラリ関数で、確保したメモリは free 関数を使用して解放する必要があります。

C++ では、new 演算子と delete 演算子がコンストラクターとデストラクターでより適切に機能するため推奨されます。C コードとの互換性が必要な場合は、malloc と free を使用できます。

スタックのさまざまな側面の違いについて話す

ヒープとスタックに関しては、それらを区別する方法がたくさんあります。スタックがさまざまな点でどのように異なるかを詳しく説明します。

  1. 割り当てと解放方法:

ヒープ: ヒープ メモリの割り当てと解放は明示的であり、プログラマは手動でメモリの割り当てと解放を行う必要があります。割り当てには new または malloc を使用し、解放には delete または free を使用します。

スタック: スタック メモリの割り当てと解放は暗黙的に行われ、システムによって自動的に管理されます。関数が呼び出されると、スタック上のローカル変数が割り当てられ、関数が返された後、これらの変数は自動的に解放されます。

  1. ライフサイクル:

ヒープ: ヒープに割り当てられたメモリの有効期間は、明示的に解放されるまでプログラムの全期間にわたって持続しますオブジェクトがヒープ上に作成された後、コンストラクターを手動で呼び出して初期化し、不要になったら手動で解放する必要があります。

スタック: スタック上のローカル変数のライフサイクルは、それらが配置されている関数呼び出しに関連しています。変数はスコープに入ると割り当てられ、スコープから出ると手動で解放しなくても自動的に破棄されますしたがって、スタック上の寿命は通常短いです。

  1. メモリ管理:

ヒープ: ヒープ メモリの管理はより柔軟であり、動的なデータ構造やメモリの動的な割り当てが必要な状況に適しています。ただし、メモリを手動で管理する必要があるため、メモリ リークやダングリング ポインタなどの問題が発生しやすくなります。

スタック: スタック メモリの管理はシステムによって自動的に完了するため、プログラマにとってはより便利であり、手動でメモリを解放する必要はありません。ただし、スタック メモリの割り当てには制限があり、通常はローカル変数と関数呼び出しに使用されます。

  1. メモリ割り当て効率:

ヒープ: ヒープ メモリの割り当てと解放にはより複雑な操作が含まれるため、メモリの断片化が発生する可能性がありますしたがって、ヒープの割り当てと割り当て解除は比較的遅くなります。

スタック: スタック メモリの割り当てと解放はシステムによって自動的に管理されるため、非常に効率的です。関数の頻繁な呼び出しとローカル変数の管理は、スタック上でより効率的です。

  1. 使用するシーン:

ヒープ: 動的なデータ構造 (リンク リスト、ツリーなど)、大きなオブジェクトなど、オブジェクトのライフ サイクルの柔軟な管理が必要な状況に適しています。

スタック: 関数呼び出しとローカル変数の管理に適しており、特に頻繁な呼び出しに適しています。

要約すると、ヒープとスタックは、割り当て方法、ライフサイクル、メモリ管理、適用可能なシナリオの点で大きく異なります。プログラムのニーズに応じて、適切なメモリ管理方法を選択することは、コードの正確性、パフォーマンス、保守性にとって非常に重要です。

各スレッドには独自のスタックスペースがあります

マルチスレッド プログラミングでは、関数呼び出しとローカル変数を管理するために、各スレッドに独自のスタック領域が必要です。これは、スレッドが実行中に関数呼び出しチェーンやローカル変数値などの独自の実行コンテキストを維持する必要があるためです。スレッドのスタック領域は通常、スレッドの作成時に割り当てられ、スレッドの終了時に解放されます。

関数の場合、関数パラメータがスタックにプッシュされる順序は何ですか?

多くのコンピューター アーキテクチャでは、関数の引数の積み重ね順序は通常、右から左 (または後ろから前) です。これは、後のパラメータが最初にスタックにプッシュされ、前のパラメータが最後にスタックにプッシュされることを意味します。この順序は、一部のプログラミング言語の可変パラメーター関数 (C 言語の可変パラメーター関数など) と関数呼び出し規約をサポートするためです。
C 言語を例として、プロトタイプが次のような関数 foo があるとします。

int foo(int a, int b, int c);

foo 関数が呼び出され、パラメータが渡されるとき、パラメータがスタックにプッシュされる順序は次のとおりです。

まず、パラメータ c がパラメータ リストの右端にあるため、スタックにプッシュされます。

次に、パラメータ b がスタックにプッシュされ、その後にパラメータ a がプッシュされます。

したがって、スタック上でのパラメータのレイアウトは次のようになります:
±--------+
| ... |
±--------+
| a | <-- の先頭スタック
±-------+
| b |
±-------+
| c |
±-------+

パラメータの積み重ね順序は絶対的なものではなく、コンパイラ、アーキテクチャ、プログラミング言語によって異なる場合があることに注意してください。ただし、多くの場合、右から左の順序が一般的な規則です。一部のアーキテクチャでは、左から右の順序も使用される場合があります。特定の環境で別のプログラミング言語またはアーキテクチャを使用している場合は、パラメータがスタックにプッシュされる正確な順序については、対応するドキュメントを参照してください。

メモリリークと解決策について話す

メモリ リークとは、プログラムの実行時に、プログラムに割り当てられたメモリが正しく解放されず、システムで利用可能なメモリが徐々に減少し、最終的にはプログラムのクラッシュやシステム パフォーマンスの低下につながる可能性があることを意味します。メモリ リークは通常、プログラムがヒープ メモリなどの動的に割り当てられたメモリを適切に管理していない場合に発生します。一般的なメモリ リークの状況とその修正方法をいくつか示します。

  1. 未解放のヒープ メモリ: 最も一般的なタイプのメモリ リークは、動的に割り当てられたメモリが時間内に解放されない場合です。これは、プログラマが不要になったメモリを解放するために free() (C/C++) または delete (C++) を呼び出すのを忘れたことが原因である可能性があります。
    解決策: 動的に割り当てられたメモリが不要になった場合は、必ず適切な解放関数を呼び出してメモリを解放してください。C/C++ で free() を使用するか、C++ で delete() を使用するか、スマート ポインターなどの自動メモリ管理ツールを使用します。
  2. 参照カウントの問題: 参照カウントはメモリ管理手法ですが、特に循環参照の場合にメモリ リークを引き起こす可能性があります。2 つのオブジェクトが相互に参照しており、それらの参照カウントが適切に管理されていない場合、オブジェクトは決して解放されない可能性があります。(これは共有ポインタ (shared_ptr) が原因です)
    解決策: 循環参照の場合、弱い参照 (弱参照) を使用して、循環参照によって引き起こされるメモリ リークを回避できます。同時に、ガベージ コレクション メカニズムなど、より高度なメモリ管理手法の使用を検討できます。
  3. リソースの閉じ忘れ: メモリ以外にも、ファイルやネットワーク接続など、使用されなくなったときに閉じる必要があるリソースがあります。
    解決策: ファイル ハンドルやネットワーク接続などを閉じるなど、リソースが不要になった場合は、必ず適切なシャットダウン関数を呼び出してリソースを解放してください。
  4. オブジェクト プールが解放されない: オブジェクト プール テクノロジを使用する場合、オブジェクトがプールに正しく返されないと、メモリ リークが発生する可能性があります。
    解決策: オブジェクト プールを使用する場合は、再利用するために、使い終わったオブジェクトを必ずプールに戻してください。
  5. 内部キャッシュがクリーンアップされていない: プログラムが内部キャッシュを使用していても、不要になったキャッシュ項目を定期的にクリーンアップしていない場合、メモリ リークが発生する可能性があります。
    回避策: 内部キャッシュを使用する場合は、不要になったキャッシュ エントリを定期的にチェックしてクリーンアップするようにしてください。
  6. 検出にツールを使用する: Valgrind (C/C++)、VisualVM (Java) などのメモリ分析ツールを使用すると、メモリ リークの検出と診断に役立ちます。これらのツールは、メモリ リークの原因を特定し、解決策を提供するのに役立ちます。
  7. スマート ポインターを使用する: スマート ポインターをサポートするプログラミング言語では、スマート ポインターを使用すると、動的に割り当てられたメモリをより簡単に管理し、手動でメモリを解放する問題を回避できます。C++ では特にスマート ポインターについて説明します。
    プログラムを作成するときは、適切なメモリ管理の習慣を身につけ、適切な検出ツールを使用することがメモリ リークを減らす鍵となります。メモリ リークをタイムリーに検出して解決すると、プログラムの安定性とパフォーマンスが向上します。

記憶の調整とは何ですか

メモリの配置は、コンピュータがより効率的にデータを読み取って処理できるように、データを特定の場所に配置することを要求するルールです。
それぞれが一定量のデータを保存できるストレージ ボックスが列状に並んでいると想像してください。データの種類 (整数、浮動小数点、文字など) が異なると、それらを保存するために必要なボックスの数も異なります。メモリの配置は、コンピュータがより速くデータを読み取れるように、このデータをボックスに配置する方法を示します。
たとえば、保存するために 4 つのボックスを必要とする整数 (int) があるとします。メモリの配置では、整数の最初のボックスが特定のメモリ アドレスに位置するように、ボックスの行の先頭から整数を配置し始める必要があります。それが4バイトアライメントです。このルールに従わず、整数の最初のボックスが正しい位置にない場合、コンピューターが整数を取得するのに時間がかかり、効率に影響を与える可能性があります。
メモリの配置により、データが規則的に配置されるため、コンピュータはより効率的にデータにアクセスでき、プログラムのパフォーマンスが向上します。したがって、メモリの調整は、コンピュータがメモリ内のデータをよりインテリジェントに使用できるようにする最適化戦略と考えることができます。
例として、次の構造体を考えてみましょう。

struct MyStruct {
    
    
    char a;    // 1字节
    int b;     // 4字节
    double c;  // 8字节
};

この例では、char 型には 1 バイト、int 型には 4 バイト、double 型には 8 バイトが必要です。コンパイラとアーキテクチャに応じて、メモリ アライメントのルールは異なる場合がありますが、一般にコンパイラは、構造体全体のサイズがそのメンバの最大アライメント要件の倍数になるように構造体のメンバをアライメントします。
ここでコンパイラが 4 バイトのアライメント ルールを使用すると仮定すると、構造体のサイズは1 バイト (char) + 3 バイトのパディング + 4 バイト (int) + 8 バイト (double) = 16 バイトになります。このようにして、メモリは構造のレイアウトにより、各メンバーが適切な位置合わせ境界上に配置され、より効率的にアクセスできるようになります。
要約すると、構造体のメモリ アライメントにより、特定のアライメント ルールに従って構造体のメンバーが効率的な方法でメモリに格納され、プログラムのパフォーマンスと効率が向上します。コンパイラと設定が異なると、メモリ アライメント動作も異なる場合があります。

名前空間とは何ですか?一般的に使用される名前空間は何ですか?

プログラミングにおける名前空間 (Namespace) は、変数、関数、クラスなどの識別子を含むコンテナーです。名前空間の主な目的は、名前の衝突を避けることです。たとえば、プログラム内で「max」という関数を定義すると、同時にプログラミング言語の標準ライブラリに「max」という関数が含まれる可能性があります。名前空間がないと、これら 2 つの関数は競合します。ただし、名前空間で関数を定義すると、標準ライブラリの関数ではなく、名前空間を介して関数を呼び出すことができます。
名前空間の概念と使用法は、プログラミング言語によって異なる場合があります。一般的な名前空間のタイプをいくつか示します。

Python: Python では、モジュールは一種の名前空間であり、モジュール内の関数とクラスはすべてこの名前空間にあります。「import」ステートメントを使用してモジュールをインポートし、モジュール名を名前空間として使用して、モジュール内の関数またはクラスを呼び出すことができます。たとえば、「os」と「sys」は、Python の標準ライブラリでよく使用される 2 つのモジュールです。

C++: C++ では、「namespace」キーワードを使用してネームスペースを定義し、ネームスペース内の関数、クラスなどを定義できます。「::」演算子を使用して、ネームスペース内のメンバーにアクセスできます。たとえば、「std」は C++ 標準ライブラリの名前空間です。

Java: Java では、パッケージは名前空間の一種です。「package」キーワードを使用してパッケージを定義し、パッケージ内のクラスを定義できます。「.」演算子を使用して、パッケージ内のクラスにアクセスできます。たとえば、「java.util」と「java.io」は、Java 標準ライブラリでよく使用されるパッケージです。


C++ で最も一般的に使用される名前空間は、間違いなく標準ライブラリ (std) の名前空間です。この名前空間には、C++ 標準ライブラリのほとんどの関数とクラスが含まれています。たとえば、std::cout、std::endl、std::vector、std::string などです。
さらに、一般的に使用されるライブラリの中には、独自の名前空間を定義するものもあります。例えば:

Boost ライブラリ: Boost は、多くの高度な機能を提供する、広く使用されている C++ ライブラリです。Boost ライブラリのコンポーネントは通常、boost 名前空間の下にあります。たとえば、boost::asio は非同期 I/O 用のライブラリであり、boost::filesystem はファイルシステム操作用のライブラリです。

Eigen ライブラリ: Eigen は、線形代数、行列およびベクトル操作、数値解析、および微分方程式を解くための C++ ライブラリです。そのすべてのコンポーネントは、Eigen 名前空間の下にあります。

std::placeholders: これは、std::bind 関数の std 名前空間内のサブ名前空間です。たとえば、std::placeholders::_1、std::placeholders::_2 などです。

名前空間の使用法は、コードが参照するライブラリによって異なることに注意してください。コードで他のライブラリを使用している場合は、他の名前空間が存在する可能性があります。

include<> と include "" の違い

C および C++ プログラミングでは、#include ディレクティブを使用して外部ヘッダー ファイルをインクルードし、ヘッダー ファイルで宣言された関数、変数、およびその他の定義を現在のソース ファイルで使用できるようにします。#include ディレクティブでは、山括弧 <> と二重引用符 "" を使用してヘッダー ファイルの場所を指定します。

#include <>:
この形式の #include は、システムによって提供される標準ライブラリ ヘッダー ファイルをインクルードするために使用されます。コンパイラは、システム標準ライブラリのパスで指定されたヘッダー ファイルを検索します。

#include <stdio.h>
#include <stdlib.h>

ここで、stdio.h および stdlib.h はシステムによって提供される標準ライブラリ ヘッダー ファイルであり、コンパイラは標準ライブラリ パスからこれらのヘッダー ファイルを検索してインクルードします。

#include "":
この形式の #include は、ユーザー定義のヘッダー ファイルまたはその他のファイルをプロジェクトにインクルードするために使用されます。コンパイラはまず、現在のソース ファイルが配置されているディレクトリで指定されたヘッダ ファイルを検索します。見つからない場合は、コンパイラによって設定された他のパスで検索します。

#include "myheader.h"
#include "utils/functions.h"

ここで、myheader.h と utils/functions.h は両方ともカスタム ヘッダー ファイルであり、ソース ファイルと同じディレクトリ、または適切に設定されたパスの下に配置する必要があります。

要約すると、#include <> はシステム標準ライブラリのヘッダー ファイルをインクルードするために使用され、#include "" はユーザー定義のヘッダー ファイルをインクルードするために使用されます。コンパイラは、これら 2 つの異なる方法に従って、指定されたヘッダー ファイルを検索してインクルードします。ご使用の際は状況に応じて適切な形状をお選びください。

おすすめ

転載: blog.csdn.net/weixin_46274756/article/details/131965980