はじめに
本講義はプログラミングって何?な初心者の方から、C言語は初めて!という人が対象です。
加えて、メモリなどの説明を多めに取り入れているので、C言語はなんとなく知ってるけどコンピューター上でどういう処理になっているの?という人にもぜひ見てもらいたいと思っています!
今回はざっくりとプログラムが読み書きできるようになることを目的として、講義を進めていきます。
C言語で作られたプログラムを見てみよう!
まずは、本講義での到達目標(読めるようになるC言語でのプログラム)を示します。
#include <stdio.h> /* このプログラムは次のように動作する. ・Step1:int型の変数iBase,iAnsを定義する.iAnsは1で初期化する. char型の変数chExponent,chMod2,chCountを定義する. ・Step2:iBaseとchExponentに即値を格納する. ・Step3:次のようにiBaseに格納された値の偶数/奇数を判定する. ・chMod2にiBaseを2で割った余りを格納する. ・chMod2に格納された値が0であれば偶数/1であれば奇数と判定する. ・Step4:次のように累乗の計算をする. ・iBase ^ iExponent ・iBase = 5, iExponent = 3の場合:5 ^ 3 = 125. ・for文を用いて計算を行い,計算結果はiAnsに格納されるようにする. */ //main関数 int main(){ //変数を定義 int iBase; //int型 変数名(識別子)iBaseを定義 int iAns = 1; //int型 変数名(識別子)iAnsを定義, iAnsを1で初期化 char chExponent, chMod2, chCount; //char型 変数名(識別子)chExponent, chMod2, chCountを定義 //即値の格納 iBase = 7; //iBaseに7を格納 chExponent = 5; //chExponentに5を格納 //偶数/奇数判定 chMod2 = iBase % 2; //chMod2にiBaseを2で割った余りを格納 if(chMod2 == 0){ //chMod2が0であった場合 printf("iBaseは偶数\n"); //判定結果の出力 } else{ //chMod2が0でない(chMod2が1である場合) printf("iBaseは奇数\n"); //判定結果の出力 } //累乗計算 for(chCount = 0; chCount < chExponent; chCount++){ //繰り返し iAns *= iBase; //iAnsにiBaseを掛けた結果をiAnsに格納 } printf("iBase^iExponent = %d\n", iAns); //計算結果の出力 return 0; }
プログラムを一度実行してみるといいと思います。
実行環境を整えるか、「C言語 実行 サイト」等で検索してC言語を実行できるサイトを見つけて実行してみましょう。
プログラムの読み方/文法
それでは、先ほどのプログラムを読むために必要な知識を詰め込んでいきます。
最初の「#include <stdio.h>」は以後の講義で説明するので、今回はプログラムの先頭に書いておくものとして覚えておいてください。
C言語ではプログラムは「main関数」から開始し、上から下に1行ずつ順に実行していきます。
#include <stdio.h> ←とりあえず書いておく int main(){ ←ここから開始 処理1 ↓ 処理2 ↓ 処理3 ↓ return 0; }
半角スペース/改行
半角スペース/改行そのものには意味はありません。ただし、プログラムの可読性(人にとっての読みやすさ)を高めるため適宜利用する必要があります。
一点だけ注意が必要です。
後述の変数定義や以後の講義で学ぶプリプロセッサなど、半角スペース/改行がないとそれが何の指示なのかプログラムが判別できない場合があります。
このため、意味がないから全く無くても良いというわけではありません。
コメント
「//」の後ろ、もしくは「/*」「*/」で囲んだ範囲はコメントとしてプログラムでは無視されます。これも半角スペース/改行と同じく意味はありませんが、可読性のために用いられています。
また、コメントについては半角スペース/改行と異なり全くなくてもプログラムの実行に対して問題はありません。
最初に示したプログラムから不要な(無くても実行できるように)半角スペース/改行とコメントを取り除くと次のようになります。
#include<stdio.h> int main(){int iBase;int iAns = 1;char chExponent,chMod2,chCount;iBase=7;chExponent=5;chMod2=iBase%2;if(chMod2==0){printf("iBaseは偶数\n");}else{printf("iBaseは奇数\n");}for(chCount=0;chCount<chExponent;chCount++){iAns*=iBase;}printf("iBase^iExponent = %d\n",iAns);return 0;}
どうですか?流石に見にくいかと思います。
これは極端な例でしたが、適切な半角スペース/改行、コメントが書かれている方が良さそうだなと思ってもらえれば大丈夫です。
識別子
識別子とは変数・関数などにつける名前(変数名・関数名とも呼ばれる)のことです。→変数・関数については後述します。
A~Z、a~z、_(アンダースコア)、0~9の文字が利用可能で、任意の名前を付けることができます。ただし、数字を先頭にすることはできません。
ABC_123 〇 aBC_123 〇 _123Abc 〇 123_ABC ×
最初に示したプログラムでは「iBase」「iAns」「chExponent」「chMod2」「chCount」等が識別子(これらは全て変数なので変数名ともいう)です。
予約語
予約語とはC言語の文法上意味をもつ単語(特別な解釈が行われる単語)のことです。
予約語と同じ名前を識別子として利用することはできません。
if × return × ifreturn 〇(予約語そのものではない)
C言語の予約語は次の通りです(アルファベット順)。
auto | break | case | char |
const | continue | default | do |
double | else | enum | extern |
float | for | goto | if |
int | long | register | return |
short | signed | sizeof | static |
struct | switch | typedef | union |
unsigned | void | volatile | while |
変数
変数とはデータを一時的に記憶するメモリ上の場所・領域のことで、変数名(識別子)が定義されます。
変数は「型指定子」と「変数名(識別子)」によって次のように定義します。
型指定子 変数名(識別子); int iBase; //int型 変数名(識別子)iBaseを定義
変数名(識別子)を指定して、値の記憶やCPUで計算させることができます。
次の例ではiBaseを指定して7という値を格納しています。
iBase = 7; //iBaseに7を格納
次のように複数の変数を同時に定義することも可能です。
char chExponent, chMod2, chCount; //char型 変数名(識別子)chExponent, chMod2, chCountを定義
bitとbyte
プログラムと直接は関係のない話ですが、重要な知識なのでbitとbyteについて簡単に説明します。
コンピューターは結局のところ、電流がOFFかONかの0/1の信号しか扱えません。
このため下の図のようにAC等から無数の線がつながっており、それぞれから電流のON/OFF(0/1)の信号を1つだけ持っています。
この1本の線から得られる1つの信号のことをbitといい、それが8個集まったものをbyteと呼びます。
型指定子
さて、変数を定義する際に型指定子が必要といいました。これはデータの大きさ(扱いたい数によって決める)が関わっています。
次のデータ型とバイト幅の表(処理系に依存するため一部異なるものもある)を見てください。
扱うデータ | データ型 | バイト幅 |
---|---|---|
文字(整数) | char | 1byte |
整数 | short | 2byte |
整数 | int | 4byte |
整数 | long | 4byte |
浮動小数点 | float | 4byte |
浮動小数点 | double | 8byte |
この表をみると、データ型によってバイト幅(データの大きさ)が異なることが分かると思います。
このため、扱いたいデータ型を指定することで、メモリ上でどれくらいの領域を変数に割当てるのかを決めることができるのです。
データ型と扱える数の範囲
コンピューターは2進数でデータを扱う(0/1信号しか扱えないため)ので、
- 1bitでは2個の0/1の組合せなので2^1=2個の数
- 1byte(8bit)では8個の0/1の組合せなので、2^8=256個の数
- 4byte(32bit)では32個の0/1の組合せなので、2^32=4294967296個の数
を扱うことができます。
では実際の数の範囲はというと、符号付きか符号なしかで変わってきます。
符号付きの場合は「10000000…」を最小値とし「01111111…」を最大値とすることで、次のように「-2^(x-1)」~「2^(x-1)-1」が数の範囲になります。(xはデータ型のbit数)
符号なしの場合は次のように「0」~「2^x-1」が数の範囲になります。(xはデータ型のbit数)
ところで、数は「int型」、文字は「char型」と習うことが多いです。(実際、教える相手によってはそう教えます)
しかし実際には、どちらも2進数の数で、「char型」では数に文字を対応させることで文字を扱っているように振舞っています。
つまり、型によって変わるのはbyte幅だけで、char型では数を文字に対応させることができると覚えましょう。
メモリ上での説明
メモリ上では次のように、変数の場所・領域の確保と、変数への値の格納が行われます。
0xXXは番地といってメモリ上の住所を表しており、これはプログラムで指定しなくても自動的に空きのある場所を探して決定してくれます。
初期化
変数は定義しただけでは何の値が格納されているのか不定です。
これでは思うように動かないこともあるため、変数を定義すると同時に変数に値を格納する方法があります。このことを初期化と言い、次のように記述します。
型指定子 識別子 = データ; int iAns = 1; //int型 変数名(識別子)iAnsを定義, iAnsを1で初期化
ここでは1という即値を格納していますが、型指定子で指定したデータ型であれば格納することができます。(例:同じデータ型の変数など)
定数(即値)
定数は値を直接プログラムの中に書いたもので、数定数、文字定数、文字列定数などがあります。
文字定数は「’」、文字列定数は「”」で囲って次のように表現します。
'a' "abc"
最初に示したプログラムには出てきませんが、これは覚えておきましょう。
数定数は次のように表現できます。
- 2進数:先頭に「0b」を付ける
- 8進数:先頭に「0」を付ける
- 10進数:何も付けない
- 16進数:先頭に「0x」を付ける
int i02 = 0b1010; //2進数で10 int i08 = 0x0A; //8進数で10 int i10 = 10; //10進数で10 int i16 = 012; //16進数で10
現時点では10進数でよくない?と思ってもらっていて大丈夫ですが、きっと16進数が使いたくなる時が来ます。(来なかったらごめんね)
式
式とは計算方法を示す、定数、識別子、演算子の並びのことです。
次のようなものが式になります。(最初に示したプログラムの中からいくつかを抜粋)
iAns = 1 chMod2 = iBase % 2 chCount < chExponent
演算子
演算子には次のように分けられます。使い方は多岐にわたるため、必要な時に参考書などを参照してください。
算術演算子 | +, -, *, /, % |
代入演算子 | =, +=, -=, *=, /-, %=, &=, |=, ^=, <<=, >>= |
インクリメント・デクリメント演算子 | ++, — |
シフト演算子 | <<, >> |
ビット演算子 | ~, &, |, ^ |
関係演算子 | ==, !=, <, >, <=, >= |
論理演算子 | &&, ||, ! |
演算子には優先度がありますが、「(」と「)」で囲うことで数学の括弧と同様に優先的に処理させることができるため慣れないうちは括弧を利用しましょう。(ここでは優先度については取り上げません)
文
文とは実行する動作を指定するまとまりのことで、次の2通りに分類されます。
- 単文:式の後ろに「;」を付けたもの
- 複文:「{」と「}」で囲んだもの(「{」や「}」の後ろに「;」は不要です)
最初にプログラムは上から下に1行ずつ実行するといいましたが、文は動作の単位であるため単文も複文もそれぞれ1つのまとまりとしてプログラムは認識します。
このことだけを覚えてもらえれば十分です。
制御文
制御文とはプログラムを制御するための文で、ある条件下で実行する処理を切り替える条件分岐と、ある条件下で継続的に同じ処理を実行する繰り返しがあります。
条件分岐:if~else if~else文
条件分岐は、ある条件の真/偽によって実行する処理を変えるために用いられます。
「if~else if~else文」は次のように書きます。
if(条件式1){ 処理1 //「条件式1が真」の時実行 } else if(条件式2){ 処理2 //「条件式1が偽」且つ「条件式2が真」の時実行 } else{ 処理3 //「条件式1が偽」且つ「条件式2が偽」の時実行 }
「if」及び「else if」では「条件式」を満たす(真である)場合に、後に書かれた「文」(一般的には例のように複文を用いるが、単文でも可能だということは知っておいてもらいたい)を実行します。
「else if」は複数記述することが可能で、「else if」と「else」については省略することができます。模式図にすると次の通りです。
最初のプログラムでは、「else if」を省略して次のように利用しました。
if(chMod2 == 0){ //chMod2が0であった場合 printf("iBaseは偶数\n"); //判定結果の出力 } else{ //chMod2が0でない(chMod2が1である場合) printf("iBaseは奇数\n"); //判定結果の出力 }
条件分岐は「if~else if~else文」以外にもありますが、以後の講義でまた説明します。
繰り返し
繰り返しの処理は「for文」と「while文」のどちらかによって書かれます。また、どちらでも同じように繰り返し処理をさせることができます。
一般的には次のように使い分けることが、コードの可読性の観点から良いとされています。
- for文:変数がXからYになるまで(回数の決まっている処理)
- while文:変数がある状態になるまで(回数の決まっていない処理)
ただし、最初にも述べたようにどちらでも同じ繰り返し処理をさせることができるため、一概にこの場合はこっちと決めるのではなく、適宜コードの可読性等を目安として決めると良いと思います。
for文
「for文」は次のように書きます。
for(式1; 式2; 式3){ 処理 }
式1を最初に1度だけ実行した後、式2(条件式)が真の間、処理を実行して式3を実行します。ここで、式3は処理の後に実行されることは特に注意して覚えておいてください。
最初のプログラムでは次のように利用しました。
for(chCount = 0; chCount < chExponent; chCount++){ //繰り返し iAns *= iBase; //iAnsにiBaseを掛けた結果をiAnsに格納 }
これは次のように読み取れます。
- 式1:「chCount」に「0」を一度だけ格納する
- 式2(条件式)「chCount」が「chExponent」より小さい間、式3を繰り返す
- 式3:「iAns」に「iAns」と「iBase」を掛けたものを格納し、「chCount」に「1」加算する
while文
「while文」は次のように書きます。
while(条件式){ 処理 }
「while文」は単純で、条件式を満たしている間、処理を繰り返します。
演習
ここまでこれば最初のプログラム程度であれば、演算子などの意味を調べながら読むことができるようになったのではないでしょうか?
ここからは演習として、ちょっとした問題を解いてみましょう!
出力について
演習に入る前に、唯一まだ伝えていない出力についてです。
この講義では、出力ついて理論的な話や応用して何かすることはないため、書き方を覚えてください。
出力には「printf」を用います。次のように「”」に囲まれた部分に出力させたいものを記述します。
printf("出力したいもの"); //出力結果:出力したいもの
これに加えて次のように、「”」と「)」の間に「,」と変数を書くことで、「%d」の位置に変数の数を出力することができます。
printf("1+1=%d",ans); //出力結果:1+1=2(ansに2が格納されているとする)
また、変数は2つ以上出力することも可能です。変数は「,」で区切り、「%d」と変数がそれぞれ対応するように左から順に並べてください。
printf("A = %d, B = %d", dataA, dataB); //出力結果:A = 1, B = 2(dataA = 1, dataB = 2とする)
また「\n」と書くことで改行させることができます。
printf("A = %d\nB = %d\nC = %d", dataA, dataB, dataC); /*出力結果:A = 1 B = 2 C = 3 (dataA = 1, dataB = 2, dataC = 3とする) */
これらを適宜利用して、出力させてください。
演習1
次のプログラム中に書かれたコメントを読み、必要なプログラムを実装してください。
#include <stdio.h> //演習1 //main関数 int main(){ //①int型の変数iScore_A,iScore_B,iScore_Cを宣言する //②iScore_Aには70をiScore_Bには50を格納する //③iScore_AとiScore_Bの値を出力する //出力例:iScore_A = 70, iScore_B = 50; //④iScore_CにiScore_AとiScore_Bを足した値を格納する //⑤iScore_Cの値を出力する //出力例:iScore_C = 120 //⑥if~else文を用いてiScore_Cが100以上なら「OK」、そうでない場合は「NG」と出力する return 0; }
#include <stdio.h> //演習1 //main関数 int main(){ //①int型の変数iScore_A,iScore_B,iScore_Cを宣言する int iScore_A, iScore_B, iScore_C; //②iScore_Aには70をiScore_Bには50を格納する iScore_A = 70; iScore_B = 50; //③iScore_AとiScore_Bの値を出力する //出力例:iScore_A = 70, iScore_B = 50; printf("iScore_A = %d, iScore_B = %d\n", iScore_A, iScore_B); //④iScore_CにiScore_AとiScore_Bを足した値を格納する iScore_C = iScore_A + iScore_B; //⑤iScore_Cの値を出力する //出力例:iScore_C = 120; printf("iScore_C = %d\n", iScore_C); //⑥if~else文を用いてiScore_Cが100以上なら「OK」、そうでない場合は「NG」と出力する if(iScore_C >= 100){ printf("OK\n"); } else{ printf("NG\n"); } return 0; }
演習2
繰り返し処理を用いて次の要件を満たすプログラムを作成してください。
繰り返し処理を用いて「1+2+…」と順に足していき、合計が「1000」以上になった時に繰り返しを終了。最後に加えた数を出力してください。
#include <stdio.h> //演習2 //main関数 int main(){ //利用する変数の定義 unsigned short unsAdd_Num = 0; //足す数 unsigned short unsAdd = 0; //合計値 //合計値が1000未満の間,繰り返し処理 //合計値が1000以上で繰り返し処理を抜ける while(unsAdd < 1000){ unsAdd_Num++; //条件を満たしていれば足す数を1加算する unsAdd += unsAdd_Num; //足す数を合計値に加える } //結果の出力 printf("最後に足したのは%d\n", unsAdd_Num); return 0; }
演習3
繰り返し処理を用いて、1から10までの階乗を出力せよ。
出力例は次の通り。
1 != 1 2 != 2 3 != 6 4 != 24 5 != 120 6 != 720 7 != 5040 8 != 40320 9 != 362880 10 != 3628800
#include <stdio.h> //演習3 //main関数 int main() { char chCount; //for文で利用,掛ける数 int unsResult = 1; //階乗計算結果,掛けていくため初期値1 //1から10まで順に掛ける //毎回結果を出力する for(chCount = 1; chCount < 11; chCount++){ unsResult *= chCount; printf("%d != %d\n", chCount, unsResult); } return 0; }
最後に
どうでしょうか?
少しはC言語のプログラムが読めるように/書けるようになりましたか?
最初なので少し長くなってしまいましたが、次回以降も講義+小演習という流れでやっていきたいと思います。
質問などありましたら、下の方にあるコメント欄もしくはお問い合わせメールの方にお願いします。(メールは反応が遅くなります)
おすすめの参考書
参考書を数冊載せておきます。人によって好みはあると思うので、書店や試し読みで確認してから購入することをおすすめします。
新品価格 |
新品価格 |
新品価格 |
コメント