Инструменты пользователя

Инструменты сайта


ооп

Содержание

Введение

Cуществует два основных подхода к программированию:

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

Объектно-ориентированное программирование - подход, при котором функции и переменные, относящиеся к какому-то конкретному объекту объединены в коде определенным образом и тесно связаны между собой. (Язык С++).

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

ООП держится на трёх основных принципах:

1.Инкапсуляция.(Encapsulation)-Закрытие данных внутри класса.
Принцип независимости данных в ООП называется инкапсуляцией. Таким образом, каждая часть может содержать собственные данные, недоступные другим частям системы. Очевидно, что абсолютно независимыми эти части быть не могут, поскольку им необходимо взаимодействовать между собой, использовать общие данные и обмениваться собственными данными.

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

Объект - это некоторая уникальная единица имеющая свои переменные и функции, эти переменные обрабатывающие. Класс - Пользоватильский тип данных обьядиняющие данные и методы их оброботки.

Итак, инкапсуляция - механизм, с помощью которого все свойства и методы, относящиеся к одному определенному объекту собраны вместе под общим именем (типом), который мы будем называть - классом.

2.Наследование.(Inheritance)-Унаследывать часть методов предка.
Наследование - это процесс, с помощью которого, один объект может наследовать свойства и методы другого объекта в дополнение к своим свойствам и методам.

Предположим, на основе черного ящика мы собираемся создать красочную упаковку (коробку) для подарка. Для этого мы просто добавим к черному ящику те особенности, которые характерны для подарочной упаковки. Например, цвет. Такие характеристики, как форма и размер, а также возможность открываться и закрываться имеются и у ящика и у коробки. Поэтому, не имеет смысл описывать их повторно, достаточно просто отнаследовать «коробку» от «ящика». В этом и заключается главное преимущество использования механизма наследования. Мы сначала создаем некую простую конструкцию, а затем добавляя к ней новые свойства и методы, получаем новый усовершенствованный объект.

3.Полиморфизм.(Polymorphism)-Поведение обекта в зависемости от ситуации.
Полиморфизм - способность объекта вести себя по-разному, в зависимости от ситуации и реагировать на определенное действие строго специфичным для себя образом.

Банальная ситуация - вы приходите в магазин, что бы купить колбасу. Выбираете продукт. Если продавец - ваш знакомый, он подскажет вам - стоит или нет брать данный товар. Если - абсолютно чужой человек, равнодушно отрежет кусок. Продавец ведет себя так или иначе, в зависимости от ситуации. Так и наш с вами объект - самостоятельный тип данных будет различным образом реагировать на внешние раздражители. Сейчас вам немного сложно понять, как это будет происходить. Однако с принципом полиморфизма, вы уже знакомы, вспомните - прегруженные функции. Функция в зависимости, от переданных в нее параметров вызывала ту, или иную свою версию.

Знакомство с классами

Класс - это производный структурированный тип, введенный программистом на основе уже существующих типов.

Подобно структуре, класс C++ должен иметь уникальное имя, за которым следует открывающая фигурная скобка, один или несколько элементов и закрывающая фигурная скобка:

class имя_класса { список_компонентов };
 
class CTest
{ 
   int data; // Элемент данных 
   void member(int); // Функция-элемент 
};

Итак, принадлежащие классу функции мы будем называть методами класса или компонентными функциями. Данные класса - компонентными данными или элементами данных класса.

После определения класса можно объявлять переменные типа этого класса (называемые объектами), определим синтаксис создания объекта класса:

void main ()
{
	имя_класса имя_объекта;
 
	CTest test_1,test_2;
}

Способы доступа к компонентам класса

Существует несколько уровней доступа к компонентам класса. Рассмотрим два основных.

public - члены класса открыты для доступа извне.

private - члены класса закрыты для доступа извне. protected -защещеные используется когда есть носледование

По умолчанию все переменные и функции, принадлежащие классу, определены как закрытые (private). Это означает, что они могут использоваться только внутри функций-членов самого класса. Для других частей программы, таких как функция main(), доступ к закрытым членам запрещен. Это, кстати, единственное отличие класса от структуры - в структуре все члены по умолчанию - public.

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

Синтаксис для доступа к данным конкретного объекта заданного класса (как и в случае структур), таков:

имя_объекта.имя_члена класса;
 
test_1.data;

Ниже описанный примеры:

#include <iostream>
using namespace std;
class CTest 
{
public: //члены класса открыты для доступа извне.
	int m_c;
	void Init (int a)// инициализировает переменную в классе 
	{
		m_c=a;
	}
	void Show ()//метод показывающий переменные класса на экран
	{
		cout<<m_c<<endl;
	}
};
void main ()
{
	CTest Test;
	Test.Init(10);
	Test.Show();
}

Доступ к private данным.

#include <iostream>
using namespace std;
class CTest 
{
public:
	void Init (int a)
	{
		m_a=a;
	}
	void Show ()
	{
		cout<<m_a<<endl;
	}
private://члены класса закрытый для доступа извне.
	int m_a; //скрытые данные
};
void main ()
{
	CTest test;
	test.Init(5);
	test.Show();
}

Конструкторы и деструкторы

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

Иногда во время создания объекта его элементам необходимо присвоить начальные значения и для этого нужна использовать конструктор.
Конструктор - Это функция класса вызывающиися автоматически в момент создание объекта (может быть несколько).

  1. Конструктор автоматически вызывается при создании объекта, т.е не нужно специально его вызывать.
  2. Основное назначение конструкторов - инициализация объектов.
  3. Конструктор не имеет возвращаемого типа даже void
#include <iostream>
using namespace std;
class CTest 
{
public:
	CTest(int a=0)//конструктор класса 
	{
		m_a=a;//задаем начальное значение
	}
	void Show ()
	{
		cout<<m_a<<endl;
	}
private://члены класса закрытый для доступа извне.
	int m_a; //скрытые данные
};
void main ()
{
	CTest test,test1(33);
	test.Show();// Будет 0
	test1.Show();//Будет 33
}

Деструкторы

Деструктор выполняет функцию, противоположную функции конструктора. Деструктор (destruct - разрушать) - это специальная функция класса, которая автоматически вызывается при уничтожении объекта - например, когда объект выходит из области видимости.
Деструктор может выполнять любые задачи, в момент удаления объекта. Например, если в конструкторе было выделена динамическая память, то деструктор должен освободить эту память перед удалением объкта класса.

  1. Деструктор не принимает никаких параметров и не возвращает никаких значений.
  2. Класс может иметь только один деструктор.
# include <iostream>
using namespace std;
// описание класса CreateAndDestroy
class CreateAndDestroy
{
public: 
	CreateAndDestroy(int value)  // конструктор 
	{
	  data = value;
	  cout << " Object " << data << " constructor";
	}
	~CreateAndDestroy()    // деструктор
	{
		cout << " Object " << data << " destructor" << endl;
	}
private:
	int data;
};
 
void main ()
{
 
	CreateAndDestroy one(1); 
	CreateAndDestroy two(2);  
}
РЕЗУЛЬТАТ РАБОТЫ ПРОГРАММЫ
 
Object 1 constructor    
Object 2 constructor    
Object 2 destructor     
Object 1 destructor 

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

Перегруженные конструкторы

Для этого просто нужно добавить эти параметры в объявление и определение конструктора, а затем, при создании объекта, задать их в качестве аргументов. Теперь к нашим знаниям добавим еще одно - конструкторов может быть несколько. Рассмотрим пример:

#include <iostream>
#include <windows.h>
using namespace std;
class CTest 
{
public:
	CTest();
	CTest(int tz,int tx,int tc);
private:
	int z,x,c;
};
	//Конструктор класса CTest без параметров
CTest::CTest ()
	{
		z=x=c=0;
		cout<<"Конструктор без параметров";
	}
	//конструктор класса CTest с параметрами
CTest::CTest (int tz,int tx,int tc)
	{
		z=tz;
		x=tx;
		c=tc;
		cout<<"Конструктор с параметрами";
	}
void main ()
{
	SetConsoleOutputCP(1251);
	SetConsoleCP(1251);
	CTest A;//без параметров
	cout<<endl;
	CTest B(1,2,3);
	cout<<endl;//с параметрами
}

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

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

Указатели на объекты

До сих пор доступ к членам объекта осуществлялся, c использованием операции .(точка) это правильно, если вы работаете с объектом.
Однако доступ к членам объекта можно осуществлять и через указатель на объект. В этом случае обычно применяется операция стрелка →
Указатель на объект объявляется точно так же, как и указатель на переменную любого типа. А, для получения адреса объекта, перед ним необходим оператор &.

#include <iostream>
#include <windows.h>
using namespace std;
class CTest 
{
public:
	CTest();
	CTest(int tz,int tx,int tc);
	void Show ()
	{
		cout<<z<<" "<<x<<" "<<c<<endl;
	}
private:
	int z,x,c;
};
	//Конструктор класса CTest без параметров
CTest::CTest ()
	{
		z=x=c=0;
		cout<<"Конструктор без параметров";
	}
	//конструктор класса CTest с параметрами
CTest::CTest (int tz,int tx,int tc)
	{
		z=tz;
		x=tx;
		c=tc;
		cout<<"Конструктор с параметрами";
	}
void main ()
{
	SetConsoleOutputCP(1251);
	SetConsoleCP(1251);
	CTest A(1,2,3);//конструктор с параметром
	cout<<endl;
	//создаем указатель на объект типа CTest и в этот указатель записывается адрес объекта А
	CTest*Ptr=&A;
	//через указатель вызывается функция Show()
	Ptr->Show();
}

Динамическое выделение памяти под объект

# include <iostream>
using namespace std;
 
class Point 
{
 	double x, y;
public:
	Point()
	{
		x=y=0;
	}
 	void Show()
	{
		cout<<x<<" "<<y<<"\n";
	}
};
 
void main()
{
	Point *Ptr;
	Ptr=new Point[10];
 
	// проверка, выделилась ли память
	// и выход, если не выделилась
	if(!Ptr) exit(0);
 
	for(int i=0;i<10;i++)
	{
		Ptr[i].Show();		
	}
	// Удаление массива Рtr
	delete[]Ptr;	
 
}

Статические массивы

В отличие от динамики, при создании статического массива параметры в конструктор передать можно. Рассмотрим синтаксис этого действия на примере:

# include <iostream>
using namespace std;
 
class Point 
{
	double x, y;
public:
	//конструктор с параметрами
	Point(double iX,double iY){
		x=iX;
		y=iY;
	}
	void Show(){
		cout<<x<<" "<<y<<"\n";
	}
};
 
void main()
{
	// создание массива объектов
	// передача параметров в конструктор
	Point AR[2]={Point(2,3),Point(4,5)};
 
	// Вызов функции Show() для каждого элемента
	// массива AR 
	for(int i=0;i<2;i++){
		AR[i].Show();		
	}
}

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

Конструктор копирования

Позволяет создавать новый экземпляр класса на основе уже существующего.

Любой конструктор копирования имеет следующую форму:

имя_класса (const имя_класса & obj)
{
 ... // тело конструктора
}

Константный метод

Метод объекта обладает свойством неизменности (константности), если после его выполнения состояние объекта не изменяется. Если не контролировать свойство неизменности, то его обеспечение будет целиком зависеть от квалификации программиста.
Язык С++ позволяет пометить метод как константный.При этом неконстантные методы объекта запрещается использовать в теле помеченного метода, и в контексте этого метода ссылки на сам объект и все его поля будут константны. Для обозначения константности, используется модификатор const.

#include <iostream>
#include <windows.h>
using namespace std;
class CTest 
{
private:
	int ma;
public:
	CTest (int a)
	{
		ma=a;
	}
	void Input(int a)
	{
		ma=a;	
	}
	//Cтандартный метод
	void Show ()
	{
		cout<<ma<<endl;
	}
	//Константный метод
	void Show ()const
	{
		cout<<ma<<endl;
	}
};
void main ()
{
	//Cтандартный объект класс
	CTest t1(11);
	t1.Input(22);
	t1.Show();
	//Константный объект класс
	const CTest t2(44);
	t2.Show();
}	

Ключевое слово mutable

Иногда есть необходимость изменить некий объект внутри класса, гарантируя неприкосновенность остальных элементов. Неприкосновенность можно гарантировать при помощи const, однако const запрещает изменение всего.
Помочь в данном случае может определение переменной а с ключевым словом mutable.

#include <iostream>
#include <windows.h>
using namespace std;
class CTest 
{
private:
	int ma;
	mutable int mb; //Мутированная переменная 
public:
	//Cтандартный метод
	CTest (int a)
	{
		ma=a;
	}
	void Input(int a)
	{
		ma=a;	
	}
	void Show ()
	{
		cout<<ma<<endl;
	}
 
	//константный метод
	//С помощью мутированной переменой mb меняем значении
	void Input(int b)const 
	{
		mb=b;
	}
 
	void Show ()const
	{
		cout<<ma<<" "<<mb<<endl;;
	}
};
void main ()
{
	//Cтандартный объект класс
	CTest t1(11);
	t1.Input(22);
	t1.Show();
	//Константный объект класс
	const CTest t2(44);
	t2.Input(22);//мутировоноя переменая mb
	t2.Show();
}	

Перегрузка операторов

При перегрузке оператора используйте ключевое слово C++ operator вместе с прототипом и определением функции, чтобы сообщить компилятору C++, что класс будет использовать этот метод как оператор.

#include <iostream>
#include <windows.h>
using namespace std;
class CTest 
{
private:
	int dig;
public:
	CTest ()
	{
		dig=0;
	}
	CTest (int dig)//конструктор
	{
		this->dig=dig;//инициализируем
	}
	void Show ()
	{
		cout<<dig;
	}
	//простой метод
	CTest operator+(CTest &n)
	{
		CTest temp;
		temp.dig=dig+n.dig;
		return temp;
	}
	//константный метод
	CTest operator-(const CTest &n)
	{
		CTest temp;
		temp.dig=dig-n.dig;
		return temp;
	}
};
void main ()
{
	SetConsoleOutputCP(1251);
	SetConsoleCP(1251);
	CTest A(20),B(10),C;
	A.Show();//Выводи содиржимое Обекта А
	cout<<"+";
	B.Show();//Выводи содиржимое Обекта B
	cout<<"=";
	C=A+B;
	//C=A*B; в данном случае будет ошибка так как не было создан метод с оператором *
	C.Show();
}

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

Условно, все преобразования типов можно разделить на четыре основные группы:

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

#include <iostream>
using namespace std;
class CTest 
{
private:
	int dig;
public:
	CTest (int dig)//конструктор
	{
		this->dig=dig;//инициализируем
	}
	void Show ()
	{
		cout<<dig<<endl;
	}
};
void main ()
{
	//Преобразование от int CTest
	CTest A(5);
	A.Show();
	//Преобразование от double CTest
	CTest B(3.7);
	B.Show();
}

Исходя из примера можно сделать вывод, что конструктор с одним аргументом Class::Class(type) всегда определяет преобразование типа type к типу Class, а не только способ создания объекта при явном обращении к нему.

Абстрактный к стандартному
Абстрактный к абстрактному

Для преобразования абстрактного типа к стандартному или абстрактного к абстрактному в С++ существует средство - функция, выполняющая преобразование типов, или оператор-функция преобразования типов. Она имеет следующий синтаксис:

Class::operator type (void); 

Эта функция выполняет определенное пользователем преобразование типа Class к типу type. Эта функция должна быть членом класса Class и не иметь аргументов. Кроме того, в ее объявлении не указывается тип возвращаемого значения. Обращение к этой функции может быть как явным, так и неявным. Для выполнения явного преобразования можно использовать как традиционную, так и «функциональную» форму.

#include <iostream>
#include <Windows.h>
using namespace std;
class Number 
{
private:
	int num;
public:
	Number (int num)
	{
		this->num=num;
	}
	void Show ()
	{
		cout<<num<<endl;
	}
};
class CTest 
{
private:
	int dig;
public:
	CTest(int dig)
	{
		this->dig=dig;
	}
	void Show()
	{
		cout<<dig<<endl;
	}
	//преобразование от CTest к int
	operator int ()
	{
		return dig;
	}
	operator Number()
	{
		return Number(dig);
	}
};
void main ()
{
	SetConsoleOutputCP(1251);
	SetConsoleCP(1251);
 
	//преобразование от CTest к int
	CTest A(20);
	int a=A;
	cout<<a<<endl;
 
	//преобразование от CTest к Number
	Number B(0);
	B=A;
	B.Show();
}