Figure 4‑6 for 文のbreakとcontinue
Figure 4‑8 do〜while文のbreakとcontinue
Figure 8‑3 関数の引数として変数の値を直接渡す方法
Figure 8‑4 関数の引数として変数のアドレス(pointer)を渡す方法
この章では、C言語でプログラムを書くときに必要な基本的な事項について説明します。
この節では、C言語で使用できる文字の種類ついて説明します。
C言語で使用できる文字には、英文字(大・小)、数字、空白文字、特殊文字の4種類があります。
(1) 英大小文字(52種類)
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
A b c d e f g h i j k l m n o p q r s t u v w x y z
(2) 数字(10種類)
0 1 2 3 4 5 6 7 8 9
(3) 空白文字(3種類)
スペース、タブ、改行
(4) 特殊文字(30種類)
|
, |
コンマ |
. |
ピリオド |
|
; |
セミコロン |
: |
コロン |
|
? |
クウェッションマーク |
! |
びっくりマーク |
|
“…” |
ダブルクオート |
‘…’ |
シングルクオート |
|
( |
左カッコ |
) |
右カッコ |
|
{ |
左大カッコ |
} |
右大カッコ |
|
[ |
左中カッコ |
] |
右中カッコ |
|
< |
角カッコ |
> |
角カッコ |
|
/ |
スラッシュ |
\ |
バックスラッシュ |
|
| |
縦線 |
~ |
ティルド |
|
+ |
プラス記号 |
- |
マイナス記号 |
|
# |
シャープ |
% |
パーセント記号 |
|
_ |
アンダースコア |
^ |
カレット |
|
& |
アンパーサンド |
* |
アスタリクス |
|
= |
イコール記号 |
` |
アクセント |
ここでは、変数名と変数名の区切りや文の区切りを示すために使用される区切り記号について説明します。C言語では、空白文字や演算子などが区切り記号となります。
スペース、タブなどの空白文字は区切り記号となります。
(i) abc口xyz
abcとxyzに分けられる
区切り記号はスペース(口)
(ii) name [TAB] test
nameとtestに分けられる
区切り記号はタブ [TAB]
本書では、タブ記号を [TAB]、スペースを □ と書きます。スペースの場合は単に空けて書きますが、プログラムを説明する上で重要な意味をもつようなとき、また注意が必要となるときにのみ □ と書くことにします。
演算子も区切り記号になります。C言語の演算子には、加算や減算などの算術演算子、2つの数の大小関係などを調べる論理演算子などがあります。これらの演算子については、第3章で詳しく説明します。ここでは、いろいろな演算子も区切り記号になることを理解してください。
(i) abc + xyz
abcとxyzに分けられる
区切り記号は、プラス演算子(+)
(ii) name > test
nameとtestに分けられる
区切り記号は、比較演算子(>)
C言語では、以下に示す記号も区切り記号となります。
|
記号 |
意味 |
|
;(セミコロン) :(コロン) {}(大カッコ) ()(カッコ) [](中カッコ) 〈〉(角カッコ) ‘’(シングルクォーテーション) “”(ダブルクォーテーション) ¥(バックスラッシュ) %(パーセント) |
1つの実行文 ラベル 複文 関数の引数 配列の要素数 #include文のファイル指定 1文字 文字列 制御文字 出力書式指定文字 |
ここでは、変数や関数に名前をつけるときの規則について説明します。
(1) 変数や関数の名前に使用できる文字は、英文字(大・小)、数字、アンダースコア(_)の計63文字です。これらの文字を自由に組み合わせて名前をつけることになります。ただし、最初の文字は英文字またはアンダースコアでなければなりません。
〈正しい名前〉
name test_1_abc
〈間違った名前〉
123nametest−1*test
(2) 名前はいくら長いものをつけてもかまいませんが、通常有効な文字数8文字目までとなります。ただし、アンダースコア1文字だけの名前はつけることはできません。
(i) nametest_1234567
(ii) nametest_abc
(i)と(ii)は、おなじ名前(nametest)となります
(3) C言語では、英大文字と英小文字は明確に区別されます。通常、C言語では英小文字を使ってプログラムを書きます。英大文字は特殊な意味に使うことが多いようです。
(i) name
(ii) NAME
(i)と(ii)は、異なる名前となります
(4) C言語では、他のプログラミング言語と同様に、あらかじめその使い方が決められている名前があります。これらを予約語(キーワード)といいます。この予約語と同じ名前を変数名や関数名に使用することはできません。通常、予約語は28個あり、それらを機能別に示したのが表1.1です。なお、この予約語は Cコンパイラによって多少異なりますので、使用するコンパイラのマニュアルで確認してください。
|
表 1.1 C言語の予約語 |
||
|
|
予約語 |
意味 |
|
データの型 と 記憶クラス
|
auto |
自動変数の宣言 |
|
char |
文字型変数の宣言 |
|
|
double |
倍精度浮動小数点型変数の宣言 |
|
|
extern |
外部変数の宣言 |
|
|
float |
浮動小数点型変数の宣言 |
|
|
int |
整数型の宣言 |
|
|
long |
倍精度整数型の宣言 |
|
|
register |
レジスタ変数の宣言 |
|
|
short |
短い整数型の宣言 |
|
|
static |
静的変数の宣言 |
|
|
struct |
構造体の宣言 |
|
|
typedef |
型の定義 |
|
|
union |
共用体の宣言 |
|
|
unsigned |
符号なし整数の宣言 |
|
|
制御
|
break |
ループからの飛びだし |
|
case |
switch〜caseの定義 |
|
|
continue |
次の繰り返しへ戻る |
|
|
default |
switch〜defaultの定義 |
|
|
do |
doループ |
|
|
else |
if〜else |
|
|
for |
forループ |
|
|
goto |
無条件分岐 |
|
|
if |
条件判定 |
|
|
return |
関数からの戻り |
|
|
switch |
多条件分岐 |
|
|
while |
whileループ |
|
|
その他 |
entry |
意味なし |
|
sizeof |
データ型の長さ |
|
ここでは、C言語のプログラムの書き方(スタイル)について説明します。プログラムを書くときの約束は次に示す4つだけで、他のプログラミング言語に比べると少ないことがわかります。つまり、プログラマーは自分のスタイルでプログラムを書くことができるわけです。しかし、あまり奇抜な書き方は関心しません。
(1) 文はセミコロン( ; )で終わらなければなりません。
int name ;
printf("%c¥n", name);
(2) C言語では、行を意識することなくプログラムを書くことができます。つまり、1行に複数の文を書いたり、あるいは1つの文を複数の行に分けて書くこともできます。
1行に2つの文
int name, test ; printf("%c¥n", name) ;
1つの文を複数の行
int name ,
test ;
printf
("%c¥n" ,
name) ;
ただし、次のように名前などの途中で行を変えることはできません。
int na
me , test ;
(3) スペースやタブなどの空白文字を自由に使って、プログラムをどの位置からでも書き始めることができます。
(i) [TAB] int name [TAB] , □□□ test ;
(ii) int sw1 [TAB] , □□ table [ ] ;
(4) 注釈(コメント)
注釈は、必ず使用しなけれぱならないものではありません。しかし、プログラムを理解しやすくすることを考えるとコメントを上手に使用することをすすめます。C言語でコメントを書く場合の唯一の規則は、コメントの内容を /* と */ で囲むことです。その範囲が複数の行になってもかまいません。
int test; /* test switch */
/*
test switch
*/
int test ;
最後に、空白文字を使って書いたプログラムを紹介します。
このプログラムは、1+2を求めるためのプログラムです。プログラムの内容については、特に説明しません。ここでは、空白文字を使うことによって見やすく、そして理解しやすいプログラムを書くことができることを知ってください。
(見やすいプログラミング・スタイルの例)
/*
合計を求める
*/
#include <stdio.h>
main()
{
int a,b,c ;
a = 1 ;
b = 2 ;
c = a + b ; /* goukei */
printf (“%d” , c) ;
}
(見にくいプログラミング・スタイルの例)
#include <stdio.h>
main() { int a,b,c ; a = 1 ; b = 2 ; c = a + b ;
printf (“%d” , c) ; }
C言語で扱うことができるデータの種類について説明します。C言語でプログラムを書く場合、そのプログラムで使用する変数や定数は、あらかじめ宣言しておかなければなりません。変数とは、プログラムが実行中その値が変わることができるものです。一方、定数とは、プログラム実行中その値が変わらないものです。
変数を宣言する場合、その変数の性格(属性)を決めなければなりません。その属性には、つぎに示す3つがあります。
1. その変数が表現できる数の大きさ(データ型)
2. その変数がメモリのどの位置に置かれるか(記憶クラス)
3. その変数がプログラムのどの範囲まで使用できるか(有効範囲)
C言語では変数を宣言する場合、これらの属性をつぎのように指定します。
[記憶クラス]データ型 変数名 ;
[]省略可能
変数が宣言されたプログラム内の位置によって、その変数が使用できる範囲(変数の有効範囲)が決定されますので、変数の有効範囲は特に宣言しません。データ型、記憶クラス、有効範囲の順に説明していくことにしましょう。
C言語では、データの型として、基本的には整数型と浮動小数点数型(実数型)があります。さらに文字を扱うための文字型も用意されています。
整数は1、 2、 -1、 -2などの数で、小数部分をもたないものです。C言語では表現する数の大きさによって、つぎに示す3つの種類の整数を宣言することができます。
|
宣言方法 |
意味 |
|
(i) short |
通常2バイトで表現し、-32768 〜 +32767の範囲の数を表わすこと方言できます。 |
|
(ii) long |
通常4バイトで表現し、-2147483648 〜 +2147483647の範囲の数を表わすことができます。 |
|
(iii) int |
表現できる数の範囲はCPUのアーキテクチャに依存しますつまり、16ビット系のCPUであれば2バイト(shortと同じ)で表現し、32ビット系のCPUであれば4バイト(longと同じ)で表現します。 |
|
(iv) unsigned short |
0〜65535の範囲の数を表わすことができます。 |
|
(v) unsigned long |
0〜4294967295の範囲の数を表わすことができます。 |
|
(vi) unsigned int |
16ビット系のCPUでは(iv)と同じ、32ビット系のCPUでは(v)と同じです。 |
(i)、(ii)、(iii)は符号(プラス、マイナス)のついた数を表わしましたが、C言語では符号なしの整数も宣言することができます。符号なしの整数を宣言するには、つぎのように(i)、(ii)、(iii)で使用した宣言文の前に(iv)、(v)、(vi)のように unsigned をつけます。
浮動小数点数型を使開すると0.00000123といった小数点以下の小さな数や、10の20乗といった大きな数を表現することができます。C言語では、表現する数の大きさによって、次の2つの種類の浮動小数点を表現することができます。
|
表現方法 |
意味 |
|
float |
通常4バイトで表現し、-10E38〜 +10E38 の範囲の数を表わすことができます |
|
double |
通常8バイトで表現し、-10E306〜 +10E306 の範囲の数を表わすことができます |
文字型は a や * などの1文字を表現するためのものです。C言語では、つぎのように書きます。
|
宣言方法 |
意味 |
|
char |
1バイトで表現します |
C言語では、つぎに示すように1文字をシングルクォーテーションで囲んで表現します。
char name = 'a' ;
これは、変数nameに文字 a をセットしています。
この文字型変数 name を printf 関数で文字として出力してみましょう。
printf("%c", name);
このように、printf 関数を書くと、a と出力されます。
変数をメモリのどの位置に割り当てるかを決めるのが記憶クラスです。C言語には、記憶クラスとして自動変数(auto)、静的変数(static)、レジスタ変数(register)、外部変数(extern)の4つが用意されていますが、ここでは自動変数と静的変数についてのみ説明します。
自動変数は、自動変数が宣言されている関数やブロックの処理が始まるとメモリ内にセットされ、その実行が終わるとメモリから消えてなくなります。つまり、関数やブロック内で変数の独立性を保つことができるわけです。自動変数はつぎのように指定します。
auto int data ;
これは整数型の変数 data を自動変数として宣言することを意味しています。なお、auto を省略すると自動変数となります。この自動変数は、変数の値を蓄えておくための共用の記憶領域(スタック領域)に格納されます。つまり、自動変数を宣言するとメモリの有効利用がはかれることになります。スタックの原理を図2.1に示します。
いま、変数 a と変数 b が自動変数として変数 a、変数 b の順に宣言されると、変数 a、変数 bがスタック領域に登録されます。このスタック領域に登録された変数は、変数 b、変数 a の順(LIFO、ラーストインファーストアウト)に参照することになります。

Figure 2‑1 スタックの原理
それでは、自動変数の宣言から消滅までの様子を Figure 2-2 に示すプログラムを使いながら説明することにします。

Figure 2‑2 自動変数の範囲
このプログラムは、4つのブロック(実行単位)からできています。そして、それぞれのブロックが始まるところで変数を宣言するようにしています。つまり、ブロック1では変数 a、ブロック2では変数 b、ブロック3では変数 c、ブロック4では変数 dを宣言しています。プログラムは各ブロック内で使用できる変数のみを出力しています。このプログラムを実行するとつぎのようになります。
a=100b=200c=300d=400
a=100b=200c=300
a=100b=200
a=100
この結果をもとに、変数 a、b、c、d の宣言と参照の関連を図2.3に示します。この図は、ブロック開始から終了までの過程で、宣言した変数と参照できる変数の関係を示しています。図中のa、b、c、dは宣言した変数、a、b、c、dとなっているのは参照可能な変数を示しています。ブロックの開始のところでは、ブロックが1から4と順に形成されていく過程で使用できる変数も、a、b、c、dと増えていくことがわかります。つぎにブロックの終了を見てみましょう。ブロック4が終了するとブロック4で宣言していた変数dは消滅してしまいますから、ブロック3では変数dは参照することができません。同じように、ブロック3が終了すると変数cが消滅してしまいますから、ブロック2では変数cを参照することができません。
Figure 2-3 数式の宣言と参照関数
|
処理の流れ |
ブロック |
宣言変数と参照可能変数 |
|
ブロックの開始 |
1 2 3 4 |
a a b a b c a b c d |
|
ブロックの修了 |
4 3 2 1 |
a b c d a b c a b a |
(a、 b、 c、 d は宣言、 a、 b、 c、 d は参照可能)
静的変数は自動変数と異なり、プログラム実行中常にメモリ内に存在する変数です。つまり、静的変数の内容はプログラム実行中保証されることになります。静的変数はつぎのように指定します。
static int data;
これは、整数型の変数 data を静的変数として宣言することを意味しています。また、つぎのように静的変数の内容を初期化することができます。
static int data = 初期値 ;
なお静的変数の初期化は、コンパイル時に行なわれます。
静的変数と自動変数の違いをFigure 2-4、2-5に示します。静的変数の場合のプログラム(プログラム2.2)と自動変数の場合のプログラム(プログラム2.3)の内容は同じです。ただし、test 関数の変数 dataの記憶クラスが異なります。
プログラムは、main と test の2つの関数でできています。main 関数から test 関数を2回呼び出しています。test 関数では、変数 dataに10を加えて出力します。
静的変数の場合、test関数の変数dataはコンパイル時に初期化されますので、test関数が呼び出されるたびに変数dataの内容は初期化されません。つまり、test関数が2回目に呼び出された場合、1回目の結果が残っているので、変数dataの内容にさらに10を加えることになります。
|
#include <stdio.h> main() { int data = 1; printf("(main) %d\n", data) ; test( ); printf("(main) %d\n", data) ; test( ) ; printf ("(main) %d\n", data) ; } test( ) { static int data = 10; data += 10 ; printf("(sub ) %d\n", data); }
図2.4 プログラム2.2 静的変数の場合 |
|
#include <stdio.h> main() { int data = 1; printf("(main) %d\n", data) ; test( ) ; printf("(main) %d\n", data) ; test( ) ; printf ("(main) %d\n", data) ; } test( ) { int data = 10; data += 10 ; printf("(sub ) %d\n", data); }
図2.5 プログラム2.3 自動変数の場合 |
一方、自動変数の場合には、test関数が呼び出されるたびにtest関数の変数dataは10に初期化されますので、何回呼び出されても変数dataの内容は20となります。また、関数mainの変数dataと関数testの変数dataとは、関数ブロックが異なるため互いに異なるものとして扱われていることに注意してください。以上の結果をまとめると、つぎのようになります。
|
静的変数 |
自動変数 |
||
|
(main) (sub) (main) (sub) (main) |
1 20 1 30 1 |
(main) (sub) (main) (sub) (main) |
1 20 1 20 1 |
ここでは、宣言した変数がプログラムのどの範囲まで有効となるかについて説明します。BASICやFORTRANなどでは、宣言した変数はプログラムのどこからでも自由に参照することができますが、C言語では変数が宣言された位置によって、参照できる範囲が変わってきます。有効範囲の基本的な単位となるのが関数やブロックです。ブロックとは、{と}に囲まれた部分をいいます(実行単位と呼ぶこともある)。
それでは、変数がどの範囲まで参照できるかをプログラムを使って説明しましょう。自動変数の説明で使用したプログラム2.3をもう一度見てみましょう。
|
#include <stdio.h> main() { int data = 1 ; printf ( “(main) %d \n” , data ); test( ) ; printf ( “(main) %d \n” , data ); test( ) ; printf ( “(main) %d \n” , data ); } |
関数mainで宣言された変数dataの有効範囲
|
|
Test( ) { int data = 10 ; data += 10 ; printf ( “(sub) %d \n” , data ); }
|
関数testで宣言された変数dataの有効範囲
|
このプログラムは、mainとtestの2つの関数でできています。変数dataの有効範囲について考えてみましょう。このプログラムを実行するとつぎのようになります。
|
(main) (sub) (main) (sub) (main) |
1 20 1 20 1 |
この結果から、関数mainとtestで宣言した変数dataは、その宣言された関数内だけに有効であることがわかります。つまり、関数内で宣言した変数は関数内のみで有効となります。このような変数をローカル変数と呼ぶこともあります。
それでは、プログラムを構成するすべての関数で、共通に使用できるように変数を宣言できないのでしょうか。
プログラム2.3で使用した変数dataをmainとtest関数で共通に使用できるように修正したプログラムを以下に示します。
|
#include <stdio.h> int data = 1 ; main() { printf( “(main) %d \n” , data ); test( ) ; printf( “(main) %d \n” , data ); test( ) ; printf( “(main) %d \n” , data ); } |
------
変数dataの有効範囲
------ |
|
Test( ) { data += 10 ; printf( “(sub) %d \n” , data ); }
|
プログラム2.3との違いは、変数dataを関数mainとtestの外で宣言していることです。そして、それぞれの関数では変数dataの宣言をしていません。このように、関数の外で変数を宣言すると、それ以降のすべての関数から自由に参照することができます。このような変数をグローバル変数と呼ぶこともあります。このプログラムの実行結果をつぎに示します。
|
(main) (sub) (main) (sub) (main) |
1 11 11 21 21 |
この結果から、変数dataは共通に使用されていることがわかります。
いままで説明してきた変数は、プログラムの実行過程でその内容を変えることができますが、これから説明する定数とは、プログラムの実行中その値が変わることのないものです。C言語の定数には、整数定数、浮動小数点数定数、文字定数、文字列定数の4つが用意されています。
整数定数には、10進数、16進数、8進数の3種類の表現方法があります。
(a)10進表現
10進表現は0以外の数字で始まる数字列です。負の数を表現するときはマイナス(-)を数字の前に書くことになります。正の数を表現する場合には、通常プラス(+)をつけないことになっています。
128
-128
(b)16進表現
16進表現とは、数値を16進数で表現するためのもので、使用できる数字と文字は 0〜9とA〜F までとなります。そして、16進表現であることを示すために数字列の前に0X(ゼロエックス)をつけることになっています。
0X16 (16数の16を表現しています)

この0X16をビット列で表わすとつぎのようになります。
(c)8進表現
8進表現とは、数値を8進数で表現するためのもので、使用できる数字は0〜7となります。そして、8進数表現であることを示すために数字列の前にゼロ(0)をつけることになります。
016 (8進数の16を表現しています)
この016をビット列で表わすとつぎのようになります。
016とは10進の14を表わしていることになります。

浮動小数点数表現は、小数点のついた数値、2.34E18といった大きな数値、2.34E-8 といった小さな数値を表現したい場合に使用する、ことになります。
1.23
0.0123
また、2.34E18 のような大きな数値は、つぎに示すように指数部と仮数部に分けて表現します。
2.34E18 仮数部2.34
指数部18
つまり、仮数部の数値と指数部の数値をEでつなぐことになります。なお、仮数部と指数部の数値は10進数表現で指定します。
文字表現とは、1文字を表現するもので、表現したい1文字をつぎのようにシングルクォーテーションで囲んで書きます。
’a’ (小文字のaを表現している)
ここで問題となることがあります。 ASCIIコード表を見ましょう。この表の中で16進の20〜7Eまではそれぞれ対応するキャラクターコードが用意されています。しかし、16進の00〜1Fまでには対応するキャラクターコードがありません。つまり、シングルクォーテーションで囲む文字がないのです。このような場合、つぎに示すように表現したいキャラクターコードを8進表現し、その前にバックスラッシュ(¥)をつけ、それらをシングルクォーテーションで囲むことになります。
' ¥8進表現 '
通常、16進の00〜1Fは画面や通信の制御文字としてよく使用されています。そこで、C言語ではこれらの制御文字を簡易的につぎのように書くことができます。
|
文字表現 |
簡易表現 |
説明 |
|
’¥000’ ’¥010’ ’¥011’ ‘¥012’ ’¥014’ ‘¥015’ |
’¥0’ ’¥b’ ’¥t’ ’¥n’ ’¥f’ ’¥r’ |
ヌルコード(NULL) バックスペース(BS) 水平タプ(HT) ニューライン(NL) フォームフィード(FF) キャリジリターン(CR) |
つぎに、バックスラッシュ(¥、シングルクォーテーション(’)、ダブルクォーテーション(”)そのものを表わすにはどのように表現すればよいのでしょうか。
(a)バックスラッシュ
' ¥ '
これではバックスラッシュを表現したことになりません。
C言語では文字表現する場合、シングルクォーテーションの後に¥がくると、その後に8進表現か簡易表現などがくることになります。つまり、これでは文字表現したい文字がないのです。そこで、バックスラッシュを表現する場合には、つぎのようにバックスラッシュを2個続けて書きます。
' ¥¥ '
(b)シングルクォーテーション
単純に考えて、つぎのように書きますか?
' ' '
これでは、どこからどこまでが文字表現なのか判断することができません。そこでつぎのように書きます。
' ¥ ' '
(1)の場合と同じように表現したいシングルクォーテーションにバックスラッシュをつけて、それらをシングルクォーテーションで囲むことになります。
(c)ダブルクォーテーション
ダプルクォーテーションはつぎのように表現します。
‘ " ’
文字列表現はつぎに示すように、文字列をダブルクォーテーションで囲んで表現します。
"C language"
これは、C languageという文字列を表現しています。さらに、文字列の終わりを示すためのヌルコード(¥0)が文字列の最後に自動的に付加されます。
文字列表現の応用として、printf関数を使って abc"xyz という文字列を出力する場合について考えてみましょう。いままで学習してきた知識では次のように書くことになると思います。
Printf("abc"xyz");
これでは、abc"xyzと出力することができません。printf関数で出力する内容はダブルクォーテ一ションで囲むことになっています。つまり、普通の書き方ではこのダブルクォーテーションを出力することができません。printf関数でダブルクォーテーションを出力するには次のように書きます。
Printf("abc¥"xyz");
そうです。バックスラッシュ(¥)を使えばよいのです。つまり、出力したいダブルクォーテーションの前に¥をつけることになります。
C言語には、他のプログラミング言語の演算子よりかなり多くの種類の演算子が用意されています。このことがC言語の特長のひとつとなっています。ここでは、C言語の演算子の基本的な機能と演算子の優先順位について説明します。
C言語には、表3.1に示す(1)から(15)のような演算子が用意されています。ここでは(1)から(9)についてのみ説明します。
|
表3.1演算子の種類 |
|
|
演算子の種類 |
説明 |
|
(1) |
算術演算子 |
加算、減算、乗算、除算、乗余算。 |
|
(2) |
関係演算子 |
大小比較。 |
|
(3) |
論理演算子 |
論理積、論理和、否定。 |
|
(4) |
インクリメント演算子 |
1の加算。 |
|
(5) |
デクリメント演算子 |
1の減算。 |
|
(6) |
代入演算子 |
変数の内容を他の変数に代入します。 |
|
(7) |
キャスト演算子 |
データの型を変換します。 |
|
(8) |
順次演算子 |
式と式をつなぎます。 |
|
(9) |
条件演算子 |
条件により2つの値のどちらかを採用します。 |
|
(10) |
アドレス演算子 |
変数のアドレスを求めます。 |
|
(11) |
sizeof演算子 |
変数が確保した領域の大きさを求めます。 |
|
(12) |
間接演算子 |
間接的にデータを参照します。 |
|
(13) |
ビット演算子 |
ビットごとの論理積、論理和などを行ないます。 |
|
(14) |
シフト演算子 |
ビットを左右にシフトします。 |
|
(15) |
補数演算子 |
1の補数を求めます。 |
算術演算とは、我々が日常使っているたし算したり、ひき算したりする四則演算とほとんど同じものです。算術演算子には表3.2に示すようなものがあります。
|
表3.2算術演算子の種類 |
|||
|
演算の種類 |
演算子 |
使用例 |
意味 |
|
加算 |
+ |
c = a+b ; |
aとbの内容を加え、その結果をcにセットします。 |
|
減算 |
- |
c = a-b ; |
aの内容からbの内容を引いて、その緒果をcにセットします。 |
|
乗算 |
* |
c = a*b ; |
aの内容とbの内容を乗算し、その結果をcにセットします。 |
|
除算 |
/ |
c = a/b ; |
aの内容をbの内容で除算し、その緒果をcにセットします。 |
|
剰余算 |
% |
c = a%b ; |
aの内容をbの内容で除算し、その余りをcにセットします |
これら、5つの演算子を使ったプログラムを以下に示します。このプログラムは表3.2で説明した内容をプログラミングしたものです。
#include <stdio.h>
main()
{
int a,b,c;
a = lO;
b = 3;
c = a + b;
printf("<a+b> %d\n",c);
c = a – b;
printf("<a-b> %d\n",c);
c = a * b;
printf("<a*b> %d\n",c);
c = a / b;
printf("<a/b> %d\n",c);
c= a % b ;
printf("<a%b> %d\n",c);
}
このプログラムでは、変数aに10、bに3を初期値としてセットし、次に算術演算子を使ってcを求めて出力しています。このプログラムを実行すると次のような結果が出力されます。
<a+b> 13
<a-b> 7
<a*b> 30
<a/b> 3
<a%b> 1
この算術演算では、データの型の異なる変数どうしを演算する場合に問題が発生します。たとえば、整数と浮動小数点の演算をしたような場合です。このように異なるデータ型の演算を混合演算といいます。
コンパイラは式の中に混合演算を見つけると、その式の中で使用されている変数のうち最も大きな(バイト数)データ型に、それぞれの変数の型を自動的に変換して演算を行ないます。これを自助変換といいます。その自動変換の規則を次の表3.3に示します。
|
表3.3自動変換の規則 |
||||||
|
変換順位 |
1 |
2 |
3 |
4 |
5 |
6 |
|
データ型 |
char |
short |
int |
long |
float |
double |
|
バイト数 |
1 |
2 |
4 |
4 |
4 |
8 |
表3.3では、変換順位の数字が大きいほど変換順位が高いことを示しています。つまり、int型とlong型の演算では、int型をlong型に変換します。また、int型とfloat型の演算ではint型をfloat型に変換します。このように混合演算を行なうと自動的にデータの型が変換されます。このように自動変換が行なわれる場合、左辺のデータ型が右辺のデータ型より大きい場合には問題ありませんが、この逆の場合、つまり左辺のデータ型が右辺のデータ型より小さい場合に新たな問題が発生します。たとえば、次に示すような場合です。
int a ;
double b ;
a = b ;
これは、変数aを整数型、変数bを倍精度浮動小数点数型として宣言しています。そして、変数bの内容を変数aに代入しています。このような場合、変数bの内容が自動的にまるめられて(切捨て)変数aに代入されます。このようなコンパイラの自動変換機能は便利なこともありますが、ときとしてエラーの原因となることもありますので十分に注意してください。
関係演算子とは、if文などで判断条件を指定するのに使われる演算子で比較演算子とも呼ばれます。
|
表3.4 関係演算子の種類 |
||
|
演算子 |
使用例 |
意味 |
|
< |
c = (a < b); |
aの内容がbの内容より小さければcに1(真)、そうでなければ0(偽)をセットします。 |
|
> |
c = (a > b); |
aの内容がbの内容より大きければcに1(真)、そうでなければ0(偽)をセットします。 |
|
<= |
c = (a <= b); |
aの内容がbの内容より小さいか等しければcに1(真)、そうでなければ0(偽)をセットします。 |
|
>= |
c = (a >= b); |
aの内容がbの内容より大きいか等しければcに1(真)、そうでなければ0(偽)をセットします。 |
|
== |
c = (a == b); |
aの内容がbの内容に等しければcに1(真)、そうでなければ0(偽)をセットします。 |
|
!= |
c = (a != b); |
aの内容がbの内容に等しくなければcに1(真)、そうでなければ0(偽)をセットします。 |
関係演算子は、2つの数値や文字を比較し、条件を満足すれば真を、満足しない場合には偽を演算結果とします。通常、真は0以外の数値で、偽は0で表します。真の状態を表わす数値に1を採用しているコンパイラが多いようです。関係演算子は表3.4に示すように6種類あります(説明上、演算結果が真であれば1、偽であれば0とします)。C言語では、2つの変数が等しいかどうかを調べる場合は = = (イコール記号を2つ並べる)を、代入を表わすのに = (イコール記号)を使用しますので注意してください。次に関係演算の演算結果を調べるプログラムを以下に示します。
#include <stdio.h>
main()
{
int a, b, c ;
a = 10 ;
b = 3 ;
c = (a < b) ;
printf ( “( a < b) %d \n”, c) ;
c = (a > b) ;
printf ( “( a > b) %d \n”, c) ;
c = (a <= b):
printf ( “( a <= b) %d \n”, c) ;
c = (a >= b);
printf ( “( a >= b) %d \n”, c) ;
c = (a == b) ;
printf ( “( a == b) %d \n”, c) ;
c = (a != b);
printf ( “( a != b) %d \n”, c) ;
}
このプログラムを実行すると次のような結果が出力されます。
|
(a < b) |
0 |
(偽) |
|
(a > b) |
1 |
(真) |
|
(a <= b) |
0 |
(偽) |
|
(a >= b) |
1 |
(真) |
|
(a = = b) |
0 |
(偽) |
|
(a != b) |
1 |
(真) |
論理演算子とは、論理積、論理和、否定などを求めるための演算子です。論理積、論理和、否定をわかりやすく言いなおすと次のようになります。
論理積(AND) かつ(ともに)
論理和(OR) あるいは(どちらか)
否定(NOT) でなければ
論理演算子には、表3.5に示すような3種類があります。
|
表3.5論理演算子の種類 |
|||
|
論理演算 |
演算子 |
使用例 |
意味 |
|
論理積 |
&& |
c = a && b ; |
aの内容とbの内容がともに真ならばcに1(真)、そうでなけれぱ0(偽)をセットします。 |
|
論理和 |
| | |
c = a || b ; |
aの内容あるいはbの内容が真ならばcに1(真)、そうでなければ0(偽)をセットします。 |
|
否定 |
! |
c = ! a ; |
aの内容が真ならばcに0(偽)、aの内容が偽ならばcに1(真)をセットします。 |
それでは次のような場合、どのように表現すればよいのでしょうか。
aが50より大きく、かつaが100より小さい
これは次に示すように、論理演算子と関係演算子を組み合せると表現することができます。
aが50よりおおきかつ aが100より小さい
a>50 && a<100
これをC言語では、次のように表現します。
a>50 && a<100
それでは、論理演算子を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int a,b,c;
a = 10;
b = 200;
c = a && b ;
printf("(a&&b) %d\n",c);
c = a || b;
printf("(a||b) %d\n",c) ;
c = !a;
c= printf("(!a) %d\n",c) ;
c = a > 50 && a < 100;
printf("(a>50 && a<100) %d\n",c) ;
c = a < 50 || a > 100;
printf("(a<50 || a>100) %d\n",c) ;
c = !(a < 50);
printf("!(a <50) %d\n",c) ;
}
このプログラムを実行すると次のような結果が出力されます。
|
(a && b) |
1 |
(真) |
|
(a || b) |
1 |
(真) |
|
(! a) |
0 |
(偽) |
|
(a > 50 && a < 100) |
0 |
(偽) |
|
(a < 50 || a > 100) |
1 |
(真) |
|
(!(a < 50)) |
0 |
(偽) |
インクリメント演算子とデクリメント演算子は、他のプログラミング言語にはない演算子でC言語の特長のひとつでもあります。インクリメント演算子は1を加算し、デクリメント演算子は1を減算します。つまり、これらの演算子はカウンタによく使用されます。インクリメント演算子は次のように書きます。
|
++式 |
(前置型) |
|
式++ |
(後置型) |
このようにインクリメント演算子は ++(プラスを2つ)で表わします。その書き方としては、式の前に ++ を書く方法(前置型)と式の後に ++ を書く方法(後置型)があります。まず、インクリメント演算子を変数bの前に書いたプログラムを以下に示します。
#include <stdio.h>
main()
{
int a, b ;
b = 1 ;
a = ++b ;
printf (“a= %d b= %d \n” , a, b);
}
このプログラムを実行すると次のようになります。
a = 2 b = 2
つまり、インクリメント演算子(++)が変数bの前にある場合は、まず変数bに1を加え、その結果を変数aにセットします。次に、インクリメント演算子を変数bの後に書いたプログラムを以下に示します。
#include <stdio.h>
main()
{
int a, b ;
b = 1 ;
a = b++ ;
printf (“a= %d b= %d \n” , a, b);
}
このプログラムを実行すると次のようになります。
a = 1 b = 2
つまり、インクリメント演算子(++)が変数bの後にある場合は、まず変数bの内容を変数aにセットし、その後で変数bに1を加えます。
次に、デクリメント演算子について説明します。デクリメント演算子は次のように書きます。
|
--式 |
(前置型) |
|
式-- |
(後置型) |
このようにデクリメント演算子は --(マイナスを2つ)で表わします。その書き方は、式の前に --を書く方法(前置型)と式の後に -- を書く方法(後置型)があります。まず、前置型のプログラムを以下に示します。
#include <stdio.h>
main()
{
int a, b ;
b = 1 ;
a = --b ;
printf (“a= %d b= %d \n” , a, b);
}
このプログラムを実行すると次のようになります。
a = 0 b = 0
つまり、デクリメント演算子(--)が変数bの前にある場合、まず変数bから1を引き、その結果を変数aにセットします。次に、後置型のプログラムを以下に示します。
#include <stdio.h>
main()
{
int a, b ;
b = 1 ;
a = b-- ;
printf (“a= %d b= %d \n” , a, b);
}
このプログラムを実行すると次のようになります。
a = 1 b = 0
つまり、デクリメント演算子(--)が変数bにある場合は、まず変数bの内容を変数aにセットし、その後変数bから1を引きます。
代入演算子とは、算術演算子の拡張したもので算術演算子よりも簡略化して表現することができます。代入演算子には、四則演算のほかにビットを操作するための演算子も用意されていますが、ここでは四則演算についてのみ説明します。代入演算子には、表3.6に示すようなものがあります。
|
表3.6代入演算子の種類 |
||
|
演算子 |
使用例 |
意味 |
|
= |
c = a ; |
cにaの内容を代入します。 |
|
+= |
c += a ; |
cにaの内容を加えて、その結果をcに代入します。 |
|
-= |
c -= a ; |
cの内容からaの内容を引いて、その結果をcに代入します。 |
|
*= |
c *= a ; |
cにaの内容を掛けて、その結果をcに代入します。 |
|
/= |
c /= a ; |
cの内容をaの内容で割って、その結果をcに代入します。 |
|
%= |
c %= a ; |
cをaの内容で割って、その余りをcに代入します。 |
C言語では、変数に任意の数値を代入するといった操作は表3.6に示すように演算子(=)で行ないます。このことから、C言語では演算子で代入を行なうので次のようなことも可能となります。
a = b = c = d = 100;
これはdに100を代入し、次にcにdを、bにcを、aにbの内容を順に代入していきます。つまり、a、b、c、dとも100となります。それでは、代入演算子を使ったプログラムを以下に紹介します。
#include <stdio.h>
main()
{
int a , b ;
a = 10 ;
b = 3 ;
a += b ;
printf (“<a += b> = %d \n” , a) ;
}
このプログラムは代入演算子として+=を使用しています。この代入演算子を算術演算式を使って書きなおすと次のようになります。
a += b ; (代入演算子)
a = a + b ; (算術演算子)
上記のプログラムを実行すると次のようになります。
<a += b> = 13
データの型が異なる変数どうしの演算をする場合には、コンパイラが自動的に型の変換を行なって演算します。このようにコンパイラがデータの型を自動的に変換してくれたからといって、データの型をいいかげんに宣言することはあまり関心しません。やはり、データの型は正確に宣言するようにしましょう。
プログラムでデータの型を意識して変換するのに用いる演算子がキャスト演算子です。たとえば、整数型で宣言した変数を、プログラムのあるところで一時的に浮動小数点数として扱いたいといった場合に使用します。この演算子は、本来宣言したデータの型を変えるものではありません。この演算子を使ったときのみ、一時的に指定されたデータの型に変わることになります。キャスト演算子は次のように書きます。
|
(希望するデータの型) 変数名 |
キャスト演算子を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int a , b ;
float c , d ;
a = 100 ;
c = 1.23 ;
d = (float) a ;
b = (int) c ;
printf (“a= %d d=%6.2f c=%6.2f b=%d \n” , a,d,c,b) ;
}
このプログラムを実行すると、次のようになります。
a=100 d=100.00 c=1.23 b=1
このプログラムでは、変数a(整数)の内容を浮動小数点数型に変換して変数dにセットし、変数bに変数c(浮動小数点数)の内容を整数型に変換してセットしています。
順次演算子は、次に示すように式と式をつないで書くための演算子です。この演算子にカンマ( , )を用いるのでカンマ演算子と呼ぶこともあります。
|
文1, 文2, ・・・, 文n |
これは、カンマでくぎられた順に左から実行されます。順次演算式の結果は最も右の式の結果がその値となります。
c = (a=100, b=10, d=a+b) ;
これを実行するとd=a+bの結果がcの値となります。つまり、cは110となります。
条件演算子は、与えられた条件によって2つの式のいずれかの一方の式を選択し、実行するための演算子です。条件演算子はクウェスチョン(?)で表わし、次のように書きます。
|
条件式? 式1: 式2 |
これは、条件式が真(条件を満足している状態)のとき式1を、偽(条件を満足していない状態)のとき式2を実行することを意味します。条件演算子を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int inp_data, data1, data2, inp_cnt ;
data1 = data2 = 0 ;
for (inp_cnt=1 ; inp_cnt < 11 ; inp_cnt++ )
{
scanf ("%d" , &inp_data) ;
(inp_data < 6) ? data1++ : data2++;
}
printf ( "data1=%d data2=%d \n" , data1, data2 )
}
このプログラムは1〜10までの任意の数値を10個読み込み、1から5までの数値の個数と6から10までの数値の個数を求めるためのプログラムです。このプログラムでは条件演算子を次のよう書いています。
(inp_data < 6) ? data1++ : data2++ ;
これは、読み込んだデータ(変数inp_dataにセット)が6より小さい場合にはdata1++ を実行し、6以上の場合はdata2++ を実行することを意味します。この演算子は、便利な演算子ですから利用するようにしましょう。このプログラムの実行結果は、みなさんそれぞれ確認してください。
いくつかの演算子を組み合せて使う場合、どのような順序で処理されていくのかがよく問題となります。もし、演算の順序を自分で明確に決定したい場合には、()で囲むことを進めます。しかし()を多く使用すると演算式が複雑になるのであまりすすめられません。C言語では、表3.7に示すように演算子の優先順位が決められています。
|
表3.7演算子の優先順位 |
||
|
優先レベル |
演算子 |
結合規則 |
|
15 |
( ) [ ] -> ・ |
左→右
|
|
14 |
Sizeof(型) & - + - - ++ ~ ! |
|
|
13 |
* / % |
|
|
12 |
+ - |
|
|
11 |
<< >> |
|
|
10 |
< <= > >= |
|
|
9 |
== != |
|
|
8 |
& |
|
|
7 |
^ |
|
|
6 |
| |
|
|
5 |
&& |
|
|
4 |
|| |
|
|
3 |
? : |
|
|
2 |
>>= <<= ^= &= %= /= *= -= += = |
|
|
1 |
, |
|
この表は縦軸が優先順位のレベルを示します(数字が大きくなるほど優先順位が高いことを示しています)。結合規則とは、優先順位のレベルが同じ場合の演算の順序を示します。それでは、演算子の優先順位を具体的な例を使って説明しましょう。
a/b-c*d
ここでは、3つの演算子 /、-、* を使っています。この3つの演算子の優先順位は、(*)→(/)→(-)の順となりますので、次の順序で演算されます。
(1) a / b
(2) c * b
(3) (1) - (2)
次の場合はどのような順序で演算されるのでしょうか。
(a / b - c) * d
ここでは、4つの演算子 ( )、/、-、* を使っています。これらの中では()の中が優先されています。さらに、( ) の中には2つの演算子 /、- が使用されていますが / の方が優先順位が高いので次の順序で演算されます。
(1) a / b
(2) (1) - c
(3) (2) * d
次はどのような順序で演算されるでしょうか。
a -= b *= c
ここでは2つの演算子 -=、*= を使っています。この2つの演算子の優先順位レベルは同じですが、結合法則が左→右ですので次の順序で演算されます。
(1) b *= c
(2) a -= (1)
次はどのような順序で演算されるでしょうか。
a * b ++
ここでは2つの演算子 *、++ を使っています。この2つの演算子は ++ 演算子の方が優先順位が高くなっていますので、次のように演算されるのでしょうか。
(1) b++
(2) a * (1)
もう少し考えてみてください。ここのインクリメント演算子は、演算後1を加えるようになっていますから次のように演算されます。
(1) a * b
(2) (1)++
つまり、(a*b)++ と同じになります。
今回は、プログラムを書く上で重要な要素の1つである処理の流れの制御について説明します。通常、どのようなプログラムでも処理の流れを分岐したり、同じ処理を何回か繰り返し実行したりすることがあります。C言語には、処理の流れを効率よく制御するためのいろいろな文が用意されています。
C言語には、次に示すように処理の流れを複数方向に分岐するもの、処理を繰り返すもの(ループ)、無条件に分岐するものなどの処理の流れを制御するための文があります。さらに、これらを補助するための文も用意されています。
|
表4.1 処理の制御コマンド |
|
|
制御の種類 |
文 |
|
条件分岐 |
If文、switch文 |
|
繰り返し |
while文、for文、do〜while文 |
|
無条件分岐 |
goto文 |
|
補助文 |
break文、continue文 |
if文は、処理の流れを2つの方向に分岐するためのものです。if文には次に示すような2つの書き方があります。
(1) if (条件式) 文1;
または、
(2) if (条件式) 文1;
else 文2;
は、“条件式”を実行(評価)し、その条件式を満足している場合(評価結果が真)は“文1”を実行します。もし条件式を満足していない場合(評価結果が偽)は、if文の次の文へ制御が渡ります。この機能をフローチャートで表わすとFigure 4-1 のようになります。
ここでは、(1)を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int a;
scanf (“%d”, &a);
if ( a > 20 ) printf ("data is %d\n", a);
}
このプログラムは、数値のデータを変数aに読み込み、その内容が20より大の場合、その内容を出力するためのものです。

Figure 4‑1 if文
(2)は、(1)と同様にまず条件式を評価します。その条件式を満足した場合(真の状態)には、文1を実行します。もし、条件式を満足しなかった場合(偽の状態)には、文2を実行します。この機能をフローチャートで表わすとFigure 4-2 のようになります。

Figure 4‑2 if-else 文
次に(2)を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int a;
scanf (“%d”, &a);
if (a>20) printf ("bad data is %d\n", a);
else printf ("good data is %d\n", a);
}
このプログラムは、数値のデータを変数aに読み込み、その内容が20より大きかったらエラーデータとして出力し、20より小さかったら正しいデータとして出力するためのものです。
ここまで説明してきたプログラムはもっとも基本的なものです。実際のプログラムでは条件式を評価後、複数の文を実行することがよくあります。このような場合には、C言語では次のように書きます。
(3) if(条件式)
{
文1;
文2;
…
文n;
}
(3)は、条件式を満足した場合(真の状態)には、文1〜文nまでの文を連続に実行します。もし条件を満足しなかった場合(偽の状態)には、if文の次の文を実行します。次に、この機能を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int a ;
int b = 1 ;
scanf (“%d”, &a);
if ( a > 20 )
{
++b ;
printf ("Number of bad data is %d\n", b);
}
}
このプログラムは、エラーになった個数を求めています。
(4) if(条件式)
{
文11;
文12;
…
文1n;
}
else
{
文21;
文22;
…
文2m;
}
(4)はまず条件式を評価し、その結果が真の場合(条件を満足した)、文11〜文1nまでの文を連続に実行します。もし、評価結果が偽の場合(条件を満足しなかった)、文21〜文2mまでの文を連続に実行します。次に、この機能を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int a , b , c;
a = b = c = 0;
scanf (“%d”, &a);
if ( a > 20 )
{
++b;
printf ("%d bad data is %d\n", b , a);
}
else
{
++c;
printf ("%d good data is %d\n", c , a);
}
}
このプログラムは、if文で分岐されたそれぞれの回数を求めています。
次に条件式の評価後、分岐して実行する文の中に、さらにif文を書くプログラムを以下に示します。このプログラムは数値のデータを読み込み、そのデータが10より小さい、20より大きい、10と20の間の場合の3つに分類するものです。
#include <stdio.h>
main()
{
int a ;
scanf (“%d”, &a);
if ( a >= 10 )
if ( a < 20 ) printf ("10 < a < 20 %d\n" , a);
else printf ("a >= 20 %d\n" , a);
else printf ("a <= 10 %d\n" , a);
}
このようにif文の評価後、実行する文の中にさらにif文を書くと、elseがどのif文のものなのかわかりにくくなります。そこで、この例のようにif文とelseの関係が誰にでも一目でわかるようにプログラムを書くようにしましょう。
上記のプログラムの if 文の使い方を一般的に書くと次のようになります。
if(条件式1)
if(条件式2) 文1;
else 文2;
else 文3;
これをフローチャートに書くとFigure 4-3 のようになります。なお、文1、文2、文3には複数の文を書くこともできます。そのとき、それらの文を{ }で囲むことを忘れないようにしてください。
if文とelseの関係についてもう少し考えてみましょう。
if ( a > 0 )
if ( b>3 ) x = 10 ;
else x = 20 ;

Figure 4‑3 if文とelseの関係
このelseは直前のif文(2番目のif文)に連結します。aとbの値を変化させたときのxの値の様子を次に示します。
|
a |
-2 |
-1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
|
b |
|
|
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
x |
* |
* |
* |
20 |
20 |
20 |
10 |
10 |
10 |
10 |
*:if は次の文へ実行が移る
aが0より小さい間は、if文の次の文へ実行が移ります。そして、aが0より大きくかつbが3より小さい問はxが20となります。また、bが3より大きくなるとxは10となります。
次に、{ }を用いてifとelseの連結関係を変えてみましょう。
if ( a > 0 )
{
if ( b>3 ) x = 10 ;
}
else x = 20 ;
このelseは最初のif文に連結します。前の例と同様にaとbの値を変化させたときのxの値の様子を次に示します。
|
a |
-2 |
-1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
|
b |
|
|
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
x |
20 |
20 |
20 |
* |
* |
* |
10 |
10 |
10 |
10 |
*:if は次の文へ実行が移る
aが0より小さい間はxが20となります。aが0より大きくかつbが3より小さい間はif文の次の文を実行します。また、bが3より大きくなるとxは10となります。このようにまったく同じif文でも{ }で囲むと処理内容が違ってきますので注意してください。
switch文は、処理の流れを複数方向に分岐するためのものです。前節で説明したif文は、処理の流れを2方向に分岐するためのものですが、if〜elseを使うことによって複数方向に分岐することもできます。しかし、if〜elseを使って複数方向に分岐させるようにするとプログラムが非常に複雑になってしまいます。特に、ifとelseの関係がわかりにくくなってしまいます。このようなことから処理の流れを複数方向に分岐するときは、switch文を使うようにしましょう。switch文は次のように書きます。
switch(条件式)
{
case 定数式1: 文1;
case 定数式2: 文2;
...
case 定数式n: 文n;
default: 文d;
}
まず、switch文に指定されている条件式とcaseの定数式が一致したとき、一致したcaseの文に実行が移り、それ以降のcaseの文がすべて順々に実行されます。つまり、条件式と一致したcaseが見つかると、それ以降に指定されているcaseの定数式を無視して、一致したcaseの文から実行が始まることになります。もし、条件式とcaseの定数式が一致するものがなかった場合には、defaultで指定されている文を実行することになります。このdefaultには定数式は指定しません。もし、defaultの指定がなかった場合には何も実行しないことになります。
C言語では、caseの定数式はラベルとして扱われますので、caseの定数式の後にコロン(:)を書いて、次に実行文を書きます。なお、caseは複数指定することができます。なお、1つのcaseに指定できる実行文は、複数の文で構成されていてもかまいません。次に、switch文を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
char data ;
scanf ( “%c” , &data ) ;
switch (data)
{
case ‘a’ : printf (“(1)à”);
case ‘b’ : printf (“(2)à”);
case ‘c’ : printf (“(3)à”);
default : printf (“(default)”);
}
}
このプログラムは、文字のデータを変数dataに読み込み、その内容をa、b、cとそれ以外の文字に分類するためのものです。いま、データとしてaを入力すると次のような結果が出力されます。
(1)-->(2)-->(3)-->(default)
データとしてcを入力すると次のような結果が出力さ机ます。
(3)-->(default)
つまり、条件式とcaseの定数式が一致した以降の文が順に実行されることになります。
次に、条件式とcaseの定数式について説明します。switch文で指定する条件式やcaseの定数式は、式か定数でなければなりません。この式と定数のデータ型は、整数型か文字型のみです。つまり、次に示すような文字列をcaseの定数式に指定することはできません。
switch (data)
{
case “ranran”: r_data++ ;
case “nene” : n_data++ ;
}
通常、switch文はここまで説明してきた使い方よりも、条件式を満足したcaseの文のみを実行するといった使い方が多いと思います。それでは、そのようにするにはどうすればよいのでしょうか。条件式を満足したcaseのみの文を実行するには次のように書きます。
switch(条件式)
{
case 定数式1: 文1;
break ;
case 定数式2: 文2;
break ;
...
case 定数式n: 文n;
break ;
default: 文d;
break ;
}
以上のことをフローチャートに書くとFigure 4-4 のようになります。それでは、このフローチャートをもとに書いたプログラムを以下に示します。
#include <stdio.h>
main()
{
char data ;
scanf(“%c” , &data);
switch (data)
{
case ‘a’ : printf (“(1)à”);
break ;
case ‘b’ : printf (“(2)à”);
break ;
case ‘c’ : printf (“(3)à”);
break ;
default : printf (“(default)”);
break ;
}
}

Figure 4‑4 switch文
このプログラムを実行しデータとしてaを入力すると次のように出力されます。
(1)à
次に、データとしてcを入力すると次のように出力されます。
(3)à
このように、break文(補助文)を使用すると条件式と一致したcaseの文のみを実行することになります。つまり、break文はswitch文の実行を中断し、強制的に終了させるためのものです。
ここまで説明してきたif文やswitch文は、処理の流れを2つの方向または複数の方向に分岐するためのものです。ここで説明するwhile文は探り返しを制御するためのものです。while文は次のように書きます。
while(条件式)
{
文1;
文2;
...
文n;
}
while文はwhile文で指定した条件式を満足している間(評価評価が真)、文1〜文nまでの文を繰り返し実行します。そして、条件式を満足しなくなったら(評価結果が偽)while文の実行を終了し、while文の次の文へ実行が渡されます。このwhile文の機能を確認するためのプログラムを以下に示します。
#include <stdio.h>
main()
{
int cnt = 1 ;
while ( cnt < 5 )
{
printf (“cnt = %d\n”, cnt) ;
++cnt ;
}
}
このプログラムは、変数cntが5より小さい間、変数cntに1を加えてその内容を出力する処理を繰り返し実行します。そして変数cntが5になったら、このwhile文の実行を終了します。このプログラムを実行すると次のような結果が出力されます。
cnt = 1
cnt = 2
cnt = 3
cnt = 4
次に、while文のいろいろな書き方について説明します。
while文の条件式を次のように1と指定すると条件式の評価結果が常に真となりますので、文1から文nまでの文を無限操り返し実行することになります。
While( 1 )
{
文1;
...
文n;
}
while文は、条件式の評価結果が偽にならないかぎりwhile文を終了することはできません。そこで、C言語にはwhile文の条件式の結果が真の間でもwhile文を強制的に終了させたり、操り返しの途中からふたたび繰り返しの先頭の文へ戻ったりするための文(補助文)が用意されています。強制的に終了するためにはbreak文を、繰り返しの先頭へ戻るためにはcontinue文を使用します。次にbreakとcontinue文の機能をFigure 4-5 に示します。

Figure 4‑5 breakとcontinue文
それでは、breakとcontinue文を使ったプログラムを以下に示します。プログラムの内容は、任意の数値を20個読み込み、その合計を求めるためのものです。ただし、読み込んだ数値がマイナスだったら読み込みを終了して、それまで読み込んだデータの合計を出力します(break文の利用)。なお、10を越えるデータを読み込んだ場合には、そのデータを合計に加算しないことにします(continue文の利用)。
#include <stdio.h>
main()
{
int inp_data, cnt, total;
while(cnt < 20)
{
scanf("%d" &inp_data) ;
if (inp_data > 10) continue;
if (inp_data < O) break;
cnt++ ;
total += inp_data;
}
printf ("cnt=%d total=%d\n" , cnt , total ) ;
}
for文は、while文と同様に繰り返しを制御するためのものです。for文は次のように書きます。
式1:初期値設定式 式2:ループ実行判断式 式3:ループ付加実行式
For ( 式1 ;
式2 ; 式3 )
{
文1;
文2;
...
文n;
}
for文は、繰り返し(ループ)に入る前に式1を実行します。次に式2を実行(評価)して、その結果が真であればループに入ります。つまり、文1から文nまでの文を連続して実行することになります。1回ループすると式3を実行します。for文で注意しなければならないことは、式1、式2、式3をセミコロン(;)で区切って指定することです。for文の式1を初期値設定式、式2をループ実行判断式、3をループ付加実行式と呼びます。次にfor文を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int j ;
for ( j=1 ; j<5 ; j++ )
{
printf (“j = %d\n”, j);
}
}
このプログラムのfor文の式1、式2、式3は次のように指定しています。
式1 j=1 (初期値設定式)
式2 j<5 (ループ実行判断式)
式3 j++ (ループ付加実行式)
このプログラムはまず変数jに1をセット(初期化)しループに入ります。このループはjが5より小さい間繰り返されます。ループが1回終了するごとにjに1を加えています。jの値が5になるとfor文の実行は終了します。以下はこのプログラムの実行結果です。
j = 1
j = 2
j = 3
j = 4
このようにfor文は、式2の内容が真の場合、{と}(プログラムブロック)に囲まれている文を実行することになります。C言語では、ループの処理を中断したりするための補助文として、breakとcontinue文が用意されています。break文を用いると、Figure 4-6 に示すように繰り返しの途中で強制的にfor文を終了させることができます。また、continue文を用いると、以下に示すように繰り返し処理を途中で終了し、あたかも正常に終了したかのようにして式3の実行に移ります。
それでは、breakとcontinue文を使ったプログラムを以下に示します。プログラムの内容はwhile文で使用したプログラムと同じです。
#include <stdio.h>
main()
{
int inp_data, cnt, total ;
for (cnt=1 ; cnt<=20 ; cnt++)
{
scanf("%d" &inp_data) ;
if (inp_data > 10)
{
cnt-- ;
continue ;
}
if (inp_data < O) break ;
total += inp_data ;
}
printf ("cnt=%d total=%d\n" , cnt , total ) ;
}

Figure 4‑6 for 文のbreakとcontinue
次に、for文のいろいろな書き方について説明します。
for( ; ; ){ .. .. }
これは、for文の式1、式2、式3が省略されています。式2が省略されているので、ループ実行判断式の評価結果が常に真となります。つまり、無限ループすることになります。
for( a=0 , b=10 ; a<=10 ; a++ , b-- ){ 文1.. .. 文n }
これは、for文の式1と式3に順序演算子を使っています。ループに入る前に変数aに0、変数bに10をセットします。aの内容が10より小さい間、文1から文nまでの文を繰り返し実行します。1回のループが終わるとaに1を加え、さらにbから1を引きます。
次に、for文が2重(ネスト)になった場合のbreak文の使い方について説明します。
For ( 式1 ; 式2 ; 式3 )
{
for ( 式4 ; 式5 ; 式6 )
{
ループの範囲 ループの範囲
文1;
if ( .. ) break ;
文n;
}
文a;
if ( .. ) break ;
文p;
}
このようにfor文がネストになっている場合、break文はそのbreak文を囲むもっとも内側のループを抜けることになります。つまり、この例のようにfor文のネストが2重になっている場合には、break文が2つ必要になってきます。
do〜while文は、for文やwhile文と同様に繰り返しを制御するためのものです。このdo〜while文は次のように書きます。
do
{
文1;
文2;
...
文n;
} while (条件式) ;
do〜while文の技能をフローチャートに書くとFigure 4-7 のようになります。do〜while文は、まず文1から文nを実行します。そして、while文に指定されている条件式を満足していれば(評価結果が真)、再び文1から文nを繰り返し実行します。もし、条件式を満足していなければ(評価結果が偽)、do〜while文の次の文に実行が移ります。つまり、do〜while文は必ず1回は文1から文nを実行することになります。
do〜while文を使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int a = 1 ;
do
{
a++ ;
printf (“a = %d\n”, a) ;
} while (a < 5) ;
}

Figure 4‑7 do〜while文
このプログラムは、まず変数aに1を加え、その変数aを出力します。そして、変数aの内容が5より小さい間、この処理を繰り返し実行します。もし、変数aが5より大きくなるとdo〜while文の実行は終了します。このプログラムを実行すると次のような結果が出力されます。
a = 1
a = 2
a = 3
a = 4
a = 5
次に、do〜while文におけるbreakとcontinue文の機能について説明します。Figure 4-8 に示すように、break文はwhile文やfor文の場合と同じように繰り返しの途中で強制終了し、do〜while文の実行を終了します。一方、continue文は繰り返しを中断し、最初から再び実行します。

Figure 4‑8 do〜while文のbreakとcontinue
goto文は、無条件に指定されたラベルの文へ分岐するためのものです。繰り返し処理から抜けだすのに使用されます。しかし、あまりgoto文の多いプログラムは関心できません。goto文は、次のように書きます。
文1;
文2;
goto label a ;
...
文j;
label a: 文n;
goto文は無条件にgoto文に指定されたラベルの文に制御を移します。C言語ではラベル(名札)は次のように書きます。
ラベル:文
ラベルに使用できる文字の種類や名前のつけ方は、変数の名前をつける場合と同じです。繰り返しから抜けだす方法についてまとめると以下のようになります。
|
C言語の補助文 |
|||
|
|
while文 |
for文 |
do〜while文 |
|
break文 |
強制的に終了 |
強制的に終了 |
強制的に終了 |
|
continue文 |
繰り返しの先頭に戻る |
繰り返しを途中で終了し、式3を実行する |
繰り返しを中断し、条件式の評価から再び実行する |
|
goto文 |
無条件分岐 |
無条件分岐 |
無条件分岐 |
C言語では、キーボードからデータを入力したりディスプレイ装置へ出力したりする操作は、すべて標準入出力関数で行ないます。今回では、この標準入出力関数について説明します。なお、本書ではファイルの入出力関数については説明しません。
C言語には、標準入出力関数として、Cコンパイラによっても異なりますが約40種類の関数が用意されています。それらの中から、ここでは標準入力装置からの入力と標準出力への出力のための関数について説明します。標準入力装置からのデータの入力には、次の2つの関数があります。
scanf関数 書式付の入力
getchar関数 1文字の入力
一方、標準出力装置へのデータの出力には、次の2つの関数があります。
printf関数 書式付の出力
putchar関数 1文字の出力
これらの関数を使用する場合には、プログラムの先頭に #include 命令が必要となります。#include命令のパラメータには、ファイル名 stdio.h を指定しなければなりません。この #include <stdio.h> を指定することにより、次に示すように実際の標準入出力装置が決定されます。
標準入力装置 キーボード
標準出力装置 ディスプレイ装置
この関数は、標準入力装置(キーボード)からデータを書式付で入力するためのものです。 scanf関数は、次のように書きます。
scanf(変換書式,入力並び);
変換書式は文字列で指定します
入力並びはアドレスで指定します
具体的には次のように書きます。
Scanf ("%d %d", &data1, &data2) ;
変換書式 入力並び
この例の変換書式は、"%d %d"です。このように変換書式は文字列で指定しますので変換指示の組み合せをダブルクォーテーションで囲みます。入力並びは、&data1、&data2 です。変換指示は必ず%で始まり、その後に変換の種類を指定します。この例では、変換書式を次のように書いています。
"%d [SPACE] %d"
これは、入力データの先頭からスペース記号([SPACE])までのデータを10進の数字列として入力します。さらに、スペース記号の次のデータから最後室での数字列を10進の数字として入力することを意味しています。変換書式内のスペース記号を入力区切り文字といいます。つまり、入力区切り文字とは、変換指示と変換指示の間の文字をいいます。次に、入力区切り文字の例を示します。
%d [SPACE] %d スペース([SPACE])が入力区切り文字
%d , %d カンマ(,)が入力区切り文字
%d * %d アスタリスク(*)が入力区切り文字
%2d%4d 入力区切り文字なし
次に、変換指示について説明します。変換指示は一般に次のように書きます。
% (*) (幅)変換文字
()は省略可能
(幅)入力する桁数を指定します。省略した場合は区切り文字までとなります
(*)この変換位置に対応する数字列(文字列)を読み飛ばします
変換文字には、次に示す6種類があります。
|
d |
入力した数字列を10進数とみなし、符号つき整数に変換します。 |
|
x |
入力した英・数字列を16進数となみし、整数に変換します。入力できる文字や数字は、0〜9、A〜Fのみです。 |
|
o |
入力した数字列を8進数とみなし、符号なし整数に変換します入力できる数字は、0〜7のみです。 |
|
c |
1文字を入力します。 |
|
s |
入力区切り文字までの文字列を入力します。入力した文字列の最後にヌル文字(\0)を付加します。 |
|
f |
入力した数字列を浮動小数点数に変換します。 |
(a)変換指示 %d、%x、%oによる入力
変換指示 %d、%x、%oを使ったプログラムを以下に示します。
#include <stdio.h>
main()
{
int dec_data, hex_data, oct_data ;
/* decimal data */
printf ("convert.dec ?") ;
scanf ("%d", &dec_data) ;
printf ("dec_data= %d\n", dec_data) ;
/* hexadecimal data */
printf ("convert.hex ?") ;
scanf ("%x" , &hex_data) ;
printf ("hex_data= %d\n", hex_data) ;
/* octal data */
printf ("convert oct ?") ;
scanf ("%o", &oct_data) ;
printf ("oct_data= %d\n", oct_data) ;
}
このプログラムで入力データとして123を読み込んで処理した様子を次に示します。
|
入力 |
変換指示 |
結果 |
書式 |
|
123
|
%d %x %o |
123 291 83 |
(10進) (16進) (8進) |
(b)入力区切り記号による入力
(1) スペース [SPACE] が入力区切り記号
123 [SPACE] 45を123と45に分けて入力する。
scanf ("%d [SPACE] %d", &a, &b) ;
(2) スラッシュ (/) が入力区切り記号
87/12/25を87、12、25の3つに分けて入力する。
scanf ("%d/%d/%d", &a, &b, &c) ;
(3) カンマ (,) が入力区切り記号
123,45を123と45に分けて入力する。
scanf ("%d, %d", &a, &b) ;
(4) スペースとカンマが入力区切り記号
123 [SPACE] 45、789を123、45、789の3つに分けて入力する。
scanf ("%d [SPACE] %d, %d", &a, &b, &c) ;
(5) 入力桁数の指定
12345を123(3桁)と45(2桁)に分けて入力する。
scanf ("%3d %2d", &a, &b) ;
(6) 読み飛ばしの指定
123456を12(2桁)と56(2桁)に分けて入力する(34は読み飛ばします)。
scanf ("%2d %*2d %2d", &a, &b) ;
(c)文字と文字列の入力
ここで使用する変数は次のように宣言されているものとします。
(1) 1文字の入力
ABCをA、 B、 Cの3つに分けて入力する。
scanf ("%c%c%c", &a, &b, &c) ;
(2) 文字列を配列に入力
ABCを文字列ABCとして入力する。
scanf ("%s”, &d[O]) ;
または
scanf ("%s", &d) ;
入力された文字列の終わりにヌル文字(\0)が自動的に付加されます。
(3) 文字列を配列に入力
ABCDEを文字列ABCとDEに分けて入力する。
scanf ("%3s%2s", &d, &e) ;
入力された文字列の終わりにヌル文字(\0)が自動的に付加されます。
(4) 文字列の読み飛ばし
ABCDEFGを文字列ABとEFGに分けて入力する(CDは読み飛ばします)。
scanf ("%2s %*2s %3s", &d, &e) ;
この関数は標準出力装置(ディスプレイ装置)ヘデータを書式付で出力するためのものです。printf関数は、次のように書きます。
printf(変換書式,出力並び);
変換書式は文字列で指定します
出力並びは出力する変数や配列を指定します
具体的には、次のように書きます。
printf ("%d %d\n", a, b) ;
変換書式 出力並び
この例の変換書式は、 "%d %d\n" です。このように変換書式は文字列で指定しますので、変換指示の組み合せをダブルクォーテーションで囲むことになります。入力並びはa、bです。
変換指示は必ず%で始まり、その後に変換の種類を指定します。この例では、変数aの内容を10進数に変換して出力し、次にスペースを出力し、その後に変数bの内容を10進数に変換して出力します。さらに、制御文字のニューライン(\n)を出力することになります。変換指示は、一般に次のように書きます。
|
% (+/-) (0) (幅) (. 精度) (I) 変換文字 ()は省略可能 |
|
|
(. 精度) |
浮動小数点数を出力するときの小数点以下の桁数を指定します |
|
(幅) |
出力幅を指定します。もし、出力する数値や文字列がここで指定した桁数を超える場合には、自動的に必要な桁数まで拡張されます |
|
(0) |
出力する数値や文字列が出力幅より小さい場合には、空きの桁をゼロで出力します。省略するとスペースで出力します |
|
(+/-) |
−と書くと出力幅内で左詰めで出力し、+と書くと出力幅内で右詰めで出力されます。省略すると右詰めで出力されます |
変換文字には、次の9種類があります。
|
d |
符号つき整数を10進数に変換して出力します |
|
x |
符号なし整数を16進数に変換して出力します |
|
o |
符号なし整数を8進数に変換して出力します |
|
u |
符号なし整数を10進数に変換して出力します |
|
c |
文字として出力します |
|
s |
文字列として出力します |
|
e |
floatとdoubleの型の数値を±n.nnnE±mmmの形式で出力します。nとmは±n.nnnx10±mmmの意味です |
|
f |
floatとdoubleの型の数値を±mmm.nnの形式で出力しますmmmとnnの桁数は幅・精度で指定します。つまり、幅はmmmの桁数とnnの桁数を加えた合計にピリオドの1を加えた値となります。nnは小数点以下の桁数なので変換書式の精度で指定します |
|
g |
floatとdoubleの型の数値をまずf変換して出力します。これで表現できないときはe変換して出力します |
出力書式には、さらに次の規則があります。
(a) %で始まらない文字や文字列はそのまま出力します
(b) 画面の出力制御をするための制御文字を書くことができます
(a) 変換指示%d、%x、%o による出力
数値の100を変換指示 %d、%x、%oを用いて出力するプログラムを以下に示します。
#include <stdio.h>
main()
{
int a = 100 ;
printf ( "convert-dec = %d\n" , a) ;
printf ( "convert-hex = %x\n" , a) ;
printf ( "convert-oct = %o\n" , a) ;
}
このプログラムを実行すると次のような結果が出力されます。
convert-dec = 100
convert-hex = 64
convert-oct = 144
(b) 整数値の出力
ここでは、次のように宣言されている変数aの内容を右詰め、左詰めで出力する方法などについて説明します。
int a = 1234 ;
(a) printf ("data=%d" , a) ; data=1234
(b) prjntf ("%-5d" , a) ; l234 (左詰め)
(c) printf ("%+06d" , a) ; OO1234 (右詰め)
(c) 文字と文字列の出力
ここでは、次のように宣言されている文字Aと文字列XYZを出力する方法について説明します。
char a ='A' ;
char b[4] = "XYZ" ;
(a) printf ("data =%c", a) ; data=A
(b) printf ("data =%s", &b[O] ) ; data=XYZ
(c) printf ("data %s", b) ; data=XYZ
(d) printf ("%-6s", b) ; XYZ□□□ (左詰め)
(e) printf ("%+6s", b) ; □□□XYZ (右詰め)
(d) 浮動小数点数の出力
ここでは、次のように宣言されている変数aとbの内容をf変換とe変換して出力する方法について説明します。
float a = 3.1234
float b = 43.1234
1. printf ("%6.4f", a) ; 3.1234
2. printf ("%10.6f", a) ; 3.123400
3. printf ("%6.2f", a) ; 3.12
4. printf ("%-10.6f", a) ; 3.1234
5. printf ("%12.5e", b) ; 4.31234E+001
6. printf ("%14.6e", b) ; 4.312340E+001
ここでは、1つの変数名で複数のデータをまとめて格納することができる配列について、その宣言の方法、初期化の方法、参照の方法などについて説明します。C言語での配列の考え方はBASICやFORTRANと同じですので、他のプログラミング言語ですでに配列を使ったことのある読者にとっては難しくないと思います。
いま100個のデータを読み込み、それぞれを異なる変数に格納することを考えてみましょう。みなさんは、読み込んだデータをどのような変数名に格納しますか。単純に考えると次のように考えられます。
inp_data1, inp_data2, ..., inp_data99, inp_data100
このように、100個の異なる変数名を考えることは意外と大変なことです。そこで、これらのデータを1つの変数名で管理できるようにしたのがこれから学習する配列です。上の問題は、配列を使うと次のように書くことになります。
inp_data[100]
これで、配列名inp_dataに100個のデータを格納することができます。このように、配列を使用すると、1つの変数名のもとに複数のデータを格納することができます。
C言語では配列を次のように宣言します。
記憶クラス データ型 配列名[要素数];
(a)記憶クラス
配列の記憶クラスには、自動配列、静的配列、レジスタ配列、外部配列の4つの記憶クラスがあり、それぞれの機能は変数の場合と同じです。何も指定しないと自動配列になります。
(b)データ型
データ型は変数のデータ型と同じです。つまり、整数型、浮動小数点数型、文字型の3種類のデータ型を指定することができます。
(c)配列名
配列の名前のつけ方は、変数の名前をつけるときの規則と同じです。
(d)要素数
要素数とは配列の大きさのことです。つまり、配列に格納できるデータの個数のことです。各要素には、0から要素数マイナス1までの要素番号がつけられます。つまり、要素数が100の場合、要素番号は0から99までとなります。この要素番号を用いて配列の内容を参照することになります。要素数や要素番号を書くときは、次に示すように[]内に書きます。
配列名[要素番号]
上の書き方は1次元配列の場合ですが、C言語にも他のプログラミング言語と同じように、2次元配列、3次元配列を宣言することができます。次に、2次元配列と3次元配列の宣言の方法を示します。
記憶クラス データ型 配列名[要素数][要素数] 2次元の配列
記憶クラス データ型 配列名[要素数][要素数][要素数] 3次元の配列
それでは、配列の宣言法について具体的に説明しましょう。
例1: int test[5] ;
これは、次のような配列を宣言しています。
|
記憶クラス |
自動配列(省略) |
|
データ型 |
整数(int) |
|
配列名 |
Test |
|
要素数 |
5 |
|
要素番号 |
0〜4 |
この配列を概念的に示すと次のようになります。
test[0] test[1] test[2] test[3] test[4] 要素番号(0〜4)
この配列には整数以外のデータを格納することができません。
例2: static char name[3] ;
これは、次のような配列を宣言しています。
|
記憶クラス |
静的配列(static) |
|
データ型 |
文字型(char) |
|
配列名 |
Name |
|
要素数 |
3 |
|
要素番号 |
0〜2 |
この配列は記憶クラスが静的配列ですから、プログラムが実行中常にメモリ内に存在することになります。
例3: int test[2][3] ;
これは、次のような2次元の配列を宣言しています。
|
記憶クラス |
自動配列(省略) |
|
データ型 |
整数型 (int) |
|
配列名 |
Test |
|
要素数 |
6 (2 x 3) |
|
要素番号 |
0から1、0から2 |
これは、2行3列の配列を整数型で宣言しています。この配列を概念的に表わすと次のようになります。
test[0][0] test[0][1] test[0][2]
test[1][0] test[1][1] test[1][2]
このように2次元の配列を宣言する場合には、配列名の次に行の要素数、つづいて列の要素数を指定することになります。
例4:char name[2][3][4] ;
これは、次のような3次元の配列を宣言しています。
|
記憶クラス |
自動配列(省略) |
|
データ型 |
文字型 (char) |
|
配列名 |
Name |
|
要素数 |
24 (2 x 3 x 4) |
|
要素番号 |
0から1、0から2、0から3 |
これは、2行3列の配列が奥の方向に4つある3次元の配列を文字型で宣言しています。この配列を概念的に表わすと次のようになります。
name[0][0][3] name[0][1][3] name[0][2][3]
name[1][0][3] name[1][1][3] name[1][2][3]
name[0][0][2] name[0][1][2] name[0][2][2]
name[1][0][2] name[1][1][2] name[1][2][2]
name[0][0][1] name[0][1][1] name[0][2][1]
name[1][0][1] name[1][1][1] name[1][2][1]
name[0][0][0] name[0][1][0] name[0][2][0]
name[1][0][0] name[1][1][0] name[1][2][0]
このように3次元の配列を宣言する場合には、配列名の次に行の要素数、列の要素数、つづいて奥方向の要素数の順に指定することになります。
配列を宣言した時点で、配列の各要素にデータをセットすることを初期化といいます。この初期化ができる記値クラスは、静的配列と外部配列のみです。つまり、自動配列とレジスタ配列の配列は初期化することができません。配列を初期化する場合には、次のように書きます。
記憶クラス データ型 配列名 [要素数] ={値1,値2,...,値n};
配列に初期値をセットする場合には、配列の宣言の後にイコール記号(=)をつけて、初期値をカンマ(,)で区切りながら要素数と同じ数だけ指定します。さらに、これらの初期値を{}で囲むことになります。
(1) static int test[5] = {1, 2, 3, 4, 5} ;
これは配列testが次のように初期化されます。
test[0] test[1] test[2] test[3] test[4]
1 2 3 4 5
(2) static char name[3] = {'a', 'b', 'c'} ;
これは配列nameが次のように初期化されます。
name[0] name[1] name[2]
a b c
ここまでは1次元配列の初期化について説明してきました。次に、2次元の配列の初期化について説明します。2次元配列の初期化は次のように書きます。
記憶クラス データ型 配列名[行の要素数][列の要素数]={
{値1,値2,...,値n},
{値1,値2,...,値n}
};
2次元配列に初期値をセットする場合には、基本的には1次元配列の場合と同じように行ないます。つまり、1次元配列が複数個(行の要素数だけ)集まったと考えてください。それでは、具体的な例で考えてみましょう。
(3) static int test[2][3] = {
{1, 2, 3},
{4, 5, 6}
} ;
これは配列testが次のように初期化されます。
|
|
3列 |
||
|
2行 |
1 |
2 |
3 |
|
4 |
5 |
6 |
|
(4) static char name[3][3] = {
{'a', 'b', 'c'},
{'d', 'e', 'f'},
{'x', 'y', 'z'}
};
これは配列nameが次のように初期化されます。
|
|
3列 |
||
|
3行 |
a |
b |
c |
|
d |
e |
f |
|
|
x |
y |
z |
|
(5) static int test[2][3] = {1, 2, 3, 4, 5, 6} ;
これは、2次元の配列を1次元の配列とみなして初期化する例です。その結果は(3)と同じように初期化されます。つまり、2次元の配列は連続した1次元の配列とみることもできるわけです。
配列の参照とは、ある値を配列にセットしたり、配列の内容を出力したりすることです。このような操作を行なうには要素番号を用いて行なうのが基本です。しかし、この要素番号を用いた配列の参照は実際のプログラムでは意外と使われていません。それに代ってポインタを使って配列を参照する方法が多く用いられています。ここでは、これら2つの内要素番号を用いた参照について説明します。ポインタの考え方について次回で説明することにします。
配列を参照するには、要素番号を用います。この要素番号は必ず0から始まって配列の要素数から1を引いた番号まで存在することになります。要素番号を用いて配列を参照するには、参照したい配列名の次に参照したい要素の番号を[ ]の中に指定します。
参照配列名[参照要素番号]
たとえば、配列testの各要素に数値をセットするには次のように書きます。
int test[3] ;
test[0] = 1 ; /* 要素番号0に1をセット */
test[1] = 2 ; /* 要素番号1に2をセット */
test[2] = 3 ; /* 要素番号2に3をセット */
これは、配列testの要素番号0に数値の1が、要素番号1に数値の2が、要素番号2に数値の3がセットされることを意味しています。これで配列testのすべての要素に数値がセットされたことになります。次に配列の内容を他の変数にセットする場合について考えてみましょう。
static int a[2] = { 1, 2 } ;
int b ;
b = a[0] ;
これは、変数bに配列aの要素番号0の内容をセットすることを意味しています。この例では、配列aは初期化されていますので、変数bには数値の1がセットされることになります。
次に、配列の各要素の内容を出力してみましょう。
static int test[5] = {1, 2, 3, 4 , 5} ;
int i ;
for (i=0 ; i <5 ; i++)
{
printf ("test [%d] ... %d\n" , i , test [i]) ;
}
これは配列名testを整数型で宣言し、さらに初期化しています。この初期化された配列testの各要素の内容をprintf関数を使って出力します。1回のprintf関数で、1つの要素の内容を出力します。for文を使って配列testの要素数だけ繰り返し実行します。つまり、5回ループすることになります。出力される様子を次に示します。
|
ループ回数 |
iの値 |
出力内容 |
|
1 2 3 4 5 |
0 1 2 3 4 |
test [0] … 1 test [1] … 2 test [2] … 3 test [3] … 4 test [4] … 5 |
ここまでは1次元の配列についてのみ考えてきました。次に、2次元の配列の参照法についても考えてみましょう。
まず、2次元の配列の各要素に値をセットする場合について説明します。
int test[2][3] ;
test[0][0] = 1 ; /* (1)回目 */
test[0][2] = 2 ; /* (2)回目 */
test[1][2] = 3 ; /* (3)回目 */
これは、2行3列の配列を整数型で宣言し、3つの要素にそれぞれ値をセットしています。2次元の配列に値をセットする方法は、1次元の配列に値をセットする方法と基本的に同じです。つまり、セットしたい配列名の後に行方向の要素番号、次に列方向の要素番号を指定することになります。実際には、次のように値がセットされます。
|
3列 |
||
|
2行 |
(1) 回目 1 |
|
(2) 回目 2 |
|
|
|
(3) 回目 3 |
|
次に、2次元の配列の内容を出力する方法について説明します。
int i, j;
static char name[3][4]= {
{'a', 'b', 'c', 'd'},
{'e', 'f', 'g', 'h'},
{'w', 'x', 'y', 'z'}
};
for (i=0 ; i <3 ; i++)
{
for (j=0 ; i <4 ; j++)
{
printf("name[%d][%d] ... %c\n", i, j, name[i][j]) ;
}
}
これは、3行4列の配列nameを文字型で宣言し、さらに初期化しています。出力は、for文を使って配列nameの各要素の内容を出力しています。変数iは行の要素番号、変数jは列の要素番号を制御するのに使用しています。出力される様子を次に示します。
|
iの値 |
jの値 |
出力内容 |
|
0 |
0 |
name [0][0] … a |
|
0 |
1 |
name [0][1] … b |
|
0 |
2 |
name [0][2] … c |
|
0 |
3 |
name [0][3] … d |
|
1 |
0 |
name [1][0] … e |
|
1 |
1 |
name [1][1] … f |
|
1 |
2 |
name [1][2] … g |
|
1 |
3 |
name [1][3] … h |
|
2 |
0 |
name [2][0] … w |
|
2 |
1 |
name [2][1] … x |
|
2 |
2 |
name [2][2] … y |
|
2 |
3 |
name [2][3] … z |
前回では、配列について、その宣言の方法、初期化の方法、参照の方法などについて説明しました。今回は、ポインタを用いたアドレス演算子と配列の参照方法についても説明します。
アドレス演算子と間接演算子は、BASICやFORTRANにない演算子で、システムプログラムなどによく使用されます。ここでは、2つの演算子の基本的な考え方について説明します。まず、アドレス演算子から説明します。アドレス演算子とは、変数のデータ領域が確保されているメモリ内の位置に関する情報(アドレス)を求めるための演算子です。このアドレス演算子はアンパーサンド(&)で表わします。つまり、次に示すようにアドレスを求めたい変数の前に&をつけることになります。
&変数名;
もう少し具体的に説明しましょう。いま、次のように指定されたとします。
int a ;
a = 1 ;
&a ;
変数aが整数型として宣言され、さらに1がセットされています。次にその変数aのデータ領域のアドレスを求めています。変数aがメモリに確保される様子を以下に示します。

Figure 7‑1 アドレスとデータ概念
Figure 7-1は変数aのデータ領域がメモリの11番地に確保され、そこに1がセットされていることを示しています。変数aはint型で宣言されていますので、実際は4バイトのデータ領域が確保されるわけですが、説明上1バイトとします。この状態で変数aのアドレスを求めると11番地となります。つまり、&aと書くとその結果は11となるわけです。
次に求めた変数aのアドレスを変数bにセットしてみましょう。
int a ,b ;
a = 1 ;
b = &a ;
みなさんはこのように書くでしょうか?これは間違いです。つまり、変数bはintと宣言されています。これでは変数bにアドレスをセットすることはできません。そこで、変数bを次のように宣言します。
int a, *b ;
a = 1 ;
b = &a ;
つまり、変数bを宣言するところで変数bの前にアスタリスク(*)をつけることになります。これは、変数bが整数型変数のデータ領域のアドレスを確保することを意味しています(ポインタ変数)。この様子を以下に示します(変数aのデータ領域は11番地に、変数bのデータ領域は2番地に確保されたとします)。Figure 7-2 のように、2番地には変数aのデータ領域のアドレスである11がセットされます。

Figure 7‑2 変数aとbのアドレスとデータ
次に間接演算子について説明しましょう。この間接演算子をポインタ清算子と呼ぶこともあります。間接演算子とは、直接的にデータを参照するのではなく、ポインタ変数にセットされている内容をアドレスとしてデータを参照するための演算子です。この間接演算子はアスタリスク(*)で表わします。アドレス演算子の説明に使用したプログラムを使って間接演算子について説明しましょう。
int a, *b ;
a = 1 ;
b = &a ;
このプログラムの変数aとbの関連を次に示します(Figure 7-3)。

Figure 7‑3 変数aとbの関連
次に、変数aとbの内容を出力するprintf関数を考えてみましょう。
printf ("a=%d b=%d\n", a, b) ;
このprintf関数を実行すると次のようになります。
a=1 b=11
これはデータを直接的に参照することを意味しています。変数bがポイントしている内容を出力するには、printf関数を次のように書きます。
printf ("a=%d b=%d\n", a, *b) ;
これを実行すると次のようになります。
a=1 b=1
つまり、変数bの前にアスタリスクをつければ変数bにセットされている内容を直接出力するのではなく、その内容をアドレスとして、そのアドレスが示すデータ領域の内容を出力することになります。
ポインタの基本的な考え方とその使い方については、上記で説明しました。そこでポインタを使ったプログラムについて再び説明しましょう。
int a, *b ;
a = 1 ;
b = &a ;
この例では変数aに数値の1を、変数bに変数aのアドレスをセットしています。変数aは10番地、変数bは20番地として、この様子を以下に示します(intは4バイトのデータ領域を確保するものとします)。

Figure 7‑4 aとb変数の領域
Figure 7-4からもわかるように、変数aはメモリの10番地から4バイトの領域が確保され、そこに数値の1がセットされています。変数bも同じように20番地から4バイトの領域が確保され、そこに変数aのアドレスである10番地がセットされています。次に示すように変数bに1を加えてみます。
int a, *b ;
a = 1 ;
b = &a ;
b++ ;
変数bの内容はどうなるでしょうか。変数bには変数aのアドレスである10番地がセットされていますから、10番地+1で11番地になるでしょうか。結果は以下のようになります。

Figure 7‑5 アドレスの加算
変数bの内容は、Figure 7-5に示すように14番地となります。つまり、b++のインクリメント演算は、変数bが整数型のデータ領域のアドレスを格納するように宣言されていますから、変数bの内容に整数型データの大きさ(バイト数)である4を加えることになります。C言語では、このように論理的なアドレスのことをポインタと呼びます。そして、アドレスとはコンピュータのハードウェアで決めたメモリにデータを格納する最小単位のことです。ポインタとアドレスの違いを以下に示します(整数型で説明します)。
このように、プログラムを書くとき、論理的なアドレスであるポインタのみを考えればよいことになります。このことを確認するためのプログラムを以下に示します。
#include<stdio.h>
main()
{
int a, *b ;
a = 1 ;
b = &a ;
printf(“b = %d\n”, b) ;
b++ ;
printf(“b = %d\n”, b) ;
}
このプログラムを実行すると、次のような結果が出力されます。ただし、32ビット系のコンピュータを使用し、変数aが100番地に格納されているものとします。
b = 100 (1)
b = 104 (2)
この結果は、使用するコンピュータによって異なりますが、(1)と(2)の値との差が4(4バイト)になります。16ビット系のコンピュータを使用している場合、int型データは、2バイトのデータ領域を確保しますので、(1)と(2)の差は2(2バイト)となります。
それでは、本題であるポインタの説明に入ることにしましょう。配列の先頭のアドレスを求めるには次のように書きます。
&配列名[0] ; または &配列名;
配列の先頭である要素番号0の前にアドレス演算子であるアンパーサンド(&)をつけて書きます。または、単に配列名の前にアドレス演算子を書くこともできます。
それでは、要素番号n番のアドレスはどのようにして求めればよいのでしょうか。次のように書くことになります。
&配列名[n];
また、2次元の配列の先頭のアドレスを求めるには、次のように書きます。
&配列名[0][0] ; または &配列名 ;
このように、1次元の配列の場合と同じように書くことになります。
次に、1次元の配列の各要素のアドレスを求めるプログラムを以下に示します。
#include <stdio.h>
main()
{
int *add ;
int data[3] ;
add = &data[0] ;
printf ("address (data[0]) ---%d\n", add) ;
add = &data[1] ;
printf ("address (data[1]) ---%d\n", add) ;
add = &data[2] ;
printf ("address (data[2]) ---%d\n", add) ;
}
このプログラムの実行結果は、みなさんが使用しているコンピュータによって異なりますので、各自実行してください。出力結果が4(4バイト)ずつ数字が増えていくことを確認してください(ただし、16ビット系のコンピュータを使用する場合には2(2バイト)ずつ増えていきます)。
次に、ポインタを使って配列の各要素の内容を出力する方法について説明します。それには、間接演算子であるアスタリスク(*)を用いることになります。つまり、次に示すようにアドレスを格納している変数の前に間接演算子を表わすアスタリスク(*)をつけることになります。
int *a , data[3] ;
a = &data[0] ;
これは、変数aに配列dataの先頭のアドレスをセットすることを表わしています。次に、この変数aがポイントしている内容を参照するには次のように書きます。
b = *a ;
変数aの前にアスタリスクをつけることによって、配列dataの要素番号0の内容を示すことになります。つまり *a は data[0] と同じことになります。この例では、配列 data[0] の内容を変数bにセットします(変数bは文字型とします)。
それでは、上記で示したプログラムを変数addがポイントしている内容を出力するように修正してみましょう。修正したものを以下に示します。
#include<stdio.h>
main()
{
int *add ;
static int data[3]={1,2,3} ;
add = &data[0] ;
printf ("address (data[0]) ---%d=%d\n", add, *add) ;
add = &data[1] ;
printf ("address (data[1]) ---%d=%d\n", add, *add) ;
add = &data[2] ;
printf ("address (data[2]) ---%d=%d\n", add, *add) ;
}
さらに、このプログラムを実行すると次のような結果が出力されます(配列dataの先頭のアドレスは200番地とします)。
address (data[0]) ---200 = 1
address (data[1]) ---204 = 2
address (data[2]) ---208 = 3
それでは、配列dataのアドレスを1度だけ求め、あとはポインタを変化させながら、配列の各要素の内容を出力するように以下で示したプログラムを修正してみましょう。
#include<stdio.h>
main()
{
int *add ;
static int data[3]={1,2,3} ;
add = &data[0] ;
printf ("address (data[0]) ---%d=%d\n", add, *add) ;
add++ ;
printf ("address (data[1]) ---%d=%d\n", add, *add) ;
add++ ;
printf ("address (data[2]) ---%d=%d\n", add, *add) ;
}
このプログラムを実行すると以下で示す結果が出力されます。
address (data[0]) ---200 = 1
address (data[1]) ---204 = 2
address (data[2]) ---208 = 3
次に、ポインタを用いて2次元の配列の各要素の内容を出力する方法について説明します。参照方法について説明する前に、もう一度2次元の配列について考えてみましょう。2次元の配列は次のように宣言します。
int data[2][3] ;
これは、次のような2行3列の配列を表わしています。
|
|
3列 |
||
|
2行 |
data[0][0] |
data[0][1] |
data[0][2] |
|
data[1][0] |
data[1][1] |
data[1][2] |
|
これは、配列data[2][3]を概念的に表わしたもので、実際にコンピュータのメモリの上でこのような形で配列のデータ領域が確保されるわけではありません。実際のメモリ上には、次に示すように連続したデータ領域として確保されます。
|
data[0][0] |
data[0][1] |
data[0][2] |
data[1][0] |
Data[1][1] |
data[1][2] |
つまり、2次元の配列でも1次元の配列のように参照することができることになります。
それでは、2次元の配列の各要素の内容をポインタを使って参照するプログラムを以下に示します。
#include <stdio.h>
main()
{
int i ;
char *add ;
static char name[2][3] = {
{'a', 'b', 'c'},
{'x', 'y', 'z'}
};
add = &name[0][0];
for (i=0 ; i<6 ; i++)
{
printf ("%d---%c\n" , i , *add) ;
add++ ;
}
}
このプログラムは、2次元の配列nameを文字型で宣言し、さらに初期化しています。まず、2次元の配列nameの先頭、つまりname[0][0] のアドレスを変数addにセットします。次に、printf関数を使って各要素の内容を出力しています。このprintf関数は、for文を使ってnameの要素の数(6要素)分繰り返し実行します。このプログラムを実行すると、次のような結果が出力されます。
0 --- a
1 --- b
2 --- c
3 --- x
4 --- y
5 --- z
C言語のプログラムは、通常いくつかの関数で構成されています。ここでは、関数の基本的な考え方と関数間のデータの受渡し方法などについて説明します。
C言語での関数とは、数学でよく使う関数と同じ考えをもったものです。いま、数学で
という関数があるとします。この関数にa=1、b=2、c=3、x=2と数値を代入すると関数の値として11を求めることができます。その様子を次に示します。
a=1 b=2 c=3 x=2
f(x) = 1x4 + 2x2 + 3 = 11
このことをプログラムに置き換えて考えてみましょう。読者のみなさんにすっかりおなじみとなったprintf関数で説明することにします。printf関数は、次のように書きます。
Printf(出力書式 ,出力並び) ;
これを関数的に考えると、printf関数の出力書式と出力並びにしたがって、出力並びに指定された変数の内容がディスプレイ装置に出力されます。関数とは何らかのデータを受け取ると、そのデータをもとに処理して結果を出すものです。つまり、切符自動販売機にお金を入れると切符がでてくるのに似ています。自動販売機も1つの関数と考えることができます。
C言語では、関数は一般的に次のように書きます。
関数名(仮引数の並び)
仮引数の宣言;
{
関数内変数や配列の宣言;
実行文;
}
関数名は、変数と同じ規則にしたがって自由につけることができます。ただし、プログラムには関数名がmainという関数が必ず1つ必要です。プログラムの実行は、このmainと名前のついた関数から始まることになります。
仮引数とは、関数が受け取る変数のことです。この変数が複数あるときは、カンマで区切って並ぺることになります。この仮引数は()で囲みますが、仮引数がない関数の場合は、単に () と書きます(省略することはできません)。
関数が受け取る変数のデータの型を宣言します。このデータの仮引数は、渡す側のデータの型と同じでなければなりません。

Figure 8‑1 関数概念
1つのプログラムがいくつかの関数で構成される場合、その関数間のデータの受渡し方が問題となります。ここでは、具体的な例を使ってC言語での関数間のデータの受渡し方法について説明します。関数にはFigure 8-2 に示すように“呼びだす側”と“呼ばれる側”とがあります。C言語では、関数間のデータを渡す方向は、次の2つ力言あります。

Figure 8‑2 関数間のデータの受渡し
(a) 呼びだす側から呼ばれる側へ値を渡す。
(b) 呼ばれる側から呼びだす側へ値を渡す。
関数間のデータの受渡し方法には、いくつかの方法がありますがここでは、以下の(1)、(2)について説明します。
(1) 関数の引数として変数の値を直接渡す。
(2) 関数の引数として変数のアドレス(ポインタ)を渡す。
この方法は、関数を呼びだす側から呼ばれる側へのみ値を渡すことができます。
#include <stdio.h>
main()
{
int data1,data2;
data1 = 12 ;
data2 = 3 ;
func (data1 , data2) ;
}
func (a,b)
int a , b ;
{
int y ;
y = a*b ;
printf("(a*b) =%d\n",y) ;
}
上に示したプログラムはmain関数とfunc関数の2つの関数でできています。その2つの関数間のデータ受渡し様子を次のFigure 8-3 に示します。

Figure 8‑3 関数の引数として変数の値を直接渡す方法
上記示したようにmain関数からfunc関数へ変数data1とdata2の内容が直接渡されます。 func関数では、その内容を変数aとbで受け取り、変数aとbの掛算をし、その結果を出力します。このように、関数間で変数の値を直接渡す場合は、呼びだす側の変数の内容は呼びだされる側の処理によって変わることはありません。上記のプログラムを実行すると次のような結果が出力されます。
(a * b) = 36
関数間では配列のデータを直接渡すことはできません。このことから、文字列のデータを直接渡すことができないことになります。
この方法では、関数を呼びだす側と呼ばれる側の両方向へ値を渡すことができます。

Figure 8‑4 関数の引数として変数のアドレス(pointer)を渡す方法
(a)数値データ
以下に示したプログラムも、main関数とfunc関数の2つの関数でできています。この2つの関数間のデータ受渡し様子は以下のとおりです。
#include <stdio.h>
main()
{
int data1, data2 ;
data1 = 12 ;
data2 = 3 ;
func(&data1, &data2) ;
}
func (a,b)
int *a , *b ;
{
int y ;
y = *a - *b ;
printf("(a-b) =%d\n",y) ;
}
上記からもわかるように、main関数からfunc関数へは変数data1とdata2が格納されているデータ領域のアドレスが渡されます。func関数では、そのアドレスをもとにして変数data1からdata2の内容をひき算し、その結果を出力しています。このように、関数間で変数のアドレスを渡す場合には、main関数とfunc関数で変数data1とdata2の格納されているデータ領域を共通に使用することになりますので注意してください。このプログラムを実行すると次のような結果が出力されます。
(a–b) =9
(b)配列のデータ
#include <stdio.h>
main()
{
int data[3] ;
data[0] = 1 ;
data[1] = 2 ;
data[2] = 3 ;
func(&data[0]) ;
}
func (a)
int a[];
{
int x ;
x = a[0] + a[1] + a[2] ;
printf ( "---%d\n" , x) ;
}
上に示したプログラムは、main関数で宣言した配列dataのアドレスをfunc関数に渡しています。func関数では、受け取った配列のアドレスをもとにして、配列の各要素の内容を加算してその結果を出力しています。
main関数では、func関数に配列のアドレスを渡すには次のように書きます。
func(&data[0]) ; 又は func(data) ;
つまり、配列名を直接指定する方法です。配列名のみを書きますと、配列の先頭のアドレスを示すことになるからです。通常は、この書き方が多いようです。このプログラムを実行すると次のような結果が出力されます。
--- 6