C++/카루의 C++ 강좌

[C++ 강좌] 2-1. 변수와 상수, 배열 I

카루-R 2020. 3. 21. 15:13
반응형

오늘 강좌는 '1-3. 변수의 선언과 자료형'의 연장선입니다.

변수의 확장

그동안 우리는 변수를 선언할 때 main() 함수 안에서만 사용했습니다.

#include <iostream>

int main()
{
    double pi = 3.141592;
    std::cout << "Pi: " << pi << std::endl;
    return 0;
}

이렇게 말이죠. 그런데 사실 변수는 main() 함수 안 뿐만 아니라 밖에서도 선언이 가능합니다.

#include <iostream>

double pi = 3.141592;
int main()
{   
    std::cout << "Pi: " << pi << std::endl;
    return 0;
}

이렇게 main() 함수 밖에 선언된, 어느 함수에도 포함되지 않은 변수를 '전역 변수'라고 부릅니다.

전역 변수는 어느 함수에서나 접근이 가능합니다. 예를 들면,

double pi = 3.141592;

void printpi()
{
    std::cout << pi << std::endl;
}

int main()
{   
    printpi();
    return 0;
}

이렇게 다른 함수에서도 pi의 값을 출력할 수 있습니다. 함수에 관해선 '4-1. 함수의 선언과 정의, 재귀함수'에서 배우게 될 겁니다.

int day = 5;

int main()
{   
    std::cout << "오늘은 " << day << "일";
    day = day + 1;
    std::cout << "내일은 " << day << "일";
    return 0;
}

아무 타입이나 선언할 수 있고, 이렇게 main() 함수에서 값을 변경하는 것도 가능합니다.

하지만 전역 변수는 가능한 사용을 자제하시기 바랍니다. 나중에 클래스의 정적 멤버 변수로 대체할 수 있습니다.

일단 한 가지 중요한 사실을 말씀드리자면,

int n;

int main()
{   
    int n{5};
    std::cout << n;
    return 0;
}

위 코드는 무슨 값을 출력할까요? 전역 변수 n과 지역 변수 n, 정답은 지역 변수 n으로써, 5입니다.

main() { } 이라는 중괄호 블록이 있기 때문에, 해당 중괄호 블록 안에서 n을 먼저 찾습니다.

즉, 전역 변수보다 지역 변수가 우선입니다.

전역 변수 n을 사용하고 싶으면 앞에 :: 을 붙이면 됩니다.

std::cout << ::n;

눈치가 빠르신 분들은 std와 cout 사이의 ::과 같다는 걸 아셨을 겁니다. 맞습니다. 정확이 같은 용도인데, 이거에 대해선 좀 나중에 배울 예정입니다.

아무튼, 전역 변수 n을 초기화하지 않았는데 0이 출력됩니다. 전역변수는 자동으로 기본값(0)으로 초기화됩니다.

상수 (const vs constexpr)

문제가 하나 있습니다.

double pi = 3.141592;

int main()
{   
    pi = 6;
    std::cout << "원주율: " << pi;
    return 0;
}

이렇게 전역 변수를 사용한 경우, 그 값을 main()함수나 다른 함수에서 마음대로 바꿔버릴 수가 있습니다. 원주율이 6이 되는 기이한 일이 벌어질 수도 있습니다.

이걸 막기 위해서는 pi를 변수가 아닌 상수, 즉 변하지 않는 수로 선언해야 합니다. 값을 '고정'시켜야 한다는 의미입니다. C++에서는 이를 위해 const와 constexpr 키워드를 제공합니다.

constexpr 자료형 변수 이름 = 초기값;
const 자료형 변수;

둘의 차이점이라면 constexpr은 컴파일을 할 때 값이 고정되어 저번 시간에 배운 #define과 비슷한 효과를 낼 수 있습니다. 대신 선언과 동시에 초기화를 해야 합니다. constexpr을 사용하면 10, 3.14와 같은 리터럴 상수처럼 사용할 수 있습니다.

const는 프로그램이 실행될 때 값이 고정되기 때문에 초기화가 프로그램의 실행 직전까지 미뤄질 수 있습니다. 대신 constexpr처럼 컴파일 타임에 상수가 필요한 곳에선 사용할 수 없습니다.

일단 pi를 다시 선언해 보겠습니다.

constexpr double pi = 3.141592;

참고로 constexpr 키워드는 const의 기능을 내장하고 있어서 const의 상위 호환입니다.

따라서 constexpr const double pi라고 써도 괜찮습니다.

일단 위처럼 상수를 선언하면 pi = 6; 여기서 할당이 불가능하다면서 오류가 납니다. 이렇게 constexpr을 사용하여 변하지 않는 수를 만들 수 있습니다.

constexpr char A = 'A';
char ch = 'B';
const char B = ch;

// constexpr char chB = ch; // 에러
constexpr char chA = A;

constexpr은 리터럴이나 다른 constexpr 상수로만 초기화할 수 있습니다. 즉, 컴파일타임에 알 수 있는 값이 필요합니다. (숫자, ...) 하지만 const는 다른 변수를 대입할 수 있습니다. 일반적으로 함수의 매개변수에 const를 많이 쓰고, pi같은 상수로썬 constexpr을 많이 씁니다.

리터럴

// 정수형. 리터럴이 숫자 뒤에 붙고, 대소문자 구분이 없음
short s = 3;               // short는 자신만의 리터럴이 없습니다.
unsigned short us = 3U;    // unsigned의 리터럴 U만 사용
int n = 5;                 // 정수는 아무 표시가 없으면 int로 간주합니다.
unsigned int un = 5U;      // unsigned의 리터럴 U
long l = 7L;               // long은 리터럴 L을 사용
unsigned long ul = 7UL;    // unsigned의 U, long의 L
long long ll = 10LL;       // long long의 리터럴 LL을 사용
unsigned long long ull{10ULL}// unsigned의 U, long long의 리터럴 LL

// 실수형, E 또는 F를 사용하며, 소문자로 표기해도 무방
float f1 = 3.14F            // float의 리터럴 연산자 .(소수점) + F
double d1 = 6.28            // 소수점만 쓰면 double
double d2 = 0.6E-28;        // 이런 표기도 가능
float f2 = 0.3E-14F;        // F가 붙으면 float
auto what = 100.;           // 숫자 뒤에 소수점이 있으니 int가 아닌 double로 처리

// 문자형. 대소문자를 구분합니다.
char ch = 'A';              // 작은따옴표만 쓰면 char형
char8_t ch8 = u8'A';        // C++20에 추가된 u8을 앞에 쓰면 UTF-8
char16_t ch16 = u'A';       // 소문자 u를 쓰면 UTF-16
char32_t ch32 = U'A';       // 대문자 U를 쓰면 UTF-32
wchar_t wch = L'A';         // 대문자 L을 쓰면 wchar_t

// 기타
bool b = true;              // bool은 true와 false만 저장할 수 있습니다.
auto str = "String.";       // 문자열 저장
auto u8str = u8"String.";   // UTF-8 문자열 저장
auto u16str = u"String.";   // UTF-16
auto u32str = U"String.";   // UTF-32
auto wstr = L"String.";     // 유니코드
auto rstr = R"(String.)";   // 날 문자열 리터럴

저렇게 날 값을 가진 , 위의 코드에선 대입 연산자 오른쪽에 있는 게 모두 리터럴 상수입니다. constexpr 상수는 이러한 리터럴 상수로만 초기화할 수 있습니다.

정수 리터럴에 대해 몇가지 더 말씀드리자면

int binary = 0B0110'1101;      // 109
int oct = 075;                 // 61
int dec = 53;
int hex = 0xa3;                // 163

long long BigInt = 123'478'654'786'452LL;

C++은 정수를 이진수/8진수/10진수/16진수로 표현하는 기능을 가지고 있습니다.

  • 앞에 0b 또는 0B를 붙이면 이진수
  • 앞에 0을 붙이면 8진수
  • 아무것도 붙이지 않으면 10진수
  • 앞에 0x 또는 0X를 붙이면 16진수

또한, 숫자가 긴 경우를 대비하여 숫자의 중간중간에 작은따옴표를 넣는 것을 허용합니다.

위 코드에서 binary는 네 자리 씩 끊었고, BigInt는 세 자리 씩 끊었습니다. 몇개를 넣든, 얼마 간격으로 넣든지는 상관 없습니다. 자신이 보기 편하면 됩니다.

배열 I

배열이란, 동일한 자료형을 가진 변수의 모임입니다. 배열을 통해 한 번에 여러 변수를 선언할 수 있습니다.

#include <iostream>

int main()
{   
    constexpr int count{5};
    int num[count];

    std::cout << "5개의 정수를 입력: ";
    std::cin >> num[0] >> num[1] >> num[2] >> num[3] >> num[4];

    std::cout << "평균: " << (num[0] + num[1] + num[2] + num[3] + num[4]) / 5;
    return 0;
}

count라는 constexpr 상수를 선언했습니다. 배열은 다음과 같이 선언합니다.

자료형 배열명[요소 개수];

이때 요소 개수만큼의 변수가 만들어집니다. '요소 개수'는 컴파일 타임 상수여야 하며, 보통 수를 직접 기입합니다. 여기선 constexpr 상수로 대신했습니다. constexpr 상수가 이렇게도 쓰일 수 있습니다.

int num[5];

이렇게 선언한 것과 완전히 같습니다. int형 변수 5개를 한번에 선언한 것입니다.

우리가 int a, b, c;라고 선언하면 이들이 메모리에 저장되는 위치를 알 수 없습니다. 아무데서나 막 생성이 되기 때문인데요, 배열을 사용하면

num[0]

num[1]

num[2]

num[3]

num[4]

이렇게 따닥따닥 붙어있기 때문에 num[0]의 메모리 주소를 알면 나머지 변수의 위치를 알 수 있습니다. 참고로, 배열을 int num[5]; 라고 정의하면 5개가 생성이 되는 것은 맞으나 첨자(=대괄호 안의 숫자)가 0부터 시작합니다. 이 점 주의하시기 바랍니다.

 

배열을 선언하면 num[0], num[1]...num[4]를 각각의 변수처럼 사용이 가능합니다.

그래서 이렇게 입력을 받을 수도 있습니다.

std::cin >> num[0] >> num[1] >> num[2] >> num[3] >> num[4];

이제부터 정말 중요한 내용이 나오기 시작했습니다. 배열, constexpr 상수...

나중에 갈 수록 정말 많이 쓰니 잘 숙지하시기 바랍니다. 배열은 반복문이 끝난 후에 더 심화적으로 나갈 예정입니다.

본 강좌의 연계 심화 강좌는 따로 없습니다. 다음 시간에는 연산자로 찾아뵙겠습니다.

반응형