- •Преимущества
- •Связь: ooa→oop→ood
- •Механизм работы virtual
- •Абстрактный класс
- •Виртуальный деструктор
- •{Основные элементы языка программирования}
- •Основные подходы к семантике:
- •Формальное описание семантики:
- •Среда программирования
- •Объекты данных
- •Атрибуты объекта данных
- •Система типизации данных
- •Реализация типов данных
- •Типизация
- •Система типизации данных
- •Реализация типов данных
- •Связывание переменных
- •Контроль типов
- •Статистический контроль типов
- •Алгоритм статистического контроля типов
- •Динамический контроль типов
- •Параллелизм
- •Полиморфизм
- •Статический полиморфизм
- •Динамический полиморфизм
- •Преобразование типов
- •Связь: ooa→oop→ood
- •Среда программирования
- •Цель технологий параллелизма
- •Схемы параллелизма
- •Проблемы параллельного программирования
- •Показатели эффективности параллельного алгоритма Ускорение
- •Закон Густавсона-Барсиса
- •Масштабируемый алгоритм
- •Схемы параллелизма
- •Подтипы данных
- •Разновидности массивов. Статические
- •Кортежи
- •Разновидности массивов. Статические
- •Динамические
- •Атрибуты объекта данных
- •Операции над целыми числами
- •Вещественные числа
- •Числа с фиксированной точкой
- •Числа с плавающей точкой(float)
- •Ошибки вычислений с вещественными числами
- •Утечки памяти и повисшие указатели
- •Указатели на указатели
- •Типизированные указатели
- •Указатели на функции
- •[Отличия указателей и ссылок]
- •Параметры подпрограмм
- •Преимущества подпрограмм
- •Позиционное сопоставление
- •Сопоставление по имени
- •Методы передачи параметров:
- •Передача параметров по значению
- •Передача параметров по ссылке
- •Передача параметров по значению-результату
- •Параметр по результату
- •Стековые языки
- •Циклы и рекурсия
- •Циклы со счетчиком
- •Операторы циклы без заданного числа повторений (бесконечно)
- •Динамический полиморфизм
- •Родовые (настраиваемые) сегменты и шаблоны
Указатели на указатели
• Указатели могут ссылаться на другие указатели. При этом в ячейках памяти, на которые будут ссылаться первые указатели, будут содержаться не значения, а адреса вторых указателей.
• Число символов * при объявлении указателя показывает порядок указателя.
• Чтобы получить доступ к значению, на которое ссылается указатель его необходимо разыменовывать соответствующее количество раз.
Int var 123;
Int *ptrvar=&var;указатель на переменную
Int **ptr_ptrvar=&ptrvar;указатель на указатель на переменную
Cout<<var;123
Cout<<*ptrvar;123
Cout<<**ptr_ptrvar;123
Int i1,i2;
Int *const p1=&i1;указатель-константа. Переназначить на другую область памяти нельзя, но значение переменной можно.
Const int*p2=&i1;нельзя модифицировать значение переменной по этому аресу.
Const int*const p3=&i1; указатель-константа на константу. Нельзя перезначать адрес, нельзя изменить значение по этому указателю.
P1=&i2 ошибка, указатель-константа
*p1=5 правильно, т.к указатель объект не является константой
P2=&i2; правильно, указатель не const
*p2=5; ошибка, указуемый объект -константа
P3=&i2; ошибка, указатель-константа
*p3=5; ошибка, указуемый объект-константа
Типизированные указатели
• Неявно могут быть преобразованы в указатели void, но не обратно!
Void *void_ptr;нетипизированный указатель
Int *int_ptr;типизированный ук. на тип int
Char *char_ptr;типизированный указатель на char
Void_ptr=int_ptr;правильно
Char_ptr=void_ptr;правильно в С, но ошибка в С++
Char_ptr=int_ptr;предупреждение в С, но ошибка в С++
Указатели на функции
• Указатели могут ссылаться на функции. Имя функции, как и имя массива само по себе является указателем, то есть содержит адрес входа. Int(*a)(int)
Зачем нужны указатели??
Повышение эффективности. Вместо копирования или пересылки в памяти большой структуры данных можно скопировать или переслать только указать на эту структуру.
Динамические структуры. С помощью записей и указателей можно реализовать структуру данных, которые растут и сжимаются в период выполнения программы. Например, такие как списки и деревья. Помимо элементов данных самой структуры, узел содержит один или несколько указателей со ссылками на другие узлы.
Две секции: в одной секции(into) содержится порция данных, в другой – адрес соседнего элемента структуры.
Доступ к большим структурам данных. Используют для косвенного обращения к большим структурам данных, повышая скорость и сокращая затраты на организацию доступа.
Int *ptr;
Int a[100];
Ptr=&a[0];явный адрес первого элемента
Ptr=a;неявный адрес
*(ptr+1)- эквивалентно a[i]
[Отличия указателей и ссылок]
• Основные назначение указателя – организация динамических объектов, то есть размер, которых может меняться( увеличиваться или уменьшаться)
• Основные назначение ссылки – организации прямого доступа к тому, или иному объекту.
• Указатели ссылаются на участок памяти, используя его адрес. А ссылки на объект, по его имени(тоже своего рода адрес).
Указатели – это переменная, значением которой является адрес другой переменной. Указатель ссылается на блок данных из области памяти, причём на самое его начало.
Значение указательного (ссылочного) типа (pointer type) – это адрес. Объект, на который указывают, называется указуемым или обозначаемым объектом (designated object).
Различают типизированные и нетепизированные (void *) указатели, указатели на данные и на функции. При этом множества допустимых операций для указателей разных видов различны.
Размер (формат) адреса зависит только от величины адресного пространства компьютера и специфики его организации. Указатель позволяет предоставить косвенный доступ к элементам
известного типа (типизированное средство косвенного доступа к объектам данных).
int var = 123;
int *ptrvar; // объявление указателя
ptrvar = &var; // присвоили адрес переменной указателю
cout << &var; // адрес переменной var 0x22ff08
cout << ptrvar; // адрес переменной var 0x22ff08
cout << var; // значение в переменной var 123
cout << *ptrvar; // вывод значения содержащегося в переменной var через указатель, операцией разименования указателя 123
Основными операциями с указателями являются:
1. Объявление. int *pa; float *pb.
2. Получение адреса переменной. p = & a.
3. Разыменование. x = *p - получаем объект, на который указатель ссылается.
4. Получение адреса указателя. &p.
5. Увеличение указателя на единицу. ++p (перемещение к адресу следующего элемента массива).
6. Вычитание. p2 – p1 (результат отображается в тех же единицах, что и размер данного типа переменной).
Для типизированных указателей запрещены операция разадресации, инкремента и т.п.
Для указателя на функции определена операция вызова функции.
Приведем несколько примеров объявлений более сложных указателей.
int a; // Целое число
int *a; // Указатель на целое
int **a; // Указатель на указатель на целое
int a[10]; // Массив из десяти целых
int *a[10]; // Массив из десяти указателей на целые
int (*a)[10]; // Указатель на массив из десяти целых
int (*a)(int); // Указатель на функцию, которая берет целый аргумент и возвращает целое
int (*a[10])(int); // Массив из десяти указателей на функции, которые берут целый аргумент и возвращают целое
При работе с указателями важно четко понимать разницу между самим указателем (как переменной) и указуемым объектом. Приведем несколько примеров, иллюстрирующих эту разницу.
int i1 = 10;
int i2 = 20;
int *ptr1 = &i1; // ptr1 указывает на i1// ptr1 = 0x22ff04
int *ptr2 = &i2; // ptr2 указывает на i2// ptr2 = 0x22ff00
if (ptr1 > ptr2) {}; // истина т.к. 0x22ff04>0x22ff00
if (*ptr1 > *ptr2){}; // сравниваем сами переменные, false
*ptr1 = *ptr2; // косвенно приравниваем i1=i2=20.
if (ptr1 == ptr2) {}; // false, указатели до сих пор разные
if (*ptr1 == *ptr2) {}; // true, обознач. объекты равны
ptr1 = ptr2; // указывают на i2, ptr1=ptr2=0x22ff00
Указатели могут ссылаться на другие указатели.
При этом в ячейках памяти, на которые будут ссылаться первые указатели, будут содержаться не значения, а адреса вторых указателей.
Число символов * при объявлении указателя показывает порядок указателя.
Чтобы получить доступ к значению, на которое ссылается указатель его необходимо разыменовывать соответствующее количество раз.
int var = 123; // инициализация переменной var числом 123
int *ptrvar = &var; // указатель на переменную var
int **ptr_ptrvar = &ptrvar; // указатель на ук. на var
int ***ptr_ptr_ptrvar = &ptr_ptrvar; // ук. на ук. на указатель
cout << var; //123
cout << *ptrvar; //123
cout << **ptr_ptrvar; // два раза разименовываем указатель чтобы получить значение 123
cout << ***ptr_ptr_ptrvar;; // три раза разыменовывам чтобы получить 123
cout << "\n ***ptr_ptr_ptrvar -> **ptr_ptrvar -> *ptrvar -> var -> "<< var << endl;
cout << "\t " << &ptr_ptr_ptrvar<< " -> " << " " <<&ptr_ptrvar << " ->" << &ptrvar << " -> " << &var << " ->" << var << endl;
Результатом выполнения двух последних строк будет
***ptr_ptr_ptrvar -> **ptr_ptrvar -> *ptrvar -> var -> 123 0x22ff00 -> 0x22ff04 ->0x22ff08 -> 0x22ff0c -> 123
Важно также понимать различие между указателем-константой и указателем на константный объект. В первом случае можно менять значение указуемого объекта, но нельзя менять значение самого указателя и переназначать его на другую область памяти. Во втором случае значение указуемого объекта менять нельзя, но сам указатель можно переназначать на другую область памяти.
Приведем примеры, демонстрирующие эти различия.
int i1, i2;
/*
указатель-константа. Переназначить на другую область
памяти нельзя, но значение переменной менять можно
*/
int * const p1 = &i1;
/*
указатель на const. Нельзя модифицировать значение
переменной по этому адресу.
*/
const int* p2 = &i1;
/*
указатель-константа на константу. Нельзя переназначить
адрес, нельзя изменить значение по этому указателю.
*/
const int* const p3 = &i1; // p1 = &i2; // ошибка,указатель-константа
*p1 = 5; // правильно, ук. объект не является const
p2 = &i2; // правильно, указатель не является const
*p2 = 5; // ошибка, указуемый объект – константа
p3 = &i2; // ошибка, указатель-константа
*p3 = 5; // ошибка, указуемый объект - константа
Типизированные указатели неявно могут быть преобразованы в указатели на void. Обратное преобразрвание может привести к ошибке.
void *void_ptr; // нетепизированный указатель
int *int_ptr; // типизированный ук. на тип int
char *char_ptr; // типизированный указатель на char
void_ptr = int_ptr; // правильно
char_ptr = void_ptr; // правильно в С, но ошибка С++
char_ptr = int_ptr; // предупреждение в С, ошибка в С++
Основные преимущества использования указателей:
1. Повышение эффективности. Вместо копирования или пересылки в памяти большой структуры данных можно скопировать или переслать только указатель на эту структуру.
2. Динамические структуры. С помощью записей и указателей можно реализовать структуры данных, которые растут и сжимаются в период выполнения программы. Например, такие как списки и деревья. Помимо элементов данных самой структуры, узел содержит один или несколько указателей со ссылками на другие узлы.
3. Доступ к большим структурам данных. Используют для косвенного обращения к большим структурам данных, повышая скорость и сокращая затраты на организацию доступа.
Например для доступа к элементам массива можно использовать как явное обращение, так и косвенную адресацию, как это показано в следующем примере.
int *ptr;
int a[100];
ptr = &a[0]; // явный адрес первого элемента
ptr =a; // неявный адрес первого элемента
*(ptr+1); // эквивалент операции a[i]
56. Динамические структуры данных. Реализация динамических структур данных с помощью указателей.
В языках программирования (Pascal, C, др.) существует и другой способ выделения памяти под данные, который называется динамическим. В этом случае память под величины отводится во время выполнения программы. Такие величины будем называть динамическими. Раздел оперативной памяти, распределяемый статически, называется статической памятью; динамически распределяемый раздел памяти называется динамической памятью (динамически распределяемой памятью).
Использование динамических величин предоставляет программисту ряд дополнительных возможностей. Во-первых, подключение динамической памяти позволяет увеличить объем обрабатываемых данных. Во-вторых, если потребность в каких-то данных отпала до окончания программы, то занятую ими память можно освободить для другой информации. В-третьих, использование динамической памяти позволяет создавать структуры данных переменного размера.
57. Библиотеки программ и классов. Статические и динамические библиотеки. Критерии проектирования библиотек.
Библиотека (от англ. library) в программировании — сборник подпрограмм или объектов, используемых для разработки программного обеспечения (ПО).
В некоторых языках программирования[каких?] то же, что модуль, в некоторых — несколько модулей. С точки зрения операционной системы (ОС) и прикладного ПО библиотеки разделяются на динамические и статические.
Динамические библиотеки
Часть основной программы, которая загружается в ОС по запросу работающей программы в ходе её выполнения (Run-time), то есть динамически (Dynamic Link Library, DLL в Windows). Один и тот же набор функций (подпрограмм) может быть использован сразу в нескольких работающих программах, из-за чего они имеют ещё одно название — библиотеки общего пользования (Shared Library). Если динамическая библиотека загружена в адресное пространство самой ОС (System Library), то единственная копия может быть использована множеством работающих с нею программ, что положительно сказывается на степени использовании ресурса ОЗУ. Динамические библиотеки могут содержать в себе как критические для работы программы части, так и дополнительные функции. Дополнительным плюсом такого подхода является то, что динамическая библиотека может быть использована в качестве плагина (Plug-ins), расширяющего функциональность программы. Минусом является то, что в случае, если модуль, который содержит в себе критическую часть, отсутствует, программа не сможет продолжить работу.
Динамические библиотеки хранятся обычно в определенном месте и имеют стандартное расширение. Например, файлы .library в логическом томе Libs: в AmigaOS; в Microsoft Windows и OS/2 файлы библиотек общего пользования имеют расширение .dll; в UNIX‐подобных ОС — обычно .so; в Mac OS — .dylib.
При написании программы программисту достаточно указать транслятору (компилятору или интерпретатору) языка программирования, что следует подключить нужную библиотеку и использовать функцию из неё. Ни исходный текст, ни исполняемый код функции в состав программы на данном этапе не входит.
Статические библиотеки
Могут быть в виде исходного текста, подключаемого программистом к своей программе на этапе написания (например, для языка Fortran существует огромное количество библиотек для решения разных задач именно в исходных текстах), либо в виде объектных файлов, присоединяемых (линкуемых) к исполняемой программе на этапе компиляции (в Microsoft Windows такие файлы имеют расширение .lib, в UNIX‐подобных ОС — обычно .a). В результате программа включает в себя все необходимые функции, что делает её автономной, но увеличивает размер. Без статических библиотек объектных модулей (файлов) невозможно использование большинства современных компилирующих языков и систем программирования: Fortran, Pascal, C, C++ и других.
58. Подпрограммы. Формальные и фактические параметры подпрограмм.
Подпрограммы – группы операторов, которые могут быть выполнены неоднократно. Её можно рассматривать как некий сегмент программы, который должен выполняться на разных стадиях вычисления, можно рассматривать, как логическую единицу декомпозиции программы. Классически подпрограмму делят на процедуры и функции.
Основные понятия
Интерфейс подпрограммы – указание ее имени, параметров и типа возвращаемого значения.
Имя подпрограммы – любой допустим идентификатор языка программирования, по которому транслятор однозначно восстанавливает соответствие между вызывающим и вызываемым кодом.
Формальный параметр – некоторое значение, в том числе переменная и адрес памяти передаваемая внутрь подпрограммы и использующаяся ею для вычисления, в том числе и дл возвращения значений.
Тело подпрограммы это группа операторов, из которых состоит программа.
Вызов подпрограммы – оператор, указывающий на необходимость выполнения подпрограммы. В большинстве языков программирования это указание имени и фактических параметров.
Фактический параметр – конкретное значение, подставляемое на место формального параметра.
• Подпрограмма - группа операторов, которая может быть выполнена неоднократно.
• Рассматривать можно как:
Сегмент программы, который должен выполняться на разных стадиях вычисления, может быть написан один раз в виде подпрограммы, а затем многократно выполняться.
Логическая единица декомпозиции программы( компиляции)
• Делятся на:
Функция ( записываются внутри выражений r*sin(angle))
Процедуры ( рассматриваются как атомарные операторы read(ch);)
• Функции - расширяют встроенные в язык операции, возвращают результат, который можно использовать в выражениях, как правило вызываются из выражений.
• Процедуры – они расширяют встроенные в язык операторы, просто выполняют действие, результат не возвращают, как правило вызываются отдельным оператором.
• В языке С используются только функции.
• Если функция не должна ничего возвращать то ее тип обозначается как void.
• В ранних языках программирования подпрограммы могли выполнять действия не имели имени и параметров.
• Описание (интерфейс) подпрограммы - указатели её имени, параметров и типа возвращаемого значения (если есть).
• Имя подпрограммы - любой допустимый идентификатор языка программирования, по которому транслятор однозначно восстанавливает соответствие между вызывающим и вызываемым кодом.
• Тело подпрограммы - группа операторов, из которых состоит подпрограмма.
• Вызов подпрограммы - оператор, указывающий на необходимость выполнения подпрограммы. В большинстве языков программирования это указатели имени (иногда предваряемое ключевым словом CALL) и фактических параметров.
