Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная работа №1.doc
Скачиваний:
7
Добавлен:
13.07.2019
Размер:
1.17 Mб
Скачать

Типы данных.

Определение 1.1. Тип данных — это множество значений и набор операций с ними.

Операции связаны с типами, а не наоборот. При выполнении операции необходимо обеспечить, чтобы ее операнды и результат отвечали определенному типу. Пренебрежение этим правилом — распространенная ошибка программирования. В некоторых случаях C++ выполняет неявное преобразование типов; в других используется приведение (casting), или явное преобразование типов. Например, если х и N целые числа, выражение

((float) х) / N

включает оба типа преобразований: оператор (float) выполняет приведение — величина х преобразуется в значение с плавающей точкой. Затем для N выполняется неявное преобразование, в соответствии с правилами C++, чтобы оба аргумента оператора деления представляли значения с плавающей точкой.

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

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

Описание функций.

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

Все функции имеют список аргументов и, возможно, возвращаемое значение (return value). Рассматриваемая функция Ig имеет один аргумент и возвращаемое значение. И то и другое относится к типу int. Функция main не принимает аргументов и возвращает значение типа int (по умолчанию — значение 0, которое указывает на успешное завершение).

Функция объявляется (declare) путем присвоения ей имени и типов возвращаемых значений. Первая строка программы ссылается на библиотечный файл, который содержит объявления cout, << и endl. Вторая строка объявляет функцию Ig. Если функция описана до ее использования (см. следующий абзац), объявление необязательно. Объявление предоставляет информацию, необходимую, чтобы другие функции вызывали данную с использованием аргументов допустимого типа.

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

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

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

Разграничение описаний и объявлений создает гибкость в организации программ. Например, они могут содержаться в разных файлах (см. текст). Кроме того, в простой программе, подобной примеру 1.1, описание функции Ig можно поместить перед описанием main и опустить объявление.

Программа 1.1.

#include <iostream>

using namespace std;

int lg (int);

int main()

{

for (int N = 1000; N <= 1000000000; N *= 10)

cout << lg(N) << " " << N << endl;

}

int lg(int N)

{

int i;

for (i = 0; N > 0; i++, N /= 2);

return i;

}

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

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

Эта программа вычисляет среднее значение ц и среднеквадратичное отклонение ряда целых чисел x1, …, xn, сгенерированных библиотечной процедурой rand. Ниже приводятся математические формулы:

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

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

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

Программа 1.2.

#include <iostream>

#include <stdlib.h>

#include <math.h>

using namespace std;

typedef int Number;

Number randNum()

{

return rand();

}

int main()

{

int N = 5;

float ml = 0.0, m2 = 0.0;

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

{

Number x = randNum();

ml += ((float) x)/N;

m2 += ((float) x*x)/N;

}

cout << " Avg. : " << ml << endl;

cout << "Std. dev.: " << sqrt (m2-ml*ml) << endl;

}

Этот пример не представляет полного решения задачи разработки программ вычисления средних величин и среднеквадратичных отклонений, не зависящих от типа данных; подобная цель и не ставилась. Например, программа требует преобразования чисел типа Number в тип float, чтобы они могли участвовать в вычислении среднего значения и отклонения. В данном виде программа зависима от приведения к типу float, присущего встроенному типу int. Вообще говоря, можно явно описать такое приведение для любого типа, обозначаемого именем Number. При попытке выполнения каких-либо действий, помимо арифметических, вскоре возникнет необходимость добавления операций к типу данных. Например, может потребоваться распечатка чисел. При любой попытке разработать тип данных, основанный на идентификации важных операций программы, необходимо отыскать компромисс между уровнем обобщения и простотой реализации.

Имеет смысл подробно остановиться на способе изменения типа данных таким образом, чтобы программа обрабатывала другие типы чисел, скажем, float вместо int. Язык C++ предлагает ряд механизмов, позволяющих воспользоваться тем, что ссылки на тип данных локализованы. Для такой небольшой программы проще всего сделать копию файла, затем изменить объявление typedef int Number на typedef float Number и тело процедуры randNum, чтобы оно содержало оператор

return 1.0*rand()/ RAND_MAX

(при этом будут возвращаться случайные числа с плавающей точкой в диапазоне от 0 до 1). Однако даже для такой простой программы этот подход неудобен, поскольку подразумевает наличия двух копий программы. Все последующие изменения программы должны быть отражены в обеих копиях. В C++ существует альтернативное решение — поместить описания typedef и randNum в отдельный файл заголовков (header file) с именем, например, Number.h и заменить их директивой

#include "Number.h"

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

Третий метод рекомендован и широко распространен в программировании на С, C++ и других языках. Он предусматривает разбиение программы на три файла:

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

  • пользуемые для управления этой структурой

  • Реализация функций, объявленных в интерфейсе

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

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

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

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

typedef int Number;

Number randNum();

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

#include <stdlib.h>

#include "Number.h"

Number randNum()

{ return rand();}

Первая строка ссылается на предоставленный системой интерфейс, который описывает функцию rand(). Вторая строка ссылается на реализуемый интерфейс (она включена для проверки того, что реализуемая и объявленная функции имеют одинаковый тип). Две последних строки представляют собой код функции. Этот код можно сохранить в файле, например, int.c. Реальный код функции rand содержится в стандартной библиотеке времени выполнения C++.

Клиентская программа, соответствующая примеру , будет начинаться с дирек-

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

#include <iostream.h>

#include <math.h>

#include "Number.h"

После этих трех строк может следовать описание функции main из программы. Этот код может быть сохранен в файле с именем, например, avg.c.

Результатом совместной компиляции программ avg.c и int.c будут те же функциональные возможности, что и реализуемые программой. Но рассматриваемая реализация более гибкая, поскольку связанный с типом данных код инкапсулирован и может использоваться другими клиентскими программами, а также потому, что программа avg.c без изменений может использоваться с другими типами данных. По-прежнему предполагается, что любой тип, используемый под именем Number, преобразуется в тип float. C++ позволяет описывать это преобразование, а также описывать желаемые встроенные операторы (подобные += и <<) как часть нового типа данных.

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

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

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