시리즈:중급 프로그래밍 시리즈/C++/문법

새 표준 입출력[편집 | 원본 편집]

iostream을 인클루드하고 사용한다. 표준 라이브러리에서는 더 이상 C에서 쓰이던 확장자 h를 쓰지 않는다. C++ 라이브러리 헤더는 그대로 확장자 없이 쓰면 되며, C 헤더의 경우는 cstdio처럼 앞에 c를 붙인채 사용하면 된다. 이름 공간은 std.

표준 출력[편집 | 원본 편집]

std::cout << [기본 자료형 변수/상수];

기본적으로 이 형식으로 사용한다. 기본 자료형이 아닌 구조체, 클래스 등은 후술할 오버로딩 참고.

std::cout << [출력 대상] << [출력 대상] << ...

이렇게 여러 대상의 출력을 한 줄에 작성할 수 있다.

std::cout << ... << std::endl;

이렇게 작성하면 중간의 출력대상을 모두 출력한 후 한 줄을 강제 개행한다.

표준 입력[편집 | 원본 편집]

상수[편집 | 원본 편집]

기존의 const에 더해, volatile과 mutable 키워드를 사용할 수 있게 되었다. mutable 키워드의 경우 후술할 클래스 부분을 참고.

volatile 키워드를 사용한 변수는 그 값이 언제든지 바뀔 수 있다는 것을 컴파일러에게 명시적으로 전달해주기 때문에, 최적화를 방해한다. goto 키워드처럼 쓰면 방해되는 키워드이기 때문에 이런 키워드는 없다고 생각하고 프로그래밍하자.

좌측값, 우측값[편집 | 원본 편집]

C++11이 제정되며 강조되는 개념이다. 절대로 없던게 아니다! C부터 있던 유서깊은(?) 개념이다.

좌측값(L-Value)는 변수나 변수의 주소 등 확실하게 메모리에 위치한 주소를 가진 변수이다. C에서 사용되던 일반적인 변수를 떠올리면 된다. 반면, 우측값(R-Value)는 그 외의 모든 임시변수메모리에 위치하지 않은 것들인 상수를 포함한다. 임시변수에 대한 자세한 개념은 추가바람. 더욱 엄밀하게 말하자면, 좌측값은 식 한 줄이 실행된 후에도 남아 있는 값이고, 우측값은 식 한 줄이 실행되고 나면 소멸하는 값이다.

사실, 좌측값, 우측값이란 이름은 대입 연산자를 기준으로 올 수 있는 방향에 따라 지어졌다. 좌측값은 대입 연산자 왼쪽, 오른쪽 다 올 수 있지만, 우측값은 오른쪽에만 올 수 있다. 따라서, 대입 연산자 왼쪽에는 좌측값만 올 수 있다. 또한, 좌측값은 메모리상에 위치한 주소가 정해져 있으므로 변수로 선언할 수 있지만, 우측값은 그러지 못한다. 그러나, 함수의 인자로 오거나 반환값으로 오는 것은 가능하다. 앞으로 후술할 템플릿에도 사용할 수 있다.

이 개념을 직접 언어적 측면에서 도입함으로써, C++은 임시변수의 생성을 위한 메모리와 시간을 절약할 수 있게 되었다.

레퍼런스[편집 | 원본 편집]

대놓고 이름만 봐도 참조에 의한 호출(Call-by-Reference)[1]에서 사용하기 위해 만들어낸 개념이라는 것을 알 수 있다. 포인터가 있는데 굳이 레퍼런스를 만든 이유는, 포인터 자체가 가진 특수성 때문이다. 포인터는 메모리에 직접 접근하는 것이나, 프로그래머의 실력이 출중하지 않은 이상은 포인터 오류에 걸리기가 매우 쉽고, 또 그 사용법상 여러 가지 문제(예를 들어 Dangling pointer) 등을 초래할 수 있다. 때문에 현대 프로그래밍 언어들에서는 대부분 이러한 방식을 사용하지 않고, 레퍼런스 방식으로 안전한 접근을 유도하는데, C++에서도 포인터 접근 대신에 안전한 레퍼런스 접근(내부 구현은 포인터와 비슷하다)을 제공한 것이다.

사용법을 포인터와 비교하면 다음과 같다.

int q;
int *p;// 포인터, 가능
int &p;// 레퍼런스, 불가능, 선언시 초기화해야 함
int *p = &q;// 포인터, 가능
int &p = q;// 레퍼런스, 가능

가장 많이 쓰이는, 함수에서의 사용 예는 다음과 같다.

int f(const int n);// 임시값, 반환도 임시값
int f(const int& n);// 좌측값, 반환은 임시값
int f(const int&& n);// 우측값, 반환은 임시값

int& f(const int n);// 임시값, 반환은 좌측값
int& f(const int& n);// 좌측값, 반환도 좌측값
int& f(const int&& n);// 우측값, 반환은 좌측값

int&& f(const int n);// 임시값, 반환은 우측값
int&& f(const int& n);// 좌측값, 반환은 우측값
int&& f(const int&& n);// 우측값, 반환도 우측값

이름공간[편집 | 원본 편집]

그동안 사용하던 static 키워드에서 탈피할 수 있게 해주는 표준 문법이다. static은 해당 파일에서만 전역인 것처럼 사용할 수 있게 하였지만, 이름공간은 무려, 인클루드만 해주면 어디에서나 사용할 수 있다! 심지어 변수, 함수, 클래스 등을 용도별로 모을 수도 있다. 거기에 확장도 가능하니 금상첨화.

사실 이름 공간은 그런 목적으로 고안된 것이다. 라이브러리들이 많아지고, 이 라이브러리들 사이에서 이름이 겹치는 경우가 발생할 수 있다보니, 아무리 오버로딩을 지원하는 C++이지만 한계가 있을 수 있고, 그래서 라이브러리별로 각자 다른 이름공간을 주어 구성할 수 있게 한 개념이다. 대조되는 개념으로 Java의 package 등이 있다.

가장 대표적인 이름공간으로는 C++ 표준 라이브러리의 이름공간인 std가 있다.

용법은 다음과 같다.

namespace A{  // 여기까지가 namespace 시작부분
}  // 여기만 namespace 종결부분

namespace B{
    int V;
    int func() { }
    class C;
    enum E {};
}
B::V = 1;  // 접근자([이름공간명]::[멤버명])를 사용한 내부 멤버 접근
using B::func; // 내부 멤버를 현재 범위 내에서 접근자를 사용하지 않고 접근 가능
using namespace B; // 내부 멤버 전체를 현재 범위 내에서 개방

namespace{ }  // 이름이 없는 경우, 전역취급. 접근자는 이름이 없으므로 ::[멤버명]

공용체, 열거형[편집 | 원본 편집]

공용체의 경우 클래스와 비슷하게 생성자와 소멸자를 사용할 수 있게 되었고, 이름없는 공용체도 만들 수 있다. 다만, C 수준에서 공용체를 다룰 기회가 좀 있었던 것에 비하면, C++의 경우 대부분 low-level 부분에 C 라이브러리를 쓰는 경우가 많아 C++에서 확장된 공용체를 쓸 일은 거의 없다.

C++11에서 열거형은 열거 클래스라는 기존 열거형을 확장한 형태를 사용할 수 있게 되었다. Int로 자동 형변환이 되지 않는다는 특징이 있다.

enum class A : int
{
    FIRST = 1,
    SECOND = 2
};

클래스[편집 | 원본 편집]

C언어의 구조체(struct)를 기억하는가? C언어의 구조체는 다음과 같은 구조이다.

struct Time
{
	int minute;
	int second;
};

즉 구조체 안에는 변수를 선언할 수 있다. 그리고 우리는 구조체를 사용해 이런 함수를 사용한다.

void printtime(struct A& b)
{
std::cout<<Time.minute<<Time.second<<std::endl;
}

그런데 어차피 구조체와 밀접한 관련이 있는 함수, 합치는게 좋지 않을까? 그래서 C++에서는 struct에 변수 뿐만 아니라 함수의 선언도 허용하고 있다. 즉 다음과 같은 코딩이 가능하다.


#include<iostream>

struct Time
{
	int minute;
	int second;
	void printtime(){
		std::cout<<minute<<second<<std::endl;
	}
};

int main()
{
	Tine a={1, 30};
	a.printtime();
	return 0;
}

이 코드를 자세히 보면 struct 내부에 함수의 선언이 가능할 뿐만 아니라, 구조체 자료형의 선언 방법도 C와 다르다. C에서라면 struct Time a라고 선언했겠지만, C++에서는 Time a라고 선언하는 것을 허용한다. 또한 struct Time 내부의 함수인 func는 main 함수에서 마치 구조체 내부의 변수처럼 a 뒤에 점(.)만 찍으면 접근할 수 있다. 이것이 C++에서 struct의 새로운 기능이다. 또한 여기서 a와 같은 것들을 객체(오브젝트)라고 부른다. 그런데 실수를 해서

a.second=600;

그래서 c++에서는 '접근지정자'라는 것을 struct 안에 넣을 수 있다. 다음 코드를 보자.


#include<iostream>

struct Time
{
	int minute;
	int second;
	void printtime(){
		std::cout<<minute<<second<<std::endl;
	}
};

int main()
{
	Time a={1, 2};
        /*
        a.x=600;
        */
        //↑ 컴파일 에러 발생
	a.printtime();
	return 0;
}

이 코드에서는 a.second에 0을 대입하면 컴파일 오류가 뜨는데, private는 아래에 선언된 변수와 함수들이 오직 같은 struct 내부에서만 접근 가능하도록 한다. 따라서 printtime함수는 x와 y에 접근할 수 있지만 이 struct 밖에 있는 main 함수에서는 x에 접근할 수 없는 것이다. 즉, 원하지 않는 데이터 번경을 막아 줄 수 있으므로 데이터 멤버는 private로 하는 것이 좋다. public은 아래에 선언된 변수와 함수들이 struct 밖에서도 접근 가능하도록 한다. 또한 private와 public 외에도 protected라는 것이 있는데, 이것은 상속을 배우고 나서야 이해되는 개념이니 일단은 더이상의 자세한 설명은 생략하겠다.

그리고 c++에서는 struct 대신에 class라고 쓸 수 있다. 즉 위의 코드를

#include<iostream>

struct Time
{
	int minute;
	int second;
	void printtime(){
		std::cout<<minute<<second<<std::endl;
	}
};

int main()
{
	Tine a={1, 30};
	a.printtime();
	return 0;
}

이렇게도 쓸 수 있는 것이다. 단지 struct를 class로 교체한 것 뿐이지만, 두 코드는 완벽하게 기능이 똑같다. 물론 struct와 class에도 기능의 차이는 있는데, struct는 기본으로 설정된 접근지정자가 public이지만 class는 private이다.

struct Time
{
	
};

위의 코드는

struct Time
{
public:
int minute;
	int second;
};


class Time
{
public:
int minute;
	int second;
};

이 두 코드와 완전하게 같은 기능을 가지며,

class Time
{
	int second;
	int minute;
};

위의 코드는

class Time
{
private:
	int second;
	int minute;
};


struct Time
{
private:
	int second;
	int minute;
};

이 두 코드와 완벽하게 같은 기능을 제공한다. 차이는 오직 이것 뿐이다.


용어[편집 | 원본 편집]

접근지정자[편집 | 원본 편집]

접근 지정자에는 private, public, 그리고 protected가 있습니다. protected 접근지정자는 상속 부분에서 설명드리겠습니다.

public

말 그대로 공개되어 있기 때문에, 클래스 밖에서도 마음껏 접근할 수 있습니다.

class Time
{
public:
int minute;
int second;
int hour;
}

즉 이렇게 클래스를 만든다면

Time a;
a.second=600;
a.minute=80;
a.hour=-68;

이렇게 외부에서 마음껏 조작할 수 있기 때문에 데이터 멤버는 public 지정자로 공개하면 안 됩니다.

public 지정자는 외부에 공개해야하는 멤버 함수에 주로 사용합니다.

private

클래스 내부가 아니라면 접근이 불가능합니다.

class Time
{
public:
    int settime(int h, int m, int s)
    {
        if (minute < 0 || minute >= 60)
        {
            return -1;
        }
        if (second < 0 || minute >= 60)
        {
            return -1;
        }
        if (hour < 0)
        {
            return -1;
        }

        minute = m;
        second = s;
        hour = h;
        return 0;
    }

private:
    int minute;
    int second;
    int hour;
}

const 멤버[편집 | 원본 편집]

static 멤버[편집 | 원본 편집]

생성자[편집 | 원본 편집]

이 문서에서는 C++의 문법을 다룬다.

일반 생성자[편집 | 원본 편집]

복사 생성자[편집 | 원본 편집]

이동 생성자[편집 | 원본 편집]

소멸자[편집 | 원본 편집]

연산자 오버로딩[편집 | 원본 편집]

상속[편집 | 원본 편집]

명시적 상속[편집 | 원본 편집]

암시적 상속[편집 | 원본 편집]

다중상속[편집 | 원본 편집]

동적 생성[편집 | 원본 편집]

예외처리[편집 | 원본 편집]

원리[편집 | 원본 편집]

사용법[편집 | 원본 편집]

템플릿[편집 | 원본 편집]

컴파일 타임을 잡아먹는 주범

일반화 프로그래밍을 다룬다. 템플릿 메타프로그래밍은 해당 항목 참조.

템플릿 함수[편집 | 원본 편집]

템플릿 클래스[편집 | 원본 편집]

람다[편집 | 원본 편집]

람다의 전신, 함수자[편집 | 원본 편집]

람다와 함수 포인터[편집 | 원본 편집]

람다의 사용법[편집 | 원본 편집]

참고 자료[편집 | 원본 편집]

https://msdn.microsoft.com/en-us/library/3bstk3k5.aspx

  1. 주소를 저장하는 변수의 종류인 포인터, 레퍼런스를 이용해서 그 주소에 위치한 값을 호출하는 방식, 값에 의한 호출(Call-by-Value)와는 반대개념