文字列処理の標準ライブラリ関数を利用するだけでなく, それらのクローン(clone;そっくりさん,等価な関数)を自分自身で作成し, 仕組みを理解しよう.
なお,文字列は,文字の配列であり, 連続的なメモリ領域に格納される. このため,ポインタを利用すると, 効率的な文字列処理が可能となる.
C言語の標準ライブラリ関数のマニュアルは, Linux 等のunix系OS では一般に, 次のようなコマンドで閲覧できる:
$ man 3 関数名
操作方法としては,ページ移動はカーソルキー,[Spc]キー,[B]キー等, 終了は [Q]キーとなっている.
基本的な文字列処理関数は, 入出力関数やファイル処理関数と同様に, 標準ライブラリ libc として,すでに用意されている. まず,その中でも超基本的な関数 strlen(),strcmp(),strcpy() を使ってみよう: string.c
$ cp ~/tmpl.c string.c $ gedit string.c &
#include <stdio.h> #include <string.h> // 標準文字列処理関数 str*() の宣言 #define BUFLEN 256 // 文字列バッファのサイズ #define BUFFMT "%255s" // 文字列バッファの入力書式(文字列定数,基本の技) int main(void) { char buf[BUFLEN]; // 入力文字列のバッファ(文字配列) char *fmt = BUFFMT; // 入力書式(文字列定数へのポインタ,基本の技) /* // or 入力書式の自動生成(上級者向けの技) char fmt[16]; // 入力書式のバッファ sprintf(fmt, "%%%ds", BUFLEN - 1); // 入力書式の自動生成 */ int n; // 文字列の長さ int d; // 文字列の比較結果 char pre[BUFLEN] = ""; // 直前の入力文字列 while (1) { printf("文字列(%d文字以内)> ", BUFLEN-1); scanf(fmt, buf); n = strlen(buf); // 文字列の長さの測定 printf("文字数=%d\n", n); d = strcmp(buf, pre); // 文字列の内容の比較 if (d == 0) break; // 同内容の文字列が続いたら終了 strcpy(pre, buf); // 文字列の内容の代入(pre = buf) } return (0); }
このプログラムでは,文字列をキーボード入力し, 文字数(文字列長)を表示する. もし,同じ内容の文字列が連続したら終了する.
$ cc string.c -o string $ ./string 文字列(255文字以内)> hello 文字数=5 文字列(255文字以内)> bye 文字数=3 文字列(255文字以内)> bye 文字数=3 $
次に,標準文字列処理関数のクローンを作成してみたい. まずは,strlen() について, 仕様を確認しよう:
$ man strlen
クローン関数およびテスト用メイン関数の ソースコードは概ね次のようになるだろう: (string.c を改造)
#include <stdio.h>// #include <string.h>// 標準文字列関数は不使用に // 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) { printf(...); scanf(...);// n = strlen(buf);// 標準関数は不使用に n = mystrlen1(buf); // クローン関数1 // n = mystrlen2(buf); // クローン関数2 printf("文字数=%d\n", n); ... } return (0); }
動作的には,配列版とポインタ版は, どちらも標準関数と同じである. 各自で確認しよう.
ソースコード的にも両者はあまり変わらないが, 実行効率的には,一般に,ポインタ版の方が優れている. たとえば,文字列 s 内の1文字にアクセスする場合を考えよう. 配列版では「s[i]」等と記述することになるが, これは内部的には「*(s + i)」という, 2手順の計算(アドレス計算「s+i」と間接参照「*」) として実行される.
一方,ポインタ版では間接参照「*s」の1手順だけで済み, 配列版よりも計算量が少ない. また,要素番号の変数 i も不要となり, メモリ使用量も少なくできる.
では,strlen() と同様に, strcpy() と strcmp() についても クローンを定義してみたい.
仕様確認:
$ man strcmp $ man strcpy
ソースコード: (string.c を改造)
...// #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); } #define ... ... int main(void) { ... while (1) { ...// d = strcmp(buf, pre);// 標準関数は不使用に d = mystrcmp(buf, pre); // クローン関数 ...// strcpy(pre, buf);// 標準関数は不使用に mystrcpy(pre, buf); // クローン関数 } return (0); }
次の基本的な文字処理・文字列処理のライブラリ関数について, クローンを定義してみよう.
文字種検査関数では,仮引数の文字 c は, char型ではなく, int型であることに注意しよう.
これまで,入出力関数で数値を入出力する際, scanf("%d"...) や printf("%f"...) 等としてきた. 実はこれらの関数内では, 数値そのものを入出力している訳ではなく, 文字列(数字列)を入出力している. また,そのために,数値を文字列へ変換したり, 逆に文字列を数値へ変換している.
次の関数について,クローンを作成せよ: (標準ライブラリ関数を極力,使わずに...)
なお,クローン関数の名前としては, myatoi(),myatof(),等としよう.
なお,書式指定などについては省略してよい. この劣化版クローン関数のプロトタイプとしては, void scan1d(int *val), void scan1f(double *val),等としよう. ソースコードには,ライブラリ関数の getchar() を利用してよい.
なお,書式指定などについては省略してよい. この劣化版クローン関数のプロトタイプとしては, void print1d(int val), void print1f(double val),等としよう. ソースコードには,ライブラリ関数の putchar() を利用してよい.