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


как вы можете видеть из фрагмента кода ниже, я объявил один 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;
}
11 64

11 ответов:

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

ознакомиться с Часто задаваемые вопросы: как я могу узнать, сколько аргументов функция была фактически вызвана?


именно по этой причине должен быть хотя бы один фиксированный аргумент для определения числа и, возможно, типов переменных аргументов. И этот аргумент ( стандарт называет это 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) отслеживают типы аргументов, по крайней мере, в некоторых ситуациях. Но язык так не устроен.

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

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

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

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

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

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

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

возможности для передачи такого рода информации многочисленны. Например, в POSIX, the 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 после того, как он использовал все аргументы. Наши my_exec функция должна быть вызвана следующим образом:

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

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

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

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

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

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

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

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

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

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

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