Базовые понятия информатики. Понятие «Информатика» и «Информация». 3

Предмет информатики как науки. 3

Задачи информатики как науки. 3

Данные. 3

Информация. 4

Знание. 7

Информационная система. 7

Информационные технологии. 8

Понятие алгоритма. Свойства и классы алгоритмов. Формы представления алгоритмов. 9

Классы алгоритмов. 9

Формы представления алгоритмов. 10

Понятие алгоритма. Базовые алгоритмические структуры.. 13

Представление данных в памяти персонального компьютера. 16

Принципы обработки программных кодов. 20

Микропроцессор. 20

Мнемонические коды.. 20

Компиляторы.. 21

Интерпретатор. 21

Язык С. История развития. Основные свойства языка. 23

Отличительные особенности языка C.. 23

Элементы языка C.. 25

Базовые типы данных. 31

Структура программы на C++. 34

Директива #include. 35

Использование void. 36

Инструкция return. 36

Описание переменных. 37

Обработка данных. Операторы.. 38

Арифметические операторы.. 38

Приоритет операторов и порядок вычислений. 39

Используемые алгоритмы обработки данных. 42

Аккумуляторы.. 43

Преобразования типов данных. 44

Функции языка C++. 46

Декларации и дефиниции функций. 46

Формальные и фактические параметры. Вызов функций. 47

Возврат функцией значений. 47

Переменные в функциях. 48

Автоматические (локальные) переменные. 48

Внешние (глобальные) переменные. 49

Статические переменные. 50

Передача параметров по значению.. 51

Передача параметров по ссылке. 52

Значения параметров по умолчанию.. 53

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

Рекурсия. 55

Встроенные функции. 56

Обработка символьных данных. 58

Вывод в C/C++. 58

Функция puts() 58

Функция putchar() 59

Функция printf() 59

Выбор правильных средств вывода информации. 62

Вывод в Си++. 63

Ввод в C/C++. 63

Функция gets() 63

Функция getchar() 65

Функция scanf() 66

Выбор соответствующих средств ввода данных. 67

Ввод в Си++. 68

Управляющие структуры.. 68

Структуры выбора (if / else) 68

Структуры выбора (switch/case/default) 71

Структуры повторения (циклы) 72

Использование цикла for 72

Использование цикла do...while (постусловие) 73

Использование цикла while (предусловие) 74

Операторы передачи управления. 74

Оператор безусловного перехода goto. 74

Оператор break. 75

Оператор continue. 75

Препроцессор языка Си. 76

Массивы.. 77

Объявление переменной массива. 77

Использование индексной переменной. 78

Инициализация массива при объявлении. 78

Передача массивов в функции. 79

Использование констант при объявлении массивов. 81

Символьные строки. 81

Массивы строк. 84

Алгоритмы сортировки массива. 85

Поиск заданного элемента в массиве. 90

Указатели. 92

Объявление указателя. 92

Указатели на массивы.. 94

Операции над указателями. 96

Указатели на строку. 96

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

Функции, возвращающие указатель. 97

Указатели на многомерные массивы.. 98

Массивы указателей. 98

Динамическое распределение памяти. 100

Структуры данных. 104

Реализация одних структур на базе других. 104

Очередь. 105

Операции над очередями. 106

Стек. 107

Операции над стеками. 108

Ссылочные реализации структур данных. 111

Списки. 111

Операции над списками. 113


Базовые понятия информатики. Понятие «Информатика» и «Информация»

 

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

Термин "информатика" происходит от французского слова Informatique и образован из двух слов: информация и автоматика. Термин информация произошел от латинского слова informatio — разъяснение, осведомление.

 

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

 

Предмет информатики как науки составляют:

аппаратное обеспечение средств вычислительной техники;

программное обеспечение средств вычислительной техники;

средства взаимодействия аппаратного и программного обеспечения;

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

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

 

Задачи информатики как науки составляют:

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

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

 

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

 

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

 

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

 

Пример данных:

812, 930, 944.

(для человека это ничего не значит, если нет объяснения, что обозначают эти цифры).

01000001 01101100 01101100 01100001

(для человека это ничего не значит, но в ASCII – это слово Alla).

 

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

сбор данных — накопление информации с целью обеспечения достаточной полноты для принятия решения;

формализация данных — приведение данных, которые поступают из разных источников к единой форме;

фильтрация данных — устранение лишних данных, которые не нужны для принятия решений;

сортировка данных — приведение в порядок данных за заданным признаком с целью удобства использования;

архивация данных — сохранение данных в удобной и доступной форме;

защита данных — комплекс мер, направленных на предотвращение потерь, воспроизведения и модификации данных;

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

преобразование данных — преобразование данных с одной формы в другую, или с одной структуры в другую, или изменение типа носителя.

 

Информация

Информация (information)— это данные, сопровождающиеся смысловой нагрузкой.

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

Информация (в системе, о системе) по отношению к окружающей среде (окружению) бывает трех типов: входная, выходная и внутренняя.

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

Выходная информация (по отношению к окружающей среде) - та, которую система выдает в окружающую среду.

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

Информация по отношению к конечному результату проблемы бывает:

  • исходная (на стадии начала использования актуализации этой информации);
  • промежуточная (на стадии от начала до завершения актуализации информации);
  • результирующая (после использования этой информации, завершения ее актуализации).

Информация (по ее изменчивости при актуализации) бывает:

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

 

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

Основные свойства информации (и сообщений):

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

 

Пример информации: 812 рублей, 930 рублей, 944 рубля.

Более информативное сообщение: 812 рублей, 930 рублей, 944 рубля — цены на бальзам после бритья.

Ещё более информативное: 812 рублей, 930 рублей, 944 рубля — цены на бальзам после бритья "Dune", 100 мл. в Москве.

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

Информация - это данные, рассматриваемые с учетом некоторой их семантической сущности.

                                В итоге мы имеем следующую простую формулу:

информация = данные + смысл

 

Методы получения и использования информации можно разделить на три группы, иногда разграничиваемые лишь условно:

  1. эмпирические методы или методы получения эмпирической информации (эмпирических данных);
  2. теоретические методы или методы получения теоретической информации (построения теорий);
  3. эмпирико-теоретические методы (смешанные, полуэмпирические) или методы получения эмпирико-теоретической информации.

Охарактеризуем кратко эмпирические методы:

  1. Наблюдение - сбор первичной информации или эмпирических утверждений о системе (в системе).
  2. Сравнение - установление общего и различного в исследуемой системе или системах.
  3. Измерение - поиск, формулирование эмпирических фактов.
  4. Эксперимент - целенаправленное преобразование исследуемой системы (систем) для выявления ее (их) свойств.

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

Охарактеризуем кратко эмпирико-теоретические методы.

  1. Абстрагирование - установление общих свойств и сторон объекта (или объектов), замещение объекта или системы ее моделью. Абстракция в математике понимается в двух смыслах: а) абстракция, абстрагирование - метод исследования некоторых явлений, объектов, позволяющий как выделить основные, наиболее важные для исследования свойства, стороны исследуемого объекта или явления, так и игнорировать несущественные и второстепенные; б) абстракция - описание, представление объекта (явления), получаемое с помощью метода абстрагирования; особо важно в информатике такое понятие как абстракция потенциальной осуществимости, которое позволяет нам исследовать конструктивно объекты, системы с потенциальной осуществимостью (т.е. они могли бы быть осуществимы, если бы не было ограничений по ресурсам); используются и абстракция актуальной бесконечности (существования бесконечных, неконструктивных множеств, систем и процессов), а также абстракция отождествления (возможности отождествления любых двух одинаковых букв, символов любого алфавита, объектов, независимо от места их появления в словах, конструкциях, хотя их информационная ценность при этом может быть различна).
  2. Анализ - разъединение системы на подсистемы с целью выявления их взаимосвязей.
  3. Декомпозиция - разъединение системы на подсистемы с сохранением их взаимосвязей с окружением.
  4. Синтез - соединение подсистем в систему с целью выявления их взаимосвязей.
  5. Композиция - соединение подсистем в систему с сохранением их взаимосвязей с окружением.
  6. Индукция - получение знания о системе по знаниям о подсистемах; индуктивное мышление: распознавание эффективных решений, ситуаций и затем проблем, которые оно может разрешать.
  7. Дедукция - получение знания о подсистемах по знаниям о системе; дедуктивное мышление: определение проблемы и затем поиск ситуации, его разрешающей.
  8. Эвристики, использование эвристических процедур - получение знания о системе по знаниям о подсистемах системы и наблюдениям, опыту.
  9. Моделирование (простое моделирование) и/или использование приборов - получение знания об объекте с помощью модели и/или приборов; моделирование основывается на возможности выделять, описывать и изучать наиболее важные факторы и игнорировать при формальном рассмотрении второстепенные.
  10. Исторический метод - поиск знаний о системе путем использования ее предыстории, реально существовавшей или же мыслимой, возможной (виртуальной).
  11. Логический метод - метод поиска знаний о системе путем воспроизведения ее некоторых подсистем, связей или элементов в мышлении, в сознании.
  12. Макетирование - получение информации по макету объекта или системы, т.е. с помощью представления структурных, функциональных, организационных и технологических подсистем в упрощенном виде, сохраняющем информацию, которая необходима для понимания взаимодействий и связей этих подсистем.
  13. Актуализация - получение информации с помощью активизации, инициализации смысла, т.е. переводом из статического (неактуального) состояния в динамическое (актуальное) состояние; при этом все необходимые связи и отношения (открытой) системы с внешней средой должны быть учтены (именно они актуализируют систему).
  14. Визуализация - получение информации с помощью наглядного или визуального представления состояний актуализированной системы; визуализация предполагает возможность выполнения в системе операции типа "передвинуть", "повернуть", "укрупнить", "уменьшить", "удалить", "добавить" и т.д. (как по отношению к отдельным элементам, так и к подсистемам системы). Это метод визуального восприятия информации.

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

Охарактеризуем кратко теоретические методы.

  1. Восхождение от абстрактного к конкретному - получение знаний о системе на основе знаний о ее абстрактных проявлениях в сознании, в мышлении.
  2. Идеализация - получение знаний о системе или о ее подсистемах путем мысленного конструирования, представления в мышлении систем и/или подсистем, не существующих в действительности.
  3. Формализация - получение знаний о системе с помощью знаков или же формул, т.е. языков искусственного происхождения, например, языка математики (или математическое, формальное описание, представление).
  4. Аксиоматизация - получение знаний о системе или процессе с помощью некоторых, специально для этого сформулированных аксиом и правил вывода из этой системы аксиом.
  5. Виртуализация - получение знаний о системе созданием особой среды, обстановки, ситуации (в которую помещается исследуемая система и/или ее исследующий субъект), которую реально, без этой среды, невозможно реализовать и получить соответствующие знания.

 

Знание

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

Пример знания:

Цены на бальзам после бритья "Dune", 100 мл. 8 апреля 2000 г. (по Москве)

-------------------------------------------------------------------------

В салоне Christian Dior (Тверская ул.): 812 рублей;

В салоне Rivoli (Торговый комплекс на Манежной площади): 930 рублей;

В салоне Л'Этуаль (Рамстор на Молодёжной): 944 рубля.

 

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

В итоге мы имеем следующую простую формулу:

знание = информация + сравнение

 

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

 

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

В работе информационной системы можно выделить следующие этапы:

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

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

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

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

4. Отображение данных — представление их в форме, пригодной для восприятия человеком. Прежде всего — это вывод на печать, то есть создание документов на так называемых твердых (бумажных) носителях. Широко используют построение графических иллюстративных материалов (графиков, диаграмм) и формирование звуковых сигналов.

 

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

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

 

 

Информационные технологии

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

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

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


Понятие алгоритма. Свойства и классы алгоритмов. Формы представления алгоритмов

 

Программа - конкретная формулировка абстрактных алгоритмов, основанная на конкретных представлениях и структурах данных.

 

Алгоритм — точное и понятное предписание исполнителю совершить последовательность действий, направленных на решение поставленной задачи.

 

Исполнитель алгоритма — это некоторая абстрактная или реальная (техническая, биологическая или биотехническая) система, способная выполнить действия, предписываемые алгоритмом.

Основные свойства алгоритмов следующие:

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

Дискpетность (прерывность, раздельность) — т.е. алгоpитм должен пpедставлять пpоцесс pешения задачи как последовательное выполнение пpостых (или pанее опpеделенных) шагов (этапов).

Опpеделенность — т.е. каждое пpавило алгоpитма должно быть четким, однозначным и не оставлять места для пpоизвола. Благодаpя этому свойству выполнение алгоpитма носит механический хаpактеp и не тpебует никаких дополнительных указаний или сведений о pешаемой задаче.

Pезультативность (или конечность). Это свойство состоит в том, что алгоpитм должен пpиводить к pешению задачи за конечное число шагов.

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

Алгоритм может быть определен не для любой задачи; для некоторых задач может быть несколько алгоритмов.

Классы алгоритмов:

1.    Вычислительные – исходные данные простые и их немного; процесс вычислений долгий и сложный. Матем. и физич. задачи.

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

3.    Управляющие – исходные данные поступают от процесса или объекта, которым управляют. Результат – воздействие на процесс. Автоматизированное управление технолог процессами (участие человека на определенных этапах; автоматическое – без участия).

4.    Реального времени – результат должен быть получен к определенному времени.

Примеры алгоритмов:

 

m

n

 

30

18

 

12

18

 

12

6

d=

6

6

 

1. Алгоритм Эвклида

d=НОД(m,n)                                              

 

 

Алгоритм:

шаг 1: Ввести два целых положительных числа.

шаг 2: Если числа равны, то взять в качестве ответа первое, и на этом закончить

выполнение алгоритма.

шаг 3: Определить большее из двух.

шаг 4: Заменить большее число на разность

             большего и меньшего чисел.

шаг 5: Перейти к шагу 2.

 

2. Алгоритм решения линейного уравнения

  ax+b=0

 

1)    x=-b/a, a¹0;

2)    a=0 и b¹0 – решений нет;

3)    a=0 и b=0 – решений бесконечное множество

 

шаг 1: Ввести a и b.

шаг 2: Если a=0, перейти к шагу 4.

шаг 3: Вычислить x=-b/a и закончить выполнение алгоритма.

шаг 4: Если b=0, перейти к шагу 6.

шаг 5: Решений нет. Закончить выполнение алгоритма.

шаг 6: Любое x – решение. Закончить выполнение алгоритма.          

Формы представления алгоритмов:

·                    словесная (записи на естественном языке);

Словесный способ не имеет широкого распространения по следующим причинам:

* такие описания строго не формализуемы;

* страдают многословностью записей;

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

·                    графическая (изображения из графических символов);

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

Такое графическое представление называется схемой алгоритма или блок-схемой.

 

Блок Пуск-останов. Начало, конец алгоритма, вход и выход в подпрограмму

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

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

Блок Документ. Вывод результатов на печать

·                    псевдокоды (полуформализованные описания алгоритмов на условном алгоритмическом языке, включающие в себя как элементы языка программирования, так и фразы естественного языка, общепринятые математические обозначения и др.);

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

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

Рассмотрим базовые конструкции:

Псевдокод

алгоритм  линейное уравнение

         начало

              ввод   a, b

              если   a=0,  то  если b=0 то вывод

                                               «Любое х решение»

                                                      иначе вывод

                                               «Решений нет»

                                        все-если

                                  иначе начало

                                                  x=-b/a

                                                  вывод х

                                             конец

              все-если

         конец

 

·                    программная (тексты на языках программирования).

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

В группу языков низкого уровня входят машинные языки и языки символического кодирования: (Автокод, Ассемблер).

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

Машинно-ориентированные языки – это языки, наборы операторов и изобразительные средства которых существенно зависят от особенностей ЭВМ (внутреннего языка, структуры памяти и т.д.).

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

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

Среди процедурных языков выделяют в свою очередь структурные и операционные языки. В структурных языках одним оператором записываются целые алгоритмические структуры: ветвления, циклы и т.д. В операционных языках для этого используются несколько операций. Широко распространены следующие структурные языки: Паскаль, Си, Ада, ПЛ/1. Среди операционных известны Фортран, Бейсик, Фокал.

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

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

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

Можно выделить еще один класс языков программирования - объектно-ориентированные языки высокого уровня. На таких языках не описывают подробной последовательности действий для решения задачи, хотя они содержат элементы процедурного программирования. Примером такого языка может служить язык программирования визуального общения Object Pascal.

Языки описания сценариев, такие как Perl, Python, Rexx, Tcl и языки оболочек UNIX, предполагают стиль программирования, весьма отличный от характерного для языков системного уровня. Они предназначаются не для написания приложения с нуля, а для комбинирования компонентов, набор которых создается заранее при помощи других языков. Развитие и рост популярности Internet также способствовали распространению языков описания сценариев. Так, для написания сценариев широко употребляется язык Perl, а среди разработчиков Web-страниц популярен JavaScript.

 

К языкам сверхвысокого уровня можно отнести лишь Алгол-68 и APL. Повышение уровня этих языков произошло за счет введения сверхмощных операций и операторов.

 


Понятие алгоритма. Базовые алгоритмические структуры

 

Команды алгоритма можно разделить на простые (определяют 1 шаг алгоритма, переработки (отображения информации) – ввод/вывод, присваивание) и составные (формируются из простых команд).

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

следование, ветвление, цикл.

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

начало

        Команда 1

        Команда 2

        Команда N

конец

В Си: {

            Оператор 1

            Оператор 2

            Оператор N

           }

 

Задача: упорядочить a, b, c: a£b£c.

 

 

2. Базовая структура ветвление. Обеспечивает в зависимости от результата проверки условия (да или нет) выбор одного из альтернативных путей работы алгоритма. Каждый из путей ведет к общему выходу, так что работа алгоритма будет продолжаться независимо от того, какой путь будет выбран.

Структура ветвление существует в четырех основных вариантах:

  • если-то;
  • если-то-иначе;
  • выбор;
  • выбор-иначе.

 

 

 

 

Структура ветвления

 

1. если-то

2. если-то-иначе

3. выбор

4. выбор-иначе

 

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

1) Цикл типа пока.

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

 

 «Пока»

Вычисление факториала

 

f = n! = 1*2*3*…*(n-1)*n.

 


Представление данных в памяти персонального компьютера. 

 

Вычислительная техника первоначально возникла как средство автоматизации вычислений, о чем совершенно недвусмысленно говорит название ЭВМ.

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

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

Компьютер по определению способен хранить только дискретную информацию.

Дискретность (от лат. discretus – разделенный, прерывистый) – прерывность; противопоставляется непрерывности. Напр., дискретное изменение к.-л. величины во времени – это изменение, происходящее через определенные промежутки времени (скачками); система целых (в противоположность системе действительных чисел) является дискретной.

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

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

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

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

Целые числа. Как мы уже знаем, этот тип информации является дискретным и преобразуется для хранения в компьютере довольно просто: достаточно перевести число в двоичную систему счисления и дополнить полученный результат слева нулями до стандартной разрядности. Например, восьмиразрядное число 14 будет иметь вид

0000 1110

Это же самое число в 16-разрядном представлении будет иметь слева еще 8 нулей.

Не менее просто определить минимальное и максимальное значение чисел для N-разрядного беззнакового целого: минимальное состоит из одних нулей, а значит, при любом N равняется нулю; максимальное, напротив, образовано одними единицами и, разумеется, для разных N различно. Для вычисления максимально допустимого значения обычно используют формулу

Max = 2N – 1

 

Ниже приведена таблица максимальных значений для некоторых практически интересных N.

Таблица. Максимальные значения для беззнаковых целых N-разрядных чисел

N

8

16

32

64

Max

255

65 535

4 294 967 295

18 446 744 073 709 551 615

В связи с обсуждением граничных значений определенный интерес представляет проблема перехода через эти значения. Если взять самый простой, 8-разрядный случай, то это будут действия 255+1 и 0-1.

 

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

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

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

·         модуль числа перевести в двоичную форму;

·         проинвертировать каждый разряд получившегося кода, т.е. заменить единицы нулями, а нули – единицами (попутно заметим, что такой код называется обратным);

·         к полученному результату обычным образом прибавит единицу.

Например, переведем число –8 в двоичный 8-разрядный код. Возьмем модуль числа и дополним его до необходимого числа разрядов нулями слева:

0000 1000


Теперь проинвертируем:

1111 0111


и, наконец, прибавим единицу. Получим окончательный ответ

1111 1000

 

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

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

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

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

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

В математике уже существовал подходящий способ записи, основанный на том факте, что любое число A в системе счисления с основанием Q можно записать в виде

A = (±M) * Q ±P ,


где M называют мантиссой, а показатель степени P - порядком числа.

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

3*108 = 30*107 = 0,3*109 = 0,03*1010 = ...

Поэтому договорились для выделения единственного варианта записи числа считать, что мантисса всегда меньше единицы, а ее первый разряд содержит отличную от нуля цифру – в нашем примере обоим требованиям удовлетворит только число 0,3*109. Описанное представление чисел называется нормализованным и является единственным. Любое число может быть легко нормализовано.

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

Все сказанное о нормализации можно применять и к двоичной системе:

A = (± M) * 2 ±P,   причем ½ ≤ M <1.

Таким образом, мы видим, что при использовании метода представления вещественных чисел с плавающей запятой фактически хранится два числа: мантисса и порядок. Разрядность первой части определяет точность вычислений, а второй – диапазон представления чисел.

К описанным выше общим принципам представления вещественных чисел необходимо добавить правила кодирования мантиссы (особенно отрицательной) и порядка. Эти правила могут отличаться для различных машин. Для IBM PC мантисса хранится в прямом коде, а для хранения порядка используется так называемый сдвиг: к значению порядка прибавляется некоторая константа. В результате все значения порядка будут положительными беззнаковыми числами.

Символы. Это еще одна дискретная величина, поскольку компьютер оперирует с определенным ограниченным набором символов. Такой набор вполне можно назвать алфавитом машины, а в алфавите все символы имеют свои фиксированные позиции. Отсюда основная идея хранения символов в памяти ЭВМ состоит в замене каждого из них номером в алфавите, т.е. числом. При выводе символа компьютер по номеру определяет, как его надо изобразить на экране или на бумаге. Раньше для каждого символа хранилась его растровая картинка, т.е. некоторая матрица из черных и белых точек, окрашенных в соответствии с начертанием символа. Учитывая, что размеры всех символов были одинаковыми, ориентироваться в такой таблице (ее было принято по научному называть знакогенератором) компьютеру было нетрудно. Главным недостатком подобного метода вывода текста было то, что начертания букв и их размер оказывались жестко зафиксированными. На первых порах с этим мирились, но постоянное увеличение обработки текстов на компьютере потребовало новых принципов организации шрифтов. На современном этапе для каждого символа хранится не его начертание, а своеобразная программа, его порождающая (векторный способ создания изображений). Геометрические параметры этих "программ" могут легко изменяться, что обеспечивает быстрое и удобное масштабирование шрифтов.

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

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

Звук. Звуковая информация также является величиной непрерывной, и, следовательно, для ввода в ЭВМ нуждается в дискретизации. Причем дискретизация должна производится как по времени, так и по величине интенсивности звука. Первый процесс означает, что замеры интенсивности должны производится не непрерывно, а через определенные промежутки времени, а второй – что интенсивность звука, которая в природе может принимать какие угодно значения, должна быть "подтянута" ("округлена") к ближайшему из стандартного набора фиксированных значений. При такой процедуре мы снова получаем последовательность целых чисел, которые и сохраняются в памяти ЭВМ. Таким образом, и в случае звука информацию удается описать определенным образом сформированной последовательностью чисел, что автоматически решает проблему кодирования.

 

 


Принципы обработки программных кодов

 

Микропроцессор   это интегральная микросхема, которая управляет всем, что происходит в компьютере.

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

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

Электрические сигналы, с которыми имеет дело компьютер, могут иметь только два состояния (в зависимости от уровня напряжения): высокий уровень напряжения— электрический сигнал есть (состояние «включен») либо низкий уровень— электрический сигнал отсутствует (состояние «выключен»). Для того чтобы выполнить любую задачу, мы задаем микропроцессору последовательность сигналов в состоянии «включен» или «выключен».

Для того чтобы задавать инструкции компьютеру на самом низком уровне, используется цифра 0, означающая состояние «выключен», и цифра 1, означающая состояние «включен». Мы называем это двоичными цифрами (битами)* или двоичными кодами, так как они основаны на двоичной системе счисления, в которой, как известно, все числа представлены только при помощи комбинаций нулей и единиц.

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

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

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

Мнемонические коды— это несложные для запоминания слова или аббревиатуры, представляющие завершенное задание для микропроцессора. Например, код MOV указывает компьютеру на то, что некую информацию следует переместить из одной области памяти в другую, а код JMP указывает, что необходимо перейти в другую область памяти. Таким образом, вместо того, чтобы составлять последовательные ряды 0 и 1, программист на ассемблере может использовать мнемонические коды, подобные приведенным выше, каждый из которых представляет собой восемь или более бит.

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

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

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

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

Компиляторы

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

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

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

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

Си, Си++, Паскаль, Кобол и Фортран— это примеры компилирующих языков.

Интерпретатор

Интерпретатор переводит компьютеру все инструкции непосредственно в момент их выполнения.

Программа, обрабатываемая интерпретатором, существует только в виде исходного текстового файла. Язык BASIC, который поставляется с операционной системой MS-DOS, является примером интерпретирующего языка.

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

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

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


 

Язык С. История развития. Основные свойства языка

Язык Си (C) был создан в начале 70-х годов Дэннисом Ритчи, который работал в компании Bell Telephone Laboratories. Родословная языка Си (C) берет свое начало от языка Алгол и включает в себя Паскаль и ПЛ/I.

В 1978 году Ричи и Керниган опубликовали первую редакцию книги «Язык программирования Си». Эта книга, известная среди программистов как «K&R», служила многие годы неформальной спецификацией языка.

В конце 1970-х годов Си начал вытеснять Бейсик с позиции ведущего языка для программирования микрокомпьютеров. В 1980-х годах он был адаптирован для использования в IBM PC, что привело к резкому росту его популярности. В то же время Бьярне Страуструп и другие в лабораториях Bell Labs начали работу по добавлению в Си возможностей объектно-ориентированного программирования. Язык, который они в итоге сделали, C++, в настоящее время является самым распространённым языком программирования для платформы Microsoft Windows. Си остаётся более популярным в UNIX-подобных системах.

В 1983 году Американский Национальный Институт Стандартизации (ANSI) сформировал комитет для разработки стандартной спецификации Си. По окончании этого долгого и сложного процесса в 1989 году он был наконец утверждён как «Язык программирования Си» ANSI X3.159-1989. Эту версию языка принято называть ANSI C.

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

Язык программирования, известный как Си++— это надмножество языка Си. Реально он не является новым языком, так как включает все операторы и средства языка Си, добавив только некоторые новые. Изучая Си, вы по большей части одновременно изучаете и язык Си++. Преимущество Си++ в том, что он позволяет с большей легкостью разрабатывать большие сложные программы за счет более модульного подхода и некоторых других усовершенствований. Кроме того, Си++ является языком объектно-ориентированного программирования.

 

Отличительные особенности языка C

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

Скорость

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

Кроме того, большинство компиляторов Си генерируют высоко оптимизированные коды. Вы помните, что компьютеру необходимы двоичные коды? Чем меньше этих кодов генерирует компилятор, тем более оптимизированным является код и тем быстрее работает программа. Многие компиляторы других языков генерируют менее оптимизированные коды, так что их программы работают медленнее.

Переносимость

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

Си использует стандартные наборы ключевых слов. В общем случае вы пишете программу один раз, безотносительно того на какой платформе (с каким компьютером или операционной системой) собираетесь ее использовать. Тем не менее, хотя исходный файл сохраняется без изменений, необходимы два компилятора: один, чтобы перевести программу в двоичные коды, которые понимает IBM, другой, чтобы перевести программу в двоичные коды для Apple. Но сам текст программы вы создали раз и навсегда.

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

Структурирование

Каким бы легким для изучения ни был язык Си, у него есть свои требования. Делая программу на интерпретирующем языке BASIC, можно сидеть у компьютера и писать текст прямо «из головы». В Си это не так-то просто. Язык Си имеет свою структуру и правила создания программы, которые заставляют программиста мыслить логически. Можно обойтись без серьезного структурирования и быстро написать «корявую» программу, сравнительно простую и небольшого размера, но чтобы создать действительно серьезную программу на Си, требуется прежде всего хорошо подумать. Необходимость структурирования, однако, далеко не является обузой. Благодаря этому качеству программу на Си очень легко проектировать, поддерживать и отлаживать.

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

Язык Си как таковой содержит небольшое количество операций. В отличие от других языков, Си не имеет встроенных средств ввода и вывода информации или работы со строками (строка— это последовательность символов, образующих слово или фразу). Например, первоначальный стандарт языка Си имел только 27 ключевых слов (keywords) или команд.

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

Существуют функции, которые используются так часто, что вместе со многими компиляторами поставляются их исходные тексты. Они содержатся в файлах, которые называются файлами заголовков (header file) и обычно имеют расширение .H. Файлы заголовков также содержат директивы компилятору и инструкции, указывающие использовать конкретные определения. Во время процесса компиляции содержимое файла заголовков добавляется к вашей собственной программе и для него также создаются объектные коды.

Файлы заголовков, в отличие от библиотечных файлов, не откомпилированы. Так же как и ваш исходный файл с текстом программы на Си, их можно читать, печатать на принтере и редактировать. Однако вам следует остерегаться вносить изменения в файлы заголовков, поставляемые с компилятором. Если вы сделаете ошибку, то компилятор больше не сможет генерировать для них объектные коды.

Именно использование библиотечных файлов делает Си легко переносимым.

 

 

 

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

Транслятор - в широком смысле - программа, преобразующая текст, написанный на одном языке, в текст на другом языке.

Лексема - единица текста программы, которая при компиляции воспринимается, как единое целое и по смыслу не может быть разделена на более мелкие элементы.

Элементы языка C

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

Элементы языка Си:

символы;
константы;
идентификаторы;
ключевые слова;

комментарии.

Используемые символы

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

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

 

Таблица 1

Прописные буквы латинского алфавита

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Строчные буквы латинского алфавита

a b c d e f g h i j k l m n o p q r s t u v w x y z

Символ подчеркивания

_

 

2. Группа прописных и строчных букв русского алфавита и арабские цифры (табл.2).

 

Таблица 2

Прописные буквы русского алфавита

А Б В Г Д Е Ж З И К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ы Ь Э Ю Я

Строчные буквы русского алфавита

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

Арабские цифры

0 1 2 3 4 5 6 7 8 9

 

3. Знаки нумерации и специальные символы (табл. 3). Эти символы используются с одной стороны для организации процесса вычислений, а с другой - для передачи компилятору определенного набора инструкций.

 

Таблица 3

Символ

Наименование

Символ

Наименование

,

запятая

)

круглая скобка правая

.

точка

(

круглая скобка левая

;

точка с запятой

}

фигурная скобка правая

:

двоеточие

{

фигурная скобка левая

?

вопросительный знак

< 

меньше

'

апостроф

> 

больше

!

восклицательный знак

[

квадратная скобка

|

вертикальная черта

]

квадратная скобка

/

дробная черта

#

номер

\

обратная черта

%

процент

~

тильда

&

амперсанд

*

звездочка

^

логическое не

+

плюс

=

равно

-

мину

"

кавычки

 

4. Управляющие и разделительные символы. К той группе символов относятся: пробел, символы табуляции, перевода строки, возврата каретки, новая страница и новая строка. Эти символы отделяют друг от друга объекты, определяемые пользователем, к которым относятся константы и идентификаторы. Последовательность разделительных символов рассматривается компилятором как один символ (последовательность пробелов).

 

5. Кроме выделенных групп символов в языке СИ широко используются так называемые, управляющие последовательности, т.е. специальные символьные комбинации, используемые в функциях ввода и вывода информации. Управляющая последовательность строится на основе использования обратной дробной черты (\) (обязательный первый символ) и комбинацией латинских букв и цифр (табл.4).

Таблица 4

Управляющая последовательность

Наименование

Шестнадцатеричная замена

\a

Звонок

007

\b

Возврат на шаг

008

\t

Горизонтальная табуляция

009

\n

Переход на новую строку

00A

\v

Вертикальная табуляция

00B

\r

Возврат каретки

00C

\f

Перевод формата

00D

\"

Кавычки

022

\'

Апостроф

027

\0

Ноль-символ

000

\\

Обратная дробная черта

05C

\ddd

Символ набора кодов ПЭВМ в восьмеричном представлении

 

\xddd

Символ набора кодов ПЭВМ в шестнадцатеричном представлении

 

Последовательности вида \ddd и \xddd (здесь d обозначает цифру) позволяет представить символ из набора кодов ПЭВМ как последовательность восьмеричных или шестнадцатеричных цифр соответственно. Например, символ возврата каретки может быть представлен различными способами:

\r - общая управляющая последовательность,

\015 - восьмеричная управляющая последовательность,

\x00D - шестнадцатеричная управляющая последовательность.

Следует отметить, что в строковых константах всегда обязательно задавать все три цифры в управляющей последовательности. Например отдельную управляющую последовательность \n (переход на новую строку) можно представить как \010 или \xA, но в строковых константах необходимо задавать все три цифры, в противном случае символ или символы следующие за управляющей последовательностью будут рассматриваться как ее недостающая часть. Например:

"ABCDE\x009FGH" данная строковая команда будет напечатана с использованием определенных функций языка СИ, как два слова ABCDE FGH, разделенные 8-ю пробелами, в этом случае если указать неполную управляющую строку"ABCDE\x09FGH",то на печати появится ABCDE=|=GH, так как компилятор воспримет последовательность \x09F как символ "=+=".

Отметим тот факт, что, если обратная дробная черта предшествует символу не являющемуся управляющей последовательностью (т.е. не включенному в табл.4) и не являющемуся цифрой, то эта черта игнорируется, а сам символ представляется как литеральный. Например:

символ \h представляется символом h в строковой или символьной константе.

Кроме определения управляющей последовательности, символ обратной дробной черты (\) используется также как символ продолжения. Если за (\) следует (\n), то оба символа игнорируются, а следующая строка является продолжением предыдущей. Это свойство может быть использовано для записи длинных строк.

 

Константы

Константами называются перечисление величин в программе. В языке СИ разделяют четыре типа констант: целые константы, константы с плавающей запятой, символьные константы и строковыми литералы.

Целая константа: это десятичное, восьмеричное или шестнадцатеричное число, которое представляет целую величину в одной из следующих форм: десятичной, восьмеричной или шестнадцатеричной.

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

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

Шестнадцатеричная константа начинается с обязательной последовательности 0х или 0Х и содержит одну или несколько шестнадцатеричных цифр (цифры представляющие собой набор цифр шеснадцатеричной системы счисления: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)

      Примеры целых констант:
         Десятичная      Восьмеричная       Шестнадцатеричная
         константа       константа          константа
             16             020                0x10
            127             0117               0x2B
            240             0360               0XF0

Если требуется сформировать отрицательную целую константу, то используют знак "-" перед записью константы (который будет называться унарным минусом). Например: -0x2A, -088, -16 .

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

- десятичные константы рассматриваются как величины со знаком, и им присваивается тип int (целая) или long (длинная целая) в соответствии со значением константы. Если константа меньше 32768, то ей присваивается тип int в противном случае long.

- восьмеричным и шестнадцатеричным константам присваивается тип int, unsigned int (беззнаковая целая), long или unsigned long в зависимости от значения константы согласно табл 5.

 

 

 

Таблица 5

Диапазон шестнадцатеричных констант

Диапазон восьмеричных констант

Тип

0x0 - 0x7FFF

0 - 077777

int

0X8000 - 0XFFFF

0100000 - 0177777

unsigned int

0X10000 - 0X7FFFFFFF

0200000 - 017777777777

long

0X80000000 - 0XFFFFFFFF

020000000000 - 037777777777

unsigned long

Для того чтобы любую целую константу определить типом long, достаточно в конце константы поставить букву "l" или "L". Пример:

5l, 6l, 128L, 0105L, OX2A11L.

Константа с плавающей точкой - десятичное число, представленное в виде действительной величины с десятичной точкой или экспонентой. Формат имеет вид:

[ цифры ].[ цифры ] [ Е|e [+|-] цифры ] .

Число с плавающей точкой состоит из целой и дробные части и (или) экспоненты. Константы с плавающей точкой представляют положительные величины удвоенной точности (имеют тип double). Для определения отрицательной величины необходимо сформировать константное выражение, состоящее из знака минуса и положительной константы.

Примеры: 115.75, 1.5Е-2, -0.025, .075, -0.85Е2

Символьная константа - представляется символом заключенном в апострофы. Управляющая последовательность рассматривается как одиночный символ, допустимо ее использовать в символьных константах. Значением символьной константы является числовой код символа. Примеры:

' '- пробел ,

'Q'- буква Q ,

'\n' - символ новой строки ,

'\\' - обратная дробная черта ,

'\v' - вертикальная табуляция .

Символьные константы имеют тип int и при преобразовании типов дополняются знаком.

Строковая константа (литерал) - последовательность символов (включая строковые и прописные буквы русского и латинского а также цифры) заключенные в кавычки (") . Например: "Школа N 35", "город Тамбов", "YZPT КОД".

Отметим, что все управляющие символы, кавычка ("), обратная дробная черта (\) и символ новой строки в строковом литерале и в символьной константе представляются соответствующими управляющими последовательностями. Каждая управляющая последовательность представляется как один символ. Например, при печати литерала "Школа \n N 35" его часть "Школа" будет напечатана на одной строке, а вторая часть "N 35" на следующей строке.

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

Строковый литерал имеет тип char[] . Это означает, что строка рассматривается как массив символов. Отметим важную особенность, число элементов массива равно числу символов в строке плюс 1, так как нулевой символ (символ конца строки) также является элементом массива. Все строковые литералы рассматриваются компилятором как различные объекты. Строковые литералы могут располагаться на нескольких строках. Такие литералы формируются на основе использования обратной дробной черты и клавиши ввод. Обратная черта с символом новой строки игнорируется компилятором, что приводит к тому, что следующая строка является продолжением предыдущей. Например:

"строка неопределенной \n

длины"

полностью идентична литералу

"строка неопределенной длинны" .

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

 

Идентификатор

Идентификатором называется последовательность цифр и букв, а также специальных символов, при условии, что первой стоит буква или специальный символ. Для образования идентификаторов могут быть использованы строчные или прописные буквы латинского алфавита. В качестве специального символа может использоваться символ подчеркивание (_). Два идентификатора для образования которых используются совпадающие строчные и прописные буквы, считаются различными. Например: abc, ABC, A128B, a128b .

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

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

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

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

 

Ключевые слова

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

Приведем список ключевых слов

 

   auto      double     int   struct  break   else   long   switch
   register  tupedef    char  extern  return  void   case   float
   unsigned  default    for   signed  union   do     if     sizeof
   volatile  continue   enum  short   while
 

Кроме того, в рассматриваемой версии реализации языка СИ, зарезервированными словами являются:

_asm, fortran, near, far, cdecl, huge, paskal, interrupt .

В языке Си++ добавлены ключевые слова:

catch

cin

class

cout

delete

friend 

inline

new

operator

private

protected

 

Ключевые слова far, huge, near позволяют определить размеры указателей на области памяти. Ключевые слова _asm, cdelc, fortran, pascal служат для организации связи с функциями написанными на других языках, а также для использования команд языка ассемблера непосредственно в теле разрабатываемой программы на языке СИ.

Ключевые слова не могут быть использованы в качестве идентификаторов.

 

 

Использование комментариев в тексте программы

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

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

Неправильное определение комментариев.

     /* комментарии к алгоритму /* решение краевой задачи */ */
      или
     /* комментарии к алгоритму решения */ краевой задачи */

 Базовые типы данных

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

Тип данных определяет:

·         внутреннее представление данных в памяти компьютера;

·         множество значений, которые могут принимать величины этого типа;

·         операции и функции, которые можно применять к величинам этого типа.

Основные (стандартные) типы данных часто называют арифметическими, по­скольку их можно использовать в арифметических операциях. Для описания ос­новных типов определены следующие ключевые слова:

int (целый);

char (символьный);

wchar_t (расширенный символьный);      

bool (логический);

float (вещественный);

double (вещественный с двойной точностью).

Первые четыре типа называют целочисленными (целыми), последние два — ти­пами с плавающей точкой. Код, который формирует компилятор для обработки целых величин, отличается от кода для величин с плавающей точкой.

Существует четыре спецификатора типа, уточняющих внутреннее представле­ние и диапазон значений стандартных типов:

short (короткий);

long (длинный);

signed (знаковый);

unsigned (беззнаковый).

 

Целочисленные величины

Если вы хотите производить математические операции, то должны использовать числовой тип данных. Язык Си имеет несколько типов числовых данных в зависимости от значения, которое может быть им присвоено, и занимаемого объема памяти.

Целые числа (int, от английского integer)— это числа, не имеющие дробной части. Их значение может быть положительным, отрицательным или нулевым, но никогда не имеет в своем составе знаков после точки. В языке Си есть простая аксиома, которая гласит: «Используйте для подсчетов целые числа». Используйте целые числа всегда, когда есть возможность представить некое значение в виде целого числа, например, при подсчете количества повторов определенного события.

Каждый элемент целочисленных данных занимает в памяти столько же места, сколько два элемента символьных, независимо от величины самого числа (и число 2, и число 2000 требуют для хранения одинакового объема памяти). Но для того чтобы занимаемое место не превышало двух элементов памяти, величина целочисленных данных в языке Си ограничена. К целочисленным данным (собственно тип int) относятся величины, значение которых находится в промежутке между –32768 и +32767. Величины, значение которых выходит за эти пределы, требуют для хранения больше двух элементов памяти.

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

 

short int

короткие целые числа: положительные величины от 0 до 255

int

целые числа: величины от –32768 до +32767

long int

длинные целые числа: величины от –2147483648 до +2147483647

unsigned long

длинные целые числа без знака: положительные величины от 0 до 4294967295

По умолчанию все целочисленные типы считаются знаковыми, то есть специфи­катор signed можно опускать.

Константам, встречающимся в программе, приписывается тот или иной тип в со­ответствии с их видом. Если этот тип по каким-либо причинам не устраивает программиста, он может явно указать требуемый тип с помощью суффиксов L, 1 (long) и U, u (unsigned). Например, константа 321 будет иметь тип long и зани­мать 4 байта. Можно использовать суффиксы L и U одновременно, например, 0x22UL или 05lu.

Типы short int, long int, signed int и unsigned int можно сокращать до short, long, signed, и unsigned соответственно.

 

Символьные данные

Под величину символьного типа отводится количество байт, достаточное для размещения любого символа из набора символов для данного компьютера, что и обусловило название типа. Как правило, это 1 байт. Тип char, как и другие целые типы, может быть со знаком или без знака. В величинах со знаком можно хра­нить значения в диапазоне от -128 до 127. При использовании спецификатора unsigned значения могут находиться в пределах от 0 до 255. Этого достаточно для хранения любого символа из 256-символьного набора ASCII. Величины типа char применяются также для хранения целых чисел, не превышающих границы указанных диапазонов. Однако Си проводит различия между символом «1» и числом 1. Как символ единица не может использоваться в математических операциях, поскольку она не рассматривается в этом случае как математическая величина. Как число единица участвует в вычислениях, при этом, как вы скоро увидите, для хранения символа «1» Си отводит объем памяти вполовину меньший, чем для хранения числа 1.

 

Расширенный символьный тип (wchar_t)

Тип wchar_t предназначен для работы с набором символов, для кодировки кото­рых недостаточно 1 байта, например, Unicode. Размер этого типа зависит от реа­лизации; как правило, он соответствует типу short. Строковые константы типа wchar_t записываются с префиксом L, например, L"Gates".

 

Логический тип (bool)

Величины логического типа могут принимать только значения true и false, яв­ляющиеся зарезервированными словами. Внутренняя форма представления зна­чения false - 0 (нуль). Любое другое значение интерпретируется как true. При преобразовании к целому типу true имеет значение 1.

 

Вещественные числа

Числа, которые могут содержать десятичную часть (вещественные), называются числами с плавающей точкой (floating-point values). Для работы с ними в языке Си используется тип данных с плавающей точкой (float). Так как числа с плавающей точкой могут быть чрезвычайно маленькими или большими, для их записи часто используют экспоненциальную форму, например, значение числа с плавающей точкой может равняться 3.4е+38. Расшифровать это можно следующим образом: «передвинуть точку вправо на 38 пунктов, добавив соответствующее количество нулей». Существуют дополнительные типы данных для работы в очень широких пределах величин:

 

float

величины от 3.4Е–38 до 3.4Е+38

double

величины от 1.7Е–308 до 1.7Е+308

long double

величины от 3.4Е–4932 до 1.1Е+4932

 

Тип данных с плавающей точкой имеет предел точности, диапазон которого зависит от компилятора. Например, число 6.12345678912345 в пределах допустимого диапазона для чисел типа float может быть записано компьютером только как 6.12345. Этот тип, как принято говорить, является типом с одинарной точностью, что означает, что точность его ограничена пятью или шестью знаками после точки. Тип double принято называть типом с двойной точностью, он имеет 15–16 знаков после точки.

Для записи данных с одинарной точностью резервируется четыре элемента памяти; двойная точность требует резервирования восьми, повышенная (long double)— десяти.

 

Почему надо использовать целые числа?

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

Частично причина кроется в необходимости резервирования памяти для хранения информации. Если компьютеру не хватает памяти для выполнения вашей программы, он прекращает работу, а память стоит денег. Хорошие программисты стараются экономить память. Чем меньше ее требуется для выполнения программы, тем лучше. Использование типа int вместо float и типа char вместо строк помогает в этом.

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

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


Структура программы на C++

Файлы,  содержащие текст программы на С++, должны иметь расширение cpp.

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

 

#include <stdio.h>
{
   int a,b,c;
   a=5; b=7;
   c=a+b;
   printf("Cумма = %d \n",c)
}

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

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

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

 

main()

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

Следом за main() вводятся инструкции. Инструкции могут быть представлены в виде стандартных команд и имен функций, содержащихся в библиотеках или написанных вами самостоятельно. Прямая, или открывающая фигурная скобка { помещается перед первой инструкцией, а обратная, или закрывающая фигурная скобка } следует за последней инструкцией. Открывающая и закрывающая фигурные скобки называются ограничителями и служат для выделения части кода в единый блок. Когда вы пишете функцию, она всегда должна начинаться и заканчиваться фигурными скобками. Кроме того, отдельные блоки внутри функции также могут отмечаться при помощи своих собственных пар фигурных скобок.

Таким образом, простейшая структура программы, написанной на языке Си, такова:

 

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

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

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

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

При запуске программы компьютер начинает ее выполнение с первой инструкции функции main(). Ниже приведена завершенная программа на Си/Си++, которая выводит на экран монитора слово «OK»:

 

main()

        {

        puts("OK");

        }

 

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

 

main(){puts("OK");}

 

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

·         помещать функцию main() на отдельной строке;

·         помещать фигурные скобки на отдельных строках;

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

 

Другие правила, принятые в Си, регулируют употребление прописных и строчных букв. Команды и имена функций всегда пишутся маленькими буквами, так что следует писать puts(), но не PUTS() или Puts(). Если эта функция определена вами, то вы не получите сообщения об ошибке, но исходный текст программы не будет похож на программу, написанную на языке Си. Заглавные буквы в языке Си обычно употребляются для задания имен констант и макроопределений.

Директива #include

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

Файлы с расширением h, которые вы включаете в начало (или заголовок) вашей программы, называются заголовочными файлами. Если вы посмотрите на каталог, содержащий файлы вашего компилятора, то найдете подкаталог с именем INCLUDE, в котором находятся разные заголовочные файлы. Символы, окружающие имя файла заголовков (< и >), указывают компилятору, что данный файл может находиться в каталоге INCLUDE. Если во время компиляции файл заголовков не будет найден в текущем каталоге, компилятор будет искать его в каталоге INCLUDE. Вы можете также заключить имя файла заголовка в кавычки

 

#include "stdio.h"

 

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

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

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

Чтобы лучше понять содержимое заголовочных файлов, найдите время для того, чтобы напечатать заголовочный файл IOSTREAM.H, содержимое которого вы будете использовать в каждой создаваемой вами программе на C++. Обычно заголовочный файл IOSTREAM.H расположен в подкаталоге с именем INCLUDE, который находится в каталоге, содержащем файлы компилятора C++. Используйте текстовый редактор, чтобы просмотреть и напечатать содержимое заголовочных файлов.

Замечание: Никогда не изменяйте содержимое заголовочных файлов. Это может привести к ошибкам компиляции в каждой создаваемой вами программе.

Использование void

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

Инструкция return

Что происходит после того, как компьютер заканчивает выполнение инструкций, заданных в вашей программе? Программа завершается, и компьютер возвращается в исходное состояние, то есть если программа выполнялась из операционной системы MS-DOS, на дисплее вновь появится ее подсказка, если же программа выполнялась из среды Windows, то вы вновь возвратитесь в ее оболочку.

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

 

main()
        {
        puts("У меня все в порядке");
        puts("А у тебя?");
        return(0);
        }

Инструкция return(0) указывает компьютеру, что необходимо вернуться назад в исходную среду. При работе с большинством компиляторов включение подобной инструкции в текст программы не является обязательным, вы не получите сообщения об ошибке, даже если она отсутствует. Использование символа 0 также не является обязательным. Большинство компиляторов позволяет вместо записи return(0); использовать сокращенную запись return; без скобок. Однако если вы поставили скобки, то должны использовать полную запись, во избежание ошибки компилятора.

Если вы все-таки используете инструкцию return(0), не помещайте никаких инструкций между ней и фигурной скобкой, завершающей программу. Например, ошибкой было бы написать:

 

main()
        {
        puts("У меня все в порядке");
        return(0);
        puts("А у тебя?");
        }
 

так как в этом случае компьютер вернется в операционную систему после выполнения первой функции puts(), вы так и не дождетесь появления строки "А у тебя?".

Описание переменных

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

int a, b; int low;  char c;  float x, dl;

Переменным в описаниях можно задавать начальные значения, объединяя таким образом описание и оператор присваивания.

int  p = 1; float eps = 1.0 e-5;

 

 


Обработка данных. Операторы

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

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

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

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

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

 

Арифметические операторы

При математических вычислениях используются следующие арифметические операторы:

 

Оператор Функция оператора

+                сложение

                вычитание

*                 умножение

/                 деление

%               получение остатка от деления нацело

 

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

 

sales_tax = 4500;

 

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

 

sales_tax = amount * tax_rate;

price = cost + shipping + insurance;

per_unit = total / count;

 

Эти инструкции говорят компилятору, что следует выполнить три операции:

·         присвоить переменной sales_tax значение, полученное в результате умножения значения переменной amount на значение переменной tax_rate;

·         присвоить переменной price значение, полученное из суммы значений трех переменных: cost, shipping и insurance;

·         присвоить переменной per_unit значение, полученное в результате деления total на count.

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

 

sales_tax = amount * 0.06;

price = 56.90 + shipping + 7.87;

per_unit = 156.65 / 12.50;

 

Оператор % используется для расчета остатка от деления нацело. Если вы используете оператор деления (/) для целочисленных данных, то результат деления тоже всегда будет целым числом. Например, при делении 12 на 5 (12/5) вы получите 2, а не 2.4. Дробная часть, равная в данном случае 0.4, при делении целых чисел отбрасывается.

Разумеется, нередко возникает необходимость узнать значение остатка от деления. Пока мы имеем дело с целыми числами, мы не можем использовать значение 0.4, так как это число относится к типу float. Мы же определили результат деления как целое. В этом случае получается, что число 12 состоит из двух чисел 5, а лишняя двойка просто игнорируется. Число 2 в данном случае является остатком от деления нацело, для получения которого и используется оператор %. Остаток от деления нацело также всегда является целым числом.

Приоритет операторов и порядок вычислений

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

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

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

result =(2+3)* (3+4);

C++ вычисляет данное выражение в следующем порядке:

result = (2 + 3) * (3 + 4);
= (5) * (3 + 4);
= 5 * (7);
=5*7;
= 35;

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

 

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

Унарное выражение состоит из операнда и предшествующего ему знаку унарной операции и имеет следующий формат:

знак-унарной-операции операнд.

Бинарное выражения состоит из двух операндов, разделенных знаком бинарной операции:

операнд1 знак-бинарной-операции операнд2.

Тернарное выражение состоит из трех операндов, разделенных знаками тернарной операции (?) и (:), и имеет формат:

операнд1 ? операнд2 : операнд3 .

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

В языке Си имеются следующие унарные операции:

- арифметическое отрицание (отрицание и дополнение);

~ побитовое логическое отрицание (дополнение);

! логическое отрицание;

* разадресация (косвенная адресация);

& вычисление адреса;

+ унарный плюс;

++ увеличение (инкремент);

-- уменьшение (декремент);

sizeof размер.

Унарные операции выполняются справа налево.

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

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

 

Таблица 

Знак операции

Операция

Группа операций

*

Умножение

Мультипликативные

/

Деление

%

Остаток от деления

+

Сложение

Аддитивные

-

Вычитание

<< 

Сдвиг влево

Операции сдвига

>> 

Сдвиг вправо

< 

Меньше

Операции отношения

<=

Меньше или равно

>=

Больше или равно

==

Равно

!=

Не равно

&

Поразрядное И

Поразрядные операции

|

Поразрядное ИЛИ

^

Поразрядное исключающее ИЛИ

&&

Логическое И

Логические операции

||

Логическое ИЛИ

,

Последовательное вычисление

Последовательного вычисления

=

Присваивание

Операции присваивания

*=

Умножение с присваиванием

/=

Деление с присваиванием

%=

Остаток от деления с присваиванием

-=

Вычитание с присваиванием

+=

Сложение с присваиванием

<<=

Сдвиг влево с присваиванием

>>=

Сдвиг вправо присваиванием

&=

Поразрядное И с присваиванием

|=

Поразрядное ИЛИ с присваиванием

^=

Поразрядное исключающее ИЛИ с присваиванием

Левый операнд операции присваивания должен быть выражением, ссылающимся на область памяти (но не объектом, объявленным с ключевым словом const).

Используемые алгоритмы обработки данных

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

Счетчики

Счетчик — это переменная, которая увеличивает свое значение на единицу каждый раз, когда происходит определенное событие. Алгоритм счетчика таков:

 

variable = variable + 1

 

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

 

Новое значение переменной равно старому значению плюс 1

 

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

 

int count;
count=0;
 

Теперь вступает в действие алгоритм

 

count = count + 1;
 

Компьютер выполняет эту инструкцию так:

 

count = 0 + 1
 

К начальному значению переменной count, которое равно 0, добавлен литерал, имеющий значение 1. В результате вычислений получено значение 1, которое теперь присваивается переменной count. Значение переменной изменяется с 0 на1. Затем та же процедура повторяется снова:

 

count = count + 1;

 

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

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

 

variable = variable – 1
 

Теперь при каждом выполнении операции значение переменной будет уменьшаться на единицу (декремент).

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

 

variable = variable + 1;

 

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

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

 

printf("Второе значение переменной 
               count равно %d\n", count+1);
 

Выражение count+1 не изменяет значения, присвоенного переменной count. В результате выполнения этой инструкции значение переменной, увеличенное на единицу, только отображается на экране, но не заносится в память.

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

 

number = ++count;
 

те же операции можно было выполнить и так:

 

count = count + 1;
number = count;
 

Оператор ++ можно использовать с именем переменной как инструкцию:

 
++number;

 

Если же знаки ++ помещены справа от имени переменной,

 
variable++;

 

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

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

 

--variable
 

уменьшает значение переменной на 1 до выполнения инструкции

 

variable—
 

уменьшает значение переменной на 1 после выполнения инструкции.

Аккумуляторы

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

 

variable = variable + other_variable;

 

Аккумулятор получил такое название оттого, что он накапливает значение переменной.

Посмотрите на следующий пример:

 
int total, number;
total = 0;
scanf("%d", &number);
total = total + number;
 

Допустим, что переменной number присвоено значение 10. После выполнения инструкции

 

total=total+number;
 

переменная total приобретает значение 10, так как компьютер выполнил операцию сложения, используя следующие значения:

 

total=0+10;
 

Теперь предположим, что снова происходит ввод данных с помощью функции scanf() и выполняется новая операция суммирования, но на этот раз переменной number присвоено значение 15:

 

scanf("%d", &number);
total = total + number;
 

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

 

total = 10 + 15;
 

Произошло накопление значений переменной number.

Операторы присваивания   

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

 

Оператор

Пример

Эквивалент

+=

total += amount

total = total + amount

–=

total –= discount

total = total – discount

*=

total *= tax_rate

total = total * tax_rate

/=

total /= count

total = total / count

%=

total %=count

total = total % count

 

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

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

Преобразования типов данных

Разнообразие типов данных в языке C++ дает программисту возможность выбирать вариант, соответствующий конкретной потребности. Однако такое разнообразие, с дру­гой стороны, усложняет задачу компьютера. Например, сложение двух чисел типа short может выполняться с помощью иных машинных команд, чем сложение двух чисел типа long.

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

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

 

main()
{
int total;
float cost, shipping;
cost = 56.09;
shipping = 4.98;
total = cost + shipping;
printf("Общая стоимость составляет
 сумму %d долларов", total);
}
 

В операции сложения участвуют две переменные типа float (cost и shipping), но полученный результат присваивается целочисленной переменной total. Если сложить эти числа на калькуляторе, то в результате получим 61.07, но так как переменная total— целочисленная, то и результат будет преобразован в целое число. Использование указателя формата %d задает отображение на экране целого числа 61. Обратите внимание, вначале выполняется математическое действие, а затем происходит присваивание значения. Если бы заданные значения преобразовывались в целые числа до их сложения, то результат оказался бы равен 60 (56+4).

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

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

Рассмотрим общие арифметические преобразования.

1. Операнды типа float преобразуются к типу double.

2. Если один операнд long double, то второй преобразуется к этому же типу.

3. Если один операнд double, то второй также преобразуется к типу double.

4. Любые операнды типа char и short преобразуются к типу int.

5. Любые операнды unsigned char или unsigned short преобразуются к типу unsigned int.

6. Если один операнд типа unsigned long, то второй преобразуется к типу unsigned long.

7. Если один операнд типа long, то второй преобразуется к типу long.

8. Если один операнд типа unsigned int, то второй операнд преобразуется к этому же типу.

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

 

Проблемы, которые могут возникать при преобразовании типов данных:

 

Преобразование

Возможные проблемы

Данные с плавающей точкой большей размерности в данные с плавающей точкой меньшей размерности

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

Данные с плавающей точкой

в целочисленные данные

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

неопределенным

Целочисленные данные большей размерности в целочисленные данные меньшей размерности, например, тип long в тип short

Исходное значение может не укладываться в

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

 


Функции языка C++

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

Декларации и дефиниции функций

С использованием функций в языке СИ связаны три понятия - определение функции (описание действий, выполняемых функцией), объявление функции (задание формы обращения к функции) и вызов функции.

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

 

Заголовок_функции
{ тело_функции;
}

 

Заголовок_функции имеет следующий синтаксис:

 

тип_возвращаемого_функцией_значения имяФункции (СписокФормальныхПараметров)
 

тело_функции – это набор необходимых операторов, реализующий нужный алгоритм.

Обратите внимание! Тело функции всегда заключено в фигурные скобки – блок.

 

В языке СИ нет требования, чтобы определение функции обязательно предшествовало ее вызову. Определения используемых функций могут следовать за определением функции main, перед ним, или находится в другом файле.

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

 

Декларация функции (прототип) – это ее заголовок с символом точка с запятой в конце.

Например, обе декларации идентичны:

 

void my_func (void);
void my_func ();

 

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

 

Замечание! Если декларация функции записана следующим образом:

my_func (void);

то функция по умолчанию возвращает значение типа int.

 

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

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

 

Дефиниция функции – это полная запись функции с заголовком (но БЕЗ точки с запятой!) и телом.

 

Формальные и фактические параметры. Вызов функций

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

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

В наших примерах функций my_func и pause пока нет ни формальных, ни фактических параметров. Вызовы таких функций имеют вид:

 

my_func();
pause();

 

Обратите внимание! Служебное слово типа данных здесь НЕ пишется НИКОГДА!

В простейших случаях количество и тип фактических параметров должен совпадать с количеством и типом формальных.

Возврат функцией значений

Очень часто от функции требуется, чтобы она не только выполняла определенную работу (например, вывод сообщения с помощью printf), но и возвращала результат вычислений в вызывающую функцию. Для обеспечения возврата результата в вызывающий код в функции должен использоваться оператор return, который имеет следующий формат:

 

return(result);

 

Тип функции определяет тип возвращаемого ею значения (int,float, char и т.д.). Например, если функция возвращает значение типа int, то имени функции должно предшествовать то же самое имя типа:

 

int some_function(int value)

{

    // Оператор функции

}

 

Следующая функция i_cube возвращает куб от целого значения, определенного в качестве параметра. Например, если в функцию передается 5, то i_cube будет возвращать значение равное 5*5*5 или 125:

 

int i_cube (int value)

{

    return(value * value * value);

}

 

Как можно видеть, для возврата результата вычислений в вызывающий код функцией используется оператор return. В вызывающей функции возвращаемое функцией значение может быть присвоено переменной или использовано при вызове некоторой другой функции (такой кик printf) следующим образом:

 

result = i_cube(5);

 

printf("Kyб от 5 равен %d\n", i_cube(5));

 

Если функция должна возвращать значение другого типа (float, double, char и т.д.), то этот тип необходимо задавать при объявлении функции. В следующей программе функция average_value используется для вычисления среднего арифметического трех значений типа int. При этом значение, которое возвращается функцией, имеет тип float:

 

#include <stdio.h>
 
float average_value(int a, int b, int c)
{
   return ((a + b + с) / 3.0);
}
 
void main(void) 
{
    printf("Среднее от 100, 133 и 155 равно %f\n", 
        average_value(100, 133, 155));
}

 

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

 

float average_value(int a, int b, int с)

 

Примечание: Если компилятору не сообщается тип возвращаемого функцией значения, то в качестве такового по умолчанию считается int.

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

 

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


void my_function (int age, char *name);

 

Если в дальнейшем в программе будет предпринята попытка использовать значение функции

 

result = my_function (32, "Jamsa");

 

то компилятор выдаст ошибку.

Переменные в функциях

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

Автоматические (локальные) переменные

Некоторые функции нуждаются в собственных переменных и константах.

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

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

Например, в следующей функции use_abc объявляется три локальные переменные a, b и с:

 

void use_abc(void)
{
    int a, b, с;
 
    а=3;
    b=а+1;
    с = а + b;
 
    printf("a содержит %d b содержит %d с содержит %d\n ", а, b, с);
}

При каждом вызове функции для размещения локальных переменных а, b и с Си выделяет пространство в стеке. При завершении функции пространство стека, содержащее значения этих переменных, освобождается. Какое бы количество локальных переменных ни объявлялось в функции, Си сохраняет в стеке значение каждой из них.

 

Внешние (глобальные) переменные

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

 

int temp;
main()
 

Здесь переменная temp определена как внешняя, так что с ней могут работать все функции, содержащиеся в программе.

 

main()
        {
        printf("Введите значение температуры: ");
        scanf("%d", &temp);
        convert();
        freeze();
        boil();
        }
convert()
        {
float celsius;
        celsius = (5.0 / 9.0) * (temp - 32);
        printf("%d градусов по шкале Фаренгейта
                соответствует %6.2f градусам \
        по шкале Цельсия\n", temp, celsius);
        return(0);
        }
freeze()
        {
        printf("Это составляет %d градусов
        от точки замерзания воды\n", temp-32);
        return(0);
        }

 

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

 

#include <stdio.h> 
 
void unknown_title(void)
{
    printf("Название книги %s\n ", title);
} 
char title[] = "1001 совет по C/C++";
 
void main(void)
{
    printf("Название: %s\n", title);
}

Как можно видеть, в функции unknown_title делается попытка вывести значение переменной title на экран. Однако, поскольку объявление глобальной переменной выполняется после определения функции, глобальная переменная в функции неизвестна. При компиляции этой программы будет выдаваться ошибка. Для исправления ошибки следует перенести объявление глобальной переменной в место программы, предшествующее определению функции.

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

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

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

Статические переменные

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

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

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

 
myfunc()
        {
        static int count;
        }

 

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

 

Передача параметров по значению

Когда параметр передается в функцию, по умолчанию используется техника, известная как передача параметра по значению, при которой функция обеспечивается копией значения параметра. При передаче параметра по значению любые изменения, выполняемые в функции над значением данного параметра, существуют только внутри самой функции. Когда функция завершается, значение переменной, переданной по значению, оказывается не измененным. Например, в следующей программе функции display_and_change передаются три параметра (переменные a, b и с):

 

#include <stdio.h>
 
 void display_and_change(int first, int second, int third)
{
    printf("Начальные значения функции %d %d %d\n", 
        first, second, third);
 
    first += 100;
    second +=100;
    third += 100;
 
    printf("Конечные значения функции %d %d %d\n",
        first, second, third);
}
 
void main(void) 
{
    int a=l, b=2, с = 3;
 
    display_and_change(a, b, c);
 
    printf("Конечные значения main %d %d %d\n", a, b ,c);
}
 

После компиляции и выполнения программы на экран выводится:

 

C:\> NOCHANGENTER>
Начальные значения функции 123 
Конечные значения функции 101 102 103 
Конечные значения main 123
 

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

При передаче параметров в функцию их значения размещаются в стеке. Для случая переменных a, b и с в стеке будут содержаться значения 1,2 и 3 соответственно. Любые изменения, выполняемые в функции над значениями параметров, в действительности изменяют значения ячеек стека.

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

 

Передача параметров по ссылке

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

Для того чтобы функция могла возвращать модифицированное значение своего параметра, при вызове такой функции должен использоваться способ, называемый передача параметра по ссылке. Различие между передачей параметра по значению и по ссылке заключается в том, что в первом случае функция получает копию значения параметра, а во втором - адрес значения переменной. Таким образом, при получении параметра по ссылке функция может производить изменения значения, сохраненного по данному адресу, и эти изменения остаются в силе и после завершения функции. Для организации передачи параметров функции по ссылке в программе должны использоваться указатели. Подробно этот тип переменных обсуждается в разделе "Массивы, указатели и структуры". Здесь же будем рассматривать указатель просто как адрес памяти. Для присваивания указателю адреса переменной используется операция получения адреса (&). Для доступа к значению, расположенному по месту памяти, задаваемому указателем, применяется операция "звездочка" (*).

 

int x;
int &y=x;

 

Идентификатор y назначает другое, альтернативное имя ячейке, названной x. Если прибавить к значению переменной x некоторое число, то оно будет прибавлено и к переменной y.

 

#include <iostream.h>
#include <conio.h>
void summa(int &number1, int &number2)
{
number1++;
number2++;
}
void main()
{
int a=10;
int b=15;
cout<<"Before upd "<<a<<" "<<b;
summa(a,b);
cout<<"\nAfter upd "<<a<<" "<<b;
getch();
}
 
 Результат
Before upd 10 15
After upd 11 16

 

Так как number1 и a – это одна и та же ячейка, то изменение number1 автоматически приведет к изменению переменной a (аналогично number2 и b).

 

Значения параметров по умолчанию

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

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

 

void some_function(int size=12, float cost=19.95) //--->Значения по умолчанию 
{
   // Операторы функции
}

 

Следующая программа присваивает значения по умолчанию параметрам a, b и c внутри функции show_parameters. Затем программа четыре раза вызывает эту функцию, сначала не указывая параметров вообще, затем указывая значение только для а, потом значения для а и b и, наконец, указывая значения для всех трех параметров:

 

#include <iostream.h>
void show__parameters (int a=1, int b=2, int c=3)
{
   cout << "a" << a << " b " << b << " с " << с << endl;
}
void main(void)
{
   show_parameters();
   show_parameters(1001);
   show_parameters(1001, 2002);
   show_parameters(1001, 2002, 3003);
}

 

Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

 

С:\> DEFAULTS <ENTER>
а 1 b 2 с 3
а 1001 b 2 с 3
а 1001 b 2002 с 3
а 1001 b 2002 с 3003
 

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

 

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

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

 

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

 

int my_function(void);

 

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

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

При определении функций в своих программах вы должны указать тип возвращаемого функцией значения, а также количество параметров и тип каждого из них. При программировании на языке С, когда у вас была функция, например, с именем add_values, которая работала с двумя целыми значениями, а вы хотели бы использовать подобную функцию для сложения трех целых значений, вам следовало создать функцию с другим именем. Например, вы могли бы использовать add_two_values и add_three_values. Аналогично если вы хотели использовать подобную функцию для сложения значений типа float, то вам была бы необходима еще одна функция с еще одним именем. Чтобы избежать дублирования функции, C++ позволяет вам определять несколько функций с одним и тем же именем. В процессе компиляции C++ принимает во внимание количество аргументов, используемых каждой функцией, и затем вызывает именно требуемую функцию. Предоставление компилятору выбора среди нескольких функций называется перегрузкой.

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

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

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

 

#include <iostream.h>

int add_values(int a,int b)

{
   return(a + b);
)

int add_values (int a, int b, int c)

(
   return(a + b + c);
)

void main(void)

{
   cout << "200 + 801 = " << add_values(200, 801) << endl;
   cout << "100 + 201 + 700 = " << add_values(100, 201, 700) << endl;
}

 

Как видите, программа определяет две функции с именами add_values Первая функция складывает два значения типа int, в то время как вторая складывает три значения. Вы не обязаны что-либо предпринимать специально для того, чтобы предупредить компилятор о перегрузке, просто используйте ее. Компилятор разгадает, какую функцию следует использовать, основываясь на предлагаемых программой параметрах.

Одним из наиболее общих случаев использования перегрузки является применение функции для получения определенного результата, исходя из различных параметров. Например, предположим, что в вашей программе есть функция с именем day_of_week, которая возвращает текущий день недели (0 для воскресенья, 1 для понедельника, ..., 6 для субботы). Ваша программа могла бы перегрузить эту функцию таким образом, чтобы она верно возвращала день недели, если ей передан юлианский день в качестве параметра, или если ей переданы день, месяц и год:

 

int day_of_week(int julian_day)

{
   //
Операторы
}

int day_of_week(int month, int day, int year)

{
   // Операторы
}

 

Рекурсия

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

Рекурсивной функцией называется функция, которая для выполнения определенной операции вызывает саму себя. Процесс вызова функцией самой себя называется рекурсией. По мере возрастания сложности программ и функций может оказаться, что при определении некоторых операций удобно использовать сами эти операции. В таких случаях имеет смысл создавать рекурсивные функции. Классическим примером рекурсивной обработки является вычисление факториала. Факториал значения 1 равен 1. Факториал значения 2 равен 2*1. Факториал значения 3 равен 3*2*1. Аналогично, факториал значения 4 равен 4*3*2*1. Этот процесс может быть продолжен до бесконечности. Если внимательно приглядеться к процессу вычисления факториала, то можно увидеть, что факториал, например, от 4 равен 4-кратному значению факториала от 3 (3*2*1). Аналогично, факториал от 3 равен 3-кратному значению факториала от 2 (2*1). Факториал от 2 равен двукратному значению факториала от 1 (1). В таблице демонстрируется вычисление факториала.

 

Таблица. Вычисление факториала

Значение

Вычисление

Результат

Факториал

1

1

1

1

2

2*1

2

2*Factorial(l)

3

3*2*1

6

3* Factorial(2)

4

4*3*2*1

24

4* Factorial(3)

5

5*4*3*2*1

120

5* Factorial(4)

 

В следующей программе представлена рекурсивная функция factorial, которая используется для вычисления факториала значений от 1 до 5:

 

#include <stdio.h>

 

int factorial (int value)

{

    if (value ==1)

return(1);

    else

return(value * factorial(value-1));

}

 

void main(void)

{

    int i;

    for (i =1; i <= 5; i++)

printf("Факториал от %d равен %d\n", i, factorial(i));

}

 

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

При выполнении этой функции первым делом осуществляется проверка значения параметра на равенство 1. Если значение равно 1, то функция возвращает 1. В противном случае, в качестве результата возвращается значение, равное произведению значения входного параметра и факториала от числа, равного значению параметра минус 1. Например, предположим, что функция вызывается со значением 3. Тогда в качестве результата функцией будет возвращено 3*factorial(3-1). Обнаруживая в операторе return вызов функции factorial, компилятор организует повторный вызов, на этот раз со значением 3-1 или 2. Поскольку значение не равно 1, результатом выполнения функции будет 2*factorial(2-1). Наконец, при следующем вызове функции значение параметра равно 1, поэтому в качестве результата вызвавшей программе (функции) возвращается значение 1.

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

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

 

Встроенные функции

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

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

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

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

 

#include <iostream.b>

void show_message(int count, char *message)

{
   int i;
   for (i = 0; i < count; i++) cout << '\a';
   cout << message << endl;
}

void main(void)

{
   show_message(3, "
Учимся программировать на языке C++");
   show_mes sage(2, "
Встроенные функции");
}

 

Следующая программа NO_CALL.CPP не вызывает функцию show_message. Вместо этого она помещает внутри себя те же операторы функции при каждой ссылке на функцию:

 

#include <iostream.h>

void main (void)

{
   int i;
   for (i = 0; i < 3; i++) cout << '\a';
   cout << " Учимся программировать на языке C++" << endl;
   for (i = 0; i < 2; i++) cout << '\a';
   cout << "
Встроенные функции " << endl;
}

 

Обе программы выполняют одно и то же. Поскольку программа NO_CALL не вызывает функцию show_message, она выполняется немного быстрее, чем программа CALLBEEP. В данном случае разницу во времени выполнения определить невозможно, но, если в обычной ситуации функция будет вызываться 1000 раз, вы, вероятно, заметите небольшое увеличение производительности. Однако программа NO_CALL более запутана, чем ее двойник CALL_BEEP, следовательно, более тяжела для восприятия.

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

 

Использование ключевого слова inline

При объявлении функции внутри программы C++ позволяет вам предварить имя функции ключевым словом inline. Если компилятор C++ встречает ключевое слово inline, он помещает в выполнимый файл (машинный язык) операторы этой функции в месте каждого ее вызова. Таким образом, можно улучшить читаемость ваших программ на C++, используя функции, и в то же время увеличить производительность, избегая издержек на вызов функций. Следующая программа INLINE.CPP определяет функции тах и min как inline:

 

#include <iostream.h>

inline int max(int a, int b)

{
   if (a > b) return(a);
   else return(b) ;
}

inline int min(int a, int b)

{
   if (a < b) return(a);
   else return(b);
}

void main(void)

{
   cout << "Минимум из 1001 и 2002 равен " << min(1001, 2002) << endl;
   cout << "Максимум из 1001 и 2002 равен " << max(1001, 2002) << endl;
}

 

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

 

Обработка символьных данных

Вывод в C/C++

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

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

Функции, используемые для вывода данных, зависят от типа данных и способа их представления. Наиболее прост вывод строк и символьных данных.

Функция puts()

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

 

строковый литерал:

        puts("Всем привет!");

строковая константа:

        #define MESSAGE "Всем привет"

        main()

        {

        puts(MESSAGE);

        }

строковая переменная:

        char greeting[]="Всем привет";

        main() 

        {

        puts(geering);

        }

 

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

Большинство компиляторов выполняют перевод строки после выполнения функции puts(). Это значит, что после того, как данные выведены на экран монитора, курсор автоматически переходит в начало следующей строки.

Функция putchar()

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

 

символьный литерал:

        putchar('H');

символьная константа:

        #define INITIAL 'H'

        main()

        {

        putchar(INITIAL);

        }

символьная переменная:

        main()

        {

        char letter;

        letter='G';

        putchar(letter);

        }

 

С помощью функции putchar() можно отображать только один символ. Инструкция

 
putchar('Hi');

 

приведет к ошибке компиляции.

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

Большинство компиляторов Си не имеет автоматического перевода строки после функции putchar(), и курсор остается сразу за выведенным символом, не переходя к началу следующей строки. Для перехода на новую строку вы должны ввести управляющий код \n.

Функция printf()

Функции puts() и putchar() используются довольно часто, но, к сожалению, их возможности несколько ограничены. Ни одна из них не может обеспечить вывод числовых данных, и обе они имеют только один аргумент (параметр). Это означает, что обе функции могут отобразить только один объект.

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

В простейшем случае функцию printf() можно использовать вместо функции puts() для вывода строки:

 

#define MESSAGE "Привет!"
main()
{
printf(MESSAGE);
printf("Добро пожаловать в мой мир! ");
}

 

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

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

Каждый указатель формата начинается с символа процента (%), после которого стоит буква, указывающая тип данных:

 

%d
целое число
%u
беззнаковое целое число
%f
вещественное число типа float или double
%e
вещественное число в экспоненциальной форме
%g
вещественное число, отображаемое по формату %f или %e, в зависимости от того, какая форма записи является более короткой
%c
символ
%s
строка

 

Таким образом, первая часть инструкции printf() записывается так:

 

printf("%d")

 

Знак процента говорит компилятору, что за ним последует указатель формата (чтобы отобразить сам символ процента, напишите его дважды: printf("%%");).

Буква d указывает компилятору, что следует отобразить целое число, записанное в десятичной системе счисления.

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

Простейший пример использования функции printf() приведен ниже:

 

printf("%d", 12);

 

В процессе выполнения этой инструкции значение 12 будет подставлено на место указателя формата. В нашем примере мы на самом деле передали библиотечной функции printf() два параметра: строку формата и числовой литерал12.

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

 

printf("Мне исполнилось %d лет", 12);

 

Строкой формата в этом примере является запись

"Мне исполнилось %d лет"

 

Указатель формата, %d, говорит о том, что мы хотим вставить число между словами "Мне исполнилось" и словом "лет". Когда компилятор подставит число12 на место указателя формата, мы увидим следующую фразу:

 

Мне исполнилось 12 лет

 

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

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

 

printf("Мне исполнилось 12 лет");

puts("Мне исполнилось 12 лет");

 

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

 

main()

        {

        int age;

        age = 12;

        printf("Мне исполнилось %d лет", age);

        }

 

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

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

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

Если вы хотите перевести курсор на следующую строку, вы должны добавить в строку формата управляющий код «новая строка» \n:

 

printf("Стоимость составляет %f за %d         штук\n", amount, count);

 

Форматированный вывод

Функцию printf() можно использовать для управления форматом данных. Определять величину пробелов и количество выводимых символов можно с помощью указателей ширины поля.

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

 

printf("Стоимость составляет %f за
                %d штук", amount, count);
 

и появляется строка:

 

Стоимость составляет 45.580000 за 5 штук

 

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

 

Стоимость составляет 45.579998 за 5 штук

 

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

Чтобы определить число знаков после точки, используется указатель %.nf, где число n определяет количество знаков.

Например, если написать:

 

printf("Стоимость составляет %.2f", amount);

 

то при выводе значения переменной с типом float оно будет иметь только два знака после точки:

Стоимость составляет 45.58

 

Можно также определить общую ширину поля, то есть размер (в символах) пространства, занимаемого выводимым числом, если использовать следующий формат:

 

%N.nf

 

где N— это общая ширина поля.

Если задать инструкцию

 

printf("Стоимость составляет %8.2f", amount);

появится строка

Стоимость составляет 45.58

 

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

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

 

printf("Стоимость составляет %2.2f", amount);

 

приведет к появлению сообщения

 

Стоимость составляет 45.58

 

Указатель ширины поля может работать как с символьными, так и со строковыми данными. Дополнительные пробелы помещаются перед текстом, сдвигая строку к правому краю воображаемой «коробочки». Например, если строковая переменная, называемая message, имеет значение «Привет», то инструкция

 

printf("Я позвонил, чтобы сказать %8s", message);

 

отобразит на экране следующую строку

 

Я позвонил, чтобы сказать Привет

 

при выводе значения строковой переменной message перед ним появятся два дополнительных пробела.

Выбор правильных средств вывода информации

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

Примечание: рассмотренные выше функции вызываются из библиотеки stdio.h.

Чтобы вывести на экран обычный текст или символы, можно использовать функции puts() или putchar(). Так как эти функции не имеют никаких возможностей форматирования данных, они работают быстрее, и их коды занимают меньший объем на диске, чем коды функции printf(). Имея дело с функцией puts(), прежде всего проверьте, добавляет ли компилятор код «новая строка» автоматически. Если он не делает этого, а вы не проверите сразу, потом вам придется потратить довольно много времени на редактирование программы.

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

Вывод в Си++

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

В Си++ существует стандартный поток вывода cout, позволяющий в сочетании с двумя символами «меньше» (<<), которые называются оператором вставки, отображать литералы или значения констант и переменных без использования указателей формата.

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

 

int age;

age = 43;

cout << "Вам исполнилось " << age << " года.";

отображает текст

Вам исполнилось 43 года.

 

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

Так же, как и функция printf(), cout не добавляет никаких команд новой строки после отображения данных. Чтобы перейти к новой строке, там, где вы хотите ее начать, надо добавить управляющий код \n.

 

Ввод в C/C++

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

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

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

Функция gets()

Функция gets() вводит строку в переменную. Параметром функции является имя переменной. Рассмотрим такую программу:

 
main()
        {
        char name[15];
        gets(name);
        puts(name);
        }

 

Функция gets() будет рассматривать первые 14 символов, введенные с клавиатуры, как значение строковой переменной с именем name. Вы помните, что Си отводит строковой переменной столько элементов памяти, сколько указано в максимальном значении при определении переменной, а так как один элемент необходим для нулевого символа (\0), реально можно ввести на один символ меньше. Если вы хотите ввести в переменную name строку, состоящую из 15 символов, то укажите в квадратных скобках максимальное значение 16:

 

char name[16];

 

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

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

Рассмотрим более подробный пример:

 

main()
        {
        char name[25];
        printf("Пожалуйста, введите Ваше имя: ");
        gets(name);
        printf("Подтвердите, Ваше имя: %s", name);
        }

 

Когда программа будет выполняться, вы увидите на экране подсказку:

 

Пожалуйста, введите Ваше имя:

 

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

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

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

После нажатия клавиши Enter Си присваивает введенные символы переменной name и вставляет нулевой символ в конце строки. Затем программа переходит к выполнению второй функции printf() и на экране появляется вторая надпись (предположим, что вас зовут Петр Иванов):

 

Пожалуйста, введите Ваше имя: Петр Иванов
Подтвердите, Ваше имя: Петр Иванов

 

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

Функция getchar()

Функция getchar() вводит с клавиатуры единичный символ. Для большинства компиляторов безразлично, к какому типу (char или int) вы отнесете вводимый символ, что обусловлено способом определения символьной переменной в K&R-стандарте языка Си.

Для ввода символа можно использовать оба этих формата:

 

        int letter;            char letter;
        letter = getchar();    letter = getchar();

 

Обратите внимание на то, что вызов функции getchar() осуществляется не так, как вызов функций, которые мы рассматривали раньше. Вместо того чтобы поставить имя функции в начало строки инструкции, мы относим его к переменной с помощью знака «равно». Эта запись означает: «Присвоить переменной letter значение, полученное в результате выполнения функции getchar()». Практически, функция getchar() рассматривается программой как значение переменной (рис.5.3). При выполнении этой инструкции вызывается функция getchar() и осуществляется ввод символа, который присваивается переменной. Функция getchar() не имеет аргумента. Поэтому круглые скобки после имени функции остаются пустыми.

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

Некоторые компиляторы Си и Си++ используют функцию getch(), которая вводит символ без последующего нажатия Enter. Может оказаться, что при работе с getchar(), эти компиляторы требуют нажатия Enter после ввода символа.

Для чего может понадобиться ввод единичного символа? Вероятно, вам приходилось видеть программы, в которых необходимо ответить «Да» или «Нет» в ответ на запрос, или выбрать один из пунктов предложенного меню. Функция getchar() идеально подходит в этих случаях, ведь при работе с ней нет необходимости нажимать Enter, ввод одного из предложенных на выбор символов позволяет немедленно продолжить выполнение программы.

Функцию getchar() можно использовать для приостановки выполнения программы, что может оказаться весьма полезно в некоторых ситуациях. Приведем простой пример. На экран можно одновременно вывести ограниченное количество строк (обычно 25). Если в программе используется длинный ряд инструкций puts(), при выполнении которых надо вывести больше строк, чем может поместиться на экране, первые появившиеся строки информации быстро уйдут за верхний край, и пользователь просто физически не успеет ознакомиться с их содержанием до того, как на этом месте появятся другие.

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

 

printf("Для продолжения нажмите Enter");

getchar();

 

В этом примере функция getchar() приостанавливает выполнение программы до тех пор, пока пользователь не нажмет клавишу. Совершенно необязательно этой клавишей должна быть именно Enter, но нажатие любой другой приведет к появлению на экране соответствующего символа, а это может запутать пользователя. Функция getchar() в данном случае не связана ни с какой переменной. Если для продолжения выполнения программы будет нажата, например, клавиша Y, соответствующий символ появится на экране, но не будет присвоен в качестве значения ни одной переменной.

Функция scanf()

Функция scanf() является многоцелевой функцией, дающей возможность вводить в компьютер данные любых типов. Название функции отражает ее назначение— функция сканирует (просматривает) клавиатуру, определяет, какие клавиши нажаты, и затем интерпретирует ввод, основываясь на указателях формата (SCAN Formatted characters). Так же, как и функция printf(), scanf() может иметь несколько аргументов, позволяя тем самым вводить значения числовых, символьных и строковых переменных в одно и то же время.

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

Указатели формата аналогичны тем, которые используются функцией printf():

 

%d

целые числа

%u

беззнаковые целые числа

%f

вещественные числа, float

%e

вещественные числа в экспоненциальной форме

%g

вещественные числа в наиболее коротком из форматов %f или %e

%c

символы

%s

строки

%o

целые числа в восьмеричной системе счисления

%x

целые числа в шестнадцатеричной системе счисления

 

Оператор получения адреса &

Вы уже знаете, что для каждой переменной, используемой в программе, отводится определенный объем памяти, который зависит от типа переменной. Каждый элемент памяти пронумерован от 0 и далее. Если компьютер имеет 2 Мегабайта оперативной памяти, элементы будут пронумерованы от 0 до 2097151. Эти номера и называют адресами элементов памяти.

При ссылке на переменную (например, при выводе ее значения на экран) используется имя переменной. Можно также сослаться и на ее адрес, если поставить символ амперсанда (&) перед именем переменной, скажем, так: &count. Символ & называется оператором получения адреса. Он указывает Си, что вас в настоящий момент интересует адрес элемента памяти, где зарезервировано место для переменной, а не значение переменной, хранящееся в этом элементе.

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

Вводя числовые или символьные данные, следует указывать в списке данных функции scanf() адрес переменной, а не просто ее имя:

 

main()

        {

        float amount;

        scanf("%f", &amount);

        }

 

В этом примере функция scanf() вводит число с плавающей точкой и вносит его в область памяти, зарезервированную для переменной amount. Как только число помещается в эту область памяти, оно автоматически становится значением переменной.

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

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

Когда данные вводятся при помощи функции gets(), все символы, которые были набраны на клавиатуре до нажатия Enter, становятся значением переменной. Когда символ вводится с помощью функции getchar(), нажатие клавиши автоматически приводит к присвоению соответствующего символа переменной.

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

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

Функция scanf() игнорирует не содержащие информации знаки: пробелы, символы табуляции, символы новой строки, кроме тех случаев, когда текущий тип данных определен как char. Рассмотрим программу:

 

main()

        {

        int count;

        puts("Пожалуйста, введите число: ");

        scanf("%d", &count);

        printf("Число равно %d", count);

        }

 

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

Какие символы программа расценивает как «подходящие», зависит от указателей формата. Если стоит указатель %d, то «подходящими» являются только цифры и знак «минус». Если поставить указатель %x, то соответствующими формату окажутся символы 0123456789ABCDE, так как все они используются при записи чисел в шестнадцатеричной системе счисления. Если же стоит указатель %c, принимаются любые символы, даже пробел внутри входного потока функция scanf() в этом случае не игнорирует. Если написать инструкцию:

 

char letter;

scanf("%c", &letter);

 

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

Выбор соответствующих средств ввода данных

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

Примечание: рассмотренные выше функции вызываются из библиотеки stdio.h.

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

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

Ввод в Си++

Компиляторы языка Си++ поддерживают функции gets(), getchar() и scanf(), о которых мы говорили в этой главе. Кроме того, Си++ имеет собственное многоцелевое средство ввода для всех типов данных. Стандартный поток ввода cin в сочетании с двумя символами «больше» (>>), которые называются оператором извлечения*, служит для считывания данных с клавиатуры. Программа

 

int count;
cin >> count;

 

вносит целочисленные данные с клавиатуры в значение переменной count. Стандартный поток ввода cin не требует указания адреса переменной для числовых и символьных данных, указывается только имя переменной.

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

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

Оператор вставки << изображен в виде стрелки, направленной от переменной. Это значит, что данные выводятся из переменной на экран. Со стандартным потоком вывода cout используйте, соответственно, тот оператор, который как бы указывает направление от переменной.

Управляющие структуры

Структуры выбора (if / else)

 

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

Инструкция if используется в тех случаях, когда необходимо решить, должна ли быть выполнена конкретная инструкция программы (if по-английски значит «если»). Структура if выглядит следующим образом:

 

if (condition)

instruction;

 

Этой записью мы говорим: «Если некоторое условие выполняется (является истинным), инструкция должна быть выполнена».

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

* условие заключается в круглые скобки;

* точку с запятой ставят не после условия, а только в конце инструкции;

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

 

Условия

Условием в инструкции if является сравнение значений: значение переменной или константы сравнивается с литералом, или со значением другой переменной или константы. Сравнение выполняется с помощью одного из следующих операторов отношения:

 

Оператор

Значение

Пример

==

равно

if (tax == 0.06)

> 

больше

if (hours > 40)

< 

меньше

if (hours < 40)

>=

больше или равно

if (salary >= 10000)

<=

меньше или равно

if (cost <= limit)

!=

не равно

if (count != 1)

 

Обратите внимание: когда вы хотите узнать, равны ли два значения друг другу, то должны использовать оператор отношения, состоящий из двух знаков равенства (==) подряд. Если поставить только один знак равенства, компилятор сгенерирует предупреждение, или, реже, ошибку. Единичный знак равенства (=) используется для обозначения присваивания значения переменной.

Простейшая инструкция с использованием if выглядит примерно так:

 

if (time > 11)
        puts("Уже поздно, ступайте домой.");

 

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

 

Составные инструкции

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

 

Конструкция if...else

Сама по себе инструкция if используется в тех случаях, когда важно выполнить некие действия при истинности определенного условия. Возможна ситуация, когда при истинности условия должен быть выполнен один набор инструкций, а в противном случае— другой. Допустим, мы хотим, чтобы в тех случаях, когда цена какого-либо товара ниже той, для которой установлен налог на предметы роскоши, программа генерировала сообщение: «Для данного наименования налог на предметы роскоши не установлен». Это можно сделать с помощью двух инструкций if:

 
if (cost > 40000.00)
        {
        luxury = cost * 0.005;
        printf("Размер налога на предметы роскоши для \
 данного наименования составляет %.2f", luxury);
        }
if (cost < 40000.00)
        puts("Для данного наименования налог \
 на предметы роскоши не установлен");

 

Но есть более эффективный способ. Можно объединить обе инструкции в одну, пользуясь тем, что есть только два возможных случая в использовании одной и той же переменной: либо цена товара больше 40 тысяч долларов, либо цена товара меньше или равна указанной сумме. Если одно из условий не выполняется, следовательно, выполняется второе условие, так что можно скомбинировать их, используя ключевое слово else (которое переводится как «иначе»):

 

if (condition)

        instruction;

else

        instruction;

 

Здесь сказано: «Если условие истинное, то должна быть выполнена команда, являющаяся частью инструкции if, иначе надо выполнить инструкцию, следующую за else». Инструкция, помещенная после ключевого слова else, выполняется только в том случае, если условие оказалось ложным. Если возникает необходимость выполнить в этом случае несколько инструкций, можно использовать составную инструкцию, заключив ее в фигурные скобки точно так же, как для if. Точка с запятой ставится в конце каждой инструкции и не ставится после ключевого слова else.

 

Логические операторы

Как вы уже могли заметить в приведенных выше примерах, инструкция if проверяет выполнение условия только для одной переменной и одного значения. Значит, в инструкции можно ввести только одно условие с целью проверки его истинности. На самом деле часто возникает необходимость проверить в условии более одного значения. Есть три логических оператора: ИЛИ (||), И (&&) и отрицания (!). Оператор ИЛИ означает, что для выполнения инструкции if достаточно истинности одного из двух (или обоих вместе) заданных условий. Оператор И указывает на то, что должны быть истинными оба условия одновременно. Оператор отрицания означает, что инструкция if выполняется, если некое условие оказалось ложным.

 

Вложенные инструкции if

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

 

if (income > 100000)
        if (status == 'S')
               taxrate = 0.35;

 

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

 

if (income > 100000 && status == 'S')
        taxrate = 0.35;

 

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

Структуры выбора (switch/case/default)

Если в программе следует учесть больше трех возможных вариантов, конструкция с вложенными инструкциями if...else может оказаться очень запутанной. В таких случаях в качестве альтернативы используется переключатель switch. Переключатель switch представляет собой структуру, построенную по принципу меню, и содержит все возможные варианты условий и инструкции, которые следует выполнить в каждом конкретном случае. Пример подобной конструкции приведен ниже.

 

main()
        {
        int answer;
        puts("Си это: \n");
        puts("1. Язык, на котором
 говорят на юге Франции\n");
        puts("2. Язык, который используется только
 для написания \
больших компьютерных программ\n");
        puts("3. Компилирующий язык, легко 
        совместимый с любыми системами\n");
        puts("4. Ничего из перечисленного\n");
        puts("Введите номер ответа\n");
        answer = getchar();
        putchar('\n');
        switch (answer)
               {
               case '1':
                       puts("К сожалению, Вы ошиблись, \
на юге Франции говорят на языке Паскаль\n");
                       break;
               case '2':
                       puts("Вы ошибаетесь,
 язык Си используют для написания программ\n");
                       puts("любых типов и размеров\n");
                       break;
               case '3':
                       puts("Очень хорошо, Вы
 дали правильный ответ\n");
                       puts("Си является
 компилирующим языком и может использоваться\n");
                       puts("с
         любыми компьютерными системами\n");
                       break;
               case '4':
                       puts("К сожалению,
 Вы ошибаетесь, правильный ответ - номер 3\n");
                       break;
               default:
                       puts("Вы ввели символ,
 не соответствующий \
ни одному из номеров ответа\n");
               }

 

В круглых скобках после переключателя switch находится переменная типа int или char, следом расположен блок инструкций, заключенных в фигурные скобки, которые содержат ряд ветвей case. Каждая ветвь case выполняет инструкции, основываясь на возможном значении переменной. Это значение должно быть представлено в виде целого числа или символа, заключенного в одинарные кавычки, либо в виде имени целочисленной или символьной константы.

Ветвь case '1':, например, предписывает программе выполнить следующие ниже инструкции, если значение переменной переключателя switch соответствует символу '1'. Если значение переменной отличается от единицы, компилятор переходит к проверке условия второй ветви case.

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

Если значение переменной не удовлетворяет условиям ни одной из ветвей case, выполняется ветвь, помеченная инструкцией default. Это дает возможность учесть все возможные варианты ввода. После инструкции default нет необходимости ставить break, поскольку она всегда является последней в процедуре выполнения switch. Имеет смысл включать default даже тогда, когда вы полагаете, что учли все возможные условия и все возможные случаи ввода значений.

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

 

case 'Y':
case 'y': puts ("Вы ответили \"Да\"");
        break;
case 'N':
case 'n': puts ("Вы ответили \"Нет\"");
        break;

 

 

Структуры повторения (циклы)

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

Язык Си и Си++ имеет три структуры, известные под названием циклов, которые используются для управления повторами:

цикл for;

цикл do...while;

цикл while.

 

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

Использование цикла for

Цикл for используется в том случае, когда известно точное количество повторов, которое нужно выполнить.

 
for (инициализация цикла (начальное значение); выражение-условие (повторять, пока выполняется условие); список выражений (приращение значения))
{
тело_цикла
}

 

В приведенной ниже программе цикл for используется для того, чтобы вывести на экран монитора числа от 1 до 10, расположенные друг под другом.

 
main()
        {
        int repeat;
        for (repeat = 1; repeat <= 10; repeat++)
               printf("%d\n", repeat);
        }

 

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

 

repeat=1
инициализация переменной repeat путем присваивания ей начального значения
repeat <= 10
задает условие повтора цикла до тех пор, пока значение переменной repeat остается меньше или равно 10
repeat++
приращение значения переменной repeat после каждого повтора цикла

 

Когда программа начнет выполнение цикла, она присвоит переменной repeat начальное значение, равное 1. Затем будет проверено, является ли истинным условие, что значение переменной меньше или равно 10. Если условие истинное, начнется выполнение инструкции, связанной с циклом, то есть вывод на экран значения переменной.

После выполнения инструкции произойдет увеличение значения переменной на единицу и снова будет проведена проверка истинности условия. Так как условие все еще является истинным, цикл будет выполнен во второй раз, отображая на дисплее текущее значение переменной. Этот процесс будет повторяться до тех пор, пока значение переменной не вырастет до 11. Как только это произойдет, условие repeat <= 10 уже не будет истинным, так что выполнение инструкции прекратится и цикл завершится.

Если один цикл for выполняется внутри другого, принято говорить, что мы имеем дело с вложенным циклом. Внутренний цикл целиком выполняется во время каждого повторения внешнего цикла. Вложенные циклы for можно представить себе как двухмерные, а единичный— как одномерный.

Использование цикла do...while (постусловие)

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

 

do {
операция_1;
операция_2;
……………
операция_n;
} while (тестовое_условие);
 

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

Используя цикл do...while, следует указывать условие так, чтобы выполнение его не оказалось бесконечным.

Цикл do...while часто используется для того, чтобы повторять программу до тех пор, пока пользователь не решит закончить ввод:

 

main()

        {

        int temp;

        float celsius;

        char repeat;

        do

               {

               printf("Введите значение температуры: ");

               scanf("%d", &temp);

               celsius = (5.0 / 9.0) * (temp - 32);

               printf("%d градусов по

 Фаренгейту соответствует %6.2f по Цельсию\n",

 temp, celsius);

               printf("Желаете ввести еще значение?");

               repeat = getchar();

               putchar('\n');

               }

        while (repeat == 'y' || repeat == 'Y');

        }

 

В этом примере практически весь текст программы, исключая определение переменных, входит в большой блок do...while. Цикл будет повторяться до тех пор, пока в ответ на запрос будет вводиться символ Y или y. Обратите внимание, что инструкция, выводящая на экран запрос о продолжении работы, является последней инструкцией в блоке.

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

 

Использование цикла while (предусловие)

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

 

while (тестовое выражение)

        { оператор 1;

   оператор 2;

   ……………

   оператор n;

}

 

 

Так же, как и цикл do, цикл while выполняется до тех пор, пока является истинным условие, но в отличие от конструкции do...while, условие проверяется до начала выполнения цикла, даже если цикл выполняется первый раз. Если условие окажется ложным, цикл не будет выполнен ни разу.

 

Операторы передачи управления

Оператор безусловного перехода goto

Этот оператор организует переход на строку с так называемой меткой (произвольно выбранный идентификатор).

В примере оператор goto передает управление оператору, стоящему за меткой alpha, при достижении переменной i величины, превышающей m.

 

#include <iostream.h>

void main ()

{

int n=10;

int m=4;

int s=0;

for(int i=0;i<n;i++)

{

s+=i;

if (i>m) goto alpha;

}

alpha: cout<<”\ns= ”<<s;

}

 

Результат:

s=15.

 

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

 

Оператор break

Оператор break передает управление на первый оператор, стоящий после цикла.

 

#include <iostream.h>

void main ()

{

int t=1;

int s=0;

while (t<10)

{s+=t;

if (s>20) break:

t++;

}

cout<<”\nsum= ”<<s;

}

 

Результат:

sum=21.

 

Оператор continue

C помощью этого оператора завершается текущая итерация и начинается проверка условия дальнейшего продолжения цикла.

 

Сумма только отрицательных чисел:

 

#include <iostream.h>

main()

{

int sum, i, n, ch;

sum=0;

cin>>n;

for(i=0;i<n;i++)

{

cin>>ch;

if(ch>=0) continue;

sum+=ch;

}

cout<<sum;

return 0;

}

 

 

Препроцессор языка Си

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

Препроцессор читает строки текста и выполняет действия, определяемые командными строками. Если первый отличный от пробела символ в строке управляющий (#), то такая строка рассматривается препроцессором как командная. Строки, не являющиеся командными, либо подвергаются преобразованиям, либо остаются без изменения.

 

Макрогенерация (замена лексических единиц)

Командная строка вида #define name text вызывает в оставшейся части программы замену всех вхождений идентификатора name на строку text.

Например, определение #define p1 3.14159265 позволяет использовать в программе имя p1 вместо константы 3.14159265.

Обратите внимание, что это определение не завершается точкой с запятой.

Внутри строк, заключенных в кавычки, подстановка не производится, так что, например, для определенного выше имени P1 в printf("P1"); подстановки не будет.

Директива #define предписывает компилятору заменить имя константы на то, что следует за этим именем. Если после имени константы ввести какую-нибудь инструкцию, компилятор тоже произведет подстановку. Например, в следующей инструкции мы подставляем на место константы ENTER функцию printf():

 

#define ENTER printf("Пожалуйста, введите число: ")

 

Теперь, при необходимости отобразить сообщение, записанное в аргументе функции printf(), достаточно в соответствующем месте программы использовать инструкцию ENTER:

 

 

#define ENTER printf("Пожалуйста, введите число: ")

main()

        {

        int age, size;

        ENTER;

        scanf("%d", &age);

        ENTER;

        scanf("%d", &size);

        }

 

Директива #undef

Директива #undef используется для отмены действия директивы #define. Синтаксис этой директивы следующий #undef идентификатор

Директива отменяет действие текущего определения #define для указанного идентификатора.

 

Включение файлов

Директива #include включает в текст программы содержимое указанного файла. Эта директива имеет две формы:

 

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

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

 

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

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

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

 

Условная компиляция

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

 

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

 

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

 

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

 

Последующий тест компилируется, если "идентификатор" уже был опредеоен для препроцессора в команде #define.

 

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

 

Последующий текст компилируется, если "идентификатор" в данный момент не определен. Конструкция

 

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

 

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

 

Массивы

Объявление переменной массива

Массив представляет собой переменную, способную хранить одно или несколько значений. Подобно переменным, используемым вашими программами до сих пор, массив должен иметь тип (например, inl, char или float) и уникальное имя. В дополнение к этому вам следует указать количество значений, которые массив будет хранить. Все сохраняемые в массиве значения должны быть одного и того же типа.

Массив должен быть объявлен в следующем виде:

 

Тип имя_массива [размерность];

 

Например:

 

int array[10];

 

Данная запись означает, что резервируется память для 10 чисел целого типа с именем array и порядковыми номерами от 0 до 9.

Отдельный элемент массива определяется именем массива и индексом элемента в квадратных скобках. Индекс –целое число.

Например, для обращения к первому элементу массива array вы должны использовать значение индекса 0. Для обращения ко второму элементу используйте индекс 1.

Первый элемент массива всегда имеет индекс 0, а значение индекса последнего элемента на единицу меньше размера массива.

Следующая программа ARRAY.CPP создает массив с именем values, который вмещает пять целочисленных значений. Далее программа присваивает элементам значения 100, 200, 300, 400 и 500:

 

#include <iostream.h>

void main(void)

{
   int values[5]; // Объявление массива
   values[0] = 100;
   values[1] = 200;
   values[2] = 300;
   values[3] = 400;
   values [4] = 500;
   cout << "Массив содержит следующие значения" << endl;
   cout << values [0] << ' ' << values [1] << ' ' << values [2] << ' ' << values [3] << ' ' << values [4] << endl;
}

 

Использование индексной переменной

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

Следующая программа SHOWARRA.CPP использует индексную переменную i внутри цикла for для вывода элементов массива. Цикл for инициализирует i нулем, так что программа может обращаться к элементу values[0]. Цикл for завершается, когда i больше 4 (последний элемент массива):

 

#include <iostream.h>

void main (void)

{
   int values[5]; //
Объявление массива int i;
   values[0] = 100;
   values[1] = 200;
   values[2] = 300;
   values[3] = 400;
   values[4] = 500;
   cout << "
Массив содержит следующие значения" << endl;
   for (i = 0; i < 5; i++) cout << values [i] << ' ';
}

 

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

 

Инициализация массива при объявлении

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

 

int values[5] = {100, 200, 300, 400, 500};

 

Подобным образом следующее объявление инициализирует массив с плавающей точкой:

 

float salaries[3] = {25000.00. 35000.00, 50000.00};

 

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

 

int values[5] = {100, 200, 300};

 

Программа не инициализирует элементы values[3] и values[4]. В зависимости от вашего компилятора, эти элементы могут содержать значение 0.

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

 

int numbers[] = { 1, 2, 3, 4 };

Передача массивов в функции

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

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

 

void some_function(int array[], int number_of_elements);

 

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

 

#include <iostream.h>

void show_array (int array [] , int number_of_elements)

{
   int i;
   for (i = 0; i < number_of_elements; i++) cout << array[i] << ' ';
   cout << endl;
}

void main(void)

{
   int little_numbers[5] ={1,2,3,4,5};
   int big_numbers[3] = { 1000, 2000, 3000 };
   show_array(little_numbers, 5);
   show_array(big_numbers, 3);
}

 

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

show_array(little_numbers, 5);

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

 

#include <iostream.h>

void get_values(int array[], int number_of_elements)

{
   int i;
   for (i = 0; i < number_of_elements; i++)

   {
       cout “ "Введите значение " << i << ": ";
       cin ” array [i];
   }
}

void main(void)

{
   int numbers[3];
   get_values(numbers, 3);
   cout << "Значения массива" << endl;
   for (int i = 0; i < 3; i++)
   cout << numbers [i] << endl;
}

 

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

 

Функция sizeof

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

 

#include <iostream.h>

void main()

{

cout<<”\nsizeof(char) =”<<sizeof(char);

cout<<”\nsizeof(short) =”<<sizeof(short);  //короткое целое

cout<<”\nsizeof(int) =”<<sizeof(int);

cout<<”\nsizeof(long) =”<<sizeof(long);

cout<<”\nsizeof(float) =”<<sizeof(float);

cout<<”\nsizeof(doble) =”<<sizeof(double); //вещест двойной точности

cout<<”\nsizeof(long double) =”<<sizeof(long double);

cout<<”\n”;

double a[] = {1.35, 6.12, -.6389, 41.4};

int b = sizeof(a);

int с = sizeof(a[0]);

int n=b/c;  //размер массива

cout<<”Total memory = ”<<b;

cout<<”\nMemory to first arrays member =”<<c;

cout<<”\nArray’s size =”<<n;

}

 

Результат:

sizeof(char) = 1

sizeof(short) = 2

sizeof(int) = 4

sizeof(long) = 4

sizeof(float) = 4

sizeof(doble) = 8

sizeof(long double) = 8

Total memory = 32

Memory to first arrays member = 8

Arrays size = 4

 

Использование констант при объявлении массивов

При работе с массивами необходимо указать размер массива.

Предположим, мы хотим изменить программу, увеличив размер массива до 10 значений; в этом случае нам придется изменить не только объявление массива, но и границу цикла for. Альтернативой этому является объявление массива с использованием константы.

 

#include <stdio.h>

#define ARRAY_SIZE 5

void main(void)

{

int values[AKKAY_SIZE] = (80, 70, 90, 85, 80};

int i;

 

for (i = 0; i < ARRAY_SIZE; i++)

      printf("values[%d] %d\n", i, values[i]);

}

 

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

Символьные строки

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

Для объявления символьной строки внутри программы просто объявите массив типа char с количеством элементов, достаточным для хранения требуемых символов. Например, следующее объявление создает переменную символьной строки с именем filename, способную хранить 64 символа (не забывайте, что символ NULL является одним из этих 64 символов):

 

char filename[64];

 

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

Программы на C++ представляют конец символьной строки с помощью символа NULL, который в C++ изображается как специальный символ ' '. Когда вы присваиваете символы символьной строке, вы должны поместить символ NULL (' ') после последнего символа в строке. Например, следующая программа ALPHABET. CPP присваивает буквы от А до Я переменной alphabet, используя цикл for. Затем программа добавляет символ NULL в эту переменную и выводит ее с помощью cout.

 

#include <iostream.h>

void main(void)

{
   char alphabet [34]; // 33 буквы плюс NULL char letter;
   int index;
   for (letter = 'A', index = 0; letter <= 'Я';
   letter++, index++) alphabet[index] = letter;
   alphabet[index] = NULL;
   cout << "Буквы " << alphabet;
}

 

Когда выходной поток cout выводит символьную строку, он по одному выводит символы строки, пока не встретит символ NULL.

 

Как 'А' отличается от "А"

При рассмотрении программ на C++ вы можете встретить символы, заключенные в одинарные кавычки (например, 'А') и символы, заключенные в двойные кавычки ("А"). Символ внутри одинарных кавычек представляет собой символьную константу. Компилятор C++ выделяет только один байт памяти для хранения символьной константы. Однако символ в двойных кавычках представляет собой строковую константу — указанный символ и символ NULL (добавляемый компилятором). Таким образом, компилятор будет выделять два байта для символьной строки.

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

 

ОН сказал "Стоп".

 

Поскольку в Си двойные кавычки используются для определения строковой константы, необходимо средство, подсказывающее компилятору присутствие кавычек в строке. Для задания кавычки в строке используется последовательность \":

 

"Он сказал \"Стоп\"."

 

Инициализация символьной строки

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

 

char title[64] = "Учимся программировать на языке C++";

 

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

 
char title[] = "Учимся программировать на языке C++";

 

Следующая программа INIT_STR.CPP инициализирует символьную строку при объявлении:

 

#include <iostream.h>
void main(void) 
{
   char title[64] = "
Учимся программировать на языке C++";
   char lesson[64] = "
Символьные строки";
   cout << "
Книга: " << title << endl;
   cout << "
Урок: " << lesson << endl;
}

 

Передача строк в функцию

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

 

#include <iostream.h>
void show_string(char string[]) 
{
   cout << string << endl;
}
void main(void) 
{
   show_string("Привет, C++!");
   show_string("Учусь программировать на C++");
}

 

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

 

Вычисление длины строки

Известно, что символ NULL рассматривается в функциях Си обычно как символ, обозначающий конец строки. Такие функции, как fgets и cgets, присваивают символ NULL автоматически. В следующей программе SHOW_STR.C функция sets используется для чтения строки символов с клавиатуры, -затем с помощью цикла for выполняется посимвольный вывод содержимого строки до тех пор, пока не будет обнаружен символ NULL:

 
#include <stdio.h>
void main (void)
{
        char string[256]; // Строка для ввода пользователя
 
        int i;            // Индекс в строке
 
        printf("Введите строку символов и нажмите Enter:\n");
        gets(string); 
        for (i = 0; string[i] != NULL; i++) putchar(string[i]);
        printf("\nЧисло символов в строке равно %d\n", i);
}

 

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

 

#include <string.h> 
size_t strlen(const char string);

 

В следующей программе демонстрируется использование strlen:

 

#include <stdio.h> 
#include <string.h>
 
void main(void)
{
    char book_title[] = "1001 совет по C/C++";
 
    printf("%s содержит %d символов\n", 
        book_title, strlen (book_title));
}

 

Копирование символьных строк

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

 

#include <string.h>
char *strcpy(char *destination, const char *source);

 

В качестве результата функция strcpy возвращает указатель на начало целевой строки. В следующей программе STRCPY.C демонстрируется использование функции strcpy:

 

#include <stdio.h>
#include <string.h>
void main(void)
{
    char title[] = "1001 совет по C/C++";
    char book[128];
    strcpy(book, title);
    printf("Название книги %s\n", book);
}

 

Массивы строк

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

Двухмерный массив можно представить себе как таблицу, имеющую ряды и колонки. Такой массив следует определять с двумя индексами, один из которых определяет количество рядов таблицы, а второй устанавливает количество колонок. Ниже приведены инструкции, определяющие массив, имеющий 10 рядов и 20 колонок, то есть содержащий 200 целочисленных переменных:

 

int table[10][20];

 

Представим себе каждый элемент как целое число, занимающее собственную клеточку в таблице 10х20. Элемент table[0][0] находится в левом верхнем углу таблицы, а элемент table[0][1] занимает соседнюю клетку справа в том же ряду.

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

 

char names[10][20];

 

задает десять строковых переменных names длиной не больше 19 символов в каждой.

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

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

 

name[index][index2] = letter;

 

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

 

Расположение матриц в памяти

Двумерный массив можно инициировать так:

 

static int matr[2][5] = {{3,4,0,1,2},{6,5,1,4,9}};   

 

Матрица хранится в памяти построчно, т.е. самый правый индекс в наборе индексов массива меняется наиболее быстро.

Например, для массива char aCh[2][4] будет выделено восемь байтов памяти, в которых в следующем порядке будут размещены элементы массива:

 

элемент

aCh[0][0]

aCh[0][1]

aCh[0][2]

aCh[0][3]

aCh[1][0]

aCh[1][1]

aCh[1][2]

aCh[1][3]

N байта

1

2

3

4

5

6

7

8

 

Трехмерный массив

При размещении трехмерного массива char aс[3][2][3] память под элементы этого массива будет выделяться последовательно в соответствии со следующими значениями индексов:

 

A000

A001

A002

 

A010

A011

A012

 

A100

A101

A102

A110

A111

A112

 

A200

A201

A202

 

A210

A211

A212

 

Общий объем выделяемой под массив памяти определяется как произведение всех размерностей массива (общее число элементов), умноженное на длину типа данных массива.

Алгоритмы сортировки массива

Пузырьковая сортировка

Расположим массив сверху вниз, от нулевого элемента - к последнему.

Идея метода: шаг сортировки состоит в проходе снизу вверх по массиву. По пути просматриваются пары соседних элементов. Если элементы некоторой пары находятся в неправильном порядке, то меняем их местами.

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




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

 

#include <iostream.h> 
#include <conio.h> 
int main()
{int array[100],size,x,y,temp,i;
cin>>size;
for (x=0;x<size;x++)
cin>>array[x];
for (i=size;i>0;i--)
for (int j=0;j<i;j++)
if (array[j]>array[j+1])
{
int b=array[j];
array[j]=array[j+1];
array[j+1]= b;
}
for (i=0;i<size;i++)
cout<<array[i]<<" ";
getch();
return 0;
}

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

Выборочная сортировка

Идея метода состоит в том, чтобы создавать отсортированную последовательность путем присоединения к ней одного элемента за другим в правильном порядке. Будем строить готовую последовательность, начиная с левого конца массива. Алгоритм состоит из n последовательных шагов, начиная от нулевого и заканчивая (n-1)-м.
На i-м шаге выбираем наименьший из элементов a[i] ... a[n] и меняем его местами с a[i]. Последовательность шагов при n=5 изображена на рисунке ниже.

 

 

Вне зависимости от номера текущего шага i, последовательность a[0]...a[i] (выделена курсивом) является упорядоченной. Таким образом, на (n-1)-м шаге вся последовательность, кроме a[n] оказывается отсортированной, а a[n] стоит на последнем месте по праву: все меньшие элементы уже ушли влево.

Далее  приведен  код  выборочной  сортировки  по  возрастанию элементов.

Как и при пузырьковой сортировке, данный алгоритм будет просматривать подмассивы и отыскивать наименьший элемент в каждом из них. Подмассивы будут ограничиваться с двух сторон индексами start и size-1. Правый индекс равен size-1, т.к., если все прочие элементы заняли свои места, наибольший элемент автоматически оказывается в правильном расположении.

 

#include <iostream.h> 
#include <conio.h> 
main() {
int array[100], size, min_pos, i, start, min, temp; 
cin>>size;       
for (i=0; i<size; i++)
cin>>array[i];  
start=0;         
while (start!=size-1) {
min=array[start];
min_pos=start;  
for (i=start; i<size; i++)
if (array [i]<min)  
{
min=array[i];    
min_pos=i;       }
if (min_pos!=start)
temp=array[start]; 
array[start]=array[min_pos]; 
array[min_pos]=temp; }
start++;                               
} for (i=0; i<size; i++)
cout<<array[i]<< " ";  
getch();   
return 0;
}
Пузырьковая сортировка и выборочная сортировка сравнительно неэффективны, их среднее время работы пропорционально n2, где n – количество элементов массива.

 

Быстрая сортировка

"Быстрая сортировка", хоть и была разработана более 40 лет назад, является наиболее широко применяемым и одним их самых эффективных алгоритмов.
Метод основан на подходе "разделяй-и-властвуй". Общая схема такова:

1.    из массива выбирается некоторый опорный элемент a[i],

2.    запускается процедура разделения массива, которая перемещает все ключи, меньшие, либо равные a[i], влево от него, а все ключи, большие, либо равные a[i] - вправо,

3.    теперь массив состоит из двух подмножеств, причем левое меньше, либо равно правого,

4.    для обоих подмассивов: если в подмассиве более двух элементов, рекурсивно запускаем для него ту же процедуру.

 

В конце получится полностью отсортированная последовательность.

 

Рассмотрим алгоритм подробнее.

1.   Введем два указателя: i и j. В начале алгоритма они указыва­ют, соответственно, на левый и правый конец последователь­ности.

2.   Будем двигать указатель i с шагом в один элемент по направ­лению   к   концу   массива,   пока   не   будет   найден   элемент array [i] >=p. Затем аналогичным образом начнем двигать ука­затель j  от конца массива к началу, пока не будет найден array[j]<=р.

3.  Далее, если i<=j, меняем array [i] и array [j] местами и про­должаем двигать i и j по тем же правилам.

4.   Повторяем шаг 2, пока i<=j.

Теперь массив разделен на две части: все элементы левой меньше либо равны опорному элементу, все элементы правой — больше либо равны опорному элементу. Раз­деление завершено.

Проиллюстрируем работу алгоритма на примере. Исходный массив:

5   2   7   2   13   3   8   15   19

Выберем опорный элемент р. Например, пусть это будет элемент 13. 1. 

 

Шаг 1. Меняем местами элементы в позициях 4 и 6:

5   2   7   2   8   3   13   15   19

Выбираем опорный элемент 7.

 

Шаг 2. Меняем местами элементы в позициях 2 и 5:

5   2   3   2   8   7   13   15   19

Выбираем опорный элемент 2.

 

Шаг 3. Меняем местами элементы в позициях 0 и 3:

2  2   3   5   8  7   13   15   19

Выбираем опорный элемент 8

 

Шаг 4. Меняем местами элементы в позициях 4 и 5:

2  2   3   5  7   8   13   15   19

 

Сортировка закончена, т. к. все элементы встали на свои позиции.

Далее приведен код быстрой сортировки. Быструю сортировку выполняет функция quicksort. В качестве параметров функции выступают верхняя и нижняя границы обрабатываемого на данном шаге подмассива.

 

#include <iostream.h>
#include <conio.h>
int array[100000];  
void quicksort(long High, long Low)     
{
long i,j; 
int p, temp;
i=Low;     
j=High;    
p=array[(Low+High)/2];  
while(array[i]<p)    
i++;      
while(array[j]>p)   j--;
if   (i<=j)     
{
temp=array[i]; 
array[i]=array[j]; 
array[j]=temp;
while (i<=j);  
if (j>Low) quicksort(j, Low);  
if (High>i) quicksort(High, i);  
}
main()  
{
int size;  
int i ;
cin>>size;     
for (i=0; i<size; i++) 
cin>>array[i];  
quicksort(size-1, 0);  
for (i=0; i<size; i++) 
cout<<array[i]<<” “;
getch();
return 0;
}
 

Сортировка массива при известном интервале значений элементов

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

Пусть дан массив натуральных чисел, значение каждого элемента которого не превышает девяти (табл. 1).

Таблица 1. Массив натуральных чисел

 

Для сортировки такого массива будет использоваться вспомога­тельный массив, число ячеек в котором совпадает с числом К. В нашем случае необходимо выделить память для массива из де­вяти ячеек, которые будут заполнены нулями (табл. 2).

 

Таблица 2. Вспомогательный массив, заполненный нулями

 

Теперь будем поочередно просматривать каждый элемент масси­ва, который необходимо отсортировать, и, если на /-ом шаге вы­делен элемент М, то во вспомогательном массиве значение эле­мента, находящегося в позиции М, увеличиваем на единицу.

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

Таблица 3.

 

Теперь вспомогательный массив обработан, можно выводить ре­зультат следующим образом: если элемент вспомогательного массива не равен нулю, то выводим номер его позиции Р раз, где Р равно значению данного элемента.

Итак, в нашем случае вывод будет следующим: 2, 4, 5, 5, 8, 9.

Теперь рассмотрим программу, которая сортирует массив нату­ральных чисел вышеуказанным методом. Чтобы не вводить все элементы массива, сгенерируем их случайным образом при по­мощи функции random(int). Ниже приведен код данной про­граммы:

 

#include <iostream.h>
#include <conio.h>
tinclude <stdlib.h>
int i, j, N, K, array[1000], Help_array[1000];
main()
{
cin>>N>>K;  
randomize();  
for (i=0; i<N; i++)
array[i]=random(K);  
for (i=0; i<N; i++)
Help_array[array[i]]+=1; 
for (i=1; i<K; i++)
if (Help_array!=0)
for (j=0; j<Help_array[i]; j++)
cout<<i<<” “;
getch();
return 0;
}

Поиск заданного элемента в массиве

Последовательный поиск элемента

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

 

#include <iostream.h>
#include <conio.h>
int Search(int array[], int N, int Number)
{
int i = 0; 
while (i!=N)
if (array[i]==Number) return i; else i++;
return -1; }
main() 
{
int i, Size, Numerals[1000], Number, P; 
соut<<"Введите размерность массива! "<<endl; 
cin>>Size;                         
cout<<" Введите элементы массива! "<<endl; 
for (i=0; i<Size; i++)
cin>>Numerals[i];  
cout<<"Введите число для поиска! "<<endl; 
cin>>Number;  
P=Search (Numerals, Size, Number);  
if (P==-l) 
cout<<"No"; else cout<<"Yes "<<P+l; 
getch();   
return 0;  
}

 

Двоичный (бинарный) поиск

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

Переменные Left и Right содержат, соответственно, левую и правую границы отрезка массива, где находится нужный нам элемент. Мы начинаем всегда с исследования среднего элемента отрезка. Если искомое значение меньше среднего элемента, мы переходим к поиску в верхней половине отрезка, где все элементы меньше только что проверенного. Таким образом, в результате каждой проверки мы вдвое сужаем область поиска. Так, в нашем примере, после первой итерации область поиска – всего лишь три элемента, после второй остается всего лишь один элемент. Таким образом, если длина массива равна 6, нам достаточно трех итераций, чтобы найти нужное число.

Двоичный поиск - очень мощный метод. Если, например, длина массива равна 1023, после первого сравнения область сужается до 511 элементов, а после второй - до 255. Легко посчитать, что для поиска в массиве из 1023 элементов достаточно 10 сравнений.

 

#include <iostream.h>
#include <conio.h>
int Binary_Search(int array[], int N, int Number)
{
int Left, Right, Middle;
Left=0;                                   
Right=N-1;                             
Middle=(Left+Right)/2;  
while (Left<=Right)
{
if (array[Middle]==Number) return Middle;
if (array [Middle]>Number) Right=Middle-l;
else Left=Middle+l;
Middle=(Left+Right)/2; }
return -1;  }
main() 
{
int i, Size, Numerals[100000], Number, Pos;
cout<<"Введите размер массива! "<<endl; 
cin>>Size;
for (i=0; i<Size; i++)
Numerals[i]=i;
cout<<"Введите число для поиска! "<<endl; 
cin>>Number;
Pos=Binary_Search(Numerals,Size,Number); 
if (Pos==-1) 
cout<<"Число в массиве отсутствует"; 
else cout<<"Число находится в позиции: "<<Pos; 
getch();
return 0;
}
 

 


Указатели

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

При работе с указателями действуют следующие правила:

·                     при объявлении переменной-указателя перед именем переменной указывается операция *;

·                     если одним оператором объявляется несколько переменных-указателей, то перед каждой такой переменной следует указывать операцию *;

·                     после объявления указателя его следует инициализировать адресом значения того же типа, что и тип указателя;

·                     для получения адреса переменной перед ее именем указывается операция взятия адреса &;

·                     для получения значения переменной по указателю на нее перед указателем ставится операция разыменования * (называемая иногда операцией взятия значения);

·                     указатель строки содержит адрес первого символа строки;

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

 

Объявление указателя

Так как указатель содержит адрес объекта, это дает возможность "косвенного" доступа к этому объекту через указатель. Предположим, что х - переменная, например, типа int, а рх - указатель, созданный неким еще не указанным способом. Унарная операция & выдает адрес объекта, так что оператор
 
рх = &х; 
 
присваивает адрес х переменной рх; говорят, что рх "указывает" на х. Операция & применима только к переменным и элементам массива, конструкции вида &(х-1) и &3 являются незаконными. Нельзя также получить адрес регистровой переменной.
Унарная операция * рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. Следовательно, если y тоже имеет тип int, то
 
y = *рх; 
 
присваивает y содержимое того, на что указывает рх. Так последовательность
 
рх = &х; 
y = *рх; 
 
присваивает y то же самое значение, что и оператор
 
y = x; //операция разыменования
 
переменные, участвующие во всем этом необходимо описать:
 
int x, y; int *px; 

 

Итак:
Если a - переменная, то &a - ее адрес в памяти.
Если p - указатель(адрес чего-то), *p - значение по этому адресу, которое можно читать и изменять.

 

 
Адрес
Значение
Указатель
pnt
*pnt
переменная
&var
var

В таблице всё относится к переменной и её адрес и значение.

Вы можете получить адрес и указателя:

 

= &pnt; 


В этом случае p - будет двойным указателем, который обычно объявляется следующим образом:

 
int **= &pnt; 

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

 

cout << **<< "\n"; 

 

Указатели могут входить в выражения. Например, если px указывает на целое x, то *px может появляться в любом контексте, где может встретиться x. Так оператор
 
y = *px + 1 
 

присваивает y значение, на 1 большее значения x;

 
printf("%d\n", *px) 
 

печатает текущее значение x;

 

d = sqrt((double) *px) 

 

получает в d квадратный корень из x, причем до передачи функции sqrt значение x преобразуется к типу double.

В выражениях вида

 

y = *px + 1 
 

унарные операции * и & связаны со своим операндом более крепко, чем арифметические операции, так что такое выражение берет то значение, на которое указывает px, прибавляет 1 и присваивает результат переменной y.

Ссылки на указатели могут появляться и в левой части присваиваний. Если px указывает на x, то

 

*px = 0 
 

полагает x равным нулю, а

 
*px += 1 
 

увеличивает его на единицу, как и выражение