C 基本構文

C 言語は汎用プログラミング言語であり、システムプログラミング、組み込み開発、ハイパフォーマンスコンピューティングなどの分野で広く使用されています。

C 言語は、効率性、柔軟性、移植性の高さといった特徴を持ち、多くの他のプログラミング言語(C++、Java、Go など)の基盤となっています。現在でも、オペレーティングシステム、デバイスドライバ、低レベルライブラリの開発における主流の選択肢です。

C 言語では、トークン(Token)はプログラムの基本的な構成単位です。コンパイラはソースコードを字句解析し、コードを個々のトークンに分解します。トークンはコンパイラが認識できる最小の意味のある単位であり、C プログラム全体はこれらのトークンで構成されています。

C 言語のトークンは主に以下の種類に分類されます:

  • キーワード(Keywords):言語が予約している特別な意味を持つ単語。例:int、if、return。
  • 識別子(Identifiers):プログラマーが定義する名前で、変数、関数、配列などに使用されます。
  • 定数(Constants):プログラム内で固定された不変の値。例:整数42、浮動小数点数3.14。
  • 文字列リテラル(String Literals):二重引用符で囲まれた文字の並び。例:"Hello"。
  • 演算子(Operators):演算を実行するための記号。例:+、==、&&。
  • 区切り文字(Separators):文やコードブロックを区切るための記号。例:;、{}。

C プログラムの基本構造

これは "Hello, World!" を出力する簡単な C 言語プログラムです:

例

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

上記のコードの構成は次のとおりです:

  • プリプロセッサディレクティブ:例:#includeと#define、コンパイル前にプリプロセッサによって処理されます。
  • メイン関数:すべての C プログラムが必ず1つだけ持つmain()関数であり、プログラムの実行入口です。
  • 変数宣言:プログラムで使用する変数を宣言します。C89 では宣言はコードブロックの先頭でなければなりません。
  • 関数定義:プログラムで使用する関数を定義し、関数の具体的な実装ロジックを記述します。

もう少し複雑な C プログラムの構造の説明(後の章で各項目を詳しく説明します):

例

#include <stdio.h> // ヘッダファイルのインクルード

#define PI 3.14159 // マクロ定義

// 関数宣言
int add(int a, int b);

int main() {         // メイン関数
    // 変数宣言
    int num1, num2, sum;

    // ユーザー入力
    printf("Enter two integers: ");
    scanf("%d %d", &num1, &num2);

    // 関数呼び出し
    sum = add(num1, num2);

    // 結果の出力
    printf("Sum: %d\n", sum);

    return 0;        // 0 を返すとプログラムが正常に実行されたことを示す
}

// 関数定義
int add(int a, int b) {
    return a + b;
}

ヘッダファイルのインクルード

  • ヘッダファイルは通常、プログラムの先頭で#includeディレクティブでインクルードします。ヘッダファイルは関数やライブラリの宣言を提供します。例:標準入出力ライブラリ<stdio.h>、標準ライブラリ<stdlib.h>などです。これらは関数、マクロ、定数などを定義し、プログラムが事前定義されたライブラリ関数を使用できるようにします。
  • システムヘッダファイルは山括弧を使用し<>、カスタムヘッダファイルは二重引用符を使用します""、両者の検索パスは異なります。
  • 例:#include <stdio.h>(システムヘッダファイル)、#include "myheader.h"(カスタムヘッダファイル)

マクロ定義

  • マクロは#defineディレクティブで定義される記号定数またはコード断片です。マクロはコンパイル前にプリプロセッサによってテキスト置換され、実行時のメモリを消費しません。定数や単純なインラインコードロジックの定義によく使用されます。
  • マクロには型チェックがないため、使用には注意が必要です。型を要求する定数には、const変数で置き換えることをお勧めします。
  • 例:#define PI 3.14159、#define MAX(a, b) ((a) > (b) ? (a) : (b))

関数宣言

  • C 言語では、関数の宣言(プロトタイプとも呼ばれる)は、関数の定義または呼び出しより前に存在する必要があります。宣言は関数の戻り値の型、関数名、パラメータリストを提供し、コンパイラが呼び出し地点で型チェックを行えるようにします。
  • 関数宣言はヘッダファイルに置くことができ、複数のソースファイルで共有して使用できます。
  • 例:int add(int a, int b);

メイン関数

  • main()関数は C プログラムのエントリポイントであり、すべての C プログラムには必ず1つのmain()関数が含まれます。プログラムはmain()から実行を開始し、戻り値は通常0がプログラムの正常終了を表し、非ゼロの値はエラーが発生したことを表します。
  • main()関数はコマンドライン引数を受け取ることもできます:int main(int argc, char *argv[])、ここでargcは引数の個数、argvは引数の文字列配列です。
  • 例:int main() { ... }

変数宣言

  • C プログラムでは、すべての変数は使用前にその型を宣言しなければなりません。変数はmain()関数内で宣言(ローカル変数)することも、関数の外部でグローバルに宣言(グローバル変数)することもできます。
  • 変数宣言時に初期化も行えます。初期化されていないローカル変数は不定値(ガベージ値)を含みますが、グローバル変数と静的変数は自動的に0。
  • 例:int count = 0;、float price = 9.99;、char grade = 'A';

文と式

  • 文は C プログラムの基本的な実行単位であり、通常は関数呼び出し、代入、制御文(例:if、forなど)または式です。式は変数、演算子、定数からなるコードの断片であり、値を計算できます。
  • C 言語では、代入自体も式であり、値(代入された値)を持ちます。そのため、a = b = c = 0;のようなチェーン代入を書くことができます。
  • 例:
    printf("Enter two integers: ");
    sum = add(num1, num2);

制御フロー文

  • 制御フロー文はプログラムの実行順序を制御するために使用され、条件分岐(if/else、switch)とループ(for、while、do-while)が含まれます。また、ジャンプ文もあります。break、continue、gotoとreturn。
  • 制御フロー文を適切に使用することは、論理が明確なプログラムを書く鍵であり、乱用を避けるべきです。gotoコードの可読性を保つためです。
  • 例:
    if (num1 > num2) {
        printf("num1 is greater than num2");
    }

関数定義

  • 関数定義は完全な関数本体を含み、関数の具体的な実装を記述します。関数は戻り値の型、関数名、パラメータリスト、関数本体から構成されます。優れた関数設計は「単一責任」の原則に従うべきであり、各関数は1つのことだけを行います。
  • C 言語の関数はオーバーロードをサポートしていません。各関数名は同じスコープ内で一意でなければなりません。関数は自分自身を再帰呼び出しできます。
  • 例:
    int add(int a, int b) {
        return a + b;
    }

return 文

  • returnreturn 文は関数の実行を終了し、制御権を戻り値とともに呼び出し元に返します。void型の関数は値なしのreturn;で途中終了できます。また、return文を省略することもできます。
  • main()関数の戻り値はオペレーティングシステムに渡されます。0は通常成功を表します。標準マクロEXIT_SUCCESSとEXIT_FAILURE(定義されている<stdlib.h>)。
  • 例:return 0;、return EXIT_SUCCESS;

区切り文字

区切り文字は文、式、コードブロックを区切るために使用され、コンパイラがプログラムの構造を理解するのに役立ちます。一般的な区切り文字は次のとおりです:

  • カンマ,:変数宣言、関数パラメータ、初期化リスト内の複数の要素を区切るために使用されます。
  • セミコロン;:文を終了するために使用され、C 言語で最も一般的な区切り文字です。
  • 括弧:
    • 丸括弧():関数呼び出しのパラメータリスト、制御フロー文の条件、式の評価順序を強制するために使用されます。
    • 中括弧{}:関数本体、制御フローコードブロック、複合リテラルと配列/構造体の初期化を定義するために使用されます。
    • 角括弧[]:配列の宣言と要素アクセス(添字演算)に使用されます。

C プログラムでは、セミコロン;は文の終端記号であり、すべての文はセミコロンで終了する必要があります。これは論理エンティティの終わりを示します。セミコロンの欠落は初心者に最もよくある構文エラーの1つです。

例として、次は2つの異なる文です:

printf("Hello, World! \n");
return 0;

単独のセミコロンは空文としても使用でき、何も行わないことを示します。特定のループ構造でよく使用されます:

while (condition);  // 空循环体,等待条件变化

コメント

C 言語には2つのコメント方法があります:

// 単行コメント

以 //で始まる単行コメント(C99 で導入)。このコメントは//から行末まで続き、単独の行にすることも、コード行の末尾に置くこともできます。

/* 単行コメント */
/* 
 複数行コメント
 複数行コメント
 複数行コメント
 */

/* */この形式のコメントは1行または複数行にでき、関数の説明やモジュールのヘッダなど、長いコメントのシナリオに適しています。

例

// これは単行コメントです

/*
これは複数行コメントです
複数行にまたがることができます
*/


int main() {
    // メッセージを出力する
    printf("Hello, World!\n");
    return 0;
}

コメントを使用する際は、次の点に注意する必要があります:

  • 注釈内に注釈をネストすることはできません(/* /* */ */は非合法です)。
  • コメントは文字列リテラルまたは文字定数の内部に置くことはできません。
  • コメントは人が読むためのものであり、簡潔かつ明確にすべきです。「何をしたか」ではなく「なぜ」を重点的に説明し、コード自体が「何をするか」を表現できるようにすべきです。
  • 優れたコメント習慣はプロフェッショナルプログラミングの重要な構成要素であり、コードの保守とチーム協力に役立ちます。

識別子

識別子は、プログラム内の変数、関数、配列、型などの名前であり、プログラマー自身が定義します。識別子は英字(大文字または小文字)、数字、アンダースコアで構成されますが、最初の文字は英字またはアンダースコアでなければならず、数字は使用できません。

識別子は、英字 A-Z または a-z またはアンダースコア _ で始まり、その後にゼロ個以上の英字、アンダースコア、数字(0-9)が続きます。

C の識別子内では、@、$、% などの句読文字は許可されません。C は大文字と小文字を区別するプログラミング言語です。したがって、C では、Manpowerとmanpowerは2つの異なる識別子です。以下に有効な識別子をいくつか示します:

mohd       zara    abc   move_name  a_123
myname50   _temp   j     a23b9      retVal

以下は無効な識別子の例とその理由です:

2abc        // 错误:以数字开头
my-name     // 错误:包含连字符 -
float       // 错误:与关键字重名
my name     // 错误:包含空格

命名の推奨事項:識別子は説明的であり、その用途を明確に表現できるものであるべきです。一般的な命名スタイルには、スネークケース(my_variable)と lower camel case(myVariable)があります。同じプロジェクト内で一貫性を保てば十分です。二重アンダースコア__で始まる識別子は通常、コンパイラまたは標準ライブラリによって予約されているため、ユーザーコードでの使用は避けるべきです。

定数

定数は固定値であり、プログラムの実行中に変更されません。C 言語で定数を定義する方法は主に2つあります:constキーワードを使用するか、#defineマクロ定義を使用します。

定数には、整数定数、浮動小数点定数、文字定数、列挙定数などがあります:

const int MAX = 100;          // 整型常量
const float PI = 3.14159;    // 浮点型常量
const char NEWLINE = '\n';   // 字符常量(转义字符)

文字定数でよく使われるエスケープ文字には以下があります:

  • \n:改行
  • \t:水平タブ
  • \\:バックスラッシュ
  • \':単一引用符(アポストロフィ)
  • \0:ヌル文字(文字列終了マーク)

を使用してconst定義された定数は型情報を持ち、コンパイラが型チェックを行うことができ、#defineマクロよりも安全性が高く、C99 以降で推奨される方法です。

文字列リテラル

文字列リテラルは、二重引用符で囲まれた文字の並びであり、テキストデータを表すために使用されます。

文字列リテラルはメモリ内で文字配列として格納され、末尾に自動的にヌル文字が追加されます。\0(つまり null 終端文字)が付加され、文字列の終わりを示します。

char greeting[] = "Hello, World!";  // 编译器自动计算长度(包含 \0)

文字列リテラルに関する重要な注意点は以下のとおりです:

  • "Hello" はメモリ内で実際には6バイトを占めます:'H'、'e'、'l'、'l'、'o'、'\0'。
  • 文字列リテラルは読み取り専用メモリ領域に格納されるため変更できません。変更する必要がある場合は、文字配列にコピーする必要があります。
  • 隣接する2つの文字列リテラルは、コンパイラによって自動的に連結されます:"Hello, " "World!"は次と同等です"Hello, World!"。
  • 文字'A'(文字定数)と文字列"A"(文字列リテラル)は異なります。後者には'A'と'\0'2つの文字が含まれます。

演算子(Operators)

演算子は、算術演算、論理演算、比較演算などのさまざまな操作を実行するために使用されます。演算子はオペランドに作用し、式を形成して結果を生成します。

C 言語の演算子は種類が豊富で、一般的なものには次があります:

  • 算術演算子:+, -, *, /, %(このうち/は整数に対して整除を行い、%は余りを取ります)
  • 関係演算子:==, !=, >, <, >=, <=(1 を返せば真、0 を返せば偽を表します)
  • 論理演算子:&&(AND),||(OR),!(NOT)。短絡評価をサポートします
  • ビット演算子:&(ビットごとの AND),|(ビットごとの OR),^(ビットごとの XOR),~(ビットごとの NOT),<<(左シフト),>>(右シフト)
  • 代入演算子:=, +=, -=, *=, /=, %=(複合代入。例えばa += bは次と同等ですa = a + b)
  • インクリメント/デクリメント演算子:++(インクリメント),--(デクリメント)。前置(先に演算してから値を取得)と後置(先に値を取得してから演算)の2つの形式があります
  • その他の演算子:sizeof(型または変数のバイトサイズを計算),?:(三項演算子)&(アドレス取得),*(間接参照)->(ポインタによるメンバアクセス),.(直接のメンバアクセス)
int a = 5, b = 10;
int sum = a + b;           // 算术运算符 +,sum = 15
int isEqual = (a == b);   // 关系运算符 ==,isEqual = 0(假)
int result = a << 1;      // 位运算左移,result = 10(相当于 a * 2)
a++;                       // 后缀自增,a 变为 6
int max = (a > b) ? a : b; // 三目运算符,max = b = 10

演算子には優先順位と結合性があります。優先順位は演算の順序を決定し、結合性は同じ優先順位の演算子の演算方向を決定します。優先順位が不明な場合は、コードの可読性を高めるために括弧を使用して明確にグループ化することをお勧めします。

キーワード

次の表に、C の予約語を示します。これらの予約語は、定数名、変数名、その他の識別子名として使用できません。C 言語では特定の構文上の意味を持ちます。

キーワード 説明
auto自動変数を宣言する(ローカル変数のデフォルトの記憶域クラス。現代の C では明示的に使われることはほとんどない)
break現在のループまたは switch 文から抜け出す
caseswitch 文内の分岐ラベル
char文字型の変数または関数の戻り値の型を宣言する(通常1バイト)
const読み取り専用定数を定義する。const で修飾された変数の値は変更できない
continue現在のループ反復を終了し、直接次のループを開始する
defaultswitch 文のデフォルト分岐(すべての case が一致しない場合に実行)
dodo-while ループのループ本体。ループ本体は少なくとも1回は実行される
double倍精度浮動小数点型の変数または関数の戻り値の型を宣言する(通常8バイト)
else条件文の否定分岐(if と共に使う)
enum列挙型を宣言する。名前付き整数定数の集合を定義するために使う
extern変数または関数が他のファイル、または同じファイル内の別の場所で定義されていることを宣言する
float単精度浮動小数点型の変数または関数の戻り値の型を宣言する(通常4バイト)
forfor ループ文。ループ回数が既知のシナリオに適する
goto無条件ジャンプ文(コードの可読性を低下させる可能性があるため、慎重に使用すべき)
if条件判断文
int整数型の変数または関数の戻り値の型を宣言する(通常4バイト)
longlong 整数型の変数または関数の戻り値の型を宣言する
register変数をレジスタに格納してアクセス速度を向上させるようコンパイラに提案する(現代のコンパイラは通常自動的に最適化する)
return関数の return 文。戻り値を伴うことも、伴わないこともできる(void 関数)
shortshort 整数型の変数または関数の戻り値の型を宣言する(通常2バイト)
signed符号付き型の変数を宣言する(負数を表現できる。整数型はデフォルトで符号付き)
sizeofデータ型または変数が占めるバイト数を計算する(コンパイル時演算子)
static静的変数を宣言する(ローカル静的変数の寿命はプログラム終了まで続く。グローバル静的変数はファイルスコープに制限される)
struct構造体型を宣言する。異なる型のデータを1つのまとまりに結合する
switch多分岐選択文。式の値に応じて対応する case にジャンプする
typedef既存のデータ型に新しい別名を定義し、コードの可読性を高める
unsigned符号なし型の変数を宣言する(非負数のみ表現できるが、範囲が広い)
union共用体型を宣言する。すべてのメンバが同じメモリ領域を共有する
void関数が戻り値を持たない、または引数を持たないことを宣言する。型のないポインタを宣言するためにも使うvoid*
volatile変数がプログラム外部の要因(ハードウェアや割り込みなど)によって変更される可能性があることを宣言し、その変数の読み書きに対するコンパイラの最適化を禁止する
whilewhile ループ文。ループ回数は不明だが終了条件が明確なシナリオに適する

C99 で追加されたキーワード

_Bool _Complex _Imaginary inline restrict

C99 で追加されたキーワードは主に、ブール型のサポート(_Bool)、複素数のサポート(_Complex、_Imaginary)、インライン関数の最適化(inline)、ポインタのエイリアス制限の最適化(restrict)。

C11 で追加されたキーワード

_Alignas _Alignof _Atomic _Generic _Noreturn
_Static_assert _Thread_local      

C11 で追加されたキーワードは主に、メモリ配置制御(_Alignas、_Alignof)、原子操作とマルチスレッドサポート(_Atomic、_Thread_local)、ジェネリック選択(_Generic)、静的アサーション(_Static_assert)および戻り値なし関数のマーク(_Noreturn)。


C の空白

空白のみを含む行は空白行と呼ばれ、コメントが含まれる場合もあります。C コンパイラはそれを完全に無視します。

C では、空白は空白文字(スペースキー)、タブ(Tab)、改行、コメントを表すために使われます。空白は文の各部分を区切り、コンパイラが文中のある要素(たとえばint)がどこで終わり、次の要素がどこで始まるかを認識できるようにします。したがって、次の文では:

int age;

intとageの間には、少なくとも1つの空白文字(通常は空白)が必要で、これによりコンパイラはそれらを区別できます。一方、次の文では:

fruit = apples + oranges;   // 获取水果的总数

fruitと=、または=とapplesの間の空白文字は必須ではありませんが、可読性を高めるために、必要に応じて適宜空白を追加することができます。

優れた空白とインデントの習慣は高品質なコードの重要な構成要素です。以下の規範に従うことをお勧めします:

  • 一貫したインデント(通常はスペース4個またはタブ1個)を使用して、コードブロックの階層を表します。
  • 演算子の両側に空白を追加します(例:a + b)。これにより式が読みやすくなります。
  • コンマの後に空白を追加します(例:int a, b, c;)、複数の要素を区別します。
  • 適切に空行を使用して論理的に関連するコードセグメントを区切り、全体的な可読性を高めます。
その他の拡張