Обмен технологиями

Классы и объекты (часть 1)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

В предыдущей главе мы кратко упомянули некоторые понятия классов и объектов. Теперь давайте рассмотрим их вместе.

Определение класса

Формат определения класса

class — это ключевое слово, определяющее класс, за которым следует имя определяющего класса, {} — это основное тело класса, за которым следует «;», а функции-члены, определенные в классе, по умолчанию расширяются (встроенные).

квалификатор доступа

Формат инкапсуляции, разделенный на три типа: общедоступный, частный и защищенный. Объем разрешений доступа начинается с позиции, где появляется квалификатор доступа, до появления следующего квалификатора доступа. Если последующего квалификатора доступа нет, он будет использовать домен. заканчивается на } т.е. class.

домен класса

Класс определяет новую область видимости. Все члены класса находятся в области видимости класса. При определении членов вне класса необходимо использовать оператор ::scope, чтобы указать, к какому домену класса принадлежит элемент.

Ниже представлен художественный фильм///

Создать экземпляр

концепция

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

Подводя итог в одном предложении: только когда объекты создаются с помощью классов, они будут выделять пространство (точно так же, как строительство дома с чертежами будет занимать пространство).

  1. class Date
  2. {
  3. public:
  4. void Init(int year, int month, int day)
  5. {
  6. _year = year;
  7. _month = month;
  8. _day = day;
  9. }
  10. void Print()
  11. {
  12. cout << _year << "/" << _month << "/" << _day << endl;
  13. }
  14. private:
  15. // 这⾥只是声明,没有开空间
  16. int _year;
  17. int _month;
  18. int _day;
  19. };

В приведенном выше коде определяются только частные переменные-члены, и новое пространство не открывается;

  1. // Date类实例化出对象d1和d2
  2. Date d1;
  3. Date d2;

Затем создаются экземпляры двух новых объектов, и открывается новое пространство.

Правила выравнивания памяти

определение

• Первый член находится по адресу, смещенному на 0 от структуры.
• Другие переменные-члены должны быть сопоставлены с адресами, которые являются целыми числами, кратными определенному числу (логарифму).
• Примечание: логарифм = меньшее из логарифма компилятора по умолчанию и размера члена.
• Логарифм по умолчанию в VS равен 8.
• Общий размер структуры: целое число, кратное максимальному числу пар (наибольшая из всех типов переменных и наименьший параметр пары по умолчанию).
• Если структура является вложенной, вложенная структура выравнивается по целому числу, кратному ее максимальному логарифму, и общему размеру структуры.
Это целое число, кратное всем наибольшим логарифмам (включая логарифмы вложенных структур).

Это то, что мы знаем на языке C. В качестве примера возьмем следующие вопросы:

  1. // 计算⼀下A实例化的对象是多⼤?
  2. class A
  3. {
  4. public:
  5. void Print()
  6. {
  7. cout << _ch << endl;
  8. }
  9. private:
  10. char _ch;
  11. int _i;
  12. };

Функция не вызывается, поэтому просто посмотрите на переменные-члены. _ch — символьный тип размером 1 байт; _i — тип int размером 4 байта. Наибольшее пространство, занимаемое всеми членами, — это. номер выравнивания, то есть _ Поместите ch в позицию с индексом 0, оставьте три пробела пустыми, а затем поставьте _i. Он занимает в общей сложности 8 байтов пространства от 0 до 7, что является целым числом, кратным максимальному номеру выравнивания. . Оно соответствует условиям, поэтому ответ на этот вопрос — 8 .

А что, если это именно эти два вопроса?

  1. class B
  2. {
  3. public:
  4. void Print()
  5. {
  6. //...
  7. }
  8. };
  9. class C
  10. {
  11. };

Мы можем обнаружить, что в классе B есть пустая функция и нет членов в классе C. В языке C мы заставляем оба класса занимать 1 байт пространства.

этот указатель

концепция

ДВ классе ate есть две функции-члена: Init и Print. В теле функции нет различия между разными объектами. Затем, когда d1 вызывает Init и Print.
Как функция при печати функции узнает, должна ли она обращаться к объекту d1 или к объекту d2?Тогда мы увидим, что C++ дает
Неявный указатель this решает проблему здесь
• После компиляции компилятора функции-члены класса по умолчанию добавляют указатель текущего типа класса, называемый этим указателем, в первую позицию формального параметра. Например, реальный прототип класса Init of Date — void Init(Date* const this, intyear,
целочисленный месяц, целочисленный день)
• При доступе к переменным-членам в функциях-членах класса доступ к ним осуществляется по указателю this. Например, при присвоении значения _year в функции Init это:
&gt;_год = год;
• В C++ предусмотрено, что этот указатель не может быть записан явно в позиции фактических параметров и формальных параметров (компилятор будет обрабатывать это во время компиляции), но этот указатель можно явно использовать в теле функции.

например

  1. class Date
  2. {
  3. public:
  4. // void Init(Date* const this, int year, int month, int day)
  5. void Init(int year, int month, int day)
  6. {
  7. // 编译报错:error C2106: “=”: 左操作数必须为左值
  8. // this = nullptr;
  9. // this->_year = year;
  10. _year = year;
  11. this->_month = month;
  12. this->_day = day;
  13. }
  14. void Print()
  15. {
  16. cout << _year << "/" << _month << "/" << _day << endl;
  17. }
  18. private:
  19. // 这⾥只是声明,没有开空间
  20. int _year;
  21. int _month;
  22. int _day;
  23. };
  24. int main()
  25. {
  26. // Date类实例化出对象d1和d2
  27. Date d1;
  28. Date d2;
  29. // d1.Init(&d1, 2024, 3, 31);this指针
  30. d1.Init(2024, 3, 31);
  31. // d1.Init(&d2, 2024, 7, 5);
  32. d2.Init(2024, 7, 5);
  33. //不能自己写
  34. return 0;
  35. }

Для записи реальных параметров в функцию Init C++ добавит невидимую константу this указатель (которая не может быть изменена) к первой переменной функции, как показано в комментариях к функции.

Уведомление:

Пока нулевой указатель не разыменован, об ошибке не будет сообщено.

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. }
  8. private:
  9. int _a;
  10. };
  11. int main()
  12. {
  13. A* p = nullptr;
  14. p->Print();
  15. return 0;
  16. }

Хотя p присвоен нулевой указатель и он указывает на функцию печати, он не разыменовывается, поэтому здесь не будет сообщено об ошибке. И эта программа:

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. cout << _a << endl;//这个地方多了一句
  8. }
  9. private:
  10. int _a;
  11. };
  12. int main()
  13. {
  14. A* p = nullptr;
  15. p->Print();
  16. return 0;
  17. }

По сравнению с предыдущей программой, здесь есть только одно предложение cout &lt;&lt; _a &lt;&lt; endl, но в этот момент нулевой указатель указывает на переменную-член, а нулевой указатель будет разыменован, поэтому программа сообщит об ошибке.

Функция-член класса по умолчанию

концепция

Функция-член по умолчанию — это функция-член, которая не реализована явно пользователем и автоматически генерируется компилятором. Она называется функцией-членом по умолчанию. Для класса, если мы его не напишем, компилятор по умолчанию сгенерирует следующие 6 функций-членов, а именно:

Конструктор

определение

Конструктор — это специальная функция-член. Следует отметить, что хотя имя конструктора и называется «constructor», основная задача конструктора — не открывать пространство для создания объектов (локальный объект, который мы часто используем, — это пространство, в котором создается стек). кадр создан) ), вместо этого объект инициализируется при создании экземпляра объекта. Суть в замене инициализации функцией Init.

Функции

Пять основных функций

1. Имя функции совпадает с именем класса.
2. Нет возвращаемого значения. (Не нужно писать void)
3. Когда объект будет создан, система автоматически вызовет соответствующий конструктор.
4. Конструкторы могут быть перегружены.
5. Если в классе нет явно определенного конструктора, компилятор C++ автоматически создаст конструктор по умолчанию без параметров. Как только пользователь явно определит его, компилятор больше не будет его генерировать.

Первые пять пунктов относительно просты, приведем пример:

  1. class Date
  2. {
  3. public:
  4. //1.⽆参构造函数,无需写void
  5. Date()//若不写,则构造函数会写出这一中无参构造函数
  6. {
  7. _year = 1;
  8. _month = 1;
  9. _day = 1;
  10. }
  11. //2.全缺省构造函数
  12. Date(int year = 1, int month = 1, int day = 1)
  13. {
  14. _year = year;
  15. _month = month;
  16. _day = day;
  17. }
  18. private:
  19. int _year;
  20. int _month;
  21. int _day;
  22. }
  23. int main()
  24. {
  25. Date d1;//此时相当于已经自动调用了Init函数
  26. Date d2;
  27. return 0;
  28. }

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

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

6. Конструкторы без параметров, полные конструкторы по умолчанию и конструкторы, генерируемые компилятором по умолчанию, когда мы не пишем конструктор, — все они называются конструкторами по умолчанию. Однако одна и только одна из этих трех функций существует и не может существовать одновременно. Хотя конструктор без параметров и полный конструктор по умолчанию представляют собой перегрузку функции, при их вызове может возникнуть неоднозначность. Следует отметить, что многие студенты думают, что конструктор по умолчанию — это конструктор по умолчанию, создаваемый компилятором. На самом деле конструктор без параметров и полный конструктор по умолчанию также являются конструкторами по умолчанию. Подводя итог, можно сказать, что они являются конструкторами, которые можно вызывать без передачи фактического значения. параметры. Это называется конструкцией по умолчанию.

6

Шестая функция более сложна: конструкторы без параметров, конструкторы по умолчанию и конструкторы, генерируемые компилятором по умолчанию, когда мы не пишем конструктор, — все они называются конструкторами по умолчанию, и конструктор по умолчанию может быть только один. как конструктор, сгенерированный компилятором по умолчанию!

7


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

деструктор

концепция

Функция деструктора противоположна функции конструктора. Деструктор не завершает уничтожение самого объекта. Например, локальный объект имеет кадр стека. Когда функция завершается и кадр стека уничтожается, он будет уничтожен. освобожден. Нам не нужно его контролировать. В C++ предусмотрено, что при уничтожении объектов автоматически вызывается деструктор для завершения очистки и освобождения ресурсов в объекте. Функция деструктора аналогична функции Destroy, которую мы реализовали ранее в Stack. Например, у Date нет Destroy. Фактически, нет ресурсов, которые нужно освободить. Строго говоря, Date не нуждается в деструкторе. Деструктор аналогичен функции Destroy. Его следует использовать для структур данных, которым требуются нелокальные объекты, такие как стеки и очереди, для подачи заявок на ресурсы.

Функции

1. Имени деструктора перед именем класса ставится символ ~. (Аналогично побитовому отрицанию в языке C)
2. Нет параметров и возвращаемого значения. (Это похоже на структуру, и нет необходимости добавлять пустоту)
3. Класс может иметь только один деструктор. Если это не указано явно, система автоматически сгенерирует деструктор по умолчанию.
4. Когда жизненный цикл объекта закончится, система автоматически вызовет деструктор.
5. Как и в случае с конструктором, если мы не напишем деструктор, автоматически сгенерированный компилятором, он не будет обрабатывать члены встроенного типа. Члены пользовательского типа будут вызывать свои деструкторы.
6. Следует также отметить, что когда мы явно пишем деструктор, также будет вызываться деструктор члена пользовательского типа. То есть деструктор члена пользовательского типа будет вызываться автоматически независимо от ситуации.
7. Если в классе нет заявленных ресурсов, деструктор не нужно писать, и можно напрямую использовать деструктор по умолчанию, созданный компилятором, например Date. Если можно использовать деструктор, созданный по умолчанию. нет необходимости его отображать. Напишите деструктор, например MyQueue, но когда есть приложение-ресурс, вы должны написать деструктор самостоятельно, иначе это приведет к утечке ресурсов, например Stack.
8. Для нескольких объектов в локальном домене C++ предусматривает, что сначала будут уничтожены те, которые определены позже.

Использование деструктора

Как и в случае с конструктором выше, деструктор по умолчанию, автоматически сгенерированный C++, обычно не будет нам очень полезен, но в некоторых редких случаях он более удобен, например, при использовании двух стеков для реализации очереди:

  1. public:
  2. Stack(int n = 4)//相当于Init
  3. {
  4. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  5. if (nullptr == _a)
  6. {
  7. perror("malloc fail!");
  8. return;
  9. }
  10. _capacity = n;
  11. _top = 0;
  12. }
  13. ~Stack()//相当于Destroy
  14. {
  15. cout << "~Stack()" << endl;
  16. free(_a);
  17. _a = nullptr;
  18. _top = _capacity = 0;
  19. }
  20. private:
  21. STDataType* _a;
  22. size_t _capacity;
  23. size_t _top;
  24. };
  25. class MyQueue
  26. {
  27. public:
  28. //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
  29. // 显⽰写析构,也会⾃动调⽤Stack的析构
  30. private:
  31. Stack pushst;
  32. Stack popst;
  33. };

В это время компилятор в MyQueue автоматически вызывает конструктор (инициализацию) и деструктор (уничтожение) стека.