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

Основы C (2)

2024-07-12

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

Оглавление

1. Классы и объекты

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

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

1.3 Домен класса

2. Создание экземпляра

2.1 Концепция создания экземпляров

2.2 Размер объекта

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

4. Функции-члены классов по умолчанию.

4.1 Конструктор

4.2 Деструктор

4.5 Перегрузка оператора


1. Классы и объекты

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

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

class — ключевое слово, определяющее класс, Stack — имя класса, а {} — тело класса. Обратите внимание, что точка с запятой в конце определения не опускается. Содержимое тела класса называется членами класса: переменные в классе называются атрибутами, а переменные-члены класса называются методами или функциями-членами класса;

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //成员变量
  7. int* a;
  8. int top;
  9. int capacity;
  10. //成员函数
  11. void Push()
  12. {
  13. }
  14. void Pop()
  15. {
  16. }
  17. };//分号不能省略
  18. int main()
  19. {
  20. return 0;
  21. }

  • Чтобы различать переменные-члены, обычно к переменной-члену обычно добавляют специальный идентификатор, например, начинающийся с _ или m_ до или после переменной-члена.Для этого синтаксиса C++ не существует каких-либо правил, это всего лишь вопрос личных предпочтений или предпочтений компании.
  1. //为区分成员变量,一般前面加_
  2. //成员变量
  3. int* _a;
  4. int _top;
  5. int _capacity;
  • Struct в C++ также может определять классы. C++ совместим с использованием struct в C. В то же время struct была повышена до класса. При обычных обстоятельствах мы также можем определять функции. по-прежнему рекомендую использовать class для определения классов.

  • Члены, определенные в классе, по умолчанию являются встроенными.

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

C++ — это способ реализовать инкапсуляцию, используя классы для объединения свойств и методов объекта, чтобы сделать объект более полным, и выборочно предоставлять его интерфейс внешним пользователям через права доступа.

  • К членам, измененным с помощью public (public), можно получить прямой доступ вне класса, к членам, измененным с помощью protected (protected) и Private (private), нельзя получить прямой доступ за пределами класса, protected и Private — это одно и то же.
  • Область разрешения доступа начинается с позиции, где появляется разрешение доступа, до появления следующего квалификатора доступа. Если последующего квалификатора доступа нет, область заканчивается }, что является концом класса.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. ///
  7. void Push()
  8. {
  9. }
  10. //Push 没给限定符 class默认私有 private
  11. ///
  12. public:
  13. void Pop()
  14. {
  15. }
  16. int Swap()
  17. {
  18. }
  19. //Pop和Swap 被public修饰,直到下一个限定符出现之前都为公有
  20. ///
  21. protected:
  22. int add();
  23. //add 被public修饰,直到下一个限定符出现之前都为保护
  24. /// /
  25. private:
  26. int* _a;
  27. int _top;
  28. int _capacity;
  29. //成员变量被private修饰,直到}结束都为私有
  30. };
  31. int main()
  32. {
  33. Stack st;
  34. //公有可以访问
  35. st.Pop();
  36. st.Swap();
  37. //私有不可访问
  38. st._top;
  39. return 0;
  40. }

Дополнительные замечания:

  1. Если члены определения класса не изменяются квалификаторами доступа, они по умолчанию являются частными, а структура по умолчанию — общедоступной.
  2. Как правило, переменные-члены будут ограничены частными/защищенными, а функции-члены, которые должны использоваться другими, будут размещены как общедоступные.

1.3 Домен класса

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

Домен класса влияет на правила поиска компиляции. Если в Init в следующей программе не указан стек домена класса, компилятор будет рассматривать Init как глобальную функцию. Если во время компиляции он не сможет найти такие члены, как _top, он перейдет к функции. домен класса, чтобы найти их.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. public:
  7. void Init(int x, int y);
  8. };
  9. void Stack::Init(int x, int y)
  10. {
  11. _top = x;
  12. _capacity = y;
  13. }
  14. int main()
  15. {
  16. return 0;
  17. }

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

  1. Объявления функций и определения в классе разделены. После создания класса формируется новый домен класса. Необходимо указать домен класса, иначе он недоступен.

2. Создание экземпляра

2.1 Концепция создания экземпляров

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

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //声明
  7. int* _a;
  8. int _top;
  9. int _capacity;
  10. };
  11. int main()
  12. {
  13. Stack::_top = 2024;
  14. //编译器报错,_top只是声明,并未实例化
  15. return 0;
  16. }
  • Класс может создавать экземпляры нескольких объектов, а созданные объекты занимают фактическое физическое пространство и хранят переменные-члены. Например: создание экземпляров объектов из класса похоже на использование чертежей архитектурного проекта для строительства дома в реальности. здание, и оно не может жить в людях, только когда дом построен по проектным чертежам, люди могут жить в доме.Тот же класс похож на проектный чертеж. Он просто сообщает компилятору, сколько памяти будет открыто, но не открывает память. Для хранения данных выделяется только созданным объектам.
    1. //text.cpp
    2. #include<iostream>
    3. using namespace std;
    4. class Stack
    5. {
    6. //声明
    7. int* _a;
    8. int _top;
    9. int _capacity;
    10. };
    11. int main()
    12. {
    13. Stack st;
    14. st._top=2024;
    15. //Stack实例化出st,系统已经给st分配内存了,可以存储数据,编译通过
    16. return 0;
    17. }

    2.2 Размер объекта

  • Проанализируйте, какие члены есть у объекта класса? Каждый объект, экземпляр которого создается классом, имеет независимое пространство данных, поэтому объект должен содержать переменные-члены. Итак, включены ли функции-члены? Во-первых, после компиляции функции это часть инструкций, которую нельзя сохранить в объекте. Эти инструкции хранятся в отдельной области (сегменте кода). Если объект необходимо сохранить, это может быть только указатель на него. функция-член. Необходимо ли хранить указатели в объекте? Date создает экземпляры двух объектов d1 и d2. И di, и d2 имеют независимые переменные-члены _year/_month/_day для хранения собственных данных, но функции-члены Init для d1 и d2. Указатель /Print. один и тот же, и он теряется, если хранится в объекте. Если вы используете Date для создания экземпляров 100 объектов, то указатель функции-члена будет сохраняться повторно 100 раз, что слишком расточительно. На самом деле указатель функции не нужно сохранять. Указатель функции — это адрес. Вызывающая функция компилируется в ассемблерную инструкцию [адрес вызова]. Фактически, компилятор должен найти адрес функции при компиляции и компоновке. . Он не найден во время выполнения. Его можно найти только динамически. Состояние обнаруживается во время выполнения, поэтому адрес функции необходимо сохранить.

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

  • Первый член находится по адресу, смещенному 0 от структуры.
  • Другие переменные-члены должны быть выровнены по адресам, кратным номеру выравнивания.
  • Номер выравнивания = меньшее из числа выравнивания компилятора по умолчанию и размера члена.
  • Номер выравнивания по умолчанию для платформы VS x64 — 4, а номер выравнивания по умолчанию для x86 — 8.
  • Общий размер структуры равен целому числу, кратному максимальному числу выравнивания (наибольшему из всех переменных типа и наименьшему числу выравнивания по умолчанию).
  • Если структуры являются вложенными, вложенная структура выравнивается до целого числа, кратного ее собственному максимальному числу выравнивания, а общий размер структуры равен целому числу, кратному всем максимальным числам выравнивания (включая число выравнивания вложенных структур).
  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << _ch << endl;
  7. }
  8. private:
  9. char _ch;
  10. int _i;
  11. };
  12. //_ch 是一个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存在_ch
  13. // _i是4个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存储_i
  14. class B
  15. {
  16. public:
  17. void Print()
  18. {
  19. //。。。
  20. }
  21. };
  22. class B
  23. {
  24. };
  25. //B和C里面没有存储任何成员变量,只有一个函数,可成员函数不存对象里面
  26. // 按理来说是0,但是结构体怎么会没大小,为表示对象存在C++对这种规定大小为1,为了占位标识对象存在

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

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

Например, прототипом Init в классе Date является void Init (Date * const this, int Year, int Month, Int Day. При доступе к переменным-членам в функциях-членах класса доступ к сути осуществляется через указатель this. например, _year в функции Init, this-&gt;_year=year.

опытный образец:

  1. class Date
  2. {
  3. void Print()
  4. {
  5. cout << _year << "n" << _month << "n" << _day << endl;
  6. }
  7. void Init( int year, int month,int day)
  8. {
  9. _year = year;
  10. _month = month;
  11. _day = day;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(2024,7,10);
  3. d1.Print();
  4. Date d2;
  5. d2.Init(2024, 7, 9);
  6. d2.Print();

реальный прототип

  1. class Date
  2. {
  3. void Init(Date* const this,int year, int month,int day)
  4. {
  5. this->_year = year;
  6. this->_month = month;
  7. this->_day = day;
  8. }
  9. void Printf(Date* const this)
  10. {
  11. cout << this->_year << "n" <<this-> _month << "n" << this->_day << endl;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(&d1,2024,7,10);
  3. d1.Print(&d1);
  4. Date d2;
  5. d2.Init(&d2,2024, 7, 9);
  6. d2.Print();

В C++ предусмотрено, что этот указатель нельзя записывать в позиции фактических параметров и формальных параметров (компилятор будет обрабатывать его при компиляции), но этот указатель можно использовать явно в теле функции. Этот указатель нельзя изменить, но его нельзя изменить. содержимое, на которое указывает этот указатель, может

этот указатель хранится в стеке

4. Функции-члены классов по умолчанию.

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

4.1 Конструктор

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

Особенности конструктора:

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

  1. class Date
  2. {public:
  3. //1.无参构造函数
  4. Date()
  5. {
  6. _year = 1;
  7. _month = 1;
  8. _day = 1;
  9. }
  10. //2.带参构造函数
  11. Date(int year, int month, int day)
  12. {
  13. _year = year;
  14. _month = month;
  15. _day = day;
  16. }
  17. //3.全缺省构造函数
  18. Date(int year = 1, int month = 1, int day = 1)
  19. {
  20. _year = year;
  21. _month = month;
  22. _day = day;
  23. }
  24. private:
  25. int _year;
  26. int _month;
  27. int _day;
  28. };

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

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

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. private:
  19. STDataType* _a;
  20. size_t _capacity;
  21. size_t _top;
  22. };
  23. //两个Stack实现队列
  24. class MyQueue
  25. {
  26. private:
  27. int size;
  28. Stack pushst;
  29. Stack popst;
  30. };
  31. int main()
  32. {
  33. MyQueue my;
  34. return 0;
  35. }

В C++ типы делятся на пользовательские и встроенные (базовые типы). Встроенные типы — это собственные типы данных, предоставляемые языком, например int/char/double/pointer и т. д. Пользовательские типы — это типы, которые мы определяем сами, используя такие ключевые слова, как class/struct.Конструктор здесь инициализируется автоматически, а VS также инициализирует размер встроенного типа. Разные компиляторы имеют разные значения инициализации, и C++ их не задает.

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

Резюме: В большинстве случаев нам необходимо реализовать конструктор самостоятельно. В некоторых случаях он похож на MyQueue, и когда у Stack есть конструктор по умолчанию, MyQueue может быть автоматически сгенерирован и использован.

4.2 Деструктор

  1. ~Stack()
  2. {
  3. free(_a);
  4. _a = nullptr;
  5. _top = _capacity = 0;
  6. }

Характеристики деструктора:

1. Имени деструктора предшествуют символы~

2. Никаких параметров и возвращаемого значения (согласно конструктору).

3. Класс может иметь только один деструктор.Если определение не отображается, система автоматически сгенерирует деструктор по умолчанию.

4. Когда цикл объявления объекта завершится, система автоматически вызовет деструктор.

5. Как и в случае с конструктором, мы не пишем деструктор, автоматически сгенерированный компилятором, и не обрабатываем члены встроенного типа. Члены пользовательского типа будут вызывать другие деструкторы.

6. Следует также отметить, что когда мы отображаем деструктор, также будет вызываться деструктор члена пользовательского типа, а это означает, что деструктор члена пользовательского типа будет вызываться автоматически независимо от ситуации.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. ~Stack()
  19. {
  20. free(_a);
  21. _a = nullptr;
  22. _top=_capacity=0;
  23. }
  24. private:
  25. STDataType* _a;
  26. size_t _capacity;
  27. size_t _top;
  28. };
  29. //两个Stack实现队列
  30. class MyQueue
  31. {public:
  32. //编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化
  33. //编译器默认生成MyQueue的析构函数调用了Stack的析构,释放了Stack内部的资源
  34. //显示写析构也会调用Stack的析构
  35. ~MyQueue()
  36. {
  37. cout << "~MyQueue" << endl;
  38. }
  39. private:
  40. Stack pushst;
  41. Stack popst;
  42. };
  43. int main()
  44. {
  45. MyQueue my;
  46. return 0;
  47. }

Деструктор в MyQueue ничего не делает, но C++ предусматривает, что для освобождения памяти будут вызываться другие деструкторы.

Если никакие ресурсы не запрашиваются, деструктор не нужно записывать, и можно напрямую использовать деструктор по умолчанию, сгенерированный компилятором, например Date. Если можно использовать сгенерированный деструктор по умолчанию, нет необходимости явно записывать деструктор. например MyQueue, но есть ресурсное приложение. При деструкции обязательно напишите деструктор напрямую, иначе это приведет к утечке ресурсов, например Stack.

4.5 Перегрузка оператора

  • Когда операторы используются в типизированных объектах, язык C++ позволяет нам указывать новые значения в форме перегрузки операторов. В C++ предусмотрено, что когда объект типа класса использует оператор, он должен быть преобразован в вызов соответствующей перегрузки оператора. В противном случае компилятор сообщит об ошибке.
  • Перегрузка оператора — это функция с определенным именем. Ее имя состоит из оператора и оператора, который будет определен позже.Как и другие функции, она также имеет тип возвращаемого значения и список параметров, а также тело функции.
  1. bool operator<(Date d1, Date d2)
  2. {
  3. }
  4. bool operator==(Date d1,Date d2)
  5. {
  6. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  7. }
  • Перегруженная операторная функция принимает столько же параметров, сколько параметров, с которыми действует оператор.Унарный транспортный комфорт имеет один параметр, а бинарный оператор — два параметра. Левый операнд бинарного оператора передается в первый параметр, а правый операнд — во второй параметр.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Date
  5. {
  6. public:
  7. Date(int year, int month, int day)
  8. {
  9. _year= year;
  10. _month = month;
  11. _day = day;
  12. }
  13. int _year;
  14. int _month;
  15. int _day;
  16. };
  17. bool operator<(Date d1, Date d2)
  18. {
  19. }
  20. bool operator==(Date d1,Date d2)
  21. {
  22. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  23. }
  24. int main()
  25. {
  26. Date d1(2024, 7, 10);
  27. Date d2(2024,7,9);
  28. //两种用法都可以
  29. d1 == d2;
  30. operator==(d1 , d2);
  31. return 0;
  32. }
  • Если перегруженная функция-оператор является функцией-членом, ее первый операнд по умолчанию передается неявному указателю this. Поэтому, когда оператор перегружается как функция-член, он имеет на один параметр меньше, чем операнд.
  • После перегрузки оператора его приоритет и ассоциативность остаются согласованными со встроенными операциями с типами.
  • Вы не можете создать сексуальный оператор, объединив совпадения, которых нет в синтаксисе: например, оператор @.