본문 바로가기
GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~

[C/C++ 프로그래밍 : 중급] 2. 클래스와 객체

by GDNGY 2023. 5. 16.

Chapter 2. 클래스와 객체

클래스와 객체는 C++과 같은 객체 지향 언어에서 중요한 역할을 합니다. 이를 통해 개발자들은 효율적이고 가독성 높은 코드를 작성하는 데 도움을 받게 됩니다. 다양한 예제와 함께 이해를 돕고, 이론과 실제가 어떻게 연결되는지 보여드릴 것입니다.

 

반응형

 


[Chapter 2. 클래스와 객체]


2.1. 클래스와 객체 이해하기
2.1.1. 클래스의 정의와 구조
2.1.2. 객체란 무엇인가
2.1.3. 클래스와 객체의 관계

2.2. 클래스 선언과 구현
2.2.1. 클래스 선언의 기본 구조
2.2.2. 멤버 변수와 멤버 함수
2.2.3. 클래스의 생성과 소멸: 생성자와 소멸자
2.2.4. 객체 생성과 메모리 할당

2.3. 클래스의 접근 지시자
2.3.1. public, private, protected의 이해
2.3.2. 접근 지시자의 사용법

2.4. this 포인터의 이해
2.4.1. this 포인터란 무엇인가
2.4.2. this 포인터의 사용

2.5. 정적 멤버와 정적 함수
2.5.1. 정적 멤버 변수의 이해와 활용
2.5.2. 정적 멤버 함수의 이해와 활용

2.6. 객체 지향 프로그래밍의 기본 개념 복습
2.6.1. 캡슐화
2.6.2. 상속
2.6.3. 다형성


2.1 클래스와 객체 이해하기

이 섹션에서는 C++의 핵심 구성요소인 클래스와 객체에 대해 깊게 이해할 것입니다. 클래스는 데이터와 함수를 하나로 묶은 사용자 정의 데이터 타입입니다. 객체는 클래스에 기반하여 생성되며, 실제 우리가 조작하는 대상입니다. 이를 통해 우리는 코드를 더 효율적이고 체계적으로 관리할 수 있습니다. 여기서는 클래스와 객체의 기본적인 정의와 구조, 그리고 둘 사이의 관계에 대해 배우게 됩니다.

2.1.1 클래스의 정의와 구조

클래스는 C++에서 특히 중요한 개념입니다. 이는 데이터와 함수를 하나의 묶음으로 취급하는 사용자 정의 데이터 타입입니다. 클래스를 이해하는 것은 객체 지향 프로그래밍(OOP)의 핵심을 이해하는 것을 의미합니다.

 

클래스는 '틀' 혹은 '설계도'와 같습니다. 그 안에는 변수(데이터)와 함수(메서드)가 포함되어 있습니다. 이러한 변수와 함수를 '멤버'라고 합니다. 이 멤버들이 어떻게 구성되는지, 어떤 행동을 하는지에 따라 클래스의 형태와 기능이 결정됩니다.

 

이제 클래스의 기본적인 정의와 구조를 알아보겠습니다.

 

클래스의 정의

C++에서 클래스는 다음과 같은 형태로 정의됩니다.

[예제]

class ClassName {
    // 멤버 변수
    // 멤버 함수
};


ClassName은 우리가 정의하는 클래스의 이름입니다. 클래스의 본문은 중괄호 {}로 둘러싸여 있으며, 세미콜론(;)으로 끝납니다. 이 안에 들어가는 멤버 변수와 멤버 함수를 우리가 원하는 대로 정의하게 됩니다.

 

예를 들어, 'Dog'라는 클래스를 정의해 봅시다.

 

[예제]

class Dog {
    public:
    // 멤버 변수
    string name;
    int age;

    // 멤버 함수
    void bark() {
        cout << "Bark!" << endl;
    }
};


이렇게 'Dog' 클래스를 정의하면, 이제 우리는 'Dog' 형식의 객체를 만들 수 있게 됩니다. 이 객체는 'name'과 'age'라는 변수를 가지며, 'bark'라는 함수를 사용할 수 있습니다.

 

클래스의 구조

클래스는 멤버 변수와 멤버 함수로 구성됩니다. 멤버 변수는 클래스가 가지는 데이터를 정의하며, 멤버 함수는 그 데이터를 다루는 방법을 정의합니다.

 

멤버 변수와 함수는 보통 '접근 지시자'에 의해 제어됩니다. 'public', 'private', 'protected'와 같은 접근 지시자를 사용하여 멤버들의 접근 권한을 설정할 수 있습니다.

  • 'public'은 어떤 곳에서든 접근이 가능하게 합니다.
  • 'private'은 클래스 내부에서만 접근이 가능하게 합니다.
  • 'protected'는 클래스 내부와 해당 클래스를 상속받은 자식 클래스에서만 접근이 가능하게 합니다.

C++에서는 보통 멤버 변수는 private로, 멤버 함수는 public으로 설정하는 것이 일반적입니다. 이렇게 하면 멤버 변수에 직접 접근하는 것을 방지하고, 멤버 함수를 통해 멤버 변수를 안전하게 조작하는 것을 보장할 수 있습니다.

 

2.1.2 객체란 무엇인가

클래스를 통해 우리는 데이터와 함수를 한 곳에 묶어서 관리할 수 있습니다. 이제 클래스를 이용해 실제로 우리가 다룰 수 있는 '객체'에 대해 알아보겠습니다.

 

객체는 클래스를 기반으로 만들어진 실체입니다. 클래스는 일종의 틀이며, 그 틀을 이용해 만든 것이 바로 객체입니다. 다르게 표현하자면, 클래스는 설계도이고 객체는 그 설계도를 바탕으로 만든 제품입니다. 이 객체를 우리가 코드에서 직접 다루게 됩니다.

 

객체는 클래스에서 정의한 멤버 변수와 멤버 함수를 모두 가지게 됩니다. 객체마다 멤버 변수는 독립적으로 메모리에 할당되며, 그 값을 개별적으로 유지합니다. 멤버 함수는 모든 객체가 공유하게 됩니다.

 

객체를 생성하려면 다음과 같이 '클래스 이름' 다음에 '객체 이름'을 적고, 세미콜론(;)으로 끝내면 됩니다.

 

[예제]

ClassName objectName;


이제 'Dog' 클래스를 기반으로 'myDog'라는 객체를 만들어 봅시다.

 

[예제]

Dog myDog;


이 코드는 'Dog' 클래스의 인스턴스인 'myDog' 객체를 생성합니다. 이제 'myDog' 객체는 'name'과 'age'라는 멤버 변수를 가지고, 'bark'라는 멤버 함수를 사용할 수 있습니다.

 

멤버 변수에 접근하려면 다음과 같이 객체 이름 다음에 점(.)을 찍고, 변수 이름을 적으면 됩니다.

 

[예제]

myDog.name = "Puppy";
myDog.age = 1;


멤버 함수를 호출하려면, 변수에 접근하는 방법과 동일하게 객체 이름 다음에 점(.)을 찍고, 함수 이름을 적으면 됩니다.

[예제]

myDog.bark(); // "Bark!"를 출력합니다.


이처럼 클래스와 객체를 이해하고 사용하는 것은 C++ 프로그래밍의 기본이며, 효율적이고 체계적인 코드 작성의 핵심입니다.

 

2.1.3 클래스와 객체의 관계

앞서 본 것처럼 클래스는 '틀' 혹은 '설계도'이고, 객체는 그 틀을 바탕으로 만들어진 '실체'입니다. 클래스와 객체의 이러한 관계를 잘 이해하는 것이 객체 지향 프로그래밍(OOP)의 핵심을 파악하는 데 큰 도움이 됩니다.

 

클래스는 데이터(멤버 변수)와 기능(멤버 함수)을 하나로 묶어 놓은 설계도입니다. 객체는 이 설계도를 따라 만들어진 실체로, 클래스에 정의된 멤버 변수와 멤버 함수를 가지게 됩니다. 여러 객체는 같은 클래스를 바탕으로 만들어졌다면, 그 클래스에 정의된 멤버 변수와 멤버 함수를 공유하게 됩니다. 하지만 각 객체의 멤버 변수는 독립적으로 값을 유지하며, 이를 통해 각 객체는 자신만의 상태를 갖게 됩니다.

 

예를 들어, 'Dog' 클래스에는 'name'과 'age'라는 멤버 변수와 'bark'라는 멤버 함수가 정의되어 있다고 해 봅시다.

 

[예제]

class Dog {
public:
    // 멤버 변수
    string name;
    int age;

    // 멤버 함수
    void bark() {
        cout << "Bark!" << endl;
    }
};


이제 이 클래스를 기반으로 'myDog'와 'yourDog'라는 두 개의 객체를 만들어 봅시다.

 

[예제]

Dog myDog;
Dog yourDog;


'myDog'와 'yourDog'는 모두 'Dog' 클래스에 정의된 멤버 변수와 멤버 함수를 가지게 됩니다. 따라서 둘 다 'name', 'age' 변수를 가지고, 'bark' 함수를 사용할 수 있습니다.

 

그러나 'myDog'와 'yourDog'는 각각 독립적인 객체이므로, 각 객체의 'name'과 'age' 변수는 서로 다른 값을 가질 수 있습니다. 예를 들어, 'myDog'의 이름은 'Puppy'이고, 'yourDog'의 이름은 'Rex'일 수 있습니다.

 

[예제]

myDog.name = "Puppy";
yourDog.name = "Rex";


멤버 함수 'bark'는 두 객체가 공유하므로, 각 객체에서 호출할 때 동일한 기능을 수행합니다.

[예제]

myDog.bark(); // "Bark!"를 출력합니다.
yourDog.bark(); // "Bark!"를 출력합니다.

 

이처럼 클래스와 객체의 관계를 이해하는 것은 C++의 객체 지향 프로그래밍을 이해하는 데 중요한 첫걸음입니다.

 

2.2 클래스 선언과 구현

클래스를 선언하고 구현하는 방법을 배워보겠습니다. 클래스 선언은 클래스의 구조를 정의하며, 클래스의 멤버 변수와 멤버 함수를 나열합니다. 구현은 클래스의 멤버 함수가 어떤 작업을 수행하는지 정의합니다. 클래스 선언과 구현을 통해 C++의 객체 지향 특성인 캡슐화, 상속, 다형성을 이해하고 활용할 수 있게 됩니다.

 

2.2.1 클래스 선언의 기본 구조

C++에서 클래스를 선언하는 것은 새로운 자료형을 만드는 것과 비슷합니다. 클래스는 자신만의 특징과 행동을 가지는 복합 자료형입니다. 클래스는 객체를 생성하는 틀로, 객체의 상태를 나타내는 데이터(멤버 변수)와 행동을 나타내는 함수(멤버 함수)를 포함합니다.

 

클래스를 선언하는 기본 구조는 다음과 같습니다.

 

[예제]

class ClassName {
    // 멤버 변수
    // 멤버 함수
};


클래스 선언은 'class' 키워드와 클래스 이름, 그리고 중괄호 {}로 둘러싸인 멤버 변수와 멤버 함수로 구성됩니다. 선언 뒤에는 세미콜론(;)을 반드시 붙여야 합니다.

 

예를 들어, 학생을 나타내는 'Student' 클래스를 선언해 보겠습니다.

 

[예제]

class Student {
public: // 접근 지시자
    // 멤버 변수
    string name;
    int age;
    double grade;

    // 멤버 함수
    void study() {
        grade += 0.1;
    }
};


위의 'Student' 클래스는 'name', 'age', 'grade'라는 멤버 변수와 'study'라는 멤버 함수를 가집니다. 'public'은 접근 지시자로, 이 클래스의 멤버들이 외부에서 접근 가능함을 나타냅니다. 접근 지시자에 대한 자세한 내용은 다음 섹션에서 다루겠습니다.

 

클래스를 선언하면 이 클래스 타입의 객체를 만들 수 있습니다. 객체를 만드는 방법은 기본 자료형의 변수를 선언하는 것과 비슷합니다.

 

[예제]

Student student1; // Student 타입의 객체 생성

 

위 코드는 'Student' 클래스 타입의 'student1' 객체를 생성합니다. 이제 'student1' 객체는 'Student' 클래스의 모든 멤버 변수와 멤버 함수를 사용할 수 있습니다. 

 

이처럼 클래스 선언은 객체 지향 프로그래밍의 핵심 요소로, 클래스를 이해하는 것은 C++ 프로그래밍의 중요한 기초입니다.

 

2.2.2 멤버 변수와 멤버 함수

클래스는 멤버 변수와 멤버 함수로 구성됩니다. 멤버 변수는 클래스 내에서 선언되는 변수로, 객체의 상태를 나타내는 데 사용됩니다. 반면 멤버 함수는 클래스 내에서 선언되는 함수로, 객체가 수행할 수 있는 작업을 나타냅니다.

 

멤버 변수는 클래스 내부에서만 접근 가능하지만, 멤버 함수는 클래스 내부뿐만 아니라 클래스의 객체를 통해 외부에서도 호출이 가능합니다. 즉, 멤버 함수는 객체의 인터페이스를 제공하는 역할을 합니다.

 

예를 들어, 학생을 나타내는 Student 클래스를 다시 살펴보겠습니다.

 

[예제]

class Student {
public: 
    // 멤버 변수
    string name;
    int age;
    double grade;

    // 멤버 함수
    void study() {
        grade += 0.1;
    }
};


Student 클래스는 멤버 변수 name, age, grade를 가지고 있으며, 멤버 함수 study를 가지고 있습니다. 이 클래스를 이용하여 객체를 생성하면, 각 객체는 이 멤버 변수와 함수를 사용할 수 있게 됩니다.

 

[예제]

Student student1;
student1.name = "John";
student1.age = 20;
student1.grade = 3.5;

cout << student1.name << " is " << student1.age << " years old." << endl;
student1.study();
cout << "After studying, " << student1.name << "'s grade is " << student1.grade << endl;


이 코드에서 student1 객체는 Student 클래스의 멤버 변수 name, age, grade에 접근하여 값을 설정하고, 멤버 함수 study를 호출하여 grade 값을 변경하고 있습니다.

 

멤버 변수와 멤버 함수는 클래스의 핵심 요소로, 객체 지향 프로그래밍에서 클래스를 이해하고 사용하는 데 필수적입니다. 다음 섹션에서는 객체를 생성하고 삭제하는 생성자와 소멸자에 대해 알아보겠습니다.

 

2.2.3 클래스의 생성과 소멸: 생성자와 소멸자

클래스의 객체가 생성될 때와 소멸될 때 수행할 작업을 정의하는 특별한 멤버 함수가 있습니다. 이를 생성자(Constructor)와 소멸자(Destructor)라고 합니다.

 

생성자는 클래스의 객체가 생성될 때 호출되는 멤버 함수입니다. 객체 생성 시 초기 상태를 설정하는 등의 작업을 수행합니다. 생성자의 이름은 클래스 이름과 같으며, 반환 형이 없습니다.

 

소멸자는 객체가 소멸될 때 호출되는 멤버 함수입니다. 객체가 소멸될 때 수행해야 할 작업을 정의합니다. 예를 들어, 객체가 동적 메모리를 할당했다면, 소멸자에서 이 메모리를 해제합니다. 소멸자의 이름은 클래스 이름 앞에 '~' 기호를 붙인 것이며, 마찬가지로 반환 형이 없습니다.

 

예를 들어, 학생을 나타내는 Student 클래스에 생성자와 소멸자를 추가해 보겠습니다.

 

[예제]

class Student {
public: 
    string name;
    int age;
    double grade;

    // 생성자
    Student(string n, int a, double g) : name(n), age(a), grade(g) {
        cout << "A student " << name << " is created." << endl;
    }

    // 소멸자
    ~Student() {
        cout << "A student " << name << " is destroyed." << endl;
    }

    void study() {
        grade += 0.1;
    }
};


Student 클래스의 생성자는 이름, 나이, 성적을 매개변수로 받아 멤버 변수를 초기화하며, 객체 생성을 알리는 메시지를 출력합니다. 소멸자는 객체가 소멸될 때 메시지를 출력합니다. 이제 Student 객체를 생성하고 소멸하는 코드를 살펴봅시다.

[예제]

{
    Student student1("John", 20, 3.5);
    student1.study();
} // student1 객체가 소멸되는 지점


이 코드를 실행하면, "A student John is created."라는 메시지가 출력되고, student1.study();를 통해 성적이 증가합니다. 그리고 student1 객체가 소멸하는 지점에서 "A student John is destroyed."라는 메시지가 출력됩니다.

 

2.2.4 객체 생성과 메모리 할당

클래스는 사용자 정의 데이터 유형입니다. 이를 기반으로 객체를 생성하면, 이 객체는 메모리 공간을 차지하게 됩니다. 이 섹션에서는 클래스로부터 객체를 생성하고, 그 과정에서 메모리가 어떻게 할당되는지 알아보겠습니다.

 

C++에서 클래스의 객체는 두 가지 방법으로 생성됩니다. 첫 번째 방법은 스택에 객체를 생성하는 것이며, 두 번째 방법은 힙에 객체를 생성하는 것입니다.

 

스택에 객체 생성하기

스택에 객체를 생성하려면, 클래스의 이름을 사용해 객체를 선언하기만 하면 됩니다. 이런 경우, 객체는 해당 범위(scope)가 끝날 때 자동으로 소멸됩니다.

[예제]

{
    Student student1("John", 20, 3.5);
} // student1 객체가 소멸되는 지점

 

힙에 객체 생성하기

힙에 객체를 생성하려면, new 키워드를 사용해야 합니다. 이 경우, 객체는 수동으로 delete 키워드를 사용하여 소멸시켜야 합니다.

 

[예제]

{
    Student* student1 = new Student("John", 20, 3.5);
    delete student1; // 객체를 소멸시키지 않으면 메모리 누수 발생
} // 스코프 끝


힙에 객체를 생성하면, 그 객체의 수명을 코드에서 수동으로 관리해야 합니다. new를 사용하여 메모리를 할당하면 반드시 delete를 사용하여 메모리를 해제해야 합니다. 그렇지 않으면 메모리 누수가 발생합니다. 메모리 누수는 프로그램이 계속 실행되면서 점차 메모리를 소비하는 문제로, 시스템 성능을 저하시킵니다.

 

결론적으로, 클래스로부터 객체를 생성하면 그 객체는 메모리를 차지하게 됩니다. 객체가 생성되는 곳(스택 혹은 힙)에 따라 그 수명과 메모리 관리 방법이 달라집니다. 이에 대한 정확한 이해는 효과적인 C++ 프로그래밍에 필수적입니다.

 

2.3. 클래스의 접근 지시자

클래스 내부의 멤버 변수와 함수에 대한 접근을 제어하는 방법에 대해 배웁니다. 클래스는 private, public, protected 세 가지 접근 지시자를 제공합니다. private 멤버는 해당 클래스 내부에서만 접근 가능하며, public 멤버는 어디에서나 접근 가능합니다. protected 멤버는 해당 클래스 및 파생 클래스 내에서만 접근 가능합니다. 이러한 접근 지시자는 캡슐화와 정보 은닉을 제공하며, 데이터를 보호하는 데 중요합니다.

2.3.1. public, private, protected의 이해

클래스의 멤버에 대한 접근 권한을 제어하는 것은 OOP(객체 지향 프로그래밍)의 핵심 요소 중 하나입니다. C++에서는 public, private, 그리고 protected라는 세 가지 접근 지시자를 제공합니다.

 

  • public : 이 접근 지시자 아래에 선언된 멤버는 어디에서나 접근할 수 있습니다. 클래스 내부, 클래스 외부, 그리고 상속받은 클래스에서 모두 이 멤버를 접근할 수 있습니다.

[예제]

class MyClass {
public:
  int x;  // 모든 곳에서 접근 가능
};

 

 

  • private : private 멤버는 해당 클래스 내에서만 접근할 수 있습니다. 클래스 외부나 상속받은 클래스에서는 접근할 수 없습니다.

[예제]

class MyClass {
private:
  int y;  // 오직 MyClass 내부에서만 접근 가능
};

 

  • protected : protected 멤버는 해당 클래스 내부와 이 클래스를 상속받은 클래스 내에서만 접근할 수 있습니다. 다른 외부 클래스나 함수에서는 접근할 수 없습니다.

[예제]

class ParentClass {
protected:
  int z;  // ParentClass와 이를 상속받은 클래스에서만 접근 가능
};

 

이 세 가지 접근 지시자를 이해하는 것은 중요하며, 클래스를 설계할 때 어떤 멤버가 어디에서 접근될 수 있어야 하는지 결정하는 데 필요합니다. 일반적으로 클래스의 내부 데이터는 private으로 설정하여 외부 접근을 제한하며, 필요한 경우에만 public이나 protected 멤버를 통해 접근하도록 합니다. 이것이 데이터 캡슐화라는 OOP의 중요한 개념으로, 데이터를 보호하고 코드의 안정성을 향상합니다.

 

2.3.2. 접근 지시자의 사용법

이전에 언급한 public, private, protected 접근 지시자들을 어떻게 사용하는지에 대해 자세히 알아보겠습니다. 각각의 접근 지시자는 클래스 선언 안에서 사용되며, 그 아래에 오는 멤버들에게 해당 접근 지시자의 특성을 부여합니다.

 

public 사용법

public 접근 지시자는 모든 곳에서 접근 가능한 멤버를 선언하는데 사용됩니다. 클래스의 외부에서도 접근할 수 있는 메서드를 정의할 때 사용합니다.

 

[예제]

class MyClass {
public:
  int x;  // 어디에서나 접근 가능
  void publicMethod() {
    // 어디에서나 호출 가능
  }
};

 

 

private 사용법

private 접근 지시자는 오직 해당 클래스 내부에서만 접근 가능한 멤버를 선언하는 데 사용됩니다. 클래스의 내부 상태를 숨기고 싶을 때 사용합니다.

 

[예제]

class MyClass {
private:
  int y;  // MyClass에서만 접근 가능
  void privateMethod() {
    // MyClass 내부에서만 호출 가능
  }
};

 

protected 사용법

protected 접근 지시자는 해당 클래스 및 상속받은 클래스에서 접근 가능한 멤버를 선언하는 데 사용됩니다. 하위 클래스에서 접근해야 하는 멤버를 정의할 때 사용합니다.

 

[예제]

class ParentClass {
protected:
  int z;  // ParentClass 및 상속받은 클래스에서만 접근 가능
  void protectedMethod() {
    // ParentClass 및 상속받은 클래스에서만 호출 가능
  }
};

 

이처럼 C++에서는 클래스의 멤버에 대한 접근 제어를 위해 public, private, protected 세 가지 접근 지시자를 제공합니다. 각 접근 지시자는 클래스의 멤버에 대한 접근 범위와 권한을 정의합니다. 이를 통해 데이터 캡슐화와 정보 은닉 등의 객체 지향 프로그래밍의 핵심 원칙을 실현할 수 있습니다. 적절한 접근 지시자의 사용은 클래스 설계의 중요한 부분으로, 코드의 안정성과 유지 관리성을 향상합니다.

 

2.4. this 포인터의 이해

'this' 포인터는 C++의 객체 지향 프로그래밍에서 매우 중요한 개념입니다. 각 객체의 멤버 함수가 호출될 때, 해당 함수 내에서 'this'는 현재 객체의 주소를 가리키는 포인터입니다. 따라서 'this'를 통해 객체의 멤버 변수에 접근하거나, 객체 자신을 전달하거나 반환하는데 사용할 수 있습니다. 이는 객체가 자기 자신의 정보에 접근할 수 있는 방법을 제공하며, 특히 연산자 오버로딩 같은 고급 기능에 유용합니다.

 

2.4.1. this 포인터란 무엇인가

'this' 포인터는 C++에서 매우 중요한 개념입니다. 이것은 객체 내부에서 사용되며, 해당 객체의 주소를 나타냅니다. 그런데 왜 이러한 포인터가 필요한 걸까요? 간단한 예제를 통해 살펴봅시다.

 

[예제]

class Rectangle {
    int width, height;
public:
    void set(int width, int height) {
        this->width = width;
        this->height = height;
    }
};

int main() {
    Rectangle rect;
    rect.set(10, 20);
    return 0;
}


위의 코드에서 'this' 포인터는 set 함수 내부에서 사용되며, 클래스 Rectangle의 멤버 변수 width와 height를 참조합니다. 여기서 this->width와 this->height는 클래스의 멤버 변수를 가리키고, width와 height는 set 함수의 매개변수를 가리킵니다. 즉, 'this' 포인터를 사용하면 멤버 함수 내부에서 클래스의 멤버 변수와 같은 이름의 매개변수를 사용할 수 있습니다. 

 

또한, 'this' 포인터는 멤버 함수 내부에서만 사용할 수 있습니다. 즉, 클래스의 멤버 함수가 아닌 곳에서는 'this' 포인터를 사용할 수 없습니다. 이는 'this' 포인터가 객체 자신을 참조하기 때문입니다. 따라서, 'this' 포인터는 현재 객체에 대한 정보를 가져올 때, 혹은 현재 객체를 다른 함수에 전달하거나 반환할 때 유용하게 사용됩니다.                                                                                                               

 

'this' 포인터를 이해하고 활용하면, 객체 지향 프로그래밍의 이해도가 높아질 것입니다. 오버로딩된 연산자나 다형성 등의 고급 기능을 구현하는 데에도 이 포인터는 중요한 역할을 합니다. 따라서 'this' 포인터는 C++ 객체 지향 프로그래밍의 중요한 부분이며, 깊이 이해하고 활용하는 것이 중요합니다.

 

2.4.2. this 포인터의 사용

'this' 포인터는 클래스의 멤버 함수 내에서 해당 함수를 호출한 객체의 주소를 가리키는 포인터입니다. 'this' 포인터는 명시적으로 호출할 필요가 없으며, 멤버 함수가 호출되면 자동으로 사용할 수 있습니다. 이것을 활용하면, 멤버 함수 내에서 클래스의 멤버 변수와 매개변수의 이름이 같아도 구분해서 사용할 수 있습니다.

 

또한, 'this' 포인터를 사용하여 객체 자신을 참조하거나 다른 함수에 현재 객체를 전달하는 것도 가능합니다. 이제 'this' 포인터의 사용법을 보다 자세히 살펴보겠습니다.

 

[예제]

class Student {
    int id;
public:
    void setID(int id) {
        this->id = id; // 'this' pointer is used to distinguish between the parameter "id" and the member variable "id"
    }
    int getID() {
        return this->id; // 'this' pointer is not necessary here, but it is used for clarity
    }
};

int main() {
    Student stu;
    stu.setID(123);
    std::cout << "Student ID: " << stu.getID() << std::endl;
    return 0;
}


위의 예제에서 setID 함수는 'this' 포인터를 사용하여 멤버 변수 id와 매개변수 id를 구분하고 있습니다. 이렇게 'this' 포인터를 사용하면 멤버 변수와 동일한 이름의 매개변수를 사용할 수 있으므로, 코드의 가독성을 높일 수 있습니다.

 

하지만 getID 함수에서 보시다시피, 'this' 포인터를 굳이 사용하지 않아도 멤버 변수를 참조할 수 있습니다. 이는 'this' 포인터가 묵시적으로 작동하기 때문입니다. 하지만 가독성을 위해 'this' 포인터를 명시적으로 사용하는 것이 좋습니다.

 

이러한 방식으로 'this' 포인터는 멤버 함수 내에서 현재 객체에 대한 정보를 가져오거나 현재 객체를 다른 함수에 전달하는 데 사용됩니다. 이를 이해하고 활용하면 C++의 객체 지향적 특성을 더욱 효과적으로 활용할 수 있습니다.

 

2.5. 정적 멤버와 정적 함수

C++의 클래스는 정적 멤버 변수와 정적 멤버 함수를 가질 수 있습니다. 정적 멤버 변수는 클래스의 모든 객체가 공유하는 변수로, 객체가 생성되지 않아도 사용할 수 있습니다. 이는 프로그램 실행 시작 시 메모리에 할당되고, 종료 시 해제됩니다. 반면, 정적 멤버 함수는 객체를 통하지 않고 호출할 수 있으며, 일반 멤버 변수에는 접근할 수 없습니다. 이는 정적 멤버 함수가 객체의 특정 인스턴스에 속하지 않기 때문입니다. 정적 멤버 변수나 함수를 선언할 때는 'static' 키워드를 사용합니다.

 

2.5.1. 정적 멤버 변수의 이해와 활용

C++에서 '정적 멤버 변수(static member variable)'는 클래스가 생성하는 모든 객체에 대해 공유되는 변수입니다. 이는 클래스 내에서 'static' 키워드를 사용하여 선언됩니다. 이것의 특징은 객체를 생성하지 않고도 사용이 가능하다는 점입니다. 프로그램이 시작될 때 메모리에 할당되며, 프로그램이 종료될 때까지 유지되는데, 이렇게 할당된 메모리는 클래스의 모든 인스턴스들이 공유합니다. 

[예제]

class MyClass {
public:
    static int staticVar;
};

// Static 변수는 클래스 외부에서 초기화해야 합니다.
int MyClass::staticVar = 0;

 

위 코드에서 'staticVar'는 정적 멤버 변수로, 모든 'MyClass'의 인스턴스에 대해 공유됩니다. 클래스 외부에서 초기화해야 함을 유의해야 합니다. 

 

정적 멤버 변수의 활용을 보자면, 만약 여러분이 게임을 개발하고 있고, 게임 내 모든 캐릭터가 공유해야 하는 특정 정보(예: 전체 게임 점수)가 있다면, 이 정보는 정적 멤버 변수로 선언될 수 있습니다. 

 

다음은 정적 멤버 변수의 활용 예제입니다.

 

[예제]

#include<iostream>
using namespace std;

class GameCharacter {
public:
    static int gameScore;

    void addScore(int score) {
        gameScore += score;
    }
};

int GameCharacter::gameScore = 0; // 초기화

int main() {
    GameCharacter character1, character2;

    character1.addScore(50);
    character2.addScore(100);

    cout << "Total Game Score : " << GameCharacter::gameScore << endl;
    return 0;
}


이 프로그램을 실행하면, 모든 GameCharacter 인스턴스가 'gameScore'를 공유함을 확인할 수 있습니다. 'character1'과 'character2' 모두가 점수를 추가하였지만, 최종 점수는 공유되어 더해진 결과를 출력합니다. 

 

2.5.2. 정적 멤버 함수의 이해와 활용

C++에서 정적 멤버 함수(static member function)는 클래스의 모든 객체가 공유하는 함수입니다. 이 함수는 'static' 키워드를 사용하여 선언됩니다. 정적 멤버 함수의 특징 중 하나는 이 함수 내에서는 정적 멤버 변수만 접근할 수 있다는 점입니다. 즉, 해당 클래스의 비정적 멤버 변수에는 접근할 수 없습니다. 

[예제]

class MyClass {
public:
    static int staticVar;
    static void staticFunc() {
        // 함수 내에서 staticVar에 접근 가능
        staticVar = 10;
    }
};

int MyClass::staticVar = 0; // 초기화


이 예제에서, staticFunc는 MyClass의 정적 멤버 함수로서, 이 함수 내에서는 staticVar라는 정적 멤버 변수에 접근이 가능합니다.

 

정적 멤버 함수의 주요 활용 중 하나는 정적 멤버 변수의 값을 변경하거나 조회하는 것입니다. 이를 'getter'와 'setter'라고도 합니다.

 

다음은 정적 멤버 함수의 활용 예시입니다.

 

[예제]

#include<iostream>
using namespace std;

class GameCharacter {
public:
    static int gameScore;
    static void addScore(int score) {
        gameScore += score;
    }
    static int getScore() {
        return gameScore;
    }
};

int GameCharacter::gameScore = 0; // 초기화

int main() {
    GameCharacter::addScore(50); // 객체 생성 없이 사용 가능
    GameCharacter::addScore(100);

    cout << "Total Game Score : " << GameCharacter::getScore() << endl; // 객체 생성 없이 사용 가능
    return 0;
}


이 코드를 실행하면, 'addScore'와 'getScore'와 같은 정적 멤버 함수를 통해 'gameScore'를 쉽게 관리하고 조회할 수 있습니다. 또한 이들 함수는 클래스 이름을 통해 직접 호출될 수 있으므로, 객체를 생성하지 않고도 사용할 수 있습니다.

 

정적 멤버 변수와 정적 멤버 함수를 함께 사용하면, 코드를 더 효율적이고 관리하기 쉽게 만들 수 있습니다. 이 기능을 이해하고 잘 활용한다면, 프로그래밍 능력을 크게 향상시킬 수 있습니다.

 

2.6. 객체 지향 프로그래밍의 기본 개념 복습

객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 프로그래밍 패러다임 중 하나로, 프로그램을 객체들의 집합으로 보고, 객체들 간의 상호작용을 통해 구현하게 됩니다. 이 패러다임의 핵심 개념에는 클래스, 객체, 상속, 캡슐화, 다형성 등이 있습니다.

클래스는 객체를 생성하는 템플릿으로, 객체는 클래스에 정의된 속성과 메소드를메서드를 가집니다. 캡슐화는 데이터와 데이터를 조작하는 함수를 하나로 묶는 과정을 의미하며, 상속은 기존 클래스의 속성과 메서드를 재사용하고 확장할 수 있는 기능입니다. 마지막으로, 다형성은 하나의 인터페이스를 통해 다양한 형태로 동작하도록 만드는 기능을 의미합니다.

2.6.1. 캡슐화

캡슐화는 객체 지향 프로그래밍의 핵심 원칙 중 하나로, 객체의 상태를 나타내는 데이터와 해당 데이터를 조작하는 메소드를 하나의 단위로 묶는 것을 의미합니다. 이를 통해 코드를 보다 효율적이고 안전하게 관리할 수 있습니다. 

 

[예제]

class Car {
private: // private 접근 지시자로 데이터를 은닉합니다.
    int speed;
    int gear;

public: // public 접근 지시자로 메소드를 외부에 공개합니다.
    void setSpeed(int s) {
        if (s < 0) { // 데이터 유효성을 검사하는 로직이 포함될 수 있습니다.
            cout << "Invalid speed value" << endl;
            return;
        }
        speed = s;
    }

    int getSpeed() {
        return speed;
    }

    // gear에 대한 setter/getter 메소드도 마찬가지로 정의할 수 있습니다.
};


이러한 방식을 통해, 클래스의 데이터는 외부에서 직접 접근할 수 없게 되며, 오직 클래스가 제공하는 메소드를 통해서만 접근 및 수정이 가능합니다. 이는 데이터의 무결성을 보장하고, 코드의 안정성을 높이는 효과가 있습니다. 

 

다음은 Car 클래스의 객체를 생성하고 사용하는 예시입니다.

 

[예제]

int main() {
    Car myCar;
    myCar.setSpeed(100); // 캡슐화된 데이터에 접근하는 메소드를 사용합니다.
    cout << "Speed: " << myCar.getSpeed() << endl;
    return 0;
}

 

이 코드는 Car 클래스의 setSpeed 메소드를메서드를 호출하여 speed 데이터를 설정하고, getSpeed 메서드를 사용하여 현재 speed 값을 출력합니다. 이 과정에서 speed 데이터에 직접 접근하는 것이 아니라 메서드를 통해 간접적으로 접근하고 있음을 확인할 수 있습니다. 이것이 바로 캡슐화의 핵심 원칙입니다. 

 

[주석]

  • private: 클래스 외부에서 접근할 수 없는 멤버를 정의하는 키워드입니다.
  • public: 클래스 외부에서 접근할 수 있는 멤버를 정의하는 키워드입니다.
  • setSpeed: speed 데이터를 설정하는 메소드입니다.
  • getSpeed: speed 데이터를 반환하는 메소드입니다.
  • Invalid speed value: 속도 값이 잘못 설정되었을 때 출력되는 메시지입니다.

2.6.2. 상속

상속은 객체 지향 프로그래밍에서 가장 중요한 특성 중 하나로, 기존 클래스(부모 클래스 또는 기본 클래스라고도 함)의 특성을 다른 클래스(자식 클래스 또는 파생 클래스라고도 함)가 그대로 이어받는 것을 말합니다. 이를 통해 코드의 재사용성을 높이고, 보다 효율적인 프로그래밍이 가능합니다.

 

다음은 Animal 클래스를 상속하여 Dog 클래스를 생성하는 C++ 코드입니다.

 

[예제]

class Animal {  // 기본 클래스
public:
    void eat() {
        cout << "Eating..." << endl;
    }
};

class Dog : public Animal {  // Animal 클래스를 상속받은 Dog 클래스
public:
    void bark() {
        cout << "Barking..." << endl;
    }
};


이 예제에서 Dog 클래스는 Animal 클래스를 상속받고 있으므로, Animal 클래스에 정의된 모든 퍼블릭 메서드를 사용할 수 있습니다. 

 

[예제]

int main() {
    Dog dog;
    dog.eat();  // Animal 클래스의 메소드를 사용
    dog.bark(); // Dog 클래스에서 새롭게 정의된 메소드를 사용
    return 0;
}


Dog 객체에서 eat 메소드를 호출할 수 있는 것을 확인할 수 있습니다. 이 메서드는 Animal 클래스에서 정의되었지만, Dog 클래스에서 상속받았기 때문에 사용할 수 있는 것입니다. 또한 Dog 클래스에서 정의한 bark 메서드 역시 사용 가능합니다. 

 

[주석]

  • Animal: 기본 클래스입니다. eat 메소드를 가지고 있습니다.
  • Dog: Animal 클래스를 상속받은 파생 클래스입니다. bark 메서드를 추가로 가지고 있습니다.
  • public Animal: Animal 클래스를 public으로 상속하겠다는 의미입니다. 이렇게 하면 Animal의 public 멤버가 Dog에서도 public으로 상속됩니다.

상속은 또한 다형성과 밀접한 관련이 있습니다. 다형성은 상속 관계에 있는 클래스들 사이에서 같은 이름의 메서드가 다르게 동작하도록 하는 특성입니다. 

 

2.6.3. 다형성

다형성이란, 객체 지향 프로그래밍에서 상속 관계에 있는 클래스들 사이에서 같은 이름의 메소드가 다르게 동작하도록 하는 특성을 말합니다. 이는 코드의 유연성을 높이며, 유지보수와 확장성을 강화시킵니다.

 

다음은 Animal 기본 클래스와 그를 상속받는 Dog과 Cat 클래스를 사용한 다형성의 예제입니다.

[예제]

class Animal {  // 기본 클래스
public:
    virtual void makeSound() {
        cout << "The animal makes a sound" << endl;
    }
};

class Dog : public Animal {  // Animal 클래스를 상속받은 Dog 클래스
public:
    void makeSound() override {
        cout << "The dog barks" << endl;
    }
};

class Cat : public Animal {  // Animal 클래스를 상속받은 Cat 클래스
public:
    void makeSound() override {
        cout << "The cat meows" << endl;
    }
};


여기서, Animal 클래스에는 makeSound라는 가상 함수가 정의되어 있습니다. Dog와 Cat 클래스는 이 makeSound 함수를 오버라이드(재정의)하여 각각 고유의 행동을 정의하였습니다.

[예제]

int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();
    animal1->makeSound();  // The dog barks
    animal2->makeSound();  // The cat meows
    delete animal1;
    delete animal2;
    return 0;
}


위의 main 함수에서, Animal 포인터를 통해 Dog와 Cat 객체에 접근하였습니다. 이 때, makeSound 함수를 호출하면, 실제로 가리키는 객체의 makeSound 함수가 호출됩니다. 이것이 바로 다형성입니다.

 

[주석]

  • virtual: 기본 클래스에서 메소드를 가상으로 선언하면 파생 클래스에서 해당 메서드를 오버라이드할 수 있습니다.
  • override: 파생 클래스에서 기본 클래스의 가상 메소드를 재정의하는 키워드입니다.

다형성은 코드를 더욱 유연하게 만들어 줍니다. 그러나 이러한 다형성을 제대로 활용하려면, 가상 함수와 오버라이딩 같은 기법을 이해해야 합니다. 

 

반응형

댓글