Каталог статей
предисловие
Эта глава официально положит начало
C++
серии объяснений, а эта статья даст предварительное представление о классах и объектах.
1. От процессно-ориентированного к объектно-ориентированному
C语言
Он ориентирован на процесс, фокусируется на процессе , анализирует шаги для решения проблемы и решает проблему шаг за шагом с помощью вызовов функций.C++
Он основан на объектно-ориентированном подходе, фокусируясь на объектах , разбивая одну вещь на разные объекты и полагаясь на взаимодействие между объектами для завершения.
Например: Постирать
C语言
:
C++
:
- Четыре объекта: люди, одежда, стиральный порошок, стиральная машина.
- Процесс стирки одежды: человек кладет одежду в стиральную машину, насыпает стиральный порошок, запускает стиральную машину, стиральная машина стирает одежду и сушит ее.
- Весь процесс завершается взаимодействием между четырьмя объектами: людьми, одеждой, стиральным порошком и стиральной машиной Людям не нужно заботиться о том, как стиральная машина стирает одежду и как она ее сушит.
2. Введение в класс
C语言
В структуре могут быть определены только переменные В C++
структуре могут быть определены не только переменные , но и функции .
Когда мы раньше реализовывали структуру данных — стек C语言
, мы определяли ее так:
//C语言
typedef int dataOfStackType;
typedef struct stack
{
dataOfStackType* a;
int top;
int capacity;
}stack;
void StackInit(stack* ps);
void StackPush(stack* ps, dataOfStackType data);
void StackPop(stack* ps);
//...
В то время как в C++
, мы можем определить это так:
//C++
typedef int dataOfStackType;
typedef struct stack
{
void StackInit(stack* ps);
void StackPush(stack* ps, dataOfStackType data);
void StackPop(stack* ps);
//...
dataOfStackType* a;
int top;
int capacity;
}stack;
Как и в приведенном выше определении, вместо этого C++中
лучше использовать новое имя :class
struct
class stack
{
void StackInit(stack* ps);
void StackPush(stack* ps, dataOfStackType data);
void StackPop(stack* ps);
//...
dataOfStackType* a;
int top;
int capacity;
};
3. Определение класса
1. Что такое класс
class className
{
//...类的主体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
Как показано выше, определение класса очень похоже на определение структуры:
class
: ключевое слово, определяющее классclass Name
: имя класса- Основная часть класса:
{}
середина — это основная часть класса, состоящая из переменных-членов и функций-членов. - Переменная-член: также известная как атрибут класса, относится к переменной, определенной в классе.
- Функция-член: также известная как метод класса, относится к функции, определенной в классе.
2. Определение класса
Обычно существует два способа определения класса:
- Объявление и определение помещаются в тело класса.Примечание: если функция-член определена в классе, компилятор может рассматривать ее как встроенную функцию .
//日期类
class Date
{
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
int _year;
int _month;
int _day;
};
- Объявление класса помещается в файл .h , а определение функции-члена — в файл .cpp.Примечание : имя класса необходимо добавить перед именем функции-члена
::
.
//Date.h文件中声明类
//日期类
class Date
{
public:
void Init(int year, int month, int day);
void Print();
int _year;
int _month;
int _day;
};
//Date.c文件中定义成员函数
#include"Date.h"
void Date::Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Date::Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
В целом второй подход предпочтительнее.
3. Предложения по правилам именования переменных-членов
Давайте посмотрим на следующий класс:
class Date
{
public:
void Init(int year)
{
//这里的year到底是成员变量,还是函数形参?
year = year;
}
private:
int year;
};
Переменные выше неудобны? Это переменные-члены или параметры функции? Во избежание путаницы мы рекомендуем именовать переменные-члены следующим образом Init函数
:year
class Date
{
public:
void Init(int year)
{
_year = year;
}
private:
int _year;
};
// 或者这样
class Date
{
public:
void Init(int year)
{
mYear = year;
}
private:
int mYear;
};
Возможны и другие способы, в зависимости от требований компании. Как правило, достаточно добавить префикс или суффикс, чтобы определить разницу.
4. Классификатор доступа к классу и инкапсуляция
1. Квалификаторы доступа
Мы определяем различные функции-члены и переменные-члены в классе. Иногда мы не хотим, чтобы другие члены случайно обращались к некоторым членам класса, таким как переменные-члены, но другие члены открыты для внешнего мира, такие как функции-члены. Тогда нам нужно использовать квалификаторы доступа для изменения этих членов .
Существует три квалификатора доступа: public
, protected
,private
Описание квалификатора доступа :
class
Разрешения на доступ по умолчанию:private
дляstruct
(public
из-заstruct
совместимостиC
)public
К измененным членам можно получить прямой доступ вне классаprotected
и модифицированные члены не могут быть напрямую доступныprivate
вне класса (здесь и аналогично)protected
private
- Области доступа начинаются с появления этого квалификатора доступа до следующего появления квалификатора доступа.
- Если позади квалификатора доступа нет, область действия заканчивается в конце
}
класса.
Примечание. Квалификаторы доступа полезны только во время компиляции , когда данные сопоставляются с памятью, нет никакой разницы в квалификаторах доступа.
Так вот вопрос : в чем разница между c++
нейтрализацией struct
и нейтрализацией?class
Ответ:
C++
он должен быть совместимымC语言
, поэтому его можно использовать неC++
только как структуру , но и для определения класса , что аналогично определению класса.Разница в том, что разрешение на доступ по умолчанию для определенного класса равно , а разрешение на доступ по умолчанию для определенного класса — .struct
C++
struct
class
struct
public
class
private
2. Инкапсуляция
Объектно-ориентированный язык имеет три характеристики: инкапсуляция, наследование и полиморфизм.
На текущем этапе мы только изучаем особенности пакета. Так что же такое инкапсуляция?
Инкапсуляция: органично сочетайте данные и методы манипулирования данными , скрывайте свойства объектов и детали реализации и предоставляйте только интерфейсы для взаимодействия с объектами.
Инкапсуляция — это, по сути, своего рода управление, упрощающее пользователям использование классов :
- Например: для такого сложного устройства, как компьютер, пользователю предоставляется только кнопка питания, ввод с клавиатуры, монитор,
USB
разъем и т. д., что позволяет пользователю взаимодействовать с компьютером и выполнять повседневные дела. Но на самом деле реальная работа компьютера — это некоторые аппаратные компоненты, такие как процессор, видеокарта и память. - Пользователям компьютеров не нужно заботиться о внутренних основных компонентах , например о том, как расположены схемы на материнской плате, как устроен ЦП внутри и т. д. Пользователям нужно только знать, как включить компьютер и как взаимодействовать с компьютером через клавиатуру и мышь . Поэтому, когда производитель компьютера покидает завод, он помещает снаружи оболочку, чтобы скрыть детали внутренней реализации, и предоставляет только внешние переключатели, разъемы для мыши и клавиатуры и т. д., чтобы пользователи могли взаимодействовать с компьютером .
- Инкапсуляция в
C++
языке позволяет органично объединять данные и методы манипулирования данными через классы, скрывать детали внутренней реализации объектов через права доступа и контролировать, какие методы можно использовать непосредственно вне класса .
5. Объем занятия
Класс определяет новую область видимости , и все члены класса находятся в области действия класса. При определении членов вне класса вам необходимо использовать ::
оператор области действия, чтобы указать, к какому домену класса принадлежит член :
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " "<< _gender << " " << _age << endl;
}
Шесть. Создание экземпляра класса
Процесс создания объекта с классом называется созданием экземпляра класса:
//类的声明
class Date
{
public:
//...
int _year;
int _month;
int _day;
};
int main()
{
//类的实例化对象
Date d1;
Date d2;
Date d3;
return 0;
}
Реализация объектов в классах подобна строительству дома с использованием проектных чертежей в реальности . Классы подобны проектным чертежам . Проектируется только то, что необходимо, но нет физического здания. Созданный объект — это реальный дом , который может хранить данные и занимать физическое пространство.
Подобно проектному чертежу, проектный чертеж не может построить только один дом, а класс также может создавать экземпляры нескольких объектов.
7. Объектная модель класса
Класс может иметь как переменные-члены, так и функции-члены, так как же рассчитать размер объекта класса?
Как упоминалось выше, C++
классы и структуры по существу одинаковы. Чтобы быть совместимыми C语言
, C++
будут соблюдаться правила расчета размера структуры , а класс будет таким же, как и структура, поэтому класс будет продолжать использовать те же правила расчета, что и структура .
Примечание. Функции-члены находятся в сегменте открытого кода , поэтому функции-члены не занимают места . Следовательно, размер класса на самом деле равен размеру переменных-членов в классе в соответствии с правилами выравнивания памяти.
Давайте проверим это:
// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1() {
}
private:
int _a;
char _b;
};
// 类中仅有成员函数
class A2 {
public:
void f2() {
}
};
// 类中什么都没有---空类
class A3
{
};
int main()
{
cout << sizeof(A1) << endl;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
return 0;
}
результат операции:
Как показано на рисунке выше, результат согласуется с выводом: размер класса вычисляет только размер переменных-членов в классе, и следует правилам выравнивания памяти .
- Так почему размер пустого класса
1
?
Это связано с тем, что пустой класс является особым, и компилятор дает пустому классу байт для уникальной идентификации объектов этого класса .
Дополнение: Правила выравнивания памяти структуры :
- Первый член находится по адресу структуры по смещению .
0
- Другие переменные-члены должны быть выровнены по адресу, который является целым числом, кратным определенному числу (номеру выравнивания) . Примечание. Выравнивание = меньшее значение между выравниванием по умолчанию компилятора и размером элемента . Номер выравнивания по умолчанию в VS равен 8.
- Общий размер структуры: целое число, кратное максимальному номеру выравнивания (самый большой из всех типов переменных и наименьший параметр выравнивания по умолчанию) .
- Если структура является вложенной, вложенная структура выравнивается до целого числа, кратного ее собственному максимальному выравниванию , а общий размер структуры является целым числом , кратным всем максимальным выравниваниям (включая выравнивание вложенной структуры) .
8. этот указатель
1. Знайте указатель this
Давайте сначала определим класс даты Date
:
//日期类
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
По вышеуказанной операции есть такой вопрос:
Date
В классе есть две функции-членаInit
и , и в теле функции нет различий между разными объектами , поэтому, когда функция вызывается , как функция узнает, что объект должен быть установлен вместо объекта?Print
d1
Init
d1
d2
C++
this指针
Решите эту проблему введением :
C++
Компилятор добавляет скрытый параметр-указатель к каждой нестатической функции-члену , заставляя указатель указывать на текущий объект (объект, который вызывает функцию во время выполнения функции), и доступ ко всем операциям с переменными-членами в теле функции осуществляется через этот указатель . Просто все операции прозрачны для пользователя, то есть пользователю не нужно их передавать, а компилятор выполняет их автоматически .
2. Характеристики этого указателя
this
Указатели обладают следующими свойствами :
this
Тип указателя: тип* const
, то есть в функции-члене, не может присвоитьthis
значение указателю
void Init(int year, int month, int day)
{
//错误示例
this=nullptr;
}
- Может использоваться только внутри функций-членов
this
Указатель по существу является формальным параметром функции-члена.Когда объект вызывает функцию-член, адрес объекта передается формальному параметру в качестве фактическогоthis
параметра . Таким образом, указатель не хранится в объектеthis
.this
Указатель — это первый параметр неявного указателя функции-члена.Как правило, он автоматически передается компилятором черезecx
регистр и не требует передачи пользователем.
С this
указателями класс даты, который мы реализовали выше, также может быть реализован следующим образом:
//日期类
class Date
{
public:
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
void Print()
{
cout <<this-> _year << "-" <<this-> _month << "-" <<this-> _day << endl;
}
private:
int _year;
int _month;
int _day;
};
Но нет необходимости писать это в процессе собственно написания кода, потому что компилятор уже сделал это за нас, поэтому мы не хотим заниматься неблагодарными вещами.
【Вопросы для интервью】
this
Где существуют указатели ?
Ответ. Указатель this является формальным параметром функции-члена, поэтому существует область стека .this
Может ли указатель быть нулевым ?
Посмотрите на следующие два фрагмента кода:
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
Выберите C для первого курса и B для второго курса, потому что:
P
CallPrint
, не будет разыменован, т.к. адрес неPrint
находится в объекте ,P
переданном в качестве аргументаthis
указателю- Программа 1:
this
указатель нулевой, но функция не разыменовываетthis
указатель - Вторая программа:
this
указатель пустой, но доступ внутри функции_a
, суть в томthis->_a
, то есть разыменование нулевого указателя, и произойдет ошибка операции.
9. Сравнение языка C и стека реализации C++
1. Реализация на языке C
typedef int DataType;
typedef struct Stack
{
DataType* array;
int capacity;
int size;
}Stack;
void StackInit(Stack* ps)
{
assert(ps);
ps->array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == ps->array)
{
assert(0);
return;
}
ps->capacity = 3;
ps->size = 0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
if (ps->array)
{
free(ps->array);
ps->array = NULL;
ps->capacity = 0;
ps->size = 0;
}
}
void CheckCapacity(Stack* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity * 2;
DataType* temp = (DataType*)realloc(ps->array,
newcapacity * sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
ps->array = temp;
ps->capacity = newcapacity;
}
}
void StackPush(Stack* ps, DataType data)
{
assert(ps);
CheckCapacity(ps);
ps->array[ps->size] = data;
ps->size++;
}
int StackEmpty(Stack* ps)
{
assert(ps);
return 0 == ps->size;
}
void StackPop(Stack* ps)
{
if (StackEmpty(ps))
return;
ps->size--;
}
DataType StackTop(Stack* ps)
{
assert(!StackEmpty(ps));
return ps->array[ps->size - 1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->size;
}
int main()
{
Stack s;
StackInit(&s);
StackPush(&s, 1);
StackPush(&s, 2);
StackPush(&s, 3);
StackPush(&s, 4);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackPop(&s);
StackPop(&s);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackDestroy(&s);
return 0;
}
Можно видеть, что при реализации на языке C функции работы со стеком имеют следующую общность:
- Первый параметр каждой функции
Stack*
- Первый параметр должен быть обнаружен в функции , потому что параметр может быть
NULL
- В функции
Stack*
управление стеком осуществляется через параметры. - Адрес
Stack
структурной переменной должен быть передан при вызове
В структуре может быть определена только структура для хранения данных , а метод работы с данными не может быть помещен в структуру, то есть данные и способ работы с данными разделены , а реализация немного сложнее, включающая большое количество операций с указателями, и при невнимательности могут возникать ошибки.
2. Реализация С++
typedef int DataType;
class Stack
{
public:
void Init()
{
_array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = 3;
_size = 0;
}
void Push(DataType data)
{
CheckCapacity();
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
_size--;
}
DataType Top() {
return _array[_size - 1]; }
int Empty() {
return 0 == _size; }
int Size() {
return _size; }
void Destroy()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
DataType* temp = (DataType*)realloc(_array, newcapacity *
sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
_array = temp;
_capacity = newcapacity;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s;
s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Pop();
s.Pop();
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Destroy();
return 0;
}
C++
Классы могут использоваться для идеального объединения данных и методов манипулирования данными , а права доступа могут использоваться для управления теми методами, которые могут быть вызваны вне класса , то есть инкапсуляцией , что больше соответствует человеческому познанию вещи, как и использование ее собственных членов . При этом каждому методу не нужно передавать параметры , и параметры будут автоматически восстановлены после компиляции компилятора, то есть параметры поддерживаются компилятором, а параметры нужно поддерживать пользователю.Stack*
C++
Stack *
C语言
Это конец этой статьи, текст кода не прост, пожалуйста, поддержите меня!