C Language
演算子
サーチ…
前書き
プログラミング言語の演算子は、コンパイラまたはインタプリタに、特定の数学的、関係的または論理的演算を実行し、最終結果を生成するように指示するシンボルです。
Cには多くの強力な演算子があります。多くのC演算子は2項演算子です。つまり、2つのオペランドを持ちます。たとえば、 a / b
では、 /
は2つのオペランド( a
、 b
)を受け入れるバイナリ演算子です。 1つのオペランド(たとえば、 ~
、 ++
)を取る単項演算子と、1つの三項演算子しかないものがあり? :
。
構文
- expr1演算子
- 演算子expr2
- expr1演算子expr2
- expr1? expr2:expr3
備考
演算子は、 アリティ 、 優先順位 、および結合性を持っています 。
Arityはオペランドの数を示します。 Cでは、3つの異なる演算子が存在します。
- 単項(1オペランド)
- バイナリ(2オペランド)
- 3進(3オペランド)
優先順位は、どのオペレータが最初にオペランドに「バインド」するかを示します。すなわち、どのオペレータがそのオペランドを操作する優先順位を有するかである。例えば、C言語は、乗算と除算が加算と減算よりも優先されるという規則に従います。
a * b + c
同じ結果を与える
(a * b) + c
これが欲しかったものでない場合、すべての演算子の中で最も高い優先順位を持つため、カッコを使用して優先順位を強制することができます。
a * (b + c)
この新しい式は、前の2つの式とは異なる結果を生成します。
C言語には多くの優先レベルがあります。以下の表は、すべての演算子の優先順位の降順で示しています。
優先順位表
演算子 関連性 ()
[]
->
.
左から右へ !
~
++
--
+
-
*
(間接参照)(type)
sizeof
右から左へ *
(乗算)/
%
左から右へ +
-
左から右へ <<
>>
左から右へ <
<=
>
>=
左から右へ ==
!=
左から右へ &
左から右へ ^
左から右へ |
左から右へ &&
左から右へ ||
左から右へ ?:
右から左へ =
+=
-=
*=
/=
%=
&=
^=
|=
<<=
>>=
右から左へ ,
左から右へ Associativityは、優先順位の等しい演算子がデフォルトでどのようにバインドするかを示し、 左から右 、 右 から左の 2種類があります。 左から右へのバインディングの例は、減算演算子(
-
)です。表現a - b - c - d
3つの同一優先順位の減算がありますが、
((a - b) - c) - d
左端のために
-
その2つのオペランドに最初にバインドします。右から左への結合性の例は、逆参照
*
および後置増数++
演算子です。どちらも同じ優先順位を持っているので、* ptr ++
、これは
* (ptr ++)
一番右の単項演算子(
++
)が最初にその単一オペランドにバインドするためです。
関係演算子
関係演算子は、2つのオペランド間の特定の関係が真であるかどうかをチェックします。結果は1( 真を意味する)または0( 偽を意味する)と評価される。この結果は、( if
、 while
、 for
使用して)制御フローに影響を与えるためによく使用されますが、変数に格納することもできます。
"=="
指定されたオペランドが等しいかどうかをチェックします。
1 == 0; /* evaluates to 0. */
1 == 1; /* evaluates to 1. */
int x = 5;
int y = 5;
int *xptr = &x, *yptr = &y;
xptr == yptr; /* evaluates to 0, the operands hold different location addresses. */
*xptr == *yptr; /* evaluates to 1, the operands point at locations that hold the same value. */
注意:この演算子を代入演算子( =
)と混同しないでください!
"!="と等しくない
指定されたオペランドが等しくないかどうかをチェックします。
1 != 0; /* evaluates to 1. */
1 != 1; /* evaluates to 0. */
int x = 5;
int y = 5;
int *xptr = &x, *yptr = &y;
xptr != yptr; /* evaluates to 1, the operands hold different location addresses. */
*xptr != *yptr; /* evaluates to 0, the operands point at locations that hold the same value. */
この演算子は、等号( ==
)演算子の結果と実質的に逆の結果を返します。
"ではない!"
オブジェクトが0
かどうかを確認してください。
!
次のように変数に直接使用することもできます。
!someVal
これと同じ効果があります:
someVal == 0
">"より大きい
左手オペランドが右手オペランドより大きい値を持つかどうかをチェックします。
5 > 4 /* evaluates to 1. */
4 > 5 /* evaluates to 0. */
4 > 4 /* evaluates to 0. */
"<"より小さい
左手オペランドの値が右手オペランドの値よりも小さいかどうかをチェックします。
5 < 4 /* evaluates to 0. */
4 < 5 /* evaluates to 1. */
4 < 4 /* evaluates to 0. */
より大きいまたは等しい "> ="
左オペランドの値が右オペランドの値より大きいか等しいかをチェックします。
5 >= 4 /* evaluates to 1. */
4 >= 5 /* evaluates to 0. */
4 >= 4 /* evaluates to 1. */
より小さいか等しい "<="
左オペランドの値が右オペランドの値より小さいか等しいかをチェックします。
5 <= 4 /* evaluates to 0. */
4 <= 5 /* evaluates to 1. */
4 <= 4 /* evaluates to 1. */
代入演算子
右オペランドの値を左オペランドに指定された格納場所に割り当て、その値を返します。
int x = 5; /* Variable x holds the value 5. Returns 5. */
char y = 'c'; /* Variable y holds the value 99. Returns 99
* (as the character 'c' is represented in the ASCII table with 99).
*/
float z = 1.5; /* variable z holds the value 1.5. Returns 1.5. */
char const* s = "foo"; /* Variable s holds the address of the first character of the string 'foo'. */
いくつかの算術演算には、 複合代入演算子があります。
a += b /* equal to: a = a + b */
a -= b /* equal to: a = a - b */
a *= b /* equal to: a = a * b */
a /= b /* equal to: a = a / b */
a %= b /* equal to: a = a % b */
a &= b /* equal to: a = a & b */
a |= b /* equal to: a = a | b */
a ^= b /* equal to: a = a ^ b */
a <<= b /* equal to: a = a << b */
a >>= b /* equal to: a = a >> b */
これらの化合物の割り当ての重要な特徴の1つは、左側( a
)の式が1回だけ評価されることです。たとえば、 p
がポインタの場合
*p += 27;
逆参照p
は1回だけであるが、以下は2回繰り返す。
*p = *p + 27;
また、 a = b
ような代入の結果は、 rvalueとして知られているものであることにも留意すべきである。したがって、割り当てには実際には別の変数に割り当てることができる値があります。これにより、1つのステートメント内で複数の変数を設定するための割り当ての連鎖が可能になります。
この右辺値は、別の式や関数呼び出しの結果に対してあるコードを保護するif
文(またはループ文またはswitch
文)の制御式で使用できます。例えば:
char *buffer;
if ((buffer = malloc(1024)) != NULL)
{
/* do something with buffer */
free(buffer);
}
else
{
/* report allocation failure */
}
このため、不思議なバグにつながる可能性がある共通のタイプミスを避けるために注意を払わなければなりません。
int a = 2;
/* ... */
if (a = 1)
/* Delete all files on my hard drive */
a = 1
は常に1
と評価されるため、 if
ステートメントの制御式は常に真です(この共通の落とし穴についてはこちらを参照してください )。作者は、以下に示すように、等号演算子( ==
)を使用することをほぼ確実に意味していました。
int a = 2;
/* ... */
if (a == 1)
/* Delete all files on my hard drive */
オペレーター連合
int a, b = 1, c = 2;
a = b = c;
これは、割り当てc
するb
返す、 b
に割り当て以上です、 。 a
これは、すべての代入演算子が正しい連想性を持っているためです。つまり、式の右端の演算が最初に評価され、右から左に進みます。
算術演算子
基本算術
関連する数学演算を使用して、左辺オペランドを右辺オペランドに適用した結果の値を返します。転流の通常の数学的規則が適用されます(つまり、加算と乗算は可換、減算、除算、係数はありません)。
加算演算子
加算演算子( +
)は、2つのオペランドを一緒に加算するために使用されます。例:
#include <stdio.h>
int main(void)
{
int a = 5;
int b = 7;
int c = a + b; /* c now holds the value 12 */
printf("%d + %d = %d",a,b,c); /* will output "5 + 7 = 12" */
return 0;
}
減算演算子
減算演算子( -
)は、第1オペランドから第2オペランドを減算するために使用されます。例:
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 7;
int c = a - b; /* c now holds the value 3 */
printf("%d - %d = %d",a,b,c); /* will output "10 - 7 = 3" */
return 0;
}
乗算演算子
乗算演算子( *
)は、両方のオペランドを乗算するために使用されます。例:
#include <stdio.h>
int main(void)
{
int a = 5;
int b = 7;
int c = a * b; /* c now holds the value 35 */
printf("%d * %d = %d",a,b,c); /* will output "5 * 7 = 35" */
return 0;
}
*
間接参照演算子と混同しないでください。
事業者
除算演算子( /
)は、第1オペランドを第2オペランドで除算します。除算の両方のオペランドが整数の場合、整数値を返し、残りを破棄します(剰余を計算して取得するためにモジュロ演算子%
を使用します)。
オペランドの1つが浮動小数点値の場合、結果は小数の近似値になります。
例:
#include <stdio.h>
int main (void)
{
int a = 19 / 2 ; /* a holds value 9 */
int b = 18 / 2 ; /* b holds value 9 */
int c = 255 / 2; /* c holds value 127 */
int d = 44 / 4 ; /* d holds value 11 */
double e = 19 / 2.0 ; /* e holds value 9.5 */
double f = 18.0 / 2 ; /* f holds value 9.0 */
double g = 255 / 2.0; /* g holds value 127.5 */
double h = 45.0 / 4 ; /* h holds value 11.25 */
printf("19 / 2 = %d\n", a); /* Will output "19 / 2 = 9" */
printf("18 / 2 = %d\n", b); /* Will output "18 / 2 = 9" */
printf("255 / 2 = %d\n", c); /* Will output "255 / 2 = 127" */
printf("44 / 4 = %d\n", d); /* Will output "44 / 4 = 11" */
printf("19 / 2.0 = %g\n", e); /* Will output "19 / 2.0 = 9.5" */
printf("18.0 / 2 = %g\n", f); /* Will output "18.0 / 2 = 9" */
printf("255 / 2.0 = %g\n", g); /* Will output "255 / 2.0 = 127.5" */
printf("45.0 / 4 = %g\n", h); /* Will output "45.0 / 4 = 11.25" */
return 0;
}
モジュロ演算子
モジュロ演算子( %
)は整数オペランドのみを受け取り、第1オペランドを第2オペランドで除算した後の剰余を計算するために使用されます。例:
#include <stdio.h>
int main (void) {
int a = 25 % 2; /* a holds value 1 */
int b = 24 % 2; /* b holds value 0 */
int c = 155 % 5; /* c holds value 0 */
int d = 49 % 25; /* d holds value 24 */
printf("25 % 2 = %d\n", a); /* Will output "25 % 2 = 1" */
printf("24 % 2 = %d\n", b); /* Will output "24 % 2 = 0" */
printf("155 % 5 = %d\n", c); /* Will output "155 % 5 = 0" */
printf("49 % 25 = %d\n", d); /* Will output "49 % 25 = 24" */
return 0;
}
インクリメント/デクリメント演算子
インクリメント( a++
)演算子とデクリメント( a--
)演算子は、代入演算子なしでそれらに適用する変数の値を変更する点で異なります。インクリメント演算子とデクリメント演算子は、変数の前後に使用できます。オペレータの配置により、値の増分/減分のタイミングが変数に割り当てられる前または後に変更されます。例:
#include <stdio.h>
int main(void)
{
int a = 1;
int b = 4;
int c = 1;
int d = 4;
a++;
printf("a = %d\n",a); /* Will output "a = 2" */
b--;
printf("b = %d\n",b); /* Will output "b = 3" */
if (++c > 1) { /* c is incremented by 1 before being compared in the condition */
printf("This will print\n"); /* This is printed */
} else {
printf("This will never print\n"); /* This is not printed */
}
if (d-- < 4) { /* d is decremented after being compared */
printf("This will never print\n"); /* This is not printed */
} else {
printf("This will print\n"); /* This is printed */
}
}
c
とd
例が示すように、両方の演算子には接頭辞表記と後置記表の2つの形式があります。両方とも変数をインクリメント( ++
)またはデクリメント( --
)するのと同じ効果を持ちますが、戻り値によって異なります。プレフィックス操作では最初に操作を行い、次に値を返すのに対し、後置操作では最初に返され、次に操作を行います。
この潜在的に反直観的な振る舞いのために、式の中のインクリメント/デクリメント演算子の使用は議論の余地があります。
論理演算子
論理AND
両方のオペランドが非ゼロの場合、1を返す2つのオペランドの論理ブール論理和を実行します。論理AND演算子はint
型です。
0 && 0 /* Returns 0. */
0 && 1 /* Returns 0. */
2 && 0 /* Returns 0. */
2 && 3 /* Returns 1. */
論理OR
オペランドのいずれかが非ゼロの場合、1を返す2つのオペランドの論理ブール論理和を実行します。論理OR演算子はint
型です。
0 || 0 /* Returns 0. */
0 || 1 /* Returns 1. */
2 || 0 /* Returns 1. */
2 || 3 /* Returns 1. */
論理NOT
論理否定を実行します。論理NOT演算子はint
型です。 NOT演算子は、少なくとも1つのビットが1に等しいかどうかを調べ、0の場合は1を返します。
!1 /* Returns 0. */
!5 /* Returns 0. */
!0 /* Returns 1. */
短絡評価
&&
と||
両方に共通するいくつかの重大な特性があります。 :
- 右オペランド(RHS)が完全に評価される前に左オペランド(LHS)が完全に評価され、
- 左オペランドと右オペランドの評価の間にシーケンスポイントがあり、
- 最も重要なのは、左側のオペランドの結果が全体の結果を決定する場合、右側のオペランドは全く評価されないということです。
この意味は:
- LHSが「真」(非ゼロ)と評価された場合、
||
評価されません(「真のOR何か」の結果が「真」であるため) - LHSが 'false'(ゼロ)と評価された場合、
&&
のRHSは評価されません( 'false AND anything'の結果は 'false'なので)。
これは、次のようなコードを書くことができるので重要です。
const char *name_for_value(int value)
{
static const char *names[] = { "zero", "one", "two", "three", };
enum { NUM_NAMES = sizeof(names) / sizeof(names[0]) };
return (value >= 0 && value < NUM_NAMES) ? names[value] : "infinity";
}
負の値が関数に渡された場合、 value >= 0
項は偽と評価され、 value < NUM_NAMES
項は評価されません。
インクリメント/デクリメント
増分および減分演算子は接頭辞と接尾 辞の形式で存在します 。
int a = 1;
int b = 1;
int tmp = 0;
tmp = ++a; /* increments a by one, and returns new value; a == 2, tmp == 2 */
tmp = a++; /* increments a by one, but returns old value; a == 3, tmp == 2 */
tmp = --b; /* decrements b by one, and returns new value; b == 0, tmp == 0 */
tmp = b--; /* decrements b by one, but returns old value; b == -1, tmp == 0 */
算術演算はシーケンスポイントを導入しないので、 ++
演算子または--
演算子を使用する特定の式は未定義の動作を導入する可能性があることに注意してください。
条件付き演算子/ 3項演算子
最初のオペランドを評価し、結果の値がゼロでない場合は、2番目のオペランドを評価します。それ以外の場合は、次の例に示すように、3番目のオペランドを評価します。
a = b ? c : d;
次のものと同等です。
if (b)
a = c;
else
a = d;
この疑似コードはそれを表しcondition ? value_if_true : value_if_false
。各値は、評価された式の結果である可能性があります。
int x = 5;
int y = 42;
printf("%i, %i\n", 1 ? x : y, 0 ? x : y); /* Outputs "5, 42" */
条件付き演算子はネストできます。たとえば、次のコードは3つの数値のうち大きい方を決定します。
big= a > b ? (a > c ? a : c)
: (b > c ? b : c);
次の例では、整数を1つのファイルに、奇数の整数を別のファイルに書き込みます。
#include<stdio.h>
int main()
{
FILE *even, *odds;
int n = 10;
size_t k = 0;
even = fopen("even.txt", "w");
odds = fopen("odds.txt", "w");
for(k = 1; k < n + 1; k++)
{
k%2==0 ? fprintf(even, "\t%5d\n", k)
: fprintf(odds, "\t%5d\n", k);
}
fclose(even);
fclose(odds);
return 0;
}
条件演算子は、右から左に関連付けられます。次の点を考慮してください。
exp1 ? exp2 : exp3 ? exp4 : exp5
関連付けが右から左にあるので、上記の式は次のように評価されます。
exp1 ? exp2 : ( exp3 ? exp4 : exp5 )
コンマ演算子
その左のオペランドを評価し、結果の値を破棄し、次にその権利オペランドを評価し、結果は右端のオペランドの値をもたらす。
int x = 42, y = 42;
printf("%i\n", (x *= 2, y)); /* Outputs "42". */
コンマ演算子は、そのオペランドの間にシーケンスポイントを導入します。
別々の引数を持つ関数呼び出しで使用されるカンマは、 カンマ演算子ではなく、 カンマ演算子とは異なるセパレータと呼ばれます 。したがって、 カンマ演算子のプロパティはありません。
上記のprintf()
コールには、 カンマ演算子とセパレータの両方が含まれています 。
printf("%i\n", (x *= 2, y)); /* Outputs "42". */
/* ^ ^ this is a comma operator */
/* this is a separator */
コンマ演算子は、初期化セクションとfor
ループの更新セクションでよく使用されます。例えば:
for(k = 1; k < 10; printf("\%d\\n", k), k += 2); /*outputs the odd numbers below 9/*
/* outputs sum to first 9 natural numbers */
for(sumk = 1, k = 1; k < 10; k++, sumk += k)
printf("\%5d\%5d\\n", k, sumk);
キャストオペレータ
指定された式を評価した結果の値から、指定された型への明示的な変換を実行します。
int x = 3;
int y = 4;
printf("%f\n", (double)x / y); /* Outputs "0.750000". */
ここでx
の値はdouble
に変換され、 y
の値もdouble
に昇格され、除算の結果、 double
はprintf
に渡されて印刷されます。
演算子のサイズ
型をオペランドとして使用する
指定された型のオブジェクトのsize_t
型のサイズをバイト数で評価します。型の周りにかっこが必要です。
printf("%zu\n", sizeof(int)); /* Valid, outputs the size of an int object, which is platform-dependent. */
printf("%zu\n", sizeof int); /* Invalid, types as arguments need to be surrounded by parentheses! */
オペランドとしての式の場合
指定された式の型のオブジェクトのsize_t
型のサイズをバイト単位で評価します。式自体は評価されません。カッコは必須ではありません。ただし、与えられた式は単項式でなければならないため、常に使用することをお勧めします。
char ch = 'a';
printf("%zu\n", sizeof(ch)); /* Valid, will output the size of a char object, which is always 1 for all platforms. */
printf("%zu\n", sizeof ch); /* Valid, will output the size of a char object, which is always 1 for all platforms. */
ポインタ演算
ポインタの追加
与えられたポインタとスカラー型N
は、指し示されたオブジェクトの後にメモリ内で直接後続する指さし型のN
番目の要素へのポインタに評価されます。
int arr[] = {1, 2, 3, 4, 5};
printf("*(arr + 3) = %i\n", *(arr + 3)); /* Outputs "4", arr's fourth element. */
ポインタがオペランド値またはスカラー値として使用されるかどうかは関係ありません。これは、 3 + arr
などのものが有効であることを意味します。 arr[k]
が配列のk+1
メンバーの場合、 arr+k
はarr[k]
へのポインタです。言い換えれば、 arr
またはarr+0
はarr[0]
へのポインタであり、 arr+1
はarr[2]
へのポインタです。一般に、 *(arr+k)
はarr[k]
と同じです。
通常の算術とは異なり、 int
へのポインタに1
を加えると現在のアドレス値に4
バイトが追加されます。配列名は定数ポインタなので、 +
は配列名を使ってポインタ表記法で配列のメンバーにアクセスするために使用できる唯一の演算子です。しかし、配列へのポインタを定義することで、配列内のデータをより柔軟に処理することができます。たとえば、次のように配列のメンバーを出力できます。
#include<stdio.h>
static const size_t N = 5
int main()
{
size_t k = 0;
int arr[] = {1, 2, 3, 4, 5};
for(k = 0; k < N; k++)
{
printf("\n\t%d", *(arr + k));
}
return 0;
}
配列へのポインタを定義することにより、上記のプログラムは次のようになります:
#include<stdio.h>
static const size_t N = 5
int main()
{
size_t k = 0;
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr; /* or int *ptr = &arr[0]; */
for(k = 0; k < N; k++)
{
printf("\n\t%d", ptr[k]);
/* or printf("\n\t%d", *(ptr + k)); */
/* or printf("\n\t%d", *ptr++); */
}
return 0;
}
配列arr
メンバーには、演算子+
と++
を使用してアクセスすることができます。ポインタptr
使用できる他の演算子は-
と--
です。
ポインタ減算
同じ型への2つのポインタが与えられた場合、最初のポインタの値を取得するために2番目のポインタに追加する必要があるスカラー値を保持するptrdiff_t
型のオブジェクトを評価します。
int arr[] = {1, 2, 3, 4, 5};
int *p = &arr[2];
int *q = &arr[3];
ptrdiff_t diff = q - p;
printf("q - p = %ti\n", diff); /* Outputs "1". */
printf("*(p + (q - p)) = %d\n", *(p + diff)); /* Outputs "4". */
アクセス演算子
メンバアクセス演算子(ドット.
矢印は、 ->
)のメンバーにアクセスするために使用されるstruct
。
オブジェクトのメンバー
アクセスされたオブジェクトのメンバであるオブジェクトを示す左辺値を評価します。
struct MyStruct
{
int x;
int y;
};
struct MyStruct myObject;
myObject.x = 42;
myObject.y = 123;
printf(".x = %i, .y = %i\n", myObject.x, myObject.y); /* Outputs ".x = 42, .y = 123". */
pointed-toオブジェクトのメンバー
逆参照のための構文的砂糖とその後のメンバーアクセス。実際には、 x->y
という形式の式は(*x).y
- の略語ですが、特に構造体ポインタがネストされている場合は、矢印演算子がはっきりしています。
struct MyStruct
{
int x;
int y;
};
struct MyStruct myObject;
struct MyStruct *p = &myObject;
p->x = 42;
p->y = 123;
printf(".x = %i, .y = %i\n", p->x, p->y); /* Outputs ".x = 42, .y = 123". */
printf(".x = %i, .y = %i\n", myObject.x, myObject.y); /* Also outputs ".x = 42, .y = 123". */
住所
単項&
演算子は、演算子のアドレスです。与えられた式を評価します。結果のオブジェクトは左辺値でなければなりません。次に、型が結果のオブジェクトの型へのポインタであるオブジェクトに評価され、結果のオブジェクトのアドレスが格納されます。
int x = 3;
int *p = &x;
printf("%p = %p\n", (void *)&x, (void *)p); /* Outputs "A = A", for some implementation-defined A. */
相互参照
単項演算子*
、ポインタをデリファレンスします。与えられた式を評価した結果得られるポインタの逆参照から生じる左辺値を評価します。
int x = 42;
int *p = &x;
printf("x = %d, *p = %d\n", x, *p); /* Outputs "x = 42, *p = 42". */
*p = 123;
printf("x = %d, *p = %d\n", x, *p); /* Outputs "x = 123, *p = 123". */
インデックス作成
索引付けは、ポインタの追加とそれに続く逆参照の構文的な砂糖です。実際にはa[i]
という形式の式は*(a + i)
と同じですが、明示的な添字表記が優先されます。
int arr[] = { 1, 2, 3, 4, 5 };
printf("arr[2] = %i\n", arr[2]); /* Outputs "arr[2] = 3". */
インデックスの互換性
整数へのポインタの追加は可換的な操作です(つまり、オペランドの順序は結果を変更しません)。したがって、 pointer + integer == integer + pointer
です。
その結果、 arr[3]
と3[arr]
は同等です。
printf("3[arr] = %i\n", 3[arr]); /* Outputs "3[arr] = 4". */
式3[arr]
使用法arr[3]
代わりに3[arr]
はコードの可読性に影響するため、一般的にはお勧めできません。難読化されたプログラミングコンテストでは人気が高い傾向があります。
関数呼び出し演算子
第1オペランドは関数ポインタでなければなりません(関数へのポインタに変換されるため、関数指定子も許容されます)。呼び出し対象の関数を識別し、他のすべてのオペランドがある場合はそれらを総称して関数呼び出しの引数。それぞれの引数で適切な関数を呼び出した結果の戻り値を評価します。
int myFunction(int x, int y)
{
return x * 2 + y;
}
int (*fn)(int, int) = &myFunction;
int x = 42;
int y = 123;
printf("(*fn)(%i, %i) = %i\n", x, y, (*fn)(x, y)); /* Outputs "fn(42, 123) = 207". */
printf("fn(%i, %i) = %i\n", x, y, fn(x, y)); /* Another form: you don't need to dereference explicitly */
ビット演算子
ビット単位の演算子を使用して、変数に対してビットレベルの演算を実行できます。
以下は、C言語でサポートされている6つのビット演算子の一覧です。
シンボル | オペレーター |
---|---|
そして、 | ビット単位AND |
| | ビット単位OR |
^ | ビット単位排他的論理和(XOR) |
〜 | ビット単位ではない(1の補数) |
<< | 論理左シフト |
>> | 論理右シフト |
次のプログラムは、すべてのビット演算子の使用方法を示しています。
#include <stdio.h>
int main(void)
{
unsigned int a = 29; /* 29 = 0001 1101 */
unsigned int b = 48; /* 48 = 0011 0000 */
int c = 0;
c = a & b; /* 32 = 0001 0000 */
printf("%d & %d = %d\n", a, b, c );
c = a | b; /* 61 = 0011 1101 */
printf("%d | %d = %d\n", a, b, c );
c = a ^ b; /* 45 = 0010 1101 */
printf("%d ^ %d = %d\n", a, b, c );
c = ~a; /* -30 = 1110 0010 */
printf("~%d = %d\n", a, c );
c = a << 2; /* 116 = 0111 0100 */
printf("%d << 2 = %d\n", a, c );
c = a >> 2; /* 7 = 0000 0111 */
printf("%d >> 2 = %d\n", a, c );
return 0;
}
このようなビット表現の符号ビットは特定の意味を持つため、符号付きの型を使用したビット演算は避ける必要があります。シフト演算子には特定の制限が適用されます。
1ビットを符号付きビットに左シフトすることは誤りであり、未定義の動作につながる。
負の値(符号ビット1)を右シフトすることは実装定義であるため、移植性がありません。
シフト演算子の右オペランドの値が負であるか、または昇格された左オペランドの幅以上である場合、その動作は未定義です。
マスキング:
マスキングとは、論理ビット単位演算を使用して変数から所望のビットを抽出する(または、所望のビットを変換する)プロセスを指す。マスキングの実行に使用されるオペランド(定数または変数)は、 マスクと呼ばれます。
マスキングはさまざまな方法で使用されます。
- 整数変数のビットパターンを決定する。
- 与えられたビットパターンの一部を新しい変数にコピーし、新しい変数の残りの部分を0で埋めます(ビット単位のANDを使用します)
- 与えられたビットパターンの一部を新しい変数にコピーする一方、新しい変数の残りは1で埋められます(ビット単位のORを使用します)。
- 与えられたビットパターンの一部を新しい変数にコピーし、元のビットパターンの残りの部分を新しい変数内で反転させます(ビット単位の排他的論理和を使用します)。
次の関数は、マスクを使用して変数のビットパターンを表示します。
#include <limits.h>
void bit_pattern(int u)
{
int i, x, word;
unsigned mask = 1;
word = CHAR_BIT * sizeof(int);
mask = mask << (word - 1); /* shift 1 to the leftmost position */
for(i = 1; i <= word; i++)
{
x = (u & mask) ? 1 : 0; /* identify the bit */
printf("%d", x); /* print bit value */
mask >>= 1; /* shift mask to the right by 1 bit */
}
}
_Alignof
指定された型の配置要件を照会します。整列要件は、タイプの2つのオブジェクトを割り当てることができるバイト数を表す2の正の整数累乗である。 Cでは、整列要件はsize_t
で測定されsize_t
。
型名は、不完全型でも関数型でもない場合があります。配列が型として使用されている場合、配列要素の型が使用されます。
この演算子は、 <stdalign.h>
便利なマクロのalignof
によってアクセスされることがよくあります。
int main(void)
{
printf("Alignment of char = %zu\n", alignof(char));
printf("Alignment of max_align_t = %zu\n", alignof(max_align_t));
printf("alignof(float[10]) = %zu\n", alignof(float[10]));
printf("alignof(struct{char c; int n;}) = %zu\n",
alignof(struct {char c; int n;}));
}
可能な出力:
Alignment of char = 1
Alignment of max_align_t = 16
alignof(float[10]) = 4
alignof(struct{char c; int n;}) = 4
論理演算子の短絡動作
短絡は、可能な場合に(if / while / ...)条件の部分をスキップする機能です。 2つのオペランドの論理演算の場合は、最初のオペランドが評価され(真または偽)、判定がある場合(&&を使用すると最初のオペランドが偽の場合、||を使用すると最初のオペランドは真です)、2番目のオペランドは評価されていない。
例:
#include <stdio.h>
int main(void) {
int a = 20;
int b = -5;
/* here 'b == -5' is not evaluated,
since a 'a != 20' is false. */
if (a != 20 && b == -5) {
printf("I won't be printed!\n");
}
return 0;
}
あなた自身でそれをチェックしてください:
#include <stdio.h>
int print(int i) {
printf("print function %d\n", i);
return i;
}
int main(void) {
int a = 20;
/* here 'print(a)' is not called,
since a 'a != 20' is false. */
if (a != 20 && print(a)) {
printf("I won't be printed!\n");
}
/* here 'print(a)' is called,
since a 'a == 20' is true. */
if (a == 20 && print(a)) {
printf("I will be printed!\n");
}
return 0;
}
出力:
$ ./a.out
print function 20
I will be printed!
短絡は、(計算上)コストが高い条件の評価を避けたい場合に重要です。さらに、この場合のようにプログラムの流れに大きく影響することがあります: なぜこのプログラムは "forked!" 4回?