12.基本ライブラリ

12-6. 文字列用の関数

1.文字列のコピー

 まず、最初にC言語では変数の取り扱いが厳格なため、他の言語では気にもせず直感的に操作できたことが、C言語では決まった手順を踏まなければならないことがあります。
 そのひとつに文字列の処理があります。例えば、文字列をコピーしたり、連結したりするとき他の言語では

Basic(Javaもこの形式でいけるかもしれません)
a = “あいう”; ‘文字列の代入
b = a ; ‘文字列のコピー
c = a + b; ‘文字列の連結

Perl
a = “あいう”; ‘文字列の代入
b = a ; ‘文字列のコピー
c = a.b; ‘文字列の連結

となります。
ただし、C言語では上記のような演算式による文字列操作が出来ません。
そこで、文字列用の関数の登場となるわけです。

文字列をコピーする関数 strcpy()

関数名   :strcpy
必要なヘッダ:string.h
関数のタイプ:char *strcpy (char *s1, const char *s2);
引数    :s1 コピー先の文字列へのポインタ
       s2 コピー元の文字列へのポインタ
戻り値   :s1の値(コピー先の先頭アドレス)

◇サンプルプログラム1

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

main()
{
    char a[20]="あいうえお",b[11];
    
    strcpy(b,a);
    printf("a[]=%s\n",a);
    printf("b[]=%s\n",b);
    return;
}

◇実行結果1
a[]=あいうえお
b[]=あいうえお

ここで、注意がひとつあります。
コピー先の配列大きさはコピー元以上にする必要はありませんが、必ずコピーする文字列のバイト数+1(NULL文字:\0)以上にしてください。
強制終了の原因になります。

2.指定数の文字列をコピー

 次は、コピー元の文字列から指定数だけの文字をコピーする関数です。

◇指定数だけの文字をコピーする関数 strncpy()

関数名   :strncpy
必要なヘッダ:string.h
関数のタイプ:char *strncpy (char *s1, const char *s2, size_t n);
引数    :s1 コピー先の文字列へのポインタ
       s2 コピー元の文字列へのポインタ
       n コピーする最大文字数
戻り値   :s1の値(コピー先の先頭アドレス)

◇サンプルプログラム2-1

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

main()
{
    char a[20]="あいうえお",b[11];
    
    strncpy(b,a,8);
    printf("a[]=%s\n",a);
    printf("b[]=%s\n",b);
    return;
}

◇実行結果2-1
a[]=あいうえお
b[]=あいうえ***
(文字化けを起こした為、*で伏せています)

上記のようにコピーする文字列に\0がない場合は表示するときに文字列の終了がわからないため、残りの内容も表示してしまい最悪文字化けを起こします。上のような文字化けを回避するためにはコピー先の文字列の最後に\0を代入して対処します。
また、コピーする文字数よりもコピー元の文字数が少ないときは残りを\0で埋めます。

◇サンプルプログラム2-2

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

main()
{
    int i=8;
    char a[20]="あいうえお",b[11];
    
    strncpy(b,a,i);
    b[i]='\0';            /* ここで\0を代入 */
    printf("a[]=%s\n",a);
    printf("b[]=%s\n",b);
    return;
}

◇実行結果2-2
a[]=あいうえお
b[]=あいうえ

◇文字列の結合

3.文字列の結合strcat()

文字列の結合にはstrcat()を使います。結合先s1の文字列終端(\0の場所)に結合元s2の先頭を上書きして文字列の連結を行います。
注意するところは、コピーの時と同じで連結先の領域があふれない様に十分領域を確保することです。

関数名   :strcat
必要なヘッダ:string.h
関数のタイプ:char *strcat (char *s1, const char *s2);
引数    :s1 結合先の文字列へのポインタ(文字列の先頭)
       s2 結合元の文字列へのポインタ(後方に結合する文字列)
戻り値   :s1の値(結合先の先頭アドレス)

◇サンプルプログラム3

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

main()
{
    char a[30]="あいうえお",b[11]="かきくけこ";
    
    strcat(a,b);
    printf("a[]=%s\n",a);
    printf("b[]=%s\n",b);
    return;
}

◇実行結果3

a[]=あいうえおかきくけこ
b[]=かきくけこ

4.文字列の結合strncat()(文字数指定)

 こちらは、前回のstrncpyと同じ様に最大文字数が指定できるものです。

関数名   :strncat
必要なヘッダ:string.h
関数のタイプ:char *strncat (char *s1, const char *s2, size_t n);
引数    :s1 結合先の文字列へのポインタ(文字列の先頭)
       s2 結合元の文字列へのポインタ(後方に結合する文字列)
       n 結合を行う最大文字数
戻り値   :s1の値(結合先の先頭アドレス)

◇サンプルプログラム4

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

main()
{
    char a[30]="あいうえお",b[11]="かきくけこ";
    
    strncat(a,b,6);
    printf("a[]=%s\n",a);
    printf("b[]=%s\n",b);
    return;
}

◇実行結果4

a[]=あいうえおかきく
b[]=かきくけこ

 こちらは文字数指定コピーと違って連結後の最後尾に’\0’が付加されるので余計な処理を行う必要がありません。

◇文字列の比較

ここでの比較についてですが、文字列のいったい何を比較するのでしょうか?

同じものかどうか?それとも総文字数・・・?
答えは、2つの文字列を辞書順にしたときどちらが前になるかの比較です。

5.文字列の比較

関数名   :strcmp
必要なヘッダ:string.h
関数のタイプ:int strcmp (const char *s1, const char *s2);
引数    :s1,s2 文字列へのポインタ
戻り値   :s2よりs1が辞書順で前 正の値
       s2とs1が同じ文字列  0
       s2よりs1が辞書順で後 負の値

◇サンプルプログラム5

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

main()
{
    char a[30]="あいうえお",b[11]="あいう";
    int  x;
    printf("a[]=%s\n",a);
    printf("b[]=%s\n",b);
    x = strcmp(a,b);
    if(x>0) printf("a > b\n");
    else if(x==0) printf("a = b\n");
    else printf("a < b\n");
    return;
}

◇実行結果5
a[]=あいうえお
b[]=あいう
a > b

 上記の実行結果で、a[]の”あいうえお”より、b[]の”あいう”のほうが辞書順で前になるため x は正の数になり、a > b と出力されました。

 比較の方法は先頭の文字より比較し同じならば次の文字で比較して結果を出力します。(実際の辞書の順序と同じ)アルファベットと平仮名といった種類の違うものや漢字の場合でも、文字コードを基準に比較を行うので問題はありません。

※漢字を使った文字列の比較で日本語コードが違う環境の場合は
 環境によって結果が変わるときがあります。

6.文字列の比較(指定文字数)

関数名   :strncmp
必要なヘッダ:string.h
関数のタイプ:int strncmp (const char *s1, const char *s2, size_t n);
引数    :s1,s2 文字列へのポインタ
       n 比較する文字数
戻り値   :s2よりs1が辞書順で前 正の値
       s2とs1が同じ文字列  0
       s2よりs1が辞書順で後 負の値

こちらは比較の文字数指定版です。尚、文字列の長さが n に満たない場合は文字列が短い方の’\0’までの場所で比較を行います。

◇サンプルプログラム6

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

main()
{
    char a[30]="あいうえお",b[11]="あいう";
    int  x,n=6;
    printf("a[]=%s\n",a);
    printf("b[]=%s\n",b);
    x = strncmp(a,b,n);
    if(x>0) printf("%d byteで比較 a > b\n",n);
    else if(x==0) printf("%d byteで比較 a = b\n",n);
    else printf("%d byteで比較 a < b\n",n);
    return;
}

◇実行結果6

a[]=あいうえお
b[]=あいう
6 byteで比較 a = b

◇文字列の長さ

7.文字列の長さ

関数名   :strlen
必要なヘッダ:string.h
関数のタイプ:size_t strlen (const char *s);
引数    :s1 文字列へのポインタ
戻り値   :’\0’を除く文字列の長さ(バイト)

◇サンプルプログラム7

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

main()
{
    char a[30]="あいうえお";
    printf("a[]=%s -->%d byte\n",a,strlen(a));
    return;
}

◇実行結果7
a[]=あいうえお –>10 byte

 このように文字列の長さを取得できる関数は後で応用的なプログラムを作成するときに大変役に立ちます。

◇文字列の型変換

 今回は、文字列を他の型に変換する関数群の紹介です。
例えば、123と入力された文字列を数値として使いたい場合はどうでしょう。Perl,BASIC等では自動的に型変換を行ってくれます。しかしC言語では数式や代入による文字列の型変換はできないのです。(C言語では文字列に対する制約が厳しいです)

 そこで、入力された文字列(もちろん数字の並び)を数値として扱うには基本ライブラリにある型変換関数を使うことになります。

8.型変換関数(文字列→整数)

関数名   :atoi
必要なヘッダ:stdlib.h
関数のタイプ:int atoi (const char *s);
引数    :s 文字列へのポインタ
戻り値   :変換後の数値

 文字列型変換の第1弾は整数への変換です。多分、一番良く使うのではと思います・・・。

◇サンプルプログラム8

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

main()
{
    char str[11];
    int n;
    printf("数字を入力(10桁以内) : ");
    scanf("%s",str);
    printf("%s が入力されました\n",str);
    
    n = atoi(str);
    
    printf("整数に変換し n に代入しました\n");
    printf(" n = %d\n",n);
    printf("2n = %d\n",n*2);
    return;
}

◇実行結果8(123と入力した場合)

数字を入力(10桁以内) : 123
123 が入力されました
整数に変換し n に代入しました
n = 123
2n = 246

これでめでたく数値に変換できました。

・・・ただし、これで終わってはいけません。

  <<『例外処理はどうなってるのか?』>>

そう、文字列から変換を行うということは、数字以外の文字が入力された場合どうなってしまうのかということを考慮しなくてはなりません。(最終的には数字以外の入力を受け付けないようにすべきですが・・・)

ここでatoi関数がどのような例外処理をするのかを検討します!

例1:最初に-(マイナス)を入力

数字を入力(10桁以内) : -836
-836 が入力されました
整数に変換し n に代入しました
n = -836
2n = -1672

例2:小数点付

数字を入力(10桁以内) : 1100.23
1100.23 が入力されました
整数に変換し n に代入しました
n = 1100
2n = 2200

例3:数字以外の文字を混ぜる

数字を入力(10桁以内) : 234e4
234e4 が入力されました
整数に変換し n に代入しました
n = 234
2n = 468

例4:先頭に+(プラス)記号

数字を入力(10桁以内) : +24
+24 が入力されました
整数に変換し n に代入しました
n = 24
2n = 48

例5:途中に+や-

数字を入力(10桁以内) : 438+45-6
438+45-6 が入力されました
整数に変換し n に代入しました
n = 438
2n = 876

例6:デタラメ

数字を入力(10桁以内) : dspjvci
dspjvci が入力されました
整数に変換し n に代入しました
n = 0
2n = 0

以上実験結果としてatoiは先頭は符号あるいは数字、2番目から数字を見て不正な文字か’\0’までを値に変換していることがわかりました。

9.型変換関数(文字列→倍長整数)

関数名   :atol
必要なヘッダ:stdlib.h
関数のタイプ:long atol (const char *s);
引数    :s 文字列へのポインタ
戻り値   :変換後の数値

 前回とほぼ同じで、違いは変換される値がlong型になることです。

◇サンプルプログラム9

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

main()
{
    char str[11];
    long n;
    printf("数字を入力(10桁以内) : ");
    scanf("%s",str);
    printf("%s が入力されました\n",str);
    
    n = atol(str);
    
    printf("整数に変換し n に代入しました\n");
    printf(" n = %d\n",n);
    printf("2n = %d\n",n*2);
    return;
}

前回、テストで忘れていました。先頭からスペースを入力した場合を以下に報告します。

◇実行結果9
数字を入力(10桁以内) : 345
345 が入力されました
整数に変換し n に代入しました
n = 345
2n = 690

10.型変換関数(文字列→倍長浮動小数)

型変換については今回で最後です。
似たような形式なので、さらっと流しましょう(笑)

関数名   :atof
必要なヘッダ:stdlib.h
関数のタイプ:double atof (const char *s);
引数    :s 文字列へのポインタ
戻り値   :変換後の数値

 atofですがfloat型ではなくdouble型ですお間違いなく。
ここでは、今までのatoi,atolで認識されなかった記号の内

 .(小数点)
 e(指数部)
 E(指数部)

が認識されます。
また、指数部を表すe(E)の直後の符号も認識されます。

◇サンプルプログラム10

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

main()
{
    char str[21];
    double n;
    printf("数値を入力(20文字以内) : ");
    scanf("%s",str);
    printf("%s が入力されました\n",str);
    
    n = atof(str);
    
    printf("浮動小数に変換し n に代入しました\n");
    printf(" n = %d\n",n);
    printf("2n = %d\n",n*2);
    return;
}

◇実行結果10

数値を入力(20文字以内) : -123.456e-2
-123.456e-2 が入力されました
浮動小数に変換し n に代入しました
n = -1.234560
2n = -2.469120

◇文字列用の領域確保

 プログラミングしていて配列や文字列をあらかじめ決めた容量ではなく、必要にしたがってメモリを確保できたらいいと思いませんか?

 今日紹介する malloc 関数は、必要に応じてプログラムの実行中に動的にメモリーを確保する関数です。

11.メモリー確保(malloc)

関数名   :malloc
必要なヘッダ:stdlib.h
関数のタイプ:void *malloc (size_t size);
引数    :size 確保したいサイズ
戻り値   :確保した領域のポインタ、もし確保できなかったときは
       NULLを返す

12.メモリー開放(free)

関数名   :free
必要なヘッダ:stdlib.h
関数のタイプ:void free (void *ptr);
引数    :ptr 開放したい領域のポインタ

 以上の2つはセットです。動的に確保したメモリはfreeによって開放しなくてはなりません。さもないとメモリーリークの発生の原因にもなりシステムを不安定にさせてしまう可能性もあるため必ず行ってください。

◇サンプルプログラム11-12

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

main()
{
    int n;
    void *p;
    printf("確保したい領域(文字数) : ");
    scanf("%d",&n);
    
    p = malloc(sizeof(char)*(n+1));
    if(p != NULL){
        printf("%d文字以内で入力してください : ",n);
        scanf("%s",p);
        printf("%s が入力されました\n",p);    
        free(p);
    }
    else printf("メモリの確保に失敗\n");
    return;
}

◇実行結果11-12
確保したい領域(文字数) : 12
12文字以内で入力してください : opqrstu
opqrstu が入力されました

13.メモリー確保(calloc)

関数名   :calloc
必要なヘッダ:stdlib.h
関数のタイプ:void *calloc( size_t num, size_t size );
引数    :num 確保したい数
       size 1単位のサイズ(バイト単位)
戻り値   :確保した領域のポインタ、もし確保できなかったときは
       NULLを返す

 前回のmallocと違うのは要素1つ当りのサイズと確保したい要素数を分けているところです。これによって何型をいくつ確保したいといったメモリ確保の仕方が容易になります。

◇サンプルプログラム13

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

main()
{
    int n;
    void *p;
    printf("確保したい領域(文字数) : ");
    scanf("%d",&n);
    
    p = malloc( n+1,sizeof(char) );
    if(p != NULL){
        printf("%d文字以内で入力してください : ",n);
        scanf("%s",p);
        printf("%s が入力されました\n",p);    
        free(p);
    }
    else printf("メモリの確保に失敗\n");
    return;
}

◇実行結果13
確保したい領域(文字数) : 30
30文字以内で入力してください : edijf
edijf が入力されました

 このようにmalloc,calloc,freeなどの動的メモリ確保・開放の関数は、ポインタを利用した文字列の入力などに臨機応変に活躍することができます。

次章
13.基本アルゴリズム