72363

Программирование на языке высокого уровня СИ/СИ++

Книга

Информатика, кибернетика и программирование

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

Русский

2014-11-21

622 KB

6 чел.

Р. М. Тамарина

Программирование на языке высокого уровня СИ/СИ++

Учебное пособие

Вологда

2011


Сведения об авторе

Тамарина Р. М. – преподаватель Вологодского техникума железнодорожного транспорта–филиал федерального государственного бюджетного образовательного учреждения высшего профессионального образования «Петербургский государственный университет путей сообщения »

Программирование на языке высокого уровня СИ/СИ++. Учебное пособие для студентов специальности «Вычислительные машины, комплексы, системы и сети» (Электронный ресурс).

Учебное пособие содержит изложение учебного материала по дисциплине «Программирование на языке высокого уровня».

В данном пособие подробно рассматривается различные аспекты программирование на языке высокого уровня СИ/СИ++; рассматривается  программирование с применение различных типов данных и операторов данного языка и принципов ООП.

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

Тамарина Р. М. выражает благодарность студентам ВМ-28 Вологодского техникума железнодорожного транспорта– филиал федерального государственного бюджетного образовательного учреждения высшего профессионального образования «Петербургский государственный университет путей сообщения » специальности «Вычислительные машины, комплексы, системы и сети» Бологовой А., Проншину Е. в участии подготовки материала данного пособия.

 


СОДЕРЖАНИЕ

Глава 1.История и основные понятия СИ и СИ++

3

Глава 2.Элементы языка СИ++

4

2.1. Алфавит

4

2.2. Идентификаторы

4

2.3. Служебные (ключевые) слова

4

Глава 3. Типы данных

6

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

7

3.2. Константы

7

Глава 4. Операции и выражения

10

4.1. Арифметические операции

10

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

11

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

11

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

12

4.5. Операция явного преобразования типа

(Операция «Тип»)

13

4.6. Операция SIZEOF

13

4.7. Операция «Запятая»

13

4.8. Операция «Условие ?»

14

4.9. Приведение типов при вычислении выражений

14

Глава 5. Линейные программы на СИ/СИ++

16

5.1. Структура программы

16

5.2. Форматированный вывод на экран

17

5.3. Форматированный ввод с клавиатуры

19

5.4. Потоковый Ввод-Вывод в СИ++

20

Глава 6. Программирование ветвлений

22

6.1. Условный оператор

22

6.2. Оператор выбора (Переключатель)

23

Глава 7. Программирование циклов

25

7.1. Цикл с предусловием

25

7.2. Цикл с постусловием

26

7.3. Цикл с параметром

27

Глава 8. Функции

30

8.1. Определение функции. Обращение к функции

30

8.2. Прототип функции

32

8.3. Использование библиотечных функций

33

8.4. Рекурсивные определения функций

35

8.5. Классы памяти

36

Глава 9. Массивы

38

9.1. Многомерные массивы

39

Глава 10. Указатели

42

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

44

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

45

11.1. Строка как параметр функции

46

Глава 12. Структуры и объединения

47

Глава 13. Потоковый Ввод-Вывод в стандарте СИ++

50

Глава 14. Объектно – ориентированное программирование с

СИ++

54

Глава 15. Форматированный Ввод-Вывод СИ++

56

15.1. Использование манипуляторов

59


1 ИСТОРИЯ И ОСНОВНЫЕ ПОНЯТИЯ  СИ И СИ++

Язык Си был создан в 1972 г. сотрудником фирмы Веll Lаbоrаtоries в США Денисом Ритчи.

По замыслу автора, язык Си должен был обладать противоречивыми свойствами. С одной стороны, это язык программирования высокого уровня, поддерживающий методику структурного программирования (подобно Паскалю). С другой стороны, этот язык должен обеспечивать возможность создавать такие системные программы, как компиляторы и операционные системы. До появления Си подобные программы писались исключительно на языках низкого уровня — Ассемблерах, Автокодах. Первым системным программным продуктом, разработанным с помощью Си, стала операционная система UNIX. Из-за упомянутой выше двойственности свойств нередко в литературе язык Си называют языком среднего уровня. Стандарт Си был утвержден в 1983 г. Американским национальным институтом стандартов (АNSI) и получил название АNSI С.

В начале 1980-х гг. в той же фирме Веll Lаbоrаtоries ее сотрудником Бьерном Строуструпом было разработано расширение языка Си, предназначенное для объектно-ориентированного программирования. По сути дела, был создан новый язык, первоначально названный «Си с классами», а позднее (в 1983 г.) получивший название Си++ (Си-плюс-плюс). Язык Си++ принято считать языком объектно-ориентированного программирования. Однако этот язык как подмножество включает в себя Си и по-прежнему сохраняет свойства языка для системного программирования. Все существующие версии трансляторов для Си++  поддерживают стандарт АNSI С.

Из сказанного выше следует, что язык Си++ поддерживает как процедурную, так и объектно-ориентированную парадигмы программирования.

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

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

Пример 1

/* Ваша первая программа на Си */

#include <stdio.h>

void main()

{

printf ("\n Здравствуй, Мир! \n");

}

Здесь первая строка представляет собой комментарий. Начало и конец комментария ограничиваются парами символов /* и */. Все, что расположено между этими символами, компилятор игнорирует.

Вторая строка программы содержит директиву препроцессора:

#include <stdio.h>

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

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

void main()

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

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

после заголовка.

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

Здравствуй, Мир!

Впереди данной строки и после нее будет пропущено по одной пустой строке, что обеспечивается наличием управляющих символов \n.

Следующий пример содержит программу, выполняющую те же самые действия, но написанную на Си++.

Пример 2

// Ваша первая программа на Си++

#include <iostream.h>

void main()

 {

соut "\n3дравствуй Мир!\n";

}

Первое отличие от программы из примера 1 состоит в форме комментария. В Си++ можно использовать строчный комментарий, который начинается с символов / / и заканчивается концом строки. Для организации вывода на экран к программе подключается специальная библиотека объектов, заголовочный файл которой имеет имя iostream.h. Вывод осуществляется посредством объекта соut из этой библиотеки.

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

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

2 ЭЛЕМЕНТЫ ЯЗЫКА СИ++

2.1 АЛФАВИТ

В алфавит языка Си++ входят:

• латинские буквы: от a до z (строчные) и от А до Z (прописные);

• десятичные цифры: О, 1, 2, 3, 4, 5, 6, 7, 8, 9;

• специальные символы: " { } , | [ ] () + - / % \  ;  ' : ? < = > _ ! & # ~  ^ . *

К специальным символам относится также пробел.

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

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

++ -- == && || << >> >= <= += -= *= /= ?: /* */ //

В Си++ в качестве ограничителей комментариев могут использоваться как пары символов /* и */, принятые в языке Си, так и символы //, используемые только в Си++. Признаком конца такого комментария является невидимый символ перехода на новую строку. Примеры:

/* Это комментарий, допустимый в Си и Си++ */

// Это строчный комментарий, используемый только в Си++

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

2.2 ИДЕНТИФИКАТОРЫ

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

B12 rus hard_RAM_disk MAX ris_32

В отличие от Паскаля в Си/Си++ различаются прописные и строчные буквы. Это значит, что, например, flag, FLAG, Flag, FlAg - разные идентификаторы.

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

2.3 СЛУЖЕБНЫЕ (КЛЮЧЕВЫЕ) СЛОВА

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

asm

auto

break

case

catch

char

class

const

continue

default

delete

do

double

else

enum

extern

float

for

friend

goto

if

inline

int

long

new

operator

private

protected

public

register

return

short

signed

sizeof

static

struct

switch

template

this

throw

try

typedef

typeid

union

unsigned

virtual

void

volatile

while


3 ТИПЫ ДАННЫХ

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

В Си/Си++ имеется четыре базовых арифметических (числовых) типа данных. Из них два целочисленных - char, int - и два плавающих (вещественных) - float и double. Кроме того, в программах можно использовать некоторые модификации этих типов, описываемых с помощью служебных слов - модификаторов. Существуют два модификатора размера - short (короткий) и long (длинный) - и два модификатора знаков - signed (знаковый) и unsigned (беззнаковый). Знаковые модификаторы применяются только к целым типам.

Как известно, тип величины связан с ее формой внутреннего представления, множеством принимаемых значений и множеством операций, применимых к этой величине. Размер типа int и unsigned int зависит от размера слова операционной системы, в которой работает компилятор Си++. В 16-разрядных ОС (MS DOS) этим типам соответствуют 2 байта, в 32-разрядных (Windows) - 4 байта.

Программисту, работавшему на Паскале, покажется странным, что тип char причислен к арифметическим типам. Ведь даже его имя указывает на то, что это символьный тип! В Си/Си++ величины типа char могут рассматриваться в программе и как символы, и как целые числа. Все зависит от контекста, т.е. от способа использования этой величины. В случае интерпретации величины типа char как символа ее числовое значение является ASCII - кодом. Следующий пример иллюстрирует сказанное.

char а=65;

printf ("%с", а) ; /*На экране появится символ А*/

printf ("%d", а) ; /*На экране появится число 65*/

Символы "%с" являются спецификацией формата ввода/вывода символьных данных, а "%d" - спецификацией для целых чисел.

Еще одной особенностью Си, которая может удивить знатоков Паскаля, является отсутствие среди базовых типов логического типа данных. Между тем, как мы дальше увидим, в Си используются логические операции и логические выражения. В качестве логических величин в Си/Си++ выступают целые числа. Интерпретация их значений в логические величины происходит по правилу: равно нулю - ложь (в Паскале — false), не равно нулю - истина (в Паскале - true).

В последние версии Си++ добавлен отдельный логический тип с именем bool. Его относят к разновидности целых типов данных.


3.1 ОПИСАНИЕ ПЕРЕМЕННЫХ

В программах на Си/Си++ имеет вид:

имя_типа список_переменных;

Примеры описаний:

char symbol, cc;

unsigned char code;

int number, row;

unsigned long long_number;

float x, X, cc3;

double e, b4;

long double max_num;

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

Например:

float pi=3.14159, с=1.23;

unsigned int year=2000;

3.2 КОНСТАНТЫ

-Запись целых констант. Целые десятичные числа, начинающиеся не с нуля, например: 4, 356, -128.

Целые восьмеричные числа, запись которых начинается с нуля, например: 016, 077.

Целые шестнадцатеричные числа, запись которых начинается с символов 0x, например: 0x1A, 0х253, 0xFFFF.

Тип константы компилятор определяет по следующим правилам: если значение константы лежит в диапазоне типа int, то она получает тип int; в противном случае проверяется, лежит ли константа в диапазоне типа unsigned int, в случае положительного ответа она получает этот тип; если не подходит и он, то пробуется тип long и, наконец, unsigned long. Если значение числа не укладывается в диапазон типа unsigned long, то возникает ошибка компиляции.

-Запись вещественных констант. Если в записи числовой константы присутствует десятичная точка (2.5) или экспоненциальное расширение (1Е-8), то компилятор рассматривает ее как вещественное число и ставит ей в соответствие тип double. Примеры вещественных констант: 44.  3.14159  44Е0  1.5Е-4.

-Использование суффиксов. Программист может явно задать тип константы, используя для этого суффиксы. Существуют три вида суффиксов: F(f) - float; U(u) - unsigned; L(l) - long (для целых и вещественных констант). Кроме того, допускается совместное использование суффиксов U и L в вариантах UL или LU.

Примеры:

3.14159F - константа типа float, под которую выделяется 4 байта памяти;

3.14L - константа типа long double, занимает 10 байт;

50000U - константа типа unsigned int, занимает 2 байта памяти (вместо четырех без суффикса);

0LU - константа типа unsigned long, занимает 4 байта;

24242424UL - константа типа unsigned long, занимает 4 байта.

-Запись символьных и строковых констант. Символьные константы заключаются в апострофы. Например: 'А', 'а', '5', '+'. Строковые константы, представляющие собой символьные последовательности, заключаются в двойные кавычки. Например: "rezult", "введите исходные данные".

Особую разновидность символьных констант представляют так называемые управляющие символы. Их назначение - управление выводом на экран. Как известно, такие символы расположены в начальной части кодовой таблицы ASCII (коды от 0 до 31) и не имеют графического представления. В программе на Си они изображаются парой символов, первый из которых ' \ '. Вот некоторые из управляющих символов:

'\n'-переход на новую строку;

'\t'-горизонтальная табуляция;

‘\a'-подача звукового сигнала

Управляющие символьные последовательности являются частным случаем эскейп-последовательностей (ESC-sequence), с помощью которых можно задать символьную константу указанием ее кода. Код символа можно указать в восьмеричном или в шестнадцатеричном представлении. Формат восьмеричного представления: ' \ddd '. Здесь  d - восьмеричная цифра (от 0 до 7).

Формат шестнадцатеричного представления: ' \xhh ' (или ' \Xhh '), где h - шестнадцатеричная цифра (от 0 до F). Например, константа, соответствующая заглавной латинской букве А, может быть представлена тремя способами: 'А', '\101’, '\х41’.

-Именованные константы (константные переменные). Как  и  в

Паскале, в программе на Си/Си++ могут использоваться именованные константы. Употребляемое для их определения служебное слово const принято называть квалификатором доступа. Квалификатор const указывает на то, что данная величина не может изменяться в течение всего времени работы программы. В частности, она не может располагаться в левой части оператора присваивания. Примеры описания константных переменных:

const float pi=3.14159;

const int iMIN=1, iМАХ=1000;

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

# define <имя константы> <значение константы>

Например:

# define  iMIN 1

# define  iМАХ 1000

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

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

Х=iМАХ - iМIN;

то в результате препроцессорной обработки он примет вид:

Х=1000 - 1;

При этом идентификаторы iМАХ и iМIN не требуют описания внутри программы.

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

enum {А, В, С, D};

В результате имени А будет сопоставлена константа 0, имени B - константа 1, C - 2, D - 3. По умолчанию значение первой константы равно нулю.

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

enum {А=10, В, С, D};

В результате будут установлены следующие соответствия: А=10, В=11, С=12, D=13.

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

enum {А=10, В=20, С=35, D=100};

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

enum metal {Fe, Co, Na, Cu, Zn} ;

                  metal Met1, Met2;

Здесь идентификатор metal становится именем типа. После такого описания в программе возможны следующие операторы:

Met1=Na, Met2=Zn;


4 ОПЕРАЦИИ И ВЫРАЖЕНИЯ

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

а+b         12.5-z        2*(Х+Y)   

х++         х+++b         --n*2         n*=1

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

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

4.1 АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ

К арифметическим операциям относятся:

-   вычитание или унарный минус;

+   сложение или унарный плюс;

*   умножение;

/   деление;

%   деление по модулю (аналог Mod в Паскале);

++ унарная операция увеличения на единицу (инкремент);

--унарная операция уменьшения на единицу (декремент).

Все операции, кроме деления по модулю, применимы к любым

числовым типам данных. Операция % применима только к целым числам.

Рассмотрим особенности выполнения операции деления. Если делимое и делитель - целые числа, то и результат -  целое число. В этом случае операция / действует аналогично Div в Паскале. Например, значение выражения 5/3 будет равно 2, а при вычислении 1/5 получится 0.

Если хотя бы один из операндов имеет вещественный тип, то и результат будет вещественным. Например, операции 5. / 3, 5 . / 3., 5/3. дадут вещественный результат 1.6666.

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

х=х+1;   ++х;   х++

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

а=3; b=2;

с=а++*b++;

В результате выполнения переменные получат следующие значения: а =4, b=3, с= 6. Второй пример:

а=3; b=2;

с=++а*++b;

Результаты будут такими: а=4, b=3, с=12.

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

было использовано в выражении, а префиксные операции - до использования. Поэтому в первом примере значение переменной с вычислялось как произведение 3 на 2, а во втором - как произведение 4 на 3.

По убыванию старшинства арифметические операции расположены в следующем порядке:

++, --

- (унарный минус)

*, /, %

+, -

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

4.2 ОПЕРАЦИИ ОТНОШЕНИЯ

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

<      меньше,

<=    меньше или равно,

>      больше,

>=    больше или равно, равно,

! =     не равно.

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

Примеры отношений:

a<0,   101>=105,   'а'=='А',    'а'!='А'

Результатом второго и третьего отношений будет 0 - ложь;

результат четвертого отношения равен 1 — истина; результат первого отношения зависит от значения переменной «а».

4.3 ЛОГИЧЕСКИЕ ОПЕРАЦИИ

Три основные логические операции в языке Си записываются иначе, чем в Паскале.

!         операция отрицания (НЕ),

  &&     конъюнкция, логическое умножение (И),

||        дизъюнкция, логическое сложение (ИЛИ).    

Например, логическое выражение, соответствующее системе неравенств 0 < х < 1 в программе на Си запишется в виде следующего логического выражения:

х>0 && х<1

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

!

> < >= <=

== !=

&&

||

Помимо рассмотренных в Си имеются поразрядные логические операции. Эти операции выполняются над каждой парой соответствующих двоичных разрядов внутреннего представления операндов. Их еще называют битовыми логическими операциями. Знаки

битовых логических операций:

& поразрядная конъюнкция (И),

I поразрядная дизъюнкция (ИЛИ),

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

~ поразрядное отрицание (НЕ).

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

4.4 ОПЕРАЦИЯ ПРИСВАИВАНИЯ

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

а=b=с=х+y;

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

В языке Си имеются дополнительные операции присваивания, совмещающие присваивание с выполнением других операций. Среди них: +=, -=, /=, *=, %=. Приоритет у них такой же, как и у простого присваивания. Примеры использования этих операций:

а+=2    эквивалентно а=а+2,

х-=а+b  эквивалентно х=х-(а+b),

р/=10   эквивалентно р=р/10,

m*=n    эквивалентно m=m*n,

r%=5    эквивалентно r=r%5.

Заметим, что вместо выражения а=а+2 предпочтительнее писать в программе а+=2, поскольку второе выражение будет вычисляться быстрее.

4.5 ОПЕРАЦИЯ ЯВНОГО ПРЕОБРАЗОВАНИЯ ТИПА

(ОПЕРАЦИЯ «ТИП»)

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

(имя_типа) операнд

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

(long) 8, (float) 1, (int) х%2

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

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

float с;

   int а=1, b=2;

   с= (float) а/b;

В результате переменная с получит значение 0,5. Без преобразования типа ее значение стало бы равно 0.

4.6 ОПЕРАЦИЯ SIZEOF

Эта операция имеет две формы записи:

sizeof (тип) и sizeof (выражение)

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

sizeof(int)   результат - 2

sizeof(1)   результат - 2

sizeof(0.1)  результат - 8

sizeof(1L)   результат - 4

sizeof(char)  результат - 1

sizeof(‘a’)   результат – 2

4.7 ОПЕРАЦИЯ «ЗАПЯТАЯ»

Эта необычная операция используется для связывания нескольких выражений в одно. Несколько выражений, разделенных запятыми, вычисляются последовательно слева направо. В качестве результата такого совмещенного выражения принимается значение самого правого выражения. Например, если переменная X имеет тип int, то значение выражения (Х=3, 5*Х) будет равно 15, а переменная X примет значение 3.

4.8 ОПЕРАЦИЯ «УСЛОВИЕ ?:»

Это единственная операция, которая имеет три операнда. Формат операции:

выражение1 ? вьгражение2 : выражениеЗ

Данная операция реализует алгоритмическую структуру ветвления. Алгоритм ее выполнения следующий: первым вычисляется значение выражения 1, которое обычно представляет собой некоторое условие. Если оно истинно, т. е. не равно 0, то вычисляется выражение 2 и полученный результат становится результатом операции. В противном случае в качестве результата берется значение выражения 3.

Пример а Вычисление абсолютной величины переменной X можно организовать с помощью одной операции:

X<0 ? –X : X;

Пример б   Выбор большего значения из двух переменных а и b:

max=(а<=b)?b:a;

Пример в  Заменить большее значение из двух переменных а и     

b на единицу:

(а>b)?а:b=1;

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

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

Подведем итог всему разговору об операциях Си/Си++, сведя их в общую таблицу и расположив по рангам (см.приложение Б2).

4.9 ПРИВЕДЕНИЕ ТИПОВ ПРИ ВЫЧИСЛЕНИИ ВЫРАЖЕНИЙ

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

•все символы, составляющие выражение, записываются в строку (нет надстрочных и подстрочных символов);

•в выражении проставляются все знаки операций;

•при записи выражения учитываются приоритеты операций;

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

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

•преобразование не выполняется, если оба операнда имеют одинаковый тип;

•при разных типах операндов происходит приведение величины с младшим типом к старшему типу (кроме операции присваивания);

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

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

charshotintlong

Порядок старшинства вещественных типов следующий:

floatdoublelong double


5 ЛИНЕЙНЫЕ ПРОГРАММЫ НА СИ/СИ++

5.1 СТРУКТУРА ПРОГРАММЫ

Общая структура программы на Си/Си++ следующая:

директивы_препроцессора

определение_функции_1 определение_функции_2

………………………………

определение_функции_N

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

директивы_препроцессора

void main()

{ определения_объектов;

исполняемые_операторы;

}

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

Пример 3. Дано: а, b, с - стороны треугольника. Вычислить S - площадь треугольника. По формуле Герона:

S =    р(р-а)(р-b)(р-с),

где p - полупериметр треугольника.

#include <stdio.h>

#include <math.h>

void main ()

{ float а,b,с,p,s;

printf ("\nа=");  scanf ("%f", &а) ;

printf ("\nb=");  scanf ("%f", &b) ;

printf ("\nc=");  scanf ("%f", &c) ;

р=(а+b+с)/2;

S=sqrt (р*(р-а)*(р-b)*(р-с)) ;

printf ("\nПлощадь треугольника = %f", s) ;

}

Разберемся более подробно во всех элементах данной

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

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

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

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

i++;

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

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

директива препроцессора #include <math.h>. Здесь math.h - имя заголовочного файла этой библиотеки. (Описания некоторых наиболее часто используемых функций математической библиотеки Си смотри в приложении Б3).

В рассматриваемой программе операторы printf (...); и scanf (...); реализуют соответственно вывод на экран и ввод исходных данных с клавиатуры. Они осуществляют обращение к соответствующим функциям стандартной библиотеки ввода-вывода Си, заголовочный файл которой имеет имя stdio.h.

5.2 ФОРМАТИРОВАННЫЙ ВЫВОД НА ЭКРАН

Оператор вызова функции printf () имеет следующую структуру:

printf (форматная_строка, список_аргументов) ;

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

В примере 3 оператор printf ("\na=") ; содержит текст ("а=") и управляющие символы ("\n"). Текст выводится на экран в том виде, в котором он записан. Управляющие символы влияют на расположение на экране выводимых знаков. В результате выполнения этого оператора на экран с новой строки выведутся символы а=.

Признаком управляющего символа является значок \. Ниже приводится их список:

\n - перевод строки;

\t - горизонтальная табуляция;

\r - возврат курсора к началу новой строки;

\а - сигнал-звонок;

\b - возврат на один символ (одну позицию);

\f - перевод (прогон) страницы;

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

Оператор printf ("\nПлощадь треугольника=%f", s) ; содержит все виды параметров функции printf. Список аргументов состоит из одной переменной s. Ее значение выводится на экран. Пара символов %f является спецификацией формата выводимого значения переменной s. Значок % - признак формата, а буква f указывает на то, что выводимое число имеет вещественный (плавающий) тип и выводится на экран в форме с фиксированной точкой. Например, если в результате вычислений переменная s получит значение 32,435621, то на экран выведется:

Площадь треугольника=32.435621

Спецификатор формата определяет форму внешнего представления выводимой величины. Вот некоторые спецификаторы формата:

%с - символ;

%s - строка;

%d - целое десятичное число (тип int);

%u - целое десятичное число без знака (тип unsigned);

%f. -  вещественные числа в форме с фиксированной точкой;

%е - вещественные числа в форме с плавающей точкой (с мантиссой и порядком).

Например, после выполнения следующих операторов

float m, p;

int k;

m=84.3; k= -12; p=32.15;

printf ("\nm=%f\tk=%d\tp=%е",m, k, p);

на экран выведется строка:

 m=84.299999 k= -12   p=3.21500e+01

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

ментами списка аргументов устанавливается в порядке их записи слева направо.

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

printf ("\nm=%5.2f\tk=%5d\tp=%8.2е\tp=%11.4е",m, k, p, p);

В результате на экране получим:

m=84.30       k= -12       p=   32.15        p= 3.2150е+01

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

К спецификаторам формата могут быть добавлены модификаторы в следующих вариантах:

%ld - вывод long int;

%hu – вывод short unsigned;

%Lf - вывод long double.

5.3 ФОРМАТИРОВАННЫЙ ВВОД С КЛАВИАТУРЫ

Оператор вызова функции scanf () имеет следующую структуру:

scanf (форматная_строка, список_аргументов) ;

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

из примера 3 имеется оператор:

scanf ("%f",&а) ;

Здесь "%f" - форматная строка; &а — список аргументов, состоящий из одного элемента. Этот оператор производит ввод числового значения в переменную а.

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

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

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

*ширина_поля модификатор спецификатор

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

d - для целых десятичных чисел (тип int);

u - для целых десятичных чисел без знака (тип unsigned int);

f - для вещественных чисел (тип float) в форме с фиксированной точкой;

e - для вещественных чисел (тип float) в форме с плавающей точкой.

Звездочка в спецификации позволяет пропустить во входном потоке определенное количество символов. Ширина поля - целое

положительное число, позволяющее определить число символов из входного потока, принадлежащих значению соответствующей вводимой переменной. Как и в спецификациях вывода для функции printf (), в спецификациях ввода функции scanf ()  допустимо использование модификаторов h, l, L. Они применяются при вводе значений модифицированных типов:

hd - для ввода значений типа short int;

ld - для ввода значений типа long int;

lf, le - для ввода значений типа double в форме с фиксированной и плавающей точкой;

Lf, Le - для ввода значений типа long double в форме с фиксированной и плавающей точкой.

В программе из примера 1 все три величины а, b, с можно ввести одним оператором:

scanf ("%f%f%f",&а, &b, &с) ;

Если последовательность ввода будет такой:

5  3.2  2.4 <Enter>

то переменные получат следующие значения: а = 5,0, b = 3,2, с = 2,4. Разделителем в потоке ввода между различными значениями может быть любое количество пробелов, а также другие пробельные символы: знак табуляции, конец строки. Только после нажатия на клавишу Enter вводимые значения присвоятся соответствующим переменным. До этого входной поток помещается в буфер клавиатуры и может редактироваться.

5.4 ПОТОКОВЫЙ ВВОД-ВЫВОД В СИ++

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

помощью заголовочного файла stdio.h, как это делалось выше. Однако в Си++ имеются свои специфические средства ввода-вывода. Это библиотека классов, подключаемая к программе с помощью файла iostream.h. В этой библиотеке определены в качестве объектов стандартные символьные потоки со следующими именами:

cin - стандартный поток ввода с клавиатуры;

cout - стандартный поток вывода на экран.

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

cin>>х;

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

cout<<а+b;

cout<<"\nРезультат="<<Y;

cout<<"х=”<<x<<" у=”<<у<<” z=”<<z<<endl;

Из приведенных примеров видно, что в выходном потоке можно использовать управляющие символы, как и при использовании функции printf (); перед каждым элементом вывода нужно ставить знак операции <<. Элемент вывода endl является так называемым манипулятором, определяющим перевод курсора на новую строку (действует аналогично управляющему символу \n).

В процессе потокового ввода-вывода происходит преобразование

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

Перепишем программу из примера 3 в варианте с использованием потокового ввода-вывода Си++. Пример 4:

#include <iostream.h>

#include <math.h>

void main ()

{float а, b, с, p, s;

cout<<"\nа="; cin>>а;

cout<<"\nb="; cin>>b;

cout<<”\nc="; cin>>с;

р=(а+b+с)/2;

s=sqrt (р*(р-а)*(р-b)* (р-с));

cout<<"\nПлощадь треугольника="<<s;

}


6 ПРОГРАММИРОВАНИЕ ВЕТВЛЕНИЙ

Для программирования ветвящихся алгоритмов в языке Си имеется несколько различных средств. К ним относятся рассмотренная выше операция условия ?:, условный оператор if и оператор выбора switch.

6.1 УСЛОВНЫЙ ОПЕРАТОР

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

if (выражение) оператор1; else оператор2;

Это полная форма оператора, программирующая структуру полного ветвления. Обычно выражение - это некоторое условие, содержащее операции отношения и логические операции. Значение выражения приводится к целому и интерпретируется в соответствии с правилом: равно нулю - ложь, не равно нулю - истина. Если выражение истинно, выполняется оператор1, если ложно - оператор2.

Необходимо обратить внимание на следующие особенности синтаксиса условного оператора:

•выражение записывается в круглых скобках;

•точка с запятой после оператора 1 ставится обязательно. Последнее обозначает, что правило Паскаля - не ставить точку с запятой перед else - здесь не работает.

Возможно использование неполной формы условного оператора

if (выражение) оператор;

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

if (a>b) max=a; else max=b;

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

max=а; if (b>а) max=b;

Напомним, что эту же задачу можно решить с помощью операции «условие» .

(Примеры программирования вложенных ветвящихся структур смотри в приложении).

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

Пример 5:

// Площадь треугольника

#include <iostream.h>

#include <math.h>

void main ()

{ float а, b, с, р, s;

cout<<”\nа="; cin>>а;

cout<<"\nb="; cin>>b;

cout<<"\nс="; cin>>c;

if(а>0 && b>0 && с>0 && а+b>с && а+с>b && b+с>а)

{ р=(а+b+с)/2;

 s=sqrt (р*(р-а)*(р-b)*(р-с)) ;

cout<<"\nПлощадь треугольника="<<s;

}

 else ("\n Неверные исходные данные.");

}

6.2 ОПЕРАТОР ВЫБОРА (ПЕРЕКЛЮЧАТЕЛЬ)

Формат оператора выбора:

switch (целочисленное выражение)

{ case константа 1: список_операторов;

case константа 2: список операторов;

…………………………………………….

default: список операторов

Последняя строка (default) может отсутствовать. Выполнение оператора происходит в следующем порядке:

1. Вычисляется выражение.

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

3. Если ни с одной из констант совпадения не произошло, то выполнятся операторы после слова default.

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

Рассмотрим фрагмент программы, который переводит числовую оценку знаний ученика в ее словесный эквивалент. Согласно вузовской системе: 5 - «отлично», 4 - «хорошо», 3 - «удовлетворительно», 2 - «неудовлетворительно».

Пример 6:

#include <iostream.h>

void main ()

{int ball;

  cout<<"\nВведите оценку: "; cin>>ball;

  switch (ball)

     { case 2: cout<<"\'tЭто неудовлетворительно! \n";

                   break;

       case 3: cout<<"\t:Это удовлетворительно! \n";

                  break;

       case 4: cout<<"\'t Это хорошо !\n"; break;

       case 5: cout<<"\'tЭто отлично !\n"; break;

       default cout:«"\tНет такой оценки!\n";

       }

    }

Здесь используется еще один новый для нас оператор break - оператор выхода. Его исполнение завершает работу оператора выбора, т.е. происходит «обход» других ветвей. Вот два варианта

результатов выполнения этой программы:

Введите оценку: 3    Это удовлетворительно!

Введите оценку: 7    Нет такой оценки!

Если на всех ветвях убрать оператор break, то результат может выглядеть следующим образом:

Введите оценку: 3    Это удовлетворительно!

Это хорошо!

Это отлично!

Нет такой оценки!

В этом случае выполнились операторы на всех ветвях, начиная с той, которая помечена константой 3.

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

 case 5: y=y*x;

case 4: y=y*x;

case 3: y=y*x;

case 2: y=y*x;

case 1: y=y*x; cout<<”y=”<<y; break;

default cout <<"Степень больше 5";

}


7 ПРОГРАММИРОВАНИЕ ЦИКЛОВ

В Си, как и в Паскале, существуют все три типа операторов цикла: цикл с предусловием, цикл с постусловием и цикл с параметром.

7.1 ЦИКЛ С ПРЕДУСЛОВИЕМ

Формат оператора цикла с предусловием:

while (выражение) оператор;

Цикл повторяет свое выполнение, пока значение выражения отлично от нуля, т. е. заключенное в нем условие цикла истинно.

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

Пример 7:

Программа на Паскале

Program Factorial                  

Var F: Longint;  i,N:

Integer;

Begin write (‘N=’);

ReadLn (N);

F:=1;  i :=1;

While i<N Do

Begin

F:=F*I;

i:=i+1

End;

WriteLn (N,’! =’, F)

End.

Программа на Си++

// Программа вычисления

факториала

#include <iostream.h>

void main ()

{  long int F;

int i, N;

cout<<”N=”; cin>>N;

F=i=1;

while (i<=N)  F=F*i++;

cout<<”\n”<<N<<”!=”<<F;

}


Обратите внимание на операторы в теле цикла. Конечно, и в Си-программе можно было написать два оператора присваивания, объединив их фигурными скобками. Однако использованный способ записи более лаконичен и более характерен для Си. Этот же самый оператор можно было записать еще короче: F*=i++

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

Интересно свойство следующего оператора:

while (1) ;

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

7.2 ЦИКЛ С ПОСТУСЛОВИЕМ

Формат оператора цикла с постусловием:

 do оператор while (выражение) ;

Цикл выполняется до тех пор, пока выражение отлично от Нуля, т.е. заключенное в нем условие цикла истинно. Выход из Цикла происходит после того, как значение выражения станет Ложным,

иными словами равным нулю. Таким образом, в отличие от оператора repeat ... until, используемого в Паскале, где в конце пишется условие выхода из цикла, в операторе dowhile в Си в конце пишется условие повторения цикла. В качестве призера рассмотрим программу вычисления N!, в которой используется цикл с постусловием, и сопоставим ее с аналогичной программой на Паскале.

Пример 8:

Программа на Паскале

Program Factorial                  

Var F: Longint;  i,N:

Integer;

Begin write (‘N=’);

ReadLn (N);

F:=1;  i :=1;

Repeat

F:=F*I;

i:=i+1

Until i>N;

WriteLn (N,’! =’, F)

End.

Программа на Си++

// Программа

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

#include <iostream.h>

void main ()

{  long int F;

int i, N;

cout<<”N=”; cin>>N;

F=i=1;

do  F*=i++;

while (i<=N);

cout<<”\n”<<N<<”!=”<<F;

}

7.3 ЦИКЛ С ПАРАМЕТРОМ

Формат оператора цикла с параметром:

for (выражение_1; выражение_2; выражение_3)

оператор;

Выражение 1 выполняется только один раз в начале цикла. Обычно оно определяет начальное значение параметра цикла (инициализирует параметр цикла). Выражение 2 - это условие выполнения цикла. Выражение 3 обычно определяет изменение параметра цикла, оператор - тело цикла, которое может быть простым или составным. В последнем случае используются

фигурные скобки.

Алгоритм выполнения цикла for представлен на блок-схеме

Рис – 1

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

С помощью цикла for нахождение N! можно организовать следующим образом:

F=1;

for (i=1; i<=N; i++) F=F*i;

Используя операцию «запятая», можно в выражение 1 внести инициализацию значений сразу нескольких переменных:

for (F=1; i=1, i<=N; i++) F=F*i;

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

F=1;

i=1;

for (;i<=N; i++)  F=F*i;

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

внесена в выражение 3.

For (F=1,i=1; i<=N; F=F*i,i++) ;

Этот же оператор можно записать в следующей форме:

For (F=1,i=1; i<=N; F*=i++) ;

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

for(n=1,S=0;1.0/n>eps && n<INT_МАХ;n++) S+=1.0/n;

И наконец, эта же самая задача с пустым телом цикла:

for(n=1,S=0;1.0/n>eps && n<INT_МАХ;S+=1.0/n++);

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

for(х=2;х<=9;х++)

for(у=2;у<=9;у++)

cout<<"\n"<<х<<"*"<<у<<"="<<х*у;

На экране будет получен следующий результат:

2*2=4 2*3=6

9*8=72 9*9=81

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

for(i=1; i<=100; i++)      { if (i%2) continue; cout<<"t"<<i; }

Для нечетных значений переменной i остаток от деления на 2

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

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

goto  метка;

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

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

for (...)

{ while (...)

{ for (...)

{... goto exit ...}

}

}

exit: cout<<"выход из цикла";

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

• нельзя входить внутрь блока извне;

• нельзя входить внутрь условного оператора (if...еlse...);

• нельзя входить внутрь переключателя;

• нельзя входить внутрь цикла.


8 ФУНКЦИИ

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

8.1 ОПРЕДЕЛЕНИЕ ФУНКЦИИ. ОБРАЩЕНИЕ К ФУНКЦИИ

В Си используется лишь один тип подпрограмм - функция. Здесь вообще не принято употреблять термин «подпрограмма», потому что функция является основной программной единицей в Си, минимальным исполняемым программным модулем. Всякая программа обязательно включает в себя основную функцию с именем main. Если в программе используются и другие функции, то они выполняют роль подпрограмм.

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

Требуется составить программу нахождения наибольшего значения из трех величин - mах (а, b, с). Для ее решения можно использовать вспомогательный алгоритм нахождения максимального значения из двух, поскольку справедливо равенство: mах (а, b, с) = mах (mах (а, b), с).

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

Пример 9:

#include <iostream.h>

//Определение вспомогательной функции

int МАХ (int x, int y) //1

{ if  (x>y) return х;//2

else return y;

}

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

void main ( )

{ int а, b, с, d;//3

cout<<"Введите а, b, с";

cin>>а>>b>>с;

d=МАХ(МАХ(а,b),с) ;//4

cout<<"\nmах (а, b, с) ="<<d;

}

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

тип имя_функции (спецификация_параметров)

{тело функции}

Тип функции - это тип возвращаемого функцией результата. Если функция не возвращает никакого результата, то для нее указывается тип void.

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

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

Тело функции - это либо составной оператор, либо блок.

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

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

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

Оператором возврата из функции в точку ее вызова является оператор return. Он может использоваться в функциях в двух формах:

return;  или  return выражение;

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

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

Формат обращения к функции (вызова функции) традиционный:

имя_функции(список_фактических_праметров)

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

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

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

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

8.2 ПРОТОТИП ФУНКЦИИ

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

Пример10:

#include <iostream.h>

 //Прототип функции МАХ

 int МАХ(int, int);

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

void main ()

{ int а, b, с, d;

cout (<<" Введите а,b,с:";

cin>>а>>b>>с;

d=МАХ(МАХ(а,b),c) ;

cout<<"\nmах (а, b, с) ="<<d.;

}

//Определение функции МАХ

int МАХ (int x, int y)

          { if (х>у) return х;

else return y;

}

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

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

int МАХ(int х, int y) ;

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

8.3 ИСПОЛЬЗОВАНИЕ БИБЛИОТЕЧНЫХ ФУНКЦИЙ

Библиотечными называются вспомогательные функции, хранящиеся в отдельных файлах. Стандартные библиотеки входят в стандартный комплект системы программирования на Си/Си++. Кроме того, программист может создавать собственные библиотеки функций. Ранее уже говорилось о том, что для использования стандартных функций необходимо подключать к программе заголовочные файлы соответствующих библиотек. Делается это с помощью директивы претранслятора #include с указанием имени заголовочного файла. Например, #include  <stdio.h>. Все заголовочные файлы имеют расширение h (от английского header). Теперь должно быть понятно, что эти файлы содержат прототипы функций библиотеки. На стадии претрансляции происходит подстановка прототипов перед основной функцией, после чего компилятор в состоянии контролировать правильность обращения к функциям. Сами программы, реализующие функции, хранятся в форме объектного кода и подключаются к основной программе на стадии редактирования связей (при работе компоновщика).

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

Математическое решение этой задачи следующее. Обозначим координаты вершин четырехугольника так: (x1, y1), (x2, y2), (xЗ, y3), (x4, y4). Площадь четырехугольника можно вычислить как сумму площадей двух треугольников. В свою очередь, площадь каждого треугольника вычисляется по формуле Герона. Для применения формулы Герона нужно найти длины сторон. Длина стороны между первой и второй вершинами вычисляется по формуле:

   L12 =   (x1-x2)2 + (y1-y2)2

Рис – 2

Аналогично вычисляются длины других отрезков.

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

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

Пример 11:

//Площадь выпуклого четырехугольника

#include <iostream.h>

#include <math.h>

#include <conio.h>

typedef double D; //Переименование типа double

D Line (D, D, D, D); //Прототип функции Line

D Geron (D, D, D, D, D, D); // Прототип функции Geron

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

void main ()

{ D х1,у1,х2,у2,х3,у3,х4,у4,31234;

clrscr () ;

cout<<"х1="; cin>>х1; cout<<"у1="; cin>>у1;

cout<<"х2="; cin>>х2; cout<<"у2="; cin>>у2;

cout<<"х3="; cin>>х3; cout<<"у3="; cin>>у3;

cout<<"х4="; cin>>х4; cout<<"у4="; cin>>у4;

31234=Geron(х1,у1,х2,у2,хЗ,уЗ) +Geron(х1,у1,х3,у3,х4,у4) ;

cout<<"Площадь четырехугольника="<<S1234 ;

}

//Определение функции Line

D Line (D а, D b, D с,D d)

{return sqrt ( (а-с) * (а-с) + (b-d) * (b-d) ) ; }

//Определение функции Geron

D Geron (D а1, D а2, D b1, D b2, D с1, D с2)

{D р,аb,bс,са;

аb=Line (а1,а2,b1,b2); bс=b1пе(b1,b2,с1,с2) ;

ca=Line (с1,с2,а1,а2) ;

   р= (аb+bс+са) /2;

return sqrt (р*(р—аb)*(р—bс)*(р—са)) ;

}

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

выводом на экран в символьном режиме. Она является аналогом модуля CRT в Турбо Паскале. В программе из этой библиотеки используется функция clrscr () - очистка экрана.

Еще одним новым элементом в приведенной программе является строка     typedef double D;

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

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

8.4 РЕКУРСИВНЫЕ ОПРЕДЕЛЕНИЯ ФУНКЦИЙ

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

long Factor (int n)

{ if (n<0) return 0;

if (n==0) return 1;

return n*Factor (n-1);

}

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

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

double х ;

int func1 ()

{ int y;... }

void main ()

{ float: у;...}

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

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

8.5 КЛАССЫ ПАМЯТИ

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

• автоматическая (ключевое слово auto);

• внешняя (extern);

• статическая (static);

• регистровая (register).

Под глобальные переменные выделяется место во внешней памяти (не нужно думать, что речь идет о магнитной памяти; это оперативная память класса extern). Глобальную переменную можно объявить либо вне программных блоков, либо внутри блока с ключевым словом extern. Обычно это делается в тех случаях, когда программный модуль хранится в отдельном файле и, следовательно, отдельно компилируется.

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

Пример 12:

Файл 1:     

int var

void main ()

{ var=5;

func ();

cout<<var;

}

Файл 2:

void func ()

{ extern int var;

var=10*var;

}

                        

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

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

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

f ()

{ static int schet=10; ...}

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

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


9 МАССИВЫ

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

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

тип элементов имя [константное_выражение]

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

int А[10] ;

объявлен массив с именем А, содержащий 10 элементов целого типа. Элементы массива обозначаются индексированными именами. Нижнее значение индекса равно 0:

А[0], А[1], А[2], А[3], А[4], А[5], А[6], А[7], А[8], А[9]

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

Размер массива может явно не указываться, если при его объявлении производится инициализация значений элементов. Например:    int р[ ]={2, 4, 6, 10, 1};

В этом случае создается массив из пяти элементов со следующими значениями:

р[0]-2, р[1]=4, р[2]=6, р[3]=10, р[4]=1

В результате следующего объявления массива

int М[6]={5, 3, 2};

будет создан массив из шести элементов. Первые три элемента получат инициализированные значения. Значения остальных будут

либо неопределенными, либо равны нулю, если массив внешний или статический.

Рассмотрим несколько примеров программ обработки одномерных массивов.

Пример13  Ввод с клавиатуры и вывод на экран одномерного массива.

//Ввод и вывод массива

#include <iostream.h>

#include <conio.h>

void main ()

{ int i, А[5] ;

clrscr () ;

for (i=0; i<5; i++)

{ cout<<"А["<<1<<"]="; cin>>А[1];}

for(i=0; i<5; i++)

            cout<<"А [ "<<i<<" ] ="<<А [ i ] <<"  " ;

}

9.1 МНОГОМЕРНЫЕ МАССИВЫ

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

float R[5][10] ;

объявляет массив из пяти элементов, каждый из которых есть массив из десяти вещественных чисел. Отдельные величины этого массива обозначаются именами с двумя индексами: R [ 0 ] [ 0 ], R[0][1], ..., R [ 4 ] [ 9 ]. Объединять индексы в одну пару скобок нельзя, т. е. запись к [ 2, 3 ] ошибочна. Пример описания трехмерного массива:

double Х[3] [7] [20];

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

При описании многомерных массивов их также можно инициализировать. Делать это удобно так:

int М[3][3]={ 11,12,13,

                   21,22,23,

                   31,32,33 };

Рассмотрим примеры программ обработки матриц — числовых двумерных массивов.

Пример14: Вычисление и вывод на экран таблицы умножения в форме матрицы Пифагора.

// Матрица Пифагора

#include<stdio.h>

#include <conio.h>

void main ()

{ int i,j, А[10][10];

   clrscr () ;

for (i=1; i<=9; i++)

  { for (j=1; j<=9; j++)

  { А[i] [j]=i*j;

  printf ("%5d",А[1] [j]);

  }

  printf ("\n") ;

    }

 }

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

Пример15: Заполнение матрицы случайными числами в диапазоне от 0 до 99 и поиск в ней максимального значения.

#include <iostream.h>

# include <iomanip.h>

# include <conio.h>

# include <stdlib.h>

#define n 5

void main ( )

{ int i, j , ImaxА, JmaxА, А[n] [n];

clrscr ( ) ;

randomize ( ) ; //Установка датчика случайных чисел

for(i=0; i<n; i++)

{ for (j=0; j<п; j++)

{ А[i] [j]=rand( )%100;

        cout<<setw (6) <<А [ i ] [ j ] ;

       }

       cout<<endl;

}

ImaxА=JmaxА=0 ;

for(i=0; i<n; i++)

{ for(j=0; j<n; j++)

if (А[1][ j ]>А[ImaxА][JmaxА])

{ ImaxА=i; JmaxА=j; }

}

cout<<"Максимальное_значение:["<<ImaxА<<"]["<<Jmax<<"]=" <<А[ImaxА] [Jmax] ;

}

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

46

23

57

35

18

8

48

68

4

70

56

98

16

71

40

70

84

66

67

11

20

44

37

57

38

Максимальное значение: А[2][1]=98

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

Результатом этой функции является целое случайное число из диапазона от 0 до RAND_MAX. Значение константы RAND_MAX определено в заголовочном файле stdlib.h и обычно равно 32767 — максимально допустимому целому числу. Для получения случайных чисел в диапазоне от 0 до N— 1 достаточно вычислить остаток от целого деления rand() на N. Функция с прототипом void randomize(void) выполняет первоначальную настройку датчика случайных чисел так, чтобы последовательность чисел не повторялась при повторном выполнении программы.

Другим новым элементом в данной программе является использование манипуляторов для управления потоковым выводом с помощью стандартного объекта cout. Манипуляторы объявляются в заголовочном файле iomanip.h. Манипулятор setw(n) влияет на формат следующего элемента выходного потока. Он указывает на то, что значение будет выводиться в n позиций на экране (в программе n = 6). Другой использованный манипулятор — endl — обозначает конец строки и переводит экранный курсор на начало новой строки. Его действие аналогично действию управляющего символа \n.


10 УКАЗАТЕЛИ

Смысл этого понятия в Си/Си++ остается тем же: указатель — это адрес поля памяти, занимаемого программным объектом.

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

int а=5 ;

char с='G' ;

float r =1.2Е8;

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

Память

FFС0

FFС1

FFС2

FFСЗ

FFС4

FFС5

FFС6

Переменные

         а

  с

                     r

Значения

         5

 'G'

                1.2*108

Операция & — адрес. Применение этой операции к имени переменной дает в результате ее адрес в памяти. Для переменных из данного выше примера: &а равно FFСО, &с - FFС2, &rFFC3.

Описание указателей. Для хранения адресов используются переменные типа «указатель». Формат описания таких переменных следующий:

тип *имя переменной

Примеры описания указателей:

int *pti;   char *ptc;  float *ptf;

После такого описания переменная pti может принимать значение указателя на величину целого типа; переменная ptc предназначена для хранения указателя на величину типа char; переменная ptf — на величину типа float.

Указателям могут присваиваться значения адресов объектов только того типа, с которым они описаны. В нашем примере допустимы операторы:  pti=&а; ptc=&с; ptf=&r;

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

pti – FFC0,  ptc - FFС2,  ptf- FFC3.

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

int а=5;  int *pti=&а;

char с='G'; char *ptc=&с;

float r =1.2Е8;   float *ptf=&r;

В заголовочном файле stdio.h определена константа — нулевой указатель с именем NULL. Ее значение можно присваивать указателю. Например:

ptf=NULL;

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

Использованный в описаниях указателей символ * (звездочка) в данном контексте является знаком операции разадресации. С ее помощью можно сослаться через указатель на соответствующую переменную.

После приведенных выше описаний в записи выражений этой программы взаимозаменяемыми становятся а и *pti, с и *ptc, r и *ptf 

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

Продемонстрируем это правило на определенных выше указателях. Выполнение операторов

pti=рti+1;  или  pti++

изменит значение указателя pti на 2, в результате чего он примет значение FFС2. В результате выполнения оператора pti - - значение

указателя уменьшится на 2 и станет равным FFBE.

Аналогично для указателей других типов:

ptc++; увеличит значение указателя на 1;

ptf ++; увеличит значение указателя на 4.

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

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

void swap(int *а,int*b)

{ int с;

с=*а; *а=*b; *b=с;

}

Если в основной программе имеется следующий фрагмент:

int х=1,у=2;

swap(&х,&у) ;

printf("х=%d у=%d",х,у);

то на экран будет выведено: х=2   у=1

т. е. переменные х и у поменялись значениями.

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

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

10.1 УКАЗАТЕЛИ И МАССИВЫ

Имя массива трактуется как указатель-константа на массив.

Пусть, например, в программе объявлен массив:

int Х[10] ;

В таком случае х является указателем на нулевой элемент массива в памяти компьютера, В связи с этим истинным является отношение

Х==&Х[0]

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

имя [индекс]   тождественно * (имя + индекс)

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

Х[5], или *(Х+5), или *(5+Х).

Напоминаем, что для указателей работают свои правила сложения. Поскольку х — указатель на величину целого типа, то х+5 увеличивает значение адреса на 10.

В языке Си символ [ играет роль знака операции сложения адреса массива с индексом элемента массива.

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

элемента массива всегда нуль. Его адрес должен совпадать с адресом массива:

Х[0]==*(Х+0)

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

int Х[10], У[10] ;

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

Теперь рассмотрим двумерные массивы. Пусть в программе присутствует описание:

int Р[5][10];

Это матрица из пяти строк и десяти чисел в каждой строке. Двумерный массив расположен в памяти в последовательности по строкам. По-прежнему Р является указателем-константой на массив, т. е. на элемент Р [ 0 ] [ 0 ] . Индексированное имя Р [ i ] обозначает i-ю строку. Ему тождественно следующее обозначение в форме разадресованного указателя:

*(Р+i*10)

Обращение к элементу массива Р [ 2 ] [ 4 ] можно заменить на  *(Р+2*10+4) . В общем случае эквивалентны обозначения:

Р[1] [ j ]   И   *(Р+i*10+j)

11 ОБРАБОТКА СИМВОЛЬНЫХ СТРОК

В языках Си/Си++ нет специально определенного строкового типа данных, как в Турбо Паскале. Символьные строки организуются как массивы символов, последним из которых является символ \0, внутренний код которого равен нулю. Отсюда следует одно важное преимущество перед строками в Турбо Паскале, где размер строки не может превышать 255 (длина указывается в первом байте), — на длину символьного массива в Си нет ограничения.

Строка описывается как символьный массив. Например:

char STR[20] ;

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

сhаr 3[10] "строка" ;

сhаr 3[ ]="строка" ;

сhаr 3[10]={'с','т','р','о','к','а','\0'};

По результату первого описания под строку S будет выделено 10 байт памяти, из них первые 7 получат значения при инициализации (седьмой — нулевой символ). Второе описание сформирует строку из семи символов. Третье описание по результату равнозначно первому. Конечно, можно определить символьный массив и так:

сhаr S{.10]={'с' ,'т' ,'р' ,'о' , 'к' ,'а'];

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

Отдельные символы строки идентифицируются индексированными именами. Например, в описанной выше строке S [ 0 ] = ' с ', S[5]='а'.

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

Пример 16:

//Замена символов на звездочки  

#include <stdio.h>

#include <conio.h>

void main ()

{ char S[ ]="fh5j";

int i=0;

clrscr() ;

puts (S) ;

      while(S[i])

{S[i++]='*'; puts(S) ;}

printf ("\n, Длина строки=",i);

}

В результате выполнения программы на экране получим:

fh5j

*h5j

**5j

* * * j

****

Длина строки=4

В этой программе цикл повторяет свое выполнение, пока S [1] не получит значение нулевого символа.

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

Среди стандартных библиотек Си/Си++ существует библиотека функций для обработки строк. Ее заголовочный файл — string.h. В следующем примере используется функция определения длины строки из этой библиотеки. Имя функции — strlen (). В качестве аргумента указывается имя строки (приложение Б  ).

11.1 СТРОКА КАК ПАРАМЕТР ФУНКЦИИ

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

Пример17 Запишем определение функции вычисления длины строки (аналог стандартной функции strlen ()).

int lenght(char *S)

{ int k;

for(k=0; *s++!='\0'; k++) ;

return k;

}

Здесь функция использует явный механизм работы с указателем. Изменение значения указателя з допустимо благодаря тому, что он является переменной. Еще раз напомним, что для числовых массивов этого делать нельзя! Если соблюдать данное ограничение и для строк, то условное выражение в операторе for следовало бы писать так: * (s+k) !=' \0 ' или s [k]!='\0'.

12 СТРУКТУРЫ И ОБЪЕДИНЕНИЯ

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

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

 

Рис – 3

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

Формат описания структурного типа следующий:

struct имя_типа

{определения_элементов} ;

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

struct student {char fam[30];

  int kurs;

  char grup [ 3];

  float stip;

 };

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

struct student stud1, stud2;

Правила Си++ разрешают в этом случае служебное слово struct опускать и писать

Здесь stud1 и stud2 — переменные структурного типа. Допускаются и другие варианты описания структурных переменных. Можно вообще не задавать имя типа, а описывать сразу

Переменные:

struct {char fam[30] ;

int kurs;

           сhar grup[ 3 ] ;

float stip;

} stud1, stud2, *pst;

В этом примере кроме двух переменных структурного типа объявлен указатель pst на такую структуру. В данном описании можно было сохранить имя структурного типа student.

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

имя структуры.имя элемента

Снова все похоже на Паскаль. Примеры уточненных имен для описанных выше переменных:

stud1. fam;   stud1.stip

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

student stud1={ "Кротов", 3, "Ф32", 350};

Пусть в программе определен указатель на структуру

student *pst, stud1;

Тогда после выполнения оператора присваивания

рst=&stud1;

к каждому элементу структурной переменной stud1 можно обращаться тремя способами. Например, для поля fam

stud1.fam  или  (*pst).fam  или  pst ->fam

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

рst->FIO,  рst->grup,  pst->stip.

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

Допускается использование массивов структур. Например, сведения о 100 студентах могут храниться в массиве, описанном следующим образом:

student  stud [100] ;

Тогда сведения об отдельных студентах будут обозначаться, например, так: stud[1].fam, stud[5].kurs и т.п. Если нужно взять первую букву фамилии 25-го студента, то следует писать:

stud[25] .fam[0].

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

struct имя_структуры

            { тип имя_поля_1: длина_в_битах;

              тип имя_поля_2: длина_в_битах;

              тип имя поля N: длина_в_битах;

            };

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

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

union имя_типа

{определения_элементов} ;

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

Пусть в программе описана структура:

struct s

{ int i;

char ch ;

long int L;

};

Расположение ее элементов в памяти будет следующим:

байт

байт

байт

байт

байт

байт

байт

       i

 сh

                L

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

А теперь рассмотрим объединение со следующим описанием:

union s

{ int i;

char ch;

long int L ;

};

Величина с таким типом в памяти будет расположена следующим образом:

байт

байт

байт

байт

ch

       i

                L

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

Изменение значения любого поля объединения меняет значения других полей.

13 ПОТОКОВЫЙ ВВОД-ВЫВОД В СТАНДАРТЕ СИ ++

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

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

Вспомним, что в Паскале мы использовали представления о внутреннем и внешнем файле. Внутренний файл — это переменная

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

ошибке.

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

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

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

Байт0

Байт1  

Байт2

EOF

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

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

stdin — поток стандартного ввода (обычно связан с клавиатурой);

stdout — поток стандартного вывода (обычно связан с дисплеем);

stderr — вывод сообщений об ошибках (связан с дисплеем).

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

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

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

FILE *имя_указателя;

Например:

FILE *fр;

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

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

имя_указателя=:fореn (имя_файла, режим_открытия) ;

Параметры функции fореn () являются строками, которые могут быть как константами, так и указателями на символьные массивы. Например: fр=fореn("test.dat","r") ;

Здесь test.dat — это имя физического файла в текущем каталоге диска, с которым теперь будет связан поток с указателем fр. Параметр режима r означает, что файл открыт для чтения. Что касается терминологии, то допустимо употреблять как выражение «открытие потока», так и выражение «открытие файла».

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

Параметр            Режим

r    открыть для чтения

w   создать для записи

а   открыть для добавления

r+ открыть для чтения и записи

w+     создать для чтения и записи

а+       открыть для добавления или создать для чтения

          и записи

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

Поток может быть открыт либо для текстового, либо для двоичного (бинарного) режима обмена.

Смысл понятия остается прежним: это последовательность символов, которая делится на строки специальными кодами — возврат каретки (код 13) и перевод строки (код 10). Если файл открыт в текстовом режиме, то при чтении из такого файла комбинация символов «возврат каретки — перевод строки» преобразуется в один символ \n — переход к новой строке. При записи в файл осуществляется обратное преобразование.

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

Указанные выше параметры режимов открывают текстовые файлы. Если требуется указать на двоичный файл, то к параметру добавляется буква b. Например: rb, или wb, или r+b. В некоторых компиляторах текстовый режим обмена обозначается буквой t, т.е. записывается а+t: или rt.

Если при открытии потока по какой-либо причине возникла ошибка, то функция fореn () возвращает значение константы NULL. Эта константа также определена в файле stdio.h. Ошибка может возникнуть из-за отсутствия открываемого файла на диске, нехватки места в динамической памяти и т. п. Поэтому желательно контролировать правильность прохождения процедуры открытия файла. Рекомендуется следующий способ открытия:

FILE *fр;

if (fр=fореn("test.dat", "r")==NULL 

{puts("Не могу открыть файл\n");

return;

}

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

Закрытие потока (файла) осуществляет функция fclose(), прототип которой имеет вид:

Int fclose(FILE *fptr);

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

Запись символов в поток производится функцией putc() с прототипом

int put(int ch, FILE *fptr) ;

Если операция прошла успешно, то возвращается записанный символ. В случае ошибки возвращается константа EOF.

Считывание символа из потока, открытого для чтения, производится функцией gets() с прототипом

int gets(FILE *fptr);

Функция возвращает значение считываемого из файла символа. Если достигнут конец файла, то возвращается значение EOF. Заметим, что это происходит лишь в результате чтения кода EOF. Исторически сложилось так, что gets () возвращает значение типа int. То же можно сказать и про аргумент сh в описании функции puts (). Используется же в обоих случаях только младший байт. Поэтому обмен при обращении может происходить и с переменными типа char.

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

In putw(int, FILE *fptr) ;

Если операция прошла успешно, то возвращается записанное число. В случае ошибки возвращается константа EOF.

Считывание целого числа из потока, открытого для чтения, производится функцией getw() с прототипом

int getw(FILE *fptr) ;

Функция возвращает значение считываемого из файла числа. Если прочитан конец файла, то возвращается значение EOF.

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

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

Функция записи блока данных имеет прототип

Int fread (void *buf, int bytes, int n, FILE*fptr) ;

Здесь

buf — указатель на адрес данных, записываемых в файл;

bytes — длина в байтах одной единицы записи (блока данных);

n — число блоков, передаваемых в файл;

fptr — указатель на поток.

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

Функция чтения блока данных из файла имеет прототип

Int fwrite (void*buf, int bytes, int n, FILE*fptr) ;

Форматный обмен с файлами. С помощью функции форматного вывода можно формировать на диске текстовый файл с результатами вычислений, представленными в символьном виде. В дальнейшем этот файл может быть просмотрен на экране, распечатан на принтере, отредактирован с помощью текстового редактора. Использовавшаяся нами ранее функция printf() для организации вывода на экран является частным вариантом функции fprintf().Не будет ошибкой, если в программе вместо printf () написать fprintf (stdin,...) .

14 ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В СИ++

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

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

Тип «класс» устанавливается для объектов. Принято говорить:

однотипные объекты принадлежат одному классу.

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

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

private   (частный);

public    (общедоступный);

protected (защищенный).

Режим доступа private обозначает, что соответствующий элемент может использоваться только функциями данного класса. Этот режим доступа устанавливается по умолчанию. Элементы с режимом доступа public доступны в других частях программы. О режиме protected будет сказано немного позже. Чаще всего режим доступа к данным (переменным) бывает private, а к функциям — public. Это отражено в приведенном выше формате объявления класса.

Наследование — второе фундаментальное понятие ООП. Механизм наследования позволяет формировать иерархии классов. Класс-наследник получает свойства класса-предка. Как и в Турбо Паскале, в классе-наследнике могут быть объявлены новые дополнительные элементы. Элементы-данные должны иметь имена, отличные от имен предка. Элементы-функции могут быть новыми относительно предка, но могут и повторять имена функций своих предков. Как и в Турбо Паскале, здесь действует принцип «снизу вверх» при обращении к функции: функция потомка перекрывает одноименную функцию своего предка.

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

Конструкторы и деструкторы. Смысл этих понятий аналогичен их  смыслу  в Турбо Паскале. Основное назначение конструктора  —

инициализация элементов-данных объекта и выделение динамической памяти под данные. Конструктор срабатывает при выполнении оператора определения типа «класс для объекта». Деструктор освобождает выделенную конструктором память при удалении объекта.

Области памяти, занятые данными базовых типов, таких, как int float, double и т.п., выделяются и освобождаются системой автоматически и не нуждаются в помощи конструктора и деструктора. Именно поэтому в программах, рассмотренных в примерах 1 и 2, конструкторы и деструкторы не объявлялись (система все равно создает их автоматически).

Конструктор и деструктор объявляются как члены-функции класса. Имя конструктора совпадает с именем класса. Имя деструктора начинается с символа ~ (тильда), за которым следует имя класса.

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

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

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


15 ФОРМАТИРОВАННЫЙ ВВОД-ВЫВОД В СИ++

Для организации ввода-вывода в Си++ можно использовать средства языка Си (conio.h). Однако в Си++ существует стандартная библиотека классов, ориентированная на организацию потокового ввода-вывода. Классы ввода-вывода образуют иерархию по принципу наследования. Базовым в этой иерархии является класс ios (исключение составляют лишь классы буферизированных потоков). В классе ios объединены базовые данные и методы для ввода-вывода. Прямыми потомками класса ios являются классы istream и ostream. Класс istream— это класс входных потоков; ostream — класс выходных потоков. Потомком этих двух классов является iostream — класс двунаправленных потоков ввода-вывода. С этим классом мы уже много раз имели дело, подключая его к программам с помощью головного файла iostream.h.

Объект cout принадлежит к классу ostream и представляет собой поток вывода, связанный с дисплеем. Объект cin принадлежит классу istream и является потоком ввода, связанным с клавиатурой. Оба эти объекта наследуются классом iostream.

Знак « обозначает перегруженную операцию вставки символов в поток вывода cout-, а » — знак операции извлечения из потока ввода cin.

Для организации форматированного потокового ввода-вывода в Си++ существуют два средства:

• применение функций-членов класса ios для управления флагами форматирования;

• применение функций-манипуляторов.

Управление флагами форматирования. Флаги форматирования — двоичные коды, управляющие форматом выводимых значений. В

заголовочном файле iostream.h определено следующее перечисление, задающее флаги форматирования:

enum{

scipws     = 0х0001 отбрасывание пробелов

left           = 0х0002 выравнивание по левому краю поля

right        = 0х0004 выравнивание по правому краю поля

internal   = 0х0008 заполнение пустых позиций

dec        = 0х0010 выдача в десятичном виде

oct        = 0х0020 выдача в восьмеричном виде

hex        = 0х0040 выдача в шестнадцатеричном виде

showbase = 0х0080 выдача основания сист. счисления

showpoint = 0х0100 выдача позиции точки

uppercase = 0х0200 выдача в формате хх.хххх Ехх

showpos = 0х0400 выдача знака у положит. числа

scientific = 0х0800 выдача в форме с плавающ. точкой

fixed      = 0х1000 выдача в форме с фиксир. точкой

unibuf     = 0х2000 улучшенная выдача

stdio      = 0х4000 освобождение потока

};

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

Изменить состояние флагов формата можно с помощью функции-члена класса ios, имеющей прототип

long setf (long flags)

Например, чтобы установить флаг showbase в активный режим (включить) применительно к стандартному потоку вывода cout, используется оператор  

cout.setf(ios::showbase) ;

Для установки флагов можно использовать побитовые операции. Например: cout. setf (ios: : left | ios : :hex) ;

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

Для выключения используется функция

unsetf(long flags) ;

Например, для отмены вывода основания системы счисления используется оператор:

cout .unsetf (ios : : showbase) ;

Вот еще некоторые функции-члены класса ios:

long flags (void) - возвращает текущее состояние флагов;

int width(int len) — возвращает текущую ширину поля вывода и устанавливает значение ширины, равное len;

char fill (char ch) —возвращает текущий символ заполнения и устанавливает новый символ заполнения ch;

int precision(int num) —возвращает текущее число деся-тичных знаков после точки и устанавливает значение этого параметра равным num.

Пример17:Следующая программа иллюстрирует применение рассмотренного способа управления форматным выводом.

#include <iostream.h>

void main (void)

{

long fl;

fl=cout. flags () ;

cout« "Исходное состояние флагов: "«fl«"\n";

//Выведется целое число — код флагов, //установленных по умолчанию

 cout.set(ios::shoupos);

cout.set: (ios::scientific);

cout«123«" "«1. 234567 8«"\n";

//Выведется:

//+123 +1.23456е+00

cout.set(ios::hex::showbase);

cout.unsetf (ios:: showpos) ;

cout.width (15) ;

cout .precision (10) ;

cout«123«" "«123.456«" "1. 2345678«"\n";

//Выведется:

//0х7В 1.23456е+02 1.2345б78е+00

cout<<"Новое состояние флагов : "<<cout . flags ()

<<"\n";

//Выведется:

//Новое состояние флагов:0х28С1

   cout . flags (fl) ; //Возврат к исходному состоянию

   cout:<<"После восстановления исходных флагов:\n";

   cout:<<123<<" "<<123 . 456<<1. 2345678<<"\n" ;

//Выведется:

//После восстановления исходных флагов:

//123 123.456 1.234567

}

15.1 ИСПОЛЬЗОВАНИЕ МАНИПУЛЯТОРОВ

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

dec

endl

ends

flush

hex

resetiosflags(lohg f)

setbase(int base)

setfill(char ch)

setiosflags(lohg f)

setprecision(int p)

setw(int w)

ws

Десятичный формат

Вывод "\n" и освобождение буфера

Вывод NULL

Освободить поток

Шестнадцатеричный формат числа

Отключить флаги, определенные f

Установить основание системы

счисления

Установить символ заполнения

Включить флаги, указанные f

Установить р цифр в дробной части

Установить ширину поля выдачи

Режим пропуска символов пробела


Пример18: В следующей программе вычисляется и выводится на экран таблица значений функций sinx и cosх на n шагах в интервале от 0 до р. Для форматирования таблицы результатов используются манипуляторы.

#include.h <iostream.h>

#include <math.h>

#include <iomanip.h>

void main ()

{ double а,b,х;

int n=20;

а=0; b=4*atan(1. ) ;

cout<<" х            sin(х)            cos(x) "<<endl;

cout:<<"-   -   -   -    -    -    -    -    -    -    -    -    -    -    -    -

-"<<endl;

for(х=а;х<=b;х=х+(b-а)/n)

cout<<setprecision (4) <<setw (10) <<х<<"      "

   <<setprecision (4) <<setw (10) <<sin(х)<<"      "

   <<setprecision (4) <<setw (10) <<cos(х)<<"      "

}

Начальная часть таблицы, по этой программе, имеет вид:      

    х                             sin(х)                   cos (х)

0      0     1  

0.1571   0.1564  0.9877

0.3142   0.309   0.9511

0.4712   0.454   0.891

0.6283   0.5878  0.809

0.7854   0.7071  0.7071

  

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


ЛИТЕРАТУРА

1.Архангельский С.В. С ++ Builder 6.0 - СПб: БХВ - Петербург, 2007.

2.Ахо А., Хопкрофт Д., Ульман Д. Структуры данных и алгоритмы. - М.: Вильямс, 2008.

3.Голицына О. Л. Попов И.И. Основы алгоритмизации и программирования: Учебное пособие. - М.: ФОРУМ: ИНФРА-М, 2009.

4.Седжвик Р. Фундаментальные алгоритмы на C++. - Киев: Диа- Софт,2009.

5.Семакин И.Г., Шестаков А.П. Основы программирования: Учебник. - М.: Мастерство, 2009.

6.Тамарина Р.М. Программирование на языке С/С ++: Практикум и методические указания к практическим и самостоятельным работам (электронный ресурс) 2011

7.Тамарина Р.М. Программирование на языке высокого уровня.  Методические указания и контрольные задания для студентов заочной формы обучения, - Вологда: ВТЖТ, 2006.

8.Тамарина P.M. Обьектно - ориентированное программирование.  Учебное пособие для студентов дневной и заочной формы обучения. - Вологда: ВТЖТ, 2010.

9.Тамарина P.M. Разработка баз данных в среде С++ Builder. Учебное пособие для студентов дневной и заочной формы обучения, - Вологда: ВТЖТ, 2009.

10.Ускова О. Ф. и др. Программирование алгоритмов обработки данных, Учебное пособие. - СПб.: БХВ - Петербург, 2008.

11.Шалыто А.А., Туккель Н.И. Реализация вычислительных алгоритмов на основе автоматного подхода //Телекоммуникации и информатизация образования. - 2001.


 

А также другие работы, которые могут Вас заинтересовать

24988. Опыты Резерфорда по рассеянию α-частиц. Ядерная модель атома 23.5 KB
  Ядерная модель атома План ответа 1. Ядерная модель атома. Рассеяние αчастиц Резерфорд объяснил тем что положительный заряд не распределен равномерно в шаре радиусом 1010 м как предполагали ранее а сосредоточен в центральной части атома атомном ядре. Так ведут себя частицы имеющие одинаковый заряд следовательно существует центральная положительно заряженная часть атома в которой сосредоточена значительная масса атома.
24989. Квантовые постулаты Бора. Испускание и поглощение света атомами. Спектральный анализ 24.5 KB
  Спектр излучения или поглощения это набор волн определенных частот которые излучает или поглощает атом данного вещества. Сплошные спектры излучают все вещества находящиеся в твердом или жидком состоянии. Линейчатые спектры излучают все вещества в атомарном состоянии. Как у каждого человека свои личные отпечатки пальцев так и у атома данного вещества свой характерный только ему спектр.
24990. Фотоэффект и его законы. Уравнение Эйнштейна для фотоэффекта и постоянная Планка. Применение фотоэффекта в технике 28.5 KB
  Уравнение Эйнштейна для фотоэффекта и постоянная Планка. Применение фотоэффекта в технике Плав ответа 1. Законы фотоэффекта. Применение фотоэффекта.
24991. Состав ядра атома. Изотопы. Энергия связи ядра атома. Цепная ядерная реакция, условия ее осуществления. Термоядерные реакции 26 KB
  Энергия связи ядра атома. Состав ядра атома. Энергия связи атомного ядра.
24992. Механическое движение Относительность движения, Система отсчета, Материальная точка, Траектория. Путь и перемещение. Мгновенная скорость. Ускорение. Равномерное и равноускоренное движение 33 KB
  Мгновенная скорость. Скорость векторная физическая величина характеризующая быстроту перемещения тела численно равная отношению перемещения за малый промежуток времени к величине этого промежутка. Промежуток времени считается достаточно малым если скорость в течении этого промежутка не менялась. Измеряют скорость спидометром.
24993. Взаимодействие тел. Сила. Второй закон Ньютона 39 KB
  Сила. Сила. В простейших случаях взаимодействия количественной характеристикой является сила. Сила причина ускорения тел по отношению к инерциальной системе отсчета или их деформации.
24994. Импульс тела. Закон сохранения импульса в природе и технике 137.5 KB
  Импульс тела. Простые наблюдения и опыты доказывают что покой и движение относительны скорость тела зависит от выбора системы отсчета; по второму закону Ньютона независимо от того находилось ли тело в покое или двигалось изменение скорости его движения может происходить только при действии силы т. в результате взаимодействия с другими телами.
24995. Закон всемирного тяготения. Сила тяжести. Вес тела. Невесомость 52.5 KB
  Вес тела. Вес тела перегрузки. Исаак Ньютон выдвинул предположение что между любыми телами в природе существуют силы взаимного притяжения. гравитационная постоянная равна силе с которой притягиваются два тела по 1 кг на расстоянии 1 м.
24996. Превращение энергии при механических колебаниях. Свободные и вынужденные колебания. Резонанс 38.5 KB
  Свободные и вынужденные колебания. Свободные колебания. Вынужденные колебания.