関数を使う


C言語の文法を学ぶことは、いわば家の建て方を勉強するのと同じです。これまでの連載では、木材の加工の仕方を解説してきたわけです。しかし、家を建てるためには、他にも知らなければならないことがあります。今回は、釘、ネジなど、家を建てるために使う加工済みの資材のお話です。


関数とは何か

 C言語には、簡単に高機能なプログラムを実現するためのしくみとして「関数」が用意されています。これまでの連載のList内に出てきた、printf()やmain()といったものが、実は「関数」なのです。まずは「関数」の概念的背景から解説することにしましょう。

Fig.1:サブルーチン

プログラムの構造化
 かつて最新鋭のコンピュータが、現代の携帯ゲーム機の足元にも及ばない能力だった頃の話です。当時、コンピュータに仕事をさせようとしていた我々の先輩たちの懸案事項は、限られた計算機資源(CPUパワーやメモリ)の中で如何に複雑な処理を実現するかでした。この問題に対処するため、「サブルーチン」というプログラミングの概念が生まれました。
 これは、処理の流れの中で出てくる同様の処理をまとめてしまおうという考え方で、「サブルーチン」というコードのかたまりを使いまわし、同じ処理を何回も書かないようにしたのです。サブルーチンの導入で、プログラムを小さくでき、計算機資源の節約が実現できたのですが、同時に、プログラムを構成する機能ごとにサブルーチンを作成することで、プログラムを見やすく、理解しやすくするという大きなメリットもありました。
 プログラムをサブルーチンという部品の集合体として考えることができるようになったため、設計時からサブルーチンによるプログラムの構成を考えるようになりました。
 例えばフォトレタッチソフトでは、同じフィルタを複数回適用したり、複数種類のフィルタを順番に適用したりしてさまざまな効果を得ようとしますが、各種フィルタがそれぞれ互いに分離独立していないと、柔軟なフィルタの適用が難しくなります。こうした場合に、各種フィルタをそれぞれサブルーチンとして作成するように考えるわけです。(Fig.1参照)
 このように、プログラム内部にサブルーチンによる構造を構成しようという考え方を「構造化プログラミング」と呼び、今日まで設計技法の土台となる概念として利用されています。

Fig.2:サブルーチン、入出力装置、関数

Fig.3:プログラムと関数

入出力装置
 次に、プログラム中でのサブルーチンの意味を考えてみましょう。サブルーチンの集合によってプログラムが構成されるとした場合に、個々のサブルーチンとその外部とのやりとりがどのように定式化できるかを考えます。
 まず、サブルーチンは、何らかの仕事(処理)を行います。これは自発的に仕事をするのではなく、プログラムの流れ上、いわば命令される形で仕事にとりかかります。人間関係に置き換えると、サブルーチンは部下やアシスタントに相当するといえるでしょう。このため、サブルーチンに仕事をさせることを「呼ぶ(call)」と表現します。サブルーチンを呼ぶ場合には、仕事の依頼内容を伝える何らかのしくみが必要になります。
 呼ばれたサブルーチンが仕事を終えたとき、今度は仕事の結果を上司に報告しなければなりません。実務を終えたとしても、終わったことを伝えなければ、まだ作業中だと上司は判断するでしょう。したがって、サブルーチンが仕事を終えたときに、自分を呼んだモノに対して、仕事が終了したことを伝える何らかのしくみが必要になります。
 このように考えると、サブルーチンには、自身が仕事をするためのしくみの他に、依頼を受けるしくみと、結果を報告するしくみの2つが必要であることが分かります。この3つのしくみを持ったモノのことを、一般的に「入出力装置」といいます。(Fig.2参照)
 プログラミング言語によっては、サブルーチンへの依頼や、サブルーチンからの報告を、言語レベルで十分にサポートしていない「サブルーチン≠入出力装置」なものもありますが、C言語では、入出力装置としての特徴を完全に備えたサブルーチンを表現するしくみ「関数」が使用できるようになっています。
 ちなみに、コンピュータで動作するプログラム自体も入出力装置だと考えられます。いってみれば、入出力装置が入出力装置から構成されているわけですが、これは分子という粒が原子という粒から構成されていたり、人間という生物が細胞という生物から構成されていたりするのと同じで、科学の世界では、そんなに珍しいことではありません。(Fig.3参照)

C言語の関数、数学の関数
 「関数」というと、数学にでてきた「関数」と同じ名前ですね。数学で出てきた関数というと

f(x) = (x - 2)(x - 1)
y = f(3)
とする時
y = 2

というやつの「f」です。
 実は数学の関数も、入出力装置の1種なのです。まず、fの実体は何らかの式なので、fは、計算するという仕事を行うしくみを持っていると考えられます。次にfは数値を受け取るしくみを持っていますので、数値を受け取ることが、その数値で計算を行えという命令を受けることだと解釈できます。また、計算結果が、f自身の値とされるので、仕事の結果がf自身の値として報告されるとみることができます。先程の例のfは、(x-2)(x-1)を計算する能力を備え、xとして3の値を受け取ると計算開始命令と解釈して計算し、結果をf(3)の値は2だとして報告していたわけです。
 C言語の関数は、サブルーチンを表現する入出力装置のモデルに、数学の関数を利用しています。C言語独自に入出力装置の表現形態をつくるのではなく、すでによく知られた数学の関数を利用することで、プログラミングを行う人に優しい言語になっているわけです。
 日本語の「関数」は極めてイメージのとらえにくい言葉です。しかし、英訳して「function」になると、functionの持つ豊富なイメージに投影して、なんとなく分かるような気がしませんか? また、昔の表記方法「函数」なら、入出力装置を箱に見立てたイメージを想像できて、分かりやすいかも知れません。言葉遊びのきらいはありますが、皆さんの頭の中でイメージが形成される一助になれば幸いです。

関数の骨格
 数学の関数が元ネタになっているC言語の関数ですが、プログラミングの世界は数学の世界ほど抽象的ではないので、現実世界のしがらみ(コンピュータのしくみ)によって、いくつかの制約を受けます。
 例えば、実際の数学とは異なり、C言語では扱う値に全て型が存在するので、関数に渡す値(引数)、結果の値(戻り値)がどのような型であるかを厳密にしなければならないことも、制約の一つです。関数を呼び出すときに、与える引数はどのような型であるのか、戻り値を受け取って変数に代入するとき、どのような型の変数を用意すればよいのかといった事柄が分かっていなければなりません。
 C言語に関するマニュアル、ヘルプなどを開くと、関数が次のように表記されていることがあります。

int function(int x, int y);

このような表記は「プロトタイプ宣言」と呼ばれ、実際にソースの中で、呼び出す関数の引数、戻り値を明らかにしておくために記述されます。この表記は「functionという名前の関数があります。この関数の引数は、int型のxとyの2つで、戻り値はint型です」ということを意味しています。こうした内容が分かっていないと、C言語の関数は利用できないしくみになっています。

標準関数を呼び出す

 それでは実際に関数を使ってみましょう。
 ANSI-C準拠の標準的なCコンパイラでは、標準関数と呼ばれる100個以上の関数があらかじめ用意されています。List.1では、そうした標準関数に含まれる、printf()とstrcpy()を使用しています。(詳細な解説はコラム参照の事)
 List.1では、strcpy()がprintf()の引数として与えられています。これは、数学において

f(x) = (x - 2)(x - 1)
g(x) = 10x + 3
y = g(f(3))
とする時
y = 23

と記述できるのと同じ意味です。変数と関数は、型が同じであれば、記述上、同じように扱うことができます。
 標準関数は、そのほとんどが、汎用性のある関数になっています。しかも、ほとんどのCコンパイラで使用可能です。このため、プログラムを作成する時には、標準関数を多用することになるでしょう。逆に、標準関数にどのようなものがあるのか知っていないと、同じ機能を持った関数を自分で作るハメになります。標準関数は、家を建てる時の釘やネジに相当します。釘やネジを知らなかったり、その使い方を知っていなければ、家を建てるのは困難になるでしょう。試行錯誤の末、釘やネジを独自に発明しても、家を建てるのに、釘やネジから作っていては大変です。

List.1:list1.c


#include <stdio.h>
#include <string.h>

int main()
{
	int a = 10;
	double b = 172.3548;
	char c[] = "ABC", d[4];

/* \の使い方 */
	printf("改行>\n<\n");
	printf("TAB>\t<\n");
	printf("各種記号>\\ \" \' %%<\n");

/* %の使い方 */
	printf("10進数を出力:%d\n", a);
	printf("実数を出力:%lf\n", b);
	printf("実数を出力(その2):%.3lf\n", b);
	printf("文字列を出力:%s\n", c);
	printf("16進数を出力:%x\n", a);
	printf("16進数を出力(その2):%X\n", a);
	printf("16進数を出力(その3):%02X\n", a);

/* printf()の戻り値 */
	a = printf("この文字列の出力バイト数は?\n");
	printf("%d bytes\n", a);

/* strcpy()の使用例 */
	printf("strcpy()の使用例:%s\n", strcpy(d, c));

	return 0;
}

List.2:list2.c


#include <stdio.h>
#include <string.h>

/* プロトタイプ宣言 */ 
int hello();
int f(int x);
int g(int x);

int main()
{
	int result;

/* 作成した関数の呼び出し */
	printf("g(f(3)) = %d\n", g(f(3)));
	result = hello();

	switch(result) {
		case 0:
			printf("hello() is complete.\n");
			break;
		case -1:
			printf("hello() is not complete.\n");
			break;
		default:
			printf("hello() is error.\n");
	}

	return 0;
}

/* 関数本体 */

int hello() {
	int printed_length;

	printed_length = printf("hello,world.\n");

	if(printed_length == 13)
		return 0;
	else if(printed_length < 13)
		return -1;
	else
		return 1;
}

int f(int x) {
	return (x - 2) * (x - 1);
}

int g(int x) {
	return 10 * x + 3;
}

関数を作る

 標準関数を使うと、さまざまな処理を間単に行うことができますが、標準関数にない機能を持ったものを使いたければ、自分で関数を作らなければなりません。
 List.2は、「hello,world.」と出力する関数hello()と、先に出てきた数学的関数f(x)、g(x)をC言語の関数として表現した例になっています。
 作成された3つの関数は、main()内部で呼び出されていますが、3つの関数がどういった関数かがmain()より後に書かれているため、このままでは3つの関数をmain()で使うことができません。そこで、プロトタイプ宣言をmain()の前に置いて、3つの関数を呼び出すときに知っていなければならない引数、戻り値の型の情報が分かるようしています。
 関数本体の記述は

戻り値の型 関数名(引数の型 引数名[, …]) {
    処理
    return 戻り値;
}

という形式をとります。引数は、関数内部で作成済みの変数として利用でき、関数呼び出し時に与えられた値が入れられた状態で、関数内部の処理を開始します。処理の最後に、returnを使って、処理の結果、戻り値として返したい値を返すように記述しておきます。
 これまでおまじないとして使っていたmain()も、こうして見れば、関数の1種だったことが分かります。main()関数は、プログラムが実行されるときに無条件に呼び出される関数です。main()の中から他の関数を呼ぶことで、処理が進んでいくとも考えられます。
 関数の処理内容によって、数値自体を返したい場合もあれば、処理の終了状態を返したい場合もあります。f()やg()では、数値自体を返していますが、hello()のような関数では、処理が正常に終了したことを伝えたいと考えるでしょう。処理の終了状態を表現する戻り値は、普通、正常終了したら0、異常終了したら0以外とします。これは、戻り値を真偽値判定した場合、0とそれ以外に区別されるので、異常終了のさまざまな要因を伝えるために、0以外を異常終了とした方が都合がよいからです。

演習:次の機能を持った関数を作成せよ。
@int if_odd(int x) xが奇数のとき1を返し、偶数のとき0を返す
Aint div_ok(int x, int y) x/yが割り切れるとき1を返し、そうでないとき0を返す
Bdouble h(double x, double y) (x + 2)(y - 2)の計算結果を返す

ローカル変数とグローバル変数

 関数内での処理を行うときに、さまざまな変数や配列や構造体を使いますが、こうしたデータを保存しておく箱には、箱の置き場所によって2つの種類があります。
 これまで使ってきた、関数内で作成する変数のことを「ローカル変数(局所変数)」といいます。この種類の箱は、一時的に箱を置いておく場所に置かれ、作成した関数が終了すると捨てられてしまいます。関数内という局所的な場所でしか使えないので、そのような名前がついているのです。また、このタイプの箱は、「部下には秘密」という特徴があります。例えば、main()から別の関数を呼んでいるとき、main()はまだ終了したわけではないので、main()内で作成した変数は存在しています。しかし、呼ばれた関数からは見ることができないのです。
 自分で関数を作成するようになると、ローカル変数では少し役不足な場合が出てきます。例えば、「何時如何なるときも、誰の参照でも受ける」といった変数が必要になる場合があります。そこで、プログラムが開始されてから終了するまで捨てられないで「何時如何なるときも、誰の参照でも受ける」箱として「グローバル変数(大域変数)」が用意されています。
 List.3では、グローバル変数piを用意して、main()からも、ensyu()からも、menseki()からも参照しています。この例のように、グローバル変数は、関数の外側で宣言が行われます。

演習:List.3に、球の体積を求める関数 double taiseki(double radius) を追加せよ。

List.3:list3.c


#include <stdio.h>
#include <string.h>

double ensyu(double radius);
double menseki(double radius);

/* グローバル変数の宣言(作成) */
double pi;

int main()
{
	int a, b, i, j;

	pi = 3.1415;

	printf("radius = 5.0\n");

	for(i = 4; i >= 0; i -= 2) {
		a = 1;
		for(j = 0; j < i; j++)
			a *= 10;
		b = pi * a;
		pi = (double)b / a;
		printf("pi = %lf\n", pi);
		printf("ensyu = %lf\n", ensyu(5.0));
		printf("menseki = %lf\n", menseki(5.0));
	}

	return 0;
}

double ensyu(double radius) {
	return 2 * pi * radius;
}

double menseki(double radius) {
	return pi * radius * radius;
}

まとめ

 今回は「関数」という、部品を使ってプログラミングするしくみについて解説しました。
 部品を使って効率よくプログラミングすること、良い部品を作ることのどちらも大切だと思うのですが、最近はコンピュータのシステムが肥大化したことも手伝って、たくさん部品を知っていないとプログラムが作れないような風潮があります。何でも自分で作るというのも変ですが、他人の作った部品を貼り合せるだけというのも変だと思います。バランスのとれた考え方ができるように精進したいものです。
 次回は「ポインタ」という、ちょっぴりヤなやつの話です。でも、ほんとうはイイやつです。

 それでは。

ソースファイル一式


コラム

#includeと#define
 これまで示してきたソースリストでは、必ず最初の行に

#include <stdio.h>

と書かれていました。私が最初にC言語を学んだときには「おまじないだと思っときなさい」といわれたものですが、いつまでも「おまじない」では気持ち悪いですよね。
 C言語では、本連載で解説してきた通常の文法とは別に、「マクロ」と呼ばれるしくみを実現するための「プリプロセッサ命令」というものが使えます。「マクロ」というのは、別になくてもいいけれども、あるとすごく便利な(ソースを書くのが楽になる)しくみのことだと思ってもらえればよいでしょう。「#」で始まる記述がプリプロセッサ命令で、#includeもその1種なのです。
 「#include」は、「指定した別のファイルの内容を、命令を書いた部分に貼り付ける」という命令です。つまり、毎回ソースを書くたびに、毎回お約束のように同じ内容を書いてもいいわけですが、その内容をファイルにしておいて#includeを使うことで、ソースを書くのが少し楽になるわけです。
 C言語では特に、標準関数を利用するときに必要となるプロトタイプ宣言や各種定義を記述した「ヘッダファイル」をincludeすることで、標準関数の利用を容易にしています。ヘッダファイルと#includeがなければ、利用する全ての標準関数の、全ての引数、戻り値を調べて、自分でプロトタイプ宣言を書かなければなりません。
 標準関数をはじめ、コンパイラに付属するライブラリなどを利用するためのヘッダファイルは、通常、コンパイラ本体がインストールされているディレクトリ以下のincludeディレクトリに格納されています。このいわばコンパイラ管理下のヘッダファイルをincludeする場合には

#include <string.h>

のように、ヘッダファイル名を<>で囲みます。
 一方、自分で作成したヘッダファイルを利用する場合には、作成したヘッダファイルを、それをincludeするソースファイルと同一のディレクトリに置き、

#include "mylib.h"

のように、ヘッダファイル名を""で囲みます。List.4と、List.5 + List.6が、まったく同じ意味になることを確認してみてください。
 ヘッダファイル内に何を書けばよいのかという疑問がわく人も多いと思います。そうした場合は、コンパイラ管理下の標準ヘッダファイルを実際にのぞいてみるとよいでしょう。stdio.hがどういったポリシーに基づいて書かれているのかを見れば、ヘッダファイルのお約束を体感できます。
 あと、#includeと同じようによく使われるプリプロセッサ命令である「#define」を紹介しておきます。「define」を直訳すると「定義」ですが、その名のとおり、「別名を定義する」命令です。
 例えば、

#define HOGE_MAX 100

と記述すると、その行以降の全てのHOGE_MAXを、コンパイル時に100に置き換えてくれます。このしくみを使えば、作成したプログラムを後で改変する場合にラクができます。もし#defineがなくて、すべて100と書かれていると、100を150に変更する場合に書き換えなければならない行の数はいくらになるか分かりません。しかし、#defineを使ってHOGE_MAXを定義し、HOGE_MAXを使っておけば、定義自体を変更するだけでよいので、書き換えは1行で済みます。実際にList.7とList.8を見比べれば、理解してもらえるでしょう。
 プリプロセッサ命令には、条件によってコンパイルする部分を制御したり、defineをテクニカルに使ったりといった、さまざまな使用方法があります。もし見たことのない「#〜」に出会ったら、意味を調べるのはもちろん、もしそのしくみがなかったらどう不便なのか考えるようにすると、理解しやすくなるでしょう。

List.4:list4.c


#include <stdio.h>
#include <string.h>

typedef struct {
	char name[32];
	int length, weight, test[5];
} member;

int main()
{
	member jan;

	strcpy(jan.name, "jan");
	jan.length = 172;
	jan.weight = 63;
	jan.test[0] = 55;
	jan.test[1] = 79;
	jan.test[2] = 82;
	jan.test[3] = 44;
	jan.test[4] = 52;

	printf("jan\'s weight : %d\n", jan.weight);

	return 0;
}

List.5:list5.c


#include "list6.h"

int main()
{
	member jan;

	strcpy(jan.name, "jan");
	jan.length = 172;
	jan.weight = 63;
	jan.test[0] = 55;
	jan.test[1] = 79;
	jan.test[2] = 82;
	jan.test[3] = 44;
	jan.test[4] = 52;

	printf("jan\'s weight : %d\n", jan.weight);

	return 0;
}

List.6:list6.h


#include <stdio.h>
#include <string.h>

typedef struct {
	char name[32];
	int length, weight, test[5];
} member;

List.7:list7.c


#include <stdio.h>

int main()
{
	int a[100], i;
	unsigned char b = 171;

	printf("generate a[]\n");
	for(i = 0; i < 100; i++) {
		b = (b << 1) | (((b >> 7) ^ (b >> 4)) & 1);
		a[i] = b;
		printf("%d ", a[i]);
	}
	printf("\n\n");

	printf("a[] < 128\n");
	for(i = 0; i < 100; i++)
		if(a[i] < 128)
			printf("%d ", a[i]);
	printf("\n");

	return 0;
}

List.8:list8.c


#include <stdio.h>

#define MAX 100

int main()
{
	int a[MAX], i;
	unsigned char b = 171;

	printf("generate a[]\n");
	for(i = 0; i < MAX; i++) {
		b = (b << 1) | (((b >> 7) ^ (b >> 4)) & 1);
		a[i] = b;
		printf("%d ", a[i]);
	}
	printf("\n\n");

	printf("a[] < 128\n");
	for(i = 0; i < MAX; i++)
		if(a[i] < 128)
			printf("%d ", a[i]);
	printf("\n");

	return 0;
}

前回の演習問題の答え
 プログラムを作成する演習問題に関しては。答えとなるソースファイルを、今月号のCD-ROMに収録してあります。確認しておいて下さい。
 「初対面の人に会った場合、自分がどのような行動を行うのか、C言語風に表現せよ」は、例えばList.9のように表現できます。
 複数のデータを特定の条件で順番に並べ直す処理のことを「ソート」と呼びますが、「配列に入れられた数字を小さい順に並べるプログラムを書け」は、まさにこの「ソート」を行うプログラムを作成せよという問題です。「ソート」に関しては、次々回に詳しく取り上げる予定ですので、答えは次々回に持ち越しとさせてもらいます。

List.9:「初対面の人に会った場合、自分がどのような行動を行うのか、C言語風に表現せよ」


#define 男性 1
#define 女性 2
#define 自分の年齢 28

int 敬語フラグ = 0, 年齢, 性別, 印象;

年齢 = 相手の年齢を何とかして知る();
性別 = 相手の性別();
印象 = 第1印象();

if(相手が酒を飲むか?())
	印象 *= 1.2;

if(年齢 > 自分の年齢)
	敬語フラグ = 1;

if(性別 == 女性) {
	switch(年齢 / 10) {
		case 0:
			ちゃんとした大人として接する(年齢);
			break;
		case 1:
		case 2:
			なんかそれなりに心の準備(印象);
			break;
		default:
			甘える();
	}
}
else {
	switch(年齢 / 10) {
		case 0:
			ちゃんとした大人として接する(年齢);
			break;
		case 1:
			ちょっと気をつける(年齢);
			break;
		case 2:
			友人として接する(印象);
			break;
		default:
			甘える();
	}
}

今まで出てきた標準関数一覧
 今回から、連載中で使用している関数に関する解説を掲載します。今回は、今までの連載に登場した関数を解説します。

#include <stdio.h>
int printf(char *format[, ...]);

 第1回から皆勤賞な関数printf()は、標準ヘッダファイルstdio.hでプロトタイプ宣言が行われています。printf()を使用する際には、stdio.hをインクルードしておく必要があります。
 「printf」は「print + f(ormat)」の意味で、書式(format)化された出力を書き出し(print)ます。ここでいう出力先は、OSなどによって規定される「標準出力」と呼ばれる出力先で、通常はコンソール(例:DOS窓など)に設定されています。これまでのプログラムで、画面に文字が出力されていたのは、このしくみがあったためなのです。
 printf()で出力される文字列の内容は、基本的に第1引数によって決まります。連載第1回の「hello,world.」のように、ダブルクォートで括られた文字列を出力するのが基本になります。また「\(\)」で始まる記号列には特殊な意味(List.1参照)が与えられています。
 次に、第1引数の文字列中に「書式指定子」というものが含まれている場合に、その指定と第2引数以降の引数によって、書式化された出力を作成する機能があります。書式指定子は「%」から始まる記号の列で、「%d」や「%lf」、「%s」といったもの(List.1参照)です。第1引数の文字列内には、複数の書式指定子を含めることが許されており、埋め込まれた書式指定子は、前から順番に第2引数以降の引数と1対1対応し、対応する引数の内容に応じて置換されて出力されます。この機能によって、これまでの連載中で多用してきたように、プログラム中の特定の時点での変数の内容を出力することが可能なのです。
 printf()による出力は、プログラム作成時に、プログラムが正しく動作しているのかを確認する為に使用されることがあります。例えば、自分の考えたようにプログラムが動作していればプログラムの流れ上通るはず個所にprintf()を仕掛けておいて、実際に動作させて出力されるか確認したり、特定の個所での変数の内容が、自分の思ったとおりの値になっているかを確認したりするのに利用します。このようにprintf()を使ってプログラムの動作を追確認する作業を「printfデバッグ」と呼んでいます。プログラムの動作検証(デバッグ)を行う手法としては、もっとも手軽でもっとも多用される手法です。みなさんも、自分の作ったプログラムが思ったとおりに動作しない場合に使ってみてください。
 printf()の戻り値はint型で、出力した文字列データのバイト数を返します。これは出力した文字数とは異なるので、注意が必要です。一般的には、全角文字は1文字2バイト、半角文字は1文字1バイトと考えておけば、ほぼ正しいでしょう。正しく詳しく知りたい人は、文字コードに関する知識など、日本語処理について勉強してください。
 呼び出し時に与えている引数に不正なものが混じっていたりといった何らかの理由で、printf()内部で不具合(エラー)が発生した場合は、「EOF」という値が戻り値になります。「EOF」はstdio.h内で#defineによって定義されているので、

if(printf("ABC") == EOF) {
    printf()出来なかった場合の処理
}

のように利用することができます。
 C言語の関数では、引数の個数が可変である関数を作ることができますが、printf()はそうした関数のもっともよく知られた例といえます。Cコンパイラの中には、標準関数のソースが付いているものがありますが、そうしたC言語自身を構成するソースを読むことで、C言語に関する理解を深めることと、プログラミング手法を勉強することの一石二鳥が可能です。printf()のソースを読むことは、いい経験になると思います。機会があれば、是非読んでみてください。

#include <string.h>
char *strcpy(char *dest, char *src);

 第2回の演習問題の解答で使用した関数strcpy()は、文字列を扱う関数のプロトタイプ宣言などがまとめられているstring.hに、プロトタイプ宣言があります。
 「strcpy」は「str(ing) c(o)py」の意味で、文字列(string)をコピー(copy)します。具体的には、srcに表現された文字列を、destに与えられたchar型変数の配列にコピーします。srcの文字列に対して、コピー先のdestの要素数が十分でないと、不具合を生じます。3文字しか入らないところに10文字も入るはずはないので、使用時には十分に注意して下さい。
 C言語では、文字列データを格納するために、char型変数の配列を使用します。このため、文字列のコピーは、配列の要素を順にコピーしていくことに他なりません。そう考えるとstrcpy()と同じ機能を持った関数は簡単に作れそうですね。
 strcpy()は戻り値としてコピー先を表現するdestを返します。

 なお、本連載では、ANSI-Cで規定された標準関数以外の、開発環境に依存するライブラリ関数は使用していません。したがって、各リスト中で使用されている関数の利用法は、標準的なC言語に関する書籍や、Inet上のコンテンツで調べることができます。

前回の演習問題の解答例一式