文字列処理関数を利用するだけでなく, 自分自身で作成し,仕組みを理解しよう.
基本的な文字列処理関数は標準ライブラリ 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);
}
文字列をキーボード入力し, 文字数(文字列長)を表示する. 同じ文字列が続けて入力されたら終了する.
次に,標準文字列処理関数のクローンを作成してみたい. まずは,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回以上使うなら, 配列版が不利となる.
では,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"...) 等としてきた. 実はこれらの関数内では, 数値そのものを入出力している訳ではなく, 文字列(数字列)を入出力している. また,そのために,数値を文字列へ変換したり, 逆に文字列を数値へ変換している.
次の関数について調査し,クローンを作成せよ: (標準ライブラリ関数を極力,使わずに...)
(クローン作成では,putchar() を利用してよい.)
(クローン作成では,getchar() を利用してよい.)