情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第4章 演算子

  1. 4.1 アドレス演算子と間接演算子
    1. (プログラム例 4.1 ) ポインタを利用した 2 つのデータの和
  2. 4.2 関係演算子,等値演算子,及び,論理演算子
    1. (プログラム例 4.2 ) 関係式と論理式
  3. 4.3 ビット演算子とシフト演算子
    1. (プログラム例 4.3 ) ビット演算とシフト演算
  4. 4.4 その他の演算子
  5. 演習問題4

  この章では,第3章で説明しなかった C/C++ の演算子について説明します.以下に述べる演算子においても,算術演算子と同様,演算の実行は,優先順位の高い演算子から,また,左から順に行われるのが基本です.しかし,あまり演算子の優先順位に頼り複雑な式を書くと,非常に読みにくくなります.できる限り,カッコを付けて書くことを勧めます.

4.1 アドレス演算子と間接演算子

  変数は,主記憶領域のある特定の場所に付けられた名前です.その場所(アドレス)を求めるために使用されるのがアドレス演算子です.アドレス演算子は「 & 」で表し,例えば,変数 value のアドレスを求めるためには,
&value		
と書きます.このアドレスをデータとして保存しておくためには,ポインタpointer )という特別な領域が必要です.この領域に付ける名前,つまり,ポインタ変数を定義するためには,例えば,int 型変数に対するポインタは,
int *point;   // int* point; でも可		
と宣言します.変数 value が int 型であったとすると,
point = &value;		
という文により,変数 value のアドレスがポインタ変数 point に代入されます.図 4.1 は,この関係を表しています.ポインタにおいても,宣言しただけではそこに何が記憶されているかは不明です.ポインタに何も記憶されていない(ヌルポインタである)ことを明確にするためには,数値 0 や NULL マクロを使用します.しかし,C++ においては,nullptr(C++11) キーワードを使用することが推奨されています.
point = NULL;   // point = 0; でも良い
point = nullptr;   // C++ の場合		

  先の章で説明した,
scanf("%d", &a);		
という記述における「 & 」もアドレス演算子です.つまり,「変数 a のアドレスが指す場所(結局は,変数 a)にデータを入力する」ということを意味しています.scanf におけるアドレス演算子の必要性については,後ほど( 7.3.1 節)説明します.

  アドレスを表現するのに必要な領域の大きさは,対象の変数の型が異なっても同じです.しかし,どのような型の変数を指すポインタ変数かによって,宣言方法を変えてやる必要があります.例えば,char 型や double 型に対しては,以下のように宣言します.
char *pc;   // char* pc; でも可
double *pd;   // double* pd; でも可		
なぜ,このようなことが必要かについては,プログラム例 4.1 や第 6 章を参照して下さい.

  ポインタ変数について,その変数に記憶されている値(図 4.1 では,20 )ではなく,その値が指す場所に記憶されている値(図 4.1 では,1234 )を参照したい場合があります.このようなとき使用されるのが間接演算子です.間接演算子は「 * 」で表し,例えば,ポインタ変数 point が示すアドレスに入っている内容を変数 data に代入するためには,
data = *point;		
と書きます.図 4.1 の例では,変数 data に 1234 が記憶されます.また,逆に,ポインタ変数が指すアドレスに値を代入することもできます.
*point = 100;		
と記述することにより,変数 value に 100 が代入されます.以下に示すプログラムでは,「 x = y 」( 変数 x に,変数 y に記憶されている値のコピーを代入)の操作をポインタを介して行っています.
double x, y = 10, *yp = &y;
x = *yp;		
  ここで,演習問題をやってみます.以下のプログラムに於いて,「 ? 」にはどのような単語が入るでしょうか.また,変数 y の値はいくつになるでしょうか.
double x = 100, y;
? *xp;
xp = &x;
y  = *xp;		

(プログラム例 4.1 ) ポインタを利用した 2 つのデータの和 ( A ~ C の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

  このプログラムでは,ポインタと間接演算子を使用して,2 つの値の和を出力しています.また,同時に,ポインタが指す値も出力しています.

(プログラム)
/**************************************/
/* ポインタを使用した2つのデータの和 */
/*      coded by Y.Suganuma           */
/**************************************/
#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int *ap, *bp;
/*
     アドレスの設定
*/
	ap = &a;
	bp = &b;
/*
     アドレスと計算結果の出力
*/
	printf("和は=%d アドレス(a=%08x b=%08x)\n", *ap+*bp, ap, bp);
	a++;
	bp++;
	printf("         アドレス(a=%08x b=%08x)\n", ap, bp);
	printf("bpの指す場所(a)に記憶されている値は=%d\n", *bp);

	return 0;
}
			
9 ~ 10 行目

  int 型変数 a,及び,b に対する型宣言と共に,初期値として,10,及び,20 を代入しています.

11 行目

  int 型のポインタ変数 ap,及び,bp に対する型宣言を行っています.

15 ~ 16 行目

  変数 a,及び,b のアドレスをポインタ変数 ap,及び,bp に代入しています.

20 行目

  変数 a と b の内容を加えた結果( *ap + *bp )を出力(%d)すると共に,各ポインタ変数の内容を 16 進表現で出力( %08x )で出力しています.この例のように,出力リストの中に,式を書いても構いません.

21 ~ 22 行目

  変数 a の値とポインタ変数 bp の内容を 1 だけ増加させています.

  このプログラムを実行させると,例えば,次に示すような結果が出力されます.
和は=30  アドレス(a=00051ca4 b=00051ca0)
      アドレス(a=00051ca4 b=00051ca4)
bpの指す場所(a)に記憶されている値は=11			
まず,和が正しく出力されていることが分かると思います.また,同じ行のアドレスの項を見ると,変数 a のアドレスは 00051ca4,変数 b のアドレスは 00051ca0 となっています.このプログラムを実行したコンパイラでは,int 型として 4 バイト確保されますので,2 つの変数が連続した領域に存在することが分かります.この状態を図示すると,以下のようになります.

  22 行目でポインタ変数 bp の値を 1 だけ増加させていますので,bp の値は 00051ca1 となるはずです.しかし,出力結果の 2 行目を見て下さい.bp の値は 00051ca4 となっています.これが,ポインタ変数に対しても,変数の型によって異なる宣言を行う必要がある理由です.bp は,int 型に対するポインタ変数ですので,1 だけ増加させると,int 型の変数の領域分( 4 バイト)だけ増加します.従って,例えば,double 型の場合は,8 バイト増加することになります.

  その結果,この例では,bp が変数 a の領域を指すことになり,21 行目で変数 a の値を 1 だけ増加させていますので(通常の変数の場合は,文字通り 1 だけ増加),bp が指す領域に記憶されている値を出力すれば 11 になっているはずです.実際,出力結果の 3 行目からも明らかなように,予想通りの結果が得られています.この状態を図示すると,以下のようになります.

4.2 関係演算子,等値演算子,及び,論理演算子

  関係演算子等値演算子は,2 つの数値を比較し,その結果は真または偽となります.C/C++ の場合,真の場合は int 型の 0 以外に,また,偽の場合は 0 となります.一般に,数値の 0 は偽,また,それ以外の数値は真とみなされます.C/C++ で使用できる関係演算子には,以下のようなものがあります.
>  より大きい  a > b   式 a の値が式 b の値より大きいとき真
<  より小さい  a < b   式 a の値が式 b の値より小さいとき真
>= 以上     a >= b  式 a の値が式 b の値以上のとき真
<= 以下     a <= b  式 a の値が式 b の値以下のとき真		
また,等値演算子には,以下のようなものがあります.
== 等しい    a == b  式 a の値と式 b の値が等しいとき真
!= 等しくない  a != b  式 a の値と式 b の値が等しくないとき真		
  論理演算子は,論理演算(論理和,論理積,否定)を行うための演算子であり,以下のようなものがあります.
|| 論理和  x || y  式 x が真か,または,式 y が真のとき真
&& 論理積  x && y  式 x が真で,かつ,式 y が真のとき真
!  否定    ! x      式 x が偽のとき真		
  よく犯す誤りに以下のようなものがあります.例えば,x の値が 0 と 5 の間にあるか(つまり,数学的に書けば,0 < x < 5 )否かを調べたいとします.このとき,数学と全く同じように,
0 < x < 5		
と書いてしまう人を多く見受けます.この記述は文法的には正しいのですが,目的とする判断を行ってくれないという意味で誤りです.「演算子は,前から順番に実行される」ということを思い出してください.上のように記述すると,まず,「 0 < x 」の演算が行われます.次に,「その結果 < 5 」の演算が行われます.例えば,x の値が -1 である場合について考えてみます.「 0 < x 」の演算により結果は偽( 0 )になります.次に,「 0 < 5 」の演算が行われることになります.従って,最終的な結果は真になってしまいます.C/C++ において,目的とする判断を行わせるためには,
(0 < x) && (x < 5)		
と記述してやる必要があります(カッコは必ずしも必要ない).

(プログラム例 4.2 ) 関係式と論理式 ( A ~ C の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

(プログラム)
/****************************/
/* 関係式と論理式           */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int x;
/*
     データの入力
*/
	printf("a %d   b %d\n", a, b);
	printf("整数データを1つ入力して下さい ");
	scanf("%d",&x);
/*
     様々な関係式と論理式
*/
	printf("a < -1 < b ? %d\n", a < -1 < b);
	printf("a < x < b ? %d\n", (a < x) && (x < b));
	printf("x < a or x > b ? %d\n", (x < a) || (x > b));
	printf("~(a + b + x) ? %d\n", !(a + b + x));
	printf("a = b ? %d\n", a == b);
	printf("a ≠ b ? %d\n", a != b);

	return 0;
}
			
9 ~ 11 行目

  int 型変数 a,b,及び,x に対する型宣言と共に,初期値として,変数 a,b に,10,及び,20 を代入しています.

21 ~ 26 行目

  printf の「"」の中に記述した関係式・論理式の出力をしています.21 行目では,上で説明したとおり,真( 1 )が出力されます.目的とする結果を得るためには,22 行目のように記述する必要があります.24 行目は,通常の論理式として見ると奇妙な式ですが,C/C++ では,論理型も一種の整数であるため( 0 は偽,それ以外はすべて真),このような式も意味を持ってきます(出力結果参照).また,25 行目にあるように,等しいか否かを調べるのは,「==」であることに注意して下さい.「=」と書いてしまう場合がよく見受けられます.

  入力データとして,15 を与えると,このプログラムは以下の結果を出力します.
a < -1 < b ? 1
a < x < b ? 1
x < a or x > b ? 0
~(a + b + x) ? 0
a = b ? 0
a ≠ b ? 1			

---------------------(C++11)関係式と論理式-------------------------

  以下に示すプログラムに示すように,C++11 以降においては,&&,||,! の代わりに,andornot を使用可能です.また,23 行目に対しては,コンパイラから警告メッセージが出力されます.
01	/****************************/
02	/* 関係式と論理式           */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include <iostream>
06
07	using namespace std;
08	
09	int main()
10	{
11		int a = 10;
12		int b = 20;
13		int x;
14	/*
15	     データの入力
16	*/
17		cout << "a " << a << "   b " << b << endl;
18		cout << "整数データを1つ入力して下さい ";
19		cin >> x;
20	/*
21	     様々な関係式と論理式
22	*/
23		cout << boolalpha << "a < -1 < b ? " << (a < -1 < b) << endl;   // 警告が出力
24		cout << "a < x < b ? " << ((a < x) and (x < b)) << endl;
25		cout << "x < a or x > b ? " << ((x < a) or (x > b)) << endl;
26		cout << "~(a + b + x) ? " << not(a + b + x) << endl;
27		cout << "a = b ? " << (a == b) << endl;
28		cout << "a ≠ b ? " << (a != b) << endl;
29	
30		return 0;
31	}
		
(出力)
a 10   b 20
整数データを1つ入力して下さい 15
a < -1 < b ? true
a < x < b ? true
x < a or x > b ? false
~(a + b + x) ? false
a = b ? false
a ≠ b ? true		

----------------------(C++11)関係式と論理式終わり--------------------

4.3 ビット演算子とシフト演算子

  ビット演算子は,ビット毎の論理演算(論理和,論理積,排他的論理和等)を行うための演算子です.演算子で結合されたデータの対応するビット同士で(単項演算子の場合はデータそのものに対して)与えられた演算を行います.以下に示すような演算子を使用できます.
| 論理和      x | y   対応するビットのいずれかが 1 のとき 1,そうでないときは 0
& 論理積      x & y   対応するビットの双方が 1 のとき 1,そうでないときは 0
^ 排他的論理和 x ^ y   対応するビットが異なるのとき 1,そうでないときは 0
~ 1の補数     ~ x      ビット毎に 1 と 0 を反転する		
  論理和は特定のビットを 1 に設定,また,論理積はあるビット列だけを取り出す等のために使用されます.排他的論理和は,桁上がりを無視した 1 ビット同士の加算に相当します.

  シフト演算子は,整数型または文字型のデータを表すビット列を指定された数だけ左,または,右にシフトするための演算子であり,次の 2 種類が存在します.
<< 左にシフト  x << 3  3 ビット左にシフト.x を 23 倍することに相当.
>> 右にシフト  x >> 3  3 ビット右にシフト.x を 23 で割ることに相当.		
  左にシフトする場合は,下に示すように,指定された数だけビットをシフトした後,空いた場所に 0 が補充されます.
11001111  →  3 ビット左にシフト  →  01111000		
  しかし,右にシフトする場合は気を付ける必要があります.整数型や文字型には,通常,int 型と unsigned int 型のように,符号付きと符号なしの型があります.以下に示すように,符号付きの場合は,最も左のビット(最上位ビット)は変化せず,また,空いた場所には最上位ビットと同じビットが補充されます.符号なしの場合は,左シフトと同様,すべてのビットがシフトされ,空いた場所に 0 が補充されます.
(1)符号付き  11001111  →  3 ビット右にシフト  →  11111001
(2)符号なし  11001111  →  3 ビット右にシフト  →  00011001		

(プログラム例 4.3 ) ビット演算とシフト演算 ビット演算子とシフト演算子 ( A ~ H の部分を可能な限り一つの変数,定数,演算子等で,埋めてください.その際,文字を削除してから,正しい答えを半角文字で,かつ,余分なスペースを入れないで入力してください.)

(プログラム)
/******************************/
/* ビット演算子とシフト演算子 */
/*      coded by Y.Suganuma   */
/******************************/
#include <stdio.h>

int main()
{
	int x1 = 0x00000031;
	int x2 = 0xfffffff2;
/*
		 ビット演算子
*/
	int y1 = x1 & 0x0000000f;   /* 下位4ビットの取り出し */
	int y2 = x2 & 0x0000000f;   /* 下位4ビットの取り出し */
	int z1 = y1 ^ y2;   /* 排他的論理和(和になっている) */
	int z2 = ~z1 + 1;   /* -3に対する2の補数表現 */
	printf("ビット演算子\n");
	printf("   y1 %08x(%d) y2 %08x(%d)\n", y1, y1, y2, y2);
	printf("   z1 %08x(%d) z2 %08x(%d)\n", z1, z1, z2, z2);
/*
		 シフト演算子
*/
	int a = -3;
	unsigned int b  = (unsigned)a;
	y1 = a << 3;   /* 左に3ビットシフト(8倍,符号付き) */
	unsigned int w1 = b << 3;   /* 左に3ビットシフト(8倍,符号なし) */
	printf("シフト演算子\n");
	printf("   a %08x(%d) y1 %08x(%d) w1 %08x(%d)\n", a, a, y1, y1, w1, w1);
	y2 = y1 >> 3;   /* 右に3ビットシフト(1/8倍,符号付き) */
	unsigned int w2 = w1 >> 3;   /* 右に3ビットシフト(1/8倍,符号なし) */
	printf("   y2 %08x(%d) w2 %08x(%d)\n", y2, y2, w2, w2);

	return 0;
}
			
  このプログラムは以下の結果を出力します.ビット演算子とシフト演算子の働きを十分理解して下さい.
ビット演算子
   y1 00000001(1) y2 00000002(2)
   z1 00000003(3) z2 fffffffd(-3)
シフト演算子
   a fffffffd(-3) y1 ffffffe8(-24) w1 ffffffe8(-24)
   y2 fffffffd(-3) w2 1ffffffd(536870909)		

---------------------(C++11)ビット演算子-------------------------

  以下に示すプログラムに示すように,C++11 以降においては,&,|,^,~ の代わりに,bitandbitorxorcompl を使用可能です.また,N ビットのビット集合を表す bitset クラスを利用することも可能です
/****************************/
/* ビット演算子             */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
/*
		 ビット演算子
*/
	int x1 = 0x00000031;
	int x2 = 0xfffffff2;
	int y1 = x1 bitand 0x0000000f;     /* 下位4ビットの取り出し */
	int y2 = x2 bitand 0x0000000f;     /* 下位4ビットの取り出し */
	int z1 = y1 xor y2;             /* 排他的論理和(和になっている) */
//	int z1 = y1 bitor y2;             /* この場合は,論理和でも可能 */
	int z2 = compl(z1) + 1;             /* -3に対する2の補数表現 */
	printf("ビット演算子\n");
	printf("   y1 %08x(%d) y2 %08x(%d)\n", y1, y1, y2, y2);
	printf("   z1 %08x(%d) z2 %08x(%d)\n", z1, z1, z2, z2);

	return 0;
}
		
(出力)
ビット演算子
   y1 00000001(1) y2 00000002(2)
   z1 00000003(3) z2 fffffffd(-3)		

----------------------(C++11)ビット演算子終わり--------------------

4.4 その他の演算子

  3.2.2 節で説明した以外にも,ある変数に演算した結果をその変数に記憶する演算子を,「<<」,「>>」,「&」,「|」,及び,「^」などに対しても,使用できます.
<<=  x <<= 3  x を 3 ビット左にシフトして,その結果を x に代入
>>=  x >>= 3  x を 3 ビット右にシフトして,その結果を x に代入
&=(and_eq(C++11))   x &= y   x と y のビット毎の論理積を計算し,その結果を x に代入
|=(or_eq(C++11))   x |= y   x と y のビット毎の論理和を計算し,その結果を x に代入
^=(xor_eq(C++11))   x ^= y   x と y のビット毎の排他的論理和を計算し,その結果を x に代入		
  1 つの式しか書けないような場所に,複数の式を書きたいような場合が存在します.そのような場合に利用されるのが順次演算子カンマ演算子)です.例えば,
x = 0, y = 0, z = 10;		
と書くと,これらは 1 つの式とみなされ,3 つの代入文が順次実行されます.ただ,この演算子を多用すると,プログラムが読み難くなる可能性があります.やむを得ない場合以外,使用は避けた方が良いと思います.

  例えば,「もし,y の値が 5 より大きければ,x に 10 を代入し,そうでなければ,20 を代入する」といった処理を行いたいとします.このようなとき,利用されるのが条件演算子です.条件演算子は,一般的に,
論理式 ? 式1 : 式2		
と書かれ,論理式が真のときは式1の値がとられ,そうでなければ式2の値がとられます.例えば,先の例の場合は,
x = (y > 5) ? 10 : 20;		
と書けば良いことになります.

  あるデータまたはデータ型の記憶領域の大きさを知りたい場合があります.例えば,int 型の場合,通常は,4 バイトですが,16 ビットのパソコンの場合は 2 バイトになります.もし,その大きさとして具体的な数字,例えば 4 をそのまま記述した場合,そのプログラムは int 型が 4 バイトでないコンパイラでコンパイルした場合動作しなくなる可能性があります.サイズオブ演算子sizeof 演算子)は,演算結果として,型の大きさをバイト単位で返します.例えば,
sizeof(int)		
と書けば,そのコンパイラに適した int 型のサイズを返し,異なるコンピュータ等でも正常に動作する可能性が出てきます.sizeof 演算子は,単純変数,配列(後述),構造体(後述)等にも利用でき,その演算結果は,確保された領域全体の大きさになります.

演習問題4

[問1]次の演算結果がどのようになるかを見るための適当なプログラムを書き,その実行結果から演算結果について説明せよ.
(1)x=(a==10)
(2)(c >='a') && (c<='z')

[問2]プログラム例 4.1 と同様の処理を double 型,及び,char 型変数に対して行うプログラムを書け.

[問3]次の演算結果がどのようになるかを見るための適当なプログラムを書き,その実行結果から演算結果について説明せよ.
(1)*(point++)
(2)**point

[問4]整数データの上位 4 ビットと下位 4 ビットを,それぞれ 2 進数 4 ビットの正の整数とみなし,それらを加えて結果を出力するプログラムを書け.

[問5]すべてのビットを反転した後,最上位ビットに 1 をセットするプログラムを,排他的論理和と論理和を使用して書け.

[問6]整数データを 9 倍する演算を,ビットシフトと加算を利用して行うプログラムを書け.

情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引