12. 文字処理と関数とポインタ−と |
ここでは,文字列処理の関数化をとおして,C言語を使う上での必須の知識である関数とポインターに慣れることを目的とします.
|
12.1 まずは,ウォーミング・アップ! |
プログラム中では,文字列を他の変数へコピーしたり,一部を切り取ったり,複数の文字列を1つにつなげたり...その他,文字列に対していろんな処理を行います.そのような作業を文字処理とよびます.
文字列は配列に代入して扱われるため,当然ながら,文字処理ではポインターを多用します.
ここでは,種々の文字処理を実行する関数を作りながら,C言語の必須の知識である関数とポインターに慣れることを目標とします.関数とポインターのトレーニングだと思ってください.
まずは,ウォーミング・アップです.次の3個のプログラムは,すべて同じ処理を実行します.理解できますか?
もし,理解できなければ,先へ進んでも意味がありません.配列やポインター変数の説明をもう一度読んでみてください.
それと,これらのプログラムでは,printf()文中の書式指定子が%cではなく%sになっていることに注意してください.
プログラム例 12.1.1 | |
実行結果 |
#include <stdio.h>
void main(void){
char a[20]={"Programming"};
int i;
i = 0;
while(a[i]){
printf("%s\n",&a[i]);
i++;
}
}
|
|
Programming
rogramming
ogramming
gramming
ramming
amming
mming
ming
ing
ng
g
|
プログラム例 12.1.2 | |
実行結果 |
#include <stdio.h>
void main(void){
char a[20]={"Programming"};
char *aptr;
aptr=a;
while(*aptr){
printf("%s\n",aptr);
aptr++;
}
}
|
|
Programming
rogramming
ogramming
gramming
ramming
amming
mming
ming
ing
ng
g
|
プログラム例 12.1.3 | |
実行結果 |
#include <stdio.h>
void shorten_string(char *ptr);
void main(void){
char a[20]={"Programming"};
shorten_string(a);
}
void shorten_string(char *aptr)
{
while(*aptr){
printf("%s\n",aptr);
aptr++;
}
}
|
|
Programming
rogramming
ogramming
gramming
ramming
amming
mming
ming
ing
ng
g
|
|
12.2 文字列の長さを求める |
まず,最初に文字列中の文字の個数を数える関数を作成します.通常,文字列中の文字の個数を文字列(string)の長さ(length)と呼びます.ただし,末尾の'\0'は1個の文字としてはカウントしません.
関数名を見ただけで,その関数の機能がわかるように,ここでは,関数名をmy_strlen()します.一般に,ある機能をもつ関数の書き方は複数考えられます.ここでは,次のような2つの関数を作ってみます.
(1)文字列を渡せば,戻り値として呼び出し側へ文字列長を返す.
(2)文字列を渡せば,ポインター変数で呼び出し側へ文字列長を返す.
まずは,(1)の場合の例です.
文字列の個数は整数値ですから関数の型,すなわち,関数の戻り値の型は,整数型ということになります.ということで,関数は,次のような構造になります.
int my_strlen(char *ptr){
.........
return kosuu;
}
説明の必要はないと思いますが,関数の処理内容は次のようになります.
文字配列の先頭要素のアドレスをaptrで受け取る
文字列長を記憶する変数lengthを0で初期化する.
while(*aptrが'\0'でなければ){
lengthの値をインクリメントする(1増やす).
aptrをインクリメントする(aptrの指す配列要素を1個進める).
}
戻り値として,lentgthに記憶された文字の個数を呼び出し側へ返す.
プログラム例 12.2.1 | |
実行結果 |
#include <stdio.h>
int my_strlen(char *ptr);
void main(void){
char a[20]={"Programming"};
int slen;
slen = my_strlen(a);/*引数は&a[0]でもok*/
printf("[%s]の文字の個数は%d\n", a, slen);
}
int my_strlen(char *aptr)
{
int length;
length = 0;
while(*aptr){
length++;
aptr++;
}
return length;
}
|
|
[Programming]の文字の個数は11 |
次は,先ほど述べた(2)の場合,すなわち,文字列長をポインターで返す関数を作成してみます.
関数の型は,void型とします.そうすると,関数の構造は次のような形となります.もちろん,他に何らかの情報を返したいなら必ずしもvoid型とする必要はありません.
void my_strlen(int *lptr, char *ptr){
.........
}
戻り値が無い場合,関数の終わりに次のようにretunと書いても構いません.
void my_strlen(int *lptr, char *ptr){
.........
return;
}
これも説明の必要はないと思いますが,関数の処理内容は次のとおりです.
文字列長を書き込む変数のアドレスをiptrで受け取る
文字配列の先頭要素のアドレスをsptrで受け取る
文字列長を0で初期化する.(*lptr=0)
while(*aptrが'\0'でなければ){
(*lptr)++を実行することで文字の個数を1増やす.
sptrをインクリメントする(sptrの指す配列要素を1個進める).
}
プログラム例 12.2.2 | |
実行結果 |
#include <stdio.h>
void my_strlen(int *lptr, char *sptr);
void main(void){
char a[20]={"Programming"};
int slen;
my_strlen( &slen, a);
printf("[%s]の文字長は%dです.\n", a, slen);
}
void my_strlen(int *lptr, char *sptr)
{
*lptr = 0;
while(*sptr){
(*lptr)++;
sptr++;
}
}
|
|
[Programming]の文字長は11です. |
|
12.3 文字列をコピーする |
配列変数に記憶された文字列を他の配列変数へコピー(copy)する処理もよく使われます.今度は,コピー関数を作ってみましょう.関数名は my_strcpy() とします.考え方は,単純で,コピー元の配列の個々の要素を別の配列要素に一個ずつコピー(代入)すれば良いだけです.
プログラム例12.3.1は戻り値なしのvoid型関数で,関数の処理内容は次のとおりです.
コピー先の配列の先頭要素のアドレスをポインター変数to_pで受け取る.
コピー元の配列の先頭要素のアドレスをポインター変数from_pで受け取る.
while(*fromが'\0'でなければ){
コピー元の要素の内容(文字)をコピー先の配列要素に代入する.(*to_p = *from_p)
to_pをインクリメントする(to_pの指す要素を次へ進める).
from_pをインクリメントする(from_pの指す要素を次へ進める).
}
*from_pが'\0'の時,to_pの指す要素には'\0'はコピーされないので,ここで'\0'を代入する.
プログラム例 12.3.1 | |
実行結果 |
#include <stdio.h>
void my_strcpy(char *to_p, char *from_p);
void main(void){
char a[20]={"C Language"};
char b[20];
printf("aの内容は%sです.\n", a);
my_strcpy(b, a);
printf("bの内容は%sです.\n", b);
}
void my_strcpy(char *to_p, char *from_p)
{
while(*from_p){
*to_p = *from_p;
to_p++; from_p++;
}
/*'\0'はコピーされなていので*/
*to_p = '\0';
}
|
|
aの内容はC Languageです.
bの内容はC Languageです.
|
次に,上と同様のプログラムをコピー先の変数の先頭アドレスを返すように作ってみました.関数内部での処理そのものは同じですが,main()関の2つ目のprintf()関数のような使い方ができることが特徴です.
printf("bの内容は%sです.\n", my_strcpy(b, a));
この文は単純でprintf()の実行時に,my_strcpy()関数の戻り値をプリントしているだけのようにみえますが,よくよく考えてみると,printf()実行時にmy_strcpy()関数を呼び出し実行するという意味もあることを意識して下さい.
関数内部での処理を書いておきます.
コピー先の配列の先頭要素のアドレスをポインター変数to_pで受け取る.
コピー元の配列の先頭要素のアドレスをポインター変数from_pで受け取る.
while(*fromが'\0'でなければ){
コピー元の要素の内容(文字)をコピー先の配列要素に代入する.(*to_p = *from_p)
to_pをインクリメントする(to_pの指す要素を次へ進める).
from_pをインクリメントする(from_pの指す要素を次へ進める).
}
*from_pが'\0'の時,to_pの指す要素には'\0'はコピーされないので,ここで'\0'を代入する.
to_pを返す.
プログラム例 12.3.2 | |
実行結果 |
#include <stdio.h>
char *my_strcpy(char *to_p, char *from_p);
void main(void){
char a[20]={"C Language"};
char b[20];
printf("aの内容は%sです.\n", a);
my_strcpy(b, a);
printf("bの内容は%sです.\n", my_strcpy(b, a));
}
char *my_strcpy(char *to_p, char *from_p)
{
char *to_ptr = to_p; /*先頭アドレスを保存*/
while(*from_p){
*to_p = *from_p;
from_p++;
to_p++;
}
*to_p = '\0'; /*'\0'はコピーされないので*/
return to_ptr; /*先頭アドレスを返す*/
}
|
|
aの内容はC Languageです.
bの内容はC Languageです.
|
|
12.4 文字列をつなげる |
次は,与えられた2つの文字列を連結させて1つの文字列とします.
この場合はコピー先の文字列の終端を見つけ,そこから先の要素へコピー元の配列要素を代入すればよいということになります.連結処理する関数の名前は,my_strcat()としておきます.(なぜそうするのか説明できませんが(笑))
関数内部での処理を書いておきます.
連結先の配列の先頭要素のアドレスをポインター変数to_pで受け取る.
コピー元の配列の先頭要素のアドレスをポインター変数from_pで受け取る.
while(*to_pが'\0'でなければ){
to_pをインクリメントする(to_pの指す要素を次へ進める).
}
while(*fromが'\0'でなければ){
コピー元の要素の内容(文字)をコピー先の配列要素に代入する.(*to_p = *from_p)
to_pをインクリメントする(to_pの指す要素を次へ進める).
from_pをインクリメントする(from_pの指す要素を次へ進める).
}
*from_pが'\0'の時,to_pの指す要素には'\0'はコピーされないので,ここで'\0'を代入する.
to_pを返す.
プログラム例 12.4.1 | |
実行結果 |
#include <stdio.h>
char *my_strcat(char *to_p, char *from_p);
void main(void){
char a[50]={"Programming"};
char b[20]={"LanguageC"};
printf("aの内容は%sです.\n", a);
printf("bの内容は%sです.\n", b);
my_strcat(a, b);
printf("aの内容は%sです.\n", a);
}
char *my_strcat(char *to_p, char *from_p)
{
char *to_ptr = to_p;
/*まず,to_pの文字列の'\0'を見つける*/
while(*to_p){
to_p++;
}
/*to_pの'\0'の位置へfrom_pをコピー*/
while(*from_p){
*to_p = *from_p;
from_p++;
to_p++;
}
*to_p = '\0';
return to_ptr;
}
|
|
aの内容はProgrammingです.
bの内容はLanguageCです.
aの内容はProgrammingLanguageCです.
|
|
12.5 文字列中から指定の文字をみつける |
今度は,文字列(string)中から指定した文字(character)を見つける関数を作ってみます.
関数名はmy_strchr()とします.関数には検索の対象となる文字列と,文字列中で探すべき文字を渡し,関数からは,指定の文字が見つかれば,文字列中のその文字の位置を返せば良いわけです.
ここで,関数から呼び出し側へ返す位置情報として,次のように2種の情報が考えられます.
1)文字列の先頭から何番目という形で文字の位置を返す.
2)文字列中で検出された指定文字のアドレスを返す.
ここでは,上記2)の場合の関数を作ってみます.1)の場合については,皆さんで作成し2)の場合に比べ,どのような利点や欠点があるか考えてみてください.
処理の手順は,次のように考えてみます.
検索対象となる文字配列の先頭のアドレスをポインター変数str_pで受け取る.
検索する文字cを受け取る.
while( *from != c でなければ){
str_pをインクリメントする.
}
*from_pが'\0'の時,to_pの指す要素には'\0'はコピーされないので,ここで'\0'を代入する.
to_pを返す.
プログラム例 12.5.1 | |
実行結果 |
#include <stdio.h>
char *my_strchr(char *str_p, char c);
void main(void){
char a[50]={"Programminglanguage"};
char c = 'l';
printf("文字列%sから.\n", a);
printf("文字%c検索します.\n", c);
*my_strchr(a, c) = 'L';
printf("文字lをLで置換しました.\n%s\n", a);
}
char *my_strchr(char *str_p, char c)
{
/*文字列の先頭の配列要素から順に*/
/*cと同じ文字が見つかるまでポインタを進める*/
while(*str_p != c){
str_p++;
}
return str_p;
}
|
|
文字列Programminglanguageから.
文字l検索します.
文字lをLで置換しました.
ProgrammingLanguage
|
上記のプログラムの関数は,与えられた文字列に指定の文字が必ず存在するものと仮定しています.もし,文字列中に指定の文字が見つからなかった場合はどうなるでしょうか.そのときは戻り値として,'\0'のアドレスを返す事になります.戻り値を受け取った側では,その位置が指定文字の位置と勘違いすることになります.
そのようなことを防ぐためには,工夫が必要となります.
次のプログラム中の関数は,もし,指定の文字が見つからなかった場合,ポインター変数にNULLを代入して返します.そうすることによって,呼び出し側へ指定文字がみつからなかったことを知らせることが可能となります.
ポインター変数には,通常,アドレスしか代入できませんが,次のように,特別にNULLを代入することができます.
str_p = NULL
このように,NULLを代入されたポインター変数のことをヌルポインターと呼びます.これは関数型がポインターの場合,よく使われる処理です.ちゃんと覚えておきましょう.
プログラム例12.5.2と12.5.3は,それぞれ,検索対象の文字列中に指定文字が含まれる場合と含まれない場合の例です.
関数内部の処理と呼び出し側での戻り値(ヌルポインター)の処理についてよく理解してください.
プログラム例 12.5.2 | |
実行結果 |
#include <stdio.h>
char *my_strchr(char *str_p, char c);
void main(void){
char a[50]={"Programminglanguage"};
char c = 'x'; char *pos_p;
printf("文字列%sから.\n", a);
printf("文字%c検索します.\n", c);
pos_p = my_strchr(a, c);
if(pos_p != NULL){
*pos_p = 'L';
printf("文字lをLで置換しました.\n%s\n", a);
}else{
printf("文字%cはみつかりませんでした\n", c);
}
}
char *my_strchr(char *str_p, char c)
{
/* 文字列の先頭から順に指定の文字が */
/* 見つかるまでポインタを進める */
while((*str_p != c) && (*str_p != '\0'))
{
str_p++;
}
/* もし,文字が見つからなければ */
/* ポインタstr_pにNULLを代入 */
if(*str_p == '\0') str_p = NULL;
return str_p;
}
|
|
文字列Programminglanguageから.
文字l検索します.
文字lをLで置換しました.
ProgrammingLanguage
|
プログラム例 12.5.3 | |
実行結果 |
#include <stdio.h>
char *my_strchr(char *str_p, char c);
void main(void){
char a[50]={"Programminglanguage"};
char c = 'x'; char *pos_p;
printf("文字列%sから.\n", a);
printf("文字%c検索します.\n", c);
pos_p = my_strchr(a, c);
if(pos_p != NULL){
*pos_p = 'L';
printf("文字lをLで置換しました.\n%s\n", a);
}else{
printf("文字%cはみつかりませんでした\n", c);
}
}
char *my_strchr(char *str_p, char c)
{
/* 文字列の先頭から順に指定の文字が */
/* 見つかるまでポインタを進める */
while((*str_p != c) && (*str_p != '\0'))
{
str_p++;
}
/* もし,文字が見つからなければ */
/* ポインタstr_pにNULLを代入 */
if(*str_p == '\0') str_p = NULL;
return str_p;
}
|
|
文字列Programminglanguageから.
文字x検索します.
文字xはみつかりませんでした
|
|
12.6 まとめてみましょう |
以下に,ここで覚えておいてほしい文字処理の基本となる知識をまとめてみます.
(1)文字列は配列に代入して扱う.
(2)文字列の終端は'\0'で終わる.
(3)文字列処理は,上記(1),(2)が基本となる.
(4)ポインター変数にはNULLを代入することができる.ただし,NULLはC言語で定義された記号定数である.
ここでは,いくつか例を挙げて,文字列処理関数を作成してみました.しかし,実際には基本的な文字処理関数は既成のライブラリー関数を使います.ここでの学習内容は,それらのライブラリー関数の使い方を理解するための予備知識でした.特に,ヌルポインターの使い方をよく理解しておいてください.
|