Search

Object-Oriented Programming

Class

class Point { public: // 접근 지정자 double x; // 멤버 변수 double y; // 기본 생성자 Point() : x(0.0), y(0.0) { // x와 y를 0으로 초기화 std::cout << "기본 생성자 호출 Point(" << x << ", " << y << ")" << std::endl; } // 파라미터를 받는 생성자 Point(double xVal, double yVal) : x(xVal), y(yVal) { // ':' constructor initialization list std::cout << "인자를 받는 생성자 호출 Point(" << x << ", " << y << ")" << std::endl; } }; Point p1; //instance, object p1.x = 10; // (.) = dot member selection operator p1.y = 20; Point p2(3.5, 4.5); class Counter { int count; // default로 private public: void clear() { count = 0; } // methods void inc() { count++; } int get() { return count; } };
C++
복사
public은 클래스 바깥에서 직접적으로 reference 가능 private은 method를 통해서만 접근 가능 constructor는 클래스 이름과 같은 이름의 method로 객체가 생성될 때 자동으로 호출됨. 정의하지 않으면 compiler에 의해 자동으로 생성됨. constructor는 return type이 없음.
class A { public: int number = 10; }; int main() { A a; A* p = new A; a.number = 20; p->number = 30; std::cout << a.number << std::endl; std::cout << p->number << std::endl; // p-> == (*p). std::cout << (*p).number << std::endl; delete p; }
C++
복사
클래스 인스턴스를 선언하는 방법은 두 가지가 있다. 클래스를 통해서 인스턴스를 생성하면 스택 영역에 메모리가 할당되며 함수실행이 끝난 이후 자동으로 해제된다. dot member selection operator를 통해서 멤버변수에 접근한다. 클래스 타입의 포인터를 사용하는 경우 new를 통해서 동적으로 heap에 할당되고 delete로 직접 해제해야 한다. arrow member selection operator(→) 혹은 (*p).을 통해 멤버변수에 접근할 수 있다.

this

클래스의 멤버함수 내에서 사용되며, 현재 객체를 가리키는 포인터. 객체의 멤버변수나 메서드에 접근할 수 있다. 포인터이기 때문에 멤버변수에 접근할 때 arrow를 쓴다. 일반적으로 멤버변수에서 this를 사용하지 않아도 암묵적으로 적용되지만 매개변수와 멤버변수의 이름이 같은 경우나 메서드 체이닝을 구현할 때는 this를 명시적으로 사용해야한다.
*this = {x,y} //현재 인스턴스에 대입 return *this // 현재 인스턴스 리턴 class MyClass { private: int value; public: MyClass& setValue(int value) { // 반환 타입이 참조여야 복사하지 않음 this->value = value; // 멤버 변수 설정 return *this; // this를 사용하여 현재 객체를 반환 } void printValue() const { std::cout << value << std::endl; } }; int main() { MyClass obj; obj.setValue(10).printValue(); // 메서드 체이닝 return 0; }
C++
복사

Encapsulation

캡슐화는 데이터와 데이터를 조작하는 method를 하나의 단위로 묶는 것. 객체의 내부 상태를 보호하고 외부에서 접근하는것을 제한해 데이터 무결성을 우지할 수 있다. access specifier(public, protected, private)를 통해서 접근성을 제어하고 데이터를 은닉한다. 클래스 내부의 데이터에 접근하거나 수정하기 위해서 getter, setter 메서드를 제공한다.
class BankAccount { private: // 데이터 은닉 double balance; // 잔고 public: // 외부 접근 가능 // 생성자 BankAccount(double initialBalance) : balance(initialBalance) {} // 잔고 확인 메서드 (getter) double getBalance() const { return balance; } // 입금 메서드 void deposit(double amount) { if (amount > 0) { balance += amount; } } }; int main(){ BankAccount bank1(1000); const BankAccount bank2(2000); //상태변경 불가, const메소드만 호출가능 }
C++
복사
const 멤버 함수: 객체의 상태를 변경하지 않으며, const로 선언된 객체에서도 호출할 수 있습니다. const 객체: 해당 객체는 상태를 변경할 수 없으며, const 멤버 함수만 호출할 수 있습니다. const 인자: 함수가 인자로 const 타입을 받으면, 해당 인자는 수정할 수 없으며, const 멤버 함수만 호출할 수 있습니다.

friend

friend함수는 특정 클래스의 비공식적인 멤버 함수로, 해당 클래스의 private및 protected멤버에 접근할 수 있는 함수이다. 다른 클래스나 함수가 클래스의 내부 데이터에 접근할 수 있도록 허용할 수 있다. 클래스의 내부에서 friend함수를 선언하고 함수의 정의는 외부에서 이루어지는 형태로 사용한다.
class Box { private: double width; public: Box(double w) : width(w) {} // friend 함수 선언 friend void printWidth(const Box& b); }; // friend 함수 정의 void printWidth(const Box& b) { std::cout << "Width: " << b.width << std::endl; // 접근 가능 } int main() { Box box(10.0); printWidth(box); // friend 함수 호출 return 0; }
C++
복사

Operator overloading

기존의 연산자를 재정의해서 사용자 정의 타입(클래스, 구조체) 객체 간의 연산을 자연스럽게 수행함.
std::cout << 에서 <<는 shift operator이지만 outstream객체에 대해 insertion operator로 재정의함
string type에서도 ==를 boolean을 비교하는 연산자에서 string을 비교하는 연산으로 overloading함.
단항연산자를 오버로딩할때는 인자가 필요없고 이항연산자는 한 개를 필요로 함
#include <iostream> class Vector2D { int x, y; public: Vector2D(int x, int y) : x(x), y(y) {} Vector2D operator+(const Vector2D &other) const { //this->x를 사용하지 않아도 암묵적으로 사용됨 return Vector2D(x + other.x, y + other.y); } friend std::ostream &operator<<(std::ostream &os, const Vector2D &v); }; //std::ostream을 reference로 받고 reference를 return하기 때문에 //복사된 객체가 아닌 인자로 받은 객체를 리턴한다. std::ostream &operator<<(std::ostream &os, const Vector2D &v) { os << v.x << ", " << v.y; return os; } int main() { Vector2D v1(10, 2), v2(20, 5); std::cout << v1 + v2 << std::endl; // 30, 7 출력 return 0; }
C++
복사
Vector2D 객체간의 +연산을 연산자 오버로딩을 통해 정의해준다. std::ostream은 전역공간에서 << 연산자를 오버로딩한 경우이다. std::ostream에서 Vector2D타입 객체를 받았을때의 연산을 x값과 y값을 출력하는 것으로 정의한다. operator<<는 friend함수로 Vector2D 객체의 private variable인 x,y에 접근할 수 있다.

static member

static member는 클래스의 모든 인스턴스가 공유하는 멤버로 특정 인스턴스에 종속되지 않고 인스턴스 개수와 상관없이 하나만 만들어진다. static멤버함수는 인스턴스와 관계되지 않은 멤버이기 때문에 내부에서 해당 객체의 멤버변수를 사용할 수 없고 static멤버변수만 사용할 수 있다. static멤버들은 객체를 만들지 않아도 클래스 이름을 통해서 접근할 수 있다.
class A { public: static int number; static void staticFunc(){} } int main(){ std::cout << A::number << std::endl; A::staticFunc() }
C++
복사

class vs struct

struct는 default로 멤버가 public이고 class는 private이다. 상속 또한 struct는 default로 public이고 class는 private이다. struct는 주로 데이터 구조를 정의하는데 사용하고 class는 객체지향 개념을 활용해 더 복잡한 기능을 포함.

Inheritance

ios_base 클래스가 base class이고 그 자식 클래스가 ios
ios의 자식 클래스는 istream, ostream
istream과 ostream을 둘 다 상속받은게 iostream
istream 클래스의 인스턴스가 cin
osteram클래스의 인스턴스가 cout
iostream을 상속받은것이 fstream
상속 관계에 있으면 동일한 타입이 아니라도 base class의 레퍼런스나 포인터로 인자를 전달할 수 있다.
base클래스로부터 상속받은 derived 클래스는 constructor destructor를 제외한 base클래스의 멤버를 모두 가지게 된다. base의 protected 멤버는 derived에서 접근할 수 있다(private은 안됨) 상속받는 class가 아닌 다른 클래스에서는 접근할 수 없다.

polymorphism

#include <iostream> class Base { public: // 가상 함수로 선언 virtual void f() { std::cout << "Base function called." << std::endl; } // 소멸자도 가상으로 선언 // 소멸자는 ~class이름, 파라미터 없음 virtual ~Base() {} }; class Derived : public Base { public: // 오버라이딩 void f() override { std::cout << "Derived function called." << std::endl; } }; int main() { Base b; // Base 클래스 객체 Derived d; // Derived 클래스 객체 // Derived& br = b; 는 불가능 Base& br = b; // Base 타입의 참조 Base& dr = d; // Derived 타입의 참조 // 실행 시간에 결정됨 br.f(); // Base의 f() 호출 dr.f(); // Derived의 f() 호출 return 0; }
C++
복사
Derived 클래스는 Base클래스를 상속받고 f()함수를 오버라이딩해서 다른 기능을 구현한다. br은 Base타입 객체를 참조하고 dr은 derived타입 객체를 참조하지만 둘다 Base타입으로 호출되었기 때문에 br.f(), dr.f() 둘 다 Base타입의 함수를 호출한다. Compile time에 타입이 결정되었기 때문이다. 실행시간에 동적으로 결정되려면 다형성을 활용해야 한다.
다형성을 활용하기 위해서는 Base클래스의 f()함수를 virtual(가상함수)로 선언해야 한다. 이렇게 하면 f()함수의 호출이 실행시간에 결정되며 Derived클래스에서 오버라이딩한 함수가 호출된다. 더 이상 오버라이딩되지 않도록 하기 위해서는 final 키워드를 사용하면 된다.
클래스는 상속 관계가 있을 때 상위 클래스의 포인터가 하위 클래스의 객체를 가리킬 수 있다. 하위클래가 상위 클래스의 모든 메소드를 물려받기 때문이다. 따라서 상위 클래스 포인터를 통해 하위 클래스 객체를 다룰 때 포인터는 해당 객체의 상위 클래스 부분만을 본다.
b.f(), d.f()를 사용했다면 각각 base와 오버라이딩한 함수가 호출되지만 다형성을 사용해야하는 경우가 있다. 한마디로 공통된 기본 클래스를 통해 객체들을 다루는 방법이다.
int main() { std::vector<Base*> objects; // Base 포인터를 사용하는 벡터 objects.push_back(new Derived1()); objects.push_back(new Derived2()); // Base 포인터를 통해 각 객체의 f() 호출 for (Base* obj : objects) { obj->f(); // 각 객체의 오버라이딩된 f() 호출 } // 동적 할당한 메모리 해제 for (Base* obj : objects) { delete obj; } return 0; }
C++
복사
위와 같이 Base에서 파생된 여러 클래스들의 객체를 저장하는 vector에서 for문을 실행하여 각 객체의 오버라이딩 된 f()를 실행하는 경우 두 타입의 객체를 함께 저장하는 vector의 타입은 Base여야한다.
vtable
가상함수를 가진 클래스의 인스턴스는 vtable_ptr 포인터를 추가로 가지기 때문에 일반 클래스보다 8바이트 큰 메모리를 차지한다. vtable_ptr은 해당 객체의 클래스의 vtable을 가리킨다. vtable은 해당 클래스의 가상함수 포인터를 저장하는 배열이다. 가상함수를 가진 클래스의 각각의 파생 클래스도 고유한 vtable을 가지고 있다. 객체가 생성될 때 생성자는 해당클래스의 vtable을 가리키는 포인터를 초기화하고 가상함수를 호출할 때 vtable포인터를 통해 호출할 실제 함수의 주소를 찾아 실행한다. 이 과정은 실행시간에 결정된다.

abstract class

순수가상함수를 하나이상 포함하면 추상클래스이다. 추상클래스는 인스턴스를 직접 생성할 수 없고 다른 클래스를 위한 interface를 제공한다. 순수가상함수는 선언만 있고 정의가 없는 함수이다.
virtual void functionName() = 0; // 0은 이 함수가 순수가상함수임을 나타냄
C++
복사
순수가상함수는 파생클래스에서 반드시 오버라이딩해서 구현해야한다. 그렇지 않으면 해당 파생클래스도 추상 클래스가 된다. 추상 클래스는 비슷한 특성을 가진 여러 파생 클래스를 그룹화하고 각 파생 클래스가 오버라이딩으로 고유한 특성을 가질 수 있도록 한다.
#include <iostream> // 추상 클래스 class Shape { public: // 순수 가상 함수 virtual void draw() = 0; // =0으로 순수 가상 함수 정의 virtual ~Shape() {} // 가상 소멸자 }; class Circle : public Shape { public: void draw() override { // 순수 가상 함수 오버라이딩 std::cout << "Drawing Circle" << std::endl; } }; class Square : public Shape { double height; double width; public: //constructor를 통해서 멤버변수를 초기화한다 //initialization list라고 부른다. //constructor 함수 내부에서 초기화를 진행해도 된다. Square(double base, double height) : base(base), height(height) {} void draw() override { // 순수 가상 함수 오버라이딩 std::cout << "Drawing Square" << std::endl; } }; // 참조로 받았다면 shape.draw() void render(Shape* shape) { shape->draw(); // 다형성을 사용하여 적절한 draw() 호출 } int main() { Circle circle; Square square; render(&circle); // Drawing Circle render(&square); // Drawing Square return 0; }
C++
복사