1. C++ 개요

특징

C언어의 절차지향적 언어의 특징과 클래스를 사용하는 객체 지향적 언어의 특징을 가졌다. 동시에 템플릿으로 대변되는 일반화 프로그래밍 방식의 언어이다.

C언어를 기초로 삼아 만들었기 때문에, C 표준 라이브러리를 사용할 수 있다.

C++에서 소스 파일로부터 실행 파일을 생성하는 순서

  • 소스 파일의 작성 : .cpp 확장자로 된 소스 파일 작성
  • 선행처리 : #로 시작하는 선행처리 지시문의 처리 작업
  • 컴파일 : 기계어로 변환, 오브젝트 파일 생성(.o)
  • 링크 : 운영체제와의 인터페이스를 담당하는 시동 코드(start-up code)와 오브젝트 파일, 라이브러리 파일 등을 합쳐 하나의 실행 파일 생성(.exe)

#include 문

외부에 선언된 함수나 상수를 사용하기 위해서 헤더 파일의 내용을 현재 파일에 포함할 때 사용한다. C++에서는 헤더파일의 확장자를 사용하지 않는다. 기존 C언어 헤어 파일의 앞에 c를 추가하여 C++ 스타일의 헤더 파일로 변환하기도 한다.

1
#include <cmath>

#define 문

함수나 상수를 단순화해주는 매크로 정의

namespace

이름이 기억되는 영역을 뜻하며, 이름에 대한 충돌을 방지해주는 방법을 제공한다.

C++ 프로그램에서 표준 구성 요소인 클래스, 함수, 변수 등은 std라는 이름 공간에 저장되어 있으며 사용을 위해 std::를 붙어 해당 정의가 std라는 네임스페이스에 있다는 것을 컴파일러에게 알려주어야 한다.

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout<<"hi!";
return 0;
}

네임스페이스에 속한 정의를 간단하게 사용하려면 다음 명령문을 추가해 네임스페이스 이름을 붙이지 않고 사용 가능하다.

1
using namespace std;

iostream

C++의 표준 입출력 클래스를 정의하고 있는 헤더파일이다. C++에서는 cout 객체가 출력 작업을, cin 객체가 입력 작업을 수행한다. C언어처럼 printf(), scanf() 함수로도 입출력 작업 수행 가능하다. C++ 표준 입출력 객체는 입출력 데이터의 타입을 자동으로 변환시켜준다.

(scanf/printf가 cin/cout보다 빠르다.)

1
2
3
int age;
std::cout<<"나이를 입력하세요.";
std::cin>>age;
  • <<(산입 연산자) : 출력할 데이터를 출력 스트림에 삽입
  • >>(추출 연산자) : 사용자가 입력한 데이터를 입력 스트림에서 추출해 오른쪽 변수에 저장

2. 타입

변수

변수란 데이터를 저장하기 위해 프로그램에 의해 이름을 할당받은 메모리 공간을 의미한다. 저장된 값은 변경될 수 있다.

변수 생성 규칙

  • 변수의 이름은 영문자(대소문자), 숫자, 언더스코어(_)로만 구성한다.
  • 변수의 이름은 숫자로 시작될 수 없다.
  • 변수의 이름 사이에는 공백을 포함할 수 없다.
  • 변수의 이름으로 C++에서 미리 정의된 키워드(keyword)는 사용할 수 없다.
  • 변수 이름의 길이에는 제한이 없다.
  • 대소문자는 구분된다.

변수를 선언한 뒤 초기화하지 않으면 해당 메모리 공간에는 쓰레기값이 들어있다. 또한 다른 타입의 데이터를 저장할 경우 저장한 데이터에 변형이 일어날 수 있다.

상수

변수처럼 데이터를 저장할 수 있는 메모리 공간을 의미하지만 프로그램이 실행되는 동안 메모리에 저장된 데이터를 변경할 수 없다. 상수 표현 방식에 따라 리터럴 상수와 심볼릭 상수로 나뉜다.

  • 리터럴 상수(literal constant) : 변수와 달리 메모리 공간을 가리키는 이름을 가지고 있지 않다. 타입에 따라 정수형, 실수형, 문자형 리터럴 상수로 구분할 수 있다.

    • 정수형 : 10진수, 8진수(0으로 시작), 16진수(0x로 시작)로 표현할 수 있다. 여러 가지 진법으로 표현된 정수형 상수의 출력을 위해 cout 객체는 dec, hex, oct 조정자를 제공한다. 정수형 리터럴 상수는 데이터의 값이 너무 커서 int형으로 저장할 수 없거나, 접미사를 사용해 해당 상수의 타입을 직접 명시하는 경우를 제외하고 모두 int형으로 저장된다.

      1
      2
      3
      4
      5
      6
      7
      int  a  =  10;

      cout<<"숫자 10을 10진수로 표현하면 "<<a<<"이며, "<<endl;
      cout<<oct;
      cout<<"숫자 10을 8진수로 표현하면 "<<a<<"이며, "<<endl;
      cout<<hex;
      cout<<"숫자 10을 16진수로 표현하면 "<<a<<" 입니다.";
접미사 타입
기본설정 (signed) int
u 또는 U unsigned int
l 또는 L (signed) long
ul 또는 uL 또는 Ul 또는 UL unsigned long형
long long 또는 u11 또는 U11 또는 uLL 또는 ULL unsigned long long형 (C++11부터 제공)
  • 실수형 : 실수형 리터럴 상수는 모두 double형으로 저장되며 부동 소수점 방식으로 저장된다. 접미사를 추가하여 저장되는 타입을 직접 명시할 수도 있다.
접미사 타입
f 또는 F float
기본 설정 double
l 또는 L long double
  • 포인터 : nullptr 키워드를 사용해 널 포인터를 표현할 수 있다. nullptr 키워드를 사용한 리터럴 상수의 타입은 포인터 타입이며, 정수형으로 변환할 수 없다. (0으로 포인터를 초기화해 널 포인터를 표현할 수 도 있지만 nullptr를 사용하는 것이 더 안전하다.)
  • 이진 : 0B 또는 0b 접두사와 0과 1의 시퀀스를 가지고 이진 리터럴 상수를 표현할 수 있다.
    • 심볼릭 상수(symbolic constant) : 변수와 마찬가지로 이름을 가진 상수이다. 선언과 동시에 반드시 초기화해야 한다. 매크로를 이용하거나 const 키워드를 사용하여 선언할 수 있다. C++에서는 가급적 const 키워드를 사용하는 것이 좋다.
      1
      cosnt int ages = 30;

기본 타입

타입은 해당 데이터가 메모리에 어떻게 저장되고, 어떻게 처리되어야 하는지를 명시적으로 알려준다. 기본 타입은 크게 정수형, 실수형, 문자형, bool형 타입으로 나뉜다.

  • 정수형 : 부호를 가진 소수 부분이 없는 수. unsigned는 부호를 나타내는 최상위 비트(MSB)까지 크기를 나타내는데 사용한다. 컴퓨터는 내부적으로 정수형 중에서도 int형의 데이터를 가장 빠르게 처리한다.
정수형 타입 할당되는 메모리의 크기 데이터의 표현 범위
(signed) short 2 바이트 -2^15 ~ (2^15 - 1)
unsigned short 2 바이트 0 ~ (2^16 - 1)
(signed) int 4 바이트 -2^31 ~ (2^31 - 1)
unsigned int 4 바이트 0 ~ (2^32 - 1)
(signed) long 4 바이트 -2^31 ~ (2^31 - 1)
unsigned long 4 바이트 0 ~ (2^32 - 1)
unsigned long long 8 바이트 0 ~ (2^64 - 1)
  • 실수형 타입 : 정수보다 더 넓은 표현 범위를 가지지만 컴퓨터가 실수를 표현하는 방식은 오차가 발생할 수 밖에 없다. float형은 소수점 6자리, double형은 15자리까지 오차없이 표현 가능하다.
실수형 타입 할당되는 메모리의 크기 데이터의 표현 범위
float 4 바이트 (3.4 X 10-^38) ~ (3.4 X 10^38)
double 8 바이트 (1.7 X 10^-308) ~ (1.7 X 10^308)
long double double형과 동일함. double형과 동일함.
  • 문자형 타입 : 문자형 데이터는 작은 정수나 문자 하나를 표현할 수 있다.
문자형 타입 할당되는 메모리의 크기 데이터의 표현 범위
(signed) char 1 바이트 2^-7 ~(2^7 - 1)
unsigned char 2 바이트 0 ~ 2^8
  • bool형 타입 : C++11부터 bool형 타입을 제공하며, true나 false 중 한 가지 값만을 가질 수 있다. 또한 어떤 값도 묵시적 타입 변환이 가능해 0인 값은 false로 나머지 값은 true로 자동 변환된다.

* C++11부터 auto 키워드를 이용해 변수의 초깃값에 맞춰 변수의 타입이 자동으로 선언되게 할 수 있다.

부동 소수점 수

컴퓨터에서 실수를 2진수로 표현하기 위한 방식에는 고정 소수점 방식과 부동 소수점 방식이 있다.

  • 고정 소수점 방식(fixed point) : 실수는 정수부와 소수부로 나눌 수 있다. 고정 소수점 방식은 고정된 소수부 자릿수를 미리 정해 실수를 표현한다. 정수부와 소수부의 자릿수가 크지 않아 표현할 수 있는 범위가 적다.

  • 부동 소수점 방식(floating point) : 실수를 가수부와 지수부로 나누어 표현한다.
    ±(1.가수부)×2^(지수부-127)라는 수식을 이용해 매우 큰 실수까지도 표현 가능하며, 대부분의 시스템은 부동 소수점 방식을 택한다. 하지만 부동 소수점 방식에 의한 실수 표현에는 항상 오차가 존재한다.

타입 변환

다른 타입끼리의 연산은 피연산자들을 모두 같은 타입으로 만든 후에 수행된다. 다음과 같은 경우 자동으로 타입 변환이 수행된다.

  • 다른 타입끼리의 대입, 산수 연산 시
  • 함수에 인수를 전달할 때

표현 범위가 좁은 타입에서 표현 범위가 더 넓은 타입으로 변환 시에는 문제가 없지만 반대의 경우에서의 타입 변환은 데이터 손실이 발생한다.

<타입 변환의 종류>

  • 묵시적 타입 변환(자동) : 대입, 산술 연산에서 컴파일러가 자동으로 수행하는 타입 변환이다. 대입 연산에서는 연산자의 오른쪽에 존재하는 데이터의 타입이 왼쪽에 존재하는 데이터 타입으로 변환된다. 산술 연산 시에는 데이터의 손실이 최소화되는 방향으로 타입변환이 진행된다. (char형 → short형 → int형 → long형 → float형 → double형 → long double형)

    1
    2
    3
    4
    5
    6
    int num1 = 3.1415;  //num1에 저장된 값 : 3. narrowing cast. 데이터 손실
    int num2 = 8.3E12; //num2에 저장된 값 : 2147483647, int형 저장 범위를 초과함
    double num3 = 5; //num3에 저장된 값 : 5

    double result1 = 5 + 3.14; //int형 데이터가 double형으로 자동 타입 변환됨.
    double result2 = 5.0f + 3.14; //float형 데이터가 double형으로 자동 타입 변환됨.

    *산술 연산 시 bool형 데이터인 true는 1로, false는 0으로 자동 타입 변환된다.

  • 명시적 타입 변환(강제) : 사용자가 타입 캐스트 연산자를 사용해 강제적으로 수행한다.

    1. (변환할타입) 변환할데이터
    2. 변환할타입 (변환할데이터)
      1
      2
      3
      4
      5
      int num1 = 1;
      int num2 = 4;
      double result1 = num1 / num2; //0 (int형 데이터끼리의 산술 연산에 대한 결과값은 int형이다.)
      double result2 = (double) num1 / num2; //0.25
      double result3 = double (num1) / num2; //0.25

3. 연산자

산술 연산자

+, -, *, /, %

대입 연산자

=, +=, -=, *=, /=, %=

증감 연산자

++x, x++, –x, x–

비교 연산자

==, !=, >, <, >=, <=

논리 연산자

&&, ||, !

비트 연산자

&, |, ^(xor, 같으면 0 다르면 1), ~(not, 1의 보수), <<(left shift), >>(right shift, 부호를 유지한다.)

기타 연산자

  • 삼항 연산자 : 조건식 ? 반환값1 : 반환값2
  • 쉼표 연산자 : 둘 이상의 변수를 동시에 선언하거나 둘 이상의 인수를 함수로 전달할 때
  • sizeof : 피연산자의 크기를 바이트 단위로 반환한다.

C++ 연산자

  • 범위 지정 연산자(::)

    1. ::식별자
    2. 클래스이름::식별자
    3. 네임스페이스::식별자
    4. 열거체::식별자
  • 멤버 포인터 연산자

    1. 클래스타입의객체.*멤버이름
    2. 클래스타입객체의포인터->*멤버이름
  • typreid 연산자 : 객체의 타입 반환. 런타임에 객체의 타입을 결정하거나, 템플릿에서 템플릿 매개변수의 타입을 결정할 떄 사용한다.

    typeid(표현식)


4. 제어문

조건문

  • if 문

    1
    2
    3
    4
    if (조건식)
    {
    명령문;
    }
  • if/else 문

    1
    2
    3
    4
    5
    6
    if (조건식)
    {
    명령문;
    } else {
    명령문;
    }
  • if/else if/ else 문

    1
    2
    3
    4
    5
    6
    7
    8
    if (조건식)
    {
    명령문;
    } else if {
    명령문;
    } else {
    명령문;
    }
  • switch 문

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    switch(조건 값)
    {
    case1:
    명령문;
    break;
    case2:
    case3:
    명령문;
    break;
    default:
    명령문;
    break;
    }

반복문

  • while 문

    1
    2
    3
    4
    while(조건식
    {
    명령문;
    }
  • do/while 문

    1
    2
    3
    do {
    명령문;
    } while (조건식);
  • for 문

    1
    2
    3
    4
    for (초기식; 조건식; 증감식)
    {
    명령문;
    }
  • 범위 기반 for 문 : 표현식 안에 포함되어 있는 모든 값에 대해 한 번씩 루프를 실행해준다. 배열을 자동으로 인식하며, 컨테이너 클래스에서 많이 사용된다.

    1
    2
    3
    4
    5
    int arr[5] = {1, 2,3, 4, 5};
    for(int element : arr)
    {
    cout<<element<<endl;
    }

기타 제어문

반복문의 흐름을 사용자가 직접 제어하도록 도와준다.

  • continue 문 : 루프 내에서 사용하여 해당 루프의 나머지 부분을 건너뛰고 바로 다음 조건식의 판단으로 넘어가게 해준다.
  • break 문 : 루프 내에서 사용하여 해당 반복문을 종료시킨다.
  • goto 문 : 프로그램의 흐름을 지정된 레이블로 무조건 변경시킨다. 프로그램의 흐름을 복잡하게 만들어 디버깅 이외에 거의 사용되지 않는다.

5. 배열과 포인터

배열(array)은 같은 타입의 변수들로 이루어진 유한 집합이다. 배열을 구성하는 각각의 값을 element, 배열에서의 위치를 가리키는 숫자를 index 라고한다.

1차원 배열

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//배열 선언 : 타입 배열이름[배열길이];
int grade[3];
grade[0] = 85;
grade[0] = 90;
grade[0] = 80;

int age[3] = {20, 32}; //초기화되지 못한 나머지는 0으로 초기화된다.
int age2[3] = {}; //전부 0으로 초기화된다.

int grade2[3];
grade2 = grade; //error

//배열의 길이를 입력하지 않으면 초기화 리스트에 맞춰 자동으로 길이가 설정된다.
int arr[] = {1, 2, 3};
  • 선언만 하고 초기화하지 않으면, 모든 배열 요소가 쓰레기값으로 채워진다.
  • 배열의 이름은 배열의 첫 번째 요소와 같은 주소를 가리킨다. (grade = grade[0]의 주소)
  • C++ 컴파일러는 배열의 길이 등을 일일이 검사하여 오류를 출력해 주지 않는다. 존재하지 않는 인덱스에 접근해도 오류가 없으니 주의해야 한다.
  • 배열이 차지하는 메모리의 크기 = 배열의 길이 X sizeof(타입)
  • 배열의 길이 = sizeof(배열 이름) / sizeof(배열 이름[0])

다차원 배열

2차원 이상의 배열로, 배열 요소로 또 다른 배열을 가지는 배열이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//선언
타입 배열이름[행의길이][열의길이];

//초기화 방식1
int arr1[3][5] = {1, 2, ... , 15};

//초기화 방식2
int arr2[3][5] = {
{1, 2, ...},
{6, 7, ...},
...
}

//초기화 방식3
int arr3[3][5] = {
{10, 20},
{30, 40, 50, 60, 0},
{0, 0, 70, 80}
};

// 열의 길이
int arr_col_len = sizeof(arr[0]) / sizeof(arr[0][0]);

2차원 배열에서 열의 길이는 생략할 수 있지만, 행의 길이는 반드시 명시해야 한다. 행의 길이 생략시 컴파일 오류 발생.

포인터 개념

  • 주소값 : 데이터가 저장된 메모리의 시작 주소를 의미한다.
  • 포인터 : 메모리의 주소값을 저장하는 변수로, 포인터 변수라고도 부른다.

    1
    2
    3
    4
    5
    int n = 100;   // 변수의 선언
    int *ptr = &n; // 포인터의 선언
    int **pptr = &ptr;

    int *ptr1, ptr2; //ptr1은 int형 포인터, ptr2는 int형 변수로 선언된다.
  • 주소 연산자(&) : 변수 이름 앞에 사용하여, 변수의 주소값을 반환한다.

  • 참조 연산자(*) : 포인터의 이름이나 주소 앞에 사용하여, 포인터에 저장된 주소에 저장되어 있는 값을 반환한다.
  • 포인터의 타입은 참조 연산자를 통해 값을 참조할 때, 참조할 메모리의 크기를 알려주는 역할을 한다.

포인터 연산

<포인터 연산 규칙>

  1. 포인터끼리의 덧셈, 곱셈, 나눗셈은 아무런 의미가 없다.
  2. 포인터끼리의 뺄셈은 두 포인터 사이의 상대적 거리를 나타낸다.
  3. 포인터에 정수를 더하거나 뺄 수는 있지만, 실수와의 연산은 허용하지 않는다.
  4. 포인터끼리 대입하거나 비교할 수 있다.
  • 포인터 연산 후 각각의 포인터가 가리키는 주소의 증가, 감소 폭은 포인터가 가리키는 변수의 타입에 따라 다르다. (예를들어, int형 포인터는 4바이트씩 증가, 감소한다.)
  • 배열의 이름은 값을 변경할 수 없는 상수라는 점을 제외하면 포인터와 같다. 배열의 이름은 해당 배열의 첫 번째 요소의 주소와 같다. (arr가 포인터 또는 배열일 때, arr[n] == *(arr+n))

    1
    2
    3
    4
    5
    6
    7
    8
    int arr[3] = {1, 2, 3};
    int *ptr = arr;

    cout<<ptr[0]<<", "<<ptr[1]<<", "<<ptr[2]<<endl; //1, 2, 3
    cout<<*(arr+0)<<", "<<*(arr+1)<<", "<<*(arr+2)<<endl; //1, 2, 3

    cout<<sizeof(arr)<<endl; //12
    cout<<sizeof(ptr)<<endl; //8

    * 포인터를 이용한 크기 계산에서는 배열의 크기가 아닌 포인터 변수의 크기가 출력된다.

  • 포인터 연산으로 배열의 크기를 넘어서는 접근을 해도 컴파일러 오류가 발생하지 않는다.

메모리 동적 할당

데이터 영역과 스택 영역에 할당되는 메모리는 컴파일 타임에 미리 결정된다. 하지만 힙 영역의 크기는 런 타임에 사용자가 직접 결정하게 되며, 런 타임에 메모리를 할당받는 것을 메모리 동작 할당이라고 한다.

포인터를 통해 런 타임에 메모리를 할당받아 포인터에 할당, 할당받은 메모리에 접근할 수 있다.

C++에서는 C언어의 라이브러리 함수를 통해서도 동적 할당 및 해제가 가능하지만 new, delete 연산자를 통한 방법이 효과적이다.

  • new 연산자 : 자유 기억 공간(free store)라는 메모리 공간(memory pool)에 객체를 위한 메모리를 할당받는다. new를 통해 할당받은 메모리는 이름이 없어 해당 포인터로만 접근이 가능하며, 사용할 수 있는 메모리가 부족해 메모리를 만들지 못한 경우 널 포인터를 반환한다.
  • delete 연산자 : 사용하지 않는 메모리를 다시 메모리 공간에 돌려준다.
1
2
3
4
5
6
7
8
9
10
11
///타입* 포인터이름 = new 타입;
int *ptr_int = new int;
*ptr_int = 100;

double *ptr_double = new double;
*ptr_double = 100.123;

...

delete ptr_int;
delete ptr_double;

6. 문자열

일련의 연속된 문자들의 집합을 문자열이라고 한다. C++에서는 C언어 스타일과 string 클래스를 이용해 문자열을 생성할 수 있다.

C언어 스타일 문자열

문자형 배열을 선언하면 이 배열이 곧 문자열 변수가 된다. 이 문자열 변수는 문자열의 끝에 널(NULL) 문자인 ‘\0’를 포함해줘야 한다.

1
2
3
4
5
const int SIZE = 10;
char address[SIZE];

cin >> address;
cout << address;

위 코드에서 cin 객체는 띄어쓰기, 탭, 캐리지 리턴 문자를 모두 문자열의 끝으로 인식하기 때문에 띄어쓰기가 들어간 문자열을 입력할 수 없다. 띄어쓰기를 포함한 문자열을 입력받기 위해서는 cin 객체의 get() 메소드를 사용해야 한다.

1
2
3
4
5
const int SIZE = 10;
char address[SIZE];

cin.get(address, SIZE).get();
cout << address;

또한 배열의 크기를 초과하는 문자열을 입력하면 프로그램이 강제 종료되기 때문에 cin 객체의 ignore() 메소드를 사용해 원하는 크기의 입력만 받게 할 수 있다.

1
2
3
4
5
const int SIZE = 10;
char address[SIZE];

cin.get(address, SIZE).ignore(SIZE, '\n');
cout << address;

string 클래스

string 메소드


7. 구조체

구조체 기본

구조체 활용

공용체와 열거체


8. 함수

함수의 정의

인수 전달 방법

재귀 호출

함수 포인터

참조자

디폴트 인수

함수 오버로딩

인라인 함수