カテゴリー

C言語

【C言語&文字列操作】strlenなどのCの文字列操作を試す

2021年6月13日

 

こんな人にオススメ

C言語で文字列の操作ってどうやるの?

文字列の長さを測ったり、文字列同士が同じ文字列かの判断とか。

ということで、今回はC言語での文字列操作について解説する。pythonではスッとできる操作がC言語では結構色々考えないといけないということが実感できた。まあpythonの場合は全部自動でしてくれているだけだが。

strlen関数やstrcpy関数など基本的にものだけ今回は解説する。他の関数に関してはまだ学習中。

今回も猫Cこと「猫でもわかるC言語プログラミング 第3版」を参考にさせていただいた。8.4の「文字と文字列」を中心に、その他気になったことを付け加えた。

環境設定について何を書けばいいのかわからないので、Macのバージョンとgccのバージョンを示す。

  • macOS Big Surバージョン11.4
  • M1 Macbook Pro(2020)
  • Apple clang version 12.0.5 (clang-1205.0.22.9)

運営者のメガネです。YouTubeTwitterInstagramも運営してます。

自己紹介はこちらから、お問い合わせはこちら。

運営者メガネ

NULL文字(ヌル文字)¥0

まず初めに、本記事で重要な役割を果たすNULL文字\0について解説する(一般的には¥0だが、執筆者の環境はmacOSなので\0で表す)。

C言語の文字列の最後は最後ではない

ここでの最後というのは我々がパッと見た時の文字の最後という意味。pythonで文字列を使用してきた場合はこんなこと考えなくてもよかったが、C言語では最後の文字を気にする必要がある。

最後の文字のことを「終端文字」と呼び\0で表す(macOSの場合は¥0ではなく\0で表す)。役割としては「文字列はここで終了」ということの合図をすること。

もし終端文字\0がなければどこまでが一つの文字列なのかがわからなくなったりして色々と脆弱性や不具合の元となるようだ。

strlen

strlen関数は文字列の長さを返す関数。注意点は「NULL文字\0」は出力には含まないということ。なので、単純に文字の長さだと考えればいい。

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

int main(void) {
    char str1[32];
    char str2[] = "Taro";
    size_t len;

    printf("文字列を入力してください: ");
    scanf("%s", str1);

    len = strlen(str1);
    printf("%sの長さは%zuです\\n", str1, len);

    printf("--------------------\\n");

    len = strlen(str2);
    printf("%sの長さは%zuです\\n", str2, len);

    return 0;
}

/*
文字列を入力してください: mega_ten_pa!
mega_ten_pa!の長さは12です
--------------------
Taroの長さは4です
 */

なお、これからの文字列操作にはプリプロセッサとしてヘッダファイルstring.hが必要になる。pythonでいうimport的なものだ。

ちなみにC言語は超最低限の機能しか存在しないため、必ずstdio.hincludeしないといけない。今回はこれに加えてstring.hinclude

プリプロセッサ関連については以下参照。

【C言語&define】C言語の#defineを調べコードを書いてみた

こんな人にオススメ C言語の#d ...

続きを見る

strcpystrncpy

文字列を他の文字列に上書きしたい場合はstrcpy関数とstrncpy関数が便利。前者は全文字を上書き、後者は指定文字数だけ上書きする。

猫Cや他のサイトでは「文字列をコピー」と表現されているので、ペーストしないとと思うかもしれないが、どちらかというとここでのコピーは「複製」という意味合いが強い。

strcpy

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

int main(void) {
    char str[8] = "ABC";

    printf("コピー前: %s\\n", str);

    // str1のABCをdefに上書き
    strcpy(str, "def");
    printf("コピー後: %s\\n", str);

    return 0;
}
/*
コピー前: ABC
コピー後: def
 */

strcpy関数はコピーしたい文字列の全てをコピー先の文字列に上書きする。順番は

strcpy((コピー先の文字列), (コピーしたい文字列))

ちょうど変数に代入するときの配置と同じ。

a = 10;
(代入先の変数) = 代入する値;

コピー先の文字列がコピーしたい文字列よりも長い場合はどうだろうか。以下ではABCDEFGという文字列にdeという文字列を上書きしている。

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

int main(void) {
    char str[8] = "ABCDEFG";

    printf("コピー前: %s\\n", str);

    // str1をdeに上書き
    strcpy(str, "de");
    printf("コピー後: %s\\n", str);

    return 0;
}
/*
コピー前: ABCDEFG
コピー後: de
 */

結果はdeだけが生き残る。これは、strcpy関数が終端文字の\0までコピーするから。終端文字が来ると文字列が終了するという合図なので、de以降の元からあったCDEFGは消える。

バッファオーバーラン

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

int main(void) {
    char str[8] = "ABC";

    printf("%s\\n", str);

    // strで8文字確保している中で8文字入力するとエラー(\\0の1文字分も考慮する必要あり)
    // バッファオーバーランの発生
    strcpy(str, "defghijk");
    printf("%s\\n", str);

    return 0;
}
/*
ABC
zsh: illegal hardware instruction  "(.exeファイルまでのパス)"(.exeファイル名)
 */

ここでバッファオーバーランについて解説しておく。バッファオーバーランは簡単にいうと良いしていたメモリを超えてメモリを確保しようとして意図しない動作を引き起こす状態。

上記の例では元々8文字分で用意したstrdefghijkという8文字+終端文字\\nの合計9文字を入れた時にエラーが起きることを再現している。

なお、執筆者が使用しているVScodeではエラーが吐かれたが、他のエディタやもしかしたらOSなどでエラーが発生せずにそのまま処理されてしまうことがあるかもしれない。

その場合にも備え普段から確保量を超えていないかを確認するようにしなければならない。

VScodeとC言語の拡張機能などについては以下参照。

【VScode&拡張機能】python・C・LaTeX...ユーザーのVisual Studio Code拡張機能

こんな人にオススメ Visual Studio Codeと& ...

続きを見る

そもそも代入はできないのか

str1 = 'ABC'
str2 = str1

print(str1)
print(str2)

# ABC
# ABC

pythonだと上記コードのようにstr2に直接str1を代入することが可能だった。C言語でもできるかというとできないようだ。

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

int main(void) {
    char str1[16] = "ABC";
    char str2[16];

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    // 配列の代入は不可能
    str2 = str1;

    return 0;
}

/*
strcpy_2err.c:11:10: error: array type 'char [16]' is not assignable
    str2 = str1;
    ~~~~ ^
1 error generated.
 */

したがって、面倒でもstrcpy関数を使用することで文字列の複製を行う。

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

int main(void) {
    char str1[16] = "ABC";
    char str2[16];

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    // str2にstr1の内容を上書き
    strcpy(str2, str1);

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    return 0;
}

/*
str1: ABC
str2:
str1: ABC
str2: ABC
 */

strncpy

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

int main(void) {
    char str1[16] = "ABCDEFG";
    char str2[16] = "12345";
    char str3[16] = "ABCDEFG";
    char str4[16] = "abcde";

    printf("str1: %s\\n", str1);

    // str1にstr2を3文字上書き
    strncpy(str1, str2, 3);

    // str2の文字数は3より小さいので、\\0を末尾に付加する必要あり
    str1[3] = '\\0';

    printf("str1: %s\\n", str1);

    printf("--------------------\\n");

    printf("str3: %s\\n", str3);

    // str3にstr4を8文字上書き
    strncpy(str3, str4, 8);

    // str4の文字数は8より大きいので、\\0を末尾に付加する必要なし
    // 多い分は自動で\\0で埋められる
    printf("str3: %s\\n", str3);

    return 0;
}
/*
str1: ABCDEFG
str1: 123
--------------------
str3: ABCDEFG
str3: abcde
 */

strcpyではコピーする文字列全てを使用した。strncpy関数では何文字コピーするかを指定することができる。

上の例ではstr1str2を3文字分上書きしている。また、str3str4を8文字分上書きしている。8文字分だと超えているが、超えた分は自動で\0で埋められる。

ここで注意しないといけないのが、str2で3文字コピーの際、終端文字\0が最後につかないということ。したがって

str1[3] = '\\0';

のように自分で終端文字\0をつけてあげないといけない。

strcmpstrncmp

2種類の文字列を比較する際にはstrcmp関数とstrncmp関数が便利。こちらも前者が全文字で比較、後者が指定した文字列だけ比較するというもの。

比較は辞書方式で行う。例えば以下。3文字目がbよりaの方が早いのでこの順番。

  • aaaa
  • aaba

また、文字数が異なると多い方が後に来る。以下ではaが一文字多いので多い方が下に来る。

  • aaaa
  • aaaaa

書き方は以下。

strcmp((比較したい文字列), (比較対象))

比較したい文字列の方が辞書的に先に来れば負の数、後に来れば正の数、そして一致すれば0を返す。文字列に違いが出た場合はASCIIコードにしたがって、何文字分ズレているのかを出力する。

strcmp

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

int main(void) {
    char str1[32] = "megatenpa";
    char str2[32] = "megatempa";
    char str3[32] = "magatenpa";
    char str4[32] = "megatensa";
    char str5[32] = "megatenpa";
    int cmp;

    char str6[32] = "mEgatenpa";

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);
    printf("str3: %s\\n", str3);
    printf("str4: %s\\n", str4);
    printf("str5: %s\\n", str5);
    printf("str6: %s\\n", str6);

    printf("--------------------\\n");

    // str1, str2はnとmの違い
    cmp = strcmp(str1, str2); /* str1 > str2の順番なので正の数 */
    printf("str1 vs str2: %d\\n", cmp);

    // str1, str3はeとaの違い
    cmp = strcmp(str1, str3); /* str1 > str3の順番なので正の数 */
    printf("str1 vs str3: %d\\n", cmp); /* aとeの間は4文字分空いている */

    // str1, str4はpとsの違い
    cmp = strcmp(str1, str4); /* str1 < str4の順番なので負の数 */
    printf("str1 vs str4: %d\\n", cmp); /* pとsの間は3文字分空いている */

    // str1, str5は同じ
    cmp = strcmp(str1, str5); /* 同じ順番なので0 */
    printf("str1 vs str5: %d\\n", cmp);

    printf("--------------------\\n");

    // str1, str6はeかEかの違い
    // ASCIIコードの大文字、小文字の間には[, ¥, ], ^, _, `の6文字がある
    // したがって大文字と小文字の比較時にはアルファベット26+上記記号6文字が加算
    cmp = strcmp(str1, str6); /* 文字列列の差分はASCIIコードの値で比較 */
    printf("str1 vs str6: %d\\n", cmp);

    return 0;
}
/*
str1: megatenpa
str2: megatempa
str3: magatenpa
str4: megatensa
str5: megatenpa
str6: mEgatenpa
--------------------
str1 vs str2: 1
str1 vs str3: 4
str1 vs str4: -3
str1 vs str5: 0
--------------------
str1 vs str6: 32
 */

strcmp関数は比較対象の文字列の全てと比較する。str1str2の比較では比較文字列はnmであり、nの方が辞書的に後に来るので+1となる。

また、str1str3の比較では比較文字列はeaでこちらもeの方が後に来るので正の数。しかしeaではd, c, b, aと4文字分の開きがあるので+4が出力される。

str1str4の比較ではpsであるが、こちらはpは前に来るので負の数。q, r, sと並んでいるので-3が出力される。str1str5では同じ文字列なので0が出力。

最後のstr1str6では小文字のeと大文字のEの比較なのでASCIIコードに従って32が出力される。ASCIIコードでは大文字の方が先に来るので正の数。

strncmp

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

int main(void) {
    char str1[32] = "megatenpa";
    char str2[32] = "megatempa";
    int cmp;

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    printf("--------------------\\n");

    // str1, str2はnとmの違い
    cmp = strncmp(str1, str2, 6); /* 6文字目はどちらもeなので同じ文字列判定 */
    printf("str1 vs str2 (n = 6): %d\\n", cmp);

    // str1, str2はnとmの違い
    cmp = strncmp(str1, str2, 7); /* 7文字目はmとnなので不一致判定 */
    printf("str1 vs str2 (n = 7): %d\\n", cmp);

    // str1, str2はnとmの違い
    // 文字列は9文字なので15文字以降は\\0で打ち切り
    cmp = strncmp(str1, str2, 15);
    printf("str1 vs str2 (n = 15): %d\\n", cmp);

    return 0;
}
/*
str1: megatenpa
str2: megatempa
--------------------
str1 vs str2 (n = 6): 0
str1 vs str2 (n = 7): 1
str1 vs str2 (n = 15): 1
 */

strncmp関数はstrcmp関数の指定文字数だけ比較版。指定した文字数まで比較を行い、一致か不一致かを判断する。

上記コードでは6文字目までは同じ文字列なので出力は0、7文字目でmnの違いが出るので出力が1となる。合計文字数を超えたとしても、終端文字\0で比較は終了するので全文字比較と同じ結果となる。

strcatstrncat

strcat関数とstrncat関数は文字列の結合で使用される関数。書き方は以下。

strcat((結合元の文字列), (結合したい文字列))

strcat

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

int main(void) {
    char str1[32] = "Mega";
    char str2[32] = "TenPa";

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    printf("--------------------\\n");

    strcat(str1, str2);
    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    return 0;
}
/*
str1: Mega
str2: TenPa
--------------------
str1: MegaTenPa
str2: TenPa
 */

strcat関数は結合したい文字列として指定した文字列全てを結合する。strcat関数もstrcpy関数と同様、バッファオーバーランには十分注意しなければならない。

以下のコードではstr1str2を結合するが、str1として確保したサイズが9で、結合後のMegaTenPa9文字+終端\010文字なのでバッファオーバーランでエラーとなる。

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

int main(void) {
    char str1[9] = "Mega";
    char str2[32] = "TenPa";

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    printf("--------------------\\n");

    // MegaTenPaは9文字であるが、終端文字\\0の分を考慮していないのでエラー
    // バッファオーバーランの発生
    // 文字列間の\\0は重複分として上書き
    strcat(str1, str2);

    return 0;
}
/*
str1: Mega
str2: TenPa
--------------------
zsh: illegal hardware instruction  "(.exeファイルまでのパス)"(.exeファイル名)
 */

strncat

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

int main(void) {
    char str1[32] = "Mega";
    char str2[32] = "TenPa";
    char str3[32] = "Mega";
    char str4[32] = "Mega";

    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);

    printf("--------------------\\n");

    // str2の前2文字Teだけを結合
    strncat(str3, str2, 2);
    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);
    printf("str3: %s\\n", str3);

    printf("--------------------\\n");

    // str2長さ5 < 8だが、\\0で止まるので全文字を結合
    strncat(str4, str2, 8);
    printf("str1: %s\\n", str1);
    printf("str2: %s\\n", str2);
    printf("str4: %s\\n", str4);

    return 0;
}
/*
str1: Mega
str2: TenPa
--------------------
str1: Mega
str2: TenPa
str3: MegaTe
--------------------
str1: Mega
str2: TenPa
str4: MegaTenPa
 */

strncat関数はstrcat関数の結合したい文字列のうち、指定した文字数だけ使用するという関数。

上の例では、str3str4ではそれぞれ指定文字列ずつstr1str2を結合した。str3ではstr2のうち初めの2文字を、str4ではstr2のうち初めの8文字を結合した。8文字だと文字数を超えるが、\0があるので全文字を結合したことと同じになる。

strlenが一番使いやすいか

今回はC言語の文字列操作について解説した。実は今回紹介した操作以外にも山のように関数が存在する。それらについては執筆者自身がまだ学習できていない点なので省いた。

色々と解説したが、一番使いやすくて使用例がイメージできるのがstrlen関数だと思う。ループの回数を文字の長さに自動調整するなどの用途が思いついた。

まあまだまだズブズブのとーしろーなのでこれからも色々と勉強しようと思う。

関連記事

C言語

【C言語&構造体】複数のデータ型を一つに格納したい(ポインタなし)

2021/12/11

こんな人にオススメ C言語で ...

C言語

【C言語&文字列操作】strlenなどのCの文字列操作を試す

2021/11/20

  こんな人にオススメ C言語 ...

C言語

【C言語&define】C言語の#defineを調べコードを書いてみた

2021/11/20

こんな人にオススメ C言語の#d ...

C言語

【C言語&switch文】C言語初心者がswitch文を学ぶ

2021/11/20

こんな人にオススメ C言語のsw ...

スイッチボット

2022/11/28

【SwitchBotロックレビュー】これからのスタンダードになりうるスマートロック

こんな人にオススメ SwitchBotからスマートロック「SwitchBotロック」が発売された ...

生活に役立つ

2022/11/28

【メガネ厳選】クソ便利に使っているサービスやアイテム達

このページでは執筆者「メガネ」が実際に使って便利だと感じているサ ...

マウス

2022/9/11

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【ながら聴きイヤホン比較】SONY LinkBuds、ambie、BoCoはどれがおすすめ?

こんな人におすすめ 耳を塞がない開放型のイヤホンに完全ワイヤレスӟ ...

macOSアプリケーション

2022/10/15

【M1 Mac】MacBook Proに入れている便利でニッチなアプリを21個紹介する

こんな人におすすめ MacBookを購入してLINEとか必要最低限のアプリは入れた。 ...

完全ワイヤレスイヤホン(TWS)

2022/10/23

【SENNHEISER MOMENTUM True Wireless 3レビュー】高レベルでバランス型の高音質イヤホン

こんな人におすすめ SENNHEISER MOMENTUM True Wireless 3って実際のところどうなの? 評判は良い ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【SONY WF-1000XM4レビュー】神とゴミのハーフ&ハーフ

こんな人におすすめ SONYのフラグシップモデル「SONY WF-1000XM4」ってどれくらい性 ...

完全ワイヤレスイヤホン(TWS)

2022/8/19

【Nothing ear (1)レビュー】ライトな完成度、アップデートに期待

こんな人にオススメ 完全ワイヤレスイヤホン(TWS)でスケルトンボディ ...

  • この記事を書いた人

メガネ

ベンチャー企業のWebエンジニア駆け出し。独学のPythonで天文学系の大学院を修了→新卒を1.5年で辞める→転職→今に至る。
常時金欠のガジェット好きでM1 MacBook Pro x Galaxy S22 Ultraの狂人。
人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂う→人生が楽しい

ガジェットのレビューとPythonコードを記事にしています。ぜひ楽しんでください🦊
自己紹介と半生→変わって楽しいの繰り返し

-C言語
-,