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

1. 인라인 함수의 사용

#include <iostream>

// 인라인 함수 정의 // 앞에 inline이 추가됨
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(5, 3); // 인라인 함수 호출
    return 0;
}

 

2. 인라인 함수를 사용하지 않은 경우

#include <iostream>

// 일반적인 함수 정의
int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(5, 3); // 함수 호출
    return 0;
}

두 예시의 가장 중요한 차이점은: 함수 호출 방식 (inline 키워드)

- inline O : 함수 호출 대신 함수 내용이 호출 지점에 직접 삽입

함수 호출 오버헤드가 사라지고 코드 실행 속도가 향상

따라서, 인라인 함수는 그 함수 호출이 아주 많은 경우 성능 향상에 유용함!

  코드 삽입이기 때문에 재귀가 불가능!

 

↔️ inline X : 함수 호출이 발생하며, 함수 호출 및 복귀 주소 저장을 위한 스택 프레임이 생성됨

     → 작은 함수에서도 오버헤드 발생 (여러 번 호출될 시 성능 저하 가능)

 

**

어떤 함수가 특정 구조체에 의존적일 때, 해당 함수를 구조체 내부에서 상속하여 정의할 수 있음.

단, 구조체 코드에 대한 가독성을 높이기 위해서, 상속을 유지하되 구조체 정의 코드 밖으로 빼낼 수 있음.

이때 inline과 :: 코드를 활용함.

 

struct Car
{	int curSpeed;
	void Accel(); //함수 원형을 구조체 내부에 상속시켜 정의하되
    }

inline void Car::Accel() //구조체 코드 바깥으로 함수 정의를 빼냄 //이때 inline & :: 를 사용
{
	//~~~~ }

 

728x90
728x90

"Call by value"와 "call by reference"는 함수 호출 방식을 나타내는 용어

이 두 가지 방식의 주요 차이점: 함수에 인수를 전달하는 방법 & 함수가 인수를 처리하는 방법

 

1. Call by Value (값에 의한 호출):

  • 함수에 매개 변수로 ‘값’을 전달합니다.
  • 함수 내에서 매개 변수는 별도의 변수로 취급되며, 인수의 값이 복사되어 매개 변수에 저장됨
  • 따라서 함수 내에서 매개 변수의 값을 변경하더라도 호출자(caller/원본)의 변수는 영향을 받지 않음
  • C++에서 함수의 기본 호출 방식은 "call by value"
void modifyValue(int x) {
    x = x + 10;
}

int main() {
    int num = 5;
    modifyValue(num);
    std::cout << "num: " << num << std::endl; // 출력 결과: num: 5
    return 0;
}

 

2. Call by Reference (참조에 의한 호출):

 

"주소/참조 값을 전달받아서, 함수 외부에 선언된 변수직접 접근하는 형태의 함수 호출"

- 함수 입력으로 주소/참조 값이 전달되었다는 사실이 중요한 게 아니라

- 입력된 주소/참조 값을 통해 직접 외부 변수를 참조하여 모종의 행위(읽기/조작 등)을 했을 때 call-by-reference라 칭할 수 있음

  • 함수에 매개 변수로 ‘변수의 참조/주소’를 전달
  • 함수 내에서 매개 변수는 원래 변수와 동일한 메모리 위치를 참조하므로,
    함수에서 매개 변수를 수정하면 호출자(원본)의 변수가 직접 영향을 받음
  • C++에서는 매개 변수를 참조로 전달하려면 참조자(reference) 또는 포인터(pointer)를 사용해야 함

(1) 포인터 *를 활용 (주소)

void modifyValueByPointer(int* x) {
    *x = *x + 10;
}

int main() {
    int num = 5;
    modifyValueByPointer(&num);
    std::cout << "num: " << num << std::endl; // 출력 결과: num: 15
    return 0;
}

- 늘 느끼는 건데.. (?) 주소값은 메모리가 할당된 위치이고, 이 주소값 자체는 변하지 않고 고정됨.

반면, 해당 주소에 저장된 value 자체는 변형&조작의 대상이 됨.

 

- 단, 이하와 같은 방식으로, 포인터를 통해 value를 직접 조작하는 것을 막을 수 있다.

const int* ptr1 = &num; //ptr1를 이용해서 num의 value를 바꿀 수 없음 //*ptr1를 상수화시켰다는 의미

*주의할 점은, ptr = 2; ptr이 const 변수이기 때문에 컴파일 에러가 발생 하지만

 num = 2; num이 non-const 변수이기 때문에 정상이다. 즉, 포인터 변수가 가리키는 num 자체가 상수화가 되는 것이 아니다.

https://easycoding91.tistory.com/entry/C-%EA%B0%95%EC%A2%8C-const-%EC%9C%84%EC%B9%98%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95

 

 

- (완전 별개의 이야기지만) 포인터 주소값 자체를 상수화시킬 수도 있다.

int* const ptr2 = &num; // num의 주소값인 ptr2 자체가 상수화됨

 

 

 

(2) 참조 &를 활용 (별명)

void modifyValueByReference(int& x) {
    x = x + 10;
}

int main() {
    int num = 5;
    modifyValueByReference(num);
    std::cout << "num: " << num << std::endl; // 출력 결과: num: 15
    return 0;
}

 

- int& x에서 &는 C++에서 "참조자"를 나타내는 연산자

- 자료형 바로 뒤에 &가 붙음!!! (변수명 앞에 &가 붙는 게 아니라 ~~~.)

- 참조자는 변수에 대한 별명(alias)이며, 원본 변수를 직접 참조하게 함.

이것은 값(value) 자체가 아니라 변수에 대한 참조(reference)를 함수에 전달함을 의미함

- int& x에서 x는 참조자의 이름이고, int는 참조할 변수의 데이터 타입을 나타냄 (int& x: "정수형 변수에 대한 참조를 받는다")

 

 

참조자를 사용할 때의 특징

  • 원본 변수의 별명: 참조자는 원본 변수의 별명으로 동작함, 따라서 참조자를 통해 값을 읽거나 수정하면 원본 변수에도 동일한 작업이 반영됨.
  • 포인터와 다른 점: 참조자 != 포인터.
    포인터는 메모리 주소를 저장하고 간접 참조를 통해 값을 읽고 쓰고 조작할 수 있지만,
    참조자는 변수에 직접적으로 연결되어 있으므로 포인터보다 사용하기 간편
  • 전달이 아닌 참조 전달: 함수에 참조자를 매개 변수로 전달하면 함수 내에서 원본 변수를 직접 조작할 있으며, 호출자의 변수도 변경됨( 성질은 포인터에서도 동일~)
728x90
728x90

실행중인 프로그램은 운영체제로부터 메모리 공간을 할당받는데, 프로그램이 실행 중인 동안 사용되는 메모리는 일반적으로 세 가지 주요 영역으로 나뉨

: 데이터, 스택, 힙. 각 영역에는 다음과 같은 형태의 변수나 데이터가 할당됨.

 

간단히 말해 데이터 영역은 전역 변수와 정적 변수를 저장하고, 스택 영역은 함수 호출 관련 데이터를 저장하며,  영역은 동적으로 할당된 변수와 데이터를 저장함. 이러한 영역은 각각의 역할을 수행하여 프로그램의 메모리 사용을 관리하고, 변수  데이터의 수명을 결정함.

 

1. 데이터(Data) 영역:

- 전역 변수(Global Variables): 프로그램 전체에서 접근 가능한 변수로, 초기화된 데이터를 저장. 이러한 변수는 프로그램이 시작될 메모리에 할당되고 프로그램이 종료될 때까지 유지됨.

- 정적 변수(Static Variables): 함수 내부에서 선언되지만, 프로그램이 실행될 메모리에 할당되며 함수가 호출될 변수 값이 초기화됨. 함수가 종료되더라도 메모리에 남아있는 특성을 가짐.

 

#include <iostream>

// 전역 변수 (global variable)
// globalVar는 프로그램 전체에서 접근 가능하며 초기화된 데이터를 저장
int globalVar = 10;

int main() {
    // 정적 변수 (static variable)
    // staticVar는 main 함수 내에서 선언되었지만 프로그램이 시작될 때 메모리에 할당되며, 프로그램이 종료될 때까지 유지됨
    static int staticVar = 5;
    
    std::cout << "전역 변수: " << globalVar << std::endl;
    std::cout << "정적 변수: " << staticVar << std::endl;

    return 0;
}

 

2. 스택(Stack) 영역

- 지역 변수(Local Variables): 함수 내부에서 선언되며 함수가 호출될 메모리에 할당됨. 함수가 종료되면 해당 변수도 소멸됨. 스택은 함수 호출과 관련된 데이터 함수의 호출 복귀 정보를 저장하는 사용됨.

#include <iostream>

int add(int a, int b) {
    int result = a + b; // 지역 변수 (local variable)
    return result;
    // add의 지역변수 ; 함수가 호출될 때 메모리에 할당되고 함수가 종료될 때 소멸
}

int main() {
    int x = 5; // 지역 변수 (local variable)
    int y = 3; // 지역 변수 (local variable)
    // main의 지역변수 ; 함수가 호출될 때 메모리에 할당되고 함수가 종료될 때 소멸

    int sum = add(x, y); // add 함수 호출

    std::cout << "합계: " << sum << std::endl;

    return 0;
}

 

3. 힙(Heap) 영역:

- 동적 할당된 변수(Dynamically Allocated Variables): 프로그램 실행 중에 필요한 메모리를 동적으로 할당하고 해제하는 사용됨. 이러한 변수는 개발자가 메모리 할당 해제를 관리해야 . 예를 들어, new (C++) 또는 malloc (C) 함수로 메모리를 할당하고 delete (C++) 또는 free (C) 함수로 메모리를 해제함.

 

#include <iostream>

int main() {
    int* dynamicVar = new int; // 동적 할당된 변수 (dynamically allocated variable)
    *dynamicVar = 7;

    std::cout << "동적 할당된 변수: " << *dynamicVar << std::endl;

    delete dynamicVar; // 메모리 해제

    return 0;
    
    // 프로그램 실행 중에 메모리를 할당하고 new 연산자를 사용하여 초기화함
    // 이 변수는 개발자가 명시적으로 메모리를 해제해야 하며, delete 연산자를 사용하여 메모리를 해제함
}
728x90

+ Recent posts