関数とは
定められた通りの処理を実行して結果を返す一連の命令群のことです
今までもprintf関数やscanf関数などを使って来ました
C言語のプログラムはたくさんの関数が集まって出来ています
今まで使ってきたprintfなどは標準ライブラリ関数に属しています
そのほかには関数を自分で作成することが出来、これをユーザ関数と呼びます
今までもprintf関数やscanf関数などを使って来ました
C言語のプログラムはたくさんの関数が集まって出来ています
今まで使ってきたprintfなどは標準ライブラリ関数に属しています
そのほかには関数を自分で作成することが出来、これをユーザ関数と呼びます
ライブラリ関数とは
C言語では機能ごとにライブラリファイルにまとめて用意してあります
これをライブラリ関数と呼びます
これをライブラリ関数と呼びます
プリプロセッサ指令とヘッダファイル
今まで入出力関数を扱うとき、#include <stdio.h>と最初に記述していました
includeはプリプロセッサ指令と呼ばれ、プリプロセッサと呼ばれるコンパイル前の処理を行うプログラムに命令を送る処理です
stdio.hはヘッダファイルと呼ばれ、stdio.hの中にはprintfなどを使用するために必要な宣言や定義などを行っています
関数を扱うときは必ず#includeでこのソースプログラムで関数を使いますと宣言していたのです
includeはプリプロセッサ指令と呼ばれ、プリプロセッサと呼ばれるコンパイル前の処理を行うプログラムに命令を送る処理です
stdio.hはヘッダファイルと呼ばれ、stdio.hの中にはprintfなどを使用するために必要な宣言や定義などを行っています
関数を扱うときは必ず#includeでこのソースプログラムで関数を使いますと宣言していたのです
標準ライブラリ関数
標準ライブラリ関数 | ヘッダファイル |
入出力処理 | stdio.h |
文字列処理 | string.h |
文字処理 | ctype.h |
一般ユーティリティ | stdlib.h |
数学処理 | math.h |
時間処理 | time.h |
ジャンプ処理 | setjmp.h |
シグナル処理 | signal.h |
診断処理 | assert.h |
サークルの講義ではすべてstdio.h(スタンダードインアウトヘッダ)で済む範囲でこなしてしまうため、ライブラリ関数についてはあまり深くは触れません
興味がある方は先輩方に聞くか、各自ネットや書籍などで調べてください
興味がある方は先輩方に聞くか、各自ネットや書籍などで調べてください
ユーザ関数とは
ユーザ関数とはプログラマ自身が作成する関数のことです
今まで扱ってきたプログラムは小さく、すぐに問題の場所が見つけられましたが、世の中で動いているプログラムは何千、何万行の大きなものばかりです
その処理を小分けにし、何十人、何百人で分担して作成していきます
又、小分けにすることでプログラムの修正をその関数間で行うことが出来、同じような処理の関数をまとめてプログラム間で繰り返し使うことも出来ます
今まで扱ってきたプログラムは小さく、すぐに問題の場所が見つけられましたが、世の中で動いているプログラムは何千、何万行の大きなものばかりです
その処理を小分けにし、何十人、何百人で分担して作成していきます
又、小分けにすることでプログラムの修正をその関数間で行うことが出来、同じような処理の関数をまとめてプログラム間で繰り返し使うことも出来ます
ユーザ関数の作り方
関数の返り値の型 関数名(引数宣言){ //ユーザ関数の定義 内部処理 : return; }
言葉にするとわかりにくいと思いますが、実はやってることはmain関数と変わりません
関数は必ず返り値の型の記述と引数の記述を行っています
関数は必ず返り値の型の記述と引数の記述を行っています
ユーザ関数の使い方
int sum(int x, int y){ //sum関数の定義 int wa; wa=x+y; return(wa); //変数waを返す } int main(){ int ans,num1=5,num2=10; ans=sum(num1,num2); //sum関数の呼び出し printf("%d+%d=%d",num1,num2,ans); return(0); }
ユーザ関数を使用するときは関数名と対応した引数を渡してあげることで使うことが出来ます
この渡す引数のことを実引数といい、関数で受け取ったものを仮引数といいます
また、関数内で関数を呼び出すことも可能です
この渡す引数のことを実引数といい、関数で受け取ったものを仮引数といいます
また、関数内で関数を呼び出すことも可能です
printf("%d+%d=%d",num1,num2,sum(num1,num2));//printf関数の中でsum関数を呼び出している
void型
関数は必ず返り値の型と引数の記述を行っているといいましたが、main関数は引数の記述を行っていません
実は今までmain関数の引数にはvoidが指定されて居たのです
voidとは空、無といった、何も無いことを表します
引数でvoidを指定する場合はvoidを記述するか、今までどおり省略すること出来ます
引数ではvoidの記述を省略することが出来ましたが、返り値のでは省略することが出来ません
返り値で記述を省略するとint型になるので、voidと記述してあげてください
また、返り値がない場合はreturn文は省略することが出来ます
実は今までmain関数の引数にはvoidが指定されて居たのです
voidとは空、無といった、何も無いことを表します
引数でvoidを指定する場合はvoidを記述するか、今までどおり省略すること出来ます
引数ではvoidの記述を省略することが出来ましたが、返り値のでは省略することが出来ません
返り値で記述を省略するとint型になるので、voidと記述してあげてください
また、返り値がない場合はreturn文は省略することが出来ます
プロトタイプ宣言
関数の宣言だけを行い処理内容を後述する方法です
必ずmain関数から処理が行われるため、関数の処理が後ろにあると流れとしてプログラムが追いやすくなります
しかし、プロトタイプ宣言の分だけ行数が増してしまうので、好みの問題といえるでしょう
必ずmain関数から処理が行われるため、関数の処理が後ろにあると流れとしてプログラムが追いやすくなります
しかし、プロトタイプ宣言の分だけ行数が増してしまうので、好みの問題といえるでしょう
返り値の型 関数名(引数) //プロトタイプ宣言 int main(){ 処理内容 : } 返り値の型 関数名(引数){ //関数の定義 処理内容 : }
グローバル変数とローカル変数
関数外で宣言された変数をグローバル変数と呼び
関数の中で宣言された変数をローカル変数と呼びます
関数の中で宣言された変数をローカル変数と呼びます
グローバル変数は初期化する必要が無く、すべての関数で扱うことが出来ます
ローカル変数は初期化は手動で、その関数内でしか扱うことが出来ません
ローカル変数は初期化は手動で、その関数内でしか扱うことが出来ません
グローバル変数で宣言した変数名と同名でもローカル変数で宣言することが出来ます
この場合、ローカル変数が優先的に使用されます
この場合、ローカル変数が優先的に使用されます
また、ローカル変数は関数が終了するたびに消されてしまいます
このことを変数の寿命と呼び、変数の有効範囲や寿命のことをまとめてスコープと呼ばれます
このことを変数の寿命と呼び、変数の有効範囲や寿命のことをまとめてスコープと呼ばれます
二個以上の返り値が欲しい場合
return文では一つしか返すことが出来ません
二つ以上の返り値が欲しい場合はポインタを使います
二つ以上の返り値が欲しい場合はポインタを使います
void comparison(int x,int y,int *small,int *big) { if(x<y){ *small=x; *big=y; }else{ *small=y; *big=x; } } int main(){ int num1=10,num2=5,min=0,max=0; comparison(num1,num2,&min,&max); printf("最小値=%d \n 最大値=%d",min,max); return(0); }
引数に配列を使う
関数に配列を渡す場合もポインタを扱います
int max(*array,int x){ int i,max; for(i=0;i<0x;i++){ if(max<array[i]){ max=array[i]; } } return(max); } int main(){ int data[5]={6,3,5,1,7}; int x=5; //配列の要素数 printf("最大値=%d",max(data,x)); return(0); }
又、関数定義において*arrayはarray[]と記述してもよい
どちらも同じ意味なので、どちらを使うかはプログラマの好みの問題になります
どちらも同じ意味なので、どちらを使うかはプログラマの好みの問題になります
再帰的関数
C言語で自分自身と同じ関数を呼び出すことが可能です
int fact(int n){ int x; if(n>0){ x=n*fact(n-1); }else{ x=1; } return(x); } int main(){ int num=3; printf("%dの階乗は%dです",num,fact(num)); return(0);
}
上の例は階乗を求めるプログラムです
階乗処理は下記のとおり
n!=n*(n-1)!
:
1!=1*(1-1)!
0!=1
階乗処理は下記のとおり
n!=n*(n-1)!
:
1!=1*(1-1)!
0!=1
例を見るとfact関数の処理でnが0よりも大きい時にもう一度fact関数を呼び出しています
つまりnが0になるまでfact処理を繰り返すことになります
ただし普通の繰り返し処理と違うのは、新しい関数を呼び出しても、呼び出される前の関数は処理は処理を中断した状態になります
どういうことかわかりにくいと思うので、関数の処理始めと処理終わりにprintfを入れてみるとわかりやすいと思います
つまりnが0になるまでfact処理を繰り返すことになります
ただし普通の繰り返し処理と違うのは、新しい関数を呼び出しても、呼び出される前の関数は処理は処理を中断した状態になります
どういうことかわかりにくいと思うので、関数の処理始めと処理終わりにprintfを入れてみるとわかりやすいと思います
int fact(int n){ int x=0; printf("fact start n=%d\n",n); if(n>0){ x=n*fact(n-1); }else{ x=1; } printf("fact end n=%d x=%d\n",n,x); return(x); }
実行結果
fact start n=3 fact start n=2 fact start n=1 fact start n=0 fact end n=0 x=1 fact end n=1 x=1 fact end n=2 x=2 fact end n=3 x=6 ---------------------ここまでfact関数の処理 3の階乗は6です
if文でfact関数が呼び出されるため、n=3のときの処理は中断され、新たに呼び出されたfact関数の処理を始めます
n=2でもn=1でも同じです
n=0のときに初めてfact関数が呼び出されないのでreturn文まで到達することが出来ます
返り値は前の関数に引き渡され、中断されていた処理を再開します
n=2でもn=1でも同じです
n=0のときに初めてfact関数が呼び出されないのでreturn文まで到達することが出来ます
返り値は前の関数に引き渡され、中断されていた処理を再開します
再帰的処理はオーバーヘッドなどの問題があったりするため好んでは使われませんが、フラクタル図形など再帰的処理以外では記述が難しいものも存在します