4855

Технологии и методы программирования. Конспект лекций

Конспект

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

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

Русский

2012-11-28

297 KB

105 чел.

Введение

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

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

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

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

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

Не отвлекаясь на характеристики языков, понятия о которых раскрываются в следующем изложении, можно только отметить, что C, C++, Java, C# самые распространенные и популярные языки в мире.


Понятия и основные тезисы к разделу

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

Исполнитель – человек или вычислительное устройство (вычислитель) действующие по заданной формальной инструкции (алгоритму)

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

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

Язык программирования – язык, на котором выполняется запись программы

Методология - набор целенаправленных и взаимообусловленных принципов, методов, средств и способов

Программирование – методология создания программы


1.  Понятия технологии программирования

1.1. Термин технологии  программирования 

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

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

Основными признаками технологического процесса являются:

  •  формализация;
  •  предсказуемость;
  •  повторяемость.

Среди множества критериев оценки качества технологического процесса упомянем:

  •  доступность;
  •  эффективность;
  •  надежность;
  •  сопровождение.

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

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

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

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

Ответы на вопрос: «Почему процесс программирования поэтапный и почему он имеет итеративный характер?» заключается в следующем:

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

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

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

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

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

1.2. Понятия традиционной архитектуры

Машина Тьюринга

Машина Тьюринга (МТ) — абстрактный исполнитель (абстрактная вычислительная машина). Была предложена Аланом Тьюрингом в 1936 году для формализации понятия алгоритма.

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

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

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

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

Исполнители, для которых возможно можно имитировать Машину Тьюринга, называются полными по Тьюрингу. Первым компьютером, удовлетворяющим такому условию, считается машина Z3, созданная немецким инженером Конрадом Цузе в 1941 году

Машина фон Неймана

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

  1.  Принцип использования двоичной системы счисления для представления данных и команд.
  2.  Принцип программного управления. Программа состоит из фиксированного набора команд, которые выполняются процессором.
  3.  Принцип однородности памяти. Как программы (команды), так и данные хранятся в одной и той же памяти. Над командами можно выполнять такие же действия, как и над данными.
  4.  Принцип адресуемости памяти. Структурно основная память состоит из пронумерованных ячеек; процессору в произвольный момент времени доступна любая ячейка.
  5.  Принцип последовательного программного управления. Все команды располагаются в памяти и выполняются последовательно, одна после завершения другой.
  6.  Принцип условного перехода.

Гарвардская архитектура

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

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

CISC и RISC архитектура

Современные широко распространенные компьютеры (RISC или CISC процессоры Intel) в своей основе представляют гибридные модификации, сочетающие достоинства как Гарвардской, так и фон-Неймановской архитектур. Современные CISC-процессоры обладают раздельной кэш-памятью для инструкций и данных, что позволяет им за один такт получать одновременно как команду, так и данные для её выполнения, то есть процессорное ядро, формально, является гарвардским, но с программной точки зрения выглядит как фон-Неймановское, что упрощает написание программ. Обычно в этих процессорах одна шина используется и для передачи команд, и для передачи данных, что упрощает конструкцию системы.

Суперскалярные архитектуры

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

Общность традиционной архитектуры

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

 

1.3. Понятия традиционных языков программирования

Машино ориентированные языки

С точки зрения процессора (как исполнителя) команды представляют по особому организованные данные

B82301 - внести значение 0123h в AX;

052500 - прибавить значение 0025h к AX;

8BD8 - переслать содержимое AX в BX;

31C0 - очистка AX;

CD20 - конец программы. Передача управления операционной системе.

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

:0100 MOV AX,1 

:0102 MOV CX,0003

:0105 ADD AX,0001

:0108 LOOP 0105

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

Проблемно ориентированные языки

Язык SQL для баз данных ориентированный на работу с таблицами реляционных баз данных. В начале 80 годов появление этой методологии на порядок ускорило разработку приложений для БД.

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

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

Запись на языках программирования традиционно относящихся к языкам высокого уровня вывода приветствия Hello:

//C

#include <stdio.h>

int main(void)

{ printf(“Hello!\n”);

  return 0;

}

//C++

#include <iostream.h>

int main(void)

{ cout<<“Hello”<<endl;

  return 0;

}

//Java

public class hello

{ public static void main(String[] args)

  { System.out.println(“Hello”);

  }

}

//C#

using System

class hello

{ public static void main()

  { Console.Writeln(“Hello”);

  }

}

Общее в записи:

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

Различное в записи:

  •  число элементов в записях и их соподчиненность;
  •  символы и слова, используемые в записях.

Суть общности

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

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

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

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

Структурное программирование допускает всего три типа структур:

  •  последовательное выполнение;
  •  ветвление;
  •  итерация.

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

Высокий уровень языка подразумевает:

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

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

Суть различия

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

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

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

1.4. Понятия формализации

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

  •  таблицы решений;
  •  ГОСТ 19.701-90, нотация Гейна-Сарсона, сети Петри;
  •  UML

Таблица решений

Эта нотация подходит для формализации абстрактного исполнителя работающего по принципу конечного автомата

Состояние

исходное

Условие

Решение

Состояние

конечное

1

<&Выключатель выключен> .eq. true 

<Лампочка горит >:= false

1

1

<&Выключатель включен> .eq. true 

<Лампочка горит >:= true

1

Пример программы для машины Тьюринга инвертирующей машинный код от текущего положения головки вправо

Состояние

исходное

Значение в ячейке

Состояние

конечное

Направление

перемещения

исходное

конечное

1

-

-

1

S

1

0

1

1

R

1

1

0

1

R

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

Состояние

исходное

Значение в ячейке

Состояние

конечное

Направление

перемещения

исходное

конечное

1

_

_

1

S

1

1

_

2

R

2

_

_

3

R

2

1

1

2

R

3

_

1

4

L

3

1

1

3

R

4

_

_

5

L

4

1

1

4

L

5

_

1

1

R

5

1

1

5

L

Нотации императивного подхода

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

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

ГОСТ 19.701-90 (ISO 5807-85)  действует с 01.01.92 и определяет нотацию в стиле потока команд

Данные. 

Символ отображает данные, носитель данных не определен

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

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

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

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

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

Терминатор. Начало или конец схемы программы.

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

Блок-схема записи алгоритма нахождения факториала:

Нотация Гейна-Сарсона служит для изображения диаграмм потоков данных

Внешняя сущность

Система, подсистема или

процесс

Накопитель данных

Поток

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

Нотации событийного подхода

Событийно-управляемое программирование – подход при котором задаются реакции программы на различные события

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

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

Набор диаграмм UML представляет:

  1.  Диаграммы прецедентов (описывающих взаимодействие актеров с системрй);
  2.  Диаграммы классов (задающие атрибуты и операции);
  3.  Диаграммы объектов (как экземпляра класса)
  4.  Диаграммы кооперации (обмен сообщениями между объектами)
  5.  Диаграммы последовательности (временная последовательность обработки сообщения)
  6.  Диаграммы состояний (соединяемые парой условие/действие)
  7.   Диаграммы развертывания (привязка к техническим средствам)

1.5. Проблемы технологии

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

Пример1 Семантика высказываний и семантика команд

    Логика

находимся в положении 1 (включено)   - горит                                    

находимся в положении 2 (выключено) - не горит

 Абстракция

 p1 ( в положении 1), p2  ( в положении 2) - предикаты (true, false)

s-  переменная состояния со значением (0-не горит, 1-горит)

p1=>s=1

p2=>s=0

Реализация

int p1,p2,s;

scanf(“%d %d”,&p1,&p2);

// и как лучше?

 1)if(p1) s=1;

    if(p2) s=0;

2)if(p1) s=1;

    else s=0;

3)if(p1) s=1;

    else if(p2)s=0;

  else s=1;

Проблема

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

Понятие неопределенного значения (NULL значения)

Пример2 Последовательность выполнения

Логика

 поворот заданной точки плоскости на 45 

 Абстракция

x и y – координаты точки

x= 2x-2y  

 y= 2x+2y  

Реализация

 int x, y;

scanf(“%d %d”,&x,&y);

float s2= sqrt(2);

x= s2*x-s2*y;  

y= s2*x+s2*y;  

Проблема

При вычислении y используется измененный x

Проблема параллельных вычислений

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

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

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

Понятия и основные тезисы к разделу

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

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

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

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

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

Гарвардская архитектура – отличается от архитектуры фон Неймана неоднородностью памяти по отношению к хранению и доступу для команд и данных

CISC (англ. Complex Instruction Set Computer) — концепция вычисления с полным набором команд (100-200), которая характеризуется нефиксированным значением длины команды, небольшим числом регистров, каждый из которых выполняет строго определённую функцию.

RISC (англ. Reduced Instruction Set Computer) — концепция вычисления с сокращённым набором команд (50-100), которая характеризуется фиксированным значением длины команды, большое количество регистров общего назначения (32 и более), любые операции «изменить» выполняются только над содержимым регистров.

Компьютер общего назначения - способен эмулировать машину Тьюринга.

Операция – простейшая инструкция задающая работу исполнителя

Структурное программирование – программирование, допускающее для управления порядком выполнения операций только последовательное выполнение, ветвление и итерацию

Компиляция - специальные действия по преобразованию исходного вида программы в вид понятный исполнителю

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


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

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

Верификация программы - доказательство правильности кода программы

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

Событийно-управляемое программирование – подход при котором задаются реакции программы на различные события


2. Команды и данные

1.1. Взаимодействие команд и данных

Термины «команды» и «данные» уже употреблялись при рассмотрении понятия архитектуры исполнителя.

1.2. Особенности арифметики

Повышение точности

Выполняем итерационный процесс

       double l=10000000., t=0.0000001;

       for(int i=0; i<10000000;i++)

        l=l+t;

       // результат l=10000000.006

Шаг в 10 раз меньше

       double l=10000000., t=0.00000001;

       for(int i=0; i<100000000;i++)

        l=l+t;

       // результат l=10000000.931

Погрешность нарастает

Проблемы эксплуатации

float c;

scanf(”%f ”,&c);  //вводим цену в рублях и копейках 23.32

printf(” %d\n” ,c*100); //выводим в копейках 2331

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

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

Алфавит 

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

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

Таблица 1 

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

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

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

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

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

_

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

Таблица 2 

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

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

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

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

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

0 1 2 3 4 5 6 7 8 9

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

Таблица 2 

Символ

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

Символ

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

,

запятая

)

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

.

точка

(

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

;

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

}

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

:

двоеточие

{

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

?

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

<

меньше

'

апостроф

>

больше

!

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

[

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

|

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

]

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

/

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

#

номер

\

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

%

процент

~

тильда

&

амперсанд

*

звездочка

^

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

+

плюс

=

равно

-

мину

"

кавычки

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

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

Таблица 3 

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

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

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

\a

Звонок

007

\b

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

008

\t

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

009

\n

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

00A

\v

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

00B

\r

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

00C

\f

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

00D

\"

Кавычки

022

\'

Апостроф

027

\0

Ноль-символ

000

\\

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

05C

\ddd

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

 

\xddd

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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  auto      double     int   struct  break   else   long   switch

  register  tupedef    char  extern  return  void   case   float

  unsigned  default    for   signed  union   do     if     sizeof

  volatile  continue   enum  short   while

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

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

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

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

Комментарии

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

    /* комментарии к программе */

    /* начало алгоритма */

    или

    /* комментарии к программе

     начало алгоритма */

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

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

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

     или

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

Стандартные языковые конструкции

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

- условные операторы, к которым относятся оператор условия if и оператор выбора switch;

- операторы цикла (for,while,do while);

- операторы перехода (break, continue, return, goto);

- другие операторы (оператор "выражение", пустой оператор).

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

Все операторы языка СИ, кроме составных операторов, заканчиваются точкой с запятой ";"

Любое выражение, которое заканчивается точкой с запятой, является оператором.

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

Примеры:

++ i;

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

a=cos(b * 5);

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

a(x,y);

Этот оператор представляет выражение состоящее из вызова функции.

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

- в операторах do, for, while, if в строках, когда место оператора не требуется, но по синтаксису требуется хотя бы один оператор;

- при необходимости пометить фигурную скобку.

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

Пример:

    int main ( )

    {

         :

          { if (...) goto a;    /* переход на скобку */

              { ...

              }

     a:;  }

          return 0;

      }

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

      {  [oбъявление]

          :

          оператор; [оператор];

          :

      }

Заметим, что в конце составного оператора точка с запятой не ставится.

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

Пример:

       int main ()

       {

         int    q,b;

         double t,d;

           :

           if (...)

             {

               int    e,g;

               double f,q;

               :

              }

           :

           return (0);

       }

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

   Программа С++ почти всегда состоит из нескольких раздельно

транслируемых "модулей". Каждый "модуль" обычно называется исходным

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

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

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

другом исходном файле. Например:

            extern "C" double sqrt ( double );

            extern ostream cout;

   Самый распространенный способ обеспечить согласованность описаний

 внешних во всех исходных файлах - поместить такие описания в специальные

 файлы, называемые заголовочными. Заголовочные файлы можно включать во все

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

 функции sqrt хранится в заголовочном файле стандартных математических

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

 4, можно написать:

             #include <math.h>

             //...

             x = sqrt ( 4 );

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

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

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

 функция-подстановка, а инициализаторы указаны только для констант ($$4.3).

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

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

 программы.

     В команде включения заключенное в угловые скобки имя файла (в нашем

 примере - <math.h>) ссылается на файл, находящийся в стандартном каталоге

 включаемых файлов. Часто это - каталог /usr/include/CC. Файлы, находящиеся

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

 кавычки. Поэтому в следующих командах:

           #include "math1.h"

           #include "/usr/bs/math2.h"

     включаются файл math1.h из текущего каталога пользователя и файл

 math2.h из каталога /usr/bs.

Время жизни и область видимости идентификаторов, пространства имен

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

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

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

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

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

Если объект объявлен внутри блока, то он видим в этом блоке, и во всех внутренних блоках. Если объект объявлен на внешнем уровне, то он видим от точки его объявления до конца данного исходного файла.

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

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

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

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

 int x;            // глобальное x

 void f()

 {

     int x;        // локальное x скрывает глобальное x

     x = 1;        // присвоить локальному x

     {

         int x;    // скрывает первое локальное x

         x = 2;    // присвоить второму локальному x

     }

     x = 3;        // присвоить первому локальному x

 }

 

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

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

    int x;

    void f2()

    {

      int x = 1;      // скрывает глобальное x

      ::x = 2;        // присваивание глобальному x

    }

Спецификатор класса памяти в объявлении переменной может быть auto, register, static или extern. Если класс памяти не указан, то он определяется по умолчанию из контекста объявления.

Объекты классов auto и register имеют локальное время жизни. Спецификаторы static и extern определяют объекты с глобальным временем жизни.

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

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

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

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

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


Лекция 2

Базовые типы данных: объявления , область значения, константы. Определение типов. Массивы, структуры, перечисления, объединения. Инициализация. Указатели. Преобразования типов: преобразования по умолчанию, явные преобразования

Базовые типы данных: объявления , область значения

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

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

  [спецафикатор-класа-памяти]  спецификатор-типа

  описатель [=инициатор] [,описатель [= инициатор] ]...

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

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

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

Спецификатор класса памяти - определяется одним из четырех ключевых слов языка СИ: auto, extern, register, static, и указывает,каким образом будет распределяться память под объявляемую переменную, с одной стороны, а с другой, область видимости этой переменной, т.е., из каких частей программы можно к ней обратиться.

Ключевые слова для определения основных типов данных

    Целые типы :                 Плавающие типы:

      char                         float

      int                          double

      short                        long  double

      long

      signed

      unsigned

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

Примеры:

     const double A=2.128E-2;

     const B=286; (подразумевается const int B=286)

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

Таблица 1 

Тип

Размер памяти в байтах

Диапазон значений

char

1

от -128 до 127

int

4

от -2 147 483 648 до 2 147 483 647

short

2

от -32768 до 32767

long

4

от -2 147 483 648 до 2 147 483 647

unsigned shar

1

oт 0 до 255

unsigned int

4

от 0 до 4 294 967 295

unsigned short

2

от 0 до 65535

unsigned long

4

от 0 до 4 294 967 295

Отметим, что ключевые слова signed и unsigned необязательны. Они указывают,

Для переменных, представляющих число с плавающей точкой используются следующие модификаторы-типа : float, double, long double (в некоторых реализациях языка long double СИ отсутствует).

Величина с модификатором-типа float занимает 4 байта. Из них 1 байт отводится для знака, 8 бит для избыточной экспоненты и 23 бита для мантиссы. Отметим, что старший бит мантиссы всегда равен 1, поэтому он не заполняется, в связи с этим диапазон значений переменной с плавающей точкой приблизительно равен от 3.14E-38 до 3.14E+38.

Величина типа double занимает 8 бит в памяти. Ее формат аналогичен формату float. Биты памяти распределяются следующим образом: 1 бит для знака, 11 бит для экспоненты и 52 бита для мантиссы. С учетом опущенного старшего бита мантиссы диапазон значений равен от 1.7E-308 до 1.7E+308.

Примеры:

    float f, a, b;

    double x,y;

Определение типов

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

  Приведем пример:

     typedef int coord;

coord x;

 coord y;

Это удобный способ создавать синонимы для типов, что служит двум целям:

  •  сокращению записи; 
  •  одноместному изменению типа для всех использований синонима.

  Приведем пример:

     typedef double coord;

coord x;

 coord y;

Массивы, структуры, перечисления, объединения. Инициализация.

Массивы - это группа элементов одинакового типа (double, float, int и т.п.). Из объявления массива компилятор должен получить информацию о типе элементов массива и их количестве. Объявление массива имеет два формата:

спецификатор-типа описатель [константное - выражение];

спецификатор-типа описатель [ ];

Описатель - это идентификатор массива .

Спецификатор-типа задает тип элементов объявляемого массива. Элементами массива не могут быть функции и элементы типа void.

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

- при объявлении массив инициализируется,

- массив объявлен как формальный параметр функции,

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

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

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

Примеры:

    int a[2][3]; /* представлено в виде матрицы

                    a[0][0]  a[0][1] a[0][2]

                    a[1][0]  a[1][1] a[1][2]      */

    double b[10]; /* вектор из 10  элементов имеющих тип double */

    int w[3][3] = { { 2, 3, 4 },

                    { 3, 4, 8 },

                    { 1, 0, 9 } };

В последнем примере объявлен массив w[3][3]. Списки, выделенные в фигурные скобки, соответствуют строкам массива, в случае отсутствия скобок инициализация будет выполнена неправильно.

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

Примеры:

int s[2][3];

Если при обращении к некоторой функции написать s[0], то будет передаваться нулевая строка массива s.

int b[2][3][4];

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

Пример объявления символьного массива.

char str[] = "объявление символьного массива";

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

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

struct { список определений }

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

тип-данных описатель;

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

Пример:

           struct { double x,y; } s1, s2, sm[9];

           struct {  int   year;

                     char  moth, day; } date1, date2;

Переменные s1, s2 определяются как структуры, каждая из которых состоит из двух компонент х и у. Переменная sm определяется как массив из девяти структур. Каждая из двух переменных date1, date2 состоит из трех компонентов year, moth, day. >p>Существует и другой способ ассоциирования имени с типом структуры, он основан на использовании тега структуры. Тег структуры аналогичен тегу перечислимого типа. Тег структуры определяется следующим образом:

struct тег { список описаний; };

где тег является идентификатором.

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

    struct student { char name[25];

                     int  id, age;

                     char  prp;         };

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

struct тег список-идентификаторов;

Пример:

struct studeut st1,st2;

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

       struct node { int data;

                     struct node * next; }   st1_node;

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

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

         st1.name="Иванов";

         st2.id=st1.id;

         st1_node.data=st1.age;

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

Объявление перечисления начинается с ключевого слова enum и имеет два формата представления.

Формат 1. enum [имя-тега-перечисления] {список-перечисления} описатель[,описатель...];

Формат 2. enum имя-тега-перечисления описатель [,описатель..];

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

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

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

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

Список-перечисления содержит одну или несколько конструкций вида:

идентификатор [= константное выражение]

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

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

1. Переменная может содержать повторяющиеся значения.

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

3. Имена типов перечислений должны быть отличны от других имен типов перечислений, структур и смесей в этой же области видимости.

4. Значение может следовать за последним элементом списка перечисления.

Пример:

    enum week { SUB = 0,    /*    0   */

                VOS = 0,    /*    0   */

                POND,       /*    1   */

                VTOR,       /*    2   */

                SRED,       /*    3   */

                HETV,       /*    4   */

                PJAT        /*    5   */

                }   rab_ned ;

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

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

Пример:

enum week rab1;

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

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

    union {   описание элемента 1;

              ...

              описание элемента n; };

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

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

Объединение применяется для следующих целей:

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

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

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

Пример:

    union {   char  fio[30];

              char  adres[80];

              int   vozrast;

              int   telefon;   } inform;

    union {   int ax;

              char al[2];      }   ua;

При использовании объекта infor типа union можно обрабатывать только тот элемент который получил значение, т.е. после присвоения значения элементу inform.fio, не имеет смысла обращаться к другим элементам. Объединение ua позволяет получить отдельный доступ к младшему ua.al[0] и к старшему ua.al[1] байтам двухбайтного числа ua.ax .

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

    struct { unsigned идентификатор 1 :  длина-поля  1;

             unsigned идентификатор 2 :  длина-поля  2;    }

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

Пример:

     struct { unsigned a1 :  1;

              unsigned a2 :  2;

              unsigned a3 :  5;

              unsigned a4 :  2;  } prim;

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

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

Указатели

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

спецификатор-типа [ модификатор ] * описатель .

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

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.

Для модификации размера указателя можно использовать ключевые слова near, far, huge.

Примеры:

  unsigned int * a; /* переменная  а  представляет собой указатель

                    на тип unsigned int (целые числа без знака) */

  double * x;       /* переменная  х  указывает  на  тип  данных с

                    плавающей  точкой  удвоенной  точности      */

  char * fuffer ;   /*  объявляется  указатель с именем fuffer

                     который указывает на  переменную типа char */

  double nomer;

  void *addres;

  addres = & nomer;

  (double *)addres ++;

 /* Переменная addres объявлена как указатель на объект любого типа. Поэтому ей можно присвоить адрес любого объекта (& - операция  вычисления адреса). Однако, как было отмечено выше, ни одна арифмитическая операция не может быть выполнена над указателем,  пока

 не будет явно определен тип данных,  на которые он указывает. Это

 можно сделать,  используя операцию приведения типа (double *) для

 преобразования addres к указателю на тип double, а затем увеличение адреса. */

   const * dr;

 /* Переменная  dr  объявлена как указатель на константное выражение, т.е. значение указателя может изменяться в процессе выполнения программы, а величина, на которую он указывает, нет. */

   unsigned char * const w = &obj.

 /* Переменная w объявлена как константный указатель на данные типа char unsigned.  Это означает, что на протяжение всей программы

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

 этой области может быть изменено. */

Преобразования типов: преобразования по умолчанию, явные преобразования

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

- при выполнении операций осуществляются обычные арифметические преобразования (которые были рассмотрены выше);

- при выполнении операций присваивания, если значение одного типа присваивается переменной другого типа;

- при передаче аргументов функции.

Кроме того, в Си есть возможность явного приведения значения одного типа к другому.

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

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

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

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

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

Целые значения без знака преобразуются к плавающему типу, путем преобразования целого без знака к значению типа signed long, а затем значение signed long преобразуется в плавающий тип. Преобразования из unsigned long к типу float, double или long double производятся с потерей информации, если преобразуемое значение больше, чем максимальное положительное значение, которое может быть представлено для типа long.

Преобразования плавающих типов. Величины типа float преобразуются к типу double без изменения значения. Величины double и long double преобразуются к float c некоторой потерей точности. Если значение слишком велико для float, то происходит потеря значимости, о чем сообщается во время выполнения.

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

Преобразования из float, double или long double к типу unsigned long производится с потерей точности, если преобразуемое значение больше, чем максимально возможное положительное значение, представленное типом long.

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

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

- если размер указателя меньше размера целого типа или равен ему, то указатель преобразуется точно так же, как целое без знака;

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

Целый тип может быть преобразован к адресному типу по следующим правилам:

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

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

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

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

Эти преобразования выполняются независимо для каждого аргумента. Величины типа float преобразуются к double, величины типа char и short преобразуются к int, величины типов unsigned char и unsigned short преобразуются к unsigned int. Могут быть также выполнены неявные преобразования переменных типа указатель. Задавая прототипы функций, можно переопределить эти неявные преобразования и позволить компилятору выполнить контроль типов.

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

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

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

Пример:

        int     i=2;

        long    l=2;

        double    d;

        float     f;

        d=(double)i * (double)l;

        f=(float)d;

В данном примере величины i,l,d будут явно преобразовываться к указанным в круглых скобках типам


Глава 2

2.1 Константы

Максимально допустимое значение константы типа  short 

2.2 Манипуляции с битами

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

01010000), используется формула:

x&(x-1) 

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

степенью 2 (путем проверки результата вычислений на равенство 0).

Чтобы выделить в слове крайний справа единичный бит (например,00001000 из 01011000, если такого бита нет, возвращает 0), используется формула

x&(-x)

Чтобы выделить в слове крайний справа нулевой бит (например, 00001000 из 01001111,

если такого бита нет, возвращает 0), используется формула

~x&(x+1)

Почему нет примеров с левыми битами

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

средством операций побитового сложения, вычитания, и, или, отрицания тогда

и только тогда, когда каждый бит результата зависит от битов исход-

ных операндов в той же позиции и правее нее.

Ниже приводится набор тождеств для операций сложения и

вычитания в комбинации с логическими операциями:

-x=~x+1

-x=~(x-1)

x-y=x+~y+1

x|y=(x&~y)+y

x&y=(~x|y)-~x

Абсолютное значение x

y=x>>31;

absx=(x^y)-y;   или absx=(x+y)^y;

Пример распространения влево бита в седьмой позиции.

((x+ 0x00000080) & 0x000000FF) 0x00000080

Если на машине нет команды знакового сдвига вправо (shift right signed) ее можно

заменить другими командами.

t=-(x>>31);  ((x^t)>>n)^t;

Трехзначная функция сравнения

(x>y)-(x<y)

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

После вычисления:

int k=(32.23/100)*100;

в переменной k значение 32.22


 

А также другие работы, которые могут Вас заинтересовать

43492. ИСТОРИЯ ОТЕЧЕСТВЕННОГО ГОСУДАРСТВА И ПРАВА. МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИ 76 KB
  При написании курсовой работы студент овладевает навыками работы с источниками специальной литературой учится приемам самостоятельного творческого исследования. Ответственность за достоверность данных содержащихся в работе и за соответствие ее требованиям несет автор исполнитель курсовой работы. Алгоритм написания курсовой работы выглядит следующим образом: Шаг первый – выбор темы.
43493. Информационная система Оценка оплаты отгруженного товара 2.45 MB
  В справочнике заказчиков хранятся данные о заказчиках: их наименованиях кодах адресах и датах заключения договоров. Условно постоянная информация Форма. Справочник готовой продукции Наименование изделия Код изделия Единица измерения Код единицы измерения Цена за единицу руб. 1 18 Форма 2 – Данные о заказчиках взяты из договоров Наименование заказчика Код заказчика Адрес Дата заключения договора Магазин В школу 501 Ул.2010 Оперативно учетная информация Форма 3 –Данные об отгрузке товаров из ТТН № ТТН Дата отгрузки Код заказчика...
43494. КРИМИНАЛИСТИКА. МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ И ЗАДАНИЯ 51.5 KB
  Расследование преступных нарушений правил безопасности и охраны труда . Расследование преступных нарушений правил дорожного движения. Расследование преступлений скрытых инсценировками. Расследование преступлений совершенных в условиях неочевидности.
43495. Процесс управления организацией на основе анализа деятельности фирмы ЗАО «Комфорт» 338 KB
  Миссия организации и стратегическое видение Цели организации SWOTанализ Оценка и анализ внешней среды Управленческое обследование внутренних сильных и слабых сторон организации Анализ стратегических альтернатив и выбор стратегии Реализация стратегического плана Организация взаимодействия и полномочия Мотивация Контроль Выводы и рекомендации Далее описывается основное содержание глав курсовой работы. Рекомендации по выполнению курсовой работы Характеристика организации В настоящем разделе кратко излагаются основные характеристики...
43496. Исследование и программная реализация методов алгоритмов теории графов 115 KB
  Реализовать выбранный алгоритм на языке Pscl желательно использовать представление графа списками. Пояснительная записка включает в себя 23 страницы текста рисунок исходного графа рисунок МОД схему алгоритма 2 использованных источника. Данная программа позволяет: Ввести граф используя матрицу длин дуг; Получить матрицу задающую минимальное остовное дерево; Провести тестирование алгоритма; Введение Во многих прикладных задачах теории графов важно иметь возможность сопоставить ребрам графа определенные числа которые соответствуют...
43497. МУНИЦИПАЛЬНОЕ ПРАВО РОССИИ. МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ И ЗАДАНИЯ 85.5 KB
  Развитие законодательства о местном самоуправлении в РФ. Государственный контроль и надзор за законностью местного самоуправления. Закон РФ Об общих принципах организации местного самоуправления в РФ от 6 октября 2003 г. Закон РФ О милиции от 18 апреля 1991 г.
43498. Проектирование ленточного конвейера 781 KB
  Наиболее трудоемкими в пищевой промышленности являются погрузочно-разгрузочные работы, которые занимают существенный объем в производственной деятельности предприятий. Погрузочно-разгрузочные работы выполняются на всех этапах основных производственных процессов. Для механизации этих операций используется подъемно-транспортное оборудование.
43499. Состояние рынка ценных бумаг в Казахстане 528 KB
  При купонных платежах государство устанавливает фиксированную годовую процентную ставку (купон), который выплачивается кредиторам либо раз в год, либо раз в полгода. В этом случае та сумма, которую государство заимствует в начале периода, будет равняться той сумме, которую оно выплатит в конце периода. Этот метод используете правительствами для большинства государственных облигаций.
43500. ТЕОРИЯ ГОСУДАРСТВА И ПРАВА. МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ И ЗАДАНИЯ 63 KB
  Объем курсовой работы устанавливается в пределах 30 машинописных страниц Темы курсовых работ по Теории государства и права Предмет и методология теории государства и права Развитие и современное состояние теории государства и права Происхождение государства и права Общая характеристика теорий происхождения государства и права Понятие и сущность государства Государственная власть: характерные признаки и формы осуществления Соотношение государства права и экономики Типология государства Социалистический тип государства:...