Что значит main в c. Как работает метод main() в C? Как создать свою функцию

Для чего нужны функции в C?

Функции в Си применяются для выполнения определённых действий в рамках общей программы. Программист сам решает какие именно действия вывести в функции. Особенно удобно применять функции для многократно повторяющихся действий.

Простой пример функции в Cи

Пример функции в Cи:

#include #include int main(void) { puts("Functions in C"); return EXIT_SUCCESS; }

Это очень простая программа на Си. Она просто выводит строку «Functions in C». В программе имеется единственная функция под названием main. Рассмотрим эту функцию подробно. В заголовке функции, т.е. в строке

int – это тип возвращаемого функцией значения;

main - это имя функции;

(void) - это перечень аргументов функции. Слово void указывает, что у данной функции нет аргументов;

return – это оператор, который завершает выполнение функции и возвращает результат работы функции в точку вызова этой функции;

EXIT_SUCCESS - это значение, равное нулю. Оно определено в файле stdlib.h;

часть функции после заголовка, заключенная в фигурные скобки

{
puts("Functions in C");
return EXIT_SUCCESS;
}

называют телом функции.

Итак, когда мы работаем с функцией надо указать имя функции, у нас это main, тип возвращаемого функцией значения, у нас это int, дать перечень аргументов в круглых скобках после имени функции, у нас нет аргументов, поэтому пишем void, в теле функции выполнить какие-то действия (ради них и создавалась функция) и вернуть результат работы функции оператором return. Вот основное, что нужно знать про функции в C.

Как из одной функции в Cи вызвать другую функцию?

Рассмотрим пример вызова функций в Си:

/* Author: @author Subbotin B.P..h> #include int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; }

Запускаем на выполнение и получаем:

В этом примере создана функция sum, которая складывает два целых числа и возвращает результат. Разберём подробно устройство этой функции.

Заголовок функции sum:

int sum(int a, int b)

здесь int - это тип возвращаемого функцией значения;

sum - это имя функции;

(int a, int b) - в круглых скобках после имени функции дан перечень её аргументов: первый аргумент int a, второй аргумент int b. Имена аргументов являются формальными, т.е. при вызове функции мы не обязаны отправлять в эту функцию в качестве аргументов значения перемнных с именами a и b. В функции main мы вызываем функцию sum так: sum(d, e);. Но важно, чтоб переданные в функцию аргументы совпадали по типу с объявленными в функции.

В теле функции sum, т.е. внутри фигурных скобок после заголовка функции, мы создаем локальную переменную int c, присваиваем ей значение суммы a плюс b и возвращаем её в качестве результата работы функции опрератором return.

Теперь посмотрим как функция sum вызывается из функции main.

Вот функция main:

Int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; }

Сначала мы создаём две переменных типа int

Int d = 1; int e = 2;

их мы передадим в функцию sum в качестве значений аргументов.

int f = sum(d, e);

её значением будет результат работы функции sum, т.е. мы вызываем функцию sum, которая возвратит значение типа int, его-то мы и присваиваем переменной f. В качестве аргументов передаём d и f. Но в заголовке функции sum

int sum(int a, int b)

аргументы называются a и b, почему тогда мы передаем d и f? Потому что в заголовке функций пишут формальные аргументы, т.е. НЕ важны названия аргументов, а важны их типы. У функции sum оба аргумента имеют тип int, значит при вызове этой функции надо передать два аргумента типа int с любыми названиями.

Ещё одна тонкость. Функция должна быть объявлена до места её первого вызова. В нашем примере так и было: сначала объявлена функция sum, а уж после мы вызываем её из функции main. Если функция объявляется после места её вызова, то следует использовать прототип функции.

Прототип функции в Си

Рассмотрим пример функциив Си:

/* Author: @author Subbotin B.P..h> #include int sum(int a, int b); int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; } int sum(int a, int b) { int c = 0; c = a + b; return c; }

В этом примере функция sum определена ниже места её вызова в функции main. В таком случае надо использовать прототип функции sum. Прототип у нас объявлен выше функции main:

int sum(int a, int b);

Прототип - это заголовок функции, который завершается точкой с запятой. Прототип - это объявление функции, которая будет ниже определена. Именно так у нас и сделано: мы объявили прототип функции

int f = sum(d, e);

а ниже функции main определяем функцию sum, которая предварительно была объявлена в прототипе:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

Чем объявление функции в Си отличается от определения функции в Си?

Когда мы пишем прототип функции, например так:

int sum(int a, int b);

то мы объявляем функцию.

А когда мы реализуем функцию, т.е. записываем не только заголовок, но и тело функции, например:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

то мы определяем функцию.

Оператор return

Оператор return завершает работу функции в C и возвращает результат её работы в точку вызова. Пример:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

Эту функцию можно упростить:

Int sum(int a, int b) { return a + b; }

здесь оператор return вернёт значение суммы a + b.

Операторов return в одной функции может быть несколько. Пример:

Int sum(int a, int b) { if(a > 2) { return 0;// Первый случай; } if(b < 0) { return 0;// Второй случай; } return a + b; }

Если в примере значение аргумента a окажется больше двух, то функция вернет ноль (первый случай) и всё, что ниже комментария «// Первый случай;» выполнятся не будет. Если a будет меньше двух, но b будет меньше нуля, то функция завершит свою работу и всё, что ниже комментария «// Второй случай;» выполнятся не будет.

И только если оба предыдущих условия не выполняются, то выполнение программы дойдёт до последнего оператора return и будет возвращена сумма a + b.

Передача аргументов функции по значению

Аргументы можно передавать в функцию C по значению. Пример:

/* Author: @author Subbotin B.P..h> #include int sum(int a) { return a += 5; } int main(void) { puts("Functions in C"); int d = 10; printf("sum = %d\n", sum(d)); printf("d = %d", d); return EXIT_SUCCESS; }

В примере, в функции main, создаём переменную int d = 10. Передаём по значению эту переменную в функцию sum(d). Внутри функции sum значение переменной увеличивается на 5. Но в функции main значение d не изменится, ведь она была передана по значению. Это означает, что было передано значение переменной, а не сама переменная. Об этом говорит и результат работы программы:

т.е. после возврата из функции sum значеие d не изменилось, тогда как внутри функции sum оно менялось.

Передача указателей функции Си

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

/* Author: @author Subbotin B.P..h> #include int sum(int *a) { return *a += 5; } int main(void) { puts("Functions in C"); int d = 10; printf("sum = %d\n", sum(&d)); printf("d = %d", d); return EXIT_SUCCESS; }

В этом варианте программы я перешел от передачи аргумента по значению к передаче указателя на переменную. Рассмотрим подробнее этот момент.

printf("sum = %d\n", sum(&d));

в функцию sum передается не значение переменной d, равное 10-ти, а адрес этой переменной, вот так:

Теперь посмотрим на функцию sum:

Int sum(int *a) { return *a += 5; }

Аргументом её является указатель на int. Мы знаем, что указатель - это переменная, значением которой является адрес какого-то объекта. Адрес переменной d отправляем в функцию sum:

Внутри sum указатель int *a разыменовывается. Это позволяет от указателя перейти к самой переменной, на которую и указывает наш указатель. А в нашем случае это переменная d, т.е. выражение

равносильно выражению

Результат: функция sum изменяет значение переменной d:

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

C/C++ в Eclipse

Все примеры для этой статьи я сделал в Eclipse. Как работать с C/C++ в Eclipse можно посмотреть . Если вы работаете в другой среде, то примеры и там будут работать.

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

Функция main.

Каждая программа на С и C++ должна иметь функцию main; причем ваше дело, где вы ее поместите. Некоторые программисты помещают ее в начале файла, некоторые в конце. Однако независимо от ее положения необходимо помнить следующее: Аргументы функции "main". Запускающая процедура Borland C++ посылает функции main три параметра (аргумента): argc, argv и env. - argc, целое, - это число аргументов командной строки, посылаемое функции main, - argv это массив указателей на строки (char * ). Под версией DOS 3.x и более поздними argv определяется как полный маршрут запускаемой программы. При работе под более ранними версиями DOS argv указывает на нулевую строку (""). argv указывает на первую после имени программы строку командной строки. argv указывает на вторую после имени программы строку командной строки. argv указывает на последний аргумент, посылаемый функции main. argv содержит NULL. - env также является массивом указателей на строки. Каждый элемент env содержит строку вида ENVVAR=значение. ENVVAR - это имя переменной среды, типа PATH или 87. <значение> это значение данной переменной окружения, например C:\DOS;C:\TOOLS (для PATH) или YES (для 87). Заметим, однако, что если вы описываете некоторые из этих аргументов, то вы должны описывать их в таком порядке: argc, argv, env. Например, допустимы следующие объявления аргументов: main() main(int argc) /* допустимо но не очень хорошо */ main(int argc, char *argv) main(int argc, char *argv, char *env) Объявление main(int argc) не очень удобно тем, что зная количество параметров, вы не имеете доступа к ним самим. Аргумент env всегда доступен через глобальную переменную environ. Смотрите описание переменной environ (в Главе 3) и функции putenv и getenv (в Главе 2). Параметры argc и argv также доступны через переменные_argc и _argv. Пример программы, использующей argc, argv и env. Это пример программы ARGS.EXE, которая демонстрирует простейший путь использования аргументов, посылаемых функции main. /* программа ARGS.C */ #include #include void main(int argc, char *argv, char *env) { int i; printf("Значение argc равно %d \n\n",argc); printf("В командной строке содержится %d параметров \n\n",argc); for (i=0; i<=argc; i++) printf(" argv[%d]: %s\n",i,argv[i]); printf("Среда содержит следующие строки:\n"); for (i=0; env[i] != NULL; i++) printf(" env[%d]: %s\n",i,env[i]); return 0; } Предположим, что вы запускаете программу ARGS.EXE со следующей командной строкой: C:> args first_arg "arg with blanks" 3 4 "last but one" stop! Заметим, что вы можете послать аргумент с пробелами, заключив его в двойные кавычки, как показано на примере "argument with blanks" и "last but one" в примере вызова программы. В результате работы программы вы получите примерно следующее: Значение argc равно 7 В командной строке содержится 7 параметров argv: c:\turboc\testargs.exe argv: first_arg argv: arg with blank argv: 3 argv: 4 argv: last but one argv: stop! Среда содержит следующие строки: env: COMSPEC=C:\COMMAND.COM env: PROMPT=$p $g env: PATH=C:\SPRINT;C:\DOS;C:\BC Максимальная общая длина командной строки, посылаемая функции main (включая пробелы и имя самой программы), не может превышать 128 символов; это ограничения DOS. Символы маскирования в командной строке Аргументы командной строки могут содержать символы маскирования. При этом они могут расширяться для всех имен файлов, которые совпадают с аргументом так, как это делается, например, с командой DOS copy. Для использования символов маскирования необходимо при связывании вашей программы редактором связей подсоединить к ней объектный файл WILDARGS.OBJ, который поставляется с Borland C++. Если файл WILDARGS.OBJ подсоединен к вашей программе, то вы можете в командной строке использовать аргументы типа "*.*". При этом имена всех файлов, подходящих к данной маске, заносятся в массив argv. Максимальный размер массива argv зависит только от объема динамической области памяти. Если под данную маску не нашлось подходящих файлов, то аргумент передается в том виде, в каком он был набран в командной строке. (Т.е. функции main передается строка, содержащая символы маскирования). Аргументы, заключенные в двойные кавычки ("..."), не расширяются. Пример. Следующие команды компилируют файл ARGS.C и связывают его с модулем WILDARGS.OBJ, а затем запускают получившуюся программу ARGS.EXE: bcc args wildarg.obj args C:\BORLANDC\INCLUDE\*.H "*.C" При запуске ARGS.EXE первый аргумент расширяется до имен всех файлов с расширением H в директории Borland C++ INCLUDE. Отметим, что все строки включают полный маршрут (к примеру C:\TC\INCLUDE\ALLOC.H). Аргумент *.C не расширяется, т.к. он заключен в кавычки. Если вы работаете в Интегрированном Окружении (BC.EXE), то вам просто нужно указать в меню проекта имя файла проекта, который должен содержать следующие строки: ARGS WILDARGS.OBJ Затем с помощью команд "Run/Arguments" следует установить параметры командной строки. Замечание. Если вы хотите, чтобы обработка символов маскирования происходила всегда, т.е. чтобы WILDARGS.OBJ автоматически подсоединялся редактором связей, вы должны модифицировать вашу стандартную библиотеку C?.LIB, добавив в нее файл WILDARGS.OBJ. Для этого удалите из библиотеки SETARGV и добавьте WILDARGS. Это можно сделать с помощью следующих команд (мы подразумеваем, что стандартные библиотеки и WILDARGS.OBJ содержатся в текущей директории): TLIB описана в главе 7 "Утилиты" документа "User"s Guide". tlib cs -setargv +wildargs tlib cc -setargv +wildargs tlib cm -setargv +wildargs tlib cl -setargv +wildargs tlib ch -setargv +wildargs Компиляция с использованием ключа -p (Соглашение по вызову языка Паскаль). Если вы компилируете вашу программу, используя соглашение по вызову языка Паскаль (детально описано в главе 9 "Interfacing with assembly languige", "Programmer"s Guide"), вы должны помнить, что функция main должна быть явно объявлена как функция С. Это можно сделать с помощью ключевого слова cdecl примерно так: cdecl main(int argc, char *argv, char *env) Значение, возвращаемое функцией main. Функция main возвращает значение, которое является кодом завершения программы: это целое. Однако, если ваша программа для завершения использует функцию exit (или _exit), то возвращаемым значением будет аргумент этой функции. Например, если ваша программа содержит вызов: exit(1) то код завершения будет равен 1. Если для запуска программы вы используете интегрированное окружение Borland C++ (BC.EXE), то посмотреть возвращаемое значение функции main вы можете, выбрав "File | Get Info".

Основная форма записи функции имеет следующий вид

тип_возврата имя_функции (список_параметров ) { тело_функции } Тип данных, возвращаемых функцией, задаётся с помощью элемента тип_возврата . Под элементом список_параметров подразумевается список разделяемых запятыми переменных которые могут принимать любые аргументы, передаваемой функцией.

В версии C89, если тип данных возвращаемый функцией, явно не задан, то подразумевается тип int . В языке C++ и версии C99, тип int по умолчанию, не поддерживается, хотя в большинстве компиляторов C++ такое предположение остаётся в силе.

Прототипы функций

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

тип_возврата имя_функции (список_параметров ); Например. float fn(float x); //или float fn(float);

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

Возврат значений (оператор return )

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

Return; return значение ;

В языке C99 и C++ форма оператора return , которая не задаёт возвращаемого значения, должна использоваться только в void -функциях.

Перегрузка функций

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

Void func (int a){ cout

В каждом случае для определения того, какая версия функции func() будет вызвана, анализируется тип и количество аргументов.

Передача аргументов функции по умолчанию

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

Void func (int a = 0, int b = 10){} //вызов func(); func(-1); func(-1, 99);

Области видимости и время жизни переменных.

В языках C и C++ определенны правила видимости, которые устанавливают такие понятия как область видимости и время жизни объектов. Различают глобальную область видимости и локальную.

Глобальная область видимости существует вне всех других областей. Имя объявленное в глобальной области, известно всей программе. Например глобальная переменная доступна для использования всеми функциями программы. Глобальные переменные существуют на протяжении всего жизненного цикла программы.

Локальная область видимости определяется границами блока. Имя объявленное внутри локальной области,известно только внутри этой области. Локальные переменные создаются при входе в блок и разрушаются при выходе из него. Это означает, что локальные переменные не хранят своих значений между вызовами функций. Чтобы сохранить значения переменных между вызовами, можно использовать модификатор static .

Рекурсия

В языках C и C++ функции могут вызывать сами себя. Этот процесс называют рекурсией , а функцию, которая сама себя вызывает - рекурсивной. В качестве примера приведём функцию fact(), вычисляющую факториал целого числа.

Int fact (int n) { int ans; if (n == 1) return 1; ans = fact (n-1) * n; return ans; }

Функция main()

Выполнение C/C++ программы начинается с выполнения функции main() . (Windows-программы вызывают функцию WinMain());

Функция main() не имеет прототипа. Следовательно, можно использовать различные формы функции main() . Как для языка C, так и для языка C++ допустимы следующие варианты функции main() .

Int main(); int main(int argc, char *argv)

Как видно из второй формы записи, ф. main() поддерживает по крайней мере два параметра. Они называются argc и argv . Эти аргументы будут хранить количество аргументов командной строки и указатель на них соответственно. Параметр argc имеет целый тип, и его значение всегда будет не меньше числа 1, поскольку как предусмотрено в языках C и C++, первым аргументом является всегда имя программы. Параметр argv должен быть объявлен как массив символьных указателей, в которых каждый элемент указывает на аргумент командной строки. Ниже приведён пример программы, в которой демонстрируется использование этих аргументов.

#include using namespace std; int main (int argc, char *argv) { if (argc

Передача указателей

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

Void swap (int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; } //вызов swap (&a, &b);

В языке C++ адрес переменной можно передать функции автоматически. Это реализуется с помощью параметра-ссылки . При использовании параметра-ссылки функции передаётся адрес аргумента, и функция работает с аргументом, а не с копией. Чтобы создать параметр-ссылку необходимо перед его именем поставить символ "амперсанда" (&). Внутри ф. этот параметр можно использовать обычным образом, не прибегая к оператору "звёздочка" (*), например.

Void swap (int &x, int &y){ int temp; temp = x; x = y; y = temp; } //вызов swap (a, b);

Спецификаторы функций

В языке C++ определенны три спецификатора функций:

  • inline
  • virtual
  • explict

Спецификатор inline представляет собой обращённое к компилятору требование: вместо создания вызова функции раскрыть её код прямо в строке. Если компилятор не может вставить функцию в строку он имеет право проигнорировать это требование. Спецификатором inline могут определяться как функции-члены, так и не функции-члены.

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

Спецификатор explicit применяется только к конструкторам, Конструктор, определённый как explicit , будет задействован только в том случае, когда инициализация в точности соответствует тому, что задано конструктором. Никаких преобразований выполняться не будет (т.е. спецификатор explicit создаёт "неконвертирующий конструктор").

Шаблоны функций

Общая форма определения шаблонной функции имеет следующий вид.

Tetemplate тип> тип_возврата имя_функции (список_параметров ) { //тело функции } Здесь тип означает метку - заполнитель для типа данных, с которыми эта функция фактически будет иметь дело. В операторе template можно определить несколько параметров-типов данных, используя форму списка элементов, разделённых запятыми.

Рассмотрим пример.

Template void swap (X &a, X &b) { X temp; temp = a; a = b; b = temp; } //вызов int a, b; float x, y; swap (a, b); swap (x, y);

Указатели на функцию

На функцию, как и на любой другой объект языка C, Можно создать указатель. Синтаксис следующий.

тип_возврата (*имя_указателя )(описание_переменных ); Данное объявление создаёт указатель на функцию с именем имя_указателя , в которой содержаться переменные описание_переменных и которая возвращает значение типа тип_возврата .

Функцию с помощью указателя можно вызвать следующим образом.

имя_указателя = имя_функции ; переменная = имя_указателя (переменные ); Здесь первая строка создаёт ссылку на функцию имя_функции . Вторая строка собственно производит вызов функции через указатель имя_указателя , в которую передаются переменные переменные , возврат значения происходит в переменную переменная .

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

Double y; double (*p)(doublr x); p=sin; //создание указателя на функцию sin() y = (*p)(2.5); //вызов y = p(2.5); //вызов //передача функции как параметра double y; double f(double (*c)(double x), double y){ return c(y); //вызываем переданную функцию в указатель c и возвращаем значение } y = f(sin, 2.5); //передаём функции f функцию sin, а также параметр который будет обрабатываться

Создавать массивы указателей на функции так же можно.

Int f1(void); int f2(void); int f3(void); int (*p)(void) = {f1, f2, f3} y = (*p)(); //вызов функции f2 y = p(); //вызов функции f3

Я знаю, что поток старый, но несколько лет назад этот вопрос беспокоил меня, поэтому я хотел бросить свою половину процента (если это).

Я всегда рассматриваю функции C, как если бы они фиксировали количество аргументов независимо от контекста, если только они не используют va_args. То есть, я верю, что ВСЕГДА имеют прототип:

Int main(int argc, char **argv).

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

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

Высказывание int main() просто означает, что я знаю, что функция может иметь параметры, но я их не использую, поэтому я пишу int main().

Высказывание int main (void) говорит о том, что main НЕКОТОРНО не имеет аргументов и подразумевает наличие двух разных прототипов функций:

Int main(void); int main(int argc, char **argv);

Так как C не имеет функции перегрузки, это несколько вводит меня в заблуждение, и я не доверяю коду с основным (void) в нем. Я бы не стал, если main NEVER принял какие-либо параметры, и в этом случае main (void) будет полностью в порядке.

ПРИМЕЧАНИЕ. В некоторых реализациях есть больше параметров в основном, чем argc и argv, таких как env, но это меня не беспокоит, потому что я знаю, что я не говорю прямо, что это только два параметра, но это минимальные параметры, и все в порядке, чтобы иметь больше, но не меньше. Это противоречит прямому утверждению int main (void), который кричит на меня, поскольку ЭТОТ ФУНКЦИЯ НЕТ ПАРАМЕТРОВ, что неверно, поскольку это так, они просто опускаются.

Вот мой базовый код:

/* sample.c - build into sample. */ #include int main(void) { int _argc = *((int *)2686800); char ***_pargv = (char ***)2686804; int i; for (i = 1; i < _argc; ++i) { printf("%s ", (*_pargv)[i]); } return 0; }

./sample У меня явно есть аргументы

У функции явно есть аргументы, переданные ей, несмотря на то, что она явно не говорит о том, что она не набирает void в прототип функции.

Как указано выше:

(6.7.6.3p10) Частный случай неименованного параметра типа void как только элемент в списке указывает, что функция не имеет параметров.

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

Моя точка зрения заключается в том, что аргументы все еще существуют, поэтому явное утверждение о том, что main не содержит аргументов, является нечестным. Честным способом было бы сказать int main(), который не утверждает ничего о том, сколько параметров у него есть, только о том, сколько параметров вам нужно.

ПРИМЕЧАНИЕ2: _argc, _pargv зависят от системы, чтобы найти ваши значения, которые вы должны найти, запустив эту программу:

/* findargs.c */ #include int main(int argc, char **argv) { printf("address of argc is %u.\n", &argc); printf("address of argv is %u.\n", &argv); return 0; }

Эти значения должны оставаться правильными для вашей конкретной системы.

В программы на языке Си можно передавать некоторые аргументы. Когда вначале вычислений производится обращение к main(), ей передаются три параметра. Первый из них определяет число командных аргументов при обращении к программе. Второй представляет собой массив указателей на символьные строки, содержащие эти аргументы (в одной строке - один аргумент). Третий тоже является массивом указателей на символьные строки, он используется для доступа к параметрам операционной системы (к переменным окружения).

Любая такая строка представляется в виде:

переменная = значение\0

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

Назовем аргументы функции main() соответственно: argc, argv и env (возможны и любые другие имена). Тогда допустимы следующие описания:

main(int argc, char *argv)

main(int argc, char *argv, char *env)

Предположим, что на диске A: есть некоторая программа prog.exe. Обратимся к ней следующим образом:

A:\>prog.exe file1 file2 file3

Тогда argv - это указатель на строку A:\prog.exe, argv - на строку file1 и т.д. На первый фактический аргумент указывает argv, а на последний - argv. Если argc=1, то после имени программы в командной строке параметров нет. В нашем примере argc=4.

Рекурсия

Рекурсией называется такой способ вызова, при котором функция обращается к самой себе.

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

Библиотечные функции

В системах программирования подпрограммы для решения часто встречающихся задач объединяются в библиотеки. К числу таких задач относятся: вычисление математических функций, ввод/вывод данных, обработка строк, взаимодействие со средствами операционной системы и др. Использование библиотечных подпрограмм избавляет пользователя от необходимости разработки соответствующих средств и предоставляет ему дополнительный сервис. Включенные в библиотеки функции поставляются вместе с системой программирования. Их объявления даны в файлах *.h (это так называемые включаемые или заголовочные файлы). Поэтому, как уже упоминалось выше, в начале программы с библиотечными функциями должны быть строки вида:

#include <включаемый_файл_типа_h>

Например:

#include

Существуют также средства для расширения и создания новых библиотек с программами пользователя.

Для глобальных переменных отводится фиксированное место в памяти на все время работы программы. Локальные переменные хранятся в стеке. Между ними находится область памяти для динамического распределения.

Функции malloc() и free() используются для динамического распределения свободной памяти. Функция malloc() выделяет память, функция free() освобождает ее. Прототипы этих функций хранятся в заголовочном файле stdlib.h и имеют вид:

void *malloc(size_t size);

void *free(void *p);

Функция malloc() возвращает указатель типа void; для правильного использования значение функции надо преобразовать к указателю на соответствующий тип. При успешном выполнении функция возвращает указатель на первый байт свободной памяти размера size. Если достаточного количества памяти нет, возвращается значение 0. Чтобы определить количество байтов, необходимых для переменной, используют операцию sizeof().

Пример использования этих функций:

#include

#include

p = (int *) malloc(100 * sizeof(int)); /* Выделение памяти для 100

целых чисел */

printf("Недостаточно памяти\n");

for (i = 0; i < 100; ++i) *(p+i) = i; /* Использование памяти */

for (i = 0; i < 100; ++i) printf("%d", *(p++));

free(p); /* Освобождение памяти */

Перед использованием указателя, возвращаемого malloc(), необходимо убедиться, что памяти достаточно (указатель не нулевой).

Препроцессор

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

Директива

#define идентификатор подстановка

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

Рассмотрим примеры:

Первая строка вызывает замену в программе идентификатора MAX на константу 25. Вторая позволяет использовать в тексте вместо открывающей фигурной скобки ({) слово BEGIN.

Отметим, что поскольку препроцессор не проверяет совместимость между символическими именами макроопределений и контекстом, в котором они используются, то рекомендуется такого рода идентификаторы определять не директивой #define, а с помощью ключевого слова const с явным указанием типа (это в большей степени относится к Си++):

const int MAX = 25;

(тип int можно не указывать, так как он устанавливается по умолчанию).

Если директива #define имеет вид:

#define идентификатор(идентификатор, ..., идентификатор) подстановка

причем между первым идентификатором и открывающей круглой скобкой нет пробела, то это определение макроподстановки с аргументами. Например, после появления строки вида:

#define READ(val) scanf("%d", &val)

оператор READ(y); воспринимается так же, как scanf("%d",&y);. Здесь val - аргумент и выполнена макроподстановка с аргументом.

При наличии длинных определений в подстановке, продолжающихся в следующей строке, в конце очередной строки с продолжением ставится символ \.

В макроопределение можно помещать объекты, разделенные знаками ##, например:

#define PR(x, у) x##y

После этого PR(а, 3) вызовет подстановку а3. Или, например, макроопределение

#define z(a, b, c, d) a(b##c##d)

приведет к замене z(sin, x, +, y) на sin(x+y).

Символ #, помещаемый перед макроаргументом, указывает на преобразование его в строку. Например, после директивы

#define PRIM(var) printf(#var"= %d", var)

следующий фрагмент текста программы

преобразуется так:

printf("year""= %d", year);

Опишем другие директивы препроцессора. Директива #include уже встречалась ранее. Ее можно использовать в двух формах:

#include "имя файла"

#include <имя файла>

Действие обеих команд сводится к включению в программу файлов с указанным именем. Первая из них загружает файл из текущего или заданного в качестве префикса каталога. Вторая команда осуществляет поиск файла в стандартных местах, определенных в системе программирования. Если файл, имя которого записано в двойных кавычках, не найден в указанном каталоге, то поиск будет продолжен в подкаталогах, заданных для команды #include <...>. Директивы #include могут вкладываться одна в другую.

Следующая группа директив позволяет избирательно компилировать части программы. Этот процесс называется условной компиляцией. В эту группу входят директивы #if, #else, #elif, #endif, #ifdef, #ifndef. Основная форма записи директивы #if имеет вид:

#if константное_выражение последовательность_операторов

Здесь проверяется значение константного выражения. Если оно истинно, то выполняется заданная последовательность операторов, а если ложно, то эта последовательность операторов пропускается.

Действие директивы #else подобно действию команды else в языке Си, например:

#if константное_выражение

последовательность_операторов_2

Здесь если константное выражение истинно, то выполняется последовательность_операторов_1, а если ложно - последовательность_операторов_2.

Директива #elif означает действие типа "else if". Основная форма ее использования имеет вид:

#if константное_выражение

последовательность_операторов

#elif константное_выражение_1

последовательность_операторов_1

#elif константное_выражение_n

последовательность_операторов_n

Эта форма подобна конструкции языка Си вида: if...else if...else if...

Директива

#ifdef идентификатор

устанавливает определен ли в данный момент указанный идентификатор, т.е. входил ли он в директивы вида #define. Строка вида

#ifndef идентификатор

проверяет является ли неопределенным в данный момент указанный идентификатор. За любой из этих директив может следовать произвольное число строк текста, возможно, содержащих инструкцию #else (#elif использовать нельзя) и заканчивающихся строкой #endif. Если проверяемое условие истинно, то игнорируются все строки между #else и #endif, а если ложно, то строки между проверкой и #else (если слова #else нет, то #endif). Директивы #if и #ifndef могут "вкладываться" одна в другую.

Директива вида

#undef идентификатор

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

Рассмотрим примеры. Три следующие директивы:

проверяют определен ли идентификатор WRITE (т.е. была ли команда вида #define WRITE...), и если это так, то имя WRITE начинает считаться неопределенным, т.е. не подлежащим замене.

Директивы

#define WRITE fprintf

проверяют является ли идентификатор WRITE неопределенным, и если это так, то определятся идентификатор WRITE вместо имени fprintf.

Директива #error записывается в следующей форме:

#error сообщение_об_ошибке

Если она встречается в тексте программы, то компиляция прекращается и на экран дисплея выводится сообщение об ошибке. Эта команда в основном применяется на этапе отладки. Заметим, что сообщение об ошибке не надо заключать в двойные кавычки.

Директива #line предназначена для изменения значений переменных _LINE_ и _FILE_, определенных в системе программирования Си. Переменная _LINE_ содержит номер строки программы, выполняемой в текущий момент времени. Идентификатор _FILE_ является указателем на строку с именем компилируемой программы. Директива #line записывается следующим образом:

#line номер "имя_файла"

Здесь номер - это любое положительное целое число, которое будет назначено переменной _LINE_, имя_файла - это необязательный параметр, который переопределяет значение _FILE_.

Директива #pragma позволяет передать компилятору некоторые указания. Например, строка

говорит о том, что в программе на языке Си имеются строки на языке ассемблера. Например:

Рассмотрим некоторые глобальные идентификаторы или макроимена (имена макроопределений). Определены пять таких имен: _LINE_, _FILE_, _DATE_, _TIME_, _STDC_. Два из них (_LINE_ и _FILE_) уже описывались выше. Идентификатор _DATE_ определяет строку, в которой сохраняется дата трансляции исходного файла в объектный код. Идентификатор _TIME_ задает строку, сохраняющую время трансляции исходного файла в объектный код. Макрос _STDC_ имеет значение 1, если используются стандартно - определенные макроимена. В противном случае эта переменная не будет определена.

Поделиться