C/C++ · 9 min read · Oct 10, 2025

Изучение C/C++ шаг за шагом - Страница 16

16. Постепенное изучение C/C++ — Программирование на C++ - Полиморфизм

Полиморфизм

| | 1. Перегрузка функций

  1. Полиморфизм
  2. Виды полиморфизма
  3. Обычные функции-члены, доступные через указатели
  4. Виртуальная функция
  5. Чистая функция
  6. Присваивание и копирующая инициализация
  7. Конструктор КОПИИ
  8. Указатель ‘this’ |

1. Перегрузка функций

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

| | // Пример программы для демонстрации использования перегрузки функций
#include
using namespace std;
void printline()
{
for(int i=0;i<=80; i++) cout << “-“;
}

void printline(int n)
{
for(int i =0 ;i<=n;i++) cout << “-“;
}

void printline(int n,char ch)
{
for(int i=0;i<=n; i++) cout << ch;
}

int main()
{
printline();
printline(5);
printline(10, ‘*’);
return 0;
}
|

Полиморфизм

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

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

| | #include
using namespace std;
class A
{
int x;
public : void show();
};

class B : public A
{
int y;
public : void show();
};

int main()
{
B b;
b.show();
return 0;
} |

Как мы можем использовать функцию-член show( ) для вывода значений объектов обоих классов A и B? Поскольку прототип show( ) одинаков в обоих местах, функция не перегружена, и, следовательно, статическое связывание не применяется. На самом деле компилятор не знает, что делать, и откладывает решение.

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

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

3. Виды полиморфизма

Полиморфизм бывает двух типов:

| 1 | Полиморфизм времени компиляции
Или Раннее связывание
Или Статическое связывание
Или Статическая компоновка полиморфизма. Объект связан с вызовом своей функции во время компиляции. |

| 2 | Полиморфизм времени выполнения
Или позднее связывание
Или Динамическое связывание
Или Динамическая компоновка полиморфизма. Выбор и соответствующая функция осуществляется динамически во время выполнения. |

| | |

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

4. Обычные функции-члены, доступные через указатели

Следующая программа состоит из базового класса

| | / Обычные функции, доступные через указатель /
/ Полиморфизм с классами (без использования ВИРТУАЛЬНОГО полиморфизма /
#include
using namespace std;

class BASE
{
public :
void disp() { cout << “\nВы находитесь в классе BASE “; }
};

class DERIVED1 : public BASE
{
public :
void disp() { cout << “\nВы находитесь в классе DERIVED1”; }
};

class DERIVED2 : public BASE
{
public :
void disp() { cout << “\nВы находитесь в классе DERIVED2”; }
};

int main()
{
DERIVED1 d1;               // Объект производного класса 1
DERIVED2 d2;               // Объект производного класса 2
BASE *b;                      // указатель на базовый класс

b=&d1;                          // Присвоить адрес d1 указателю b
b->disp();                      // вызов disp()
b=&d2;                          // Присвоить адрес d2 указателю b
b->disp();                      // вызов disp()
return 0;
} |

Вышеуказанная программа демонстрирует:

| | • Класс BASE
• Классы DERIVED1, DERIVED2, производные от BASE
• Объекты производных классов (d1,d2)
• Указатель класса BASE b | | | *Вывод Вы находитесь в классе BASE
Вы находитесь в классе BASE |

5. Виртуальная функция

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

| | / Полиморфизм с классами (Виртуальный полиморфизм) /
#include
using namespace std;
class B
{
public :
void show(){ cout << “\nметод Show() класса B “; }
virtual void disp() { cout << “\nметод disp() класса B”; }
};

class D : public B
{
public :
void show(){cout << “\nметод Show() класса D “; }
void disp(){ cout << “\nметод disp() класса D”; }
};

int main()
{
D d1;
d1.show();
d1.disp(); // Член базового класса

B b;
D d;
B *Bptr;
Bptr = &b;
Bptr->show();
Bptr->disp(); // Член базового класса

Bptr=&d;
Bptr->show(); // Члены производного класса
Bptr->disp(); // Член базового класса
return 0;
} | | | Вывод метод Show() класса D
метод disp() класса D |

6. Чистая функция

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

| | #include
using namespace std;
class B
{
public :
void show(){ cout << “\nметод Show() класса B “; }
virtual void disp() = 0; // чистая виртуальная функция
};

class D : public B
{
public :
void show(){cout << “\nметод Show() класса D “; }
void disp(){ cout << “\nметод disp() класса D”; }
};

int main()
{
D d1;
d1.show(); // O/P : Метод Show() класса D
d1.disp(); // O/P : Метод disp() класса D

D d;
B *Bptr;

Bptr=&d;
Bptr->show(); // O/P : Метод Show() класса B
Bptr->disp(); // O/P : Метод disp() класса D
return 0;
} |

Bptr -> show() * является стандартной исполняемой функцией из Base Bptr -> disp() является стандартной исполняемой функцией из Base, но она объявлена как чистая виртуальная функция, поэтому во время выполнения будет вызвана функция disp()* производного класса.

| | / Программа для демонстрации преимущества чистых виртуальных функций /
#include
using namespace std;
enum boolean { false, true };
class NAME
{
protected : char name[20];
public :
void getname()
{ cout << “Введите имя :”; cin >> name; }

void showname()
{ cout << “\nИмя: “<< name; }

boolean virtual isGradeA() = 0; // чистая виртуальная функция
};

class student : public NAME
{
private : float avg;
public :
void getavg()
{
cout << “\nВведите средний балл студента :”;
cin >> avg;
}

boolean isGradeA()
{ return (avg>=80) ? true : false ; }
};

class employee : public NAME
{
private : int sal;
public :
void getsal()
{ cout << “\nВведите зарплату “; cin >> sal; }

boolean isGradeA()
{ return (sal>=10000) ? true : false ; }
};

int main()
{
NAME names[20]; // количество указателей на имя
student
s; // указатель на студента
employee *e; // указатель на сотрудника
int n = 0; // количество имен в списке
char choice;
do{
cout << “Введите Студента или Сотрудника (s/e) “;
cin >> choice;
if(choice==’s’)
{
s = new student; // создать нового студента
s->getname();
s->getavg();
names[n++]=s;
}
else
{
e = new employee; // создать нового сотрудника
e->getname();
e->getsal();
names[n++]=e;
}
cout << “Введите еще (y/n) ?”; // сделать еще
cin >> choice;
} while(choice==’y’);
for(int j=0; j{
names[j]->showname( );
if(names[j]->isGradeA( )==true)
cout << “Он человек 1-го класса”;
}
return 0;
} |

7. Присваивание и копирующая инициализация

Компилятор C++ всегда занят на вашей стороне, выполняя вещи, которые вы не хотите делать. Если вы берете на себя ответственность, он будет полагаться на ваше суждение; в противном случае он будет делать все по-своему. Два важных примера этого процесса - оператор присваивания и конструктор копии.

Вы много раз использовали оператор присваивания, вероятно, не задумываясь об этом. Предположим, что a1 и a2 - это объекты. Если вы не скажете компилятору иначе, оператор:

a2 = a1;            // установить a2 в значение a1

Вызовет копирование данных из a1, член за членом, в a2. Это действие по умолчанию для оператора присваивания, =.

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

alpha a2(a1);    // инициализировать a2 в значение a1

Вызывает аналогичное действие. Компилятор создает новый объект, a2, и копирует данные из a1, член за членом, в a2. Это действие по умолчанию для конструктора копии.

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

Перегрузка оператора присваивания

| | // Перегрузка оператора присваивания ( = )
#include
using namespace std;
class alpha
{
private:
int data;
public:
alpha() { } // конструктор без аргументов
alpha( int d )
{ data = d; } // конструктор с одним аргументом
void display()
{ cout << data; } // Отобразить данные
alpha operator =(alpha & a) // перегруженный оператор =
{
data = a.data; // не сделано автоматически
cout << “\n Вызван оператор присваивания “;
return alpha(data);
}
};

int main()
{
alpha a1(37);
alpha a2;
a2 = a1; // Вызов перегруженного =
cout << “\n a2 = “; a2.display(); // отображение a2

alpha a3 = a2; // не вызывает =
cout << “\n a3 = “; a3.display(); // отображение a3
return 0;
}
| | | Вывод: a2 = 37
a3 = 37 |

8. Конструктор КОПИИ

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

alpha a3(a2);                            // Копирующая инициализация
alpha a3 = a2;                          // копирующая инициализация, альтернативный синтаксис

Оба стиля определения вызывают конструктор копии: то есть, конструктор, который копирует свой аргумент в новый объект. Конструктор копии по умолчанию, который автоматически предоставляется компилятором для каждого объекта, выполняет копирование член за членом. Это похоже на то, что делает оператор присваивания; разница в том, что конструктор копии также создает новый объект.

Следующий пример демонстрирует конструктор копии.

| | #include
using namespace std;
class alpha
{
private :
int data;
public:
alpha( ) { } // конструктор без аргументов
alpha(int d) { data = d; } // конструктор с одним аргументом
alpha(alpha& a) // конструктор копии
{
data = a.data;
cout << “\nВызван конструктор копии”;
}
void display( )
{ cout << data; }
void operator = (alpha& a) // перегруженный оператор =
{
data = a.data;
cout << “\nВызван оператор присваивания”;
}
};

int main()
{
alpha a1( 37 );
alpha a2;

a2 = a1; // вызов перегруженного =
cout << “ a2 = “; a2.display(); // отображение a2

alpha a3( a1 ); // вызов конструктора копии

// alpha a3 = a1; // эквивалентное определение a3

cout << “ a3 = “; a3.display(); // отображение a3
return 0;
} |

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

9. Указатель ‘this’

C++ использует уникальное ключевое слово this для представления объекта, который вызывает функции-члены. This - это указатель, который указывает на объект, для которого была вызвана функция this.

Этот указатель просто выполняет задачу возврата самого объекта.

Следующая программа определяет объекты i, j и i присваивается значение 5, а весь объект i присваивается своему члену функции j

| | #include
using namespace std;
class A
{
int a;
public:
A() { }
A(int x)
{ a = x; }
void display()
{
cout << a;
}
A get()
{

return *this; // Вернуть себя

}
};
int main()
{
A i(5);
A j;

j = i.get();
j.display();
return 0;
} |

Ссылка: Объектно-ориентированное программирование в Turbo C++: Роберт Лафор

Share: X/Twitter LinkedIn

Get new posts in your inbox

No spam. Unsubscribe anytime.