- •ПРОГРАММИРОВАНИЕ НА ЯЗЫКАХ С И С++
- •СОДЕРЖАНИЕ
- •ВВЕДЕНИЕ
- •1. ЛЕКСИКА ЯЗЫКА C
- •1.1. РАЗДЕЛИТЕЛИ
- •1.2. КОММЕНТАРИИ
- •2. ДАННЫЕ И ОПЕРАЦИИ
- •2.1. БАЗОВЫЕ ТИПЫ ДАННЫХ
- •Наименование
- •Диапазон
- •2.2. КОНСТАНТЫ
- •Целые константы
- •Константы с плавающей точкой
- •Символьные константы
- •2.3. ОПРЕДЕЛЕНИЕ ПЕРЕМЕННЫХ И ТИПИЗИРОВАННЫХ КОНСТАНТ
- •2.4. ОДНОМЕРНЫЙ МАССИВ
- •2.5. СТРОКИ, СТРОКОВЫЕ КОНСТАНТЫ
- •2.6. УКАЗАТЕЛИ
- •2.7. ПЕРЕИМЕНОВАНИЯ ТИПОВ
- •2.8. СТРУКТУРЫ
- •2.9. МАССИВЫ СТРУКТУР
- •2.10. МНОГОМЕРНЫЕ МАССИВЫ
- •2.11. ОБЪЕДИНЕНИЕ
- •2.12. ПЕРЕЧИСЛЕНИЕ
- •2.13. БИТОВОЕ ПОЛЕ ( ЗАПИСЬ )
- •3. ОПЕРАЦИИ И ВЫРАЖЕНИЯ
- •3.1. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ
- •3.2. ОПЕРАЦИИ СРАВНЕНИЯ
- •3.3. ЛОГИЧЕСКИЕ ОПЕРАЦИИ
- •3.4. ПОБИТОВЫЕ (ПОРАЗРЯДНЫЕ) ОПЕРАЦИИ
- •3.5. ОПЕРАЦИИ ПРИСВАИВАНИЯ
- •3.6. АДРЕСНЫЕ ОПЕРАЦИИ
- •3.7. ДОСТУП К ПОЛЯМ СТРУКТУР, ОБЪЕДИНЕНИЙ И ЗАПИСЕЙ
- •3.8. ДРУГИЕ ОПЕРАЦИИ
- •3.9. ПОРЯДОК ВЫПОЛНЕНИЯ ОПЕРАЦИЙ В ВЫРАЖЕНИИ
- •4. ОПЕРАТОРЫ
- •4.1. ОПЕРАТОР-ВЫРАЖЕНИЕ
- •4.2. ПУСТОЙ ОПЕРАТОР
- •4.3. СОСТАВНОЙ ОПЕРАТОР
- •4.4. УСЛОВНЫЕ ОПЕРАТОРЫ
- •4.5. ОПЕРАТОРЫ ЦИКЛА
- •while( выражение) оператор;
- •do оператор while ( выражение );
- •for( выражение1; выражение2; выражение3 ) оператор;
- •4.6. ОПЕРАТОРЫ ПЕРЕХОДА
- •break;
- •switch
- •continue;
- •goto метка;
- •5. ФУНКЦИИ. КЛАССЫ ПАМЯТИ
- •5.1. ФУНКЦИИ
- •5.2. КЛАССЫ ПАМЯТИ
- •класс памяти
- •ключевое слово
- •существование
- •видимость
- •5.3. ВОЗВРАЩЕНИЕ ЗНАЧЕНИЙ
- •5.4. АРГУМЕНТЫ ФУНКЦИИ
- •6. ФУНКЦИИ ВВОДА С КЛАВИАТУРЫ И ВЫВОДА НА ДИСПЛЕЙ
- •6.1. ФОРМАТИРОВАННЫЙ ВЫВОД
- •6.2. ФОРМАТИРОВАННЫЙ ВВОД
- •6.3. ВВОД СТРОКИ
- •6.4. ВЫВОД СТРОКИ
- •6.5. ВВОД СИМВОЛА
- •6.6. ВЫВОД СИМВОЛА
- •7. ПРЕПРОЦЕССОР
- •7.1. ВКЛЮЧЕНИЕ ФАЙЛОВ
- •7.2. ВЫПОЛНЕНИЕ ПОДСТАНОВОК
- •7.3. УСЛОВНАЯ КОМПИЛЯЦИЯ
- •8. ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ, СПИСКИ
- •8.1. ВЫДЕЛЕНИЕ И ОСВОБОЖДЕНИЕ ДИНАМИЧЕСКОЙ ПАМЯТИ
- •8.2. СПИСКИ
- •Рис.1. Структура однонаправленного линейного списка
- •9. ФАЙЛЫ
- •9.1. ОТКРЫТИЕ И ЗАКРЫТИЕ ФАЙЛОВ
- •9.2. ЧТЕНИЕ И ЗАПИСЬ В ФАЙЛ
- •9.3. ПРОИЗВОЛЬНЫЙ ДОСТУП К ФАЙЛУ
- •10. ЛАБОРАТОРНЫЕ РАБОТЫ
- •10.1. МАССИВЫ И СИМВОЛЬНЫЕ СТРОКИ
- •Цель работы
- •Пример выполнения работы
- •Варианты заданий
- •10.2. ФУНКЦИИ
- •Цель работы
- •Пример выполнения работы
- •Варианты заданий
- •10.3. СТРУКТУРЫ И МАССИВЫ СТРУКТУР
- •Цель работы
- •Пример выполнения работы
- •Варианты заданий
- •10.4. ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ, СПИСКИ
- •Цель работы
- •Пример выполнения работы
- •Варианты заданий
- •10.5. РАБОТА С ФАЙЛАМИ
- •Цель работы
- •Пример выполнения работы
- •Варианты заданий
- •11. ВВЕДЕНИЕ В C++
- •12. КЛАССЫ
- •12.1. ОБЩИЕ ПОЛОЖЕНИЯ
- •12.2. КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ
- •12.3. СОЗДАНИЕ И ВЫЗОВ ОБЪЕКТОВ
- •12.4. ДРУЖЕСТВЕННЫЕ ФУНКЦИИ
- •12.5. ПЕPЕГPУЖЕННЫЕ ОПЕPАЦИИ И ФУНКЦИИ
- •12.6. ПАРАМЕТРИЗИРОВАННЫЕ КЛАССЫ
- •12.7. ПРЕОБРАЗОВАНИЕ ТИПОВ
- •12.8. СТАТИЧЕСКИЕ КОМПОНЕНТЫ КЛАССА
- •13. ПРОИЗВОДНЫЕ КЛАССЫ, НАСЛЕДОВАНИЕ
- •13.1. ИЕРАРХИЯ КЛАССОВ
- •13.2. ДОСТУП К НАСЛЕДУЕМЫМ КОМПОНЕНТАМ
- •Доступ в базовом классе
- •Модификатор доступа
- •14. ВИРТУАЛЬНЫЕ ФУНКЦИИ
- •14.1. ОБЩИЕ ПОЛОЖЕНИЯ
- •14.2. ВИРТУАЛЬНЫЕ ДЕСТРУКТОРЫ
- •14.3. АБСТРАКТНЫЕ КЛАССЫ
- •15.1. ВЫВОД ДАННЫХ
- •15.2. ВВОД ДАННЫХ
- •15.3. ВВОД-ВЫВОД ДАННЫХ, ОПРЕДЕЛЕННЫХ ПОЛЬЗОВАТЕЛЕМ
- •15.4. РАБОТА С ДИСКОВЫМИ ФАЙЛАМИ
- •15.5. ОБРАБОТКА ИСКЛЮЧЕНИЙ
- •ЛИТЕРАТУРА
– 49 –
|
|
Таблица 4 |
|
Доступ в базовом классе |
Модификатор доступа |
Унаследованные права |
|
|
|
доступа |
|
|
|
|
|
private |
private |
не доступен |
|
protected |
private |
private |
|
public |
private |
private |
|
private |
public |
не доступен |
|
protected |
public |
protected |
|
public |
public |
public |
|
Свойство protected аналогично свойству private, но доступ к такому компоненту возможен как через интерфейс базового класса, так и через интерфейс производного. По умолчанию для базового класса типа class в качестве модификатора доступа используется private, для класса struct – public.
Ниже следует программа, в которой класс Node задает тип данных, которым является элемент одностороннего списка; класс List является базовым, класс Stack – производным
(List ← Stack): struct Node { int elem;
Node *next;
Node (int el, Node *ptr) {elem=el; next=ptr;} };
class List { protected: Node *head; public:
List() {head=0;}
List(int a) {head=new Node(a,0);} void Show(void);
~List();
};
class Stack: public List { public:
Stack (int el): List(el){}; void Push(int);
int Pop(); };
Унаследованная переменная head может быть использована в теле функций Push и Pop, но в программе она доступна только через интерфейс классов – функции Push, Pop, Show.
Отметим следующий момент: если производный класс был унаследован из базового с модификатором public, то указатель на базовый класс можно использовать и как указатель на производный класс без какого-либо преобразования типов:
указатель_на_базовый = указатель_на_производный;
Существуют виртуальные базовые классы. Необходимость таковых появляется при множественном наследовании в тех случаях, когда в разных родительских классах есть одинаковые поля (например, при следующей иерархии: A←С; A←B←C). Наследуя каждому родителю, производный класс может получить несколько копий одних и тех же полей, что приводит к неоднозначности обращения к этим полям. Добавление слова virtual в списке базовых классов (перед модификатором доступа) устраняет дублирование.
14. ВИРТУАЛЬНЫЕ ФУНКЦИИ
14.1. ОБЩИЕ ПОЛОЖЕНИЯ
Рассмотрим следующую программу. class Parent{
© 1998 Калачев Д.П., Лутай В.Н.
– 50 –
public:
double F1(double x){ return x*x;} double F2(double x { return F1(x)/2; }
};
class Child : public Parent { public:
double F1( double x) { return x*x*x; } //Переопределение функции F1 };
void main(){ Child child;
cout<<child.F2(3)<<endl;
}
Здесь класс Child, производный от класса Parent, наследует функцию F2, но переопределяет функцию F1. Компилятор оттранслирует выражение child.F2(3) в обращение к унаследованной функции Parent::F2, которая в свою очередь вызовет Parent::F1, а не Child::F1. Полученный результат поэтому равен 4,5 вместо ожидаемого 13,5. Объявление функции F1 виртуальной приведет к правильному результату.
Свойство виртуальности задается ключевым словом virtual перед типом функции class Parent { ...
public:
virtual double F1(double x){ return x*x; };
//В базовом слово virtual обязательно
...
};
class Child :public Parent { ...
public:
virtual double F1( double x) { return x*x*x; }
//Повторение virtual здесь необязательно
};
Таким образом, механизм виртуальных функций заставляет функцию с одним и тем же именем работать по-разному в зависимости от того, объектом какого класса она вызывается. Вызов виртуальных функций обычно производится посредством указателей на базовый класс.
(Виртуальность является частью полиморфизма. Полиморфизм, наряду с инкапсуляцией и наследованием является, основой объектно-ориентированного программирования. Кроме виртуальности, к полиморфизму относятся перегруженные функции и операции).
Механизм виртуальных функций основан на позднем (динамическом) связывании. Раннее связывание характерно для языка С – код вызова функции является частью откомпилированного кода всей программы. При позднем связывании виртуальная функция не связывается с объектом при компиляции. Если в некотором классе имеется хотя бы одна виртуальная функция, то все объекты этого класса содержат указатель на связанную с их классом виртуальную таблицу указателей функций этого класса. Доступ к виртуальной функции осуществляется посредством косвенной адресации – через этот указатель и соответствующую таблицу. (Отметим, что виртуальная функция не может быть статическим компонентом класса).
Использование виртуальных функций снижает быстродействие и увеличивает размер объектов класса, содержащих такие функции. Но это – неизбежная плата за эффективность.
14.2. ВИРТУАЛЬНЫЕ ДЕСТРУКТОРЫ
Приведем пример использования виртуальных функций:
#include <iostream.h>
struct pet{ virtual void speak(void){cout<<“ Привет.“;} virtual ~pet(){cout<<“Я-деструктор животных”<<endl;} };
struct dog: public pet{
void speak(void){pet::speak(); cout<<"Гав-гав"; } ~dog(){cout<<“Я-деструктор Гав-Гав”<<endl;} };
© 1998 Калачев Д.П., Лутай В.Н.
– 51 –
struct cat: public pet{
void speak(void){pet::speak(); cout<<"Мяу-мяу";} ~cat(){cout<<“Я -деструктор Мяу-мяу”<<endl;} };
void main() {
pet *members[]={new dog, new cat}; for (int i=0; i<2; i++)
members[i]->speak(); // Вызов виртуальных функций через указатели на базовый класс for(i=0; i<2; i++) // Вызов деструкторов
delete members[i];
}
Информация на дисплее:
Привет.Гав-Гав Привет.Мяу-мяу Я- деструктор Гав-гав
Я- деструктор животных Я- деструктор Мяу-мяу Я- деструктор животных
Рассмотрим деструкторы классов. Во-первых, создание массива объектов в свободной памяти требует для вызова деструкторов обязательного использования delete. Во-вторых, деструктор класса pet объявлен как виртуальный; вследствие этого и все деструкторы производных классов являются виртуальными. Если бы они не были таковыми, то не были бы вызваны деструкторы классов cat и dog. Виртуальный деструктор самостоятельно определяет объект того класса, который подлежит разрушению.
Если в классе есть хотя бы одна виртуальная функция, то рекомендуется деструктор класса делать виртуальным. Конструктор виртуальным быть не может.
14.3. АБСТРАКТНЫЕ КЛАССЫ
Абстрактным называется класс, имеющий хотя бы одну виртуальную функцию, при объявлении приравненную к нулю. Такие функции не имеют практического применения и называются абстрактными. Для абстрактных классов нельзя создавать объекты; они могут использоваться как базовые классы для других классов. При этом, если абстрактная функция не переопределена в производном классе, то она остается абстрактной и таковым же является производный класс.
Основная идея абстрактных классов заключается в том, чтобы описать интерфейс без конкретной его реализации. Приведем пример программы, в котором в базовом классе Shape просто перечислены методы для работы с прямоугольниками, задаваемыми в производном классе Rectangle.
#include <graphics.h> #include <iostream.h> class Shape { public:
int xb,yb,xe,ye; // Координаты прямоугольника public:
Shape(int hb,int vb,int he,int ve): xb(hb),yb(vb),xe(he),ye(ve) {}
virtual void draw()=0; // Рисует фигуру virtual void move(int,int)=0; // Перемещает virtual void copy(int,int)=0; // Копирует virtual void rotate(int)=0; // Поворачивает
};
struct Rectangle : public Shape{ Rectangle(int hb, int vb, int he, int ve):
Shape (hb,vb,he,ve) { int gd=DETECT,gm; initgraph(&gd,&gm, "");//Инициализация графического //режима
~Rectangle() {closegraph(); }
virtual void move(int x, int y){/* Какие-то действия */ } virtual void copy(int x, int y){} /* Какие-то действия */
© 1998 Калачев Д.П., Лутай В.Н.
– 52 –
virtual void rotate(int r){} /* Какие-то действия */
virtual void draw(){::rectangle(xb,yb,xe,ye);} // вызов библиотечной функции };
void main() {
Rectangle r(10,10,20,20); r.draw();
}
15.ПОТОКИ ВВОДА-ВЫВОДА
Воснову системы ввода-вывода положена библиотека iostream, заключенная в соот-
ветствующие заголовочные файлы. Как уже указывалось, в программах С++ используются обычно 3 стандартных потока ввода-вывода, объявленных как объекты библиотечного класса, помещенного в заголовочный файл iostream.h:
•cin – стандартный ввод;
•cout – стандартный вывод;
•cerr – стандартный вывод ошибок.
15.1. ВЫВОД ДАННЫХ
Вывод потока осуществляется перегруженной операцией <<, которая здесь называется операцией вставки. Примеры использования операторов вывода приводились ранее. Упомянем о компонентах класса cout.
Функция write – компонент класса cout – выводит n символов, в том числе, в отличие от вставки, и пробелы:
cout.write(arg, n)
Функция-компонент того же класса put выводит одиночный символ.
Форматирование вывода производится с помощью специальных операций, называемых манипуляторами:
cout<<setw(4)<<i<<setw(6)<<j;
setw(n) – манипулятор, устанавливающий ширину поля для работы следующей после него вставки. В результате для представления i будет отведено поле шириной 4 символа, для j-6 символов.
С помощью манипуляторов можно изменять основание системы счисления выводимых данных (dec,hex,oct), устанавливать точность представления чисел с плавающей точкой (setprecizion), устанавливать символы-заполнители (setfill). Для использования манипуляторов к программе надо подключить заголовочный файл iomanip.h.
Пример использования функций write и setw:
#include <iostream.h> #include <iomanip.h> #include <conio.h> void main() {
char* s=new char[3]; s=“ABC”;
cout.write(s,1); // Выводится A cout<<endl;
cout<<setw(3)<<s<<endl; //Выводится ABC getch();
}
Приведем пример использования потока cerr:
Дополним класс String перегруженной операцией индексирования [] и функцией
error:
char& operator [] (int i) {
if (i<0 || this->length<i) error("Индекс за границей массива");} void error(char* p) {
cerr<<p<<endl ; exit (1);}
Обе функции можно сделать встроенными.
© 1998 Калачев Д.П., Лутай В.Н.