C言語:文字列をいじる

文字列の置換
N88BASICの文字列関数で遊ぶ
配列(ポインタ)からデータを探す
コマンドライン引数の表示
文字列を分解する

文字列の置換

文字列を置換します。bufは置換後の大きさが必要です。

#include <stdio.h>
#include <string.h>

/* 置換する。 buf の中の mae を ato にする。成功=1 失敗=0 */
int strrep(char *buf, char *mae, char *ato)
{
    char *mituke;
    size_t maelen, atolen;
    
    maelen = strlen(mae);
    atolen = strlen(ato);
    if (maelen == 0 || (mituke = strstr(buf, mae)) == NULL) return 0;
    memmove(mituke + atolen, mituke + maelen, strlen(buf) - (mituke + maelen - buf ) + 1);
    memcpy(mituke, ato, atolen);
    return 1;
}

main()
{
    char buf[100];
    
    strcpy(buf, "肉肉肉肉肉肉肉肉肉肉");
    while (strrep(buf, "肉", "土下座")) ;
    printf("%s\n", buf);
}

N88BASICの文字列関数で遊ぶ

N88BASICのLEFT$,MID$,RIGHT$関数を作ってみました。 MID$は文字の先頭が1になるようにしました。 あと、TIME$とDATE$を作ってみました。でも関数です。 とても便利でしょう。

Boehm GC 6.8を使ってみました。もしかしたら使い方間違っているかも。 普通のmallocとかだったら、どこかでfreeしないといけませんね。

#include <stdio.h>
#include <string.h>
#include <time.h>
#include "gc.h"

/* buf の左から n 文字までを返す。*/
char *LEFT$(char *buf, int n)
{
    char *tmp;
    size_t buflen = strlen(buf);
    
    if (buflen < n || n < 0 ) return NULL;
    tmp = (char *)GC_MALLOC(n + 1);
    memcpy(tmp, buf, n);
    tmp[n] = 0;
    return tmp;
}

/* buf の n 文字目から l 文字を返す。 */
char *MID$(char *buf, int n, int l)
{
    char *tmp;
    size_t buflen = strlen(buf);
    
    n--;
    if (buflen < n || buflen < n + l || n < 0) return NULL;
    tmp = (char *)GC_MALLOC(l + 1);
    memcpy(tmp, buf + n, l);
    tmp[l] = 0;
    return tmp;
}

/* buf の右から n 文字を返す。 */
char *RIGHT$(char *buf, int n)
{
    char *tmp;
    size_t buflen = strlen(buf);
    
    if (buflen < n || n < 0 ) return NULL;
    tmp = (char *)GC_MALLOC(n + 1);
    memcpy(tmp, buf + buflen - n, n);
    tmp[n] = 0;
    return tmp;
}

/* 時間をhh:mm:ssで返す */
char *TIME$(void)
{
    time_t tim;
    struct tm *t_st;
    char *tmp = GC_MALLOC(9);
    
    time(&tim);
    t_st = localtime(&tim);
    sprintf(tmp, "%02d:%02d:%02d", t_st->tm_hour, t_st->tm_min, t_st->tm_sec);
    return tmp;
}

/* 日付をyy/mm/ddで返す */
char *DATE$(void)
{
    time_t tim;
    struct tm *t_st;
    char *tmp = GC_MALLOC(9);
    
    time(&tim);
    t_st = localtime(&tim);
    while(t_st->tm_year > 99) t_st->tm_year -= 100;
    sprintf(tmp, "%02d/%02d/%02d", t_st->tm_year, t_st->tm_mon + 1, t_st->tm_mday);
    return tmp;
}

main()
{
    char *t = (char *)GC_MALLOC(100);
    strcpy(t, "あqwせdrftgyふじこp;");
    printf("%s\n", LEFT$(t, 4));
    printf("%s\n", MID$(t, 21, 6));
    printf("%s\n", RIGHT$(t, 8));
    printf("日時%s 時間%s\n", DATE$(), TIME$());
    printf("%s\n", RIGHT$(TIME$(), 2));
}

配列(ポインタ)からデータを探す

N88BASICのSEARCH関数のようなものです。 char,short,longの3つの型に対応しています。

使い方はSEARCH(配列名, 配列の大きさ, 型, 探す値)です。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* 配列からデータを見つける。
 * 戻り値:見つかった位置(先頭=1)、0なら無し。
 * hairetu:配列
 * saizu:配列のサイズ
 * kata:型
 * mituke:探すデータ
 */
int SEARCH(void *hairetu,int saizu,int kata,int mituke)
{
    int n;
    
    switch (kata) {
        case sizeof(char):
             for (n = 0; n < saizu; n++)
                 if (*((char *)hairetu + n) == mituke) return n+1;
        break;
        case sizeof(short):
             for (n = 0; n < saizu; n++)
                 if (*((short *)hairetu + n) == mituke) return n+1;
        break;
        case sizeof(long):
             for (n = 0; n < saizu; n++)
                 if (*((long *)hairetu + n) == mituke) return n+1;
        break;
    }
    /* 見つからない、型が違う*/
    return 0;
}

main(){
    /* お試し */
    char *ca;
    long la[] ={1,2,3,4,5,6,7,8,9,10};
    
    printf("%d\n", SEARCH(la, sizeof la, sizeof *la, 7));/* 配列 */
    ca = malloc(100);
    strcpy(ca,"debudebu@nikuniku.29.jp");
    printf("%d\n", SEARCH(ca, strlen(ca), sizeof *ca, '@'));/* ポインタ */
    free(ca);
}

コマンドライン引数の表示

main関数は当然関数です。だから再帰呼び出しが可能です。 コマンドライン引数を順に表示するプログラムです。 N88-BASICでゲームを作るとき、ゲームオーバーなどで初期化させたいときにrunさせるというテクニックがあります。 C言語で同じようなことをやろうと思ってmain関数を呼んでもスタックに積まれるため、初期化にはならないので注意してください。

#include <stdio.h>

main(int argc, char *argv[])
{
    if (argc) {
        main(--argc, argv);
        printf("%d %s\n", argc, argv[argc]);
    }
}

文字列を分解する

perlなどのsplit関数って便利ですよね。 あれに似たような関数を作ってみました。名前はそのままsplit関数です。

使い方は split(文字列, デリミタ, 受け取るポインタ..., NULL)です。 可変引数を使っているため、受け取るポインタの数は無制限です。 最後のNULLは番人なので、これが無かったら多分落ちます。

標準関数のstrtokでも良いのですが、引数の文字列を勝手に書き換えられるので、 あえて自分でmystrtokを定義しています。 freeのタイミングを考えるのが嫌なので、gcを使っています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "gc.h"
#pragma comment (lib, "gc.lib")
#define malloc GC_MALLOC_ATOMIC

/* strtokの文字列版。 */
char *mystrtok(char *s, char *t)
{
    static char *buf = NULL; /* 実際のデータ */
    static char *atamap; /* 頭ポインタ */
    char *oshirip; /* お尻ポインタ */
    char *p;
    
    if (s != NULL) {
        buf = malloc(strlen(s)+1);
        strcpy(buf, s);
        atamap = buf; /* atamapは先頭を指す */
    }
    oshirip = strstr(atamap, t); /* oshiripは区切り文字を指す */
    if (oshirip == NULL) return NULL;
    *oshirip = 0;
    p = atamap;
    atamap = oshirip + strlen(t); /* 次の頭 */
    return p;
}

/* strの文字列をdelimiterで区切る。 */
int split(char *str, char *delimiter, ...)
{
    int count = 0;
    char *p;
    char *buf;
    va_list ap;
    
    va_start(ap, delimiter);
    p = mystrtok(str, delimiter);
    buf = va_arg(ap, char *);
    if (p == NULL || buf == NULL) return count;
    strcpy(buf, p);
    while ((buf = va_arg(ap, char *)) != NULL && (p = mystrtok(NULL, delimiter)) != NULL) {
        strcpy(buf, p);
        count++;
    }
    va_end(ap);
    return count;
}

typedef struct {
    char name[100];
    char mail[100];
    char date[100];
    char text[1000];
} RES;

main()
{
    char *data = "名無し<>debudebu@nikuniku.29.jp<>1549年12月13日<>これはテストです。あいうえー<>";
    RES res;

    split(data, "<>", res.name, res.mail, res.date, res.text, NULL);
    printf("name : %s mail : %s date : %s\n%s\n", res.name, res.mail, res.date, res.text);
    
}