Тема 11. Классы — продолжение
Сегодня мы более детально познакомимся с конструкторами и деструкторами класса, а также научимся работать с файлами в потоковом режиме, с помощью библиотеки fstream.
Продолжим написание программы учета оценок.
Конструктор Students
Сохранение оценок в файл
Деструктор Students
Конструктор Students
Добавим в класс Students конструктор, который будет принимать имя и фамилию ученика, и сохранять эти значения в соответствующих переменных класса.
// Конструктор Students
Students::Students(std::string name, std::string last_name)
{
Students::set_name(name);
Students::set_last_name(last_name);
}
При создании нового объекта, мы должны передать конструктору имя и фамилию студента. Иначе компиляция программы завершится с ошибкой. std::string name = "Василий"; std::string last_name = "Пупкин";
Students *student =
new Students(name, last_name);
Теперь добавим прототип конструктора в файл
students.h.
/* students.h */ #pragma once /* Защита от двойного подключения заголовочного файла */ #include class Students {
public:
// Конструктор класса Students Students(std::string, std::string);
// Установка имени студента void set_name(std::string);
// Получение имени студента std::string
get_name();
// Установка фамилии студента void set_last_name(std::string);
// Получение фамилии студента std::string
get_last_name();
// Установка промежуточных оценок void set_scores(
int []);
// Установка среднего балла void set_average_ball(
float);
// Получение среднего балла float get_average_ball();
private:
// Промежуточные оценки int scores[5];
// Средний балл float average_ball;
// Имя std::string name;
// Фамилия std::string last_name;
};
В файле
students.cpp определим сам конструктор.
/* students.cpp */ #include #include #include "students.h" // Конструктор Students Students::Students(std::string name, std::string last_name)
{
Students::set_name(name);
Students::set_last_name(last_name);
}
// Установка имени студента
void Students::set_name(std::string student_name)
{
Students::name = student_name;
}
// Получение имени студента std::string Students::get_name()
{
return Students::name;
}
// Установка фамилии студента
void Students::set_last_name(std::string student_last_name)
{
Students::last_name = student_last_name;
}
// Получение фамилии студента std::string Students::get_last_name()
{
return Students::last_name;
}
// Установка промежуточных оценок
void Students::set_scores(int scores[])
{
int sum = 0;
for (int i = 0; i < 5; ++i) {
Students::scores[i] = scores[i]; sum += scores[i];
}
}
// Установка среднего балла
void Students::set_average_ball(float ball)
{
Students::average_ball = ball;
}
// Получение среднего балла
float Students::get_average_ball()
{
return Students::average_ball;
}
В main() мы принимаем от пользователя имя и фамилию ученика, и сохраняем их во временных локальных переменных. После этого создаем новый объект класса Students, передавая его конструктору эти переменные.
/* main.cpp */
#include
#include "students.h"
int main(
int argc,
char *argv[])
{
// Локальная переменная, хранящая имя ученика std::string name;
// И его фамилию std::string last_name;
// Ввод имени std::cout << "Name: "; getline(std::cin, name);
// И фамилии std::cout << "Last name: "; getline(std::cin, last_name);
// Передача параметров конструктору Students *student =
new Students(name, last_name);
// Оценки int scores[5];
// Сумма всех оценок int sum = 0;
// Ввод промежуточных оценок for (
int i = 0; i < 5; ++i) { std::cout << "Score " << i+1 << ": "; std::cin >> scores[i];
// суммирование sum += scores[i];
}
// Сохраняем промежуточные оценки в объект класса Student student->set_scores(scores);
// Считаем средний балл float average_ball = sum / 5.0;
// Сохраняем средний балл student->set_average_ball(average_ball);
// Выводим данные по студенту std::cout << "Average ball for " << student->get_name() << " "
<< student->get_last_name() << " is "
<< student->get_average_ball() << std::endl;
// Удаление объекта student из памяти delete student;
return 0;
}
Сохранение оценок в файл Чтобы после завершения работы с программой, все данные сохранялись, мы будем записывать их в текстовый файл.
Оценки каждого студента будут находится в отдельной строке. Имя и фамилии будут разделяться пробелами. После имени и
фамилии ученика ставится еще один пробел, а затем перечисляются все его оценки.
Пример файла с оценками:
Василий Пупкин 5 4 5 3 3
Иван Сидоров 5 5 3 4 5
Андрей Иванов 5 3 3 3 3
Для работы с файлами мы воспользуемся библиотекой fstream, которая подключается в заголовочном файле с таким же именем.
#include // Запись данных о студенте в файл void Students::save()
{ std::ofstream
fout("students.txt", std::ios::app); fout << Students::get_name() << " "
<< Students::get_last_name() << " ";
for (
int i = 0; i < 5; ++i) { fout << Students::scores[i] << " ";
} fout << std::endl; fout.close();
}
Переменная fout — это объект класса ofstream, который находится внутри библиотеки fstream. Класс ofstream используется для записи каких-либо данных во внешний файл. Кстати, у него тоже есть конструктор. Он принимает в качестве параметров имя выходного файла и режим записи.
В данном случае, мы используем режим добавления — std::ios:app (англ. append). После завершения работы с файлом, необходимо вызвать метод close() для того, чтобы закрыть файловый дескриптор.
Чтобы сохранить оценки студента, мы будем вызывать только что созданный метод save().
Students student =
new Students("Василий", "Пупкин"); student->save();
Деструктор Students Логично было бы сохранять все оценки после того, как работа со студентом закончена.
Для этого создадим
деструктор класса Students, который будет вызывать метод save() перед уничтожением объекта.
// Деструктор Students Students::Students()
{
Students::save();
}
Добавим прототипы деструктора и метода save() в
students.h.
/* students.h */ #pragma once /* Защита от двойного подключения заголовочного файла */ #include
class Students {
public:
// Запись данных о студенте в файл
void save();
// Деструктор класса Students
Students();
// Конструктор класса Students
Students(std::string, std::string);
// Установка имени студента
void set_name(std::string);
// Получение имени студента std::string get_name();
// Установка фамилии студента
void set_last_name(std::string);
// Получение фамилии студента std::string get_last_name();
// Установка промежуточных оценок
void set_scores(int []);
// Получение массива с промежуточными оценками
int *get_scores();
// Получение строки с промежуточными оценками std::string get_scores_str(char);
// Установка среднего балла
void set_average_ball(float);
// Получение среднего балла
float get_average_ball();
private:
// Промежуточные оценки
int scores[5];
// Средний балл
float average_ball;
// Имя std::string name;
// Фамилия std::string last_name;
};
И определим эти функции в students.cpp.
/* students.cpp */
#include
#include
#include "students.h"
// Деструктор Students
Students::Students()
{
Students::save();
}
// Запись данных о студенте в файл
void Students::save()
{ std::ofstream fout("students.txt", std::ios::app); fout << Students::get_name() << " "
<< Students::get_last_name() << " ";
for (int i = 0; i < 5; ++i) { fout << Students::scores[i] << " ";
} fout << std::endl; fout.close();
}
// Конструктор Students
Students::Students(std::string name, std::string last_name)
{
Students::set_name(name);
Students::set_last_name(last_name);
}
// Установка имени студента
void Students::set_name(std::string student_name)
{
Students::name = student_name;
}
// Получение имени студента std::string Students::get_name()
{
return Students::name;
}
// Установка фамилии студента
void Students::set_last_name(std::string student_last_name)
{
Students::last_name = student_last_name;
}
// Получение фамилии студента std::string Students::get_last_name()
{
return Students::last_name;
}
// Установка промежуточных оценок
void Students::set_scores(int scores[])
{
int sum = 0;
for (int i = 0; i < 5; ++i) {
Students::scores[i] = scores[i]; sum += scores[i];
}
}
// Получение массива с промежуточными оценками
int *Students::get_scores()
{
return Students::scores;
}
// Установка среднего балла
void Students::set_average_ball(float ball)
{
Students::average_ball = ball;
}
// Получение среднего балла
float Students::get_average_ball()
{
return Students::average_ball;
}
Содержимое main.cpp останется прежним. Скомпилируйте и запустите программу. Перед тем, как приложение завершит свою работу, в директории с исполняемым файлом будет создан новый текстовый файл с оценками — students.txt.
Тема 12. Векторы в C++
Вектор в C++ — это замена стандартному динамическому массиву, память для которого выделяется вручную, с помощью оператора new
Разработчики языка рекомендуют в использовать именно vector вместо ручного выделения памяти для массива. Это позволяет избежать утечек памяти и облегчает работу программисту.
Пример создания вектора
#include
#include
int main()
{
// Вектор из 10 элементов типа int std::vector<int> v1(10);
// Вектор из элементов типа float // С неопределенным размером std::vector<
float> v2;
// Вектор, состоящий из 10 элементов типа int // По умолчанию все элементы заполняются нулями std::vector<
int> v3(10, 0);
return 0;
}
Управление элементами вектора Создадим вектор, в котором будет содержаться произвольное количество фамилий студентов.
#include #include #include int main()
{
// Поддержка кириллицы в консоли Windows setlocale(LC_ALL, "");
// Создание вектора из строк std::vector
students;
// Буфер для ввода фамилии студента std::string buffer = ""; std::cout << "Вводите фамилии студентов. "
<< "По окончание ввода введите пустую строку" << std::endl;
do { std::getline(std::cin, buffer);
if (buffer.size() > 0) {
// Добавление элемента в конец вектора students.push_back(buffer);
}
} while (buffer != "");
// Сохраняем количество элементов вектора
unsigned int vector_size = students.size();
// Вывод заполненного вектора на экран std::cout << "Ваш вектор." << std::endl;
for (int i = 0; i < vector_size; i++) { std::cout << students[i] << std::endl;
}
return 0;
}
Результат работы программы:
Методы класса vector
Для добавления нового элемента в конец вектора используется метод push_back()
Количество элементов определяется методом size()
. Для доступа к элементам вектора можно использовать квадратные скобки
[]
, также, как и для обычных массивов.
pop_back()
— удалить последний элемент
clear()
— удалить все элементы вектора
empty()
— проверить вектор на пустоту
Подробное описание всех методов std::vector
(на английском) есть на C++ Reference.
Следующий урок: Наследование классов C++ →.
Тема 13. Наследование классов в C++
Наследование позволяет избежать дублирования лишнего кода при написании классов.
Пусть в базе данных ВУЗа должна храниться информация о всех студентах и преподавателях. Представлять все данные в одном классе не получится, поскольку для преподавателей нам понадобится хранить данные, которые для студента не применимы, и наоборот.
Создание базового класса
Наследование от базового класса o
Конструктор базового класса o
Создание объекта класса student
Создание класса-наследника teacher o
Создание объекта класса teacher
Когда нужно использовать конструктор
Создание базового класса
Для решения этой задачи создадим базовый класс human, который будет описывать модель человека. В нем будут храниться имя, фамилия и отчество.
Создайте файл human.h:
// human.h
#ifndef HUMAN_H_INCLUDED
#define HUMAN_H_INCLUDED
#include
#include
class human {
public:
// Конструктор класса human human(std::string last_name, std::string name, std::string second_name)
{
this->last_name = last_name;
this->name = name;
this->second_name = second_name;
}
// Получение ФИО человека std::string get_full_name()
{ std::ostringstream full_name; full_name << this->last_name << " "
<< this->name << " "
<< this->second_name;
return full_name.str();
}
private: std::string name; // имя std::string last_name; // фамилия std::string second_name; // отчество
};
#endif // HUMAN_H_INCLUDED
Наследование от базового класса
Теперь создайте новый класс student, который будет наследником класса human.
Поместите его в файл student.h.
// student.h
#ifndef STUDENT_H_INCLUDED
#define STUDENT_H_INCLUDED
#include "human.h"
#include
#include
class student : public human {
public:
// Конструктор класса Student student( std::string last_name, std::string name, std::string second_name, std::vector<int> scores
) : human( last_name, name, second_name
) {
this->scores = scores;
}
// Получение среднего балла студента float get_average_score()
{
// Общее количество оценок unsigned int count_scores =
this->scores.size();
// Сумма всех оценок студента unsigned int sum_scores = 0;
// Средний балл float average_score;
for (
unsigned int i = 0; i < count_scores; ++i) { sum_scores +=
this->scores[i];
} average_score = (
float) sum_scores / (
float) count_scores;
return average_score;
}
private:
// Оценки студента std::vector<
int> scores;
};
#endif // STUDENT_H_INCLUDED Функция get_average_score вычисляет среднее арифметическое всех оценок студента.
Все публичные свойства и методы класса human будут доступны в классе student.
Конструктор базового класса
Для того, чтобы инициализировать конструктор родительского класса (в нашем случае — это сохранение имени, фамилии и отчества ученика), используется следующий синтаксис:
// Конструктор класса Student student(
// аргументы конструктора текущего класса ) : human(
// инициализация конструктора родительского класса ) {
// инициализация конструктора текущего класса }
В конструктор класса human мы передаем инициалы человека, которые сохраняются в экземпляре класса. Для класса students, нам необходимо задать еще и список оценок студента. Поэтому конструктор students принимает все аргументы конструктора базового класса, а также дополнительные аргументы для расширения функционала:
// Конструктор класса Student student( std::string last_name,
std::string name, std::string second_name, std::vector<int> scores
) : human( last_name, name, second_name
) {
this->scores = scores;
}
Список оценок студента хранится в векторе.
Создание объекта класса student
Реализуем пользовательский интерфейс для работы с классом student.
// main.cpp
#include
#include
#include "human.h"
#include "student.h"
int main(int argc, char* argv[])
{
// Оценки студента std::vector<int> scores;
// Добавление оценок студента в вектор scores.push_back(5); scores.push_back(3); scores.push_back(2); scores.push_back(2); scores.push_back(5); scores.push_back(3); scores.push_back(3); scores.push_back(3); scores.push_back(3);
// Создание объекта класса student student *stud = new student("Петров", "Иван", "Алексеевич", scores);
// Вывод полного имени студента (используется унаследованный метод класса human) std::cout << stud->get_full_name() << std::endl;
// Вывод среднего балла студента std::cout << "Средний балл: " << stud->get_average_score() << std::endl;
return 0;
}
В этом примере мы написали программу, которая создает объект класса student, сохраняя в нем его имя, фамилию, отчество и список оценок.
После инициализации объекта, происходит вывод полного имени студента с помощью функции get_full_name. Эта функция была унаследована от базового класса human.
Затем программа вычислияет средний балл студента и выводит его на экран. Этим занимается функция get_average_score, которую мы описали внутри класса student.
Мы реализовали часть функционала для нашей базы данных института (я конечно утрирую, когда оперирую столь серьезными высказываниями про настоящую базу данных
:)
Создание класса-наследника teacher Нужно создать еще один класс, в котором будут храниться данные преподавателей. Дадим ему название — teacher. Как вы уже поняли, мы не будем описывать все методы этого класса с нуля, а просто унаследуем его от класса human. Тогда, не нужно будет реализовывать хранение имени, фамилии и отчества препода. Это уже есть в базовом классе human.
Создайте файл teacher.h:
// teacher.h #ifndef TEACHER_H_INCLUDED #define TEACHER_H_INCLUDED #include "human.h" #include class teacher :
public human {
// Конструктор класса teacher public: teacher( std::string last_name, std::string name, std::string second_name,
// Количество учебных часов за семетр у преподавателя unsigned int work_time
) : human( last_name, name, second_name
) {
this->work_time = work_time;
}
// Получение количества учебных часов unsigned int get_work_time()
{
return this->work_time;
}
private:
// Учебные часы
unsigned int work_time;
};
#endif // TEACHER_H_INCLUDED
У класса teacher появилось новое свойство — количество учебных часов, отведенное преподавателю на единицу времени (семестр). Весь остальной функционал наследуется от базового класса human. Если бы мы писали все с нуля, то одинакового кода бы получилось в разы больше, и его поддержка усложнилась бы на порядок.
Создание объекта класса teacher
Изменим содержимое файла main.cpp, чтобы проверить работу класса teacher.
#include
#include "human.h"
#include "teacher.h"
int main(int argc, char* argv[])
{
// Количество учебных часов преподавателя
unsigned int teacher_work_time = 40; teacher *tch = new teacher("Васильков", "Петр", "Сергеевич", teacher_work_time); std::cout << tch->get_full_name() << std::endl; std::cout << "Количество часов: " << tch->get_work_time() << std::endl;
return 0;
}
Если сборка программы прошла без ошибок, то результат работы программы будет таким:
Можно таким же образом создать класс, в котором будут храниться данные обслуживающего персонала или руководящего состава. Наследование используют, когда у каждой группы объектов есть общие параметры, но для каждой из этих групп нужно хранить более кастомные данные.
Также, мы можем создать класс, который будет описывыть студента заочной формы обучения. Его мы унаследовали бы от класса student, добавив какие-либо дополнительные данные.
В класс human можно добавить еще больше свойств, которые будут описывать данные, имеющиеся у любого человека. Например, номер паспорта, дату рождения, прописку и место проживания.
Подобный подход позволяет в разы уменьшить дублирование кода в реальных проектах, и упросить его поддержку.
Когда нужно использовать конструктор Если у класса много свойств — их совсем не обязательно задавать в конструкторе. Для сохранения отдельных свойств класса используют set-функции. Например, для сохранения номера паспорта, можно создать публичный метод set_passport_number(std::string number), который будет принимать
значение свойства и сохранять его в объекте, через переменную this.