기술나눔

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;
  • C++의 구조체는 클래스를 정의할 수도 있습니다. C++는 C의 구조체 사용과 호환됩니다. 동시에 구조체가 클래스로 업그레이드되었습니다. 일반적인 상황에서는 함수를 구조체에서도 정의할 수 있습니다. 여전히 클래스를 사용하여 클래스를 정의하는 것이 좋습니다.

  • 클래스에 정의된 멤버는 기본적으로 인라인으로 설정됩니다.

1.2 접근 한정자

C++는 캡슐화를 구현하는 방법으로, 클래스를 사용하여 개체의 속성과 메서드를 결합하여 개체를 더욱 완벽하게 만들고 액세스 권한을 통해 외부 사용자에게 해당 인터페이스를 선택적으로 제공합니다.

  • public(public)으로 수정된 멤버는 클래스 외부에서 직접 접근 가능하고, protected(protected)와 프라이빗(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. 클래스 정의 멤버가 액세스 한정자에 의해 수정되지 않으면 기본값은 private이고 구조체의 기본값은 public입니다.
  2. 일반적으로 멤버 변수는 private/protected로 제한되며, 다른 사람이 사용해야 하는 멤버 함수는 public으로 배치됩니다.

1.3 클래스 영역

클래스는 새 범위를 정의합니다. 클래스의 모든 멤버는 클래스 범위에 있습니다. 클래스 외부에서 멤버를 정의하는 경우 ::scope 연산자를 사용하여 해당 멤버가 속한 클래스 범위를 표시해야 합니다.

클래스 도메인은 컴파일의 검색 규칙에 영향을 미칩니다. 다음 프로그램의 Init가 클래스 도메인 Stack을 지정하지 않으면 컴파일러는 컴파일 중에 Init를 전역 함수로 처리합니다. 클래스 도메인에서.

  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를 가지고 있지만 d1 및 d2의 멤버 함수 Init는 /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. 이 포인터

컴파일러가 컴파일된 후 클래스의 멤버 함수는 기본적으로 이 포인터라고 하는 형식 매개변수의 첫 번째 위치에 현재 클래스에 대한 포인터를 추가합니다.

예를 들어 Date 클래스의 Init 프로토타입은 void Init(Date * const this, int year, int Month, int day)입니다. 클래스의 멤버 함수에서 멤버 변수에 액세스할 때 this 포인터를 통해 본질에 액세스합니다. 예를 들어 Init 함수의 _year, 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생성자

생성자는 특별한 멤버 함수입니다. 생성자의 이름은 생성자이지만 생성자의 주요 내용은 객체를 생성하기 위해 공간을 여는 것이 아닙니다(우리가 일반적으로 사용하는 로컬 객체는 생성되는 공간입니다). 스택 프레임이 생성될 때 열림)), 객체가 인스턴스화될 때 객체가 초기화됩니다. 생성자의 본질은 이전에 Stack 및 Date 클래스에서 작성한 Init 함수의 함수를 대체하는 것입니다. 생성자의 자동 호출은 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 포인터에 전달됩니다. 따라서 연산자가 멤버 함수로 오버로드되면 피연산자보다 매개 변수가 하나 적습니다.
  • 연산자가 오버로드된 후에도 해당 우선순위와 연관성은 내장 유형 연산과 일관되게 유지됩니다.
  • 구문에 존재하지 않는 일치 항목을 연결하여 성 연산자를 만들 수 없습니다. 예: Operator@