一週間で身につくC言語の基本

おさえておきたいプログラミングの基本

【6日目】 関数の作り方

6-1.関数

(1) 関数とは何か

実用的なソフトウェアを開発しようとすると、プログラムは必然的に長いものになります。するとその中に複雑な処理が現れたり、何度も同じ処理を行ったりすることが必要になります。

C言語ではそういった処理に特別な名前を与え、何度でも再利用したりすることが出来るような仕組みがあります。それを関数(かんすう)と言います。

私たちはすでにprintf、scanfといった関数を利用してきました。これらは通常C言語で標準的に利用可能なものです。C言語ではそれ以外にユーザー自身が自分で関数を作ることができます。そういった関数はユーザー定義関数と言います。

(2) ユーザー定義関数を作る

ここでは実際にユーザー定義関数を作ってみましょう。てはじめにあるプログラムから重複する処理を関数として定義するという処理を実際にやって見ましょう。まずは、以下のプログラムを見てください。

list6-1:main.c
#include <stdio.h>

int main(int argc, char** argv) {
    double d1,d2,d3;
    double a = 1.2,b = 3.4,c = 2.7;
    //	同じ計算が3回
    d1 = (a + b) / 2.0;
    d2 = (4.1 + 5.7) / 2.0;
    d3 = (c + 2.8) / 2.0;
    printf("d1 = %lf,d2 = %lf,d3 = %lf\n",d1,d2,d3);
    return 0;
}
実行結果
d1 = 2.300000,d2 = 4.900000,d3 = 2.750000

d1,d2,d3は、それぞれ様々な数値の平均値を求めて入れる処理だと仮定します。このようなプログラムをかくと、7行目から9行目のように、 同じ処理を複数記述しています。もしも、今後同じような処理を何度も行わなくてはならないとすると、同じ計算処理を繰り返し記述する必要があり非常に面倒です。

そこで、ユーザー定義関数の出番になるわけです。この平均値を求める処理をユーザー定義関数に置き換えて変更したのが以下のプログラムです。

list6-2:main.c
#include <stdio.h>

//	平均値を求める関数の定義
double avg(double m,double n){
    //	引数l,mの平均値を求め、rに代入する。
    double r = (m + n) / 2.0;
    return r;
}
                                 
int main(int argc, char** argv) {
    double d1,d2,d3;
    double a = 1.2,b = 3.4,c = 2.7;
    //	同じ計算が3回(関数を呼び出して計算)
    d1 = avg(a,b);
    d2 = avg(4.1,5.7);
    d3 = avg(c,2.8);
    printf("d1 = %lf,d2 = %lf,d3 = %lf\n",d1,d2,d3);                              
    return 0;
}

実行結果は、list6-1と一緒です。ただ、main()の中を見てみるとわかると思いますが、先程の平均値の計算処理を行っている個所が、全て、avgという文字列に入れ替わっています。 このavgが関数であり、main()の手前で定義されています。では、一体、この関数はどのような仕組みになっているのでしょうか?


(3) 関数の仕組み

関数の仕組みを説明するために、まずは関数の書式をみてみます。

関数の定義の書式
戻り値の型 関数名(引数の型 引数1,引数の型 引数2,・・・,引数の型 引数n){ 処理 return 戻り値; ← 省略されることもある }

基本的に関数には、好きな名前を付けることができます。list6-2の場合avgという名前をつけています。関数の名付けのルールは変数の場合と似ています(2日目参照)。そのため予約語を用いることはできないので注意ましょう。

(4) 引数と戻り値

また、その名が示す通り、関数は、入力する値に対し、何らかの処理を行って値を出力することが求められます。入力する値のことを、引数(ひきすう)と言います。 引数は、複数定義することが可能であり、その場合は、間を,で区切ります。また、省略することも可能です。(図6-1)

図6-1.関数のイメージ①
関数のイメージ①

list6-2のケースでは、double型の変数、mとnが引数となっています。これらの変数には、関数を呼び出す側が入れた数値が入ります。例えば、15行目では、avg(4.1,5.7)としていますので(図6-2:①)、この場合、mに4.1が、nに5.7が代入されます。(図6-2:②)

この値が計算の用いられ、それがに代入され、return(リターン)文によって、呼びだした側(この場合はmain)に返されます。(図6-2:③)return r;とは、「戻り値として、rの値を返しなさい」という意味であり、この結果は、doubleであることから、戻り値の型の部分にdoubleと記述してあります。

そして最後に、戻り値は変数d2に代入されます。(図6-2:④)

このようにして定義された関数を呼び出しているのが、main()の中の14~16行目です。ここでの戻り値はここで変数d1~d3に代入されます。

図6-2.関数のイメージ②
関数のイメージ②

(5) 関数のプロトタイプ宣言

また、list6-2は、更に発展させて、以下のように書くことができます。

list6-3:main.c
#include <stdio.h>

//	関数avgのプロトタイプ宣言
double avg(double,double);
 
int main(int argc, char** argv) {
   double d1,d2,d3;
   double a = 1.2,b = 3.4,c = 2.7;
   //	同じ計算が3回(関数を呼び出して計算)
   d1 = avg(a,b);
   d2 = avg(4.1,5.7);
   d3 = avg(c,2.8);
   printf("d1 = %lf,d2 = %lf,d3 = %lf\n",d1,d2,d3);
   return 0;
}

//	平均値を求める関数
double avg(double m,double n){
    //	引数l,mの平均値を求め、rに代入する。
    double r = (m + n) / 2.0;
    return r;
}

実行結果は、list6-1およびlist6-2と一緒ですが、list6-2との違いは、main()の前にあったavg関数の定義が、後ろに移動したことです。かわりに、プロトタイプ宣言と呼ばれる処理が、書かれています。これはそのあとに呼び出す関数の型をあらかじめ予告しておくものです。

avg関数のプロトタイプ宣言
double avg(double,double);

プロトタイプ宣言とは、C言語のコンパイラに「これからこのような関数を定義しますよ」ということを伝えるためのものです。書式は関数の定義の冒頭に似ていますが、引数の変数名は省略することができ、かわりに引数の変数の型だけを記入します。

avg関数の場合、double型の引数を2つ必要とするので、引数部分が「(double,double)」としています。

なぜプロトタイプ宣言が必要かという理由は様々ですが、あらかじめ関数の型を決めておけば、そのあとで様々な場所で利用することが可能だからです。

なお、関数から、ほかの関数を呼び出すことも可能です。このとき、特に関数が、自分自身を呼び出すような処理を再帰呼び出しと言います。詳しくは、こちらを参照してください。

6-2.様々なタイプの関数

(1) 複数の関数を定義する

関数にはさまざまなタイプがあります。次は様々なタイプの関数をみてみましょう。

list6-4:main.c
#include <stdio.h>

//プロトタイプ宣言
int max(int,int);
void show(int);
void line();
 
int main(int argc, char** argv) {
   int n1 = 4,n2 = 5;
   line();
   show(n1);
   show(n2);
   printf("二つの数のうち、大きい数は、%dです。\n",max(n1,n2));
   line();
   return 0;
}

//	2つの整数のうち最大値を求める関数
int max(int a,int b){
	if(a > b){
		return a;
	}
	return b;
}
//	数値を表示する関数
void show(int n){
	printf("数値:%d\n",n);
	return;
}
//  ラインを表示する
void line(){
	printf("*********\n");
}
実行結果
********* 数値:4 数値:5 二つの数のうち、大きい数は、5です。 *********

(2) void

list6-4では、3つの関数が使用されています。冒頭でその関数のプロトタイプ宣言がなされています。

表6-1:list6-4で使用される関数
関数名 戻り値の型 引数 処理内容
max int int a,int b 2つの引数a,bのうち最大の値を返す
show void int n 引数で渡された整数の値nを表示する
line void なし 「*********」を表示

show()関数と、line()関数の戻り値の部分に書かれているvoid(ボイド)とは、関数の戻り値が無いということを意味しています。

したがって、これらの関数は、戻り値を持ちません。また、line()関数は、()内に何も書かれていないことからもわかるとおり、引数のない関数です。このような場合、以下のようにvoidをかくこともありますが、省略することも可能です。

引数のない関数の他の記述例
void line(void);

このケースでは、省略してる記述方法を採用しています。

(2) 関数の途中で処理を終える

次にこれらの関数の戻り値に注目してみましょう。max関数は、if文で引数aとbの値を比較し、もしもaがbよりも大きければ途中で「return a;」を実行して、途中で関数を終えています。(図6-3)

図6-3.途中で関数を抜ける処理
途中で関数を抜ける処理

このように、関数の途中でreturnで関数を終えることも可能です。

(3) 戻り値の省略

また、戻り値の型が「void」、つまり戻り値がない場合は「return」の後に戻り値を記述する必要がありません。

戻り値がない関数のreturn
void show(int n){ ← 戻り値の型が「void」なので引数がない printf("数値:%d\n",n); return; ← 戻り値がないのでreturnの後に戻り値がない }

また、戻り値がない関数は、returnを省略することができます。ここでは、line()関数でreturnが省略されています。show()関数では省略されていませんが、見てもわかるとおり、戻り値にあたるものが記述されていません。

戻り値がない関数のreturnの省略
void line(){ ← 戻り値の型が「void」なので引数がない printf("*********\n"); } ← returnが省略される

通常、戻り値のない関数は、returnを省略するケースが多いのですが、何らかの理由で、途中で処理を抜ける場合などは、returnを使うと便利でしょう。

(4) main関数

ここまでくると、1日目から特に説明をせずに使ってきたmain()の正体がなんとなくわかってきたのではないでしょうか。

mainもまた関数の一種ですが、C言語でプログラムがここから始まるという特殊な関数です。戻り値はintであり、最後に「return 0;」で戻り値0を返すルールとなっています。

引数はポインタと呼ばれる特殊な変数です。詳細はコラムで詳しく説明しますので、詳しくはそちらをご覧ください。

6-3.グローバル変数とローカル変数

(1) 変数のスコープ

次に、変数のスコープ(有効範囲)について説明しましょう。

list6-5:main.c
#include <stdio.h>
    
//	グローバル変数
int global = 10;

//プロトタイプ宣言
void func1(double,int);
void func2();
    
int main(int argc, char** argv) {
    double a = 123.41;
    int b = 100;
    printf("main処理中\n");
    printf("global=%d\n",global);
    printf("a=%f b=%d\n",a,b);
    printf("******************\n");
    // func1を呼び出し
    func1(3.1,4);
    // func2を呼び出し
    func2();
    return 0;
}

//	func1
void func1(double a,int b){
    printf("func1処理中\n");
    printf("global=%d\n",global);
    printf("a=%f b=%d\n",a,b);
    printf("******************\n");
}
//	func2
void func2(){
    double a = -4.1;
    int b = 2;
    printf("func2処理中\n");
    printf("global=%d\n",global);
    printf("a=%f b=%\n",a,b);
}
実行結果
main処理中 global=10 a=123.410000 b=100 ****************** func1処理中 global=10 a=3.10000 b=4 ****************** func2処理中 global=10 a=-4.10000 b=2 ******************

このプログラムでは、a、bおよびglobalという3種類の変数が用いられており、それらの値をmain、func1、func2でそれぞれの値を表示しています。

実行結果をみるとglobalは全て同じ値である10となっていますが、a、bの値は各関数の中で値が違います。この違いはそれぞれの変数のスコープの違いに由来するのです。

(2) ローカル変数とグローバル変数

変数のスコープには以下のようなものがあります。(表6-2)

表6-2:C言語のスコープ
No 名前 概要 有効範囲
ブロックスコープ ブロック({}で囲まれた部分)内で定義された変数。
if、while、for文の中や関数の中で宣言された変数
ブロック内
ファイルスコープ ファイル(.cファイルなど)全体で有効なスコープ。
ファイルの冒頭で宣言される。
ファイル内全体

関数やブロック内で定義されたブロックスコープを持つ変数をローカル変数と言います。

a、bという変数はそれぞれ、main()、および、func1()、func2()の範囲内のみで有効なローカル変数です。ローカル変数は、その変数が定義されている関数内のみで有効です。func1()のように、関数の引数もこの範疇に入ります。ローカル変数はブロックから抜けた時に廃棄されます。

それに対し、globalはプログラムの先頭で定義されたグローバル変数と言います。グローバル変数はファイルスコープを持ち、main.cファイル内のどの関数で呼び出しても同じものを指します。

(3) 変数のスコープと変数名

ローカル変数は、定義されている関数が違えば、同名のものを定義してもそれぞれ別のものとして扱われます。それに対し、グローバル変数の場合は、プログラム全体でただ一つしかありません。したがって、名前の重複は許されません。(図6-4)

図6-4.変数のスコープ
変数のスコープ

練習問題 : 問題6.


一週間で学べるコースの一覧
Udemy
...
2024/10/01

Udemyでも学びましょう!

一週間でわかるC言語・C++言語がオンライン講座になりました!動画音声によってさらにわかりやすくなりました!! 1講座で2つの言語を学ぶことができる上に、練習問題の回答もダウンロードできます。

Read →
Impress一週間シリーズ
1週間でC言語の基礎が学べる本
2024/10/01

書籍化された一週間シリーズ

本講座が「1週間でC言語の基礎が学べる本」として書籍化されました!サイトの内容プラスアルファでより学習しやすくなっています!Impressより発売中です!!

Read →
Impress一週間シリーズ
...
2024/10/01

書籍化された一週間シリーズ

一週間シリーズは書籍化されています。こちらもどうぞ!

Read →
プログラマーなら欲しいグッズ
プログラミンググッズ

プログラミンググッズ

快適なプログラミング環境を構築したい人々にぜひとも揃えてほしいグッズです。

Read →
制作・管理
シフトシステム株式会社

シフトシステム株式会社

このサイトはシフトシステム株式会社によって制作・管理がなされています。

Read →