文字列処理2

文字列処理関数を利用するだけでなく, 自分自身で作成し,仕組みを理解しよう.

教科書の該当範囲:第13章(13.2〜13.4)

文字列処理関数

Step 1. 標準ライブラリ関数の利用

基本的な文字列処理関数は標準ライブラリ libc にすでに用意されている. その中でも超基本的な関数 strlen(),strcmp(),strcpy() を使ってみよう. (cmp と cpy は前回も利用した.)

string.c:

#include <stdio.h>
#include <string.h>	// 標準文字列処理関数 str*() の宣言

#define	BUFLEN	256		// 文字列バッファのサイズ
#define	BUFFMT	"%255s"		// 文字列バッファの入力書式

int main(void)
{
	int	n, d;
	char	buf[BUFLEN];		// 入力文字列
	char	pre[BUFLEN] = "";	// 直前の入力文字列
	char	*fmt = BUFFMT;		// 入力書式
/*
	char	fmt[16];
	sprintf(fmt, "%%%ds", BUFLEN - 1);
*/

	while (1) {
		printf("文字列 > ");
		scanf(fmt, buf);

		d = strcmp(buf, pre);		// 文字列の比較
		if (d == 0) break;

		n = strlen(buf);		// 文字列の長さ
		printf("文字数=%d\n", n);

		strcpy(pre, buf);		// 文字列の代入(pre = buf)
	}
	return (0);
}

文字列をキーボード入力し, 文字数(文字列長)を表示する. 同じ文字列が続けて入力されたら終了する.

Step 2. 標準関数クローンの作成1

次に,標準文字列処理関数のクローンを作成してみたい. まずは,strlen() について, 仕様を確認しよう:

$  man  strlen

ソースコードは概ね次のようになるだろう:

#include ...

// strlen() クローンの定義例1(配列版)
int mystrlen1(char s[])
{
	int	n = 0;	// 文字数・要素番号

//	while (s[n] != '\0') {		// これでも良いですが...
	while (s[n]) {		// '\0' == 0 なので,コンパクトに記述
		n++;	// 文字数・要素番号のカウント(次の文字へ)
	}
	return (n);
}

// strlen() クローンの定義例2(ポインタ版)
int mystrlen2(char *s)
{
	int	n = 0;	// 文字数

//	while (*s != '\0') {
	while (*s) {
		n++;	// 文字数のカウント
		s++;	// ポインタの前進(参照先を次の文字へ)
	}
	return (n);
}

#define	...

int main(void)
{
	...
	while (1) {
		...
	//	n = strlen(buf);
		n = mystrlen1(buf);
	//	n = mystrlen2(buf);
		printf("文字数=%d\n", n);

		...
	}
	return (0);
}

配列版とポインタ版は,動作的には, どちらも標準関数と同じである. ソースコード的にも両者はあまり変わらないが, 実行効率的には,一般に,ポインタ版の方が優れている.

たとえば,文字列 s 内の1文字にアクセスする場合を考えよう. 配列版では s[i] のように記述することになるが, これは内部的には *(s + i) という, アドレス計算と間接参照とが実行されることになる.

一方,ポインタ版では間接参照 *s だけで済むため, 計算量が少ない. また,要素番号の変数も不要となり, メモリ使用量も少なくできる.

ただし,この strlen() クローンの場合, 単純すぎるので両者の性能は同じ. 両者を比較すると,配列版では s[i] の計算1回分, ポインタ版でも s++ の計算1回分が他方にないものであり, 計算量的には互角となる.

しかし,より複雑なプログラムの場合, s[i] とか *s を2回以上使うなら, 配列版が不利となる.

Step 3. 標準関数クローンの作成2

では,strlen() と同様に, strcpy() と strcmp() についても クローンを定義してみたい.

仕様確認:

$  man  strcmp
$  man  strcpy

ソースコード:

...
// #include <string.h>		// 標準文字列関数は不使用に

// strcpy() のクローンの定義例
char *mystrcpy(char *dst, char *src)
{
	char	*r;	// 戻り値
	r = dst;	// コピー先の先頭アドレス
	while (1) {
		*dst = *src;	// 1文字ずつ代入
		if (*src == '\0') break;
			// 終端記号も代入して終了
		src++;		// 代入元の次の文字へ
		dst++;		// 代入先も次の文字へ
	}
	return (r);
}

// strcmp() のクローンの定義例
int mystrcmp(char *s1, char *s2)
{
	while (*s1 == *s2) {
		if (*s1 == '\0') break;
		s1++;
		s2++;
	}
	return (*s1 - *s2);
}

...

int main(void)
{
	...
	while (1) {
		...

	//	d = strcmp(buf, pre);
		d = mystrcmp(buf, pre);

		...

	//	strcpy(pre, buf);
		mystrcpy(pre, buf);
	}
	return (0);
}

練習問題

次のような標準ライブラリ関数のクローンを作成せよ:


補足情報:ライブラリ関数のオンラインマニュアル

C言語の標準ライブラリ関数のマニュアルは, 一般に,次のようなコマンドで閲覧できる:

$  man  3  関数名

番号 3 を省略できる場合もある.

操作方法としては,ページ移動はカーソルキー,[Spc]キー,[B]キー等, 終了は [Q]キーとなっている.


(上級者向け)数値と文字列の相互変換

これまで,入出力関数で数値を入出力する際, scanf("%d"...) や printf("%f"...) 等としてきた. 実はこれらの関数内では, 数値そのものを入出力している訳ではなく, 文字列(数字列)を入出力している. また,そのために,数値を文字列へ変換したり, 逆に文字列を数値へ変換している.

次の関数について調査し,クローンを作成せよ: (標準ライブラリ関数を極力,使わずに...)


(c) 2018, yanagawa@kushiro-ct.ac.jp