Открытый канал ID 77000965607995

Я просто спросить про указатели C++ (изучаю С++).

Начинаю свой путь изучения С++. Пока читаю книгу Прата С++ 6 издание.
Здесь нет каких-то рекомендаций, канал создан, чтобы автор не забыл, что он изучал..

Публикации за период

По реакциям

Поиск и вся лента — после бесплатной регистрации. Свои даты — по подписке; владелец канала видит их бесплатно. Сортировка по реакциям — по подписке. Зарегистрироваться

Загрузка…

В дополнение:
Это происходит, потому что в АБК Role() виртуальный и в производных классах остаётся виртуальным. А при вызове Show() сначала определяется для кого Show() вызывать (ученика, учителя и тд), а после в самом методе вызывается Role() и точно также идет определение "к кому принадлежит Role()".

Также в main создадим указатель school из 5 объектов, указатель типа Person, благодаря этому мы можем спокойной создавать массив из производных классов от АБК и использовать их.

Начиная с класса VicePrincipal, в метод Show() был добавлен тернарный оператор, который добавляет окончание к слову class при необходимости:

void Show() const {
Teacher::Show();
std::cout << "Managed " << m_classesManaged;
m_classesManaged > 1 ? std::cout << " classes" : std::cout << " class";
std::cout << std::endl;
}

Просмотры: 25
Перейти в пост Открыть в MAX Сообщить о неточности
Загружаем изображение…
Глава 13. Наследование классов
Сделаем класс Person абстрактным базовым классом (АБК), так как в контексте школы не может быть просто человека. Абстрактный класс задаёт общие черты, а специфику мы делаем в производных классах. Для создания АБК нам необходимо "занулить" виртуальную функцию, стоит помнить, что объекты нельзя создавать.

Код:

//заголовочный файл person.h
#pragma once
#include <string>

class Person {
protected:
std::string m_name;
int m_age;
public:
Person() : m_name("Noname"), m_age(0) {}
Person(std::string name, int age) : m_name(name), m_age(age) {}
virtual void Show() const = 0;
virtual std::string Role() const = 0;

};

class Student : public Person {
private:
int m_grade;
double m_averageScore;
public:
Student() : Person("Noname", 0), m_grade(0), m_averageScore(0.0) {}
Student(std::string name, int age, int grade, double averageScore)
: Person(name, age), m_grade(grade), m_averageScore(averageScore) {
}

void Show() const {
std::cout << "Name: " << m_name << std::endl
<< "Age: " << m_age << std::endl
<< "Grade: " << m_grade << std::endl
<< "Average score: " << m_averageScore << std::endl
<< "Role: " << Role() << std::endl;
}

std::string Role() const {
return "student";
}
};

class Teacher : public Person {
protected:
std::string m_subject;
double m_salary;
public:
Teacher(std::string n, int a, std::string s, double sal)
: Person(n, a), m_subject(s), m_salary(sal) {
}

void Show() const {
std::cout << "Name: " << m_name << std::endl
<< "Age: " << m_age << std::endl
<< "Subject: " << m_subject << std::endl
<< "Salary: " << m_salary << std::endl
<< "Role: " << Role() << std::endl;
}
std::string Role() const override { return "Teacher"; }
double Salary() const { return m_salary; }
};


class VicePrincipal : public Teacher {
int m_yearsOfExperience;
int m_classesManaged;
public:
VicePrincipal(std::string n, int a, std::string s, double sal, int y, int c)
: Teacher(n, a, s, sal), m_yearsOfExperience(y), m_classesManaged(c) {
}

void Show() const {
Teacher::Show();
std::cout << "Managed " << m_classesManaged;
m_classesManaged > 1 ? std::cout << " classes" : std::cout << " class";
std::cout << std::endl;
}
std::string Role() const { return "VicePrincipal"; }
};

class Principal : public VicePrincipal {
double m_schoolBudget;
public:
Principal(std::string n, int a, std::string s, double sal, int y, int c, double b)
: VicePrincipal(n, a, s, sal, y, c), m_schoolBudget(b) {
}

void Show() const {
VicePrincipal::Show();
std::cout << "Budget: " << m_schoolBudget << std::endl;
}
std::string Role() const override { return "Principal"; }
};

Код в main.cpp

//main.cpp
#include <iostream>
#include "person.h"

int main() {

Student Peter("Peter", 12, 5, 4.5);
Teacher t1("Izolda", 55, "History", 27500);
VicePrincipal vp1("Antonio", 77, "Math", 90000, 57, 1);
VicePrincipal vp2("Antonioresky", 78, "Math", 90100, 58, 2);
Principal p1("Oleg", 50, "informatics", 100000, 30, 5, 300000);

Person* school[5] = { &Peter, &t1, &vp1, &vp2, &p1 };

for (int i = 0; i < 5; ++i) {
school[i]->Show();
std::cout << std::endl;
}
return 0;
}

Консоль:
Name: Peter
Age: 12
Grade: 5
Average score: 4.5
Role: student

Name: Izolda
Age: 55
Subject: History
Salary: 27500
Role: Teacher

Name: Antonio
Age: 77
Subject: Math
Salary: 90000
Role: VicePrincipal
Managed 1 class

Name: Antonioresky
Age: 78
Subject: Math
Salary: 90100
Role: VicePrincipal
Managed 2 classes

Name: Oleg
Age: 50
Subject: informatics
Salary: 100000
Role: Principal
Managed 5 classes
Budget: 300000

Мы видим, что переопределенный метод Role() работает в каждом классе, даже, если мы подключаем часть Show() из наследуемого класса.
Просмотры: 20
Перейти в пост Открыть в MAX Сообщить о неточности
Загружаем изображение…
Глава 13. Наследование классов
Виртуальные функции.
Теперь рассмотрим такой случай, что у наших классов есть метод Show().

//заголовочный файл person.h
#pragma once
#include <string>
 
class Person {
protected:
    std::string m_name;
    int m_age;
public:
    Person() : m_name("Noname"), m_age(0) {}
    Person(std::string name, int age) : m_name(name), m_age(age) {}
    void Show() const { std::cout << m_name << ", " << m_age; }
};
 
class Student : public Person {
private:
    int m_grade;
public:
    Student() : Person("Noname", 0), m_grade(0) {}
    Student(std::string name, int age, int grade)
        : Person(name, age), m_grade(grade) {}
       
    void Show() const {
        Person::Show();
        std::cout << ", grade " << m_grade << std::endl;
    }
};

 
Сделаем указатель типа Person (базового класса) на производный класс Student и попытаемся вывести данные объекта. В заголовочном фале person.h в производном классе добавили метод Show(), где:
 Person::Show(); выводит данные от базового класса, а в производном, соответственно, добавили вывод grade (класс, в котором учится ученик).

//main.cpp
#include <iostream>
#include "person.h"
 
int main() {
 
    Person Ivan("Ivan", 12);
    Ivan.Show();
    std::cout << std::endl;
 
    Student Peter("Peter", 12, 5);
    Peter.Show();
   
    Person* pp = &Peter;
    pp->Show();
    return 0;
}

 
Вывод консоли:
Ivan, 12
Peter, 12, grade 5
Peter, 12

Как можем заметить, grade не был выведен указателем pp. Здесь показано статическое связывание, то есть тип указателя Person, в связи с чем была выведена часть данных метода класса Person, несмотря на то что указатель pp ссылается на объект класса Student.
Чтобы решить эту проблему добавим к методу Show класса Person virtual, то есть сделаем динамическое связывание.

virtual void Show() const { std::cout << m_name << ", " << m_age; }

Теперь в консоли получили такие данные:
Ivan, 12
Peter, 12, grade 5
Peter, 12, grade 5

Но нам следует добавить виртуальный деструктор, иначе может произойти утечка virtual ~Person(){}.
Просмотры: 17
Перейти в пост Открыть в MAX Сообщить о неточности
Загружаем изображение…
Глава 13. Наследование классов
Наследование необходимо, чтобы не писать множество классов, которые может объединять что-то общее. Наследование позволяет вынести общее в базовый класс, а специфику в производные.
В наследовании может быть множество отношений, в данной главе книги было представлено is-a (является). Возьмем школьную систему: ученик, учитель, завуч и директор (на рисунке представлено как будут наследоваться объекты).

//заголовочный файл person.h
#pragma once
#include <string>
 
class Person {
protected:
    std::string m_name;
    int m_age;
public:
    Person() : m_name("Noname"), m_age(0) {}
    Person(std::string name, int age) : m_name(name), m_age(age) {}
    void Show() const { std::cout << m_name << ", " << m_age << std::endl; }
};
 
class Student : public Person {
private:
    int m_grade;
public:
    Student() : Person("Noname", 0), m_grade(0) {}
    Student(std::string name, int age, int grade)
        : Person(name, age), m_grade(grade) {
    }
};

 

//main.cpp
#include <iostream>
#include "person.h"
 
int main() {
 
    Person Ivan("Ivan", 12);
    Ivan.Show
();
 
    Student Peter("Peter", 12, 5);
    Peter.Show
();;
 
}

Консоль:
Ivan, 12
Peter, 12 <- как можем заметить, не отображается класс ученика (grade)

Класс Student наследует класс Person открыто (public). Это значит, что у класса Student будут теже методы, что и в базовом классе.  
 
Также используются инициализаторы (могут использовать только в конструкторах), они позволяют инициализировать члены класса до того, как начнёт выполняться тело конструктора {}.
Есть разница в использовании инициализаторов и присваивания значений в теле конструктора.

// Вариант 1: список инициализаторов
Person(std::string name, int age) : m_name(name), m_age(age) {}
// Вариант 2: присваивание в теле
Person(std::string name, int age) {
    m_name = name;
    m_age = age;
};

Во втором варианте сначала name и age получают значения по умолчанию, а потом получают значения, в первом варианте – переменные получают значения, а после выполняется тело {}.

Некоторые правила:
1. Производный объект использует методы базового.
2. Указатель/ссылка базового на производный объект.

    Student Anna;
    Person* pt = &Anna;
    Person& ref = Anna;

3. Обратно (от производного на базовый) – нельзя без явного приведения (так как в производном у нас добавлены специфика, а в базовом нет таких полей связи с чем и возникает ошибка, при явном приведении это устраняется, но тоже нежелательно можем поймать мусор из памяти).

Student* ptr = (Student*)&Ivan;
Просмотры: 17
Перейти в пост Открыть в MAX Сообщить о неточности
Загружаем изображение…
Глава 12. Классы и динамическое выделение памяти
При почленном копировании копируется указатель, а не данные, что ведёт к двойному освобождению (конструктор Text() создаёт объект, деструктор ~Text() уничтожает объект). Показано на картинке.

#include <iostream>
#include <cstring>
 
class Text {
private:
    char * data;
    int length;
public:
    Text(const char * str) {
        length = std::strlen(str);
        data = new char[length + 1]; //динамическая память new
        std::strcpy(data, str);
    }
    ~Text() {
        delete[] data;
    }
};
 
int main() {
    Text t1("Hello");
    Text t2 = t1;
    return 0;
}

 
Здесь возникнет ошибка – двойное удаление, так как указатель data у объектов t1 и t2 указывает на одну и туже ячейку памяти, при завершении у одного объекта уже адрес у указателя удален, а у второго объекта указатель продолжает указывать на ту ячейку памяти, но адрес уже удален и повторное удаление невозможно, так как ячейка памяти уже очищена. В таких случаях используется глубокое копирование.

#include <iostream>
#include <cstring>
class Text {
private:
    char * data;
    int length;
public:
    //Конструктор по умолчанию
    Text(const char * str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
        std::cout << "Default constructor, data: " << &data << std::endl;
    }
    //Конструктор копирвания
    Text(const Text & other) {
        length = other.length;
        data = new char[length + 1];
        std::strcpy(data, other.data
);
        std::cout << "Copy constructor, data: "<< &data << std::endl;
    }
    // Оператор=
    Text & operator=(const Text & other) {
        if (this == &other) return *this;
        delete[] data; // сначала очищаем указатель
        length = other.length;
        data = new char[length + 1]; //добавляем новый адрес
        std::strcpy(data, other.data
);
        std::cout << "Operator=, this data: "<< &data << std::endl;
        std::cout << "Operator=, other data: "<< &other.data << std::endl;
        return *this;
    }
   
    ~Text() {
        delete[] data;
    }
};
 
int main() {
    Text t1("Hello");
    std::cout << "============" << std::endl;
    Text t2 = t1;
    std::cout << "============" << std::endl;
    Text t3("Hi");
    std::cout << "============" << std::endl;
    t3 = t1;
   
    return 0;
}

Консоль:
Default constructor, data: 0xffffd4a86330
============
Copy constructor, data: 0xffffd4a86320
============
Default constructor, data: 0xffffd4a86310
============
Operator=, this data: 0xffffd4a86310
Operator=, other data: 0xffffd4a86330

Здесь добавился конструктор копирования (в отличие от конструктора по умолчанию можем заметить что принимает тип Text, а не const char *), перегруженный оператор присваивания (как раз-таки, когда у нас при двух созданных объектах один приравнивается к другому).
Специальные функции-члены, генерируемые компилятором
1. Автоматически создаются, если не определены:
2. Конструктор по умолчанию
3. Конструктор копирования
4. Операция присваивания
5. Деструктор
6. Операция взятия адреса
Конструктор копирования по умолчанию — почленное копирование (поверхностное).
Операция присваивания по умолчанию — почленное присваивание.
Просмотры: 18
Перейти в пост Открыть в MAX Сообщить о неточности
Загружаем изображение…
Основное из 11 главы Прата.

Дружественные функции.
Это функции, не являющиеся членами класса, но имеющие доступ к закрытым данным. Нужны, когда левый операнд не является объектом класса. Для того, чтобы функция стала дружественной, необходимо добавить friend перед типом функции, пример:
friend std::ostream& operator<<(std::ostream & os, const Distance & d);
Здесь, кстати, мы используем перегрузку оператора <<, для того, чтобы выводить объекты необходимо возвращать сам объект cout (std::ostream), который мы обозначим как os.
Преобразование.
Не думаю, что это хороший способ, но посмотрим на него.
У нас будет конструктор с 1 аргументом и мы благодаря это сможем делать неявное преобразование:
file.h //заголовочный файл
Distance(int m);
main.cpp //основной файл исполнения


#include <iostream>
#include "file.h"
Distance::Distance(int m, int cm) : meters(m), centimeters(cm) {}
Distance::Distance(int m) {
    meters = m;
};
Distance Distance::operator+(const Distance& other) {
    centimeters = centimeters + other.centimeters;
    meters += other.meters + centimeters / 100;
    centimeters %= 100;
    return *this;
}
std::ostream & operator<<(std::ostream & os, const Distance& d) {
    os << "m: "<< d.meters << " cm: "  << d.centimeters << std::endl;
    return os;
}
void Distance::show() const {
    std::cout << meters << " m " << centimeters << " cm" << std::endl;
}
 
int main() {
    Distance d = 50;
    std::cout << d << std::endl;
    return 0;
}

 
Чем плохо такое преобразование, может возникнуть момент, когда мы случайно объект делаем числом или другим типом не ожидая этого, как показано в последней строке – так происходит из-за неявного преобразования. Чтобы исключить  возможные проблемы, в заголовочный файл file.h добавим к конструктору ключевое слово explicit, что запретит неявное преобразование.

    // Distance d = 50;  - ошибка, неявное преобразование
    Distance d(50); // явное преобразование, мы так и задумывали.
    std::cout << d << std::endl;
Просмотры: 17
Перейти в пост Открыть в MAX Сообщить о неточности
ВКонтакте Телеграм Одноклассники
Избранное в datool.ru

Следите за каналом в личном кабинете

Зарегистрируйтесь в datool.ru, чтобы добавить канал «Я просто спросить про указатели C++ (изучаю С++).» в избранное и отслеживать его показатели.

Зарегистрироваться
Для владельцев каналов

Подтвердите свой канал

Войдите или зарегистрируйтесь в datool.ru, затем добавьте нашего бота в канал по инструкции в личном кабинете. После проверки владения замок исчезнет и вам станет доступно управление каналом.

Войти или зарегистрироваться

Сообщить о неточности

Опишите, что нужно исправить. Если есть источник, добавьте ссылку.

Вы покидаете datool.ru

Ссылка ведёт на внешний сайт. datool.ru не отвечает за его содержимое — проверьте адрес, прежде чем вводить пароли или платёжные данные.

Переход через 10 с

Перейти