文と式

学べる事

  • 式とは
  • 文とは

ここからは、前回とはまったく異なるアプローチで、C言語を説明してみます。

What is 式

基本事項のページ で、初期化を伴う変数の宣言は以下のようなコードでした。

int main() {
    // データ型 識別子 代入演算子 式;
    int x = 0;
}

ここで、 とはC言語の構文がとる一つの要素です。
この説明を、本ページを通して解説していきますが、最初のうちは分からないのが当たり前です。

まず、C言語において、以下はすべて式です。

  • 0
  • 1.1
  • ~0 (~ は単項演算子で、ビット反転)
  • 1 + 1
  • "Hello" (また詳しく説明しますが、Hello に対する先頭アドレスを保持する値として評価されます)
  • 1 < 2 (<は比較演算子。この式はtrueとして評価されます)
  • (1 + 2) - 3 * 4 / 5 % 6 (整数リテラルは、intでしたね。もちろんこの式の結果はintになります)

なにか思うところがあるかもしれませんが、C言語及び多くのプログラミング言語において、ただの整数リテラル0も、 なのです。
あえて今のうちに説明するなら、C言語において 式 は演算子を含む必要はまったくないのです。

What is 文

int x = 1 + 1;

この初期化を伴う変数の宣言において、1 + 1は式ですね。
そして、これは 代入 です。
代入文は次のような構文(文法)をとるものでしたね。

データ型 識別子 代入演算子(=) 式 ;

例:

// データ型 識別子 代入演算子(=) 式 ;
int x = 0;

そうです。式は、代入文の構成要素となっています。

関数がもつ文

#include <stdio.h>

int main() {
    printf("Hello, ");
    printf("World");
    printf("!\n");
    int x = 1 + 1;
    123; // 式; で文となる
    return 0;
}

この例では、main関数は6つの文をもっています。
関数が)の後にとりうるブロック({})の中には、任意の数のを書くことができます。

他の文の例をみてみましょう。

return文

return文の例をみてみましょう。

int main() {
    return 0;
}

これは何もないプログラムですが、0は式です。
このことから、return文の構文は次のようなものであると考えることができます。

return 式;

他の例もみてみましょう。

int one_add_two() {
    return 1 + 2;
}

この関数で、return文はreturnキーワードの後に1 + 2の式をとっています。
12intの整数リテラルで、+は、二つの(二項の)被演算子(オペランド)をとる、加算を行う演算子(オペレータ)です。

また、変数は式の一部になり得ます。
以下の例で説明してみます。

int sum(int x, int y) {
    return x + y;
}

return文がとる式に、x + y が書かれていますが、もちろん文法上のエラーはないですね。
変数の宣言でも同様です。

int main() {
    int x = 1;
    int y = 2;
    int z = x + y;
//  int z = 1 + 2; と同じ
}

変数の宣言において、代入演算子の後は式で、ここでは1, 2, x + y がそうですね!
このように、変数は式の一部になることが分かりましたか?

関数呼び出しは式?

また、値を返す関数は、式になります。

int sum(int x, int y) {
    return x + y;
}

int main() {
    int a = sum(1, 2) + 3 + sum(4, 5);

    return 0;
}

ここで、sum()intを返すので、有効な式で、a15になりますね。
このように、ある例外を除いて関数呼び出しは式になります。
例外とは、返り値がvoidの式です。

void

void とは主に、「何もない」または「型がない」という概念を表す型で、キーワードです。
実際のところ、複数の役割をもつために、一つの言葉で纏めることは難しいです。
ですが、今までのとおり、voidを識別子に使うことは出来ません。
以下の例は、そのvoidの役割の一つである、関数の返り値の型がvoidになっている例をみてみましょう。

#include <stdio.h>

void f() {
    printf("Hello\n");
     // 値を返さないため、return 文には式がない
    return;
}

int main() {
    f();
    return 0;
}

この例で、f()の返り値の型は、voidになっていることが分かります。
そして、return文の定義は、

return 式;

のようなものではなかったですか?
この例では、式が書かれていませんね。
関数の返り値の型に void を指定すると、その関数は値を返さないことを示します。
つまり、関数の返り値として void を使うことで、呼び出し側は戻り値を期待しなくてよいことが分かります。

式文

先ほども少し例に示しましたが、以下はすべて 式; という構文をとる式文です。(注: あまり一般的でない名前)

123;
1+1;
printf("Hello!"); // 実はprintf()の返り値は int: 標準出力された文字列のバイト数
x;

if 文

続いて、今まで避けてきたif文を以下に示します。
見たことがある人ならば、ある程度構文を予想できるのではないでしょうか。

if (式) {}

また、ブロックのすぐ後ろに、任意の数のelse if (式) {}と、任意のelse {}をとります。

if (式) {

} else if (式) {

} else if (式) {

} else {

}

ここで、最初のif文がもつ式が最終的に1に評価された場合、if文がとりうるブロック内が実行されます。
また、それが0と評価された場合、一つ下のelse ifが評価され、さらにその式が0であった場合、elseが実行されるか、elseが存在しない場合はif文が実行されないことになります。

0/1と評価されるとは、どういうことでしょうか?
C言語における論理演算は、等価演算( == )や非等価演算( != )、論理積( && )、論理和( || )などが挙げられます。
それらの論理演算が真のときは1、偽のときは0と評価(計算)されます。
(実際には、0意外のすべての値が真とされます)
逆に、先ほども言いましたが、ただの整数リテラルもでした。

if (0 /* 偽 */) {
    do_something();
}

このようなコードを書いても、明らかに構文上正しいですし、絶対に実行されないif文が完成することになります!

文と式

いきなりですが、問題です。
以下は、構文上問題のあるコードでしょうか?

int x = if (1 != 2) {
    return 42;
} else {
    return 0;
};

よく考えてみましょう。

変数の宣言の構文は、以下のようなものでした。

型 識別子 = 式;

さて、もしこのコードが正しいとするなら、if (1 != 2) {...};までの部分は式になります。
ですが、if文は if でしたね?
そうです。
C言語では、if文は文であって式でないので、このコードは構文エラーとなります。
if文のブロックの中は、任意の数の文を期待する(もつ)ので、return が来ていることについては正しいですが、これは関数を終了させる処理で、if文とは関係がありません。

また、"C言語では" と言いました。
これは、また逆に if式 が存在する言語があるということです。
それは前に登場したRustです。
Rustでは、以下のように書くことができます。(▶マークで実行ができるかもしれません)

Rust:

fn main() {
let x = if 1 != 2 {
    42
} else {
    0
};

println!("{x}"); // 42
}

このように、式と文の区別がついたでしょうか?

if文がとりうる括弧の中は式であると具体化しましたが、多くは論理式や条件(condition)と呼ばれます。

課題

検索やこのページなどを読んで、他の制御構文を、ここで覚えた文と式の違いに着目して理解してください。