サーチ…


前書き

Cでは、文字列は組み込み型ではありません。 C文字列は、 '\0'によってヌル文字で終わる文字の1次元配列を持つ規則です。

これは、内容が"abc" C文字列が4文字の'a''b''c''\0'を持つことを意味します。

文字列基本的な紹介の参照してください。

構文

  • char str1 [] = "こんにちは、世界!"; / *変更可能* /
  • char str2 [14] = "こんにちは、世界!"; / *変更可能* /
  • char * str3 = "こんにちは、世界!"; / *変更不可* /

長さを計算する:strlen()

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

int main(int argc, char **argv) 
{
    /* Exit if no second argument is found. */
    if (argc != 2) 
    {
        puts("Argument missing.");
        return EXIT_FAILURE;
    }

    size_t len = strlen(argv[1]);
    printf("The length of the second argument is %zu.\n", len);

    return EXIT_SUCCESS;
}

このプログラムは、2番目の入力引数の長さを計算し、結果をlen格納します。次に、その長さを端末に出力します。たとえば、パラメータprogram_name "Hello, world!"使用して実行すると、プログラムは出力しますThe length of the second argument is 13.なぜなら、文字列Hello, world! 13文字です。

strlenは、文字列の先頭から終了NUL文字'\0'までのすべてのバイト数をカウントします 。したがって、文字列がNULで終了することが保証されている場合にのみ使用できます。

また、文字列にUnicode文字が含まれている場合、 strlenは文字列内にいくつの文字があるかを教えません(いくつかの文字は複数バイトの長さかもしれないので)。このような場合には、文字( すなわち 、コード単位)を自分でカウントする必要があります。次の例の出力を考えてみましょう。

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

int main(void) 
{
    char asciiString[50] = "Hello world!";
    char utf8String[50] = "Γειά σου Κόσμε!"; /* "Hello World!" in Greek */

    printf("asciiString has %zu bytes in the array\n", sizeof(asciiString));
    printf("utf8String has %zu bytes in the array\n", sizeof(utf8String));
    printf("\"%s\" is %zu bytes\n", asciiString, strlen(asciiString));
    printf("\"%s\" is %zu bytes\n", utf8String, strlen(utf8String));
}

出力:

asciiString has 50 bytes in the array
utf8String has 50 bytes in the array
"Hello world!" is 12 bytes
"Γειά σου Κόσμε!" is 27 bytes

コピーと連結:strcpy()、strcat()

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

int main(void)
{
  /* Always ensure that your string is large enough to contain the characters
   * and a terminating NUL character ('\0')!
   */
  char mystring[10];

  /* Copy "foo" into `mystring`, until a NUL character is encountered. */
  strcpy(mystring, "foo");
  printf("%s\n", mystring);

  /* At this point, we used 4 chars of `mystring`, the 3 characters of "foo",
   * and the NUL terminating byte.
   */

  /* Append "bar" to `mystring`. */
  strcat(mystring, "bar");
  printf("%s\n", mystring);

  /* We now use 7 characters of `mystring`: "foo" requires 3, "bar" requires 3
   * and there is a terminating NUL character ('\0') at the end.
   */

  /* Copy "bar" into `mystring`, overwriting the former contents. */
  strcpy(mystring, "bar");
  printf("%s\n", mystring);

  return 0;
}

出力:

foo
foobar
bar

既存の文字列に追加または既存の文字列からコピーする場合は、NULで終了することを確認してください。

文字列リテラル( "foo" )は、コンパイラによってNUL終了されます。

比較:strcmp()、strncmp()、strcasecmp()、strncasecmp()

strcase*関数は、標準Cではなく、POSIX拡張です。

strcmp関数は、2つのNULL終端文字配列を辞書的に比較します。関数は、最初の引数が辞書順で2番目の前に現れる場合は負の値を返し、等しい場合は0を、辞書順の順で最初の引数が現れた場合は正を返します。

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

void compare(char const *lhs, char const *rhs)
{
    int result = strcmp(lhs, rhs); // compute comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "BBB");
    compare("BBB", "CCCCC");
    compare("BBB", "AAAAAA");
    return 0;
}

出力:

BBB equals BBB
BBB comes before CCCCC
BBB comes after AAAAAA

strcmpとして、 strcasecmp関数は、各文字を小文字の対応に変換した後で、辞書的にその引数を比較します。

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

void compare(char const *lhs, char const *rhs)
{
    int result = strcasecmp(lhs, rhs); // compute case-insensitive comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "bBB");
    compare("BBB", "ccCCC");
    compare("BBB", "aaaaaa");
    return 0;
}

出力:

BBB equals bBB
BBB comes before ccCCC
BBB comes after aaaaaa

strncmpstrncasecmpは最大でn文字を比較します:

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

void compare(char const *lhs, char const *rhs, int n)
{
    int result = strncmp(lhs, rhs, n); // compute comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "Bb", 1);
    compare("BBB", "Bb", 2);
    compare("BBB", "Bb", 3);
    return 0;
}

出力:

BBB equals Bb
BBB comes before Bb
BBB comes before Bb

トークン化:strtok()、strtok_r()、strtok_s()

関数strtokは、区切り文字のセットを使用して、文字列を小さな文字列またはトークンに分割します。

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

int main(void)
{
    int toknum = 0;
    char src[] = "Hello,, world!";
    const char delimiters[] = ", !";
    char *token = strtok(src, delimiters);
    while (token != NULL)
    {
        printf("%d: [%s]\n", ++toknum, token);
        token = strtok(NULL, delimiters);
    }
    /* source is now "Hello\0, world\0\0" */
}

出力:

1: [Hello]
2: [world]

デリミタの文字列には1つ以上のデリミタが含まれ、 strtok呼び出すたびに異なるデリミタ文字列を使用できます。

同じソース文字列をトークン化し続けるstrtok呼び出しは、元の文字列をもう一度渡すべきではなく、その代わりに最初の引数としてNULLを渡しNULL 。同じソース文字列渡された場合、代わりに最初のトークンが再トークンされます。つまり、同じ区切り文字を指定すると、 strtokは単に最初のトークンを戻します。

strtokはトークンに新しいメモリを割り当てないので、ソース文字列を変更することに注意してください。つまり、上の例では、文字列srcは、 strtokへの呼び出しによって返されたポインタによって参照されるトークンを生成するように操作されます。つまり、ソース文字列をconstすることはできません(文字列リテラルにすることはできません)。また、区切りバイトのアイデンティティが失われていることも意味します(例では、 "、"と "!"がソース文字列から効果的に削除され、どの区切り文字が一致したかはわかりません)。

また、ソース文字列内の複数の区切り文字は1つとして扱われることに注意してください。この例では、2番目のコンマは無視されます。

strtokは、解析中に静的バッファを使用するため、スレッドセーフでもリエントラントでもありません。これは、関数呼び出した場合ということstrtok 、使用している間に呼び出していない機能strtok使用することもできstrtok 、それが使用して自分自身をある任意の関数によって呼び出すことはできませんstrtok

strtokが再入可能ではないという事実によって引き起こされる問題を示す例は次のとおりです。

char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ","); 

do 
{
    char *part;
    /* Nested calls to strtok do not work as desired */
    printf("[%s]\n", first);
    part = strtok(first, ".");
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok(NULL, ".");
    }
} while ((first = strtok(NULL, ",")) != NULL);

出力:

[1.2]
 [1]
 [2]

予想される動作は、外側の点であるdo whileループ(各進数文字列からなる3つのトークンを作成する必要が"1.2""3.5""4.2"これらの各々のために、) strtok内部ループが別に分割しなければならないために呼び出し( "1""2""3""5""4""2" )を含む。

しかし、 strtokはリエントラントではないので、これは発生しません。代わりに、最初のstrtok正しく "1.2 \ 0"トークンを作成し、内部ループはトークン"1""2"正しく作成します。しかし、外側ループ内のstrtokは、内側ループによって使用される文字列の最後にあり、すぐにNULLを返します。 src配列の2番目と3番目の部分文字列はまったく解析されません。

C11

標準のCライブラリには、スレッドセーフなバージョンやリエントラントなバージョンは含まれていませんが、POSIXのstrtok_rなどの他のバージョンもあります。 MSVCではstrtokと同等のstrtok_sがスレッドセーフであることに注意してください。

C11

C11には、 strtok_sという名前のスレッドセーフでリエントラントなバージョンを提供するオプションパートAnnex Kがあります。 __STDC_LIB_EXT1__を使用して機能をテストできます。このオプション部分は広くサポートされていません。

strtok_s関数はPOSIX strtok_s関数とは異なり、トークン化されている文字列の外部への格納や実行時制約のチェックをstrtok_rます。しかし、正しく書かれたプログラムでは、 strtok_sstrtok_rは同じように動作します。

この例でstrtok_sを使用すると、正しい応答が得られるようになります。

/* you have to announce that you want to use Annex K */ 
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif

char src[] = "1.2,3.5,4.2";  
char *next = NULL;
char *first = strtok_s(src, ",", &next);

do 
{
    char *part;
    char *posn;

    printf("[%s]\n", first);
    part = strtok_s(first, ".", &posn);
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok_s(NULL, ".", &posn);
    }
} 
while ((first = strtok_s(NULL, ",", &next)) != NULL);

出力は次のようになります。

[1.2]
 [1]
 [2]
[3.5]
 [3]
 [5]
[4.2]
 [4]
 [2]

特定の文字の最初か最後のオカレンスを探します:strchr()、strrchr()

strchr関数とstrrchr関数は、NULで終了する文字配列内の文字列内の文字を検索します。 strchrは最初のオカレンスへのポインタを返し、 strrchrは最後のオカレンスへのポインタを返します。

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

int main(void)
{
    char toSearchFor = 'A';

    /* Exit if no second argument is found. */
    if (argc != 2)
    {
        printf("Argument missing.\n");
        return EXIT_FAILURE;
    }

    {
        char *firstOcc = strchr(argv[1], toSearchFor);
        if (firstOcc != NULL) 
        {
            printf("First position of %c in %s is %td.\n", 
              toSearchFor, argv[1], firstOcc-argv[1]); /* A pointer difference's result 
                                     is a signed integer and uses the length modifier 't'. */
        }
        else
        {
            printf("%c is not in %s.\n", toSearchFor, argv[1]);
        }
    }

    {
        char *lastOcc = strrchr(argv[1], toSearchFor);
        if (lastOcc != NULL)
        {
            printf("Last position of %c in %s is %td.\n",
              toSearchFor, argv[1], lastOcc-argv[1]);
        }
    }

    return EXIT_SUCCESS;
}

出力( posという名前の実行ファイルを生成した後):

$ ./pos AAAAAAA
First position of A in AAAAAAA is 0.
Last position of A in AAAAAAA is 6.
$ ./pos BAbbbbbAccccAAAAzzz
First position of A in BAbbbbbAccccAAAAzzz is 1.
Last position of A in BAbbbbbAccccAAAAzzz is 15.
$  ./pos qwerty             
A is not in qwerty.

strrchr一般的な使い方の1つは、パスからファイル名を抽出することです。たとえば、 C:\Users\eak\myfile.txtからmyfile.txtを抽出するには:

char *getFileName(const char *path)
{
    char *pend;

    if ((pend = strrchr(path, '\')) != NULL)
        return pend + 1;

    return NULL;
}

文字列の文字の繰り返し処理

文字列の長さがわかっている場合は、forループを使用してその文字を繰り返し処理できます。

char * string = "hello world"; /* This 11 chars long, excluding the 0-terminator. */
size_t i = 0;
for (; i < 11; i++) {
    printf("%c\n", string[i]);    /* Print each character of the string. */
}

代わりに、標準の関数strlen()を使用して、文字列の内容がわからない場合に文字列の長さを取得することができます。

size_t length = strlen(string);
size_t i = 0; 
for (; i < length; i++) {
    printf("%c\n", string[i]);    /* Print each character of the string. */
}

最後に、Cの文字列がヌルで終了することが保証されていることを利用することができます(前の例のstrlen()に渡したときにすでに行っていました;-))。そのサイズに関係なく配列全体を繰り返し処理し、ヌル文字に達すると反復処理を停止できます:

size_t i = 0;
while (string[i] != '\0') {       /* Stop looping when we reach the null-character. */
    printf("%c\n", string[i]);    /* Print each character of the string. */
    i++;
}

文字列の基本的な紹介

Cでは、 文字列はヌル文字( '\ 0')で終わる文字列です。

文字列リテラルを使用して文字列を作成することができます。文字列リテラルは二重引用符で囲まれた文字列です。例えば、文字列リテラル"hello world"取る。文字列リテラルは自動的にヌルで終了します。

いくつかの方法を使って文字列を作成することができます。例えば、 char *を宣言して文字列の最初の文字を指すように初期化することができます:

char * string = "hello world";

上記のようにchar *を文字列定数に初期化する場合、通常、文字列自体は読み取り専用データに割り当てられます。 stringは配列の最初の要素へのポインタで、文字'h'です。

文字列リテラルは読み取り専用メモリに割り当てられているため、変更不可能です1 。それを変更しようとすると未定義の動作につながるので、 constを追加してこのようなコンパイル時エラーを取得する方が良い

char const * string = "hello world";

これは、同様の効果2を持っています

char const string_arr[] = "hello world";

変更可能な文字列を作成するには、文字配列を宣言し、文字列リテラルを使用してその内容を初期化することができます。

char modifiable_string[] = "hello world";

これは以下と同じです:

char modifiable_string[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};

2番目のバージョンは中括弧で囲まれたイニシャライザを使用しているため、通常は文字配列に'\0'文字が明示的に含まれていない限り、文字列は自動的にNULLで終了しません。


1変更不可能とは、文字列リテラルの文字を変更できないことを意味しますが、ポインタstringを変更することができます(別の場所を指すか、増分または減分することができます)。

2両方の文字列は、両方の文字列の文字を変更できないという意味で同様の効果があります。 stringcharへのポインターであり、 変更可能なl値であるため、配列string_arrが変更不可能なl値である間にインクリメントしたり、他の場所をポイントしたりすることはできません。

ストリングの配列の作成

文字列の配列は、いくつかのことを意味します:

  1. 要素がchar * sの配列
  2. 要素がcharの配列である配列

次のように文字ポインタの配列を作成できます:

char * string_array[] = {
    "foo",
    "bar",
    "baz"
};

注意:文字列リテラルをchar *代入すると、文字列自体が読み込み専用メモリに割り当てられます。ただし、配列string_arrayは読み取り/書き込みメモリに割り当てられます。つまり、配列内のポインタを変更できますが、ポインタが指す文字列を変更することはできません。

Cでは、main argv (プログラムの実行時に渡されたコマンドライン引数の配列)へのパラメータは、 char *char * argv[]配列です。

文字配列の配列を作成することもできます。文字列は文字の配列なので、文字列の配列は単なる要素の文字配列である配列です。

char modifiable_string_array_literals[][4] = {
    "foo",
    "bar",
    "baz"
};

これは次のようになります。

char modifiable_string_array[][4] = {
    {'f', 'o', 'o', '\0'},
    {'b', 'a', 'r', '\0'},
    {'b', 'a', 'z', '\0'}
};

配列の2番目の次元のサイズとして4を指定することに注意してください。私たちの配列の各文字列は実際には4バイトです。ヌル終端文字を含める必要があるからです。

strstr

/* finds the next instance of needle in haystack 
   zbpos: the zero-based position to begin searching from
   haystack: the string to search in
   needle: the string that must be found
   returns the next match of `needle` in `haystack`, or -1 if not found
*/
int findnext(int zbpos, const char *haystack, const char *needle)
{
    char *p; 

    if (((p = strstr(haystack + zbpos, needle)) != NULL)
        return p - haystack;

    return -1;
}

strstrhaystack (最初の)引数でneedle指す文字列を検索します。見つかった場合、 strstrはオカレンスのアドレスを返します。 needleが見つからない場合は、NULLを返します。 zbposを使って同じ針を何度も何度も探し続けることはありません。最初のインスタンスをスキップするために、 zbposオフセットを追加します。メモ帳のクローンは、次のような検索ダイアログを実装するためにfindnextをこのように呼び出すことができます。

/*
    Called when the user clicks "Find Next"
    doc: The text of the document to search
    findwhat: The string to find
*/
void onfindnext(const char *doc, const char *findwhat)
{
    static int i;

    if ((i = findnext(i, doc, findwhat)) != -1)
        /* select the text starting from i and ending at i + strlen(findwhat) */
    else
        /* display a message box saying "end of search" */
}

文字列リテラル

文字列リテラルは、ヌル終了した静的持続時間char配列を表します。それらは静的な記憶期間を持つため、文字列リテラルまたは同じ基礎となる配列へのポインタは、自動配列へのポインタができないいくつかの方法で安全に使用できます。たとえば、関数から文字列リテラルを返すと、明確に定義された動作が得られます。

const char *get_hello() {
    return "Hello, World!";  /* safe */
}

歴史的な理由から、文字列リテラルに対応する配列の要素は正式にはconstはありません。それにもかかわらず、それらを変更しようとすると未定義の動作が発生します。通常、文字列リテラルに対応する配列を変更しようとするプログラムは、クラッシュするか、そうでなければ誤動作します。

char *foo = "hello";
foo[0] = 'y';  /* Undefined behavior - BAD! */

ポインターが文字列リテラルを指している場合、またはポインターの参照先constを宣言して、このような未定義の振る舞いを誤って行うことを避けることが推奨されます。

const char *foo = "hello";
/* GOOD: can't modify the string pointed to by foo */

一方、文字列リテラルの基本となる配列へのポインタは、本質的に特別なものではありません。その値は他のものを指すように自由に変更することができます:

char *foo = "hello";
foo = "World!"; /* OK - we're just changing what foo points to */

さらに、 char配列のイニシャライザはchar列リテラルと同じ形式を持つことができますが、このようなイニシャライザの使用は、初期化された配列の文字列リテラルの特性を与えません。イニシャライザは、単に配列の長さと初期値を指定するだけです。特に、明示的に宣言されていなければ要素は変更可能ですconst

char foo[] = "hello";
foo[0] = 'y';  /* OK! */

文字列をゼロにする

文字列(または他のメモリブロック)をゼロにするためにmemsetを呼び出すことができます。

strはゼロにする文字列、 nは文字列中のバイト数です。

#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>


int main(void)
{
  char str[42] = "fortytwo";
  size_t n = sizeof str; /* Take the size not the length. */

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

  memset(str, '\0', n);

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

  return EXIT_SUCCESS;
}

印刷物:

'fortytwo'
''

もう一つの例:

#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>


#define FORTY_STR "forty"
#define TWO_STR "two"

int main(void)
{
  char str[42] = FORTY_STR TWO_STR;
  size_t n = sizeof str; /* Take the size not the length. */
  char * point_to_two = strstr(str, TWO_STR);

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

  memset(point_to_two, '\0', n);

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

  memset(str, '\0', n);

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

  return EXIT_SUCCESS;
}

印刷物:

'fortytwo'
'forty'
''

strspnとstrcspn

文字列が与えられた場合、 strspnは最初の部分文字列(span)の長さを特定の文字リストのみから計算します。 strcspnは似ていますが、リストされた文字以外の文字からなる最初の部分文字列の長さを計算する点を除いては同じです:

/*
  Provided a string of "tokens" delimited by "separators", print the tokens along
  with the token separators that get skipped.
*/
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char sepchars[] = ",.;!?";
    char foo[] = ";ball call,.fall gall hall!?.,";
    char *s;
    int n;

    for (s = foo; *s != 0; /*empty*/) {
        /* Get the number of token separator characters. */
        n = (int)strspn(s, sepchars);

        if (n > 0)
            printf("skipping separators: << %.*s >> (length=%d)\n", n, s, n);

        /* Actually skip the separators now. */
        s += n;

        /* Get the number of token (non-separator) characters. */
        n = (int)strcspn(s, sepchars);

        if (n > 0)
            printf("token found: << %.*s >> (length=%d)\n", n, s, n);

        /* Skip the token now. */
        s += n;
    }

    printf("== token list exhausted ==\n");

    return 0;
}

ワイド文字列を使用する類似関数はwcsspnwcscspnです。彼らは同じように使用されています。

文字列のコピー

ポインタの割り当ては文字列をコピーしません

あなたは使用することができます=整数をコピーする作業をしますが、使用することはできません=そう使用して、終端のヌル文字と文字の配列として表現されているCにCで文字列内の文字列をコピーする演算子を= (アドレスのみが保存されます演算子をポインタ)。

#include <stdio.h>

int main(void) {
    int a = 10, b;
    char c[] = "abc", *d;

    b = a; /* Integer is copied */
    a = 20; /* Modifying a leaves b unchanged - b is a 'deep copy' of a */
    printf("%d %d\n", a, b); /* "20 10" will be printed */

    d = c; 
    /* Only copies the address of the string - 
    there is still only one string stored in memory */
    
    c[1] = 'x';
    /* Modifies the original string - d[1] = 'x' will do exactly the same thing */

    printf("%s %s\n", c, d); /* "axc axc" will be printed */

    return 0;
}

上記の例は、 char d[3]ではなくchar *dを使用しているためコンパイルされています。後者を使用するとコンパイルエラーが発生します。 Cの配列に割り当てることはできません。

#include <stdio.h>

int main(void) {
    char a[] = "abc";
    char b[8];

    b = a; /* compile error */
    printf("%s\n", b);

    return 0;
}

標準関数を使用した文字列のコピー

strcpy()

実際に文字列をコピーするには、 strcpy()関数をstring.hで使用できます。コピーする前に、十分なスペースを宛先に割り当てる必要があります。

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

int main(void) {
    char a[] = "abc";
    char b[8];

    strcpy(b, a); /* think "b special equals a" */
    printf("%s\n", b); /* "abc" will be printed */

    return 0;
}
C99

snprintf()

バッファオーバーランを避けるために、 snprintf()を使用することができます。テンプレート文字列を解析する必要があるため、パフォーマンス上の理由で最適なソリューションではありませんが、標準ライブラリですぐに使用できる文字列をコピーするための唯一のバッファ制限セーフ機能です。

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

int main(void) {
    char a[] = "012345678901234567890";
    char b[8];

#if 0
    strcpy(b, a); /* causes buffer overrun (undefined behavior), so do not execute this here! */
#endif

    snprintf(b, sizeof(b), "%s", a); /* does not cause buffer overrun */
    printf("%s\n", b); /* "0123456" will be printed */

    return 0;
}

strncat()

strncat()strcat()バッファオーバーフローチェックバージョンstrncat()を使用することです - それはコピーする最大バイト数を指示する3番目の引数をとります:

char dest[32];

dest[0] = '\0';
strncat(dest, source, sizeof(dest) - 1);
    /* copies up to the first (sizeof(dest) - 1) elements of source into dest,
    then puts a \0 on the end of dest */

この定式化はsizeof(dest) - 1使用することに注意してください。 strncat()常にstrncat()を追加しますが、それは文字列のサイズでカウントされません(混乱とバッファの上書きの原因strncat()ので、これは重要です。

また、空でない文字列の後の代替 - 連結はさらに厄介であることに注意してください。検討してください:

char dst[24] = "Clownfish: ";
char src[] = "Marvin and Nemo";
size_t len = strlen(dst);

strncat(dst, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);

出力は次のとおりです。

23: [Clownfish: Marvin and N]

ただし、長さとして指定されたサイズは、宛先の配列のサイズではなく 、その中に残っているスペースの量であり、終端のヌルバイトを数えないことに注意してください。これは大きな上書きの問題を引き起こす可能性があります。それはまた、少し無駄です。 length引数を正しく指定するには、宛先のデータの長さを知っているので、 strncat()再スキャンを省略して、既存のコンテンツの末尾にnullバイトのアドレスを指定することができます。

    strcpy(dst, "Clownfish: ");
    assert(len < sizeof(dst) - 1);
    strncat(dst + len, src, sizeof(dst) - len - 1);
    printf("%zu: [%s]\n", strlen(dst), dst);

これは以前と同じ出力を生成しますが、 strncat()はコピーを開始する前にdstの既存の内容をスキャンする必要はありません。

strncpy()

最後のオプションはstrncpy()関数です。あなたが最初に来るはずだと思うかもしれませんが、それは2つの主な問題点を持っている、

  1. strncpy()を介したコピーがバッファ制限にstrncpy()と、終了ヌル文字は書き込まれません。
  2. strncpy()は、必要に応じて常にデスティネーションを完全に満たします。

(このような奇妙な実装は歴史的なもので、最初はUNIXのファイル名を扱うためのものでした

それを使用する唯一の正しい方法は、手動でヌル終了を保証することです。

strncpy(b, a, sizeof(b)); /* the third parameter is destination buffer size */
b[sizeof(b)/sizeof(*b) - 1] = '\0'; /* terminate the string */
printf("%s\n", b); /* "0123456" will be printed */

それでも大きなバッファがある場合、追加のヌルパディングのためにstrncpy()を使用することは非常に非効率的になります。

文字列を数値に変換する:atoi()、atof()(危険な、使用しないでください)

警告:関数atoiatolatollatofは本質的に安全ではありません結果の値が表現できない場合、その動作は未定義です。 (7.20.1p1)

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

int main(int argc, char** argv)
{
    int val;
    if (argc < 2)
    {
        printf("Usage: %s <integer>\n", argv[0]);
        return 0;
    }

    val = atoi(argv[1]);

    printf("String value = %s, Int value = %d\n", argv[1], val);

    return 0;
}

変換される文字列が範囲内の有効な10進整数の場合、関数は次のように動作します。

$ ./atoi 100
String value = 100, Int value = 100
$ ./atoi 200
String value = 200, Int value = 200

数字で始まり、何かが続く文字列の場合、最初の数字だけが解析されます。

$ ./atoi 0x200
0
$ ./atoi 0123x300
123

他のすべての場合、その動作は未定義です。

$ ./atoi hello
Formatting the hard disk...

上記のあいまいさとこの未定義の動作のため、 atoiファミリの関数は決して使用しないでください。

  • long intに変換するには、 atol()代わりにstrtol()使用します。
  • doubleに変換するには、 strtod()代わりにatof()ます。
C99
  • long long int変換するには、 atoll()代わりにstrtoll()使用します。

文字列形式のデータの読み書き

フォーマットされたデータを文字列に書き出す

int sprintf ( char * str, const char * format, ... );

sprintf関数を使用してfloatデータを文字列に書き出します。

#include <stdio.h>
int main ()
{
  char buffer [50];
  double PI = 3.1415926;
  sprintf (buffer, "PI = %.7f", PI);
  printf ("%s\n",buffer);
  return 0;
}

文字列から書式設定されたデータを読み込む

int sscanf ( const char * s, const char * format, ...);

sscanf関数を使用して、書式設定されたデータを解析します。

#include <stdio.h>
int main ()
{
  char sentence []="date : 06-06-2012";
  char str [50];
  int year;
  int month;
  int day;
  sscanf (sentence,"%s : %2d-%2d-%4d", str, &day, &month, &year);
  printf ("%s -> %02d-%02d-%4d\n",str, day, month, year);
  return 0;
}

安全にストリングを数値に変換する:strtoX関数

C99

C99以降、Cライブラリには、文字列を数値として解釈する一連の安全変換関数があります。それらの名前はstrtoXの形式であり、 Xluldなどのいずれかであり、変換のターゲット・タイプを判別します

double strtod(char const* p, char** endptr);
long double strtold(char const* p, char** endptr);

それらは、コンバージョンにオーバーフローまたはアンダーフローがあることを確認します。

double ret = strtod(argv[1], 0); /* attempt conversion */

/* check the conversion result. */
if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return;  /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

実際に文字列に数字がまったく含まれていない場合、 strtodこの使用法は0.0返します。

これが満足のいくものでなければ、追加のパラメータendptrを使用できます。文字列中の検出された番号の終わりを指すポインタへのポインタです。上記のように0 、またはNULLに設定されている場合は、単に無視されます。

このendptrパラメータは、変換が成功したかどうかを示し、成功した場合は終了した番号を示しendptr

char *check = 0;
double ret = strtod(argv[1], &check); /* attempt conversion */

/* check the conversion result. */
if (argv[1] == check) 
    return; /* No number was detected in string */
else if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

より広い整数型に変換する類似の関数があります:

long strtol(char const* p, char** endptr, int nbase);
long long strtoll(char const* p, char** endptr, int nbase);
unsigned long strtoul(char const* p, char** endptr, int nbase);
unsigned long long strtoull(char const* p, char** endptr, int nbase);

これらの関数には、数値が書き込まれる数値ベースを保持する第3のパラメータnbaseがあります。

long a = strtol("101",   0, 2 ); /* a = 5L */
long b = strtol("101",   0, 8 ); /* b = 65L */
long c = strtol("101",   0, 10); /* c = 101L */
long d = strtol("101",   0, 16); /* d = 257L */
long e = strtol("101",   0, 0 ); /* e = 101L */
long f = strtol("0101",  0, 0 ); /* f = 65L */
long g = strtol("0x101", 0, 0 ); /* g = 257L */

nbaseの特別な値0は、文字列がCプログラムで数値リテラルが解釈されるのと同じ方法で解釈されることを意味します。接頭辞0xは16進表現に対応し、それ以外の場合は先頭の0は8進数であり、他のすべての数字は10進数で表されます。

したがって、コマンドラインの引数を数値として解釈する最も実用的な方法は、次のようになります。

int main(int argc, char* argv[] {
    if (argc < 1)
        return EXIT_FAILURE; /* No number given. */

    /* use strtoull because size_t may be wide */
    size_t mySize = strtoull(argv[1], 0, 0);

    /* then check conversion results. */

     ...

    return EXIT_SUCCESS;
}

つまり、プログラムは8進数、10進数、または16進数のパラメータで呼び出すことができます。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow