Что такое main в си. Функции. Как из одной функции в Cи вызвать другую функцию

9 ответов

Некоторые из функций языка C начинаются как хаки, которые только что сработали.

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

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

Это так, если вызывающие соглашения таковы, что:

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

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

;; pseudo-assembly-language ;; main(argc, argv, envp); call push envp ;; rightmost argument push argv ;; push argc ;; leftmost argument ends up on top of stack call main pop ;; caller cleans up pop pop

В компиляторах, где этот тип соглашения о вызове имеет значение, ничего особого не нужно делать для поддержки двух типов main или даже дополнительных типов. main может быть функцией без аргументов, и в этом случае он не обращает внимания на элементы, которые были перенесены в стек. Если это функция из двух аргументов, она находит argc и argv в качестве двух верхних элементов стека. Если это вариант с тремя аргументами, ориентированный на платформу, с указателем среды (общим расширением), это тоже будет работать: он найдет третий аргумент как третий элемент из верхней части стека.

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

/* I"m adding envp to show that even a popular platform-specific variant can be handled. */ extern int main(int argc, char **argv, char **envp); void __start(void) { /* This is the real startup function for the executable. It performs a bunch of library initialization. */ /* ... */ /* And then: */ exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere)); }

Другими словами, этот начальный модуль всегда вызывает основной аргумент с тремя аргументами. Если main не принимает никаких аргументов или только int, char ** , он работает нормально, а также если он не принимает никаких аргументов из-за соглашений о вызовах.

Если бы вы делали такие вещи в своей программе, это было бы непереносимо и считалось бы поведением undefined по ISO C: объявлением и вызовом функции одним способом и определением ее в другой. Но трюк запуска компилятора не должен быть переносимым; он не руководствуется правилами для переносных программ.

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

То есть вы пишете это:

Int main(void) { /* ... */ }

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

Int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore) { /* ... */ }

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

Другая стратегия реализации для компилятора или, возможно, линкера для пользовательской генерации функции __start (или того, что она называется), или, по крайней мере, выбрать один из нескольких предварительно скомпилированных альтернатив. В объектном файле может храниться информация о том, какая из поддерживаемых форм main используется. Компонент может посмотреть эту информацию и выбрать правильную версию модуля запуска, которая содержит вызов main , который совместим с определением программы. В реализациях C обычно имеется только небольшое количество поддерживаемых форм main , поэтому этот подход возможен.

Компиляторы для языка C99 всегда должны в некоторой степени относиться к main , чтобы поддерживать хак, что если функция завершается без оператора return , поведение выглядит так, как если бы выполнялось return 0 . Это, опять же, можно рассматривать с помощью преобразования кода. Компилятор замечает, что скомпилирована функция с именем main . Затем он проверяет, может ли конец тела потенциально достижим. Если это так, он вставляет return 0;

Нет никакой перегрузки main даже в С++. Основная функция - это точка входа для программы, и должно существовать только одно определение.

Для стандартного C

Для размещенной среды (обычной), стандарт C99 говорит:

5.1.2.2.1 Запуск программы

Функция, вызванная при запуске программы, называется main . Реализация не объявляет прототипа для этой функции. Это должно быть определенный с типом возврата int и без параметров:

Int main(void) { /* ... */ }

или с двумя параметрами (называемыми здесь argc и argv , хотя любые имена могут использоваться, поскольку они являются локальными для функции, в которой они объявляются):

Int main(int argc, char *argv) { /* ... */ }

или эквивалент; 9) или каким-либо другим способом реализации.

9) Таким образом, int можно заменить на имя typedef, определенное как int , или тип argv можно записать как char **argv , и и так далее.

Для стандартного С++:

3.6.1 Основная функция

1 Программа должна содержать глобальную функцию main, которая является назначенным началом программы. [...]

2 Реализация не должна предопределять основную функцию. Эта функция не должна быть перегружена . Он должен имеют тип возвращаемого типа int, но в противном случае его тип определяется реализацией. Все реализации должны допускать оба следующих определения main:

Int main() { /* ... */ }

Int main(int argc, char* argv) { /* ... */ }

В стандарте С++ явно говорится: "Он [основная функция] должен иметь тип возвращаемого типа int, но в противном случае его тип определяется реализацией" и требует тех же двух сигнатур, что и стандарт C.

В размещенной среде (среда C, которая также поддерживает библиотеки C) - операционная система вызывает main .

В не-размещенной среде (один предназначен для встроенных приложений) вы всегда можете изменить точку входа (или выйти) вашей программы, используя директивы предварительного процессора, такие как

#pragma startup #pragma exit

Если приоритет является необязательным интегральным числом.

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

Это одна из странных асимметрий и специальных правил языка C и С++.

По-моему, он существует только по историческим причинам, и нет реальной серьезной логики. Обратите внимание, что main является особенным также по другим причинам (например, main в С++ не может быть рекурсивным, и вы не можете взять его адрес, а на C99/С++ вы можете опустить окончательный оператор return).

Обратите внимание, что даже в С++ это не перегрузка... либо программа имеет первую форму, либо имеет вторую форму; он не может иметь обоих.

Что необычно для main не в том, что его можно определить более чем одним способом, он может быть определен только одним из двух способов.

main - пользовательская функция; реализация не объявляет прототип для него.

То же самое верно для foo или bar , но вы можете определять функции с этими именами так, как вам нравится.

Различие заключается в том, что main вызывается реализацией (среда выполнения), а не только вашим собственным кодом. Реализация не ограничивается обычной семантикой вызова функции C, поэтому она может (и должна) иметь дело с несколькими вариантами, но не требует обработки бесконечно многих возможностей. Форма int main(int argc, char *argv) допускает аргументы командной строки, а int main(void) в C или int main() в С++ - это просто удобство для простых программ, которые не требуют обработки аргументов командной строки.

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

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

Обратите внимание, что оба C и С++ допускают другие определения, определенные для реализации для main , но редко есть веские основания для их использования. А для автономных реализаций (таких как встроенные системы без ОС) точка входа в программу определяется реализацией и необязательно даже называется main .

main - это просто имя для начального адреса, решенного компоновщиком, где main - имя по умолчанию. Все имена функций в программе - это начальные адреса, где начинается функция.

Минимальной программой на C++ является

Int main() { } // the minimal C++ program

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

Каждая программа, написанная на C++, имеет в себе функцию main() , с которой начинается запуск программы. Функция main(), как правило, возвращает результат своего выполнения, о чем сигнализирует int (integer - целочисленный), который написан перед функцией main() . При правильном, успешном завершении функция main() возвращает в качестве результата 0 . Значение результата, отличное от нуля сигнализирует о нештатном завершении программы.

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

Типичным примером первой программы на любом языке программирования является вывод текста "Hello, World!":

#include int main() { std::cout << "Hello, World!\n"; }

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

  1. Директива #include
    #include
    сообщает компилятору о том, что необходимо подключить некий заголовочный файл, компоненты которого планируется использовать в файле, где объявлена функция main() . iostream - это стандартная библиотека ввода вывода из STL. То есть здесь уже используется функционал библиотек, хоть и являющихся для языка стандартом. И последний момент - это угловые скобки, в которых находится название библиотеки, которые говорят о том, что это включение внешних файлов в проект, а не тех которые находятся в составе проекта. Те же файлы, которые находятся в составе проекта подключаются обрамляясь в обычные кавычки, например #include "myclass.h". Такое подключение библиотек является стандартом. Например, в Visual Studio при несоблюдении данного стандарта будут выпадать ошибки.
  2. std - это использование пространства имён, в котором находится оператор вывода cout. Пространства имён были введены в C++ для того, чтобы убрать конфликты имён между библиотеками и проектом разработчика, если где-то имеются повторяющиеся наименования функций или классов. В Java для разрешения конфликтов имён используется система пакетов.

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

Это помимо того, что запись функции main может иметь различный вид, хотя стандартом являются две записи:

  1. int main()
  2. int main(int argc, char* argv)

Можно встретить ещё записи типа void main() и т.д. Но это ошибочные записи, хотя в некоторых компиляторах они будут компилироваться, причём даже без ошибок и предупреждений.

В записи int main(int argc, char* argv) передаются аргументы:

  1. argc - указывает количество переданных аргументов. Всегда не меньше 1, поскольку всегда передаётся имя программы
  2. argv - массив указателей на аргументы, которые передаются в качестве строковых переменных.

Если argc больше 1, значит при запуске программы были переданы дополнительные аргументы.

Проверка может выглядеть следующим образом:

#include int main(int argc, char* argv) { // Если бы передан дополнительный аргумент, if (argc > 1) { // то попытаемся вывести полученный аргумент std::cout << argv<

В целом, есть большое количество моментов, которые необходимо понимать в C++ даже для небольшой программы, но от этого только интереснее;-)

При автоматизированном создании консольного приложения в языке программирования С++, автоматически создается главная функция очень похожая на эту:

int main(int argc, char * argv)
{…}

Заголовок функции содержит сигнатуру главной функции main() с аргументами argс и argv .
Если программу запускать через командную строку, то существует возможность передать какую-либо информацию этой программе. Для этого существуют аргументы командной строки argc и argv .
Параметр argc имеет тип int , и содержит количество параметров, передаваемых в функцию main . Причем argc всегда не меньше 1, даже когда функции main не передается никакой информации, так как первым параметром считается имя приложения.
Параметр argv представляет собой массив указателей на строки. Через командную строку можно передать только данные строкового типа.

При запуске программы через командную строку Windows можно передавать ей некоторую информацию. При этом командная строка будет иметь вид:
Диск:\путь\имя.exe аргумент1 аргумент2 …

Аргументы командной строки разделяются одним или несколькими пробелами.

Аргумент argv содержит полное имя приложения:

#include
using namespace std;

cout << argv << endl;

Return 0;
}

Результат выполнения

Пример : вычисление произведения двух целых чисел
В программе используется функция преобразования строки в целое число StrToInt() отсюда .

#include
using namespace std;
int StrToInt(char *s) {…}
int main(int argc, char * argv) {

Int a = 0, b=0;

If (argc > 1)

a = StrToInt(argv);

If (argc > 2)

b = StrToInt(argv);

cout << a <<«*» << b << «= « << a*b << endl;

Return 0;
}

Запуск программы осуществляется как

Результат выполнения

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

Для передачи аргументов командной строки при отладке программы необходимо обратиться к меню Свойства проекта.


На вкладке Свойства конфигурации ->Отладка выбрать Аргументы команды и задать их значения.

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

Функция 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".

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

Введение

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

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

Функция – это именованная часть программы, которая может быть многократно вызвана из другого участка программы (в котором эта функция видна). Функция может принимать фиксированное либо переменное число аргументов, а может не иметь аргументов. Функция может как возвращать значение, так и быть пустой (void) и ничего не возвращать.

Мы уже знакомы с многими функциями и знаем, как их вызывать – это функции библиотек stdio, stdlib, string, conio и пр. Более того, main – это тоже функция. Она отличается от остальных только тем, что является точкой входа при запуске приложения.
Функция в си определяется в глобальном контексте. Синтаксис функции: (, ...) { }

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

#include #include float sqr(float x) { float tmp = x*x; return tmp; } void main() { printf("%.3f", sqr(9.3f)); getch(); }

Внутри функции sqr мы создали локальную переменную, которой присвоили значение аргумента. В качестве аргумента функции передали число 9,3. Служебное слово return возвращает значение переменной tmp. Можно переписать функцию следующим образом:

Float sqr(float x) { return x*x; }

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

Void printSqr(float x) { printf("%d", x*x); return; }

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

Void printSqr(float x) { printf("%d", x*x); }

Если функция не принимает аргументов, то скобки оставляют пустыми. Можно также написать слово void:

Void printHelloWorld() { printf("Hello World"); }

эквивалентно

Void printHelloWorld(void) { printf("Hello World"); }

Формальные и фактические параметры

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

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

#include #include //Формальные параметры имеют имена a и b //по ним мы обращаемся к переданным аргументам внутри функции int sum(int a, int b) { return a+b; } float square(float x) { return x*x; } void main() { //Фактические параметры могут иметь любое имя, в том числе и не иметь имени int one = 1; float two = 2.0; //Передаём переменные, вторая переменная приводится к нужному типу printf("%d\n", sum(one, two)); //Передаём числовые константы printf("%d\n", sum(10, 20)); //Передаём числовые константы неверного типа, они автоматически приводится к нужному printf("%d\n", sum(10, 20.f)); //Переменная целого типа приводится к типу с плавающей точкой printf("%.3f\n", square(one)); //В качестве аргумента может выступать и вызов функции, которая возвращает нужное значение printf("%.3f\n", square(sum(2 + 4, 3))); getch(); }

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

#include #include void main() { char c; do { //Сохраняем возвращённое значение в переменную c = getch(); printf("%c", c); } while(c != "q"); //Возвращённое значение не сохраняется getch(); }

Передача аргументов

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

#include #include void change(int a) { a = 100; printf("%d\n", a); } void main() { int d = 200; printf("%d\n", d); change(d); printf("%d", d); getch(); }

Программы выведет
200
100
200
Понятно почему. Внутри функции мы работаем с переменной x, которая является копией переменной d. Мы изменяем локальную копию, но сама переменная d при этом не меняется. После выхода из функции локальная переменная будет уничтожена. Переменная d при этом никак не изменится.
Каким образом тогда можно изменить переменную? Для этого нужно передать адрес этой переменной. Перепишем функцию, чтобы она принимала указатель типа int

#include #include void change(int *a) { *a = 100; printf("%d\n", *a); } void main() { int d = 200; printf("%d\n", d); change(&d); printf("%d", d); getch(); }

Вот теперь программа выводит
200
100
100
Здесь также была создана локальная переменная, но так как передан был адрес, то мы изменили значение переменной d, используя её адрес в оперативной памяти.

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

#include #include #include void init(int *a, unsigned size) { a = (int*) malloc(size * sizeof(int)); } void main() { int *a = NULL; init(a, 100); if (a == NULL) { printf("ERROR"); } else { printf("OKAY..."); free(a); } getch(); }

Но эта функция выведет ERROR. Мы передали адрес переменной. Внутри функции init была создана локальная переменная a, которая хранит адрес массива. После выхода из функции эта локальная переменная была уничтожена. Кроме того, что мы не смогли добиться нужного результата, у нас обнаружилась утечка памяти: была выделена память на куче, но уже не существует переменной, которая бы хранила адрес этого участка.

Для изменения объекта необходимо передавать указатель на него, в данном случае – указатель на указатель.

#include #include #include void init(int **a, unsigned size) { *a = (int*) malloc(size * sizeof(int)); } void main() { int *a = NULL; init(&a, 100); if (a == NULL) { printf("ERROR"); } else { printf("OKAY..."); free(a); } getch(); }

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

#include #include #include #include char* initByString(const char *str) { char *p = (char*) malloc(strlen(str) + 1); strcpy(p, str); return p; } void main() { char *test = initByString("Hello World!"); printf("%s", test); free(test); getch(); }

В этом примере утечки памяти не происходит. Мы выделили память с помощью функции malloc, скопировали туда строку, а после этого вернули указатель. Локальные переменные были удалены, но переменная test хранит адрес участка памяти на куче, поэтому можно его удалить с помощью функции free.

Объявление функции и определение функции. Создание собственной библиотеки

В си можно объявить функцию до её определения. Объявление функции, её прототип, состоит из возвращаемого значения, имени функции и типа аргументов. Имена аргументов можно не писать. Например

#include #include //Прототипы функций. Имена аргументов можно не писать int odd(int); int even(int); void main() { printf("if %d odd? %d\n", 11, odd(11)); printf("if %d odd? %d\n", 10, odd(10)); getch(); } //Определение функций int even(int a) { if (a) { odd(--a); } else { return 1; } } int odd(int a) { if (a) { even(--a); } else { return 0; } }

Это смешанная рекурсия – функция odd возвращает 1, если число нечётное и 0, если чётное.

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

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

#ifndef _FILE1_H_ #define _FILE1_H_ int odd(int); int even(int); #endif

Содержимое файла исходного кода File1.c

#include "File1.h" int even(int a) { if (a) { odd(--a); } else { return 1; } } int odd(int a) { if (a) { even(--a); } else { return 0; } }

Наша функция main

#include #include #include "File1.h" void main() { printf("if %d odd? %d\n", 11, odd(11)); printf("if %d odd? %d\n", 10, odd(10)); getch(); }

Рассмотрим особенности каждого файла. Наш файл, который содержит функцию main, подключает необходимые ему библиотеки а также заголовочный файл File1.h. Теперь компилятору известны прототипы функций, то есть он знает возвращаемый тип, количество и тип аргументов и имена функций.

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

#pragma once int odd(int); int even(int);

Файл File1.c исходного кода подключает свой заголовочный файл. Всё как обычно логично и просто. В заголовочные файлах принято кроме прототипов функций выносить константы, макроподстановки и определять новые типы данных. Кроме того, именно в заголовочных файлах можно обширно комментировать код и писать примеры его использования.

Передача массива в качестве аргумента

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

#include #include void printArray(int *arr, unsigned size) { unsigned i; for (i = 0; i < size; i++) { printf("%d ", arr[i]); } } void main() { int x = {1, 2, 3, 4, 5}; printArray(x, 10); getch(); }

В этом примере функция может иметь следующий вид

Void printArray(int arr, unsigned size) { unsigned i; for (i = 0; i < size; i++) { printf("%d ", arr[i]); } }

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

#include #include void printArray(int arr, unsigned size) { unsigned i, j; for (i = 0; i < size; i++) { for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void main() { int x = { { 1, 2, 3, 4, 5}, { 6, 7, 8, 9, 10}}; printArray(x, 2); getch(); }

Либо, можно писать

#include #include void printArray(int (*arr), unsigned size) { unsigned i, j; for (i = 0; i < size; i++) { for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void main() { int x = { { 1, 2, 3, 4, 5}, { 6, 7, 8, 9, 10}}; printArray(x, 2); getch(); }

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

#include #include #include #include #define SIZE 10 unsigned* getLengths(const char **words, unsigned size) { unsigned *lengths = NULL; unsigned i; lengths = (unsigned*) malloc(size * sizeof(unsigned)); for (i = 0; i < size; i++) { lengths[i] = strlen(words[i]); } return lengths; } void main() { char **words = NULL; char buffer; unsigned i; unsigned *len = NULL; words = (char**) malloc(SIZE * sizeof(char*)); for (i = 0; i < SIZE; i++) { printf("%d. ", i); scanf("%127s", buffer); words[i] = (char*) malloc(128); strcpy(words[i], buffer); } len = getLengths(words, SIZE); for (i = 0; i < SIZE; i++) { printf("%d ", len[i]); free(words[i]); } free(words); free(len); getch(); }

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

#include #include #include #include #define SIZE 10 void getLengths(const char **words, unsigned size, unsigned *out) { unsigned i; for (i = 0; i < size; i++) { out[i] = strlen(words[i]); } } void main() { char **words = NULL; char buffer; unsigned i; unsigned *len = NULL; words = (char**) malloc(SIZE * sizeof(char*)); for (i = 0; i < SIZE; i++) { printf("%d. ", i); scanf("%127s", buffer); words[i] = (char*) malloc(128); strcpy(words[i], buffer); } len = (unsigned*) malloc(SIZE * sizeof(unsigned)); getLengths(words, SIZE, len); for (i = 0; i < SIZE; i++) { printf("%d ", len[i]); free(words[i]); } free(words); free(len); getch(); }

На этом первое знакомство с функциями заканчивается: тема очень большая и разбита на несколько статей.

Последние материалы раздела:

Signal Private Messenger для мобильных или компьютера Скачать программу сигнал для виндовс
Signal Private Messenger для мобильных или компьютера Скачать программу сигнал для виндовс

Signal Private Messenger – идеальное приложение для тех, кто хочет быть полностью уверен в безопасности переписки. Разработчики мессенджера...

Описание тестов монитора Доступными 4 режима настройки цветопередачи
Описание тестов монитора Доступными 4 режима настройки цветопередачи

Как откалибровать монитор | Две причины для калибровки монитора Если вы уже прошли все описанные этапы, то значит, уже многое сделано. Если у...

Как сделать кнопку выключения в Windows?
Как сделать кнопку выключения в Windows?

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