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

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

почему мне нужно снова сказать во время сканирования моей переменной, что это строка или целочисленная переменная, указав %s или %d to 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;
}

11 ответов


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

см. C FAQ:как я могу узнать, сколько аргументов было фактически вызвано функцией?


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

void foo(...);

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

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

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

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

 push value1
 ...
 push valueN
 push format_string
 call _printf

и прототип printf это:

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

таким образом, нет никакой информации о типе, за исключением того, что предусмотрено в строке формата.


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


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


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

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

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

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

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


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

нет.

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

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


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

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


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

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

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

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

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

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

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

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

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

#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);
  /*...*/
}

если абонент забывает передать Терминатор нулевого указателя, поведение будет неопределенным, потому что функция будет продолжать вызывать va_arg после того, как он использовал все аргументы. Наши!--10--> функция должна быть вызвана следующим образом:

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

бросок на 0 требуется, потому что нет контекста для его интерпретации как константы нулевого указателя: компилятор понятия не имеет, что предполагаемый тип для этого аргумента является типом указателя. Более того!--12--> неверно, потому что это будет просто передан как void * тип, а не char *, хотя эти двое почти наверняка совместимы на двоичном уровне, поэтому он будет работать на практике. Распространенная ошибка с этим типом


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

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

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


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

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


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

при сканировании в строке с помощью%s, вы не говорите: "проанализируйте строковый ввод для моей строковой переменной."Вы не могу скажите это в C, потому что C не имеет строкового типа. Самое близкое, что C имеет к строковой переменной, - это массив символов фиксированного размера, который содержит символы представляет строку, конец которой обозначается символом null. Итак, вы действительно говорите: "вот массив для хранения строки, Я обещаю, что он достаточно велик для ввода строки, которую я хочу, чтобы вы проанализировали."

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

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