Почему я должен каждый раз указывать тип данных в C?

Как вы можете видеть из приведенного ниже фрагмента кода, я объявил одну переменную char и одну переменную int. Когда код компилируется, он должен идентифицировать типы данных переменных str и i.

Почему мне нужно снова указать во время сканирования моей переменной, что это строка или целочисленная переменная, указав %s или %d на scanf? Разве компилятор не достаточно зрелый, чтобы определить, когда я объявлял свои переменные?

#include <stdio.h>

int main ()
{
  char str [80];
  int i;

  printf ("Enter your family name: ");
  scanf ("%s",str);  
  printf ("Enter your age: ");
  scanf ("%d",&i);

  return 0;
}

Ответ 1

Поскольку нет портативного способа для переменных аргументов, таких как scanf и printf, чтобы знать типы аргументов переменной, даже не сколько аргументов передано.

См. C FAQ: Как узнать, сколько аргументов вызывала функция с помощью?


Вот почему должен быть хотя бы один фиксированный аргумент для определения числа и, возможно, типов переменных аргументов. И этот аргумент (стандарт называет его parmN, см. C11 (ISO/IEC 9899: 201x) §7.16 Переменные аргументы) играет эту особую роль и будет передан к макрокоманде va_start. Другими словами, вы не можете иметь функцию с прототипом, как это, в стандартном C:

void foo(...);

Ответ 2

Причина, по которой компилятор не может предоставить необходимую информацию, просто потому, что компилятор здесь не участвует. Прототип функций не определяет типы, поскольку эти функции имеют переменные типы. Таким образом, фактические типы данных не определяются во время компиляции, а во время выполнения. Затем функция берет один аргумент из стека, после другого. Эти значения не имеют никакой информации о типе, связанной с ней, поэтому единственный способ, который функция знает, как интерпретировать данные, - использовать информацию, предоставленную вызывающим абонентом, которая является строкой формата.

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

В С++ вы можете использовать перегрузку оператора, но это совершенно другой механизм. Потому что здесь компилятор выбирает соответствующую функцию, основанную на типах данных и доступной перегруженной функции.

Чтобы проиллюстрировать это, printf, при компиляции выглядит так:

 push value1
 ...
 push valueN
 push format_string
 call _printf

И прототипом printf является следующее:

int printf ( const char * format, ... );

Таким образом, информация типа не переносится, кроме того, что указано в строке формата.

Ответ 3

Компилятор может быть умным, но функции printf или scanf глупы - они не знают, каков тип параметра, который вы передаете для каждого вызова. Вот почему вам нужно передавать %s или %d каждый раз.

Ответ 4

printf не является встроенной функцией. Он не является частью языка C как таковой. Весь компилятор делает код для вызова printf, передавая все параметры. Теперь, поскольку C не обеспечивает reflection как механизм для определения информации о типе во время выполнения, программист должен явно предоставить необходимую информацию.

Ответ 5

Первый параметр - строка формата. Если вы печатаете десятичное число, это может выглядеть так:

  • "%d" (десятичное число)
  • "%5d" (десятичное число с шириной 5 с пробелами)
  • "%05d" (десятичное число с шириной 5 с нулями)
  • "%+d" (десятичное число, всегда со знаком)
  • "Value: %d\n" (некоторый контент до/после номера)

и т.д., см., например, Форматировать заполнители в Википедии, чтобы иметь представление о том, какие строки строк могут содержать.

Также здесь может быть несколько параметров:

"%s - %d" (строка, затем некоторое содержимое, затем число)

Ответ 6

Разве компилятор не созрел, чтобы определить, когда я объявил переменная?

Нет.

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

Кроме того, почему C и С++ остаются предпочтительными для вас, когда вы действительно заботитесь о быстрой, эффективной или близкой к металлу.

Ответ 7

scanf, поскольку прототип int scanf ( const char * format, ... ); говорит, что данные хранятся в соответствии с форматом параметров в местах, указанных дополнительными аргументами.

Это не связано с компилятором, это все о синтаксисе, определенном для scanf. Формат файла требуется, чтобы scanf знал о размере для резервирования для ввода данных.

Ответ 8

GCC (и, возможно, другие компиляторы C) отслеживают типы аргументов, по крайней мере в некоторых ситуациях. Но язык не разработан таким образом.

Функция printf - это обычная функция, которая принимает переменные аргументы. Переменные аргументы требуют какой-то схемы идентификации типа времени выполнения, но на языке C значения не содержат информацию о типе времени выполнения. (Конечно, программисты C могут создавать схемы ввода-вывода с использованием структур или трюков манипуляции с битами, но они не интегрированы в язык.)

Когда мы разработаем такую ​​функцию:

void foo(int a, int b, ...);

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

Например, если мы вызываем эту функцию следующим образом:

foo(1, 2, 3.0);
foo(1, 2, "abc");

нет способа, чтобы вызывающий мог различать случаи. В области передачи параметров есть только несколько бит, и мы не знаем, представляют ли они указатель на символьные данные или число с плавающей запятой.

Возможности для передачи этого типа информации многочисленны. Например, в POSIX семейство функций exec использует переменные аргументы, имеющие одинаковый тип, char *, а нулевой указатель используется для указания конца списка:

#include <stdarg.h>

void my_exec(char *progname, ...)
{
  va_list variable_args;
  va_start (variable_args, progname);

  for (;;) {
     char *arg = va_arg(variable_args, char *);
     if (arg == 0)
       break;
     /* process arg */
  }

  va_end(variable_args);
  /*...*/
}

Если вызывающий абонент забывает передать терминатор нулевого указателя, поведение будет undefined, потому что функция будет продолжать ссылаться на va_arg после того, как он уничтожит все аргументы. Наша функция my_exec должна быть вызвана следующим образом:

my_exec("foo", "bar", "xyzzy", (char *) 0);

Приведение на 0 требуется, потому что нет контекста для его интерпретации как константы нулевого указателя: компилятор не знает, что предполагаемый тип для этого аргумента является типом указателя. Кроме того, (void *) 0 неверен, потому что он будет просто передан как тип void *, а не char *, хотя эти два почти наверняка совместимы на двоичном уровне, поэтому он будет работать на практике. Общей ошибкой с этим типом функции exec является следующее:

my_exec("foo", "bar", "xyzzy", NULL);

где компилятор NULL определяется как 0 без кавычек (void *).

Другая возможная схема - потребовать, чтобы вызывающий абонент переместил число, которое указывает, сколько аргументов существует. Конечно, это число может быть неверным.

В случае printf строка формата описывает список аргументов. Функция анализирует его и извлекает аргументы соответствующим образом.

Как уже упоминалось ранее, некоторые компиляторы, в частности компилятор GNU C, могут анализировать строки форматирования во время компиляции и выполнять проверку статического типа по количеству и типам аргументов.

Однако обратите внимание, что строка формата может быть отличной от литерала и может быть вычислена при запуске время, которое невосприимчиво к схемам проверки типа. Фиктивный пример:

char *fmt_string = message_lookup(current_language, message_code);

/* no type checking from gcc in this case: fmt_string could have
   four conversion specifiers, or ones not matching the types of
   arg1, arg2, arg3, without generating any diagnostic. */
snprintf(buffer, sizeof buffer, fmt_string, arg1, arg2, arg3);

Ответ 9

Это потому, что это единственный способ сообщить функциям (например, printf scanf), какой тип передаваемого вами значения. например -

int main()
{
    int i=22;
    printf("%c",i);
    return 0;
}

этот код будет печатать символ не целочисленным 22. потому что вы сказали функции printf обрабатывать переменную как char.

Ответ 10

printf и scanf - это функции ввода/вывода, которые разработаны и определены таким образом, чтобы получить контрольную строку и список аргументов.

Функции не знают тип передаваемого ему параметра, и компилятор также не может передать ему эту информацию.

Ответ 11

Потому что в printf вы не указываете тип данных, вы указываете формат данных. Это важное различие на любом языке, и это вдвойне важно в C.

Когда вы просматриваете строку с помощью %s, вы не говорите: "Разбирайте строковый ввод для моей строковой переменной". Вы не можете сказать это в C, потому что C не имеет строкового типа. Ближайшей вещью C к строковой переменной является массив символов фиксированного размера, который содержит символы, представляющие строку, с завершением строки, обозначенной нулевым символом. Итак, что вы на самом деле говорите: "Здесь массив для хранения строки, я обещаю, что он достаточно большой для ввода строки, которую я хочу, чтобы вы анализировали".

Primitive? Конечно. C был изобретен более 40 лет назад, когда типичная машина имела не более 64 КБ оперативной памяти. В такой среде сохранение ОЗУ имело более высокий приоритет, чем сложная обработка строк.

Тем не менее, сканер %s сохраняется в более сложных средах программирования, где есть строковые типы данных. Потому что это касается сканирования, а не ввода.