Читайте также:
|
|
Шаблон функции начинается с ключевого слова template, за которым в угловых скобках следует список параметров. Затем следует объявление функции:
template< typename T >
void sort(T array[], int size); // прототип: шаблон sort объявлен, но не определён
template< typename T >
void sort(T array[], int size) // объявление и определение
{
T t;
for (int i = size - 1; i > 0; i--)
for (int j = i; j > 0; j--)
if (array[j] < array[j-1])
{
t = array[j];
array[j] = array[j-1];
array[j-1] = t;
}
}
template< int BufferSize > // целочисленный параметр
char* read()
{
char *Buffer = new char[ BufferSize ];
/* считывание данных */
return Buffer;
}
Ключевое слово typename появилось сравнительно недавно, поэтому стандарт[1] допускает использование class вместо typename:
template< class T >
Вместо T допустим любой другой идентификатор.
68.
Директива #include
Директива #include включает в текст программы содержимое указанного файла. Эта директива имеет две формы:
#include "имя файла"
#include <имя файла>
Имя файла должно соответствовать соглашениям операционной системы и может состоять либо только из имени файла, либо из имени файла с предшествующим ему маршрутом. Если имя файла указано в кавычках, то поиск файла осуществляется в соответствии с заданным маршрутом, а при его отсутствии в текущем каталоге. Если имя файла задано в угловых скобках, то поиск файла производится в стандартных директориях операционной системы, задаваемых командой PATH.
Директива #include может быть вложенной, т.е. во включаемом файле тоже может содержаться директива #include, которая замещается после включения файла, содержащего эту директиву.
Директива #include широко используется для включения в программу так называемых заголовочных файлов, содержащих прототипы библиотечных функций, и поэтому большинство программ на СИ начинаются с этой директивы.
69.
Директива #define
Директива #define служит для замены часто использующихся констант, ключевых слов, операторов или выражений некоторыми идентификаторами. Идентификаторы, заменяющие текстовые или числовые константы, называют именованными константами. Идентификаторы, заменяющие фрагменты программ, называют макроопределениями, причем макроопределения могут иметь аргументы.
Директива #define имеет две синтаксические формы:
#define идентификатор текст
#define идентификатор (список параметров) текст
Эта директива заменяет все последующие вхождения идентификатора на текст. Такой процесс называется макроподстановкой. Текст может представлять собой любой фрагмент программы на СИ, а также может и отсутствовать. В последнем случае все экземпляры идентификатора удаляются из программы.
Пример:
#define WIDTH 80
#define LENGTH (WIDTH+10)
Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.
Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате получится 780, а не 630.
Во второй синтаксической форме в директиве #define имеется список формальных параметров, который может содержать один или несколько идентификаторов, разделенных запятыми. Формальные параметры в тексте макроопределения отмечают позиции на которые должны быть подставлены фактические аргументы макровызова. Каждый формальный параметр может появиться в тексте макроопределения несколько раз.
При макровызове вслед за идентификатором записывается список фактических аргументов, количество которых должно совпадать с количеством формальных параметров.
Пример:
#define MAX(x,y) ((x)>(y))?(x):(y)
Эта директива заменит фрагмент
t=MAX(i,s[i]);
на фрагмент
t=((i)>(s[i])?(i):(s[i]);
Как и в предыдущем примере, круглые скобки, в которые заключены формальные параметры макроопределения, позволяют избежать ошибок связанных с неправильным порядком выполнения операций, если фактические аргументы являются выражениями.
Например, при наличии скобок фрагмент
t=MAX(i&j,s[i]||j);
будет заменен на фрагмент
t=((i&j)>(s[i]||j)?(i&j):(s[i]||j);
а при отсутствии скобок - на фрагмент
t=(i&j>s[i]||j)?i&j:s[i]||j;
в котором условное выражение вычисляется в совершенно другом порядке.
70.
Директивы препроцессора представляют собой инструкции, записанные в тексте программы на СИ, и выполняемые до трансляции программы. Директивы препроцессора позволяют изменить текст программы, например, заменить некоторые лексемы в тексте, вставить текст из другого файла, запретить трансляцию части текста и т.п. Все директивы препроцессора начинаются со знака #. После директив препроцессора точка с запятой не ставятся.Директива #define служит для замены часто использующихся констант, ключевых слов, операторов или выражений некоторыми идентификаторами. Идентификаторы, заменяющие текстовые или числовые константы, называют именованными константами. Идентификаторы, заменяющие фрагменты программ, называют макроопределениями, причем макроопределения могут иметь аргументы.Директива #define имеет две синтаксические формы:
#define идентификатор текст
#define идентификатор (список параметров) текст
Эта директива заменяет все последующие вхождения идентификатора на текст. Такой процесс называется макроподстановкой. Текст может представлять собой любой фрагмент программы на СИ, а также может и отсутствовать. В последнем случае все экземпляры идентификатора удаляются из программы.
Пример:
#define WIDTH 80
#define LENGTH (WIDTH+10)
Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате получится 780, а не 630.Во второй синтаксической форме в директиве #define имеется список формальных параметров, который может содержать один или несколько идентификаторов, разделенных запятыми. Формальные параметры в тексте макроопределения отмечают позиции на которые должны быть подставлены фактические аргументы макровызова. Каждый формальный параметр может появиться в тексте макроопределения несколько раз.При макровызове вслед за идентификатором записывается список фактических аргументов, количество которых должно совпадать с количеством формальных параметров. Пример:
#define MAX(x,y) ((x)>(y))?(x):(y)
Эта директива заменит фрагмен t=MAX(i,s[i]); на фрагмент t=((i)>(s[i])?(i):(s[i]);
Как и в предыдущем примере, круглые скобки, в которые заключены формальные параметры макроопределения, позволяют избежать ошибок связанных с неправильным порядком выполнения операций, если фактические аргументы являются выражениями. Например, при наличии скобок фрагмент t=MAX(i&j,s[i]||j); будет заменен на фрагмент t=((i&j)>(s[i]||j)?(i&j):(s[i]||j);
а при отсутствии скобок - на фрагмент t=(i&j>s[i]||j)?i&j:s[i]||j;
в котором условное выражение вычисляется в совершенно другом порядке.
71.
Компиляция программы на Си - многопроходная. На ее нулевой фазе для обработки исходного файла используется препроцессор. Компилятор вызывает препроцессор автоматически, однако последний может быть вызван и независимо. Директивы препроцессора - это инструкции, записанные непосредственно в исходном тексте программы.Директивы нужны для того, чтобы облегчить написание и модификацию программ, а также сделать их более независимыми от аппаратных платформ и операционных систем. Директивы препроцессора позволяют заменять лексемы в тексте программы, вставить в файл содержимое других файлов, запрещать компиляцию части файла или делать ее зависимой от некоторых условий и т. д.Можно выделить следующие основные виды директив:
· определение макрокоманд;
· вставка файлов;
· условная компиляция программы.
Директивы препроцессор Си перечислены в табл. 10.1.
Таблица 10.1. Директивы препроцессора Си
#define #else #if #ifndef #line #elif #endif #ifdef #include #undef
При указании любой директивы первым значащим символом в строке должен быть символ "#".Директивы могут быть записаны в любом месте исходного файла. Их действие распространяется от точки программы, в которой они записаны до конца исходного файла. Часть директив могут содержать аргументы. Использование директив #ifdef и #ifndef эквивалентно применению директивы #if совместно с операцией defined(идентификатор). Эти директивы поддерживаются для совместимости с предыдущими версиями компиляторов Си. Синтаксис директив следующий:
#ifdef идентификатор
#ifndef идентификатор
При обработке #ifdef препроцессор проверяет, определен ли в данный момент идентификатор директивой #define. Если это так, условие считается истинным, иначе ложным.Директива #ifndef противоположна по действию: если идентификатор не был определен директивой #define или его определение отменено директивой #undef, то условие считается истинным. В противном случае условие ложно.Аналогично #if, за #ifdef и #ifndef может следовать набор директив #elif и/или директива #else. Набор должен быть завершен директивой #endif.
72.
Шаблоны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например типам данных, размерам буферов, значениям по умолчанию).В C++ возможно создание шаблонов функций и классов.Шаблоны позволяют создавать параметризованные классы и функции. Например, нам нужен какой-то класс:
class SomeClass{
int SomeValue;
int SomeArray[20];
...
}
Для одной конкретной цели мы можем использовать этот класс. Но, вдруг, цель немного изменилась, и нужен еще один класс. Теперь нужно 30 элементов массива SomeArray и вещественный тип SomeValue. Тогда мы можем абстрагироваться от конкретных типов и использовать шаблоны с параметрами. Синтаксис: в начале перед объявлением класса напишем слово template и укажем параметры в угловых скобках. В нашем примере:
template < int ArrayLength, typename SomeValueType > class SomeClass{
SomeValueType SomeValue;
SomeValueType SomeArray[ ArrayLength ];
...
}
Тогда для первой модели пишем:
SomeClass < 20, int > SomeVariable;
для второй:
SomeClass < 30, double > SomeVariable2;
Хотя шаблоны предоставляют краткую форму записи участка кода, на самом деле их использование не сокращает исполнимый код, так как для каждого набора параметров компилятор создаёт отдельный экземпляр функции или класса.
struct aa
{ int b;
int c;
};
aa тут яваляется шаблоном, тоже самое для enum и unionПростейшим примером служит определение минимума из двух величин.
Если a меньше b то вернуть а, иначе - вернуть b.
В отсутствие шаблонов программисту приходится писать отдельные функции для каждого используемого типа данных. Хотя многие языки программирования определяют встроенную функцию минимума для элементарных типов (таких как целые и вещественные числа), такая функция может понадобится и для сложных (например «время» или «строка») и очень сложных («игрок» в онлайн-игре) объектов.Так выглядит шаблон функции определения минимума:
template< typename T >
T min(T a, T b)
{
return a < b? a: b;
}
Для вызова этой функции можно просто использовать её имя:
min(1, 2);
min('a', 'b');
min(string("abc"), string("cde"));
73.
Шаблоны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например типам данных, размерам буферов, значениям по умолчанию).В C++ возможно создание шаблонов функций и классов.Шаблоны позволяют создавать параметризованные классы и функции. Например, нам нужен какой-то класс: class SomeClass{ int SomeValue; int SomeArray[20];... } template <int size> struct aa{
char name[size];
};
74.
75.
Абстрактные структуры данных предназначены для удобного хранения и доступа к информации. Они предоставляют удобный интерфейс для типичных операций с хранимыми объектами, скрывая детали реализации от пользователя. Конечно, это весьма удобно и позволяет добиться большей модульности программы. Абстрактные структуры данных иногда делят на две части: интерфейс, набор операций над объектами, который называют АТД(абстрактный тип данных) и реализацию.
Стек - такой последовательный список с переменной длиной, включение и исключение элементов из которого выполняются только с одной стороны списка, называемого вершиной стека. Применяются и другие названия стека - магазин и очередь, функционирующая по принципу LIFO (Last - In - First- Out - "последним пришел - первым исключается"). Примеры стека: винтовочный патронный магазин, тупиковый железнодорожный разъезд для сортировки вагонов.
Основные операции над стеком - включение нового элемента (английское название push - заталкивать) и исключение элемента из стека (англ. pop - выскакивать).
Полезными могут быть также вспомогательные операции:
определение текущего числа элементов в стеке;
очистка стека;
неразрушающее чтение элемента из вершины стека, которое может быть реализовано, как комбинация основных операций:
x:=pop(stack); push(stack,x);
Для наглядности рассмотрим небольшой пример, демонстрирующий принцип включения элементов в стек и исключения элементов из стека. На рис. 4 (а,б,с) изображены состояния стека:
а). пустого;
б-г). после последовательного включения в него элементов с именами 'A', 'B', 'C';
д, е). после последовательного удаления из стека элементов 'C' и 'B';
ж). после включения в стек элемента 'D'.
76.
Абстрактные структуры данных предназначены для удобного хранения и доступа к информации. Они предоставляют удобный интерфейс для типичных операций с хранимыми объектами, скрывая детали реализации от пользователя. Конечно, это весьма удобно и позволяет добиться большей модульности программы. Абстрактные структуры данных иногда делят на две части: интерфейс, набор операций над объектами, который называют АТД(абстрактный тип данных) и реализацию.
Очередь FIFO
Очередью FIFO (First - In - First- Out - "первым пришел - первым исключается"). называется такой последовательный список с переменной длиной, в котором включение элементов выполняется только с одной стороны списка (эту сторону часто называют концом или хвостом очереди), а исключение - с другой стороны (называемой началом или головой очереди). Те самые очереди к прилавкам и к кассам, которые мы так не любим, являются типичным бытовым примером очереди FIFO.
Основные операции над очередью - те же, что и над стеком - включение, исключение, определение размера, очистка, неразрушающее чтение.
77.
Списком называется упорядоченное множество, состоящее из переменного числа элементов, к которым применимы операции включения, исключения. Список, отражающий отношения соседства между элементами, называется линейным. Длина списка равна числу элементов, содержащихся в списке, список нулевой длины называется пустым списком. Линейные связные списки являются простейшими динамическими структурами данных.
Графически связи в списках удобно изображать с помощью стрелок. Если компонента не связана ни с какой другой, то в поле указателя записывают значение, не указывающее ни на какой элемент. Такая ссылка обозначается специальным именем - nil.
Рис. 1: Представление односвязного списка в памяти
На рис. 1 приведена структура односвязного списка. На нем поле INF - информационное поле, данные, NEXT - указатель на следующий элемент списка. Каждый список должен иметь особый элемент, называемый указателем начала списка или головой списка, который обычно по формату отличен от остальных элементов. В поле указателя последнего элемента списка находится специальный признак nil, свидетельствующий о конце списка.
Двусвязный список характеризуется наличием пары указателей в каждом элементе: на предыдущий элемент и на следующий:
Рис. 2: Представление двусвязного списка в памяти
Очевидный плюс тут в том, что от данного элемента структуры мы можем пойти в обе стороны. Таким образом упрощаются многие операции. Однако на указатели тратится дополнительная память.
Разновидностью рассмотренных видов линейных списков является кольцевой список, который может быть организован на основе как односвязного, так и двухсвязного списков. При этом в односвязном списке указатель последнего элемента должен указывать на первый элемент; в двухсвязном списке в первом и последнем элементах соответствующие указатели переопределяются, как показано на рис.3.
Рис. 3: Структура кольцевого двухсвязного списка
При работе с такими списками несколько упрощаются некоторые процедуры. Однако, при просмотре такого списка следует принять некоторых мер предосторожности, чтобы не попасть в бесконечный цикл.
78.
Из всех универсальных невстроенных типов самым полезным, по всей видимости, является ассоциативный массив. Его часто называют таблицей (map), а иногда словарем, и он хранит пары значений. Имея одно из значений, называемое ключом, можно получить доступ к другому, называемому просто значением. Ассоциативный массив можно представлять как массив, в котором индекс не обязан быть целым: template<class K, class V> class Map { //... public: V& operator[](const K&); // найти V, соответствующее K // и вернуть ссылку на него //... }; Здесь ключ типа K обозначает значение типа V. Предполагается, что ключи можно сравнивать с помощью операций == и <, так что массив можно хранить в упорядоченном виде. Отметим, что класс Map отличается от типа assoc из $$7.8 тем, что для него нужна операция "меньше чем", а не функция хэширования.
79. Проект — это базовое понятие. Под проектом подразумевается совокупность исходных файлов, распределенных каким-то образом по подкаталогам проекта. Проект может содержать один или более проектный файл для построения из исходных текстов чего-либо, что может считаться целью проекта. Например, для C++ это может быть статическая библиотека, динамическая библиотека, исполнимый файл. Проект может вообще не содержать проектных файлов, если целью проекта является предоставление исходных файлов другим проектам. Для С++ примерами таких проектов являются cpp_util_2, auto_ptr_3, большая часть библиотек boost и т.д., в которых код сосредоточен в inline-функциях и не требует отдельной компиляции. |
Проект может нуждаться в подпроектах. Подпроект — это исходные тексты (и не только) другого проекта, импортированные в структуру каталогов проекта. Например, проект so_4 нуждается в исходных текстах проектов auto_ptr_3, cpp_util_2, memcheck_2, oess_1, threads_1 и др. Эти проекты объявляются подпроектами проекта so_4. При помощи специальной команды их исходные тексты копируются в каталог проекта so_4 и используются для компиляции so_4. Проект состоит из одного или нескольких модулей. Каждый модуль представляет некую самостоятельную сущность, которая может выступать в качестве подпроекта в другом проекте. Например, проект oess_1 состоит из модулей: oess_1/defs, oess_1/io, oess_1/file, oess_1/stdsn, oess_1/db, oess_1/tlv и т.д. Включение всех модулей подпроекта oess_1, например, в проект so_4 может быть невыгодно, если проект so_4 использует функциональность только модуля oess_1/stdsn. В этом случае в качестве подпроекта so_4 использует модуль oess_1/stdsn. 2.2. Версия: поколение, ветвь, релиз Версия — это некоторый идентификатор, который определяет функциональность проекта. Предлагается использовать идентификатор из трех составляющих: поколение (generation), ветвь (branch), релиз (release). Например, запись oess версии 1.1.4 говорит о релизе 4 ветви “2” первого поколения проекта oess. Поколение и ветвь определяют степень несовместимости различных версий проектов. Версии абсолютно не совместимы, если не совпадает значение поколения. Т.е., если проект so использовал oess первого поколения, то для перехода на второе поколение oess потребуется серьезная переделка проекта so. Значение поколения следует изменять, если в проекте происходят фундаментальные изменения. Например, меняется идеология работы с проектом. Поколение проекта является настолько важным показателем, что номер поколения следует включать в имя проекта. Так, название oess_1 сразу говорит о проекте “oess” первого поколения, название so_4 — о проекте “so” четвертого поколения и т.д. Разные версии проекта более-менее совместимы между собой, если в их версиях совпадает номер поколения, но не совпадают имена ветвей. Несовпадающие имена ветвей говорят о том, что переход с одной ветви на другую возможен без значительных изменений в использующем этот проект проекте. Т.е. гарантируется сохранение идеологии работы с проектом, но в незначительной степени меняется синтаксис. Если в качестве имен ветвей использовать числовые значения, например, 4.0.3, 4.1.5, 4.2.7, то увеличение номера ветви говорит о создании новой версии, не гарантирующей 100% совместимости “сверху-вниз”.
80.
81.
Модульное программирование
Со временем при в проектировании программ акцент сместился с организации процедур на организацию структур данных. Помимо всего прочего это вызвано и ростом размеров программ. Модулем обычно называют совокупность связанных процедур и тех данных, которыми они управляют. Парадигма программирования приобрела вид:
Определите, какие модули нужны; поделите программу так, чтобы данные были скрыты в этих модулях
Эта парадигма известна также как "принцип сокрытия данных". Если в языке нет возможности сгруппировать связанные процедуры вместе с данными, то он плохо поддерживает модульный стиль программирования. Теперь метод написания "хороших" процедур применяется для отдельных процедур модуля. Типичный пример модуля - определение стека. Здесь необходимо решить такие задачи:
[1] Предоставить пользователю интерфейс для стека (например, функции push () и pop ()).
[2] Гарантировать, что представление стека (например, в виде массива элементов) будет доступно лишь через интерфейс пользователя.
[3] Обеспечивать инициализацию стека перед первым его использованием.
Язык Модула-2 прямо поддерживает эту парадигму, тогда как С только допускает такой стиль. Ниже представлен на С возможный внешний интерфейс модуля, реализующего стек:
// описание интерфейса длямодуля, // реализующего стек символов:
void push (char);
char pop ();
const int stack_size = 100;
Допустим, что описание интерфейса находится в файле stack.h, тогда реализацию стека можно определить следующим образом:
#include "stack.h" // используем интерфейс стека
static char v [ stack_size ]; // ``static'' означает локальный
// в данном файле/модуле
static char * p = v; // стек вначале пуст
void push (char c)
{ //проверить на переполнение и поместить в стек }
char pop ()
{ //проверить, не пуст ли стек, и считать из него }
Вполне возможно, что реализация стека может измениться, например, если использовать для хранения связанный список. Пользователь в любом случае не имеет непосредственного доступа к реализации: v и p - статические переменные, т. е. переменные локальные в том модуле (файле), в котором они описаны. Использовать стек можно так:
#include "stack.h" // используем интерфейс стека
void some_function ()
{
push ('c');
char c = pop ();
if (c!= 'c') error ("невозможно");
}
Поскольку данные есть единственная вещь, которую хотят скрывать, понятие упрятывания данных тривиально расширяется до понятия упрятывания информации, т. е. имен переменных, констант, функций и типов, которые тоже могут быть локальными в модуле. Хотя С++ и не предназначался специально для поддержки модульного программирования, классы поддерживают концепцию модульности ($$5.4.3 и $$5.4.4). Помимо этого С++, естественно, имеет уже продемонстрированные возможности модульности, которые есть в С, т. е. представление модуля как отдельной единицы трансляции.
82.
Отладка программных комплексов: этапы отладки, методы отладки, организация тестирования программ.
Тестирование проекта (C++)
Запуск программы в режиме отладки делает возможным использование точек останова для приостановки выполнения программы и изучения состояния переменных и объектов.
На данном этапе производится наблюдение за значением переменной в процессе выполнения программы и выявление причин отклонения этого значения от ожидаемого.
Запуск программы в режиме отладки
Щелкните вкладку testgames.cpp в области редактирования, если этот файл не отображается.
Щелкните следующую строку в редакторе, чтобы установить ее в качестве текущей: solitaire = new Cardgame(1);
Чтобы установить в этой строке точку останова, в меню Отладка выберите команду Точка останова или нажмите клавишу F9. Кроме того, для установки или удаления точки останова можно щелкнуть в области слева от строки кода.
Слева от строки с установленной точкой останова появляется красный кружок. В меню Отладка выберите команду Начать отладку или нажмите клавишу F5.При достижении программой строки с установленной точкой останова выполнение временно приостанавливается (так как программа находится в режиме приостановки выполнения). Строка, которая должна выполняться следующей, отмечается желтой стрелкой слева от строки. Чтобы узнать текущее значение переменной totalparticipants, наведите на нее курсор. Имя переменной и ее значение, равное 12, отобразится в окне всплывающей подсказки. Чтобы наблюдать за переменной totalparticipants в окне Контрольные значения, щелкните ее правой кнопкой мыши и выберите команду Добавить контрольное значение. Кроме того, можно выделить переменную и перетащить ее в окно Контрольные значения.
В меню Отладка нажмите кнопку Шаг с обходом или нажмите клавишу F10, чтобы перейти к следующей строке кода.
Значение переменной totalparticipants изменится на 13.
Щелкните правой кнопкой мыши последнюю строку в методе main (return 0;) и выберите команду Выполнить до текущей позиции. Желтая стрелка слева от строки кода указывает на следующий оператор, который должен выполняться.
Значение переменной totalparticipants должно уменьшаться при завершении работы приложения Cardgame.На этом этапе значение totalparticipants должно быть равно нулю, так как все указатели приложения Cardgame были удалены, однако в окне Контрольные значения 1 значение переменной totalparticipants указывается равным 18.
Следовательно в программе есть ошибка, которая будет выявлена и исправлена в следующем разделе.
Чтобы остановить программу, в меню Отладка выберите команду Остановить отладку или нажмите сочетание клавиш SHIFT+F5.
2.3.3. Отладка программы
Отладка начинается с устранения из программы синтаксических ошибок, т.е. таких, которые могут быть определены компилятором. Перед выполнением программы ее надо компилировать (Compile) и компонировать (Link). Если Вы сразу запускаете программу на выполнение (Run), то компиляция и компоновка происходят автоматически. Но обратите внимание на то, что компилятор C кроме сообщений об ошибках может выдавать еще и предупреждения. Предупреждения выдаются к таким конструкциям программы, которые являются формально правильными (с точки зрения синтаксиса), но компилятор "подозревает" в них семантическую ошибку. Довольно часто предупреждения компилятора действительно отражают ошибки программиста. Программа с ошибками не может компоноваться и исполняться. Программа с предупреждениями - может. Если Вы сразу (без исполнения компиляции отдельным шагом) запускаете программу на высполнение, Вы не увидете предупреждений компилятора, т.е рискуете выполнять программу с теми ошибками, которые могли быть выявленны предварительно. Мы рекомендуем всегда выполнять компиляцию отдельным шагом и не переходить к следующему шагу, пока Вы не убедитесь в том, что предупреждений компилятора нет или его "подозрения" безосновательны. Отладку програмного кода можно вести методом "черного ящика" или "белого ящика".
Подход "черного ящика" рассматривает программу как ящик с непрозрачными стенками, и тот, кто ведет отладку, ничего не знает о том, что там внутри. Он только задает программе входные данные и проверяет, правильны ли выходные данные программы. Идеальный тест методом "черного ящика" должен проверить програму на всех теоретически возможных комбинациях входных данных, но это по большей части просто технически невозможно. Значит, для тестирования следует подобрать "критические" комбинации входных данных, на которых больше всего возможны ошибки, - а это уже требует некоторых допущений о внутренней структуре программы. К тому же, когда оошибку обнаружено, подход "черного ящика" не дает нам возможности ее локализовать - обнаружить, в каком точно месте программы она находится. Значит, подход "черного ящика" более пригоден не для самой отладки, а для внешнего тестирования программы.
Для отладки, которая проводится самим автором программы, лучше применим подход "белого ящика", когда тот, кто ведет отладку, досконально знает внутреннюю структуру программы и ведет отладку по такому плану, который позволяет проверить функциональность программы на всех ветвях ее алгоритма и на всех граничных значениях ее переменных.
Современные системы програмирования (и Borland C++ в том числе) предоставляют возможность отладки программы в пошаговом режиме - выполнение программы с остановкой после каждого оператора или в заданых точках программы, с возможностью также проверять текущие значения переменных. Но не следует чересчур увлекаться этой возможностью. В ряде случаев "старый добрый" метод вывода промежуточных результатов оказывается и более быстрым, и более информативным.
План отладки тоже следует делать заранее. Планируйте отладку поэтапно, особенно для сложных алгоритмов. Не пытайтесь сразу проверить все. На каждом этапе проверяйте что-нибудь одно и переходите к следующему этапу только, когда будете уверены, что на данном этапе все работает корректно.
Иногда отладка требует внесения некоторых изменений в программу: объявление дополнительных переменных, разложение сложных выражений на простейшие, дополнительных операторов вывода и т.п. После отладки эти изменения должны быть удалены. Но после их удаления не забудьте убедиться, что это не внесло в программу новых ошибок.
83.
Особенности перехода от «С» к С++.
Язык программирования С++ произошёл от Си. Однако в дальнейшем Си и C++ развивались независимо, что привело к росту несовместимостей между ними. Последняя редакция Си, С99, добавила в язык несколько конфликтующих с С++ особенностей. Эти различия затрудняют написание программ и библиотек, которые могли бы нормально компилироваться и работать одинаково в компиляторах Си и C++, что, конечно, запутывает тех, кто программирует на обоих языках.
Бьёрн Строуструп, придумавший C++, неоднократно выступал за максимальное сокращение различий между Си и C++ для создания максимальной совместимости между этими языками. Противники же такой точки зрения считают, что так как Си и C++ являются двумя различными языками, то и совместимость между ними не так важна, хоть и полезна. Согласно этому лагерю, усилия по уменьшению несовместимости между ними не должны препятствовать попыткам улучшения каждого языка в отдельности.
Вот различия между этими языками, существующие на сегодня:
inline — подставляемые функции существуют в глобальном пространстве С++, а в Си — в пространстве файла (статическом пространстве). Другими словами, это значит, что в С++ любое определение подставляемой функции (независимо от переопределения функций) должно соответствовать правилу одного определения, требующего того, чтобы любая подставляемая функция была определена только один раз. В Си же одна и та же подставляемая функция может быть определена по-разному в разных компилируемых файлах одной программы.
В отличие от C++, ключевое слово bool в С99 требует включения соответствующего заголовочного файла stdbool.h. В стандарте C99 определен собственный тип логических данных _Bool. Предыдущий стандарт Си (C89) не определял булевый тип вообще, поэтому для этого часто использовались различные (а значит, несовместимые) методы.
Символьные константы (заключённые в одинарные кавычки) имеют тип int в Си и тип char в C++. Поэтому в Си справедливо равенство sizeof('a') == sizeof(int), а в C++ — равенство sizeof('a') == sizeof(char).[1]
Некоторые новые возможности C99, в первую очередь, restrict, не включены в стандарт Си++.
Си перенял от C++ ряд особенностей:
прототипы объявления функций;
однострочные комментарии, начинающиеся на // и заканчивающиеся символом перевода строки;
более сильную проверку типов, включая добавление типа void, спецификатора const и удаление принятия по умолчанию типа int в качестве возвращаемого значения.
поскольку С и С++ будут использоваться одними и теми же людьми на одних и тех же системах многие годы, различия между языками должны быть либо минимальными, либо максимальными, чтобы свести к минимуму количество ошибок и недоразумений. Описание С++ было переработано так, чтобы гарантировать, что любая допустимая в обоих языках конструкция означала в них одно и то же.
Как язык, так и стандартные библиотеки С++ проектировались в расчете на переносимость. Имеющиеся реализации языка будут работать в большинстве систем, поддерживающих С. В программах на С++ можно использовать библиотеки С. Большинство служебных программ, рассчитанных на С, можно использовать и в С++.
Язык С сам развивался в последние несколько лет, что отчасти было связано с разработкой С++ [14]. Стандарт ANSI для С [27] содержит, например, синтаксис описания функций, позаимствованный из языка "С с классами". Происходит взаимное заимствование, например, тип указателя void* был придуман для ANSI С, а впервые реализован в С++. Как было обещано в первом издании этой книги, описание С++ было доработано, чтобы исключить неоправданные расхождения. Теперь С++ более совместим с языком С, чем это было вначале ($$R.18). В идеале С++ должен максимально приближаться к ANSI C, но не более [9]. Стопроцентной совместимости никогда не было и не будет, поскольку это нарушит надежность типов и согласованность использования встроенных и пользовательских типов, а эти свойства всегда были одними из главных для С++.
Для изучения С++ не обязательно знать С. Программирование на С способствует усвоению приемов и даже трюков, которые при программировании на С++ становятся просто ненужными. Например, явное преобразование типа (приведение), в С++ нужно гораздо реже, чем в С (см. "Замечания для программистов на С" ниже). Тем не менее, хорошие программы на языке С по сути являются программами на С++. Например, все программы из классического описания С [8] являются программами на С++. В процессе изучения С++ будет полезен опыт работы с любым языком со статическими типами.
Замечание для программистов на С
Чем лучше программист знает С, тем труднее будет для него при программировании на С++ отойти от стиля программирования на С. Так он теряет потенциальные преимущества С++.
Но гораздо важнее стараться думать о программе как о множестве взаимосвязанных понятий, представляемых классами и объектами, чем представлять ее как сумму структур данных и функций, что-то делающих с этими данными.
83.
Три слоя Си: ядро, препроцессор и функциональное программирование. В отличие от Паскаля, Модулы или Ады, которые были созданы автором (или авторским коллективом) в практически законченном виде, Си создавался скорее стихийно. Достаточно проанализировать Си в его современном виде, чтобы выделить первоначальное “ядро” языка и как минимум два крупных “слоя”, добавленных позднее. Скорее всего, первый Си включал в себя современный набор операторов языка и простейшие средства описания типов данных и переменных; и даже не имел средств для задания констант. Когда Си стал применяться для решения серьезных задач, к нему добавили так называемые “директивы препроцессора”, такие, как #define и #include. #define был призван решить проблему задания констант, но позднее стал применяться и для других целей (например, inline-процедур). Директива #include стала примитивным (и единственным на сегодняшний день) средством импорта модулей. Наконец, есть третий “слой” Си, на который редко обращают внимание. В момент создания языка в США была очень популярна концепция “функционального программирования”, основным языком которого был (и остается по сей день) Лисп. Несмотря на явную несхожесть этих языков, влияние функционального программирования на Си несомненно. Положение о том, что каждый оператор, каждая операция и любая функция должны выдавать какой-либо результат, правила ассоциативности операций, другие особенности, присущие языкам “функционального программирования”, сделали Си источником различных программистских “трюков” (например, на Си можно написать цикл while, в теле которого не будет ни одного оператора, но он тем не менее будет выполнять некоторую полезную работу).
Плюсы и минусы. “Стихийное” формирование языка послужило источником как положительных, так и отрицательных его качеств. В “актив” Си, безусловно, следует записать набор операторов языка. Компактные, мощные и красиво сделанные конструкции позволяют программисту кодировать алгоритмы любой сложности. Другим приятным качеством Си можно считать механизм работы со строками символов. В то же время описания типов (особенно указателей и структур) нельзя назвать идеальными. Применение директивы #include для импорта идентификаторов приводит к неоправданному расходу времени при компиляции (особенно заметному в современных ОС, содержащих тысячи стандартных функций API). Отсутствие всеобъемлющего контроля типов (строгой типизации) и, особенно, типа boolean, приводит к сложно обнаруживаемым ошибкам, которые в других языках были бы выявлены еще на этапе компиляции.
Си++ и Ява. В языках программирования, созданных на основе Си, некоторые из его недостатков были устранены. Так, в Си++ для замены директивы #define введены конструкции const (для объявления констант) и inline (для описания inline-функций). Добавлен тип boolean и контроль типов в операторе присваивании и при вызове функций. Введен тип перечисления enum и параметры-ссылки (аналоги параметров var в Паскале и Модуле). Создатели Явы пошли еще дальше. Они полностью переделали конструктор типов данных, ввели строгий контроль типов и вместо импорта модулей (#include) стали использовать импорт классов. Можно сказать, что Ява является Си-клоном только внешне. По идеологии это типичный представитель европейской школы программирования.
84.,87.
Наследование, инкапсуляция и полиморфизм, иерархия классов.
Все языки OOP, включая С++, основаны на трёх основополагающих концепциях, называемых инкапсуляцией, полиморфизмом и наследованием. Рассмотрим эти концепции.
Дата добавления: 2015-07-26; просмотров: 93 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Структуры | | | Полиморфизм |