Перейти к содержанию
  • Лента
  • Популярные
  • Последние
  • Теги
  • Пользователи
  • Сотрудничество
Свернуть
Логотип бренда
Категории
  1. Промышленный форум
  2. Категории
  3. ПO для чпу
  4. Работа со строками в C: Полное руководство

Работа со строками в C: Полное руководство

Запланировано Прикреплена Закрыта Перенесена ПO для чпу
1 Сообщения 1 Постеры 58 Просмотры 1 Отслеживают
  • Сначала старые
  • Сначала новые
  • По количеству голосов
Ответить
  • Ответить, создав новую тему
Авторизуйтесь, чтобы ответить
Эта тема была удалена. Только пользователи с правом управления темами могут её видеть.
  • TimT Не в сети
    TimT Не в сети
    Tim
    написал в отредактировано
    #1

    В C строки — это просто массивы символов (char), заканчивающиеся нулевым байтом (\0😞

    char str[] = "Hello";
    // В памяти: H e l l o \0
    // Индексы: 0 1 2 3 4 5
    

    Нулевой байт (\0) — это терминатор, он сигнализирует окончание строки. Без него функции вроде printf() не будут знать, где остановиться, и напечатают мусор из памяти.

    Это ключевое отличие от других языков, где строки — это отдельный тип данных. В C это просто договоренность: строка = массив char + нулевой терминатор.


    Объявление строк

    Способ 1: Массив с инициализацией

    char str[] = "Hello";  // Автоматический размер (6 байт: H e l l o \0)
    

    Компилятор сам считает, сколько нужно места, и выделит 6 байт (5 символов + терминатор).

    Способ 2: Массив с явным размером

    char str[50] = "Hello";  // Массив размером 50 байт
    

    Первые 6 байт: H e l l o \0, остальные 44 — неинициализированы (мусор).

    Способ 3: Указатель на строковый литерал

    char *str = "Hello";  // Указатель на неизменяемую строку
    

    Эта строка хранится в read-only памяти (в сегменте кода). Вы можете читать её, но НЕ можете менять:

    char *str = "Hello";
    str[0] = 'J';  // КРАХ! Segmentation fault — попытка написать в read-only память
    

    Способ 4: Массив для изменяемой строки

    char str[] = "Hello";  // Копия в стеке, можно менять
    str[0] = 'J';  // OK! Теперь str = "Jello"
    

    Таблица различий:

    Объявление Размер Изменяемо? Хранилище
    char str[] = "..." Автоматический Да Стек
    char str[50] = "..." Фиксированный Да Стек
    char *str = "..." N/A Нет Readonly память
    char *str = malloc(...) Динамический Да Heap

    Основные функции для строк

    Все они находятся в <string.h>:

    #include <string.h>
    

    strlen() — длина строки

    Возвращает количество символов БЕЗ терминатора:

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char str[] = "Hello";
        size_t len = strlen(str);
        printf("Длина: %zu\n", len);  // 5
        return 0;
    }
    

    Важно: strlen() идёт по памяти, пока не найдёт \0. Если его нет, программа зависнет или упадёт.

    char str[5];  // Ошибка: нет терминатора!
    strlen(str);  // Неопределённое поведение
    

    strcpy() — копирование строки

    char src[] = "Hello";
    char dest[50];
    strcpy(dest, src);
    printf("%s\n", dest);  // Hello
    

    ОПАСНОСТЬ: strcpy() не проверяет размер буфера — это классическая уязвимость:

    char dest[5];           // 5 байт
    strcpy(dest, "Hello, World!");  // 13 символов!
    // БУФЕР ПЕРЕПОЛНЕН! Пишем за границы массива
    

    Исправление: используйте strncpy():

    char dest[50];
    strncpy(dest, src, 49);  // Максимум 49 символов
    dest[49] = '\0';          // Гарантируем терминатор
    

    Или ещё лучше — используйте snprintf():

    char dest[50];
    snprintf(dest, sizeof(dest), "%s", src);  // Безопасно и удобно
    

    strcat() — конкатенация (склеивание)

    char str1[50] = "Hello";
    char str2[] = " World";
    strcat(str1, str2);
    printf("%s\n", str1);  // Hello World
    

    Требование: str1 должен быть выделен достаточно большой, чтобы вместить результат.

    ОПАСНОСТЬ: как и strcpy(), strcat() не проверяет границы:

    char str1[6] = "Hello";  // Только 6 байт
    strcat(str1, " World");  // ПЕРЕПОЛНЕНИЕ
    

    Исправление: strncat():

    char str1[50] = "Hello";
    char str2[] = " World";
    strncat(str1, str2, 49 - strlen(str1) - 1);  // Добавить не более N символов
    

    strcmp() — сравнение строк

    #include <string.h>
    
    int main() {
        char str1[] = "Hello";
        char str2[] = "Hello";
        char str3[] = "World";
        
        printf("%d\n", strcmp(str1, str2));  // 0 (одинаковые)
        printf("%d\n", strcmp(str1, str3));  // < 0 (str1 < str3 в ASCII)
        printf("%d\n", strcmp(str3, str1));  // > 0 (str3 > str1 в ASCII)
        
        return 0;
    }
    

    Возвращаемые значения:

    • 0 — строки одинаковые
    • < 0 — первая строка лексикографически меньше
    • > 0 — первая строка лексикографически больше

    Почему не использовать ==?

    char *str1 = "Hello";
    char *str2 = "Hello";
    if (str1 == str2) { }  // НЕПРАВИЛЬНО! Сравнивает адреса, не значения
    if (strcmp(str1, str2) == 0) { }  // ПРАВИЛЬНО
    

    strchr() — поиск символа

    char str[] = "Hello World";
    char *pos = strchr(str, 'o');
    if (pos != NULL) {
        printf("Найден 'o' на позиции %ld\n", pos - str);  // 4
    }
    

    strchr() возвращает указатель на первое вхождение символа или NULL, если не найден.

    Вычисляем позицию как pos - str (арифметика указателей).

    strstr() — поиск подстроки

    char str[] = "Hello World";
    char *pos = strstr(str, "World");
    if (pos != NULL) {
        printf("Найдена подстрока на позиции %ld\n", pos - str);  // 6
    }
    

    Аналогично strchr(), но ищет не один символ, а всю подстроку.

    strdup() — дублирование строки

    char str[] = "Hello";
    char *copy = strdup(str);  // Динамическое выделение + копирование
    printf("%s\n", copy);
    free(copy);  // Не забыть!
    

    strdup() эквивалентен:

    char *copy = malloc(strlen(str) + 1);
    strcpy(copy, str);
    

    Важно: strdup() выделяет память, которую вы должны освободить.

    strtok() — разбор строки на токены

    Разделяет строку по разделителям:

    char str[] = "apple,banana,orange";
    char *token = strtok(str, ",");
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok(NULL, ",");  // NULL = продолжить с предыдущей строки
    }
    

    Вывод:

    apple
    banana
    orange
    

    Внимание: strtok() модифицирует исходную строку (вставляет \0 на месте разделителей). Если вам нужна исходная строка, сделайте копию:

    char str[] = "apple,banana,orange";
    char *copy = strdup(str);
    char *token = strtok(copy, ",");
    // ... использование ...
    free(copy);
    

    Работа с динамическими строками

    Чтение строки от пользователя

    НЕПРАВИЛЬНО (уязвиво):

    char name[10];
    scanf("%s", name);  // Если пользователь введёт "Alexander", ПЕРЕПОЛНЕНИЕ!
    

    ПРАВИЛЬНО:

    char name[50];
    fgets(name, sizeof(name), stdin);  // Максимум 49 символов
    // Удалить символ новой строки
    if (name[strlen(name) - 1] == '\n') {
        name[strlen(name) - 1] = '\0';
    }
    

    ЛУЧШЕ (динамическое):

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    char *read_string(void) {
        char buffer[256];
        if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
            return NULL;
        }
        
        // Удалить \n
        buffer[strcspn(buffer, "\n")] = 0;
        
        // Скопировать в динамическую память
        return strdup(buffer);
    }
    
    int main() {
        printf("Введите имя: ");
        char *name = read_string();
        
        if (name != NULL) {
            printf("Привет, %s!\n", name);
            free(name);
        }
        return 0;
    }
    

    Конкатенация без переполнения

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char result[100] = "";
        
        snprintf(result, sizeof(result), "%s %s %d",
                 "Hello", "World", 2025);
        
        printf("%s\n", result);  // Hello World 2025
        return 0;
    }
    

    snprintf() — это самый безопасный способ форматирования и конкатенации строк.


    Строки и указатели

    Массив строк

    char *names[] = {
        "Alice",
        "Bob",
        "Charlie"
    };
    
    for (int i = 0; i < 3; i++) {
        printf("%s\n", names[i]);
    }
    

    Это массив указателей, каждый указывает на строковый литерал.

    Нельзя менять эти строки, так как они в read-only памяти:

    names[0][0] = 'X';  // КРАХ
    

    Если нужно менять: используйте массив массивов:

    char names[][20] = {
        "Alice",
        "Bob",
        "Charlie"
    };
    
    names[0][0] = 'X';  // OK, теперь names[0] = "Xlice"
    

    Обход строки по указателю

    char str[] = "Hello";
    char *ptr = str;
    
    while (*ptr != '\0') {
        printf("%c ", *ptr);
        ptr++;
    }
    // Вывод: H e l l o
    

    Это эквивалентно:

    for (char *ptr = str; *ptr; ptr++) {
        printf("%c ", *ptr);
    }
    

    Преобразования типов

    atoi() — строка в целое число

    char str[] = "123";
    int num = atoi(str);
    printf("%d\n", num);  // 123
    

    atof() — строка в float

    char str[] = "3.14";
    double num = atof(str);
    printf("%.2f\n", num);  // 3.14
    

    strtol() и strtof() — с проверкой ошибок

    #include <stdlib.h>
    
    char str[] = "123abc";
    char *endptr;
    long num = strtol(str, &endptr, 10);
    
    printf("Число: %ld\n", num);        // 123
    printf("Остаток: %s\n", endptr);    // abc
    

    strtol() правильнее, так как возвращает информацию об ошибке.


    Практический пример: парсер CSV

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAX_LINE 1000
    #define MAX_FIELDS 10
    
    int parse_csv_line(char *line, char *fields[], int max_fields) {
        int count = 0;
        char *copy = strdup(line);
        char *token = strtok(copy, ",");
        
        while (token != NULL && count < max_fields) {
            // Удалить пробелы
            while (*token == ' ') token++;
            fields[count++] = strdup(token);
            token = strtok(NULL, ",");
        }
        
        free(copy);
        return count;
    }
    
    int main() {
        char line[] = "Alice, 25, New York, Engineer";
        char *fields[MAX_FIELDS];
        
        int count = parse_csv_line(line, fields, MAX_FIELDS);
        
        for (int i = 0; i < count; i++) {
            printf("Field %d: [%s]\n", i, fields[i]);
            free(fields[i]);
        }
        
        return 0;
    }
    

    Вывод:

    Field 0: [Alice]
    Field 1: [25]
    Field 2: [New York]
    Field 3: [Engineer]
    

    Частые ошибки

    Ошибка 1: забыть место для терминатора

    char str[5] = "Hello";  // Нужно 6 байт (5 символов + \0), выделили 5
    // БУФЕРНОЕ ПЕРЕПОЛНЕНИЕ
    

    Исправление:

    char str[6] = "Hello";  // Правильно
    

    Ошибка 2: использовать strcpy без проверки размера

    char dest[10];
    char src[] = "This is a very long string";
    strcpy(dest, src);  // ПЕРЕПОЛНЕНИЕ
    

    Исправление:

    strncpy(dest, src, sizeof(dest) - 1);
    dest[sizeof(dest) - 1] = '\0';
    

    Ошибка 3: сравнивать строки с ==

    char *str1 = "Hello";
    char *str2 = "Hello";
    if (str1 == str2) { }  // Может быть неправильным!
    if (strcmp(str1, str2) == 0) { }  // Правильно
    

    Ошибка 4: забыть free для strdup

    char *str = strdup("Hello");
    // Использование
    free(str);  // ОБЯЗАТЕЛЬНО!
    

    Ошибка 5: стоковый буфер для возврата

    char *bad_function() {
        char str[] = "Hello";  // Локальная переменная
        return str;  // Возвращаем адрес стека — НЕПРАВИЛЬНО
    }
    
    int main() {
        char *str = bad_function();
        printf("%s\n", str);  // Мусор или крах
    }
    

    Исправление:

    char *good_function() {
        char *str = malloc(50);
        strcpy(str, "Hello");
        return str;  // Вызывающий должен free
    }
    

    Лучшие практики

    1. Всегда проверяйте размер буфера:

      strncpy(dest, src, sizeof(dest) - 1);
      dest[sizeof(dest) - 1] = '\0';
      
    2. Используйте snprintf() вместо sprintf():

      snprintf(buffer, sizeof(buffer), "%s: %d", name, age);
      
    3. Используйте fgets() вместо gets() или scanf("%s"):

      fgets(buffer, sizeof(buffer), stdin);
      
    4. Проверяйте возвращаемые значения:

      char *token = strtok(str, " ");
      if (token != NULL) { }
      
    5. Освобождайте динамические строки:

      char *str = strdup("Hello");
      free(str);
      str = NULL;
      
    6. Документируйте правила памяти:

      // Возвращает выделенную динамически строку (вызывающий должен free)
      char *create_greeting(const char *name) { }
      
    7. Для сложных манипуляций используйте вспомогательные функции:

      // Вместо прямого strtok, инкапсулируйте логику
      char **split_string(const char *str, const char *delim, int *count) { }
      

    Заключение

    Работа со строками в C требует дисциплины и внимания к деталям. Главное правило: всегда знайте размер вашего буфера и проверяйте границы.

    Основные инструменты:

    • strlen() — длина
    • strcpy() / strncpy() — копирование (используйте strncpy())
    • strcat() / strncat() — конкатенация (используйте strncat())
    • strcmp() — сравнение
    • strchr() / strstr() — поиск
    • strtok() — разбор на токены
    • snprintf() — безопасное форматирование

    Овладев этими функциями и избегая ошибок, вы сможете писать надёжный код без утечек памяти и переполнений буферов.

    1 ответ Последний ответ
    1

    Здравствуйте! Похоже, вас заинтересовал этот пост, но у вас ещё нет аккаунта.

    Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.

    С вашими комментариями этот пост мог бы стать ещё лучше 💗

    Зарегистрироваться Войти

    • locolizatorL

      Чертежная документация в AutoCAD: оформление по ГОСТ - полное руководство

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу autocad гост ескд
      1
      1
      0 Голоса
      1 Сообщения
      202 Просмотры
      Нет ответов
    • kirilljsxK

      Система ЧПУ: устройство и принцип работы станка с числовым управлением

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу чпу устройство принцип работы
      1
      0 Голоса
      1 Сообщения
      20 Просмотры
      Нет ответов
    • kirilljsxK

      Режимы работы Siemens Sinumerik: JOG, MDA, AUTO для ЧПУ станков

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу sinumerik режимы чпу jog auto
      1
      0 Голоса
      1 Сообщения
      42 Просмотры
      Нет ответов
    • kirilljsxK

      Создание миллиметровки в Компас 3D: пошаговое руководство для чертежей

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу компас 3d миллиметровка эскиз
      1
      0 Голоса
      1 Сообщения
      94 Просмотры
      Нет ответов
    • kirilljsxK

      Моделирование сечений и поверхностей в Компас-3D: пошаговое руководство

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу компас-3d сечения поверхности
      1
      0 Голоса
      1 Сообщения
      97 Просмотры
      Нет ответов
    • kirilljsxK

      Прямое моделирование в CAD: принципы работы и практическое использование

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу прямое моделирование cad parasolid
      1
      0 Голоса
      1 Сообщения
      12 Просмотры
      Нет ответов
    • LizaL

      LMS для быстрого обучения сварщиков и операторов ЧПУ: эффективный ввод в работу

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу lms чпу сварщики
      1
      0 Голоса
      1 Сообщения
      13 Просмотры
      Нет ответов
    • TimT

      Указатели в C и работа с памятью: Полное руководство

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу
      1
      1 Голоса
      1 Сообщения
      79 Просмотры
      Нет ответов

    Категории

    • Главная
    • Новости
    • Объявления
    • ПО и ЧПУ
    • Обсуждение

    Контакты

    • Сотрудничество
    • forum@investsteel.ru

    © 2022 - 2026 InvestSteel, Inc. Все права защищены.

    Политика конфиденциальности
    • Войти

    • Нет учётной записи? Зарегистрироваться

    • Войдите или зарегистрируйтесь для поиска.
    • Первое сообщение
      Последнее сообщение
    0
    • Лента
    • Популярные
    • Последние
    • Теги
    • Пользователи
    • Сотрудничество