친구 여러분, 우리 다시 만났습니다 이번 호에서는 C++의 클래스와 객체에 대한 지식 포인트를 설명하겠습니다. 읽고 영감을 얻으셨다면 댓글을 남겨주세요. 최선을 다하시길 바랍니다. 소원이 이루어집니다!
C언어 칼럼: C언어: 입문부터 숙련까지
데이터 구조 열: 데이터 구조
개인 홈페이지: stackY、
목차
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 언어에서는 유지관리되어야 합니다. 사용자 .
친구 여러분, 즐거운 시간은 언제나 짧습니다 이번 호의 나눔은 여기까지입니다 읽어주신 후 소중한 사진 세 장을 남겨주시는 것도 잊지 마세요 응원해주신 여러분 감사합니다!