[C++]: 클래스와 객체(1)

친구 여러분, 우리 다시 만났습니다 이번 호에서는 C++의 클래스와 객체에 대한 지식 포인트를 설명하겠습니다. 읽고 영감을 얻으셨다면 댓글을 남겨주세요. 최선을 다하시길 바랍니다. 소원이 이루어집니다!

C언어 칼럼: C언어: 입문부터 숙련까지

데이터 구조 열: 데이터 구조

개인 홈페이지: stackY、

목차

1. 프로세스 지향과 객체 지향에 대한 사전 이해 

2. 수업 소개

3. 클래스 정의

3.1 접근 한정자

3.2 클래스 정의의 두 가지 방법:

3.3 포장

4. 클래스 범위

5. 클래스 인스턴스화

6. 클래스 객체 모델

6.1 클래스 객체의 크기를 계산하는 방법

6.2 클래스 객체의 저장 방법

6.3 구조 메모리 정렬 규칙 

7. 이 포인터 

7.1 이 포인터 소개

7.2이 포인터의 특징

7.3 C 언어와 C++의 스택 구현 비교


1. 프로세스 지향과 객체 지향에 대한 사전 이해 

C 언어는 프로세스 지향적 이며 프로세스중점을 두고 문제 해결 단계를 분석하고 함수 호출을 통해 점진적으로 문제를 해결합니다.
C++는 객체지향을 기반으로 객체 에 초점을 맞추고 있으며 , 하나의 객체를 여러 객체로 분할하고 객체 간의 상호작용에 의존합니다.

2. 수업 소개

C 언어 구조에서는 변수만 정의할 수 있으며, C++에서는 변수뿐만 아니라 함수도 구조 정의할 수 있습니다. 예를 들어, 데이터 구조의 초기 단계에서 스택이 C 언어로 구현되었을 때는 구조체에 변수만 정의할 수 있었지만 , 이제 C++로 구현하면 구조체에 함수도 정의할 수 있다는 것을 알 수 있습니다.
C 언어 단계 스택
typedef int DataType;
typedef struct Stack
{
	DataType* arry;
	size_t size;
	size_t capacity;
}ST;

void STInit(ST* pst)
{
	assert(pst);
	pst->arry = NULL;
	//top为-1时,插入一个数据之后top指向的是刚刚插入数据的位置
	//pst->top = -1     
	//top为0时,插入一个数据之后top指向的是刚刚插入数据后面的位置
	pst->size = 0;
	pst->capacity = 0;
}

void STPush(ST* pst, DataType x)
{
	//...
}

void STPop(ST* pst)
{
	//...
}

int main()
{
	ST st;
	STInit(&st);
	STPush(&st, 1);
	STPush(&st, 2);
	STPush(&st, 3);
	STPop(&st);
	return 0;
}

C++로 작성된 경우 스택 인터페이스를 구조 내부에서 직접 정의할 수 있습니다.

typedef int DataType;
struct Stack
{
	void STInit(struct Stack* pst)
	{
		assert(pst);
		pst->arry = NULL;
		//top为-1时,插入一个数据之后top指向的是刚刚插入数据的位置
		//pst->top = -1     
		//top为0时,插入一个数据之后top指向的是刚刚插入数据后面的位置
		pst->size = 0;
		pst->capacity = 0;
	}

	void STPush(struct Stack* pst, DataType x)
	{
		//...
	}

	void STPop(struct Stack* pst)
	{
		//...
	}

	DataType* arry;
	size_t size;
	size_t capacity;
};


int main()
{
	struct Stack st;
	st.STInit(&st);
	st.STPush(&st, 1);
	st.STPush(&st, 2);
	st.STPush(&st, 3);
	st.STPop(&st);
	return 0;
}
위의 구조 정의를 위해 C++에서는 struct 대신 class를 사용하는 것이 좋습니다 .

3. 클래스 정의

class className
{
    // 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号
class는 클래스를 정의하는 키워드 이고, ClassName은 클래스 이름, {}는 클래스의 본문이며, 클래스 정의 끝에는 세미콜론을 생략할 수 없습니다 .
클래스 본문에 있는 내용을 클래스의 멤버라고 합니다. 클래스에 있는 변수를 클래스의 속성 또는 멤버 변수 라고 하며 , 클래스에 있는 함수를 클래스의 메서드 또는 멤버 함수 라고 합니다 .

 3.1 접근 한정자

C++에서 캡슐화를 구현하는 방식: 클래스를 사용하여 개체의 속성과 메서드를 결합하여 개체를 더욱 완전하게 만들고 액세스 권한을 통해 외부 사용자에게 해당 인터페이스를 선택적으로 제공합니다.
[접속 한정자 설명]
1. 공개 수정 멤버는 클래스 외부에서 직접 액세스할 수 있습니다.
2. Protected private 수정된 멤버는 클래스 외부에서 직접 액세스할 수 없습니다 ( 여기서 protected private 은 유사합니다 ).
3. 접근 권한 범위는 접근 한정자가 나타나는 위치부터 다음 접근 한정자가 나타날 때까지입니다.
4. 나중에 액세스 한정자가 없으면 범위는 클래스인 } 에서 끝납니다.
5. 클래스 기본 접근 권한은 private 이고 struct는 public 입니다 ( struct는 C 와 호환되어야 하기 때문입니다 ).
참고: 액세스 한정자는 컴파일 타임에만 유용하며, 데이터가 메모리에 매핑되면 액세스 한정자에는 차이가 없습니다.

C++에서 구조체와 클래스의 차이점은 무엇입니까?

C++는 C 언어와 호환되어야 하므로 C++의 struct를 구조체로 사용할 수 있습니다. 또한 C++의 struct도 사용할 수 있습니다.
클래스를 정의합니다. 클래스를 정의하는 클래스와 동일하며, struct로 정의된 클래스의 기본 접근 권한은 public이고 , class로 정의된 클래스의 기본 접근 권한은 private이라는 점이 다릅니다 . 상속과 템플릿 매개변수 목록 위치에서도 구조체와 클래스 간에 차이가 있으며 순서는 추후에 설명하겠습니다.
홈 소개.

3.2 클래스 정의의 두 가지 방법:

1. 모든 선언과 정의는 클래스 본문에 위치합니다. 참고: 멤버 함수가 클래스에 정의된 경우 컴파일러는 이를 인라인 함수 로 처리할 수 있습니다 .

class Person
{
public:
	//显示基本信息
	void ShowInfo()
	{
		cout << _name << "_" << _sex << "_" << _age << "_" << endl;
	}

//基本信息
public:
	char* _name;  //姓名
	int _age;     //年龄
	char* _sex;	  //性别
};
2. 클래스 선언은 .h 파일에 위치하며, 멤버 함수 정의는 .cpp 파일에 위치합니다. 참고: 멤버 함수 이름 앞에 클래스 이름을 추가해야 합니다.
//Person.h

class Person
{

//基本信息
public:
	char* _name;  //姓名
	int _age;     //年龄
	char* _sex;	  //性别
};


//Person.cpp

#include "Person.h"

//显示基本信息
void Person::ShowInfo()
{
	cout << _name << "_" << _sex << "_" << _age << "_" << endl;
}

우리는 일반적으로 선언과 정의를 별도로 작성하는 두 번째 방법을 사용합니다.

3.3 포장

클래스와 객체 단계에서는 클래스의 캡슐화 특성을 주로 연구하는데, 캡슐화란 무엇인가?

캡슐화(Encapsulation): 데이터와 데이터를 조작하는 방법을 유기적으로 결합하고, 객체의 속성과 구현 세부 사항을 숨기고, 객체와 상호 작용할 수 있는 인터페이스만 노출합니다 .
캡슐화는 본질적으로 사용자가 클래스를 더 쉽게 사용할 수 있도록 하는 일종의 관리입니다 . 예: 컴퓨터와 같은 복잡한 장치의 경우 사용자에게 제공되는 유일한 것은 전원 켜기/끄기 버튼, 키보드 입력, 모니터, USB 잭 등이며, 이를 통해 사용자는 컴퓨터와 상호 작용하고 일상 작업을 완료할 수 있습니다. 그러나 실제로 컴퓨터의 실제 작업은 CPU, 그래픽 카드, 메모리 및 기타 하드웨어 구성 요소입니다.
C++ 언어로 캡슐화를 구현하기 위해서는 데이터를 연산하기 위한 데이터와 메소드를 클래스를 통해 유기적으로 결합할 수 있고, 접근 권한을 이용해 객체의 내부 구현 내용을 숨기고 클래스 외부에서 직접 어떤 메소드를 사용할 수 있는지 제어할 수 있다 .

4. 클래스 범위

클래스는 새로운 범위를 정의하며 , 클래스의 모든 멤버는 클래스의 범위 내에 있습니다 . 클래스 외부에서 멤버를 정의할 때 :: 범위 연산자를 사용하여 해당 멤버가 속한 클래스 범위를 나타내야 합니다.
class Person
{
public:
	//显示基本信息
	void ShowInfo();
//基本信息
public:
	char* _name[20];  //姓名
	int _age;     //年龄
	char* _sex[10];	  //性别
};

//需要指定是哪个类域
void Person::ShowInfo()
{
	cout << _name << "_" << _sex << "_" << _age << "_" << endl;
}

5. 클래스 인스턴스화

클래스 유형에서 객체를 생성하는 프로세스를 클래스 인스턴스화라고 합니다.
1. 클래스는 객체를 설명합니다 . 이는 모델 과 유사합니다 . 클래스의 구성원을 제한합니다. 클래스를 정의해도 클래스 를 저장하기 위한 실제 메모리 공간이 할당 되지 않습니다 . 예: 등록 시 학생 정보 양식을 작성합니다. 테이블 특정 학생의 정보를 기술하는 클래스라고 볼 수 있다.
2. 클래스는 여러 개체를 인스턴스화할 수 있으며, 인스턴스화된 개체는 실제 물리적 공간을 차지하고 클래스 멤버 변수를 저장합니다.
int main()
{
     Person._age = 100;   // 编译失败:error C2059: 语法错误:“.”
     return 0;
}
//Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄
3. 비유를 사용하십시오. 클래스에서 개체를 인스턴스화하는 것은 건축 설계 도면을 사용하여 현실에서 집을 짓는 것과 같습니다. 클래스는 설계 도면과 같습니다 . 필요한 것만 설계하고 실제 건물은 없습니다. 마찬가지로 클래스는 설계일 뿐입니다. 객체는 실제로 데이터를 저장하고 물리적 공간을 차지할 수 있습니다.
class Date
{
public:
	void Init(int year, int month, int day);

	void Print();

public:
	int _year;   // 声明
	int _month;
	int _day;
};

void Date::Init(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

int main()
{
	// 定义开空间
	Date d1;
	d1.Init(2023, 9, 30);
	d1.Print();

	//实例化多个对象
	Date d2;
	d2.Init(2023, 10, 1);
	d2.Print();

	return 0;
}

6. 클래스 객체 모델

6.1 클래스 객체의 크기를 계산하는 방법

class A
{
public:
    //成员函数
    void PrintA()
    {
       cout<<_a<<endl;
    }
private:
//    成员变量
    char _a;
};
질문: 클래스는 멤버 변수와 멤버 함수를 모두 가질 수 있는데, 클래스 객체에는 무엇이 포함되나요? 수업 규모를 계산하는 방법은 무엇입니까?

6.2 클래스 객체의 저장 방법

방법 1: 개체에 클래스의 각 멤버가 포함되어 있습니다.

결함: 각 객체의 멤버변수는 다르지만 동일한 함수가 호출된다.
클래스가 여러 객체를 생성하면 각 객체에 코드 복사본이 저장되며, 동일한 코드가 여러 번 저장되므로 공간이 낭비됩니다. 그래서
어떻게 해결하나요?
방법 2: 코드 사본 하나만 저장하고, 코드의 주소를 객체에 저장
방법 3: 멤버 변수만 저장하고 멤버 함수는 공개 코드 섹션에 저장합니다.
그렇다면 컴퓨터는 어떤 방법을 사용하여 이를 저장할까요?
// 类中既有成员变量,又有成员函数
class A1 {
public:
	void f1() {}
private:
	int _a;
};
// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};
// 类中什么都没有---空类
class A3
{};

int main()
{
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;
//  分配1byte,不存储数据,只是占位,表示对象存在过
	cout << sizeof(A3) << endl;
	return 0;
}

결론적으로:

클래스의 크기는 실제로 클래스에 포함된 "멤버 변수"의 합계입니다. 물론 메모리 정렬에 주의를 기울여야 합니다.
빈 클래스의 크기에 주의하세요. 빈 클래스는 특별합니다. 컴파일러는 이 클래스의 개체를 고유하게 식별하기 위해 빈 클래스에 1바이트를 제공합니다.

6.3 구조 메모리 정렬 규칙 

1. 첫 번째 멤버는 구조에서 주소 오프셋 0에 있습니다.
2. 다른 멤버 변수는 특정 숫자(정렬 번호)의 정수배인 주소에 정렬되어야 합니다.
참고: 정렬 번호 = 컴파일러의 기본 정렬 번호와 멤버 크기 중 더 작은 것입니다.
VS의 기본 정렬 수는 8입니다.
3. 구조의 전체 크기는 최대 정렬 수(모든 변수 유형 중 가장 큰 값과 가장 작은 기본 정렬 매개변수)의 정수 배수입니다.
4. 구조가 중첩되고 중첩된 구조가 자체 최대 정렬 번호의 정수 배수로 정렬되는 경우 구조의 전체 크기는 모든 최대 정렬 번호(중첩 구조의 정렬 번호 포함)를 곱한 정수입니다.

7. 이 포인터 

7.1 이 포인터 소개

먼저 날짜 클래스를 정의합니다.

class Date
{
public:
	//初始化
	void DateInit(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//打印
	void DatePrint()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1, d2;
	d1.DateInit(2023, 9, 30);
	d1.DatePrint();

	d2.DateInit(2023, 10, 1);
	d2.DatePrint();
	return 0;
}
위 클래스에는 다음과 같은 문제가 있습니다.
Date 클래스에는 Init와 Print라는 두 개의 멤버 함수가 있습니다. 함수 본문에는 서로 다른 개체 간에 구분이 없습니다. 따라서 d1이 Init 함수를 호출할 때 함수는 함수 대신 d1 개체를 설정해야 한다는 것을 어떻게 알 수 있습니까? d2 객체?
C++는 this 포인터를 도입하여 이 문제를 해결합니다. 즉, C++ 컴파일러는 각 "비정적 멤버 함수"에 숨겨진 포인터 매개 변수를 추가하여 포인터가 현재 개체(함수가 실행될 때 함수를 호출하는 개체)를 가리키도록 합니다. 실행 중) 함수 본문의 "멤버 변수"에 대한 모든 작업은 이 포인터를 통해 액세스됩니다. 모든 작업은 사용자에게 투명합니다. 즉, 사용자가 이를 전달할 필요가 없으며 컴파일러가 자동으로 완료합니다 .

7.2이 포인터의 특징

1. 이 포인터의 유형: 클래스 유형 * const , 즉 멤버 함수에서 이 포인터에는 값을 할당할 수 없습니다.

2. "멤버 함수" 내에서만 사용할 수 있습니다.
class Date
{
public:
	// this在实参和形参位置不能显示写
	// 但是在类里面可以显示的用
	void DateInit(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
3. this 포인터는 본질적으로 "멤버 함수"의 형식 매개변수입니다 . 객체가 멤버 함수를 호출하면 객체 주소가 실제 매개변수로 this 매개변수에 전달됩니다. 따라서 this 포인터는 객체에 저장되지 않습니다 .
4. this 포인터는 "멤버 함수"의 첫 번째 암시적 포인터 매개변수입니다. 일반적으로 컴파일러는 이를 자동으로 ecx 레지스터를 통해 전달합니다.
통과됨. 사용자가 통과할 필요가 없습니다.

연습문제:

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

이 코드는 정상적으로 실행될 수 있습니다. 객체에 Print 함수가 존재하지 않고 p->Print()가 역참조되지 않기 때문입니다. 이는 p를 사용하여 공개 코드 영역에서 해당 함수를 찾는다는 의미이며 여기서 이 포인터는 p입니다.

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
        //cout<<this->_a<<ednl; 
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

 이 포인터를 전달할 때 p가 null이기 때문에 이 코드는 실행 시 충돌이 발생하고, 인쇄할 때 this 포인터를 역참조해야 하므로 null이 역참조되어 작업이 충돌합니다.

 7.3 C 언어와 C++의 스택 구현 비교

C 언어 구현:

//对栈的初始化
void StackInit(Stack* pst)
{
	assert(pst);
	pst->a = NULL;
	//top为-1时,插入一个数据之后top指向的是刚刚插入数据的位置
	//pst->top = -1     
	//top为0时,插入一个数据之后top指向的是刚刚插入数据后面的位置
	pst->top = 0;
	pst->capacity = 0;
}

//入栈
void StackPush(Stack* pst, STDataType x)
{
	assert(pst);
	//检测容量
	if (pst->top == pst->capacity)
	{
		int NewCapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
		//当pst->a为NULL时执行的功能是和malloc一样
		STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * NewCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = NewCapacity;
	}
	//入栈
	pst->a[pst->top] = x;
	pst->top++;
}

//出栈
void StackPop(Stack* pst)
{
	assert(pst);
	//判断栈是否为空
	assert(!StackEmpty(pst));
	//出栈
	pst->top--;
}

//获取栈顶元素
STDataType StackTop(Stack* pst)
{
	assert(pst);
	assert(!StackEmpty(pst));
	//top指向的是栈顶的下一个位置的元素
	return pst->a[pst->top-1];
}

//获取栈中的有效元素的个数
int StackSize(Stack* pst)
{
	assert(pst);

	return pst->top;
}

//检测栈是否为空
bool StackEmpty(Stack* pst)
{
	assert(pst);
	/*if (pst->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}*/
	return pst->top == 0;
}

//销毁栈
void StackDestroy(Stack* pst)
{
	assert(pst);

	//释放
	free(pst->a);
	pst->a = NULL;
	//重置为0
	pst->top = pst->capacity = 0;
}
C 언어로 구현하면 Stack 관련 연산 함수는 다음과 같은 공통적인 특징을 가지고 있음을 알 수 있습니다.
        각 함수의 첫 번째 매개변수는 Stack*입니다.
        첫 번째 매개변수는 NULL일 수 있으므로 함수에서 확인해야 합니다.
        함수에서 스택은 Stack* 매개변수를 통해 조작됩니다.
        호출 시 스택 구조 변수의 주소를 전달해야 합니다.
구조체에는 데이터를 저장하는 구조만 정의할 수 있고, 데이터를 연산하는 방법을 구조체에 넣을 수 없다 . 포인터 연산 횟수 주의하지 않으면 문제가 발생할 수 있습니다.

C++ 구현(기존 C++ 지식을 기반으로 설계됨)

//C++实现栈
typedef int DataType;
class Stack
{
public:
	//初始化
	void STInit()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (_array == NULL)
		{
			perror("malloc fail!!!");
			exit(-1);
		}
		_size = 0;
		_capacity = 4;
	}

	//判空
	bool STEmpty()
	{
		return _size == 0;
	}

	//入栈
	void STPush(DataType x)
	{
		STCheck();
		//入栈
		_array[_size] = x;
		_size++;
	}

	//出栈
	void STPop()
	{
		assert(!STEmpty());
		_size--;
	}

	//数据个数
	int STSize()
	{
		return _size;
	}

	//栈顶元素
	int STTop()
	{
		assert(!STEmpty());
		return _array[_size - 1];
	}

	//销毁
	void STDestroy()
	{
		free(_array);
		_array = nullptr;
		_size = _capacity = 0;
	}

private:
	//检测
	void STCheck()
	{
		if (_size == _capacity)
		{
			int NewCapacity = 2 * _capacity;

			DataType* tmp = (DataType*)realloc(_array, sizeof(DataType) * NewCapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			_array = tmp;
			_capacity = NewCapacity;
		}
	}
private:
	DataType* _array;
	int _size;
	int _capacity;
};

int main()
{
	Stack st;
	st.STInit();
	st.STPush(1);
	st.STPush(2);
	st.STPush(3);
	st.STPush(4);
	
	cout << st.STTop() << endl;
	cout << st.STSize() << endl;
	st.STPop();
	cout << st.STTop() << endl;
	return 0;
}
C++에서는 클래스를 통해 데이터와 데이터 조작 메소드를 완벽하게 결합할 수 있으며, 접근 권한을 통해 클래스 외부에서 어떤 메소드를 호출할 수 있는지, 즉 캡슐화를 제어 할 수 있다. 사물에 대한 인간의 이해, 사물에 대한 지식과 더 일치합니다. 또한 각 메소드는 Stack* 매개변수를 전달할 필요가 없습니다 . 매개변수는 컴파일러 컴파일 후 자동으로 복원됩니다. 즉, C++의 Stack* 매개변수는 컴파일러에 의해 유지되지만 C 언어에서는 유지관리되어야 합니다. 사용자 .

 

친구 여러분, 즐거운 시간은 언제나 짧습니다 이번 호의 나눔은 여기까지입니다 읽어주신 후 소중한 사진 세 장을 남겨주시는 것도 잊지 마세요 응원해주신 여러분 감사합니다!

추천

출처blog.csdn.net/Yikefore/article/details/133427075