728x90

1. 동적결합(Dynamic Binding)의 개념

프로그램이 실행 중일 , 어떤 함수나 메서드를 호출할 실제로 어떤 코드를 실행할지를 결정하는 것

- 즉, 함수 호출이 어떤 객체나 클래스의 실제 버전을 호출할지 런타임(runtime) 시점에 결정하는 

- 주로 객체 지향 프로그래밍에서 다형성을 구현하는 사용됨

 

1) '프로그램 실행 중'에 객체의 실제 타입을 고려

2) 해당 타입에 맞는 함수나 메서드를 호출

 

- 이를 통해 서로 다른 객체 동일한 함수 또는 메서드 호출하더라도, 객체의 고유한 동작 수행할 있음


2) c++에서 동적 결합을 구현하는 두 가지 주요 메커니즘

 

2-1. 가상함수 (virtual functions)

- 함수 앞에 virtual 키워드를 사용

- 가상 함수는 기본형 클래스에서 선언되고 파생 클래스(derived class)에서 재정의(override)할 수 있는 함수

- 가상 함수를 사용하면 런타임 시에 객체의 실제 타입에 따라 올바른 함수 버전이 호출됨

class Shape {
public:
    virtual void draw() {
        // 기본 구현 }
};

class Circle : public Shape {
public:
    void draw() override {}
};

2-2. 가상테이블 (virtual table)

- 가상 함수를 사용할 때, C++ 컴파일러는 객체에 대한 가상 함수 포인터 테이블을 생성함

- 테이블은 가상 함수의 주소를 저장하고, 실제 함수 호출 시에 테이블을 참조하여 올바른 함수를 호출함

 

void drawShape(Shape* shape) {
    shape->draw(); // 동적 결합
}

int main() {
    Circle circle;
    Square square;

    drawShape(&circle); // Circle 클래스의 draw()가 호출됨
    drawShape(&square); // Square 클래스의 draw()가 호출됨

    return 0;
}

구체적인 예제로 동적 결합 이해하기

 

기초형 클래스의 pointer으로 구성된 포인터 array를 선언

→ 해당 array에 기초형 클래스 Time뿐 아니라, 상속 클래스인 NewTime의 주소값도 저장됨

array의 각 원소에 대해 virtual 메서드인 show()를 실행시켜줌

(show 실행될 때마다, 어떤 class 타입인지 실시간으로 판단해스 해당 타입에 대응되는 virtual 메서드를 실행시킴)

 

time.h

#include<iostream>
using namespace std;

#ifndef FIRE_C_TIME_H
#define FIRE_C_TIME_H

class Time{
private:
    int hours;
    int mins;
public:
    Time();
    Time(int, int);

    /* virtual 통해 가상 메소드 선언 */
    virtual void show(); //가상메소드
    virtual ~Time(); //파괴자 역시 가상 메소드로 선언

    int getHour(){return hours;}
    int getMins(){return mins;}

    Time operator*(int n);
    friend Time operator*(int, Time&);
};

class NewTime : public Time {
private:
    int day;
public:
    NewTime(); //생성자
    NewTime(int, int, int); //생성
    void show();
};

#endif //FIRE_C_TIME_H

time_func.cpp

#include "time.h"

Time::Time(){
    hours = 0;
    mins = 0;}

Time::Time(int h, int m){
    hours = h;
    mins = m;}

void Time::show(){
    cout << hours << "시간 " << mins << "분" << endl;}

Time::~Time(){}

NewTime::NewTime() : Time(){
day = 0;
}

NewTime::NewTime(int h, int m, int d) : Time(h, m) {
day = d;
}

void NewTime::show(){
    cout << day << "일 " << getHour() << "시간 " << getMins() << "분" << endl;
}

main.cpp

#include "time.h"
#include "time_func.cpp"

const int MAX = 3;

int main(){
    Time* times[MAX]; //기초형 Time 클래스의 포인터들이 저장된 배열
    //이곳에 Time 기초형 클래스의 포인터도, NewTime 상속 클래스의 포인터도 담김
    int day;
    int hours;
    int mins;

    for (int i = 0; i < MAX; i++) {
        cout << i + 1 << "번째 원소를 정의합니다." << endl;
        cout << "시간을 입력하십시오." << endl;
        cin >> hours;
        cout << "분을 입력하십시오." << endl;
        cin >> mins;
        char check;
        cout << "일 정보가 있다면 1, 없다면 0을 입력하십시오." << endl;
        cin >> check;
        if (check == '0')
            times[i] = new Time(hours, mins);
        else {
            cout << "일을 입력하십시오." << endl;
            cin >> day;
            times[i] = new NewTime(hours, mins, day);
        }
    }

    for (int i = 0; i < MAX; i++){
        cout << i+1 << "번째 정보입니다." <<endl;
        times[i]->show();
        //show를 virtual로 선언했기 때문에,
        //times: time의 pointer 배열이지만
        //Times이면 Time의 show()가, NewTime이면 NewTime의 show()가 실행됨
    }

    for (int i = 0; i < MAX; i++){
        delete times[i]; //delete의 경우 time형에 대한 delete만 이루어짐
        //NewTime 객체의 경우 제대로 delete되지 않음
        //이를 위해 파괴자 역시 virtual로 선언해야 함

    }

    return 0;}
728x90
728x90

1. 기초 클래스에서 가상 메서드를 선언하면, 그 함수는 기초 클래스 및 파생되는 클래스에서 모두 가상이 됨

2. 객체에 대한 참조 or 포인터를 사용하여, 가상 메서드가 호출되면 → 참조나 포인터를 위해 정의된 메서드를 사용하지 않고 객체형을 위해 정의된 메서드를 사용한다. (=동적 결합)

3. 상속을 위해 기초 클래스로 사용할 클래스를 정의할 때, 파생 클래스에서 다시 정의해야 되는 클래스 메서드들은 가상 함수로 선언해야 한다.

 


기초 클래스의 데이터와 상속 클래스의 데이터를 출력하는 함수(메서드)를 만들고자 한다.

기존에는 각각에 서로 다른 메서드 show() / print() 를 정의했었다면,

이번에는 동일한 이름의 show() 메서드로 기초 클래스와 상속 클래스의 데이터를 출력해주고자 한다.

이를 위해서는 기초 클래스의 public 영역에서, virtual 키워드를 통해 show 메서드를 '가상 메서드'로 선언하여야 한다.

 

time.h

#include<iostream>
using namespace std;

#ifndef FIRE_C_TIME_H
#define FIRE_C_TIME_H

class Time{
private:
    int hours;
    int mins;
public:
    Time();
    Time(int, int);

    /* virtual 통해 가상 메소드 선언 */
    virtual void show();
    //가상 메소드: 프로그램에서 서로 독립된 메소드 정의가 있다는 것을 알려줌
    //show가 호출되었을 때, 호출한 객체를 따져서, 대응되는 함수를 선택함
    //현재 show가 가상 메소드로 정의된 상태

    int getHour(){return hours;}
    int getMins(){return mins;}
    ~Time();

    Time operator*(int n);
    friend Time operator*(int, Time&);
};

class NewTime : public Time {
private:
    int day;
public:
    NewTime(); //생성자
    NewTime(int, int, int); //생성
    void show();
};

#endif //FIRE_C_TIME_H

 

time_func.cpp

#include "time.h"

Time::Time(){
    hours = 0;
    mins = 0;}

Time::Time(int h, int m){
    hours = h;
    mins = m;}

// (1) 기초형 클래스에서 정의된 show() 메서드
void Time::show(){
    cout << hours << "시간 " << mins << "분" << endl;}

Time::~Time(){}

NewTime::NewTime() : Time(){
day = 0;
}

NewTime::NewTime(int h, int m, int d) : Time(h, m) {
day = d;
}

// (2) 상속 클래스에서 정의된 show() 메서드
/* show가 어디에서 호출되느냐에 따라 다른 기능 수행 - Time vs NewTime */

void NewTime::show(){
    //show(); 이름이 동일해서 재귀적으로 호출되는 문제 발생
    //기초형 클래스의 public 함수를 새로 생성 - private 멤버변수를 반환해주는.
    cout << day << "일 " << getHour() << "시간 " << getMins() << "분" << endl;
}

 

main.cpp

#include "time.h"
#include "time_func.cpp"

//public 함수를 다형으로 사용하기

int main(){
    Time temp1(1, 2);
    NewTime temp2(3, 30, 2);

    temp1.show();
    temp2.show();

    return 0;}

/*1시간 2분
2일 3시간 30분*/

 

728x90

'개발 > c++' 카테고리의 다른 글

[c++] 서로 다른 & (참조자/주소반환)  (0) 2023.09.24
[c++] 동적 결합  (0) 2023.09.11
[c++] 클래스 상속  (0) 2023.09.10
[c++] friend (feat. 연산자 오버로딩 응용)  (0) 2023.09.10
[c++] 연산자 오버로딩  (0) 2023.09.10
728x90

- 기초 클래스 (오리지널 원본)

 

- 파생 클래스

1. 파생 클래스형의 객체 안에는 기초 클래스형의 데이터 멤버들이 저장됨

2. 파생 클래스형의 객체는 기초 클래스형의 메서드를 사용할 수 있음

3. 단, 기초 클래스형의 private member에는 접근할 수 없음 → 멤버 초기자 리스트를 통해 접근할 수 있게 됨

4. 파생 클래스는 자기 자신의 생성자를 필요로함

5. 파생 클래스는 부가적인 데이터 멤버들과 멤버 함수를 임의로 추가할 수 있음

 

**아래 주석코드의 설명 잘 읽기,,

 

time.h

#include<iostream>
using namespace std;

#ifndef FIRE_C_TIME_H
#define FIRE_C_TIME_H

class Time{
private:
    int hours;
    int mins;
public:
    Time();
    Time(int, int);
    void show();
    ~Time();
};

/*클래스 상속*/
//파생 클래스: 오리지널 클래스인 Time을 상속받음
class NewTime : public Time {
private:
    int day;
public:
    NewTime(); //생성자
    NewTime(int, int, int); //생성자
    void print();

};

#endif //FIRE_C_TIME_H

time_func.cpp

#include "time.h"

Time::Time(){
    hours = 0;
    mins = 0;}

Time::Time(int h, int m){
    hours = h;
    mins = m;}

void Time::show(){
    cout << hours << "시간 " << mins << "분" << endl;}

Time::~Time(){}


// " : Time()" 부분은 '멤버 초기자 리스트'
// 기초형 클래스의 private member에 접근할 수 있게 됨
NewTime::NewTime() : Time(){
//Time()이 먼저 생성된 뒤 해당 내용이 NewTime에 전달되어, hours = mins = 0으로 초기화됨
//NewTime()에서는 day만 초기화해주면 됨
day = 0;
}

NewTime::NewTime(int h, int m, int d) : Time(h, m) {
//Time(h,m)이 생성된 뒤 NewTime의 h, m에 전달해줌
//d만 따로 정의해주면 됨
day = d;
}

void NewTime::print(){
    cout << day << "일 " << endl;
    show();
    // hours와 mins는 NewTime의 멤버변수가 아니므로 직접 접근하지는 못함
    // 따라서 기초형 클래스의 함수 통해 출력함
}

main.cpp

#include "time.h"
#include "time_func.cpp"

int main(){
    NewTime temp1();
    NewTime temp2(3, 30, 2);

    temp2.print();

    return 0;}

/*2일
3시간 30분*/
728x90
728x90

1. friend 개념

- 클래스의 멤버 함수는 아니지만, 해당 클래스의 private 멤버에 접근할 수 있도록 하는 도구

- 형태: friend 함수 / friend 클래스 / friend 연산자 오버로딩

** friend 함수는 멤버함수가 아니므로 .이나 -> 통해 호출 불가능

** 그러나 private 멤버변수에 대해, 멤버함수와 동등한 접근 권한을 가짐

 

* 주의

- friend는 비공개 멤버에 대한 접근을 허용하는 기능이므로 남용하지 않는 것이 안전

- 클래스 간의 관계를 명확하게 구성하고, 필요한 경우에만 friend 사용할

 

2. friend를 사용하는 주요 경우

2-1. friend 함수: '함수'를 클래스의 freind로 선언

해당 함수는 클래스의 멤버 함수가 아니지만 클래스의 private 멤버에 접근할 수 있게 됨

class MyClass {
private:
    int privateData;

public:
    MyClass(int data) : privateData(data) {}
    friend void friendFunction(MyClass obj); // 'friend 함수' 선언};

→ friendFunction 함수는 MyClass 클래스 '외부'에서 정의되어 있음

외부에서 정의된 함수가 MyClass의 멤버 변수에 접근하려면, MyClass 클래스 내부에 그것의 원형이 선언되어 있어야 함

→ 더 원하는 친구가 직접 발품 팔기.~! ㅎㅎ

 

 

2-2. friend 클래스: '클래스'를 다른 클래스의 friend로 선언

다른 클래스의 private 멤버에 접근할 수 있게 됨

class FriendClass {
private:
    int secretData;
public:
    FriendClass(int data) : secretData(data) {}};


class MyClass {
private:
    int privateData;
public:
    MyClass(int data) : privateData(data) {}
    friend class FriendClass; // FriendClass는 MyClass의 private member에 접근할 수 있음};

→ 마찬가지로, FriendClass 역시 MyClass '외부'에 정의되어 있음 

 

2-3. friend 함수와 friend 클래스를 모두 사용하는 경우

#include <iostream>

class FriendClass;

class MyClass {
private:
    int privateData;

public:
    MyClass(int data) : privateData(data) {}

    // FriendClass를 friend 클래스로 선언
    friend class FriendClass;

    void PrintPrivateData() {
        std::cout << "MyClass privateData: " << privateData << std::endl;
    }
};

class FriendClass {
private:
    int secretData;

public:
    FriendClass(int data) : secretData(data) {}

    void AccessMyClassPrivateData(MyClass& obj) {
        std::cout << "FriendClass accessing MyClass privateData: " << obj.privateData << std::endl;
    }
};

int main() {
    MyClass myObj(42);
    FriendClass friendObj(100);

    myObj.PrintPrivateData(); // MyClass의 멤버 함수를 통한 privateData 접근
    friendObj.AccessMyClassPrivateData(myObj); // FriendClass를 통한 privateData 접근

    return 0;
}

연산자 오버로딩에서 friend가 사용되는 사례

- 어떤 클래스에 이항 연산자를 오버로딩할 경우 friend 필요한 경우 발생함 (*이항 연산자 : 개의 피연산자를 요구하는 연산자)

 

(1) friend만을 이용해서 연산자 오버로딩 하는 경우

(단, 입력 매개변수가 'int * 클래스' 순서를 지켜야 함)

 

time.h

#include<iostream>
using namespace std;

#ifndef FIRE_C_TIME_H
#define FIRE_C_TIME_H

class Time{
private:
    int hours;
    int mins;
public:
    Time();
    Time(int, int);
    void show();
    ~Time();
    
    friend Time operator*(int n, Time& t); //friend를 활용한 연산자 오버로딩
};

#endif //FIRE_C_TIME_H

time_func.cpp

#include "time.h"

Time::Time(){
    hours = 0;
    mins = 0;}

Time::Time(int h, int m){
    hours = h;
    mins = m;}

void Time::show(){
    cout << hours << "시간 " << mins << "분" << endl;}

Time::~Time(){}

// friend 통해 선언한 함수를 정의할 때
// 그것은 멤버함수가 아니므로 클래스 귀속 :: 표시도 하지 않고, friend를 명시하지도 않음
// 매개변수 입력 순서 중요
Time operator*(int n, Time& t){ //n*t로 해석됨 
    Time result;
    long resultMin = t.hours * n * 60 + t.mins * n;
    result.hours = resultMin / 60;
    result.mins = resultMin % 60;
    return result;
}

main.cpp

#include "time.h"
#include "time_func.cpp"

//c++ friend: public 이외에도 private 멤버변수에 접근할 수 있는 또 다른 방법
int main(){

    Time t1(1, 20);
    Time t2;
    Time t3;

    t2 = 3 * t1; //순서 바뀌면 안 됨
    t2.show();
    
    return 0;
}

(2) 사실.. 굳이 friend 쓰지 않아도 구현됨 (단, 입력 매개변수가 '클래스 * int' 순서를 지켜야 함)

 

time.h

#include<iostream>
using namespace std;

#ifndef FIRE_C_TIME_H
#define FIRE_C_TIME_H

class Time{
private:
    int hours;
    int mins;
public:
    Time();
    Time(int, int);
    void show();
    ~Time();

    Time operator*(int n);
};

#endif //FIRE_C_TIME_H

time_func.cpp

#include "time.h"

Time::Time(){
    hours = 0;
    mins = 0;}

Time::Time(int h, int m){
    hours = h;
    mins = m;}

void Time::show(){
    cout << hours << "시간 " << mins << "분" << endl;}

Time::~Time(){}

// friend를 사용하지 않고 클래스의 멤버함수로 정의할 때
Time Time::operator*(int n){
    Time result;
    long resultMin = hours * n * 60 + mins * n;
    result.hours = resultMin / 60;
    result.mins = resultMin % 60;
    return result;
}

main.cpp

#include "time.h"
#include "time_func.cpp"

int main(){

    Time t1(1, 20);
    Time t2;

    t2 = t1 * 4;
    t2.show();

    return 0;
}

//5시간 20분

(3) 두 개의 입력 매개변수 순서를 자유롭게 하고 싶을 때 ~!

→ 그러면 friend 이용하는 경우와 이용하지 않는 경우 모두 활용하면 됨 (단순..)

 

time.h

#include<iostream>
using namespace std;

#ifndef FIRE_C_TIME_H
#define FIRE_C_TIME_H

class Time{
private:
    int hours;
    int mins;
public:
    Time();
    Time(int, int);
    void show();
    ~Time();

    Time operator*(int n); //멤버함수
    friend Time operator*(int, Time&); //friend 함수
};

#endif //FIRE_C_TIME_H

time_func.cpp

#include "time.h"

Time::Time(){
    hours = 0;
    mins = 0;}

Time::Time(int h, int m){
    hours = h;
    mins = m;}

void Time::show(){
    cout << hours << "시간 " << mins << "분" << endl;}

Time::~Time(){}


// friend를 사용하지 않고 클래스의 멤버함수로 정의할 때
Time Time::operator*(int n){
    Time result;
    long resultMin = hours * n * 60 + mins * n;
    result.hours = resultMin / 60;
    result.mins = resultMin % 60;
    return result;
}

// friend 통해 선언한 함수를 정의할 때
// 그것은 멤버함수가 아니므로 클래스 귀속 :: 표시도 하지 않고, friend를 명시하지도 않음
Time operator*(int n, Time& t){
    return t*n;
}

main.cpp

#include "time.h"
#include "time_func.cpp"

int main(){

    Time t1(1, 20);
    Time t2;

    t2 = t1 * 4;
    t2.show();

    t2 = 4 * t1;
    t2.show();

    return 0;
}

/*5시간 20분
5시간 20분*/

 

728x90
728x90

1. 핵심 요약

1-1. 정의

연산자 오버로딩이란?

사용자 정의 데이터 타입(클래스)에 대한 연산자 동작을 redefine & expand

 C++ 객체 지향 프로그래밍의 핵심 원칙 하나인 다형성(polymorphism) 구현

 

- 다양한 연산자(예: +, -, *, /, ==, != 등)에 대해 사용됨

- 기존 연산자는 주로 특정 데이터타입에 국한되어 사용됨. 그러나 연산자 오버로딩을 통해, 사용자 정의 클래스의 객체를 일반적인 데이터 타입처럼 사용할 수 있음

 

*오류 방지하려면, 연산자의 의미와 클래스에 대한 연산 규칙을 명확히 이해해야

 

1-2. 연산자 오버로딩의 형식

- 연산자 오버로딩은 연산자(operator)를 정의한 클래스 내에서 진행됨

 

반환_타입 operator 연산자_이름(매개변수_목록) {

    // 연산을 정의한 코드

}

 

1-3. 연산자 오버로딩의 (일반화된?) 예시

- 클래스 내부에 연산자 오버로딩 정의

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r, double i) : real(r), imag(i) {}

    Complex operator+(const Complex& other) {
        return Complex(real + other.real, imag + other.imag);
    }
};

// Complex 클래스는 + 연산자를 오버로딩하여 두 개의 복소수를 더할 수 있도록 함.

- 연산자 오버로딩의 사용

Complex a(2.0, 3.0);
Complex b(1.0, 2.0);
Complex c = a + b; // + 연산자가 오버로딩되어 두 복소수를 더함

// a + b는 연산자 오버로딩된 + 연산자에 의해 두 Complex 객체가 더해짐

2. 구체적인 예시

2-1. 예시 요약

헤더파일에서

Time operator+(Time&);

함수 정의 파일에서

Time Time::operator+(Time& time){
    Time temp;
    temp.mins = mins + time.mins;
    temp.hours += temp.mins / 60;
    temp.mins %= 60;

    return temp;
}

main 파일에서

    Time total;
    total = day1.operator+(day2);

2-2. 예시 전문

 

1. time.h

#include<iostream>

#ifndef FIRE_C_TIME_H
#define FIRE_C_TIME_H

using namespace std;

class Time{
private:
    int hours;
    int mins;
public:
    Time(); //클래스 생성자 //함수 오버로딩
    Time(int, int); //함수 오버로딩
    void addHours(int);
    void addMins(int);
//    Time sum(Time&);
    Time operator+(Time&);
    void show();
    ~Time(); //클래스 파괴자
};

#endif //FIRE_C_TIME_H

2. time_func.cpp

#include "time.h"

Time::Time(){
    hours = 0;
    mins = 0;
}

Time::Time(int h, int m) {
    hours = h;
    mins = m;
}

void Time::addHours(int h) {
    hours += h;
}

void Time::addMins(int m) {
    mins += m;
    hours += mins / 60;
    mins = mins % 60;
}

// Time Time::sum(Time& time){
//    Time temp;
//    temp.mins = mins + time.mins;
//    temp.hours += temp.mins / 60;
//    temp.mins %= 60;
//
//    return temp;
//}

Time Time::operator+(Time& time){
    Time temp;
    temp.mins = mins + time.mins;
    temp.hours += temp.mins / 60;
    temp.mins %= 60;

    return temp;
}

void Time::show(){
    cout << hours << "시간 " << mins << "분" << endl;
}

Time::~Time(){

}

3. main.cpp

#include "time.h"
#include "time_func.cpp"

int main() {
    Time day1(1, 40);
    Time day2(2, 30);

    day1.show();
    day2.show();

    //오버로딩한 연산자 사용 방법 (1)
    Time total;
    total = day1.operator+(day2);
    //기존에 만들었던 sum 함수 역할을 수행하는 연산자 오버로딩 하려면
    //함수 있던 자리에 'operator' , '오버로딩 할 연산자'의 연속으로 표현하면 됨

    //오버로딩한 연산자 사용 방법 (2)
    Time total2;
    total2 = day1 + day2; //혹은 아예 연산자만 사용해도 됨

    total.show();
    total2.show();

    return 0;
}

/*1시간 40분
2시간 30분
1시간 10분
1시간 10분*/

 

728x90
728x90

1) this 포인터

: 멤버함수를 호출하는 데 사용된 객체를 지시 (본질은 포인터 ; 주소값을 리턴)

 

#ifndef FIRE_C_STOCK_STRUCTURE_H
#define FIRE_C_STOCK_STRUCTURE_H
#include <iostream>
using namespace std;

class Stock {
private:
    string name;
    int shares;
    float share_val;
    double total_val; 
    void set_total() {total_val = shares * share_val;}

public:
    void show();
    Stock compare(Stock& stock);

    Stock(string, int, float); 
    Stock(); 
};

void Stock::show() {
    cout << "회사 명 : " << name << endl;
    cout << "주식 수 : " << shares << endl;
    cout << "주가 : " << share_val << endl;
    cout << "주식 총 가치 : " << total_val << endl << endl;
}

Stock Stock::compare(Stock& stock){
    if(stock.total_val >= total_val)
        return stock;
    else return *this; //this pointer //호출한 객체 자기 자신을 가리킴
}

Stock::Stock(string co, int n, float pr){
    name = co;
    shares = n;
    share_val = pr;
    set_total();
}

Stock::Stock(){
    name = "None";
    shares = 0;
    share_val = 0;
    set_total();
}
#endif //FIRE_C_STOCK_STRUCTURE_H
#include "stock_structure.h"

int main() {

    Stock temp = Stock("Panda", 100, 1000);
    Stock temp2("another", 200, 2000);
    temp.compare(temp2).show();
    return 0;}

/* 주식 총 가지가 더 높은 stock 클래스 객체가 출력됨
 * 
회사 명 : another
주식 수 : 200
주가 : 2000
주식 총 가치 : 400000*/

 

2) 클래스로 이루어진 배열

#include "stock_structure.h"

int main() {

    Stock s[4] = { //배열의 각 원소는 ;가 아닌 , 로 구분
            Stock("A", 10, 1000),
            Stock("B", 20, 2000),
            Stock("C", 30, 3000),
            Stock("D", 40, 4000)
    };

    Stock first = s[0]; //first: stock 배열에서 total이 가장 높은 것을 저장
    for(int i=1; i<4; i++)
        first = first.compare(s[i]);

    first.show();
    return 0;
}

→ 기존에 배열 만들고 활용하는 방식과 완전 동일함

 

**참조자 사용

#include "stock_structure.h"

int main() {

    Stock s[4] = { //배열의 각 원소는 ;가 아닌 , 로 구분
            Stock("A", 10, 1000),
            Stock("B", 20, 2000),
            Stock("C", 30, 3000),
            Stock("D", 40, 4000)
    };

    Stock* first = &s[0]; //값을 직접 대입하는 것이 아니라 주소로 참조하도록 함
    for(int i=1; i<4; i++)
        first = &first->compare(s[i]); //왜지왜지왜지왜지

    first->show();
    return 0;
}
#ifndef FIRE_C_STOCK_STRUCTURE_H
#define FIRE_C_STOCK_STRUCTURE_H
#include <iostream>
using namespace std;

class Stock {
private:
    //private member의 값을 변경하려면 public에 선언된 함수를 통해서만이 가능
    //private member에는 '직접' 접근할 수 없음 : 데이터 은닉
    string name;
    int shares;
    float share_val;
    double total_val; //double: float과 같이 실수를 표현하되 더 넓은 범위를 표현함
    void set_total() {total_val = shares * share_val;}
    //private에서 함수 포함 가능

public: //public member 함수: private member의 값을 변경할 수 있는 단위
    void show();
    Stock& compare(Stock&);

    Stock(string, int, float); //입력이 있는 생성자
    // 기존의 acquire 함수 대체 //클래스 생성할 때 init하는 것들
    Stock(); //Default 생성자
};

void Stock::show() {
    cout << "회사 명 : " << name << endl;
    cout << "주식 수 : " << shares << endl;
    cout << "주가 : " << share_val << endl;
    cout << "주식 총 가치 : " << total_val << endl << endl;
}

Stock& Stock::compare(Stock &stock){
    if(stock.total_val >= total_val)
        return stock;
    else return *this;
}

//Stock 클래스 내부에서 귀속되어 있음
/* 생성자 */
Stock::Stock(string co, int n, float pr){
    name = co;
    shares = n;
    share_val = pr;
    set_total();
}
/* Default 생성자 */
Stock::Stock(){
    name = "None";
    shares = 0;
    share_val = 0;
    set_total();
}
#endif //FIRE_C_STOCK_STRUCTURE_H

 

728x90
728x90

목차

0. 추상화란?

1. 클래스란?

2. 클래스 생성자

3. 클래스 파괴자


0. 추상화란?

- 어떠한 객체를 사실적으로 표현하는 것이 아니라, 공통된 특징을 간결한 방식으로, 이해하기 쉽게 표현하는 것

- c++에서 추상화 중요하다!

1. 클래스란?

- 추상화를 사용자 정의 데이터형으로 변환해주는 수단

- 같은 타입이면 서로 대입 가능 (마치 string)

 

1-1) stock_structure.h (헤더파일)

//클래스 선언 & 클래스 메서드 정의

#ifndef FIRE_C_STOCK_STRUCTURE_H
#define FIRE_C_STOCK_STRUCTURE_H
#include <iostream>
using namespace std;

class Stock {
private:
    //private member의 값을 변경하려면 public에 선언된 함수를 통해서만이 가능
    //private member에는 '직접' 접근할 수 없음 : 데이터 은닉
    string name;
    int shares;
    float share_val;
    double total_val; //double: float과 같이 실수를 표현하되 더 넓은 범위를 표현함
    void set_total() {total_val = shares * share_val;}
    //private에서 함수 포함 가능

public: //public member 함수: private member의 값을 변경할 수 있는 단위
    void acquire(string, int, float);
    void buy(int, float);
    void sell(int, float);
    void update(float);
    void show();
};

//사용 범위 결정 연산자 ::
void Stock::acquire(string co, int n, float pr) {
    name = co;
    shares = n;
    share_val = pr;
    set_total();
}

void Stock::buy(int n, float pr) {
    shares += n;
    share_val += pr;
    set_total();
}

void Stock::sell(int n, float pr) {
    shares -= n;
    share_val -= pr;
    set_total();
}

void Stock::update(float pr) {
    share_val = pr;
    set_total();
}

void Stock::show() {
    cout << "회사 명 : " << name << endl;
    cout << "주식 수 : " << shares << endl;
    cout << "주가 : " << share_val << endl;
    cout << "주식 총 가치 : " << total_val << endl;
}

#endif //FIRE_C_STOCK_STRUCTURE_H

1-2) main.cpp 소스파일

#include "stock_structure.h"

int main() {

    Stock temp;
    temp.acquire("Panda", 100, 1000);
    temp.show();
    temp.buy(10, 1200);
    temp.show();
    temp.sell(5, 800);
    temp.show();

    return 0;}

/*회사 명 : Panda
주식 수 : 100
주가 : 1000
주식 총 가치 : 100000
회사 명 : Panda
주식 수 : 110
주가 : 2200
주식 총 가치 : 242000
회사 명 : Panda
주식 수 : 105
주가 : 1400
주식 총 가치 : 147000*/

2. 클래스 생성자

2-1) 클래스 생성자 & default 클래스 생성자가 추가된 stock_structure.h

- 기존의 acquire함수 코드를 생성자 코드에 반영

- 생성자는 클래스 내 원형 선언 시 자료형을 쓰지 않아도 됨. 클래스 이름 그대로 사용.

#ifndef FIRE_C_STOCK_STRUCTURE_H
#define FIRE_C_STOCK_STRUCTURE_H
#include <iostream>
using namespace std;

class Stock {
private:
    //private member의 값을 변경하려면 public에 선언된 함수를 통해서만이 가능
    //private member에는 '직접' 접근할 수 없음 : 데이터 은닉
    string name;
    int shares;
    float share_val;
    double total_val; //double: float과 같이 실수를 표현하되 더 넓은 범위를 표현함
    void set_total() {total_val = shares * share_val;}
    //private에서 함수 포함 가능

public: //public member 함수: private member의 값을 변경할 수 있는 단위
    void show();
    Stock(string, int, float); //입력이 있는 생성자
    // 기존의 acquire 함수 대체 //클래스 생성할 때 init하는 것들
    Stock(); //Default 생성자
    ~Stock(); //파괴자
};

void Stock::show() {
    cout << "회사 명 : " << name << endl;
    cout << "주식 수 : " << shares << endl;
    cout << "주가 : " << share_val << endl;
    cout << "주식 총 가치 : " << total_val << endl << endl;
}

//Stock 클래스 내부에서 귀속되어 있음
/* 생성자 */
Stock::Stock(string co, int n, float pr){
    name = co;
    shares = n;
    share_val = pr;
    set_total();
}
/* Default 생성자 */
Stock::Stock(){
    name = "None";
    shares = 0;
    share_val = 0;
    set_total();
}

/* 파괴자 */
Stock::~Stock(){

}
#endif //FIRE_C_STOCK_STRUCTURE_H

 

2-2) 클래스 생성자 & default 클래스 생성자가 호출된 main.cpp 소스파일

#include "stock_structure.h"

int main() {

    Stock temp = Stock("Panda", 100, 1000);
    Stock temp2("another", 200, 2000);
    Stock temp3; //default 생성자 //함수 오버로딩 사용
    temp.show();
    temp2.show();
    temp3.show();
    return 0;}

/*회사 명 : Panda
주식 수 : 100
주가 : 1000
주식 총 가치 : 100000

회사 명 : another
주식 수 : 200
주가 : 2000
주식 총 가치 : 400000

회사 명 : None
주식 수 : 0
주가 : 0
주식 총 가치 : 0*/

2-2-0) 클래스 생성자의 특징

리턴값을 가지지 않고 & 자료형이 별도로 없음

 

2-2-1) 클래스 생성자로 생성하는 방법에는 두 가지가 있음

(1) 클래스타입 클래스명 = 클래스타입(입력)

(2) 클래스타입 클래스명(입력)

 

2-2-2) Default 클래스 생성자는 '함수 오버로딩' 컨셉을 사용하는 것

** 생성자

Stock::Stock(string co, int n, float pr){

    name = co;

    shares = n;

    share_val = pr;

    set_total(); }

 

** Default 생성자

Stock::Stock(){

    name = "None";

    shares = 0;

    share_val = 0;

    set_total(); }


3. 클래스 파괴자

3-1) 클래스 파괴자의 특징

생성자의 반대. 객체의 수명이 끝나는 시점에서 파괴자가 자동으로 호출됨. (사용자가 직접 할 수 없음)

- 클래스 생성자와 마찬가지로 리턴값을 가지지 않고 & 자료형이 따로 없으며

- 추가로, 매개변수 입력을 가지지 않음

- 파괴하는 것 이외 아무것도 하는 일이 없으므로, 내부 코드가 필요 없음

 

3-2) 파괴자가 자동으로 호출되는 세 가지 경우

1) 스코프를 벗어났을때

2) new를 사용하여 객체를 생성하고 delete 하였을때

3) 임시 객체를 생성했을 경우에 프로그램은 객체의 사용을 마쳤을때

 

3-3) 파괴자가 필요한 이유

만약 클래스 내부에서 동적할당이 일어난다면 메모리 해제도 필요함. C++에서 메모리 관리는 직접 해야하기 때문임.
파괴자가 호출됐다는 건, 더이상 그 객체가 쓰이지 않는다는것. 따라서 메모리 해제를 해야 메모리 누수(memory leak)이 발생하지 않음.
//헤더파일에서

public: //public member 함수: private member의 값을 변경할 수 있는 단위
    void show();
    Stock(string, int, float); 
    Stock(); 
    ~Stock(); //파괴자
};
// ... 헤더파일에서 

///* 파괴자 */
Stock::~Stock(){
cout << name << " 클래스가 소멸되었습니다.\n" << endl;
}

main.cpp 실행 결과

>>>

회사 명 : Panda
주식 수 : 100
주가 : 1000
주식 총 가치 : 100000

회사 명 : another
주식 수 : 200
주가 : 2000
주식 총 가치 : 400000

회사 명 : None
주식 수 : 0
주가 : 0
주식 총 가치 : 0

None 클래스가 소멸되었습니다.

another 클래스가 소멸되었습니다.

Panda 클래스가 소멸되었습니다.

 

 

Refer to

https://inf.run/31sv

https://blog.plorence.dev/502

728x90
728x90

0. 분할되지 않은 기존 코드

→ 세 영역으로 나눌 수 있음

(1) main.cpp 소스파일 (2) func.cpp 파일 (3) struct.h 헤더파일

#include <iostream>
using namespace std;

/* 헤더 파일 */
/*구조체&함수 원형 선언*/
struct MyStruct {
    string name;
    int age;
};

void display(MyStruct&);

/* 소스 파일 */
/*main 함수*/
int main(){
    MyStruct sleepy = {"somuch", 23};
    display(sleepy);
    return 0;
}

/*함수의 body를 정의*/
void display(MyStruct &refer){
    cout << "이름: " << refer.name << endl;
    cout << "나이: " << refer.age << endl;
}

 


1. 헤더 파일 (struct.h)

- #define이나 const를 사용하는 기호 상수

- 구조체 선언

- 클래스 선언

- 템플릿 선언

 

- 함수 원형 (ex. void display(MyStruct&); 와 같이 원형을 선언해두는 곳이기도 함)

- 인라인 함수

 

→ reuse하고자 하는 덩어리들의 blueprint를 모아둔 곳

https://cognota.com/blog/the-importance-of-a-learning-blueprint-design/

//struct.h

/*ifndef~endif 기호상수화 */

#ifndef STRUCT
#include <iostream>
using namespace std;

/* 헤더 파일 */
/*구조체&함수 원형 선언*/
struct MyStruct {
    string name;
    int age;
};

void display(MyStruct&);
#endif

ifndef~endif 기호상수화

- STRUCT 덩어리가 이미 include되면, 그다음 같은 것이 호출되는 것을 무시할 수 있음
- 소스파일에서 특정 헤더 파일을 여러 번 호출하는 것이 원래 안 되는데
- 이로 인한 에러가 발생하는 것을 막기 위해, 헤더 파일에 ifndef~endif 구문을 사용함

 

 

2. 소스 파일 (main.cpp)

#include "struct.h"
#include "func.cpp"

/* 소스 파일 */
/*main 함수*/
int main(){
    MyStruct sleepy = {"somuch", 23};
    display(sleepy);
    return 0;
}

- #include "struct.h" 헤더를 반드시 불러와야 함 (전처리기와 관련된 것)

- Any command starting with a # in the first column is not a C/C++ statement, but rather a preprocessor

- Ther preprocessor performs very basic text-based substitutions (간단한 텍스트 대체 기능 수행)

- preprocessor가 처리한 결과물은 compiler로 보내짐

 

- 헤더파일에서 선언한 함수 / 기호 상수 / 구조체 / 클래스 / 템플릿 / 인라인 함수만이 → main에서 실행될 수 있음

- 실제로 '실행' 대상이 되는 함수

 

3. 함수 파일 (func.cpp)

//func.cpp
#include "struct.h"

/*함수의 body를 정의*/
void display(MyStruct &refer){
    cout << "이름: " << refer.name << endl;
    cout << "나이: " << refer.age << endl;
}

- 함수가 정의되어 있는 func.cpp는 마찬가지로, struct.h를 include 해와야 함

- func.cpp와 main.cpp를 직접 연결시키는 코드는 없음. 그러나 문제 없이 작동함.

**업데이트

- 만약 함수 선언을 위한 헤더파일과 & 함수 정의하는 func.cpp를 따로 분리시켰다면, main() 파일에서 func.cpp도 함께 include해야 오류를 방지할 수 있음. 그 이유는, Linker가 func.cpp에서 정의된 함수를 어셈블리 파일에 적절한 위치에 놓아야 하는데, main 파일에 "func.cpp"를 인클루드 한다는 코드가 없다면, 헤더에서 선언한 함수 정의를 찾는 데 문제가 생길 수 있음. (이하와 같은 에러)

 

ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

 

포함시키지 않아도 작동하는 경우가 있지만.. 오류에 대비하는 게 더 좋으니.


#상황

나(programmer): c++로 헤더파일(라이브러리)도 직접 만들었고~ main 소스파일에 불러왔고~ 이제 실행시켜야지~ !

 

#절차

Programmer: source code <* .cpp>를 작성함

Preprocessor(전처리기): 프로그래머가 작성한 소스코드를 컴파일러에게 전달하기 전, 컴파일 될 수 있는 상태로 준비(전처리)시킴

- 코드를 정리하고 필요한 부분을 추가하거나 변경

- 현재 소스 파일에서 include시킨 헤더 파일을 현재 코드에 포함시킴

- 매크로를 사용하여 특정 코드를 자동으로 대체함 (ex. #define 지시어를 사용하여 상수나 함수 호출을 자동으로 대체)

 

Compiler: 전처리된 코드를 받아서 실제 기계어로 번역하고 오류가 없는지 확인함.

 

Linker: 여러 개의 컴파일된 코드파일들을 하나로 합침.

컴파일된 파일과 라이브러리 파일들 간의 상호 의존성을 해결하고, 실행하는 파일의 메모리 주소를 조정함

 

Linker를 거친 후, 프로그래머가 작성한 코드 파일은 완전히 실행 가능한 상태가 됨~~~~~!~!

 

728x90
728x90

0. 배열 & 문자열 위주로 정리

0-1. 배열(array) : 같은 데이터형의 집합

1) typeName arrayName[arraySize]

2) 배열을 다른 배열에 통째로 대입할 수 없음

short month[5] = { ... };
short year[12] = { ... };
year = month; 불가능함


3) 초기화 값의 개수를 배열 원소의 개수보다 모자라게 제공할 수 있음
- 배열을 부분적으로 초기화하면 나머지 원소는 모두 0으로 설정
- 명시적으로 첫 번째 원소만 0으로 초기화하면, 나머지 원소들은 모두 0으로 초기화됨
4) 배열 사이즈를 명시하지 않으면 초기화 시의 원소 개수로 배정됨

5) c++에서, 배열 이름을 그 배열의 첫번째 원소의 주소로 인식
arr == &arr[0]

6) 배열에 인덱스를 사용하면, 배열의 주소가 아닌, 인덱스에 해당하는 각 원소 값에 직접 접근할 수 있음

#include <iostream>
using namespace std;

const int a = 5;
int solution[a];

int main(){
    solution[0] = 1;

    for(int i=1; i<a; i++)
        solution[i] = 2*solution[i-1]+1;

    cout << solution << endl; //0x102d68000, 첫 번째 원소의 주소
    
    for(int idx=0; idx<a; idx++) {
        cout << solution[idx];
        if(idx!=a-1)
            cout <<", ";}} //1, 3, 7, 15, 31

 

0-2.  문자열을 변수에 입력하는 방법 (1) char[  ]   (2) string

1) char [] : 얘는 말 그대로 문자들이 모인 array

→ c++에서 array의 속성을 따르면 됨

char char1[20]; //선언만
char char2[20] = "jauar"; //선언과 초기화를 동시에

char1 = char2; //error, 값이 들어가지 않음

2) string

- 배열 표기를 사용하여 string객체에 저장된 개별 문자에 접근 가능
char[ ] 문자열과 속성이 비슷한데,
 string을 통해서는 배열을 다른 배열에 통째로 대입할 수 있음 
 배열 크기를 미리 지정하지 않아도 됨

string str1;
string str2 = "panda";

str1 = str2; //제대로 대입 됨

 

728x90
728x90

함수 템플릿

: 구체적인 데이터형을 포괄할 수 있는 일반형으로 함수를 정의 (일반화 프로그래밍)

- 특정한 데이터형에 귀속되지 않음

- template <class ___>이라는 키워드로 생성 (template <typenName ___>)

#include <iostream>
using namespace std;

//class 혹은 typeName
template <class Any> //Any: 사용자가 사용할 데이터형이 정해지지 않은 상태
Any sum(Any, Any);

int main() {
    int a = 3;
    int b = 4;
    cout << sum(a,b) << endl; //Any가 int로 매칭

    float c = 3.14;
    float d = 1.592;
    cout << sum(c, d) << endl; //Any가 float로 매칭

//    cout << sum(a, c) << endl; //int와 float이 모두 사용됨
// 처음 함수 원형 선언할 때 Any sum(int, Any)로 바꾸면 주석처리한 코드가 동작함
// 그러나 '일반화' 취지를 더 살리려면, 템플릿에도 오버로딩을 적용함

    return 0;
}

template <class Any>
Any sum(Any a, Any b) {
    return a+b;

템플릿 오버로딩

#include <iostream>
using namespace std;

/*함수 원형 선언*/
template <class Any> //Any: 사용자가 사용할 데이터형이 정해지지 않은 상태
Any sum(Any, Any);
template <class Any>
Any sum(int, Any);

/*main 함수*/
int main() {
    int a = 3;
    int b = 4;
//    cout << sum(a,b) << endl;
    //a, b가 모두 int인 경우는 두 가지 sum에 모두 매칭됨 -> 사용불가

    float c = 3.14;
    float d = 1.592;
    cout << sum(c, d) << endl; //4.732

    cout << sum(a, c) << endl; //6.14

    return 0;
}

/*함수 정의*/
template <class Any>
Any sum(Any a, Any b) {
    return a+b;
}

template <class Any>
Any sum(int a, Any b) {
    return a+b;
}
728x90

+ Recent posts