C言語では、文字列を扱う際に、使っている領域のサイズがよく問題になります。常に領域のサイズを意識しておかないと、思わぬ不具合の原因になります。しかし、文字列を連結したり、部分文字列の置換を行ったりするたびにいちいち処理を記述すると、効率も悪く、コードが読みにくくなります。そこで、バッファサイズの自動調整など、面倒な文字列の処理を一手に引き受けてくれるライブラリautostring.cを用意しました。
autostring.cでは、以下のヘッダファイルを使用しています。autostring.hを除いてすべて標準ヘッダファイルです。autostring.hは関数のプロトタイプ宣言を行っているだけなので、内容は割愛します。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "autostring.h"
autostring.cに関する疑問がある場合はautostring.c Q&Aを参照してください。
文字列に対して適切なサイズのバッファを確保し、そのバッファに文字列をコピーする。この関数を使うと、文字列のために必要なバッファのサイズの計算、領域の確保を省略できる。
以下に紹介するstrplus()、strreplace()、allreplace()などの関数を利用するために、文字列を動的記憶領域にコピーする用途にもよく用いる。
/* 文字列の長さを調べ、そのサイズの領域にコピーする */
/* コピーした領域へのポインタを返す */
char* strmirror(const char* str)
{
char* p = (char*)malloc(strlen(str)+1);
if(p == NULL) return NULL;
strcpy(p,str);
return p;
}
char* str=strmirror("この文字列をバッファにコピー");
この関数は内部でmalloc()により領域を確保するので、文字列が不要になった場合にはfree()関数によって領域を解放しておかなければならない。この解放は忘れやすいので注意。
なお、この関数はCプログラミング診断室の第6章を参考にしました。
文字列を連結する。連結後の文字列のためのバッファは適切なサイズに調整される。ただし、連結する前半部分の文字列は動的記憶領域に確保されたバッファ中のものでなければならない。
/* strplus() 文字列を連結する */
/* strcatと違い、バッファのサイズを気にする必要がない。 */
/* ただし、formはmalloc()などにより動的に確保された領域でなければならない。 */
char* strplus(char** form,const char* latt)
{
*form = (char*)realloc(*form,strlen(*form) + strlen(latt) + 1);
if(*form == NULL) return NULL;
strcat(*form,latt);
return *form;
}
char* str1=strmirror("前半");
str1 = strplus(&str1,"後半");
部分文字列との一致により、文字列を分割する。文字列は指定した部分文字列の前までのものになり、戻り値として、部分文字列の後に続く文字列の先頭アドレスが返却される。詳細はこの項の見出しのリンク先を参照。
/* 区切り文字列の先頭部分に文字列終了子を埋め込み、 */
/* 次の文字列の先頭のポインタを返す。 */
/* 見つからなければNULLを返す。 */
char* strsplit(char* str,const char* delimstr)
{
char* delim_point = strstr(str,delimstr);
const size_t delim_len = strlen(delimstr);
size_t i;
if(delim_point == NULL) return NULL;
else{
*delim_point = '\0';
for(i=0;i<delim_len;i++) delim_point++;
}
return delim_point;
}
文字列中の最初の指定部分文字列を他の文字列に置換する。
置換前の文字列の長さが置換後の文字列よりも長い場合には、渡されたバッファをそのまま使う。置換前の文字列の長さが置換後の文字列よりも短い場合に限り、バッファは動的記憶領域に確保されたものである必要がある。
/* 文字列を置き換える */
/* 作業用のバッファの確保に失敗した場合にはNULLを返す。 */
/* 置き換える文字列が見つからない場合には、文字列をそのまま返す。 */
char* strreplace(char** str,const char* before,const char* after)
{
const size_t ini_len = strlen(*str);
const size_t before_len=strlen(before),after_len=strlen(after);
char* tail;
char* temp_buf = strmirror(*str);
if( before_len < after_len )
*str = (char*)realloc(*str,ini_len + after_len + 1);
if(temp_buf==NULL || *str==NULL) return NULL;
tail = strsplit(temp_buf,before);
if(tail == NULL) return *str;
sprintf(*str,"%s%s%s",temp_buf,after,tail);
free(temp_buf);
return *str;
}
文字列中のすべての指定部分文字列を他の文字列に置き換える。
渡されるバッファは動的記憶領域に確保されたものでなければならない。
/* 文字列に含まれる部分文字列をすべて置き換える。 */
/* 作業領域用のメモリの確保に失敗した場合にはNULLを返す。 */
/* 渡す文字列は動的記憶領域になければならない。 */
char* allreplace(char** str,const char* before,const char* after)
{
char *form,*latt;
char* temp_buf;
temp_buf = strmirror(*str);
if(temp_buf == NULL) return *str;
strcpy(*str,""); /* 文字列をクリア */
for(form = temp_buf; (latt=strsplit(form,before)) != NULL; form = latt){
*str = strplus(str,form);
*str = strplus(str,after);
}
*str = strplus(str,form);
free(temp_buf);
return *str;
}
以下は実装例その2。必要な変数の数は増えるが、上のstrplus()を繰り返す例と違い、一度のrealloc()で済むためメモリ領域確保・領域サイズ変更失敗時のエラー処理が厳密で、おそらく高速。ver.1.01からはこちらを採用。
char* allreplace(char** str,const char* before,const char* after)
{
const size_t ini_len = strlen(*str);
const size_t before_len = strlen(before);
const size_t after_len = strlen(after);
char *form,*latt;
char* rep_point;
char* temp_buf = strmirror(*str);
int n = 0; /* 置換する文字列の個数 */
if(temp_buf == NULL) return NULL; /* メモリ確保失敗の場合はNULLを返す */
for(rep_point=*str; (rep_point=strstr(rep_point,before)) != NULL;){
size_t i;
n++;
for(i=0;i<before_len;i++) rep_point++;
}
if(!n) return *str; /* 置換する文字列が見つからなかったらそのまま返す */
*str = (char*)realloc(*str,ini_len + (after_len-before_len)*n + 1);
if(*str == NULL) return NULL; /* メモリ確保失敗の場合はNULLを返す */
strcpy(*str,""); /* 文字列をクリア */
for(form = temp_buf; (latt=strsplit(form,before)) != NULL; form = latt){
strcat(*str,form);
strcat(*str,after);
}
strcat(*str,form);
free(temp_buf);
return *str;
}
完全なソースコードを以下に示します。必要な人は参考にしてください。
改変・再配布は自由です。ただし、autostring.cファイル中に表示している原作者表示は削除しないでください。