C++ 클래스와 객체의 핵심 개념

1. 절차 지향 프로그래밍과 객체 지향 프로그래밍

절차 지향 프로그래밍 C 언어에서 배운 내용은 주로 절차 지향적이며, 핵심은 과정(함수)입니다. 프로그램을 일련의 단조 조합으로 간주합니다. "어떻게"에 초점을 맞추며, 작업을 완료하기 위해 함수와 함수 간의 호출 순서를 설계하는 데 중점을 둡니다. 전체 과정은 작업 단계(함수)를 중심으로 진행됩니다.

예를 들어, 파일 복사 프로그램에서 절차 지향적 접근 방식은 먼저 원본 파일을 열고, 원본 파일 내용을 읽은 다음 대상 파일을 열고, 읽은 내용을 대상 파일에 쓰고 마지막으로 파일을 닫는 순서입니다.

객체 지향 프로그래밍 핵심은 객체이며, 현실 세계의 사물을 객체로 추상화합니다. 객체는 데이터(속성)와 데이터를 조작하는 방법(메서드)을 포함합니다. "무엇이 있는지"에 초점을 맞추며, 객체의 속성과 동작을 정의하고 객체 간의 상호작용을 통해 작업을 완료하는 데 중점을 둡니다.

예를 들어, 파일 복사 프로그램에서 객체 지향적 접근 방식은 파일 객체를 생성하며, 이러한 파일 객체에는 파일 이름, 파일 경로, 파일 내용과 같은 자체 속성과 열기, 읽기, 쓰기, 닫기와 같은 메서드가 있습니다. 이러한 파일 객체를 조작하여 파일 복사를 구현합니다.

2. 클래스

2.1 클래스의 도입

클래스는 데이터 유형이며, C 언어에서 클래스는 구조체를 의미하지만 변수만 포함할 수 있습니다. C++에서 클래스는 변수와 함수를 모두 포함할 수 있어 객체의 속성과 동작을 명확히 설명할 수 있습니다. 따라서 클래스와 구조체는 매우 유사합니다.

// C 환경에서
struct Stack
{
    int a = 0;
    char ch = 1;
    int* arr = &a;
};

// C++ 환경에서
class Stack
{
    int a = 0;
    char ch = 1;
    int* arr = &a;
};

위의 구조체 정의는 C++에서 class로 대체하는 것을 선호하며, 실제로 C++에서는 classstruct 모두 사용할 수 있습니다. C++에서는 일반적으로 C 언어와의 차별화를 위해 class를 더 선호합니다.

2.2 클래스의 정의

문법 형식:

class className
{
    // 클래스 본체: 멤버 함수와 멤버 변수로 구성됨
};  // 뒤에 세미콜론을 꼭 기억하세요

주의할 점:

  • class는 클래스를 정의하는 키워드이며, ClassName은 클래스 이름입니다. {} 안은 클래스 본체이며, 클래스 정의가 끝날 때 뒤에 세미콜론을 생략할 수 없습니다.
  • 클래스 본체의 내용은 클래스의 멤버라고 불리며: 클래스의 변수클래스의 속성 또는 멤버 변수라고 하고, 클래스의 함수클래스의 메서드 또는 멤버 함수라고 합니다.
2.3 클래스에서의 선언과 정의

학생을 설명해야 한다고 가정해 보겠습니다.

선언과 정의를 모두 클래스 내에 배치

class student
{
    void show()
    {
        cout << _name << "-" << _sex << "-" << _age << endl; 
    }
    char _name;    // 이름
    char _sex;     // 성별
    int _age;      // 나이
};

일반적으로 프로젝트를 수행하지 않을 때는 멤버 함수의 선언과 정의를 함께 배치합니다.

선언과 정의를 분리

// test.h
class student
{
    void show();
    char _name;    // 이름
    char _sex;     // 성별
    int _age;      // 나이
};

// test.cpp
#include "test.h"
void student::show()
{
    cout << _name << "-" << _sex << "-" << _age << endl; 
}

일반적으로 나중에 프로젝트를 작성할 때 여러 파일로 분할할 가능성이 높기 때문에 선언과 정의를 분리하는 방식을 더 선호합니다. 클래스 외부에서 멤버 함수에 접근하기 때문에 범위 한정 연산자를 추가해야 합니다.

2.4 클래스의 접근 제한자 및 캡슐화

클래스의 캡슐화: 객체의 속성과 메서드를 클래스로 결합하여 객체를 더 완벽하게 만들고, 접근 권한을 통해 선택적으로 외부 사용자에게 인터페이스를 제공합니다.

접근 제한자에는:

  • public(공개)
  • protected(보호)
  • private(비공개)

예시:

class Date
{
public:
    void Init(int year)
    {
        _year = year;
    }
private:
    int _year;
};

일반적으로 멤버 변수는 비공개에 배치하고(멤버 함수를 비공개에 배치하는 경우는 나중에 설명), 멤버 함수는 공개에 배치합니다. 비공개 멤버는 직접 접근할 수 없으며, 일반적으로 멤버 함수를 통해 접근합니다(멤버 함수가 클래스 내에 있는 경우, 클래스 외부에는 친구 함수를 사용해야 하며 나중에 설명).

주의할 점:

  1. public으로 수정된 멤버는 클래스 외부에서 직접 접근할 수 있습니다.
  2. protected와 private로 수정된 멤버는 클래스 외부에서 직접 접근할 수 없습니다.
  3. protected와 private는 현재는 동일하며, 차이는 상속에서 나타납니다.
  4. 접근 권한 범위는 해당 접근 제한자가 나타나는 위치부터 다음 접근 제한자가 나타날 때까지입니다.
  5. 뒤에 접근 제한자가 없으면 범위는 } 즉 클래스 끝까지입니다.

왜 비공개 멤버 변수 이름 앞에 _를 붙이나요?

// 이 함수는 매우 부자연스럽지 않나요?
class Date
{
public:
    void Init(int year)
    {
        // 이 year는 멤버 변수인지 함수 매개변수인지?
        year = year;
    }
private:
    int year;
};

일반적으로 비공개 멤버 변수 이름 앞에 _를 붙여 매개변수 전달 시 이름과 구분하여 변수를 잘못 참조하는 것을 방지합니다.

주의할 점: 비공개 멤버 변수는 여기서는 선언이며, int _year = 0으로 작성해도 기본값 설정에 불과합니다. 변수가 선언인지 정의인지는 변수가 공간을 차지하는지에 따라 결정됩니다.

C++에서 struct와 class의 차이점은 무엇인가요?

class MyClass 
{
    int myVariable; // 기본적으로 private 접근 권한
};

struct MyStruct 
{
    int myVariable; // 기본적으로 public 접근 권한
};

접근 제한자를 지정하지 않으면 MyClass에서 myVariable은 외부에서 직접 접근할 수 없지만, MyStruct에서 myVariable은 외부에서 직접 접근할 수 있습니다.

2.5 클래스의 범위

C++ 운명의 돌문 코드 선택: C++ 입문(상)에서 우리는 범위 개념을 자세히 소개했습니다.

클래스는 새로운 범위, 즉 클래스 범위를 정의하며, 클래스의 모든 멤버는 클래스 범위에 속합니다. 클래스 외부에서 멤버를 정의할 때, :: 범위 연산자를 사용하여 멤버가 어떤 클래스 범위에 속하는지 지정해야 합니다.

class Person
{
public:
    void PrintPersonInfo();
private:
    char _name[20];
    char _sex[10];
    int  _age;
};

void Person::PrintPersonInfo()
{
    cout << _name << " "<< _sex << " " << _age << endl;
}

여기서 PrintPersonInfo가 Person 클래스 범위에 속한다는 것을 지정해야 합니다.

2.6 클래스의 인스턴스화

클래스 유형으로 객체를 생성하는 과정클래스의 인스턴스화라고 합니다.

int main()
{
    Person._age = 100;   // 컴파일 실패: error C2059: 구문 오류: "."
    return 0;
}

위의 클래스는 이러한 인스턴스화 방식이 잘못되었습니다.

비유하자면: 클래스 인스턴스화는 객체를 생성하는 것은 현실에서 건축 설계도로 집을 짓는 것과 같습니다. 클래스설계도와 같이 필요한 것을 설계하지만 실제 건축물은 존재하지 않습니다. 마찬가지로 클래스는 단지 설계일 뿐, 인스턴스화된 객체만 실제 데이터를 저장하고 물리적 공간을 차지합니다.

올바른 인스턴스화:

int main()
{
    Person student;
    student._age = 10;
    student._name = "Jack";
    student._sex = male;
    student.PrintPersonInfo();
    return 0;
}

객체를 먼저 생성해야만 클래스의 멤버를 조작할 수 있습니다.

2.7 클래스의 저장 및 크기

클래스는 어떻게 저장되나요?

모든 멤버를 클래스 저장에 포함하려면, 이러한 방식으로 저장할 때 클래스로 여러 객체를 생성하면 각 객체에 코드가 모두 저장되어 동일한 코드가 여러 번 저장되어 공간이 낭비됩니다. 그렇다면 어떻게 해결할까요?

따라서 저장 방식에서 클래스는 멤버 변수만 저장하고 멤버 함수는 공통 코드 영역에 저장하여 공간 낭비를 효과적으로 줄입니다. 각 객체의 멤버 변수는 다르지만 동일한 함수를 호출하며 함수는 사용될 때만 공통 부분에서 호출됩니다.

다양한 객체에 대해 크기를 가져와 분석:

// 클래스에 멤버 변수와 멤버 함수 모두 포함
class A1 {
public:
    void f1(){}
private:
    int _a;
};

// 클래스에 멤버 함수만 포함
class A2 {
public:
    void f2(){}
};

// 클래스에 아무것도 없음---빈 클래스
class A3
{};
  • sizeof(A1): 4
  • sizeof(A2): 1
  • sizeof(A3): 1

주의할 점: 클래스 크기 계산도 메모리 정렬 규칙을 따릅니다. 왜 클래스에 멤버 함수만 있거나 빈 클래스의 메모리 크기가 1바이트인가요? 메모리 계산은 변수만 고려하는 것이 아닌데, 이유는 매우 간단합니다. 이 두 클래스의 크기를 가져올 때 공간이 없으면 프로그램 오류가 발생하지만 이 두 클래스는 실제로 존재하므로 이러한 클래스의 크기를 1바이트로 정의한 것은 위치를 차지하기 위함이며, 객체가 존재함을 나타내며 유효한 데이터를 저장하지 않습니다.

3. this 포인터

this 포인터는 암시적 포인터이며, 현재 객체의 인스턴스를 가리킵니다. 클래스의 멤버 함수 내부에서 사용되며, 멤버 함수의 매개변수와 객체의 멤버 변수를 구분하는 데 사용되며, 특히 이름이 같을 때 유용합니다.

날짜 클래스의 예시:

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;
}

d1이 Init 함수를 호출할 때, 이 함수는 d1 객체를 설정해야 하는지 d2 객체를 설정해야 하는지 어떻게 알 수 있나요?

C++ 컴파일러는 각 비정적 멤버 함수숨겨진 포인터 매개변수를 추가하여 이 포인터가 현재 객체(함수가 실행될 때 해당 함수를 호출하는 객체)를 가리키게 하고, 함수 본체의 모든 멤버 변수 작업은 이 포인터를 통해 접근합니다. 모든 작업은 사용자에게 투명하며, 즉 사용자가 전달할 필요 없이 컴파일러가 자동으로 처리합니다.

위의 그림은 컴파일러의 숨겨진 작업을 드러내며, 실제로 함수를 호출할 때 객체 주소를 전달합니다.

this 포인터는 null일 수 있나요? this 포인터가 객체의 멤버에 접근하지 않는다면 null로 설정할 수 있습니다. this 포인터가 객체의 멤버에 접근해야 한다면 null로 설정할 수 없으며, 그렇지 않으면 프로그램 오류가 발생하고 잘못된 메모리 주소에 접근하게 됩니다.

주의할 점:

  • this 포인터의 유형: 클래스형* const, 즉 멤버 함수에서 this 포인터에 값을 할당할 수 없습니다.
  • 멤버 함수 내부에서만 this 포인터를 사용할 수 있습니다.
  • this 포인터는 본질적으로 멤버 함수의 첫 번째 숨겨진 포인터 매개변수이며, 일반적으로 컴파일러가 ecx 레지스터를 통해 자동으로 전달하므로 사용자가 전달할 필요가 없습니다.
  • this 포인터는 실제 매개변수와 형식 매개변수에서 명시적으로 전달할 수 없지만, 함수 내부에서 사용할 수 있습니다.

태그: C++ 클래스 객체 캡슐화 this 포인터

7월 25일 02:10에 게시됨