07 月 19 日(火)3-4h

文字列処理(2)

今回は,テキストデータにおける 文字種の検査とデータ型の変換について理解しよう.


テキストデータの処理

テキストファイルや端末入出力(キーボード入力,画面出力)では, すべてのデータが文字列として取り扱われている. しかし,それらのデータをプログラムで処理する際には, 単純に文字列として取り扱うだけでよい,という訳ではない. たとえば,まず「数字列」を入力し, 「数値」に変換してから計算したり, 逆に,「数値」を計算し, 「数字列」に変換してから出力する必要がある.

数値」と「数字」とを区別しよう. int val = 123;123 は数値, char *num = "123";"123" は数字列(文字列)だ. (このページの末尾の補足も参照.)

たとえば,scanf("%d %d", &x, &y) で2つの整数を入力する場合, この関数の内部では,次のような手続きが実行される:

  1. キーボード入力(123 45)から 文字列("123 45")を作る.
  2. 文字列を1文字ずつ検査し, それらがすべて数字であれば, 空白で区切られた2つの数字列 ("123""45")に分解する.
  3. それぞれの数字列を数値 (12345) に変換する.
逆に,出力処理 printf("%d", x) ではどうなるか?

このように,データの検査や変換は, テキストデータの入出力では必須の処理である.


文字種の検査

文字の種類を調べるために,次のような標準ライブラリ関数が用意されている:

これらの文字種検査関数 is○○( ) はすべて, へッダファイル ctype.h の中で, 次のようにプロトタイプ宣言されている:

int  is○○(int  c);
不思議なことに,引数の型は char ではなく,int である. しかし,実引数として char 型のデータを与えても 仮引数の方では自動的に int 型へ変換されるので, 現段階では気にしないことにしよう. (自動的な型変換の規則について思い出そう.)

なお,is○○() の戻り値は,検査条件が成立しない場合に 0, 成立する場合に 0 以外の整数となる. 使用方法と処理内容については, List 1 と List 2 を参考にしよう.

List 1. isdigit( ) の利用例 isdigit-1.c
#include <stdio.h>
#include <ctype.h>

int main( )
{
	char *s = "otanoshike 2-32-1";	// 数字混じり文字列

	while (*s != '\0') {
		if (isdigit(*s)) printf("%c", *s);	// 数字だけ表示
		s++;
	}
	printf("\n");
	return (0);
}

List 2. isdigit( ) の定義例 isdigit-2.c
#include <stdio.h>
// #include <ctype.h>

// isdigit() のクローン
int myIsdigit(int c)
{
	if (c < '0') return (0);
	if (c > '9') return (0);
	return (1);
}

int main()
{
	...
//		if (isdigit(*s)) ...
		if (myIsdigit(*s)) ...
	...
}
文字種検査関数の検査対象は ASCII 文字(半角英数文字)だけだ. 日本語文字(全角文字)には使えない. 日本語の 1 文字は,2 byte 以上のデータとして記録されているので, C言語的には,文字(char 型)ではなく, 文字列(char 型の配列 or ポインタ)扱いとなる. (または,ワイド文字 w_char 型もある.) なお,日本語文字列の処理については,本授業の範囲外. 気になる者は,独自に調査せよ.

数字と数値の変換

数字列(文字列)から数値へ変換するためには, 次の標準ライブラリ関数を利用できる:

これらの変換関数のプロトタイプ宣言は, ヘッダファイル stdlib.h に記述されている.

では,変換関数について,仕組みを考えよう. List 3 は atoi( ) の基本的な(不完全な)定義例である.

ライブラリ関数の利用例については,各自で取り組もう.
List 3. atoi( ) の定義例(不完全版)
#include <stdio.h>
// #include <stdlib.h>

// atoi() の不完全なクローン
int myAtoi(char *s)
{
	int value = 0;

	while (*s != '\0') {
		value = value*10 + (*s - '0');
			// *s - '0' は,1個の数字を1桁の数値へ変換している.
			// value*10 は,数値の桁を繰り上げている.
		s++;
	}
	return (value);
}

int main()
{
	char s[256];
	int n;

	printf("整数 > ");
	scanf("%s", s);

//	n = atoi(s);
	n = myAtoi(s);

	printf("入力文字列:%s\n", s);
	printf("数値化結果:%d\n", n);

	return (0);
}

なお,文字同士の引き算 *s - '0' については, ASCIIコード番号の計算であることに注意しよう.

List 3 は機能的には不完全. 正の整数の文字列(例: "128")を与えた場合にはうまく動くが, 符号付き整数(例:"-64""+1024")の場合には 対応していない. また,文字列に数字以外の文字が混入していると,変な値を返してしまう. 本来の atoi() ではどうなっているか?各自で確認しよう.

逆の変換(数値から文字列への変換)については, 次の標準ライブラリ関数が便利だ:

sprintf(文字配列, 書式, ... )

この変換関数 sprintf( ) は, printf( ) の仲間であり, 書式付出力を画面表示する代わりに文字配列に書き込む. プロトタイプ宣言のへッダファイルは stdio.h である.

この関数 sprintf( ) には,たとえば,こんな使い道がある:

int  x, w;
char fmt[16];

printf("整数値と表示桁数 > ");
scanf("%d %d", &x, &w);	// ここでたとえば,x に 12,w に 5 を入力すると...

sprintf(fmt, "%%0%dd\n", w);	// 書式文字列が "%05d\n" となって...
printf(fmt, x);			// 出力は 00012 のように 5 桁になる

このテクニックは,表(table)を整形して表示する場合などに有効である.

上の例では,効果がわかり易くなるように, 余分な上位桁にゼロを表示するようにした. しかし,もちろん普通に使う書式は,%05d とかではなく, %5d とかにして,上位桁を空白で埋めるべき.

本日の課題

List 3 を改造して,atoi( ) の完全なクローンを定義せよ.

条件:

変換例:(本来の atoi( ) の動作例)

ヒント:

アドバイス: atoi( ) の定義例は教科書にも掲載されているが, そちらのコードは高度過ぎる(わかりづらい)ので,パクりは禁止. 必ず,わかりやすいソースコードを書くこと

特にループについては, 適切な方法(forwhile)を使い分けること:

今回の課題には, while ループの方が適している. 入力された数字列の桁数は,検査するまで不明なので.

余裕のある人は,次のような関数も定義してみては?:

レポート提出

注意事項: 以下の点についても厳しくチェックする:


補足

今回の課題では,まず, 1桁の 数字数値について, ちがいを理解しないことには, 手出しできない. 例:

で,さらに,複数桁の場合には... 例:


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