본문 바로가기

C++/윤성우의 열혈 프로그래밍 C++

윤성우의 열혈 C++ chpt1.

C++ 버전의 'Hello World' 출력 프로그램

#include 헤더파일 선언
std::cout << 출력대상1 << 출력대상2 << 출력대상 3 << ... 출력 기본구성
std::endl // '\n' 개행 진행

 

#include <iostream>

int main(void)
{
    int num = 20;
    std::cout << "Hello World!" << std::endl;
    std::cout << "Hello " << "World!" << std::endl;
    std::cout << num << ' ' << 'A';	// 개행 처리가 없으므로 다음 출력이 이어서 출력
    std::cout << ' ' << 3.14 << std::endl;

    return 0;
}

Hello World!
Hello World!
20 A 3.14

 

출력예제를 보면 C 에서 사용하던 출력 서식 지정자를 사용하지 않는 것을 확인할 수 있다.


scanf 를 대신하는 데이터의 입력

std::cin >> val 입력 기본구성

 

#include <iostream>

int main(void)
{
    int val1;
    std::cout << "첫 번째 숫자 입력: ";
    std::cin >> val1;

    int val2;	// C++ 은 코드 중간에도 변수 선언이 가능1
    std::cout << "두 번째 숫자 입력: ";
    std::cin >> val2;

    int result = val1 + val2;	// C++ 은 코드 중간에도 변수 선언이 가능2
    std::cout << "덧셈 결과: " << result << std::endl;

    return 0;
}

첫 번째 숫자 입력: 3
두 번째 숫자 입력: 5
덧셈 결과: 8

 

입력 역시 서식 지정자를 사용하지 않고 간단하게 입력받는 것을 알 수 있다.


C++ 의 지역 변수 선언

#include <iostream>

int main(void)
{
    int val1, val2;
    int result = 0;

    std::cout << "두 개의 숫자 입력: ";
    std::cin >> val1 >> val2;	// std::cin 을 통해서 입력되는 데이터는 whitespase 들에 의해서 구분됨(구분자를 특정할 순 없음)
				// 그로 인해 연이은 데이터 입력이 가능해진다
    if (val1 < val2)
    {
        for (int i = val1 + 1; i < val2; i++) // for문 내부에서도 변수 선언이 가능
        {
            result += i;
        }
    }
    else
    {
        for (int i = val2 + 1; i < val1; i++)
        {
            result +- i;
        }
    }
    std::cout << "두 수 사이의 정수의 합: " << result << std::endl;

    return 0;
}

두 개의 숫자입력: 3 7
두 사 사이의 정수 합: 15

배열 기반의 문자열 입출력

#inlcude <iostream>

int main(void)
{
    char name[100];
    char lang[200];
    std::cout << "이름은 무엇입니까? "
    std::cin >> name;

    std::cout << "좋아하는 프로그래밍 언어는 무엇인가요? ";
    std::cin >> lang;

    std::cout << "내 이름은 " << name << "입니다.\n";
    std::cout << "제일 좋아하는 언어는 " << lang << "입니다. " << std::endl;

    return 0;
}

이름은 무엇입니까? Yoon
좋아하는 프로그래밍 언어는 무엇인가요? C++
내 이름은 Yoon입니다.
제일 좋아하는 언어는 C++입니다.

함수 오버로딩의 이해

int MyFunc(int num)
{
    num++;
    return num;
}

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

int main(void)
{
    MyFunc(20);		//MyFunc(int num) 함수 호출
    NyFunc(30, 40);	// MyFunc(int a, int b) 함수 호출
    return 0;
}

 

C++ 은 함수호출 시 '함수 이름' 과 '절달되는 인자의 정보' 를 동시에 참조하여 호출할 함수를 결정한다. 따라서 이렇듯 매개변수의 선언이 다르다면 동일한 이름의 함수도 정의 가능하다. 

그리고 이러한 형태의 함수 정의를 사리켜 '함수 오버로딩(Function Overloading)' 이라 한다.

C++ 컴파일러는 이름 맹글링(Name Mangling)을 통한 시그니처를 기반으로 고유 심볼을 만들어 구분

 

함수 오버로딩의 예

 

성립예시

int MyFunc(char c) { ... }  // 매개변수의 자료형이 다르므로
int MyFunc(int n) { ... } // 함수 오버로딩 성립
int MyFunc(int n1) { ... }	 // 매개 변수의 수가 다르므로
int MyFunc(int n1, int n2) { ... } // 함수 오버로딩 성립
int MyFunc(int a, double b); // 동일한 인자라도 순서가 다르면
int MyFunc(double b, int a); // 함수 오버로딩 성립

 

 

미성립 예시

void MyFunc(int n) { ... } // 반환형의 차이는 함수 오버로딩
int MyFunc(int n) { ... } // 조건을 만족시키지 않음
int MyFunc(int a, int b=2);
int MyFunc(int a);

MyFunc(10) 	// 디폴트가 있는 함수인지 없는 함수인지 모호해져 컴파일 에러 발생
MyFunc(10, 5);	// 디폴트를 지정했기 때문에 디폴트값이 설정된 함수가 호출
int MyFunc(int a, double b=0.0);
int MyFunc(int a, int b=0);

int MyFunc(10, 5.0) // 문제없이 호출
int MyFunc(10, 5) // double 또한 정수값을 받을 수 있으므로 모호성 발생인한 컴파일 에러

매개 변수를 설정하는 디폴트값의 의미

 

int MyFuncOne(int num=7)
{
    return num + 1;
}
// 인자를 전달하지 않으면 7 이 전달된 것으로 간주하여 num 의 디폴트 값은 7로 정해진다
// 따라서 위 함수를 대상으로 하는 다음 함수의 호출은 그 결과가 같다
MyFuncOne();
MyFuncOne(7);
int MyFuncTwo(int num1=5, int num2=7)
{
    return num1 + num2;
}
// 인자를 전달하지 않으면 5 와 7이 전달된 것으로 간주한다.
// 따라서 이 함수를 대상으로 하는 다음 두 함수의 호출은 그 결과가 같다
MyFunc();
MyFunc(5, 7);

 

디폴트 값은 한 번만 지정해야 한다.

 

전방 선언이 존재할 경우 : 전방 선언에만 표기

전방 선언이 없을 경우 : 정의 부에 표기

// 전방 선언이 존재할 경우
int MyFunc(int a=5, int b=10);

int main(void) { ... }

int MyFunc(int a, int b)
{
	return a + b;
}
-------------------------------------------------
// 전방 선언이 없을 경우
int maint(void) { ... }

int MyFunc(int a=5, int b=10);

 

부분적 디폴트 값 설정

int YourFunc(int num1, int num2=5, int num3=7) { ... }

YourFunc(10);
YourFunc(10, 20);

매개변수의 일부에만 디폴트 값을 지정하고, 채워지지 않은 매개변수에만 인자를 전달하는 것이 가능

 

int YourFunc(int num1, int num2, int num3=30) { ... }	// 유효
int YourFunc(int num1, int num2=20, int num3=30) { ... } // 유효
int YourFunc(int num1=10, int num2=20, int num3=30) { ... } // 유효

전달되는 인자가 왼쪽에서부터 채워지므로, 디폴트 값은 오른쪽에서부터 채워져야 한다.

 

int WorngFunc(int num1=10, int num2 int num3) { ... }	// 무효
int WorngFunc(int num1=10, int num2=20, int num3) { ... } // 무효

전달되는 인자가 왼쪽에서부터 채워지므로, 오른쪽이 빈 상태로 왼쪽의 매개변수에만 일부 채워진 디폴트 값은 의미를 갖지 못한다.

따라서 컴파일 에러를 일으킨다.

 

디폴트 값을 우측부터 설정하는 것은, 보통 중요한 인자를 왼쪽에서부터 채워나가는 프로그래밍 언어를 그대로 따라감으로써, 디폴트 값을 설정한다는 건 있어도, 없어도 크게 영향을 끼치지 않는 정도이기 때문에 중요도로 봤을 땐 상대적으로 낮게 판단되어 우측부터 채워 나간다.


매크로 함수의 장점과 함수의 inline 선언

매크로 함수

#define SQUARE(x) ((x)*(x))

int main(void)
{
    std::cout << SQUARE(5) << std::endl;
    return 0;
}

----------------------------------------
// 선행처리 결과 (컴파일 결과)

int main(void)
{
    std::cout << ((5)*(5)) << std""endl;
    return 0;
}

 

매크로 함수의 장점

 

1. 컴파일 오버헤드가 없음 (진짜 함수가 아님)

매크로는 전처리기에서 단순히 텍스트 치환이기 때문에 컴파일 시간에 함수 호출 관련 처리를 하지 않아도 됨

#define PI 3.14159
#define AREA(r) (PI * (r) * (r))

int a = AREA(5);  // → 컴파일 전 치환: 3.14159 * 5 * 5

 

  • 함수 호출 비용(스택 프레임 생성, 점프 등)이 전혀 없음
  • 즉시 계산으로 이어짐
  • 런타임 성능이 향상을 기대할 수 있음

2. 인라인처럼 동작(성능 유리)

#define SQUARE(x) ((x)*(x))

 

  • 런타임 성능상 함수 호출 오버헤드 없음
  • 반복 루프에서 자주 쓰일 때 성능에 도움 줄 수 있음
  • 함수 호출이 자주 일어나면 인라인보다 더 빠를 수도 있음 (컴파일러 최적화 미적용 상황에서)

3. 모든 타입에 쓸 수 있음

매크로는 단순 텍스트이기 때문에 int, double, float, long 등 어떤 타입에도 적용.

#define MAX(a, b) ((a) > (b) ? (a) : (b))

int    a = MAX(3, 5);
double b = MAX(2.2, 1.1);
  • 함수는 타입마다 오버로딩이 필요했지만,
  • 매크로는 어떤 타입이든 그대로 치환되므로 타입 독립성 확보
  • 단점과 종이 한 장 차이긴 하지만, 템플릿 이전 C언어에서는 매우 유용했음

4. 조건부 컴파일에 응용 가능

#ifdef DEBUG
  #define LOG(x) std::cout << x << std::endl;
#else
  #define LOG(x)
#endif

 

 

  • 매크로는 함수처럼 보일 수 있지만 실제로는 컴파일 시점에 포함 여부 자체를 조절 가능.
  • 디버깅용 코드 삽입/제거를 깔끔하게 처리 가능

이건 함수로는 구현할 수 없는 기능

 

 

 

매크로 함수의 단점

 

함수의 정의 방식이 일반함수에 비해서 복잡하다. 따라서 복잡한 함수의 정의에는 한계가 있다.

 

1. 타입 검사 안 됨 (Type Safety X)

#define SQUARE(x) ((x)*(x))

SQUARE("hello");   // 단순 치환되므로 에러 아님

 

  • "hello"는 문자열인데도 컴파일 에러가 발생하지 않음
  • 컴파일러가 함수 호출이 아니라 그냥 ((x)*(x))로 치환만 하기 때문
  • 결과적으로 런타임에서 이상한 동작이 발생하거나, 컴파일러 오류 메시지가 엉뚱한 곳에 나타나 디버깅이 어려워짐,

2. 디버깅이 어려움

 

#define SQUARE(x) ((x)*(x))

int main()
{
    int a = 3;
    int b = SQUARE(a);
}

 

  • SQUARE(a)는 디버깅 도구에서 함수 호출로 나오지 않음.
  • 그냥 ((a)*(a))로 처리돼서, 스택 프레임이 생기지 않고 함수 트레이스 찍히지 않음.
  • 즉, 디버깅 도중 어떤 매크로가 동작했는지 추적하기 어렵움.

3. 괄호 실수 (우선순위 문제)

 

#define SQUARE(x) (x*x)

int result = SQUARE(2 + 3);  // 의도: (2 + 3)^2 = 25

 

  • 실제로는 2 + 3 * 2 + 3 → 2 + 6 + 3 → 11 이 된다.
  • 이유인 즉 -> 치환 결과: 2 + 3 * 2 + 3
  • 해결 방법: 정의를 ((x)*(x))처럼 괄호로 감싸야 하지만, 실수하기 쉽고 매번 검증하기 어렵다.

4. 부작용 발생 (Side Effect)

int x = 5;
int y = SQUARE(x++);  // 의도: y = 36 ?

 

 

  • 치환 결과: ((x++)*(x++))
  • x는 두 번 증가되고, 결과는 x의 증가 순서에 따라 달라짐
  • 의도치 않은 동작 + 값도 엉망 + 디버깅 어려움

5. 스코프 없음 (변수 범위 통제 불가)

#define INCREMENT(x) { int temp = x + 1; x = temp; }

void foo()
{
    int temp = 10;
    int a = 5;
    INCREMENT(a);  // temp 변수가 겹쳐서 충돌
}

 

 

  • 매크로는 지역 변수와 충돌 방지 기능이 없음
  • 함수라면 스코프 안에서만 동작해서 충돌 없음

 

인라인 함수

매크로 함수의 장점은 취하고, 단점은 보완한 것이 C++ 의 인라인 함수이다.

inline int SQUARE(int x)
{
    return x * x;
}

int main(void)
{
    std::cout << SQUARE(5) << std::endl;
    std::cout << SQUARE(12) << std::endl;

    return 0;
}

25
144

 

키워드 inline 선언은 컴파일러에 의해서 처리된다. 따라서 컴파일러가 함수의 인라인화를 결정한다.

inline 선언이 되어도 inline 처리되지 않을 수도 있고, inline 선언이 없어도 inline처리될 수 있다.

즉, 컴파일러마다 inline 에 대한 규칙이 다르게 적용되기 때문에 해당 컴파일 기준에서 inline 으로 부합하면 명시 없이도 inline 처리 되는것이고, inline 선언이 있더라도 기준에 부합하지 않으면 명시하더라도 inline 처리를 하지 않는다는 것이다.

 

컴파일러에 의해 inline 이 결정이 되며, inline화 되더라도, 함수로써 존재하기 때문에 

타입 검사, 스코프 처리, 디버깅 스택 추적등이 가능하다.

'C++ > 윤성우의 열혈 프로그래밍 C++' 카테고리의 다른 글

윤성우의 열혈 C++ chpt6.  (2) 2025.06.08
윤성우의 열혈 C++ chpt5.  (0) 2025.05.16
윤성우의 열혈 C++ chpt4.  (0) 2025.05.12
윤성우의 열혈 C++ chpt3.  (0) 2025.05.12
윤성우의 열혈 C++ chpt2.  (0) 2025.05.11