Строки в C: подводные камни и техника
В следующем месяце я буду тренировать команду ACM (перейти к рисунку), и пришло время поговорить о строках в C. Кроме обсуждения стандартных lib, strcpy
, strcmp
и т.д., я бы чтобы дать им некоторые подсказки (что-то вроде str[0] is equivalent to *str
и тому подобное).
Знаете ли вы какие-либо списки (например, чит-листы) или свой собственный опыт в этом вопросе?
Я уже знаю книги для конкурса ACM (которые хороши, см., в частности, это), но я после трюки торговли.
Спасибо.
Изменить: Спасибо всем. Я согласен с большинством проголосовавших ответов и должным образом поддержал других, которые, на мой взгляд, актуальны. Я ожидаю сделать здесь резюме (например, здесь, как можно скорее). Сейчас у меня достаточно материала, и я уверен, что это значительно улучшило сеанс на строках. Еще раз спасибо.
Ответы
Ответ 1
Это очевидно, но я думаю, важно знать, что строки nothing больше, чем массив байтов, разделенных нулевым байтом.
Строки C не так уж удобны для пользователя, как вы, вероятно, знаете.
- Запись нулевого байта где-то в строке будет обрезать его.
- Выход из пределов вообще заканчивается плохо.
- Никогда, никогда не используйте strcpy, strcmp, strcat и т.д., вместо этого используйте их безопасные варианты: strncmp, strncat, strndup,...
- Избегайте strncpy. strncpy не всегда будет нулевой разграничить вашу строку! Если исходная строка не подходит в целевом буфере, она обрезает строку, но она не будет писать нулевой байт в конце буфера. Кроме того, даже если исходный буфер намного меньше места назначения, strncpy все равно перезапишет весь буфер нулями. Я лично использую strlcpy.
- Не используйте printf (string), вместо этого используйте printf ( "% s", string). Попробуйте подумать о последствиях, если пользователь помещает% d в строку.
- Вы не можете сравнивать строки с
if( s1 == s2 )
doStuff(s1);
Вы должны сравнить каждый символ в строке. Используйте strcmp или лучше strncmp.
if( strncmp( s1, s2, BUFFER_SIZE ) == 0 )
doStuff(s1);
Ответ 2
Нарушение strlen() значительно ухудшит производительность.
for( int i = 0; i < strlen( string ); i++ ) {
processChar( string[i] );
}
будет иметь по меньшей мере O (n 2) временную сложность, тогда как
int length = strlen( string );
for( int i = 0; i < length; i++ ) {
processChar( string[i] );
}
будет иметь как минимум O (n) временную сложность. Это не так очевидно для людей, которые не успели подумать об этом.
Ответ 3
Следующие функции могут использоваться для реализации не мутирующего strtok
:
strcspn(string, delimiters)
strspn(string, delimiters)
Первый находит первый символ в наборе разделителей, в который вы проходите. Второй находит первый символ не в наборе разделителей, которые вы передаете.
Я предпочитаю их strpbrk
, поскольку они возвращают длину строки, если они не могут совпадать.
Ответ 4
str[0]
эквивалентен 0[str]
, или более общ str[i]
- i[str]
, а i[str]
- *(str + i)
.
Н.Б.
это не относится к строкам, но работает также для массивов C
Ответ 5
Варианты str
n
*
в stdlib необязательно нулевые завершают строку назначения.
В качестве примера: из документации MSDN на strncpy
:
Функция strncpy копирует начальные символы счета strSource to strDest и возвращает strDest. Если количество меньше или равно длина strSource, нулевой символ автоматически не добавляется к скопированная строка. Если счет больше чем длина strSource, строка назначения заполняется нулевой символов до длины.
Ответ 6
strtok
не является потокобезопасным, поскольку он использует изменяемый частный буфер для хранения данных между вызовами; вы также не можете чередовать или аннулировать вызовы strtok
.
Более полезной альтернативой является strtok_r
, использовать ее всякий раз, когда вы можете.
Ответ 7
путать strlen()
с sizeof()
при использовании строки:
char *p = "hello!!";
strlen(p) != sizeof(p)
sizeof(p)
дает во время компиляции размер указателя (4 или 8 байтов), тогда как strlen(p)
подсчитывает во время выполнения длину массива char с нулевым завершением (7 в этом примере).
Ответ 8
kmm уже имеет хороший список. Вот с чем я столкнулся, когда начал писать код C.
-
Строковые литералы имеют собственную секцию памяти и всегда доступны. Следовательно, они могут быть, например, возвращаемым значением функции.
-
Управление памятью строк, в частности, с библиотекой высокого уровня (не libc). Кто отвечает за освобождение строки, если она возвращается функцией или передается функции?
-
Когда должно быть "const char *" и когда используется "char *". И что это говорит мне, если функция возвращает "const char *".
Все эти вопросы не так уж трудно узнать, но трудно понять, не научитесь их.
Ответ 9
Я обнаружил, что метод char buff[0]
был невероятно полезен.
Рассмотрим:
struct foo {
int x;
char * payload;
};
против
struct foo {
int x;
char payload[0];
};
см. fooobar.com/questions/57335/...
См. ссылку на последствия и варианты
Ответ 10
Я бы обсуждал, когда и когда не использовать strcpy
и strncpy
, и что может пойти не так:
char *strncpy(char* destination, const char* source, size_t n);
char *strcpy(char* destination, const char* source );
Я бы также упомянул возвращаемые значения строковых функций ansi C stdlib. Например, спросите: "делает ли это, если инструкция проходит или терпит неудачу?"
if (stricmp("StrInG 1", "string 1")==0)
{
.
.
.
}
Ответ 11
возможно, вы могли бы проиллюстрировать значение sentinel '\ 0' со следующим примером
char * a = "hello\0 world";
char b [100];
зЬгср (Ь, а);
Е (б);
У меня когда-то мои пальцы горели, когда я в своем рвении использовал stcpy() для копирования двоичных данных. Он работал большую часть времени, но иногда таинственно. Тайна была обнаружена, когда я понял, что двоичный вход иногда содержит нулевой байт, и strcpy() завершается там.
Ответ 12
Вы можете указать индексированную адресацию.
Адрес элементов - это базовый адрес + индекс * размер элемента
Ответ 13
Общей ошибкой является:
char *p;
snprintf(p, 3, "%d", 42);
он работает до тех пор, пока вы не будете использовать до sizeof(p)
байт. Затем происходят забавные вещи (добро пожаловать в джунгли).
Explaination
с char * p вы выделяете пространство для удержания указателя (sizeof(void*)
bytes) в стеке. Правильная вещь здесь - выделить буфер или просто указать размер указателя во время компиляции:
char buf[12];
char *p = buf;
snprintf(p, sizeof(buf), "%d", 42);
Ответ 14
Я бы указал на недостатки производительности чрезмерной зависимости от встроенных строковых функций.
char* triple(char* source)
{
int n=strlen(source);
char* dest=malloc(n*3+1);
strcpy(dest,src);
strcat(dest,src);
strcat(dest,src);
return dest;
}
Ответ 15
Указатели и массивы, имеющие аналогичный синтаксис, совсем не совпадают. Дано:
char a [100];
char * p = a;
Для массива a нет указателя, хранящегося в любом месте. sizeof (a)!= sizeof (p), для массива - размер блока памяти, для указателя - размер указателя. Это становится важным, если вы используете что-то вроде: sizeof (a)/sizeof (a [0]). Кроме того, вы не можете ++ a, и вы можете сделать указатель "const" указателем на символы "const" , но массив может быть только символами "const" , и в этом случае вы должны сначала его инициализировать. etc etc etc
Ответ 16
Если возможно, используйте strlcpy (вместо strncpy) и strlcat.
Еще лучше, чтобы сделать жизнь более безопасной, вы можете использовать макрос, например:
#define strlcpy_sz(dst, src) (strlcpy(dst, src, sizeof(dst)))