こんな人にオススメ
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)
目次(クリック・タップでジャンプ)
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.h
もinclude
しないといけない。今回はこれに加えてstring.h
もinclude
。
プリプロセッサ関連については以下参照。
-
【C言語&define】C言語の#defineを調べコードを書いてみた
続きを見る
strcpy
とstrncpy
文字列を他の文字列に上書きしたい場合は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文字入力するとエラー(\#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ファイル名) */の1文字分も考慮する必要あり) // バッファオーバーランの発生 strcpy(str, "defghijk"); printf("%s\\n", str); return 0; } /* ABC zsh: illegal hardware instruction "(.exeファイルまでのパス)"(.exeファイル名) */
ここでバッファオーバーランについて解説しておく。バッファオーバーランは簡単にいうと良いしていたメモリを超えてメモリを確保しようとして意図しない動作を引き起こす状態。
上記の例では元々8
文字分で用意したstr
にdefghijk
という8
文字+終端文字\\n
の合計9
文字を入れた時にエラーが起きることを再現している。
なお、執筆者が使用しているVScodeではエラーが吐かれたが、他のエディタやもしかしたらOSなどでエラーが発生せずにそのまま処理されてしまうことがあるかもしれない。
その場合にも備え普段から確保量を超えていないかを確認するようにしなければならない。
VScodeとC言語の拡張機能などについては以下参照。
-
【VScode&拡張機能】python・C・LaTeX...ユーザーの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より小さいので、\#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 */を末尾に付加する必要あり str1[3] = '\#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 */'; printf("str1: %s\\n", str1); printf("--------------------\\n"); printf("str3: %s\\n", str3); // str3にstr4を8文字上書き strncpy(str3, str4, 8); // str4の文字数は8より大きいので、\#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 */を末尾に付加する必要なし // 多い分は自動で\#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 */で埋められる printf("str3: %s\\n", str3); return 0; } /* str1: ABCDEFG str1: 123 -------------------- str3: ABCDEFG str3: abcde */
strcpy
ではコピーする文字列全てを使用した。strncpy
関数では何文字コピーするかを指定することができる。
上の例ではstr1
にstr2
を3文字分上書きしている。また、str3
にstr4
を8文字分上書きしている。8文字分だと超えているが、超えた分は自動で\0
で埋められる。
ここで注意しないといけないのが、str2
で3文字コピーの際、終端文字\0
が最後につかないということ。したがって
str1[3] = '\str1[3] = '\\0';';
のように自分で終端文字\0
をつけてあげないといけない。
strcmp
とstrncmp
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
関数は比較対象の文字列の全てと比較する。str1
とstr2
の比較では比較文字列はn
とm
であり、n
の方が辞書的に後に来るので+1
となる。
また、str1
とstr3
の比較では比較文字列はe
とa
でこちらもe
の方が後に来るので正の数。しかしe
とa
ではd
, c
, b
, a
と4文字分の開きがあるので+4が出力される。
str1
とstr4
の比較ではp
とs
であるが、こちらはp
は前に来るので負の数。q
, r
, s
と並んでいるので-3
が出力される。str1
とstr5
では同じ文字列なので0
が出力。
最後のstr1
とstr6
では小文字の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文字以降は\#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 */で打ち切り 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文字目でm
とn
の違いが出るので出力が1
となる。合計文字数を超えたとしても、終端文字\0
で比較は終了するので全文字比較と同じ結果となる。
strcat
とstrncat
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関数と同様、バッファオーバーランには十分注意しなければならない。
以下のコードではstr1
にstr2
を結合するが、str1
として確保したサイズが9
で、結合後のMegaTenPa
は9
文字+終端\0
は10
文字なのでバッファオーバーランでエラーとなる。
#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文字であるが、終端文字\#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ファイル名) */の分を考慮していないのでエラー // バッファオーバーランの発生 // 文字列間の\#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ファイル名) */は重複分として上書き 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だが、\#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(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
関数の結合したい文字列のうち、指定した文字数だけ使用するという関数。
上の例では、str3
とstr4
ではそれぞれ指定文字列ずつstr1
にstr2
を結合した。str3
ではstr2
のうち初めの2
文字を、str4
ではstr2
のうち初めの8
文字を結合した。8
文字だと文字数を超えるが、\0
があるので全文字を結合したことと同じになる。
strlen
が一番使いやすいか
今回はC言語の文字列操作について解説した。実は今回紹介した操作以外にも山のように関数が存在する。それらについては執筆者自身がまだ学習できていない点なので省いた。
色々と解説したが、一番使いやすくて使用例がイメージできるのがstrlen
関数だと思う。ループの回数を文字の長さに自動調整するなどの用途が思いついた。
まあまだまだズブズブのとーしろーなのでこれからも色々と勉強しようと思う。


関連記事
-
【C言語&構造体】複数のデータ型を一つに格納したい(ポインタなし)
2021/12/11
-
【C言語&文字列操作】strlenなどのCの文字列操作を試す
2021/11/20
-
【C言語&define】C言語の#defineを調べコードを書いてみた
2021/11/20
-
【C言語&switch文】C言語初心者がswitch文を学ぶ
2021/11/20