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
728x90

1. 함수 오버로딩 개념

함수의 기능은 동일한데 매개변수만 다른 경우! (리턴형의 차이는 해당되지 않음)

 

*함수 오버로딩이 제대로 작동하지 않고 에러 나는 경우

1) 함수의 리턴형만 다른 경우

2) 이상의 함수에 대응되는 경우

 

2. 코드

#include <iostream>
using namespace std;

int sum(int, int);
float sum(float, float);

int main() {
    //함수의 오버로딩 = 함수의 다형(다양한 형태를 지닌)
    //"여러 개의 함수를 같은 이름으로 연결한다"
    //구분 기준: 입력 매개변수의 형태
    const int a = 1;
    const int b = 2;
    const float c = 1.5;
    const float d = 2.3;
    cout <<sum(a,b)<<endl;
    cout <<sum(c,d)<<endl;

    return 0;
}

int sum(int a, int b) {
    return a+b;
}

float sum(float c, float d) {
    return c+d;
}

3. 에러 슈팅

처음에는 main 함수를 이하와 같이 작성하였더니 에러가 발생하였다.

int main() {
    cout <<sum(1,2)<<endl; //ok
    cout <<sum(1.5, 2.3)<<endl; //에러 발생
    return 0;
}

위 코드를 이하와 같이 고쳤더니 해결되었다.

const int a = 1;
const int b = 2;
const float c = 1.5;
const float d = 2.3;
cout <<sum(a,b)<<endl;
cout <<sum(c,d)<<endl;

결국 함수 오버로딩의 핵심은 매개변수의 형태가 다른 것이다.

 

바뀐 코드에서는 const int와 const float 형태의 변수 a, b, c, d를 사용하여 함수 sum을 호출하고 있다.

이러한 형태로 함수 호출을 하면 컴파일러가 적절한 sum 함수를 선택할 수 있게 된다.

 

따라서 함수 오버로딩을 실행하기 전, 매개변수의 자료형을 미리 선언(int float 변수를 사용)해야 한다.

즉, 미리 변수에 값을 입력해두고, 해당 변수를 함수의 입력으로 사용하는 것이다.

 

원래 발생했던 에러의 이유는 매개변수의 형태가 충돌하고 있었기 때문이다.

728x90
728x90

 

#include <iostream>
#include <cstring>
using namespace std;

class Printer {
private:
    char inputs[30];
public:
    void SetString(char* sentence);
    void ShowString();
};

inline void Printer::SetString(char* sentence){
    strcpy(inputs, sentence);
}

inline void Printer::ShowString() {
    cout << inputs << endl;
}

int main(){
    Printer pnt;
    pnt.SetString("Hello world!");
    pnt.ShowString();

    pnt.SetString("I love C++");
    pnt.ShowString();
    return 0;
}

 

질문

디테일이긴 하지만, strcpy에서 char* sentence -> sentence를 입력하는 이유는 무엇인가?


1. strcpy 함수는 source에서 destination으로 문자열을 복사하고, 복사 작업은 source에서 NULL 문자('\0')를 만날 때까지 계속됨

복사가 완료된 후 destination을 반환함.

destination은 충분한 크기로 선언되어야 하며, 복사할 문자열의 길이에 맞게 할당되어야 함.

 

2. strcpy 함수에는 두 개의 매개변수가 있음

  • destination (대상 문자열):  문자열을 복사한  결과가 저장될 대상 문자열을 가리키는 포인터

- destination char 배열 또는 char 포인터 선언

- 복사된 문자열은 destination 저장됨

- destination 충분한 크기로 선언되어야 하며, 복사될 문자열의 길이에 맞게 할당되어야

  • source (원본 문자열):

- 이 매개변수는 복사하려는 원본 문자열을 가리키는 포인터

- source const char 배열 또는 const char 포인터 선언

- source 가리키는 문자열이 destination 복사됨

 

3. 이 코드에서 char* sentence 대신 char sentence로 매개변수를 변경하면 컴파일 오류가 발생

char* sentence문자열을 가리키는 포인터를 받는 것이며, char sentence는 문자 하나를 저장하는 데 사용되는 단일 문자 변수임

 SetString 함수가 입력으로 받는 것은 문자열임. 문자열은 여러 문자로 구성되어 있으므로 문자열을 다루기 위해서는 문자열을 가리키는 포인터(char*)를 사용해야 함.

(char 변수 하나로는 문자열을 저장하고 복사하는 데 충분하지 않음)

 

 

 

728x90
728x90

1) c++ 클래스 디자인하기

#include <iostream>
using namespace std;

class Calculator {
private: //멤버변수
//    double a;
//    double b;
// 얘네들은 함수의 입력으로 바로 정의되는 거지, 멤버함수에서 생성하거나 입력하는 것이 아님

    int add; //멤버변수는: Class의 함수를 통해 직접 생성하거나, 값을 입력하거나, 조작할 대상
    int min;
    int mul;
    int div;

public: //멤버함수
    void Init();
    double Add(double a, double b);
    double Min(double a, double b);
    double Mul(double a, double b);
    double Div(double a, double b);
    void ShowOpCount();
};

inline void Calculator::Init(){
    add = 0;
    min = 0;
    mul = 0;
    div = 0;
}

inline double Calculator::Add(double a, double b){
    add ++;
    return a + b;}

inline double Calculator::Min(double a, double b){
    min ++;
    return a - b;}

inline double Calculator::Mul(double a, double b){
    mul ++;
    return a*b;}

inline double Calculator::Div(double a, double b){
    div ++;
    return a / b;}

inline void Calculator::ShowOpCount() {
    cout<<"덧셈: "<<add<<" "<<"뺄셈: "<<min<<" "<<"곱셈: "<<mul<<" "<<"나눗셈: "<<div<<endl;
}

int main(){
    Calculator cal;
    cal.Init();
    cout <<"3.2 + 2.4 = "<<cal.Add(3.2, 2.4)<<endl;
    cout <<"3.5 / 1.7 = "<<cal.Div(3.5, 1.7)<<endl;
    cout <<"2.2 - 1.5 = "<<cal.Min(2.2, 1.5)<<endl;
    cout << "4.9 / 1.2 = "<<cal.Div(4.9, 1.2)<<endl;
    cal.ShowOpCount();
    return 0;
}

/*3.2 + 2.4 = 5.6
3.5 / 1.7 = 2.05882
2.2 - 1.5 = 0.7
4.9 / 1.2 = 4.08333
덧셈: 1 뺄셈: 1 곱셈: 0 나눗셈: 2*/

 

2) 멤버변수 개념 다시 ~!

멤버변수는: Class의 함수를 통해 직접 생성하거나, 값을 입력하거나, 조작할 대상 (e.g. int add;)

- 함수의 입력으로 바로 정의되는 변수는, 클래스의 멤버변수로 선언하지 않음 (e.g. double a;)

 

3) 디자인 개괄 보기

(1) 클래스

class 클래스명 {

private:

      멤버변수 정의

public:

      멤버함수 정의 //이때 함수 정의까지 포함하면 클래스 구조가 너무 길어질 수 있으므로, 상속을 유지하되 inline 통해 클래스 밖으로 빼기

      //말 그대로 '선언;'만 하기

}

 

(2) 클래스 함수 - inline으로 정의

inline 자료형 클래스명::멤버함수(  ) {

함수 정의 ~ ~ ~ }

 

** 클래스 정의할 때는 괄호 따로 쓰지 않음

** 함수 정의할 때는 괄호 따로 씀. input이 반드시 있어야 하면, "자료형 input명"의 형태로 채우고, 아니면 공란으로 비워둠.

728x90

+ Recent posts