文字列処理関数を利用するだけでなく, 自分自身で作成し,仕組みを理解しよう.
基本的な文字列処理関数は標準ライブラリ 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() を利用してよい.)