Поиск…


Вступление

В C строка не является внутренним типом. С-строка - это соглашение, чтобы иметь одномерный массив символов, который заканчивается нулевым символом, с помощью '\0' .

Это означает, что C-строка с содержанием "abc" будет иметь четыре символа '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;
}

Эта программа вычисляет длину своего второго входного аргумента и сохраняет результат в 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 компилятором.

Comparsion: strcmp (), strncmp (), strcasecmp (), strncasecmp ()

strcase* -функции не являются стандартными C, а расширением POSIX.

Функция strcmp лексикографически сравнивает два массива символов с нулевым символом. Функции возвращают отрицательное значение, если первый аргумент появляется перед вторым в лексикографическом порядке, ноль, если они сравнивают равный или положительный, если первый аргумент появляется после второго в лексикографическом порядке.

#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 функция 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

strncmp и strncasecmp сравнивают не более 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

Tokenisation: 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]

Строка разделителей может содержать один или несколько разделителей, и с каждым вызовом strtok могут использоваться разные строки разделителя.

Вызовы в strtok для продолжения токенизации одной и той же исходной строки не должны снова передавать исходную строку, а вместо этого передавать NULL в качестве первого аргумента. Если же исходная строка передается затем первый маркер вместо этого будет повторно лексемы. То есть, учитывая те же разделители, strtok просто вернет первый токен снова.

Обратите внимание: поскольку strtok не выделяет новую память для токенов, она изменяет исходную строку . То есть в приведенном выше примере строка src будет обрабатываться для создания маркеров, на которые ссылается указатель, возвращаемый вызовами strtok . Это означает, что исходная строка не может быть const (поэтому она не может быть строковым литералом). Это также означает, что личность разделительного байта теряется (т. Е. В примере «,» и «!» Эффективно удаляются из исходной строки, и вы не можете определить, какой символ разделителя совпадают).

Заметим также, что несколько последовательных разделителей в исходной строке рассматриваются как один; в этом примере вторая запятая игнорируется.

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 во "1.2" "3.5" "4.2" strtok do while цикла должно создать три маркера , состоящие из каждой строки десятичных чисел ( "1.2" , "3.5" , "4.2" ), для каждого из которых strtok предусматривает внутренний цикл должен разделить его на отдельные ( "1" , "2" , "3" , "5" , "4" , "2" ).

Однако, поскольку strtok не является повторным, этого не происходит. Вместо этого первый strtok правильно создает токен «1,2 \ 0», а внутренний цикл правильно создает токены "1" и "2" . Но тогда strtok во внешнем цикле находится в конце строки, используемой внутренним циклом, и немедленно возвращает NULL. Вторая и третья подстроки массива src вообще не анализируются.

C11

Стандартные библиотеки C не содержат потокобезопасную или повторную версию, но некоторые другие, например POSIX ' strtok_r . Обратите внимание, что в MSVC эквивалент strtok strtok_s является потокобезопасным.

C11

C11 имеет необязательную часть, приложение K, которая предлагает поточно-безопасную и повторную версию с именем strtok_s . Вы можете протестировать эту функцию с помощью __STDC_LIB_EXT1__ . Эта дополнительная часть не поддерживается широко.

Функция strtok_s отличается от функции POSIX strtok_r ее от хранения за пределами токенированной строки и проверяя ограничения времени выполнения. Однако при правильно написанных программах strtok_s и strtok_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 находят символ в строке, то есть в символьном массиве с нулевым символом. 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 использования strrchr является извлечение имени файла из пути. Например, чтобы извлечь myfile.txt из C:\Users\eak\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'};

Поскольку во второй версии используется инициализатор с закрытой фигурной скобкой, строка не будет автоматически завершаться нулем, если символ '\0' явно не включен в массив символов, обычно как последний элемент.


1 Немодифицируемый означает, что символы в строковом литерале не могут быть изменены, но помните, что string указателя может быть изменена (может указывать в другом месте или может быть увеличена или уменьшена).

2 Обе строки имеют аналогичный эффект в том смысле, что символы обеих строк не могут быть изменены. Следует отметить, что string является указателем на char и это модифицируемое значение 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'}
};

Обратите внимание, что мы указываем 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;
}

strstr ищет аргумент haystack (первый) для строки, на которую указывает needle . Если найдено, strstr возвращает адрес события. Если он не мог найти needle , он возвращает NULL. Мы используем zbpos чтобы мы не продолжали находить ту же самую иглу снова и снова. Чтобы пропустить первый экземпляр, добавим смещение zbpos . Клон Notepad может вызвать 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 могут иметь ту же форму, что и строковые литералы, использование такого инициализатора не связывает характеристики строкового литерала с инициализированным массивом. Инициализатор просто обозначает длину и начальное содержимое массива. В частности, элементы могут быть изменены, если явно не объявлены 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;
}

Аналогичные функции с использованием широкоформатных строк - wcsspn и wcscspn ; они используются одинаково.

Копирование строк

Назначения указателей не копируют строки

Вы можете использовать оператор = для копирования целых чисел, но вы не можете использовать оператор = для копирования строк в 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 а не char d[3] . Использование последнего приведет к ошибке компилятора. Вы не можете назначать массивы в 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() ) - он принимает третий аргумент, который сообщает ему максимальное количество копируемых байтов:

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() всегда добавляет нулевой байт (хороший), но не считает, что размер строки (причина замешательства и перезаписывания буфера).

Также обратите внимание, что альтернатива - конкатенация после непустой строки - еще более чревата. Рассматривать:

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]

Обратите внимание, однако, что размер, указанный в качестве длины, не был размером целевого массива, а объемом оставшегося в нем места, не считая байт нулевого терминала. Это может вызвать большие проблемы с перезаписи. Это также немного расточительно; для правильного указания аргумента длины вы знаете длину данных в месте назначения, поэтому вместо этого вы можете указать адрес нулевого байта в конце существующего содержимого, сохраняя strncat() при его повторном сканировании:

    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() . Хотя вы можете подумать, что это должно быть на первом месте, это довольно обманчивая функция, которая имеет две основные ошибки:

  1. Если копирование через 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 () (опасно, не используйте их)

Предупреждение. Функции atoi , atol , atoll и atof по своей сути являются небезопасными, потому что: если значение результата не может быть представлено, поведение не определено. (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;
}

Когда строка, подлежащая преобразованию, является допустимым десятичным целым, находящимся в диапазоне, функция работает:

$ ./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 , используйте strtol() вместо atol() .
  • Чтобы преобразовать в double , используйте strtod() вместо atof() .
C99
  • Чтобы преобразовать в long long int , используйте strtoll() вместо atoll() .

чтение / запись в формате форматированных строк

Запись форматированных данных в строку

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 , где X является одним из l , ul , d и т. Д., Чтобы определить целевой тип преобразования

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 указывает, было ли успешное преобразование, и если да, то где число закончилось:

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);

Эти функции имеют третий параметр 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 */

Специальное значение 0 для nbase означает, что строка интерпретируется так же, как литералы чисел интерпретируются в программе на C: префикс 0x соответствует шестнадцатеричному представлению, в противном случае ведущее 0 является восьмеричным, а все остальные числа рассматриваются как десятичные.

Таким образом, наиболее практичным способом интерпретации аргумента командной строки как числа будет

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;
}

Это означает, что программа может вызываться с параметром в восьмеричном, десятичном или шестнадцатеричном.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow