Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Технологии программирования.-3

.pdf
Скачиваний:
7
Добавлен:
05.02.2023
Размер:
1.99 Mб
Скачать

170

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

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

*/

#endif #ifdef VAR8

//Построение таблицы функций enum {Func_X,Func_Y,Func_Z}; typedef struct A {

int x;

void *func[3]; } AF;

int X() { cout<<"Привет\n"; return 1;} void Y(int i) { cout<<"Здорово "<<i<<"\n";}

void Z(int i,char *s ) { cout<<"Добрый день "<<i<<" "<<s<<"\n";} /***************************************************************************

/

void main()

{

AF v;

//заполнение таблицы v.func[Func_X]=(void *)X; v.func[Func_Y]=(void *)Y; v.func[Func_Z]=(void *)Z;

cout<<"***********************\n";

//вызовы функций через соотвествующие элементы таблицы

((int (*)())(v.func[Func_X]))(); ((void (*)(int))(v.func[Func_Y]))(100);

((void (*)(int,char *))(v.func[Func_Z]))(100,"Козлик");

}

#endif

4.1 Объектно-ориентированное программирование на С++

4.1.1 Простейший ввод/вывод

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

171

1)cin — поток для ввода информации с клавиатуры;

2)cout — поток для вывода информации на терминал.

Как организовать ввод/вывод с помощью стандартных потоков:

1)подключить заголовочный файл iosteam.h;

2)записать поток для ввода, используя операцию «>>»;

3)записать поток для вывода информации, используя операцию

«<<».

Например,

#include <iostream.h> void main()

{

int i, char s[80];

cin>>i; //Ввод целого и присвоение в качестве значения для i cin>>s; //Ввод строки символов и присвоение переменной s //Можно записать вместе

cin>>i,s;

//Для вывода информации необходимо использовать cout cout<<i; //Вывод целого значения, которое хранится в i cout<<s; //Вывод строки символов

//можно вместе cout<<i<<s; //можно и так

cout<<"значение i ="<<i<<" значение s ="<<s<<"\n";

}

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

вразделе ...

4.1.2Понятие ссылки

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

Простые ссылки

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

172

int i; //создаем объект int c именем i

int &iref=i; //создаем ссылку на объект с именем i, это просто еще одно имя объекта

iref=3; //это тоже что и i=3;

Очевидно, что &i и &iref имеют одно и тоже значение, поскольку это адрес одного и того же объекта.

Аргументы ссылки

Ссылки могут быть объявлены как параметры функции:

void Func1(int i)

void Func2(int& iref) // объявление параметра как ссылки

...

int sum=5;

Func1(sum); //передача параметра по значению Func2(sum); // передача параметра по ссылке.

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

int Trip1(int n) // передача и возврат значений

{

return 3*n;

}

...

int x,i=5;

x=Trip1(i); // теперь x=15 i=5 без изменения

...

void Trip2(int *nptr) //передача адреса объекта через указатель

{

*nptr=(*nptr)*3;

}

...

Trip2(&i); //передаем адрес объекта i, после выполнения i=15;

void Trip3(int &nref)//передача по ссылке

{

nref=nref*3;

}

173

...

Trip3(i); //После выполнения i=45;

Передача параметров копированием является предпочтительным вариантом ( см описание функции Trip1). Однако в случае достаточно больших по размеру объектов такой способ передачи становится неэффективным. Для этого использовали передачу адреса объекта через параметр типа указатель, однако, этот метод очень громоздкий с точки зрения написания и читабельности программы (см. описание функции Trip2). Передача параметров через ссылки для таких случаев наиболее проста и понятна.

Преобразование типа при создании ссылки

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

int& iref=5; //создается временный объект типа int, которому будет присвоено значение 5

double x;

int& ix=x; //создается временный объект типа int, которому будет присвоено значение 5

double x=10.5;

int& ix=x; //создается новый объект типа int, значение x преобразуется в целое (10)

//и присваивается этому временному объекту cout<<ix<<"\n";

ix=5.1; //происходит преобразование в целое и присвоение объекту типа int, а

//значение x не изменится cout<<ix<<" "<<x<<"\n";

Аналогично это касается и передачи параметров. Например,

void Doub(int &n) { n=n*2; }

...

float x=8; Doub(x); cout<<x;

174

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

Ссылки на структуры

struct S { int i; char s[10]; };

void Show(S& z) { cout<<z.i<<" "<<z.s<<"\n";

...

S y={10, "десять" }; Show(y);

В этом случае внутри функции ссылка воспринимается как обычная структура.

Ссылки на указатели

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

int *p;

int* &pref=p; //организация ссылки на указатель

//далее pref можно использовать как указатель

int j=9; p=&j;

cout<<*pref<<"\n"; //будет выведено значение 9

Ссылки на указатели можно организовать и как параметры функций. Например,

struct Zs { int x; char *ptrs;};

void func(Zs* &pref) //параметр pref ссылка на указатель типа Zs

{

cout<<pref->x<<" "<<pref->ptrs<<"\n";

}

...

Zs w, *pw;

... //присваиваются значения w и pw func(&w);

func(pw);

175

Надо помнить следующие отличия.

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

Возврат ссылок из функций

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

struct B { int x; double dx; };

B a; //создание статического объекта

B& func() { return a; } //функция возвращает ссылку на объект а

....

func().x=10; //присвоение значения a.x func().dx=0.55;//присвоение значения a.dx

....

cout<<func().x+2<<" "<<2*func().dx<<"\n";

....

Результатом работы этого фрагмента программы будет: 12 1.10

Возврат ссылок можно организовать на объекты, которые сохраняются после завершения работы функции, а именно:

1)на статические объекты;

2)на динамические объекты;

3)на параметры, которые переданы в функцию как ссылки или ука-

затели.

Рассмотрим возврат ссылок на динамические объекты (этот раздел требует знаний операторов new и delete).

struct Pair { int x; int y; }; Pair& Get()

{

Pair *ptr;

ptr=new Pair; //выделяем память под объект

return *ptr; //возвращаем ссылку на новый объект типа Pair

}

....

Pair& pair=Get(); Pair* ptr=&Get();

Get().x=20; //так делать нельзя, теряется память выделенная под объект

176

Get().y=30; //здесь тоже

....

delete &pair; delete ptr;

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

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

stream& Show(stream& out, ...)

{

...

return out;

}

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

4.1.3 Операторы new и delete

Динамическое распределение памяти является эффективным инструментом программирования, позволяющее конструировать объекты во время выполнения программы. Причем время когда создавать и когда удалять эти объекты решает сам программист. В Си с этой задачей функции, описанные в заголовочном файле alloc.h, например, для выделения динамической памяти функция malloc(size_t n), для освобождения free(). В С++ для этих целей ввели специальные операторы:

1)new — выделяет память по объект;

2)delete — освобождает память занимаемую объектом. Рассмотрим примеры:

//пример №1 int *ptri;

177

ptri=new int; //выделить память под целое и адрес блока памяти присвоить указателю ptri;

.... //операторы, где используется указатель ptri delete ptri; //освобождение памяти.

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

//пример №2

struct Book { double price; char *name; char *author;};

Book *best; best=new Book;

...// Использование best delete best;

Здесь показан пример распределения памяти под объект типа структура. Что нужно отметить, что оператор new автоматически определяет размер необходимой памяти под объект. В данном примере память будет выделена под переменную типа double и два указателя типа char.

Память динамически можно выделять и для ссылок. Например,

//Пример №3

long &lref=*new long; lref=20L;

...//операторы где используется lref cout<<lref<<"\n";

delete &lref; //удаление объекта

Можно также рассмотреть пример из предыдущего раздела (см. функцию Get())

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

Например,

int ivec[]=new int[20]; //выделение памяти под массив из 20 целых чисел. for(int i=0; i<20; i++) ivec[i]=i;

....

delete []ivec;

178

При удалении памяти, выделенной с явным указанием количества элементов в операторе new, в операторе delete необходимо записать квадратные скобки. См. оператор delete для вектора ivec. Все это и касается типа char. Например,

char *ptrs=new char[20]; strcpy(ptrs,"Привет козлик"); cout<<ptrs<<"\n";

...

delete []ptrs;

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

В С++ имеется возможность переопределять операторы new и delete для отдельных классов, а также и для всех типов данных разрабатываемой программы.

С помощью оператора new можно распределять память по многомерные массивы

Например,

int n=10;

double (*matrix)[6]; //указатель на вектор размеров 6 элементов matrix=new double[n][6]; //выделение памяти под матрицу matrix[0][0]=1.; //операторы для работы с матрицей

delete []matrix; //удаление памяти под матрицей

int k=2;

double (*tensor)[5][10]; //указатель на матрицу размером [5][10] элементов matrix=new double[k][5][10]; //выделение памяти под тензор tensor[0][0][0]=1.; //операторы для работы с тензором

delete []tensor; //удаление памяти занимаемый тензором

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

...).

179

Новые возможности функций в C++

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

void Show(int i) { cout<<i; }

void Show(int i,char *s) { cout<<i<<s; } void Show(double f) { cout<<f; }

void Show() { cout<<"Привет"; }

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

Show(int i) внутренне имя будет Show_int, для остальных соответственно

Show_int_char

Show_double Show

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

Для функций-членов классов правила несколько сложнее и будут рассмотрены ниже

Значение параметров по умолчанию.

4.1.4 Операция разрешения видимости ::

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

Например,

void X::Show() { ... } //функция принадлежит классу X void V::Show() { ... } //функция принадлежит классу V

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

j=X::k; //это выражение должно в функции члене класса X или в наследуемом классе

z=X::Show(); // аналогично