МУ_ДЗ_2014 (1079920), страница 2
Текст из файла (страница 2)
cout << "Name: " << Group22[5].Name << endl; // Элемент массива 5
cout << "Kurs: " << Sidorov.kurs << endl; // !!! Ошибка, т.к. это поле объекта private
Для вызова методов класса они должны быть в области объекта public:
cout << "Kurs: " << Sidorov.GetKurs() << endl; // Объект
cout << "Kurs: " << pS->GetKurs() << endl; // Указатель
cout << "Kurs: " << rS.GetKurs() << endl; // Ссылка
cout << "Name: " << Group22[5]. GetKurs()<< endl; // Элемент массива 5
Sidorov.Print(); // Вызов метода печати
11 Инкапсуляция
Для эффективного программирования и надежности программы в классах определяются различные режимы доступа (по-другому, области видимости) к данным и функциям. Если данные и методы описаны в области видимости private, то они недоступны (невидимы) для внешнего использования вне класса. С такими данными могут работать только методы класса. Если данные описаны в области public, то они доступны для доступа вне класса. Инкапсуляцией называется технология скрытия данных и методов от внешнего пользователя, Существует еще один режим доступа protected, но мы поясним его в разделе наследования. Примеры открытого доступа и ошибки инкапсуляции были показаны выше для класса Student и объектов данного класса. В частности доступ к члену данных kurs запрещен, так как он расположен в области private.
12 Наследование
Наследованием называется создание новых типов данных (классов) на основе существующих классов. Исходные классы для наследования должны быть предварительно описаны или определены. При наследовании различают понятия базового и порожденного класса. Базовым классом является тот, на основе которого создается другой класс. Никаких ограничений на базовый класс не накладывается кроме: базовый класс должен быть предварительно определен и имя базового класса не должно совпадать с именем порожденным. Порожденным считается новый класс. Порождение (или наследование) может быть 2-х видов: public и private. Этот спецификатор доступа должен предшествовать имени базового класса. Формально
<список базовых классов>::= [public | private | protected] <имя базового класса>
[,<имя базового класса> …]
При наследовании классов типа public в порожденном классе доступны все члены базового класса из разделов public и protected. При наследовании типа private в порожденном классе не доступны все члены базового класса. При наследовании типа protected в порожденном классе все открытые и защищенные члены базового класса становятся защищенными в классе наследнике. Простое наследование – это наследование от одного базового класса. Если базовых классов несколько, но наследование называется множественным. Отметим, что в объект порожденного класса всегда включены все члены базового класса, хотя они могут быть невидимы (недоступны).
При наследовании в порожденном классе могут быть:
-
Добавлены новые члены данные.
-
Добавлены новые методы.
-
Заново описаны данные с тем же именем.
-
Заново описаны методы с тем же именем.
-
Изменены методы доступа к данным и методам.
Главное это то, что в порожденном классе создается новый тип данных с новыми свойствами и новым поведением (фактически новое понятие предметной области). Пример наследования:
class Magistr : public Student {
…
public:
char TemaDiss[MAX]; // Тема магистерской работы
…
viod SetNewTema(char * pNewTema); // Задание новой темы
…
};
В данном примере мы добавили новый член - данных – тема работы и метод изменения темы работы – новый метод. Отметим, что порожденном классе конструкторы порожденного должны вызывать конструкторы базового класса, если в них передаются параметры. Например:
…
Magistr (int k, float s , char * t) :Student(k, s){ strcpy(TemaDiss, t); };
…
Вызов конструкторов базовых классов выполняется с помощью фактических параметров передаваемых в конструктор порожденного класса. Более детально с вопросами наследования вы познакомитесь на лекциях и можете самостоятельно изучить в литературе [1]. Здесь затронуты только главные вопросы и понятия необходимые для выполнения ЛР.
13 Статические члены класса
В классе могут быть объявлены члены со специальным спецификатором static. Такие члены называются статическими. Фактически они являются общими для всех создаваемых объектов данного класса и могут рассматриваться как глобальные переменные программы. Обращение к таким статическим членам вне класса выполнятся так:
<имя класса>::<имя статической переменной>
Например, описана статическая переменная в качестве счетчика объектов класса:
Class Student {
…
Static int CountObj; // Статическая переменная счетчик
…
Student(){CountObj++ ;}; // Конструктор
~Student(){CountObj-- ;}; // Деструктор
…
};
Статические переменные должны быть обязательно проинициализированы (задано начальное значение). Это выполняется в программе после описания класса следующим образом:
int Student::CountObj = 0; // Инициализация статического чледа класса
…
Student::CountObj = 10; // Доступ к статической переменной вне класса как глобальной
14 Объявления класса
Для описания указателей и ссылок внутри классов на текущий описываемый класс или на классы, которые в данный момент компиляции еще не известны, используется объявление класса (не надо путать его с описанием или определением класса). С помощью такого объявления компилятору предписывается, что такой класс будет описан ниже. Объявление задается в следующей форме:
class <имя класса> ;
Например в новом классе А используется описание указателя на объект типа Student:
class Student;
class A {
…
Student * pS;
…
};
Для выполнения наследования (использования в виде базового класса) можно применять повторное описание класса базового класса в другом модуле.
15 Классы и структуры
В языке С++ понятие структуры данных расширено до понятия класса. В структурах могут определяться методы, условия защиты данных и так далее. Основное отличие структур данных от классов заключается в том, что в них по-умолчанию все члены является открытыми (public). В классах наоборот, по-умолчанию, когда не указан режим доступа, все члены класса считаются закрытыми (private).
16 Операции в классах
В классах могут быть перегружены стандартные операции (“+”, “-” и т.д.). Понятие перегрузки операций заключается в том, что для объектов нового тапа задается новый смысл операции. Этот смысл (или действия при выполнении операции) определяется специальной функцией, написанной пользователем. Более подробно механизм перегрузки операций будет изложен в методических указаниях к лабораторной работе по перегрузке. Например, если мы имеем объект типа группа студентов, то мы можем сложить две группы:
Group G1;
Group G2;
Group G3;
G3 = G1 + G2;
17 Статическое и динамическое связывание
Как сделать программу более наглядной, универсальной, понятной и компактной одновременно? Этот вопрос очень важен при построении программных систем. При построении сложных программных систем применяются разные методы и механизмы для обеспечения такого результата. Наглядность и компактность, а также универсальность, способствуют сокращению сроков разработки и отладки программного обеспечения (ПО), в значительной мере влияют на его качество (надежность ПО, сопровождение ПО, мобильность ПО) и обеспечивают увеличение сроков службы ПО. В принципе возможны два подхода достижения этих результатов: статическое и динамическое связывание.
Связывание в программировании означает внутреннюю настройку программы на классы, функции и переменные. Способ связывания существенно влияет на универсальность программы. Они отличаются временем связывания. Другими словами, это определение того, на каком этапе формирования и выполнения программы, эти связи устанавливаются. Принято рассматривать три возможных этапа: этап работы макропроцессора (предварительной обработки исходного текста), этап компиляции и этап выполнения программ. Первые два этапа, обычно, называют статическим связыванием (связи можно изменять только до компиляции). Третий способ обеспечивает динамическое связывание, такое связывание обеспечивается на этапе выполнения программы. Хотя последний способ является более сложным в реализации, он обеспечивает большую степень универсальности и компактности программных систем. Простым примером динамического связывания является использование указателей и динамической памяти в программах, а более сложным использование виртуальных функций в классах.
Данная ЛР посвящена изучению механизмов статического связывания: перегрузки функций и операций, параметров по умолчанию т.д.
18 Параметры по-умолчанию
В современных языках программирования допускается задание параметров функций по-умолчанию. Такая возможность обеспечивает использование вызова конкретной функции без задания всего перечня фактических параметров функции. Если параметр не задается, то в этом случае используется значение параметра по-умолчанию, которое может быть задано либо в прототипе функции, либо в описании функции (определении функции). Одновременно и в прототипе функции и в ее описании нельзя задавать параметры по-умолчанию.
Формат задания параметров по-умолчанию в прототипе функции:
<имя функции> (<тип> [=<значение>], <тип> [=<значение>], … );
Или возможно так:
<имя функции> (<тип> <тег>[=<значение>], <тип> <тег> [=<значение>], … );
Нетерминальная переменная <тег> - это любое имя, фактически комментарий, его обозначение может не совпадать с именами формальных параметров из описания функций. Значение - это переменная или выражение этапа компиляции (#define), либо константа, либо переменная доступная на этом этапе компиляции перед вызовом функции (глобальная переменная, объявленная в этом или другом модуле). При использовании в качестве значений глобальных переменных их можно и нужно вычислить заново перед вызовом функции. Суть использования параметра по-умолчанию следующий: если фактический параметр не задан в строке вызова (пропущен), то он заменяется значением этого параметра по-умолчанию. В этом случае, не сокращается число параметров функции, а просто подставляются стандартные значения. Пример использования параметров по-умолчанию в прототипах дан ниже:
int Summa1( int a = 10 , int b = 20 , int c = 30); // три параметра по-умолчанию
int Summa2( int a , int b = 20 , int c = 30); // два параметра по-умолчанию
int Summa3( int , int , int c = 30); // один параметр по-умолчанию
Если задано три параметра по-умолчанию, то возможны следующие вызовы данных функций:
cout << "Сумма = "<< dec << Summa1 ( ) <<endl;
cout << "Сумма = "<< Summa1 ( 3 ) <<endl;
cout << "Сумма = "<< Summa1 (3 , 3) <<endl;
cout << "Сумма = "<< Summa1 (3 , 3 , 3 ) <<endl;
Если данный позиционный параметр не может быть задан по-умолчанию, пропуск его при вызове функций является ошибкой:
cout << "Сумма = "<< Summa2 ( 3 ) <<endl; // Правильно, только 1-й параметр
cout << "Сумма = "<< Summa3 ( 3 ) <<endl; // Ошибка второго параметра
Параметры по умолчанию можно пропускать только последовательно справа налево. Такое задание в примере ниже тоже является ошибкой:
cout << "Сумма = "<< dec << Summa1 (, 3, 5 ) <<endl; //Ошибка!
При задании параметров по умолчанию в описании функции (определении функции) синтаксис записи выглядит так:
<имя функции> (<тип> <ФП>[=<значение>], <тип> <ФП> [=<значение>], … ) { …};