[c++] 클래스

About_work·2024년 9월 4일
0

c++

목록 보기
5/8

1. 기초 개념

1. 클래스 작성과 사용하는 기본 예시 (헤더 파일 없이)

우선, 헤더 파일 없이 하나의 파일 안에 클래스 정의와 사용 예시를 작성해보겠습니다.

#include <iostream>
#include <string>

// 클래스 정의
class Person {
private:
    // 멤버 변수
    std::string name;
    int age;

public:
    // 생성자 (초기화 리스트 사용하지 않은 버전)
    Person(std::string nameInput, int ageInput) {
        name = nameInput;
        age = ageInput;
    }

    // 멤버 함수
    void introduce() {
        std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl;
    }

    // 나이를 증가시키는 함수
    void increaseAge() {
        age++;
    }
};

int main() {
    // 클래스 객체 생성 및 사용
    Person person1("Alice", 25);
    person1.introduce();  // 출력: Hello, my name is Alice and I am 25 years old.
    
    person1.increaseAge();
    person1.introduce();  // 출력: Hello, my name is Alice and I am 26 years old.
    
    return 0;
}

2. 초기화 리스트를 사용한 생성자 예시

초기화 리스트를 사용하면 생성자 내부에서 멤버 변수를 더 효율적으로 초기화할 수 있습니다.

#include <iostream>
#include <string>

// 클래스 정의
class Person {
private:
    std::string name;
    int age;

public:
    // 생성자 (초기화 리스트 사용한 버전)
    Person(std::string nameInput, int ageInput) : name(nameInput), age(ageInput) {
        // 빈 블록: 초기화 리스트로 이미 초기화됨
    }

    void introduce() {
        std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl;
    }

    void increaseAge() {
        age++;
    }
};

int main() {
    Person person2("Bob", 30);
    person2.introduce();  // 출력: Hello, my name is Bob and I am 30 years old.

    return 0;
}

설명:

  • 생성자에서 name(nameInput), age(ageInput) 형태로 초기화 리스트를 사용
  • 이렇게 하면 멤버 변수를 직접 초기화할 수 있고, 효율적인 메모리 할당이 가능

3. 헤더 파일과 구현 파일로 나누는 방식 (모듈화)

헤더 파일을 사용하여 클래스 정의와 구현을 분리하는 방식은 더 큰 프로젝트에서 사용하기 적합

Person.h (헤더 파일)

#ifndef PERSON_H
#define PERSON_H

#include <string>

class Person {
private:
    std::string name;
    int age;

public:
    // 생성자
    Person(std::string nameInput, int ageInput);
    
    // 멤버 함수 선언
    void introduce();
    void increaseAge();
};

#endif

Person.cpp (구현 파일)

#include "Person.h"
#include <iostream>

// 생성자 정의 (초기화 리스트 사용)
Person::Person(std::string nameInput, int ageInput) : name(nameInput), age(ageInput) {
    // 초기화는 이미 리스트로 완료됨
}

// 멤버 함수 정의
void Person::introduce() {
    std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl;
}

void Person::increaseAge() {
    age++;
}

main.cpp (메인 파일)

#include "Person.h"

int main() {
    Person person3("Charlie", 40);
    person3.introduce();  // 출력: Hello, my name is Charlie and I am 40 years old.

    person3.increaseAge();
    person3.introduce();  // 출력: Hello, my name is Charlie and I am 41 years old.

    return 0;
}

설명:

  • Person.h 파일에는 클래스의 정의와 멤버 함수 선언만 포함되어 있습니다.
  • Person.cpp 파일은 실제로 함수가 어떻게 동작하는지에 대한 구현이 들어가 있습니다.
  • main.cpp 파일에서는 #include "Person.h"를 통해 Person 클래스를 사용

4. 헤더 파일을 사용한 방식과 사용하지 않은 방식의 차이점

  • 헤더 파일 없이 작성:
    • 모든 코드가 하나의 파일에 들어가기 때문에 작은 프로젝트에서는 관리가 간편할 수 있습니다.
    • 하지만 프로젝트가 커지면 유지보수가 어려워지고, 각 파일에 같은 클래스 정의를 반복할 위험이 있습니다.
  • 헤더 파일을 사용하는 방식:
    • 클래스의 선언과 구현을 분리하여 코드 재사용성을 높이고, 유지보수를 쉽게 만듭니다.
    • 클래스 정의가 여러 파일에 걸쳐 있더라도 필요할 때마다 해당 클래스만 포함하면 됩니다.

2. public / private / protected

C++에서 public, private, protected접근 지정자(Access Specifiers)로, 클래스의 멤버(변수 및 함수)에 대해 외부에서 접근할 수 있는 범위를 결정합니다. 각각의 접근 지정자가 하는 역할을 설명하겠습니다.

1. public (공개 접근)

  • public으로 선언된 멤버는 클래스 외부에서도 접근할 수 있습니다.
  • 객체가 만들어진 후 . 연산자를 통해 직접 해당 멤버 변수나 함수에 접근할 수 있습니다.

예시:

class Example {
public:
    int publicVar;  // public 멤버 변수

    void publicMethod() {  // public 멤버 함수
        std::cout << "This is a public method." << std::endl;
    }
};

int main() {
    Example obj;
    obj.publicVar = 10;  // 클래스 외부에서 접근 가능
    obj.publicMethod();  // 클래스 외부에서 호출 가능
}

특징:

  • publicVarpublicMethod()는 클래스 외부에서도 자유롭게 접근할 수 있습니다.

2. private (비공개 접근)

  • private로 선언된 멤버는 클래스 외부에서는 접근할 수 없습니다.
  • 오직 클래스 내부에서만 접근할 수 있습니다.
  • 외부에서 직접 접근하려면 public 메서드를 통해 접근하는 방법을 사용해야 합니다.

예시:

class Example {
private:
    int privateVar;  // private 멤버 변수

    void privateMethod() {  // private 멤버 함수
        std::cout << "This is a private method." << std::endl;
    }

public:
    void setPrivateVar(int value) {  // public 멤버 함수로 private 멤버에 접근
        privateVar = value;
    }

    void showPrivateMethod() {  // public 멤버 함수로 private 메서드 호출
        privateMethod();
    }
};

int main() {
    Example obj;
    // obj.privateVar = 10;  // 오류: private 멤버는 외부에서 직접 접근 불가능
    obj.setPrivateVar(10);  // public 함수 통해 간접적으로 접근 가능
    obj.showPrivateMethod();  // public 함수 통해 private 메서드 호출 가능
}

특징:

  • privateVarprivateMethod()는 클래스 외부에서 직접 접근할 수 없습니다.
  • setPrivateVar()showPrivateMethod() 같은 public 메서드를 통해 간접적으로 접근할 수 있습니다.

3. protected (보호된 접근)

  • protected로 선언된 멤버는 클래스 외부에서는 접근할 수 없지만, 파생 클래스(상속받은 클래스)에서는 접근할 수 있습니다.
  • 즉, private과 달리 상속받은 클래스에서 접근이 허용되지만, 외부에서는 여전히 접근할 수 없습니다.

예시:

class Base {
protected:
    int protectedVar;  // protected 멤버 변수

public:
    void setProtectedVar(int value) {
        protectedVar = value;
    }
};

class Derived : public Base {
public:
    void showProtectedVar() {
        std::cout << "Protected Var: " << protectedVar << std::endl;
        // 파생 클래스에서는 protected 멤버에 접근 가능
    }
};

int main() {
    Derived obj;
    obj.setProtectedVar(20);
    obj.showProtectedVar();  // 출력: Protected Var: 20
}

특징:

  • protectedVarDerived 클래스에서 접근할 수 있지만, main() 함수처럼 외부에서는 접근할 수 없습니다.

public, private, protected의 차이점 요약:

접근 지정자클래스 내부파생 클래스(상속)외부 (클래스 외부)
publicOOO
protectedOOX
privateOXX

일반적인 사용 예:

  • public: 외부에서 객체를 통해 직접 접근해야 하는 멤버에 사용됩니다. (예: 클래스 사용자에게 제공되는 인터페이스)
  • private: 외부에서 접근을 허용하지 않아야 하는 멤버에 사용됩니다. (예: 내부 구현 세부 사항, 보안 관련 데이터)
  • protected: 상속을 통해 파생 클래스에서 접근해야 하지만, 외부에서는 접근이 허용되지 않아야 하는 멤버에 사용됩니다. (예: 상속받은 클래스가 사용할 수 있는 변수나 함수)

이를 통해 적절한 캡슐화와 접근 제어를 유지할 수 있습니다.


3. 오버로딩과 위임(delegation)

  • 클래스 정의를 두 번 한 것이 아니라, 클래스 생성자를 두 가지 방식으로 오버로딩(overloading) 한 예시를 보여주려 함
  • 이 방식은 C++에서 생성자를 여러 개 정의할 수 있는 기능

구체적으로 살펴보면:

1. 헤더 파일(.h)의 코드 설명

헤더 파일에 두 가지 생성자 선언이 있습니다.

TsdfServer(const ros::NodeHandle& nh, const ros::NodeHandle& nh_private);
TsdfServer(const ros::NodeHandle& nh, const ros::NodeHandle& nh_private,
           const TsdfMap::Config& config,
           const TsdfIntegratorBase::Config& integrator_config,
           const MeshIntegratorConfig& mesh_config);
  • 첫 번째 생성자는 단순히 두 개의 NodeHandle을 매개변수로 받는 생성자입니다.
  • 두 번째 생성자는 더 복잡한 설정 객체들(config, integrator_config, mesh_config)을 매개변수로 받습니다.

2. 구현 파일(.cc)의 코드 설명

.cc 파일에서는 두 생성자의 실제 구현이 제공됩니다.

TsdfServer::TsdfServer(const ros::NodeHandle& nh,
                       const ros::NodeHandle& nh_private)
    : TsdfServer(nh, nh_private, getTsdfMapConfigFromRosParam(nh_private),
                 getTsdfIntegratorConfigFromRosParam(nh_private),
                 getMeshIntegratorConfigFromRosParam(nh_private)) {}
  • 첫 번째 생성자의 구현 부분에서는 다른 생성자를 호출하는 것을 볼 수 있습니다. 이는 생성자 위임(Constructor delegation) 이라고 부르며, C++11 이후부터 사용할 수 있는 기능입니다.
  • 첫 번째 생성자는 getTsdfMapConfigFromRosParam(), getTsdfIntegratorConfigFromRosParam(), getMeshIntegratorConfigFromRosParam() 함수들을 사용하여, nh_private에서 구성 정보를 가져와 두 번째 생성자를 호출합니다.
TsdfServer::TsdfServer(const ros::NodeHandle& nh,
                       const ros::NodeHandle& nh_private,
                       const TsdfMap::Config& config,
                       const TsdfIntegratorBase::Config& integrator_config,
                       const MeshIntegratorConfig& mesh_config) {
    // 이 생성자는 설정값을 직접 사용하는 경우
}
  • 두 번째 생성자는 인자로 받은 설정값들을 그대로 사용하여 초기화를 수행하는 생성자입니다.

요약:

  • 두 개의 생성자 선언이 있고, 각각 다른 인자를 받아서 객체를 초기화합니다.
  • 첫 번째 생성자는 생성자 위임을 통해 두 번째 생성자를 호출하며, 자동으로 구성 정보를 로딩하는 역할을 합니다.
  • 두 번째 생성자는 구성 객체들을 직접 받아서 초기화를 수행합니다.
  • 따라서, 클래스의 정의나 생성자가 "두 번" 존재하는 것이 아니라 오버로딩위임을 통해 유연한 생성자 로직을 제공합니다.
profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글