最終更新:2014/6/1 執筆者:mznh
今回はそこそこ難しい概念である関数と配列を学びます。
もしもわからなくなったら即質問してください。
プログラミングというのは何かと数学で聞いたことのある単語が出てきます。この関数もそうですね、f(x)とかg(x)とかいうアレです。
数学では
f(x)=ax2+bx+cとおく。
みたいな感じで使われていました。
これはax2+bx+cという計算の手続きをf(x)とまとめて表記していると考えることが出来ます。(これをf(x)を定義するとも言ったりしますね)
これをプログラミングの話に置き換えてみましょう。
例えばプログラムの至るところで「指定された変数を2乗して100足して2で割った数」が使われているとします。
つまり指定された変数を仮にaとすると、
(a*a+100)/2
というのをたくさん書くわけです。
すごい面倒くさいですね。それに何回も書いているうちにミスをするかもしれません。
そこでこれをfという名前の関数に置き換えてあげれば、
f(a)
これだけで済むわけです。
またこうしておけば後々この処理を「aを2乗して200を足して2で割ってそれに最後に10を足す処理」に変更したいなと思った時でも関数fの内容を書き換えるだけで済みます。
では実際にC言語における関数の作り方を学んでいきましょう。
関数を作ることを関数を定義するといいます。
関数の定義の仕方は
返り値の型 関数の名前(引数の型 引数の仮の名前 ...){
関数の処理の内容;
return 返り値の値;
}
という風に書きます。なんとなくこの形に見覚えがありませんか? 実は今まで必ず書いてきたmain関数もこれに沿って宣言していました。
int main(){
printf("Hello World\n");
return 0;
}
なお引数を持たない関数の場合は上記のように引数を省略することが出来ます。
定義の仕方とこのmain関数を見比べてみれば「このmain関数は引数をとらず返り値にはint型の0を返している」ということがわかると思います。
さてこれから今出てきた関数の各用語について説明していきます。
引数とは関数に与える値のことです。簡単にいえば機械に突っ込む材料といったところでしょうか。
例えばハンバーグを作る機械があったとしたら。ひき肉やタマネギやつなぎの卵とかが引数です。もしもC言語風に書くとしたら
meal hamburg_maker(food meat,food onion,food egg){
meal hamburg;
hamburg = cook(meat,onion,egg);
return hamburg;
}
といったところでしょうか。(当然ですが中の変数の型とか関数名はデタラメです)
またこのhamburg_maker関数のように複数の引数を与えることが出来ます。
その場合引数のところに
返り値の型 関数名(引数の型 引数の仮の名前,引数の型 引数の仮の名前,引数の型 引数の仮の名前,・・・){
}
といった形で書きます。
注意して欲しいのは変数の宣言の時は同じ型の場合連続してint a,b,c;
という風に書けましたが関数の引数の場合はそれが出来ない点です。
こうして与えられた引数はそのまま関数の中で変数として使うことが出来ます。なおこの変数のスコープはこの関数の中だけです。
int add_num(int x, int y){
int ans;
ans = x + y ;
return ans;
}
これは簡単に言えばf(x)の値です。例えば数学で
y=f(x)
という式があったとしてf(x)の値が5だとすると上記の式は展開されて
y=5
となります。これをC言語では「f(x)が5を返した」と表現します。
なお基本的にプログラミングにおいて関数はひとつの値しか返すことが出来ません。
引数をとらない関数は引数を省略できると言いましたがより明示的に引数が無いことをvoidという型を使って示すことができます。
int none_parameter(void){
hogehoge;
return 0;
}
同様にして返り値が無い関数も宣言することができます。
void none_value(int hoge){
hogehoge;
}
さてここまでのまとめして実際に関数を宣言してそれを使っているコードを見てみましょう。
#include<stdio.h>
//int型の引数を2つ取りint型を返す
//add_numという名前の関数を定義しています。
int add_num(int x, int y){
int ans ;
ans = x + y ;
return ans;
}
int main(){
int x,y;
printf("Please input two num[x y]:");
scanf("%d %d",&x, &y);
printf("x = %d\ny = %d\n",x,y);
printf("x + y = %d\n",add_num(x,y));
return 0;
}
自分で定義した関数は最初からあるprintf関数等と同じように別の自分で定義した関数の中でも使うことが出来ます。
ただし関数を使う場合は使う行よりも前に定義しなければなりません。(後述するプロトタイプ宣言をした場合を除く)
//make_function.c
#include<stdio.h>
int add10(int x){
int a = x + 10;
return a;
}
double add10_div2(int x){
double ans=add10(x)/2.0;//↑で定義したadd10関数を使っています。
return ans;
}
int main(){
printf("%d\n",add10_div2(100));
return 0;
}
プロトタイプ宣言を用いると「返り値の型、関数名、引数」の宣言だけを最初にまとめて書いておいてmain関数の後に本体を書く。なんてことができるようになります。
具体的にはmain関数よりも前に
返り値の型 関数名(引数の型 引数の仮の名前); //!!最後の;を忘れないように!!
を書いた後にmain関数の後に。
返り値の型 関数名(引数の型 引数の仮の名前){
処理の内容;
return 返す値;
}
と書けば普通に関数を定義したときと同じように自作した関数を使うことが出来ます。
これはかなり発展的な内容です。(具体的には一般的なI科の2年生でも厳しいレベル) わからなくても全く構いません。ただ再帰関数という概念があるよとだけ記憶の片隅にとどめておいてください。
関数を定義する際にその関数自体を定義の中に含めることが出来ます。
int factorial(int x){
if(x==0){
return 1;
}else{
return x*factorial(x-1);
}
}
こういった関数を再帰関数と呼びます。実はこれ階乗を計算する関数なんですが数学の定義をそのまま記述しているだけなんです。
f(x)=x!とおくと
f(x)=1 :(x=0)
=x*f(x-1) :(otherwise)
このように再帰を使うと一部の処理がとても簡潔に書けることがあります。
配列や文字列の前にまず文字について学びます。
皆さんご存知かと思いますがコンピュータは0と1しか扱えません。
これを2進数と言い工夫すれば我々の使う10進数も表現することができます。
しかし数値だけではABC..のようなアルファベットを表現することが出来ません。
そこで昔の偉い人たちは'A'には65、'B'には66、と言った感じで文字と数字を対応付けることを考えつきました。
これがASCIIコードと呼ばれるもので現在でも使われています。
(なお残念ですがASCIIコードに含まれている文字はa-zA-Z0-9などの英数字と;.,などの記号だけです。いわゆる全角文字は含まれていません。)
なお今後文字という場合にはASCIIコードで定義されている文字だけを指します。
C言語で文字を変数に記録するときには大抵char 型を使います。
charというのはcharacter(意:文字)の略なのでほとんど文字のために用意された型と言っていいでしょう。
なおchar型は整数値でいうと-128~127までしか記録できません。
C言語では1文字だけを 「'」(シングルクォーテーション)で囲むとそれはASCIIコードでその文字を表す数値を意味します。
例えば
char mozi;
mozi = 'A';
printf("%d",mozi);
このようにするとprintf関数は「65」を出力します。
また第一回講習の資料に載っている書式文字列の表にある文字を指定する%cを使うと
char mozi;
mozi = 'A';
printf("%c",mozi);
このprintf関数は「A」を出力します。
つまりASCIIコードで65という数値は'A'と対応しているわけです。ということは
char mozi;
mozi = 65;
printf("%c",mozi);
このように直接65という数値を入れてもこのprintf関数は「A」を出力します。
また嬉しい事にA-Zやa-zは連番になっています(大文字と小文字は離れていますが)
つまり
char mozi;
mozi = 'A';
mozi = mozi + 1;
printf("%c",mozi);
このようにするとprintf関数は「B」を出力します。
さてC言語においてわりと難しい概念である配列に入ります。
例えば学生の成績のデータがあったとします。
ヤマダさんは50点、タナカさんは60点、スズキさんは70点だとします。
これを変数として持っておくとき
int yamada,tanaka,suzuki;
yamada = 50;
tanaka = 60;
suzuki = 70;
こうしてもいいんですがこれだと学生の数が増えたときに面倒ですね。
この変数達をまとめて扱えるようにしたい、そこで配列というものが必要になってきます。
配列は同じ型のデータをひとまとめにしてそれぞれに番号を付けたものです。
具体的には以下のようになっています。
| | | | | | |
| 0 | 1 | 2 | 3 | 4 | 5 |・・・
|_____|_____|_____|_____|_____|_____|
変数をデータを保存する箱のようなものと以前説明しましたがこれは箱がならんでくっついているイメージです。
そしてその箱のそれぞれに番号が付けられています。注意してほしいのは番号付けが0から始まっている点です。
配列の宣言は通常の変数の宣言とほとんど同じですが名前のところが少し違います。
配列の場合は名前を配列の名前[宣言したい配列の長さ]にします。
配列の長さとは上の例でいう箱の個数のことです。
具体的にはint型で長さが10の名前がarrayという配列を宣言したい場合は、
int array[10];
と宣言します。なお宣言するときに[ ]の中の数字に変数を使うことは出来ません。
配列を使う場合、配列の名前[アクセスしたい箱の番号] というふうに書きます。
先ほど例で宣言したarray配列の3番目の箱に10という値を代入したい場合は
array[3]=10;
というように書きます。
嘘です。
配列の箱の番号は0から始まるので3番目の箱の番号は2です。
よって配列の3番目の箱に10という値を代入したい場合は
array[2]=10;
というように書くのが正解です。
また配列の宣言をした直後だけは以下のような特殊な代入方法が使えます。
int array[10] = {0,1,2,3,4,5,6,7,8,9};
これで配列には順番に0,1,2,...,8,9と値が代入されます。
このように変数の宣言と同時に値を代入する行為を初期化と呼びます。
なお先ほどint array[10];
と宣言したこの配列は10個の箱でできています。
そして箱の番号は0から始まるのでこの配列の箱の番号は9までです。
ちなみに
配列の箱の数のことを要素数、配列の箱それぞれのことを 要素 、何番目の箱かを表す数字(array[5]でいう5)を添字(そえじ)とそれぞれ呼びます。
また配列に関して一つ注意すべき大事なことがあります。
C言語の配列はそれだけではその配列がどれだけの長さか分からない点です。
例えば先ほど例で宣言した配列はarray[9]までしかありませんが
array[100]=10;
とプログラム中で書いてもコンパイラは何もエラーを吐きません。(賢いコンパイラだったら何か言ってくれるかも)
またこれは定数を添字に使っているのでまだ判別可能ですが添字に変数を使ってアクセスしようとした場合実行されるまでどうなるかわかりません。
またこれは次回やる範囲の知識が必要になるので今は聞き流す程度でいいですが
配列の名前(例だとarray)は配列の最初の要素(array[0])のポインタを指します。
第一回の講習でちらっと話したのですがC言語において文字と文字列は違うものです。
文字を'(シングルクォーテーション)で囲むのに対して
文字列は「"」(ダブルクォーテーション)で囲んで表します。
大方想像付いているかとは思いますが、文字列とは 文字の配列です。
もうちょっと正確に言うと文字列は配列の最後に'\0'(NULL文字)という特殊な文字がついてます。
そしてこの'\0'が文字列の終わりを示しています。
具体的な例を示しましょう、例えば”Hello”という文字列が格納されているstringという名前の配列があるとすると以下のようになっています。
| | | | | | |
| 'H' | 'e' | 'l' | 'l' | 'o' | '\0'|
|_____|_____|_____|_____|_____|_____|
0 1 2 3 4 5 //添字です
今まで使ってきたprintf関数に渡してきた"hoge %d fuga"のような文字列も上記のような形で展開されていました。
今まで説明してきた配列、文字列はどれも1次元の配列でした。
実は2次元、3次元、n次元の配列も宣言することが出来ます。
2次元配列を例にとって説明しましょう。
例えばint型の5×4の名前matrixという名前の2次元配列を宣言したい場合は、
int matrix[4][5];
というふうに宣言します。
これは正確に言うとint型の要素数5の配列を格納する要素数4の配列を宣言しているのですが、それは追々理解してもらえればいいです。
通常の配列と同じように扱うことが出来ます。
matrix[0][0]=10;
matrix[0][1]=20;
配列を扱う際は前回学習したfor文を使うと便利です。
int n,i;
int array[10]={0,5,3,6,4,7,8,9,2,1};
n = 10;//配列の要素数を記録しておく。
for(i=0;i<n;i++){
printf("%d ",array[i]);
}
上のプログラムは配列の中身最初から順番に出力してくれます。
プログラムの最初に #include<string.h>
と書き足すと文字列を扱う便利な関数が使えるようになります。ここではその一部を紹介します。
これはある文字列の内容を別の文字列にコピーしてくれる関数です。
from,toという名前の2つの文字列が宣言されていてfrom文字列の中身をto文字列の中身にコピーしたいとしたら以下のようにします。
strcpy(to,from);
これは2つの文字列を連結してくれる関数です。 beforeとafter2つの文字列が宣言されていてbeforeの後にafterの中身をくっつけたいときは以下のようにします。
strcat(before,after);
これは文字列の長さを返す関数です。 messageという文字列が宣言されていてその長さを知りたいときは以下のようにします。
int length;//長さを記録する変数
length = strlen(messaage);//lengthの中にmessageの長さが格納されます。
これは#include<string.h>
を追記しなくても使える関数なのですが今まで説明してなかったので紹介しておきます。
この関数は低機能はprintf関数で固定の文字列しか出力してくれません。
ただ最後に勝手に改行を入れてくれます。
puts("Hello");
上記のようにすると
Hello
とHelloが表示された後に改行が入ります。
この他にも様々な関数がたくさんあるので各自調べてみてください。
演習1: 何も引数をとらない"Hello"と出力するだけの関数を作成せよ。
演習2: int 型の引数を一つとりその値を3乗した値を返す関数を作成しそれを用いてAOJ10001 X Cubicを解け
注意 x^3 と表記してもこれはxの3乗を意味しません。
演習3: 最初に整数nを入力しその後n個の整数の入力を受け取った後にそれを最初から順に出力するプログラムを作成せよ。
演習4: AOJ10011 Reversing Numbers
演習5: スペースや改行を含まない文字列の入力を一つ受け取りそれをそのまま出力するプログラムを作成せよ。
演習6: スペースや改行を含まない文字列の入力を一つ受け取りそれを逆にして出力するプログラムを作成せよ。(例: "abcd"と入力されたら"dcba"と出力)
以下は前回の範囲の発展的な演習です。
演習8: AOJ10017 How many ways? 難しい
演習9: AOJ10023 Shuffle 難しい
この 作品 は クリエイティブ・コモンズ 表示 - 非営利 - 改変禁止 2.1 日本 ライセンスの下に提供されています。免責事項