sonyps4.ru

Паттерны программирования. Паттерны проектирования

Минимальной программой на 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++ даже для небольшой программы, но от этого только интереснее;-)

В программы на языке Си можно передавать некоторые аргументы. Когда вначале вычислений производится обращение к 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, если используются стандартно - определенные макроимена. В противном случае эта переменная не будет определена.

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

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

Установка /IDE

Самое первое, что вы должны сделать, прежде чем приступить к изучении C++, это убедиться, что у вас есть IDE — интегрированная среда разработки (программа в которой вы будете программировать). Если у вас нет IDE, тогда вам сюда . Когда определитесь с выбором IDE, установите её и потренируйтесь создавать простые проекты.

Введение в язык C++

Язык C++ представляет собой набор команд, которые говорят компьютеру, что необходимо сделать. Этот набор команд, обычно называется исходный код или просто код. Командами являются или «функции» или «ключевые слова». Ключевые слова(зарезервированные слова С/С++) являются основными строительными блоками языка. Функции являются сложными строительными блоками, так как записаны они в терминах более простых функций — вы это увидите в нашей самой первой программе, которая показана ниже. Такая структура функций напоминает содержание книги. Содержание может показывать главы книги, каждая глава в книге может иметь своё собственное содержание, состоящее из пунктов, каждый пункт может иметь свои подпункты. Хотя C++ предоставляет много общих функций и зарезервированных слов, которые вы можете использовать, все-таки возникает потребность в написании своих собственных функций.

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

Так как же получить доступ к этим Стандартным функциям? Чтобы получить доступ к стандартным функциям, которые поставляются с компилятором, необходимо подключить заголовочный файл используя препроцессорную директиву — #include . Почему это эффективно? Давайте посмотрим на примере рабочей программы:

#include << "Моя первая программа на С++\n"; cin.get(); }

Рассмотрим подробно элементы программы. #include это директива «препроцессору», которая сообщает компилятору поместить код из заголовочного файла iostream в нашу программу перед тем как создать исполняемый файл. Подключив к программе заголовочный файл вы получаете доступ к множеству различных функций, которые можете использовать в своей программе. Например, оператору сout требуется iostream . Строка using namespace std; сообщает компилятору, что нужно использовать группу функций, которые являются частью стандартной библиотеки std . В том числе эта строка позволяет программе использовать операторы, такие как cout . Точка с запятой является частью синтаксиса C++. Она сообщает компилятору, что это конец команды. Чуть позже вы увидите, что точка с запятой используется для завершения большинства команд в C++.

Следующая важная строка программы int main() . Эта строка сообщает компилятору, что есть функция с именем main , и что функция возвращает целое число типа int . Фигурные скобки { и } сигнализируют о начале { и конце } функции. Фигурные скобки используются и в других блоках кода, но обозначают всегда одно — начало и конец блока, соответственно.

В C++ объект cout используется для отображения текста (произносится как «Cи аут»). Он использует символы << , известные как «оператор сдвига», чтобы указать, что отправляется к выводу на экран. Результатом вызова функции cout << является отображение текста на экране. Последовательность \n фактически рассматривается как единый символ, который обозначает новую строку (мы поговорим об этом позже более подробно). Символ \n перемещает курсор на экране на следующую строку. Опять же, обратите внимание на точку с запятой, её добавляют в конец, после каждого оператора С++.

Следующая команда cin.get() . Это еще один вызов функции, которая считывает данные из входного потока данных и ожидает нажатия клавиши ENTER. Эта команда сохраняет консольное окно от закрытия, до тех пор пока не будет нажата клавиша ENTER. Это даёт вам время для того, чтобы посмотреть результат выполнения программы.

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

#include using namespace std; int main() { cout<<"Моя первая программа на С++\n"; cin.get(); return 1; }

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

Обязательно комментируйте свои программы!

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

Что делать со всеми этими типами переменных?

Иногда это может сбить с толку — иметь несколько типов переменных, когда кажется, что некоторые типы переменных являются избыточными. Очень важно использовать правильный тип переменной, так как некоторым переменным, требуется больше памяти, чем другим. Кроме того, из-за способа хранения в памяти, числа с плавающей точкой, типы данных float и double являются «неточным», и не должны использоваться, когда необходимо сохранить точное целое значение.

Объявление переменных в C++

Чтобы объявить переменную используется синтаксис тип <имя>; . Вот некоторые примеры объявления переменных:

Int num; char character; float num_float;

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

Int x, y, z, d;

Если вы смотрели внимательно, вы, возможно, видели, что объявление переменной всегда сопровождается точкой с запятой. Подробнее о соглашении — «об именовании переменных», можно .

Распространенные ошибки при объявлении переменных в C++

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

Использование переменных

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

#include using namespace std; int main() { int number; cout << "Введите число: "; cin >> number; cin.ignore(); cout << "Вы ввели: "<< number <<"\n"; cin.get(); }

Давайте рассмотрим эту программу и изучим её код, строку за строкой. Ключевое слово int говорит о том, что number — целое число. Функция cin >> считывает значение в number , пользователь должен нажать ввод после введенного числа. cin.ignore () — функция, которая считывает символ и игнорирует его. Мы организовали свой ввод в программу, после ввода числа, мы нажимаем клавишу ENTER, символ который также передаётся в поток ввода. Нам это не нужно, поэтому мы его отбрасываем. Имейте в виду, что переменная была объявлена целого типа, если пользователь попытается ввести десятичное число, то оно будет обрезано (то есть десятичная часть числа будет игнорироваться). Попробуйте ввести десятичное число или последовательность символов, когда вы запустите пример программы, ответ будет зависеть от входного значения.

Обратите внимание, что при печати из переменной кавычки не используются. Отсутствие кавычек сообщает компилятору , что есть переменная, и, следовательно, о том, что программа должна проверять значение переменной для того, чтобы заменить имя переменной на её значение при выполнении. Несколько операторов сдвига в одной строке вполне приемлемо и вывод будет выполняться в том же порядке. Вы должны разделять строковые литералы (строки, заключенные в кавычки) и переменные, давая каждому свой оператор сдвига << . Попытка поставить две переменные вместе с одним оператором сдвига << выдаст сообщение об ошибке . Не забудьте поставить точку с запятой. Если вы забыли про точку с запятой, компилятор выдаст вам сообщение об ошибке при попытке скомпилировать программу.

Изменение и сравнение величин

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

  • * умножение,
  • - вычитание,
  • + сложение,
  • / деление,
  • = присвоение,
  • == равенство,
  • > больше,
  • < меньше.
  • != неравно
  • >= больше или равно
  • <= меньше или равно

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

Вот несколько примеров:

A = 4 * 6; // использование строчного комментария и точки с запятой, a равно 24 a = a + 5; // равно сумме исходного значения и пяти a == 5 // не присваивается пять, выполняется проверка, а равно 5 или нет

Вы часто будете использовать == в таких конструкциях, как условные операторы и циклы.

A < 5 // Проверка, a менее пяти? a > 5 // Проверка, a больше пяти? a == 5 // Проверка, a равно пяти? a != 5 // Проверка, а неравно пяти? a >= 5 // Проверка, a больше или равно пяти? a <= 5 // Проверка, a меньше или равно пяти?

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

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

Введение

Ч ем дальше мы изучаем си, тем больше становятся программы. Мы собираем все действия в одну функцию 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(); }

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

Привести в пример паттерн проектирования – один из самых популярных запросов на собеседованиях. Объясняем порождающие паттерны простыми словами.

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

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

Паттерны не привязаны к конкретному языку программирования, это просто подход к проектированию.

Порождающие паттерны

Это паттерны, которые создают объекты, или позволяют получить доступ к существующим. Порождающие паттерны – это те шаблоны, по которым можно создать автомобиль и сделать это лучшим образом.

Singleton (одиночка)

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

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

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

Registry (реестр, журнал записей)

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

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

Multiton (пул «одиночек»)

По сути данный паттерн – это реестр одиночек, каждый из которых имеет имя, по которому к нему можно получить доступ.

Object pool (пул объектов)

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

Factory (фабрика)

Фабрика – достаточно точное название для этого паттерна. Когда вам понадобится пакет сока, вы обращаетесь к фабрике с соответствующим запросом, она в свою очередь копирует эталон и передает вам его экземпляр. Что при этом происходит внутри фабрики и как она это делает вас не беспокоит.

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

Builder (строитель)

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

Prototype (прототип)

Этот паттерн похож на фабрику, но только фабрика здесь в самом объекте. К примеру, у вас в руках есть пустой пакет для сока, которому вы говорите «Хочу ананасовый сок». Пакет в свою очередь копирует себя и заполняет себя ананасовым соком.

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

Factory method (фабричный метод)

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

Допустим фабрика производит пакеты с разными соками. Мы можем на каждый вид сока сделать свою производственную линию, но это не эффективно. Удобнее сделать одну линию по производству пакетов-основ, а разделение ввести только на этапе заливки сока, который мы можем определять просто по названию сока.

Для этого мы создаем основной отдел по производству пакетов-основ и предупреждаем все подотделы, что они должны производить нужный пакет с соком про простому «Надо» (т.е. каждый подотдел должен реализовать паттерн «фабричный метод»). Поэтому каждый подотдел заведует только своим типом сока и реагирует на слово «Надо».

Теперь, если нам потребуется пакет бананового сока, мы просто скажем отделу по производству бананового сока «Надо», а он в свою очередь скажет основному отделу по созданию пакетов сока: «Произведи свой обычный пакет, а этот сок нужно туда залить».

Lazy initialization (отложенная инициализация)

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

Dependency injection (внедрение зависимости)

Если нам требуется нанять нового человека, мы можем не создавать свой отдел кадров, а внедрить зависимость от компании по подбору персонала. Она, свою очередь, по нашему запросу «нужен человек», будет либо сама работать как отдел кадров, либо же найдет другую компанию, которая предоставит данные услуги.

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



Загрузка...