先頭へ戻る

いろいろな式 | Programming Place Plus C言語編 第27章

Programming Place Plus トップページC言語編

先頭へ戻る

このページの概要

以下は目次です。


ここまであまり踏み込んで考えてきませんでしたが、ここで「」というものについて取り上げておきます。

たとえば、「a = b」のようなものが式です。これは全体として、代入式を構成しています。

一方で「a = b;」のように、末尾に「;」が付いているものは、式ではなくです。文がつねに「;」で終わるというわけではなく、ブロックの末尾「}」で終わる複合文のようなものもあります。式の末尾に「;」を付けた文は、式文と呼びます。

式は、演算子を使って何らかの演算を行い、結果(値)を得ようとするものです

一方、文とは「実行すべき動作を決める」ものです。たとえば、if文なら分岐という動作を実行すべき、for文なら繰り返し処理を実行すべき、return文なら関数から戻る動作を実行すべき、という指示を与えているといえます。

「a * 2」は式ですが、これは、乗算演算子を使って乗算を行い、結果を得ようとしています。実際のコードでは、「b = a * 2」だとか「f(a * 2)」といった使い方をするでしょう。これは「a * 2」という式から得られた値を、b という変数へ代入したり、f という関数へ引き渡したりしています。なお、式から値を得ることを、式を評価するといいます

ここで、代入は代入式で行われています。関数呼び出しも、戻り値という形で結果を得る式です。そのため、「b = a * 2」も「f( a * 2 )」も、全体として1つの式であり、その内側に「a * 2」という式が含まれています。このように、式はほかの式の一部になっていることがあります。他の式の一部になっていない式は、完全式(完結式)と呼びます。

完全式のほかの例として、if や switch、while、do の条件式や、for の ( ) 内の3つの式があります。

値を得ることを目的としていない式もあります。たとえば「a++」は式ですが、これを「a++;」とだけ書いた場合、「a++」を評価した値の行き場がありません。しかし、変数a 自身の値は変化します。式を評価した結果、変数の値が変化するなど、実行環境に何らかの影響を与えることを、副作用といいます。

「a++;」は副作用を得ることだけを目的としていると言えます。「b = a++;」の場合に、変数a の値が変化するのは副作用の結果、変数b の値が変化するのは代入式の結果です。

副作用は、特定のタイミングをもって確定します。このような位置を、副作用完了点といいます。逆にいえば、副作用完了点に到達するまで、副作用による変数の値の変化は確定していません。代表的な副作用完了点は、以下のところにあります。

1つの変数の値を、副作用完了点に到達するまでの間に2回以上変更することは未定義の動作になるため、避けなければなりません。たとえば、「a = a++;」や「f( a++, a++ );」は問題があります。

左辺値

結果の型が、オブジェクト型か、void 以外の不完全型になる式を左辺値といいます(型の呼び名については「型の分類表」を参照)。

左辺値が評価されたあとは、特定のオブジェクトを指し示しています。オブジェクトとは、ある型のある値を持つ、メモリ上の一部分のことをいいます。これまでの章では、これはつまり変数のことでした。

【上級】第35章で取り上げる方法を使うと、変数を宣言することなく、メモリ上に任意の値を置けるため、大抵、変数という名称を使って説明されるようなことも、オブジェクトという用語を使わないと、不正確な場面がありえます。

変数は、左辺値の具体例としてシンプルなものです。

int n = 0;

n = 10;  // n が左辺値

この場合、n という左辺値は、メモリ上のどこかにある int型のオブジェクトを指し示しているといえます。

左辺値という名前は、代入式で「=」の左側に置かれることに由来しますが、そういう定義ではないので注意してください。

左辺値に対して、右辺値という言葉を使うことがあります。左辺値は名前に反して「式」ですが、右辺値は「式の値」です。標準規格では右辺値とは呼ばず、「式の値」と表現されています。

左辺値になり得るものが「=」の右側に現れることがありますが、その場合、指し示しているオブジェクトの値に置き換えられ、左辺値ではなくなります。

int n = 0;
int n2;

n2 = n;  // n2 は左辺値。n は 0 に置き換えられる

これは、これまで当然のように理解してきた挙動のとおりでしょう。この挙動は「=」の右側に限らず、多くの演算子で起こります。

【上級】例外的なのは、++演算子、–演算子、sizeof演算子、アドレス演算子(第31章)のオペランドです。これらの演算子では、左辺値のまま扱われます。

【上級】配列の場合、置き換えが起こるときには、先頭の要素を指すポインタに置き換えられます(第32章)。ただし、sizeof演算子、アドレス演算子のオペランドになったときと、文字の配列を初期化するときに使う文字列リテラル(文字列リテラルは配列です(第32章))は、配列のまま扱われます。

void式

通常、式は何らかの結果、つまり値を得ようとするものですが、まれに結果の値がないケースがあります。このような式は、void式と呼びます。結果の値がないとはいえ、その式の評価過程で副作用が起こるのなら、式自体には意味があります。

具体的には、戻り値型が void の関数を呼び出す式が挙げられます。

void f(void)
{
}

int main(void)
{
    f();                // OK
    int n = (int)f();   // コンパイルエラー
}

このサンプルプログラムのように、void式の結果をほかの型へキャストすることはできません。これは型の問題というより、そもそも値がないので当然ではあります。

また、戻り値型が void ではない関数でも、呼び出し側で void型へキャストすると、全体として void式になります。そのような行為に大きな意味はありませんが、戻り値があるのに使っていないときに警告を出すコンパイラに、不要であるという意思を伝えるために行う場合があります。

int f(void)
{
    return 0;
}

int main(void)
{
    (void)f();               // OK。戻り値を捨てた
    int n = (int)(void)f();  // コンパイルエラー
}


同じ型の変数をまとめて宣言する

いくつかの変数を宣言するとき、今までは次のようにしてきました。

int i;
int j;
int k;

変数の型が同じであれば、次のようにまとめて宣言できます。

int i, j, k;

初期値がある場合には、次のように書けます。

int i = 0, j = 10, k = 20;

一部の変数にだけ初期値を与えることもできます。

int i, j = 10, k;

特に、1番最後のところにだけ初期値を書くと、すべての変数に同じ初期値が与えられているように錯覚することがあるので、注意してください。

int i, j, k = 0;  // i と j は不定値

個人的には、初期値を与えるのであれば、これまでどおり、別々の行で宣言した方が安全ですし、見やすくもあると思います。

不定値は避けるべきなので、原則的には、宣言と同時に初期値を与えるべきです。その意味では、変数をまとめて宣言する構文はあまり使う機会がないと思います。C95規格までは、ローカル変数をブロックの先頭で宣言しないといけないため、その時点で適切な初期値を与えられないことがあり、やむを得ないこともあります。

次のプログラムは使用例です。

#include <stdio.h>

int main(void)
{
    int a = 5, b = 10, c = 20;

    printf( "%d %d %d\n", a, b, c);
}

実行結果:

5 10 20

カンマ演算子

カンマ演算子を使うと、複数の式を連結できます。よくあるのは、for文で 2つ以上のループ制御変数を使うというものです。

#include <stdio.h>

int main(void)
{
    for( int i = 0, j = 20; i < j; ++i, --j ){
        printf( "%d %d\n", i, j );
    }
}

実行結果:

0 20
1 19
2 18
3 17
4 16
5 15
6 14
7 13
8 12
9 11

カンマ演算子(,) で区切られた 2つの式は、左側から評価されます。全体としての結果は、一番右側の式を評価した結果になります。

for文の初期設定式で2つ(以上)の変数を宣言することもできますが、これは「同じ型の変数をまとめて宣言する」の項でみた方法を、初期設定式のところに入れているだけのことです。そのため、宣言しようとするそれぞれの変数は、同じ型でなければなりません。

カンマ演算子は、2個以上の実引数を渡すときに使う「,」や、配列や構造体を初期化するときに現れる「,」とは役割が異なります。区別が付かないので、これらの場所ではカンマ演算子を使えません(括弧を補えば使えます)。


練習問題

問題① 次のプログラムを、できるだけ短く書き直してください。

#include <stdio.h>

int main(void)
{
    int num1 = 10;
    int num2 = 20;
    int num3 = 30;

    printf( "%d %d %d\n", num1, num2, num3 );

    num1 = num3;
    num2 = num3;

    printf( "%d %d %d\n", num1, num2, num3 );
}

問題② 次のプログラムを、条件演算子を使って書き直してください。

#include <stdio.h>

int main(void)
{
    int num = 90;

    if( num < 100 ){
        printf( "100未満\n" );
    }
    else{
        printf( "100以上\n" );
    }
}

問題③ 10文字の文字列 “abcdefghij” があり、先頭と末尾の両方から 1文字ずつを取り出して出力するプログラムを書いてください。 ただし、カンマ演算子をうまく使ってください。
例として、“abcde” と入力された場合、次のように出力されるようにしてください。

a e
b d
c c
d b
e a


解答ページはこちら

参考リンク


更新履歴

≪さらに古い更新履歴を展開する≫



前の章へ (第26章 構造体)

次の章へ (第28章 関数形式マクロ)

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー