Добавил:
t.me Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
2 семестр / Вопросы к экзамену. Моё.docx
Скачиваний:
5
Добавлен:
16.07.2023
Размер:
58.39 Кб
Скачать

Int main() {

k1 n;

n.set_ab(3,4);

cout << sum(n);

return 0;

}

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

Глобальная дружественная функция - дружественная функция, описание которой находится вне дружественных ей классов.

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

Описание глобальной дружественной функции: <тип> <имя функции>(<имя дружественного класса> <имя объекта>);

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

friend <тип> <имя функции>(<имя дружественного класса> <имя объекта>);

Пример:

#include <iostream>

using namespace std;

class primer2;//объявление класса primer2, чтобы primer1 видел этот класс

class primer1

{

int x;

public:

primer1(int a)//конструктор

{

x = a;

}

friend int sum(primer1 ob1, primer2 ob2);

};

class primer2

{

int y;

public:

primer2(int b)//конструктор

{

y = b;

}

friend int sum(primer1 ob1, primer2 ob2);

};

int sum(primer1 ob1, primer2 ob2)

{

int s;

s = ob1.x + ob2.y;

return s;

}

Int main() {

primer1 ob1(7);

primer2 ob2(8);

int t;

t = sum(ob1, ob2);//обращение к дружественной функции

cout << t;//будет выведено число 15

}

11. Дружественные классы, определение и пример.

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

class ClassB;

class ClassA {

int numA;

friend class ClassB;

public:

ClassA() : numA(12) {}

};

class ClassB {

int numB;

public:

ClassB() : numB(1) {}

int add() {

ClassA objectA;

return objectA.numA + numB;

}

};

Int main() {

ClassB objectB;

cout << "Sum: " << objectB.add();

return 0;

}

12. Перегрузка функций, назначение и примеры.

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

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

Пример:

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

void vivod(float A[], int n)

{

int i;

for (i=0;i<n;i++)

{

cout<<A[i];

}

}

void vivod(int a, int b)

{

cout<<a;

cout<<b;

}

Информация из 2 семестра:

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

класса с тем же именем.

Количество и типы аргументов значения не имеют.

Для вызова метода родительского класса из метода класса наследника используется метод <имя базового класса>::<имя вызываемого метода базового класса>.

#include <iostream>

using namespace std;

class myclass {

int a; int b;

public:

myclass(int x = 0, int y = 0) {

a = x; b = y;

}

void print() {

cout << a << " " << b;

}

};

class myclass1: public myclass {

int c;

public:

myclass1(int x, int y, int z) : myclass(x, y), c(z) {}

void print() {

myclass::print();

cout << " " << c;

}

};

int main(void) {

myclass1 a1(4, 1, 6);

a1.print(); // 4 1 6

cout << endl;

a1.myclass::print(); // 4 1 (вызов базового метода для объекта наследника)

return 0;

}

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

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

Правила перегрузки операций:

1. Множество операций не может быть шире, чем множество стандартных

операций С++

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

3. Если стандартная операция одноместная, нельзя при перегрузке сделать ее двуместной. Например ++ одноместная, а + двуместная.

4. Не все операции можно перегружать. Нельзя перегружать . :: ?: sizeof

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

6. Для перегрузки операций используется ключевое слово operator

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

8. Операция = присваивания всегда должна быть реализована как метод класса

Назначение: обеспечить такие же краткие выражения для пользовательских типов, как для встроенных типов.

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

Перегрузка операций ( ) [ ] -> = реализуется функциями перегрузки операций как методами класса. Для других операций функциями перегрузки операций могут быть и методы, и дружественные функции.

Перегрузка сложения для complex:

complex& operator+(complex& x) {

re=re+x.re;

im=im+x.im; return(*this);//возвращает измененный объект

}

Перегрузка присваивания для complex:

complex& operator=(complex& x) {

re=x.re;

im=x.im;

return(*this);

}

Перегрузка индексных скобок для matr:

int* operator[](int i) {

return base[i];

}

Перегрузка индексных скобок для vect:

int& operator[](int i) {

return base[i];

}

Перегрузка присваивания для vect:

vect& operator=(const vect & x) {

for(int i=0;i<size;i++) {

base[i]=x.base[i];

}

return(*this);

}

Перегрузка сложения для vect:

vect operator+(const vect & x) {

vect sum(size);

for(int i=0;i<size;i++) {

sum.base[i]=base[i]+x.base[i];

}

return sum;

}

Перегрузка вывода для complex:

friend ostream& operator << (ostream& out,complex& x);

ostream& operator<<(ostream & out, complex& x) {

if(im<0) {

out<<x.re<<x.im<<”i”;

} else { out<<x.re<<”+”<<x.im<<”i”;

}

return out;

}

Перегрузка вывода для complex:

friend istream& operator >> (istream& in,complex& x);

istream& operator>>(istream &in, complex &x) {

cout<<”re=”;

in>>x.re;

cout<<”im=”;

in>>x.im;

return in;

}

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

Оператор << ostream& operator << (ostream& out, Complex& a)

Стандартный выходной поток cout имеет тип ostream. Поэтому первый параметр (левый операнд) операции << представляет ссылку на неконстантный объект ostream. Данный объект не должен представлять константу, так как запись в поток изменяет его состояние. Причем параметр представляет именно ссылку, так как нельзя копировать объект класса ostream.

Второй параметр оператора определяется как ссылка на объект класса, который надо вывести в поток.

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

Оператор >> istream& operator >> (istream& in, Complex& a)

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

Перегрузка операций ввода и вывода для пользовательской структуры:

#include <iostream>

using namespace std; struct student {

char fio[20];

char groop[20];

};

ostream& operator <<(ostream& out, student st) // перегрузка вывода {

out << "student" << endl;

out << "name=" << st.fio << endl;

out << "gruppa=" << st.groop << endl;

return out;

}

istream& operator >>(istream& in, student& st) // перегрузка ввода {

cout << "fio="; in >> st.fio;

cout << "gruppa="; in >> st.groop;

return in;

}

int main(void) {

student x;

cin >> x; // использование перегрузок

cout << x;

return 0;

}

Перегрузка операций ввода и вывода для комплексного числа:

#include <math.h>

#include <iostream>

using namespace std;

class Complex {

private:

double im; re;

public:

Complex(double a=0, double b=0) {

re = a; im = b;

}

~Complex() {} friend ostream& operator << (ostream& out, Complex& a); // обозначаем

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

к полям

friend istream& operator >> (istream& in, Complex& a);

};

ostream& operator << (ostream& out, Complex& a) // перегрузка вывода {

if (a.im < 0)

out << a.re << a.im << "i";

else out << a.re << "+" << a.im << "i";

return out;

}

istream& operator >> (istream& in, Complex& a) // перегрузка ввода {

cout << "vvod re "; in >> a.re;

cout << "vvod im "; in >> a.im;

return in;

}

int main(void) {

Complex a;

cin >> a; // использование перегрузок

cout << a;

return 0;

}

15. Наследование. Порожденные классы. Конструкторы базового и порожденного классов. Преимущества наследования.

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

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

2. Упрощение модификации программы

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

4. Внесение изменений в объекты, исходный код которых недоступен, но в которые требуется внести изменения

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

Иерархия наследования – отношения между родительским классом и его потомками.

Реализация класса – совокупность методов и данных класса.

Синтаксис наследования:

class <имя класса-наследника> : <тип наследования> <имя базового класса>

{<тело класса-наследника>};

Порожденные классы:

В наследнике можно описывать новые поля и методы и переопределять существующие методы.

Переопределять методы можно несколькими способами:

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

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

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

Конструкторы:

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

1. Если в конструкторе потомка явный вызов конструктора предка отсутствует, то автоматически вызывается конструктор предка по умолчанию

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

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

Вызов конструктора базового класса происходит до инициализации полей класса наследника.

Преимущества наследования:

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

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

3. Использование полиморфизма упрощает код программы

16. Видимость базового класса в порожденных классах. Использование public, protected, private.

При наследовании порожденный класс имеет прямой доступ к наследуемым членам базового класса с видимостью public и protected. Члены базового класса с видимостью private – недоступны.

Обращение к членам с видимостью private возможно только через методы. При наследовании используется 3 вида ключей доступа (типа наследования) public, protected и private. Они не влияют на доступ к членам базового класса в порожденном, но влияют на доступ к ним в main и в последующем наследовании.

Public

Protected

Private

Public

Public

Protected

Private

Protected

Protected

Protected

Private

Private

----

----

----

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

Множественное наследование.

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

Преимущества:

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

2. Возможность существования нескольких реализаций одного и того же интерфейса.

Объявление множественного наследования:

class nasled3 : public nasled1, nasled2

Проблемы множественного наследования.

Чтобы использовать базовый класс совместно, просто вставьте ключевое слово virtual в список наследования производного класса. Это создаёт так называемый виртуальный базовый класс, что означает, что существует только один базовый объект. Этот базовый объект используется всеми объектами в дереве наследования и создаётся только один раз.

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

18. Виртуальные функции их назначение и определение. Пример

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

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

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

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

Если виртуальный метод переопределён в классе-наследнике, то объекты этого класса могут получить доступ к методу предка с помощью операции доступа к области видимости.

Виртуальный метод не может объявляться с модификатором static, но может быть объявлен как дружественный.

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

#include <iostream>

using namespace std;

class X{

int i;

public:

void seti(int c) { i = c; }

virtual void print() { cout << endl << "class X : " << i; }

};

class Y : public X // наследование{

public:

void print() { cout << endl << "class Y : " << i; } // переопределение базовой функции

};