Ｃで文字列置換関数を自作してみた

Ｃ言語には文字列置換関数が無い！？　ということで、自作することにしてみました。

- プロトタイプ
```replace.h
#ifndef _REPLACE_H_
#define _REPLACE_H_

/* 文字列内の特定のテキストを置換する */
char* replace(char*, const char*, const char*, const char*);

#endif //_REPLACE_H_
```
GitHub=>[replace.h](https://github.com/anonymously115/repository115/blob/main/replace.h)
- - -
- 文字配列として一文字ずつ精査
```replace.c
#include "replace.h"

/*
 * 文字列内の特定のテキストを置換する
 * dst : 置換後の文字列
 * src : 置換する文字列
 * old : 検索文字列
 * new : 置換文字列
 * 戻り値 : 置換後の文字列（の先頭へのポインタ）
 */
char* replace(char* dst, const char* src, const char* old, const char* new){
    //置換する文字列を探索する位置
    int i=0;

    //置換した文字列を格納する位置
    int j=0;

    //置換する文字列の終端文字まで探索する
    while(src[i]!='\0'){
        
        //置換する文字列のi文字目以降が検索文字列と一致するか
        int p=0;
        while(old[p]!='\0'){
            if(src[i+p]!=old[p]){//不一致ならループを抜ける
                break;
            }
            p++;
        }
        
        if(p>0 && old[p]=='\0'){//検索文字列が空文字でない、かつ、置換する文字列のi文字目以降が検索文字列と一致した
            //置換後の文字列に置換文字列を一文字ずつ格納していく
            int q=0;
            while(new[q]!='\0'){
                dst[j++]=new[q++];
            }
            i+=p;
        }else{//置換する文字列のi文字目以降が検索文字列と一致しなかった
            //置換後文字列の次の位置に置換前文字列のi文字目を格納
            dst[j++]=src[i++];
        }
    }

    //置換後の文字列の末尾に終端文字を格納する
    dst[j]='\0';

    //置換後の文字列（の先頭へのポインタ）を返す
    return dst;
}
```
- - -
- 標準ライブラリ`string.h`をインクルード
```replace.c
#include <string.h>
#include "replace.h"

/*
 * 文字列内の特定のテキストを置換する
 * dst : 置換後の文字列
 * src : 置換する文字列
 * old : 検索文字列
 * new : 置換文字列
 * 戻り値 : 置換後の文字列（の先頭へのポインタ）
 */
char* replace(char* dst, const char* src, const char* old, const char* new){
    if(strcmp(old,"")==0){//検索文字列が空文字の場合
        //置換後の文字列に置換前の文字列をコピー
        strcpy(dst,src);
        
        //置換後の文字列（の先頭へのポインタ）を返す
        return dst;
    }

    //置換前の文字列長
    int n=(int)strlen(src);

    //検索文字列長
    int a=(int)strlen(old);
    
    //置換文字列長
    int b=(int)strlen(new);

    //置換前の文字列を探索する位置
    int i=0;

    //置換後の文字列に格納する位置
    int j=0;
    
    while(i<n){//置換する文字列の終端文字まで探索する
        int p=0;

        //置換する文字列のi文字目からa文字が検索文字列と一致するか
        while(p<a){
            if(src[i+p]!=old[p]){
                break;
            }
            p++;
        }
        
        if(p==a){//置換する文字列のi文字目からa文字が検索文字列と一致した
            //置換後の文字列に置換文字列を一文字ずつ格納していく
            int q=0;
            while(q<b){
                dst[j++]=new[q++];
            }
            i+=a;
        }else{//置換する文字列のi文字目からa文字が検索文字列と一致しなかった
            //置換後文字列の次の位置に置換前文字列のi文字目を格納
            dst[j++]=src[i++];
        }
    }

    //置換後の文字列の末尾に終端文字を格納する
    dst[j]='\0';

    //置換後の文字列（の先頭へのポインタ）を返す
    return dst;
}
```
- - -
- 文字列へのポインタを使う
```replace.c
#include <string.h>
#include "replace.h"

/*
 * 文字列内の特定のテキストを置換する
 * dst : 置換後の文字列
 * src : 置換する文字列
 * old : 検索文字列
 * new : 置換文字列
 * 戻り値 : 置換後の文字列の先頭へのポインタ
 */
char* replace(char* dst, const char* src, const char* old, const char* new){
    if(!strcmp(old,"")){//検索文字列が空文字の場合
        //置換後の文字列に置換前の文字列をコピー
        strcpy(dst,src);
        
        //置換後の文字列（の先頭へのポインタ）を返す
        return dst;
    }

    //出力文字列を空文字列で初期化
    strcpy(dst, "");
    
    //検索文字列長
    int a=(int)strlen(old);
    
    //置換する文字列の先頭へのポインタ
    const char* c=src;

    //置換する文字列に検索文字列が含まれていれば、その部分の先頭へのポインタ（無ければNULL）
    char* s=strstr(c,old);
    
    while(s){//置換する文字列に検索文字列が含まれていた
        //検索文字列にヒットするまでの文字列を出力文字列に結合
        strncat(dst,c,s-c);

        //出力文字列に置換文字列を結合
        strcat(dst,new);

        //次の検索位置はヒットした検索文字列の末尾の次
        c=s+a;

        //次の検索文字列を探索する
        s=strstr(c,old);
    }

    //最後の検索文字列以降の文字列を出力文字列に結合する
    strcat(dst,c);

    //置換後の文字列の先頭へのポインタを返す
    return dst;
}
```
GitHub=>[replace.c](https://github.com/anonymously115/repository115/blob/main/replace.c)
- - -
- サンプル：コマンドラインから入力された文字列を置換して表示するプログラム
```main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "replace.h"

/*
 * コマンドラインの第1引数の文字列に含まれる第2引数の文字列を第3引数の文字列に置換した文字列を表示する
 * 第1引数 : 置換する文字列
 * 第2引数 : 検索文字列
 * 第3引数 : 置換文字列
 */
int main(int argc, char* argv[]){
    //引数の数が3以外の場合はエラー
    if(argc!=4){//プログラム名argv[0]を含めるとargcは4が正常
        fprintf(stderr,"%s: error: too %s arguments to function call, expected 3, have %d\n", argv[0], argc<4?"few":"many", argc-1);
        abort();
    }
    //第1引数の文字列長
    size_t n=strlen(argv[1]);

    //第2引数の文字列長
    size_t a=strlen(argv[2]);

    //第3引数の文字列長
    size_t b=strlen(argv[3]);

    //出力文字列長として考えられる最大値は
    //a>=bの場合はn
    //a<bの場合はn*b/a（端数切捨）
    //終端文字を考慮して+1する
    size_t m=(a<b?n*b/a:n)+1;

    //出力領域を確保
    char str[m];

    //置換後の文字列を出力
    printf("%s\n",replace(str,argv[1],argv[2],argv[3]));

    //終了
    return EXIT_SUCCESS;
}
```
- コンパイル
```
gcc main.c replace.c -o replace
```
- - -
:::note alert
C言語では
```c
char* replace(const char* src, const char* old, const char* new){
    int m=/* 十分な領域を確保 */;
    char res[m]; //関数内で配列を宣言
    /* 処理 */
    return res; //関数内で宣言した配列を返す
}
```
のように、関数内で宣言した配列を返すことはできません。

実際、
```a.c
#include <stdio.h>

char* func(){
	char s[]="hello, world";
	return s;
}

int main(){
	printf("%s\n", func());
	return 0;
}
```
というソースファイルを作成して
```
gcc a.c
```
とコンパイルしても、
```
a.c: In function 'func':
a.c:5:9: warning: function returns adddsts of local variable [-Wreturn-local-addr]
  return s;
         ^
```
という警告が出力され、実行結果は何も出力されませんでした。
:::