Классы и объекты C++ (1)


предисловие

Эта глава официально положит начало C++серии объяснений, а эта статья даст предварительное представление о классах и объектах.

1. От процессно-ориентированного к объектно-ориентированному

  • C语言Он ориентирован на процесс, фокусируется на процессе , анализирует шаги для решения проблемы и решает проблему шаг за шагом с помощью вызовов функций.
  • C++Он основан на объектно-ориентированном подходе, фокусируясь на объектах , разбивая одну вещь на разные объекты и полагаясь на взаимодействие между объектами для завершения.

Например: Постирать

C语言:
вставьте сюда описание изображения
C++:

  1. Четыре объекта: люди, одежда, стиральный порошок, стиральная машина.
  2. Процесс стирки одежды: человек кладет одежду в стиральную машину, насыпает стиральный порошок, запускает стиральную машину, стиральная машина стирает одежду и сушит ее.
  3. Весь процесс завершается взаимодействием между четырьмя объектами: людьми, одеждой, стиральным порошком и стиральной машиной Людям не нужно заботиться о том, как стиральная машина стирает одежду и как она ее сушит.
    вставьте сюда описание изображения

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++中лучше использовать новое имя :classstruct

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
{
    
    
	//...类的主体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

Как показано выше, определение класса очень похоже на определение структуры:

  1. class: ключевое слово, определяющее класс
  2. class Name: имя класса
  3. Основная часть класса: {}середина — это основная часть класса, состоящая из переменных-членов и функций-членов.
  4. Переменная-член: также известная как атрибут класса, относится к переменной, определенной в классе.
  5. Функция-член: также известная как метод класса, относится к функции, определенной в классе.

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

Обычно существует два способа определения класса:

  1. Объявление и определение помещаются в тело класса.Примечание: если функция-член определена в классе, компилятор может рассматривать ее как встроенную функцию .
//日期类
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;
};
  1. Объявление класса помещается в файл .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

вставьте сюда описание изображения
Описание квалификатора доступа :

  1. classРазрешения на доступ по умолчанию: privateдля struct( publicиз-за structсовместимости C)
  2. publicК измененным членам можно получить прямой доступ вне класса
  3. protectedи модифицированные члены не могут быть напрямую доступныprivate вне класса (здесь и аналогично)protectedprivate
  4. Области доступа начинаются с появления этого квалификатора доступа до следующего появления квалификатора доступа.
  5. Если позади квалификатора доступа нет, область действия заканчивается в конце }класса.

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

Так вот вопрос : в чем разница между c++нейтрализацией structи нейтрализацией?class

Ответ: C++он должен быть совместимым C语言, поэтому его можно использовать не C++только как структуру , но и для определения класса , что аналогично определению класса.Разница в том, что разрешение на доступ по умолчанию для определенного класса равно , а разрешение на доступ по умолчанию для определенного класса — .structC++structclassstructpublicclassprivate

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?
    Это связано с тем, что пустой класс является особым, и компилятор дает пустому классу байт для уникальной идентификации объектов этого класса .

Дополнение: Правила выравнивания памяти структуры :

  1. Первый член находится по адресу структуры по смещению .0
  2. Другие переменные-члены должны быть выровнены по адресу, который является целым числом, кратным определенному числу (номеру выравнивания) . Примечание. Выравнивание = меньшее значение между выравниванием по умолчанию компилятора и размером элемента . Номер выравнивания по умолчанию в VS равен 8.
  3. Общий размер структуры: целое число, кратное максимальному номеру выравнивания (самый большой из всех типов переменных и наименьший параметр выравнивания по умолчанию) .
  4. Если структура является вложенной, вложенная структура выравнивается до целого числа, кратного ее собственному максимальному выравниванию , а общий размер структуры является целым числом , кратным всем максимальным выравниваниям (включая выравнивание вложенной структуры) .

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и , и в теле функции нет различий между разными объектами , поэтому, когда функция вызывается , как функция узнает, что объект должен быть установлен вместо объекта?Printd1Initd1d2

C++this指针Решите эту проблему введением :

C++Компилятор добавляет скрытый параметр-указатель к каждой нестатической функции-члену , заставляя указатель указывать на текущий объект (объект, который вызывает функцию во время выполнения функции), и доступ ко всем операциям с переменными-членами в теле функции осуществляется через этот указатель . Просто все операции прозрачны для пользователя, то есть пользователю не нужно их передавать, а компилятор выполняет их автоматически .

2. Характеристики этого указателя

вставьте сюда описание изображения
thisУказатели обладают следующими свойствами :

  1. thisТип указателя: тип * const, то есть в функции-члене, не может присвоить thisзначение указателю
void Init(int year, int month, int day)
{
    
    
	//错误示例
	this=nullptr;
}
  1. Может использоваться только внутри функций-членов
  2. thisУказатель по существу является формальным параметром функции-члена.Когда объект вызывает функцию-член, адрес объекта передается формальному параметру в качестве фактического thisпараметра . Таким образом, указатель не хранится в объектеthis .
  3. 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 для второго курса, потому что:

  • PCall Print, не будет разыменован, т.к. адрес не 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 функции работы со стеком имеют следующую общность:

  1. Первый параметр каждой функцииStack*
  2. Первый параметр должен быть обнаружен в функции , потому что параметр может бытьNULL
  3. В функции Stack*управление стеком осуществляется через параметры.
  4. Адрес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语言


Это конец этой статьи, текст кода не прост, пожалуйста, поддержите меня!

Supongo que te gusta

Origin blog.csdn.net/weixin_67401157/article/details/130302091
Recomendado
Clasificación