Это довольно длинный ответ, не стесняйтесь переходить к концу для примера кода.
Прежде всего, инициализируя массив char
с неопределенной длиной, вы делаете этомассив имеет длину 1 (содержит только пустую строку). Ключевой проблемой здесь является то, что массивы в C имеют фиксированный размер, поэтому имя не будет увеличиваться.
Во-вторых, спецификатор формата %c
заставляет scanf
когда-либо читать только один байт. Это означает, что даже если бы вы создали больший массив, вы все равно читали бы в него только один байт.
Параметр, который вы передаете scanf
, ошибочен, но случайно работает - вы передаетеуказатель на массив, когда он ожидает указатель на char
. Это работает, потому что указатель на массив указывает на первый элемент массива. К счастью, это легко исправить, массив типа может быть передан в функцию, ожидающую указатель на этот тип - он, как говорят, «распадается» на указатель. Таким образом, вы могли бы просто передать name
.
В результате этих двух действий у вас теперь есть ситуация, когда name
имеет длину 1, и вы прочитали ровно один байт. Следующая проблема - sizeof(name)/sizeof(char)
- она всегда будет равна 1 в вашей программе. sizeof char
определяется как всегда равное 1, поэтому использование его в качестве делителя не дает никакого эффекта, и мы уже знаем, что sizeof name
равно 1. Это означает, что ваш цикл for будет когда-либо читать только один байт из массива. По той же самой причине n
равно 1. Это само по себе не ошибочно, скорее всего, это не то, что вы ожидали.
Решение этой проблемы может быть выполнено несколькими способами,но я покажу один. Прежде всего, вы не хотите инициализировать name
, как вы это делаете, потому что он всегда создает массив размером 1. Вместо этого вы хотите вручную указать больший размер для массива, например, 100 байтов (из которых последнийодин из них будет посвящен завершающему нулевому байту).
char name[100];
/* You might want to zero out the array too by eg. using memset. It's not
necessary in this case, but arrays are allowed to contain anything unless
and until you replace their contents.
Parameters are target, byte to fill it with, and amount of bytes to fill */
memset(name, 0, sizeof(name));
Во-вторых, вам совсем не обязательно использовать scanf
, если вы читаете только строку байтов из стандартного ввода вместоболее сложная отформатированная строка. Вы могли бы, например. используйте fgets
, чтобы прочитать всю строку из стандартного ввода, хотя это также включает символ новой строки, который нам придется удалить.
/* The parameters are target to write to, bytes to write, and file to read from.
fgets writes a null terminator automatically after the string, so we will
read at most sizeof(name) - 1 bytes.
*/
fgets(name, sizeof(name), stdin);
Теперь вы прочитали имя в память. Но размер name
массива не изменился, поэтому, если бы вы использовали остальную часть кода как есть, вы бы получили много сообщений, говорящих The ASCII value of the letter is : 0
. Чтобы получить значимую длину строки, мы будем использовать strlen
.
ПРИМЕЧАНИЕ: strlen
, как правило, небезопасно для использования с произвольными строками, которые могут не заканчиваться должным образом нулем, так как будут продолжать читать доон находит нулевой байт, но мы получаем только портативную версию с проверкой границ strnlen_s
в C11. В этом случае мы также знаем, что строка заканчивается нулем, потому что fgets
имеет дело с этим.
/* size_t is a large, unsigned integer type big enough to contain the
theoretical maximum size of an object, so size functions often return
size_t.
strlen counts the amount of bytes before the first null (0) byte */
size_t n = strlen(name);
Теперь, когда у нас есть длина строки, мы можем проверить, является ли последний байтсимвол новой строки и удалите его, если это так.
/* Assuming every line ends with a newline, we can simply zero out the last
byte if it's '\n' */
if (name[n - 1] == '\n') {
name[n - 1] = '\0';
/* The string is now 1 byte shorter, because we removed the newline.
We don't need to calculate strlen again, we can just do it manually. */
--n;
}
Цикл выглядит очень похоже, так как с самого начала было в основном нормально. В основном мы хотим избежать проблем, которые могут возникнуть при сравнении подписанного int
и беззнакового size_t
, поэтому мы также сделаем i
типом size_t
.
for (size_t i = 0; i < n; i++) {
int e = name[i];
printf("The ASCII value of the letter %c is : %d \n", name[i], e);
}
Помещениевсе вместе мы получаем
#include <stdio.h>
#include <string.h>
int main() {
char name[100];
memset(name, 0, sizeof(name));
printf("Enter a name : \n");
fgets(name, sizeof(name), stdin);
size_t n = strlen(name);
if (n > 0 && name[n - 1] == '\n') {
name[n - 1] = '\0';
--n;
}
for (size_t i = 0; i < n; i++){
int e = name[i];
printf("The ASCII value of the letter %c is : %d \n", name[i], e);
}
/* To correctly print a size_t, use %zu */
printf("%zu\n", n);
/* In C99 main implicitly returns 0 if you don't add a return value
yourself, but it's a good habit to remember to return from functions. */
return 0;
}
, который должен работать почти так же, как ожидалось.
Дополнительные примечания:
Этот код должен бытьдействительный C99, но я считаю, что это не действительный C89. Если вам нужно написать по старому стандарту, есть несколько вещей, которые вам нужно сделать по-другому. К счастью, ваш компилятор должен предупредить вас об этих проблемах, если вы скажете ему, какой стандарт вы хотите использовать. C99, вероятно, по умолчанию в наши дни, но старый код все еще существует.
Чтение строк в буферы фиксированного размера выглядит немного негибко, поэтому в реальной ситуации вам может понадобиться способ динамического увеличения размера буфера по мере необходимости. Это, вероятно, потребует от вас использования функций ручного управления памятью, таких как malloc
и realloc
, которые не являются особенно сложными, но при этом следует проявлять большую осторожность, чтобы избежать таких проблем, как утечки памяти.
Не гарантируется, что строки, которые вы читаете, находятся в какой-либо конкретной кодировке, и строки C не очень подходят для обработки текста, который не закодирован в однобайтовой кодировке. Существует поддержка "строк широких символов", но, вероятно, чаще вы будете обрабатывать строки char
, содержащие UTF-8, где одна кодовая точка может быть несколькими байтами и даже не может представлять отдельную букву как таковую. В более универсальной программе вы должны помнить об этом.