たのしい工学

プログラミングを学んで、モノをつくりたいひと、効率的に仕事をしたい人のための硬派なブログになりました

【独学するコンピュータサイエンス】関数と式2

   

void 式

void 式とは値を無効とする式或いは void 型を持つ式の事で、その結果は値の存在そのものが無くなります。その為 void 式の結果はどんな事があっても使用出来ません。読み取る事も書き込む事もです。唯一の例外は void への型変換だけで、これは可能です。
通常の式を void へ型変換すると得られた値は無効となります。

これまで void 式は暗黙のうちに何度も使用しています。

printf( "abcd" );
これは式であり、式だけから存在する式文です。 printf 関数指示子と関数呼び出し演算子によって printf 関数を呼び出しその戻り値が式の値です。ところがその値を使用していません。これは次の様に記述するのと全く同じ事です。

(void)( printf( "abcd" ) ); /* A /
(void)printf( "abcd" ); /
B */
関数呼び出し演算子は型変換演算子よりも優先順位が高い為 A も B も同じです。 printf 関数の戻り値が関数呼び出し演算子の式の値となるものの、 void への型変換により void 式となり、その結果は無効になります。

void 式は式の値が無効になるのであって零 (0) になる訳ではありません。ですから次の記述は出来ません。

a = 5 + (void)printf( "abcd" ); /* 制約違反 /
b = 2 + (void)( 1 * 2 ); /
同上 */
二項演算子 + を演算しようにも、第一項の値が存在するだけで第二項の値が存在しないからです。

void 型を持つ式の一つとして戻り値の型が void の関数があります。戻り値が存在しないのですから次の記述は当然出来ません。

void func( int a );
int main( void )
{
int a = 1 + func( 2 ); /* 制約違反 */
return ( 0 );
}

次の記述は void 式を void へと型変換しているので正しい事になります。

(void)1; /* 良い /
(void)( 1 + 2 ); /
良い /
(void)( a + b ); /
良い */

他には p++; の様な式文の式も void 式です。
void 式は式の値を無効にしますが式そのものは評価されます。

関係演算子と等価演算子

関係演算子や等価演算子は二項演算子で、それぞれの項の評価順序は不定です。
関係演算子や等価演算子は式が偽なら int 型の 0 、真なら int 型の 1 を返します。
では次の記述の演算過程を考えてください。

int a = 5;
int b = 6;
int c = 7;
int x;
x = a < b < c;
演算子の優先順位は = より < の方が上です。また < は左から右へと演算が進みます。 < は二項演算子ですから最初に a < b が評価されます。真なので 1 になります。
次に a < b の演算結果である 1 と c との < 演算が行われます。結果は真ですから 1 になります。
よって x は 1 になります。
a < b < c 全ての条件が真だから x が 1 になるのではありません。 ( a < b ) が真であり、且つ ( a < b ) < c 即ち 1 < c が真なので右辺の式の値が 1 になるのです。
もし数学的な a < b < c を実現したいのであれば解決方法の一つとして次の記述をします。

x = ( a < b ) && ( b < c );

sizeof 演算子の補足

sizeof 演算子の結果は size_t 型の整数定数です。定数なので配列等の初期化にも使用出来ます。

int a[] = { sizeof( char ), sizeof( int ) };
a[ 0 ] には 1 が、 a[ 1 ] には sizeof( int ) の値が入ります。

定数式

定数式は呼んで字の如く定数になる式の事です。式なので演算子を使用出来ます。しかし制約はあります。

定数式に使用出来ない演算子
アサインメント演算子
インクリメント演算子
デクリメント演算子
関数呼び出し演算子
コンマ演算子

規格では「但し sizeof 演算子の対象項に含まれる場合は除外する」とあります。以上の演算子と sizeof 演算子を組み合わせる利点は何であるか疑問です。そこで単純に「これらの演算子は定数式で使用出来ない」と覚えてしまっても良いでしょう。
定数式はその式の型で表せる範囲内の値になっている必要があります。
通常の定数式は実数定数式と整数定数式の二種類存在します。実数定数式はその名の通りで 1.23 + 4.56 等の様な実数定数による式であり、結果は実数の定数です。整数定数式にはその式に使用可能な項の種類が幾つかあります。

整数定数式
定数式の項の種別 例
整数定数 1
列挙定数 enum { zero, one };
one /* この式 */
文字定数 'a'
sizeof 演算子の結果 sizeof ( int )
型変換演算子の対象項となる実数定数 (int)1.23

整数定数式に於ける型変換演算子は、整数型或いは実数型を整数型へと変換する為にだけ使用します。但し sizeof 演算子の対象項はこの制約から除外します。
定数が記述可能な場所には実数定数式及び整数定数式のいずれも記述可能です。
初期化を記述する初期化子及び初期化子リスト中では制限が緩くなります。

初期化子で使用可能な定数式

種別 算術定数式
意味 実数定数式及び整数定数式
例 1.23 + 4.56

種別 ヌル・ポインター定数
意味 値が 0 の整数定数式、若しくはその式を void * 型に型変換した式
例 int * p = 0;

種別 アドレス定数
意味 静的記憶域期間のオブジェクトを示す左辺値へのポインター、又は関数指示子へのポインター
例 int f0( void );
int f1( void );
static char s[] = "abc";
char * ps[] = { s, s + 1 };
int (*f[])(void) = { f0, f1 };
オブジェクト型の
アドレス定数に対する加減算
オブジェクト型のアドレス定数に対し、
整数定数による加減算を行う式

アドレス定数にはアドレス演算子を用いても生成出来ます。その演算子の対象項はアドレス定数の意味を逸脱するものであってはいけません。

char s0[] = "abc";
static char s1[] = "xyz";
char * p0[] = { &s0[ 0 ], &s0[ 1 ] }; /* 不可 /
char * p1[] = { &s1[ 1 ], &s1[ 2 ] }; /
可 */

今回はここまでです
ではでは!

 - コンピュータサイエンス