시리즈:쉽게 배우는 프로그래밍 입문/C++

지브릴리 (토론 | 기여)님의 2015년 5월 23일 (토) 21:58 판 (→‎준비)

문서의 내용이 너무 쉬워서 오늘부터 프로그래밍 할 수 있을 것 같습니다.

이 문서에는 독자적으로 연구한 내용이 들어갑니다. 다른 사람의 의견을 존중하면서 무례하지 않도록 작성해 주시고, 의견 충돌 시 토론 문서에서 토론해 주세요.

이 언어를 배우려는 사람에게 전하는 주의사항

만약 C 문법을 잘 모르는 경우 반드시 쉽게 배우는 프로그래밍 입문/C 참고.

C 문법을 그대로 사용하는 경우

물론 프로그래밍을 할 수는 있다. 그렇지만, C의 문법보다 형변환[1] 조건이 빡빡하다던가, C++의 많은 기능을 사용할 수 없다던가 하는 점이 있기 때문에, 정말 C로 개발해야만 하는 상황(거의 없겠지만)이 아니라면 C++의 문법을 사용하는 것이 좋다.

객체지향은 또 뭐야?

쉽게 말해, 모든 대상을 "데이터"와 "기능"으로 추상화해서 나타낸 것이다. 기존의 C에서 사용하던 구조체(struct)는 말 그대로 단순히 데이터를 담는 "자료형"의 역할을 했지만, 클래스는 어떤 대상을 데이터와 기능의 집합으로 나타내므로 그 대상의 성질, 특성과 같은 것들을 표현할 수 있다. 예를 들어서 휴대폰을 프로그래밍한다고 하자(하드웨어 같은 건 신경 끄고). 보통 기능을 늘어놓는다면(구조적 프로그래밍) 전화기능이라던가, 문자기능 등을 써넣을 것이다. 객체지향으로 프로그래밍을 한다면 휴대폰의 화면, 버튼, 카메라, 배터리, CPU 등을 (객체로) 만든 뒤, 이것들을 유기적으로 연결해서 휴대폰이란 프로그래밍을 만든다. 이렇게 현실과 비슷하게 구성하기 때문에, 프로그래밍계의 혁명이라 카더라.

쓸데없이 불친절하고 어려운 템플릿

템플릿은 메타프로그래밍 요소로, 서로 다른 여러가지의 클래스에 대해 각각을 따로 프로그래밍하지 않고 같은 이름을 가지는 대상(클래스, 함수)으로 프로그래밍할 수 있게 한다. 다시 말해서, 일반화 프로그래밍이 가능하다! 하지만, C++의 템플릿은 다른 여타 언어들의 메타프로그래밍 요소와는 달리 템플릿 메타프로그래밍이라는 변태짓을 가능하게 한다.나쁜 녀석들 같으니라고 구글링을 해도 답이 안나온다 이 기법은 C++ 프로그래머 중에서도 정말 고수들만이 제대로 활용할 수 있으므로, 공부하려고 마음먹었을 때 각오하는 것이 좋다.하지만 우리 회사는 못하게 하는데 그럴때는 실력에 대한 신뢰감을 줘야..

함수형 프로그래밍은 또 어디에서 굴러들어온건데?

시간이 지나면서, 프로그래머들은 객체지향의 한계에 봉착하게 된다. 대세는 멀티코어! 다들컴맹 빼고 들어봤을 것이다. 멀티코어 프로그래밍은 서로 다른 프로세스가 하나의 데이터에 동시에 접근하는 경우가 비일비재하기 때문에[2] 접근 통제를 위해 프로세스들을 락/언락해야만 한다. 그런데 락/언락을 자주 하게 되면 CPU에서 병목현상이 발생하기 때문에 프로그램의 실행시간, 반응 속도 등에 영향을 끼치게 된다.

이 멀티코어 프로그래밍을 위한 대안이 바로 함수형 프로그래밍이다. 함수형 프로그래밍에 사용되는 함수는 수학에서의 함수의 성질을 따른다. 기존의 함수는 모든 작업을 순차적으로 처리해야만 했지만, 함수형 프로그래밍에서는 작업 순서를 변경시켜서 어떤 것을 먼저 처리하고 어떤 것을 지연시켜도 같은 결과가 나오는 것이다!

더럽게 기능이 많은데, 이 많은 걸 언제 배워!

그래서 C++의 진입장벽이 높은거다. 현재진행형으로 언어 표준을 개정해나가면서 짧은 시간에 많은 기능이 추가되거나, 바뀌거나, 사라지고 있다. 하지만, "C++스러운" 프로그래밍 방법은 표준을 개정해나가도 크게 변하지 않는다. 표준은 "C++스러운" 방법을 위한 편의 기능을 제공하는 방향으로 개정되고 있다고 생각해도 된다. 그러니, "C++스러운" 방법이 무엇인지 안다면, 앞으로 계속 표준이 바뀌더라도 학습이 용이할 것이다.

많은 내용들 중 중심적인 내용을 먼저 학습한 후, 곁가지로 제공되는 기능을 익혀나가는 방법은 C++을 공부하는 좋은 방법 중 하나이다. C를 빠르게 공부한 후, C++의 중심적 내용인 클래스를 공부하면 나머지는 다 부가기능처럼 보일것이다.

배워두면 좋은 점

  1. 일단 언어 자체가 매우 강력하다.
    위에서 기능이 많아서 배우기 어렵다고 했는데, 그 말은 다시 돌려 말하면 그만큼 많은 기능을 사용할 수 있고, 사용 여하에 따라서는 매우 강력한 도구가 될 수 있단 뜻이다. 특히 최근 대세인 Java보다 매우 빠르며[3], 프로그램 실행 속도는 C와 거의 다를 바가 없을 수준[4]이다.
  2. 고급 언어의 특성을 많이 가지고 있으면서(추상화 등), 중간 언어(C)의 기능을 다 사용할 수 있다.
    고급 언어는 프로그램 설계를 쉽게 해준다. 특히 C++은 고급 언어들 중에서도 일부에서만 지원하는 몇몇 강력한 기능(연산자 오버라이딩) 등을 지원한다. 하지만 많은 고급 언어들은 오류 방지나 무결성 등을 위해 메모리 직접 접근을 제한하는 등 직접 프로그래머가 건드릴 수 있는 부분을 제한함으로서 안정성을 얻으려 했다. 반면, C++은 그런거 없다. 사용할 수 있는 부분은 C에서 크게 다르지 않은, 이걸로 OS를 구축할 수 있을 정도의 언어이다. 그런 부분을 버린 만큼 조심히 다뤄야하지만, 조심하게 다루면서 사용하게 되면 고급 언어들에서는 사용할 수 없는, 중간 언어들에서 사용하던 유용한 트릭들을 사용할 수 있다.
  3. 하는 사람이 생각보다 많이 없다!
    불과 한 10년전만 하더라도 C++ 프로그래머가 거의 대부분을 차지했지만, 최근 Java에 대한 지속적 투자와 임베디드화 경향이 맞물리면서 Java가 득세하는 바람에, Java 프로그래머는 넘쳐나는데 C++ 프로그래머는 수가 많이 줄었다.

준비

윈도에서라면 Visual Studio 커뮤니티 2013를 사용해보자.

개인 사용자들에게는 무료로 공개되어있는 버전이다. 참고로 이거 하나면 C++ 뿐만이 아니라 C#, VB.NET 개발도 가능하다. 학생의 경우 Dreamspark 프로그램을 통해 Professional 등의 버전을 사용할 수도 있다.

최신 표준의 기능들을 사용하고 싶다면 컴파일러를 LLVM로 바꾸면 된다.

리눅스 유저라면 eclipse를 이용하면 된다. 그리고 이클립스에서 C++을 코딩할 수 있도록 g++과 eclipse-cdt를 터미널 혹은 시냅틱에서 설치하자. IDE 따위 필요없는 변태라면, g++하나만 있어도 된다.

후술된 내용들을 보기 전에 위의 C 프로그래밍 내용을 숙지하자. C++은 기본적으로 C에서 기원했다.

예외란 없다Hello, World!

빈 프로젝트를 생성한 후 소스(.cpp)파일에 다음 내용을 작성한다.

#include <iostream>

int main()
{
    std::cout << "Hello, World!" << std::endl;
}

새 표준 입출력

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)[5]에서 사용하기 위해 만들어낸 개념이라는 것을 알 수 있다. 포인터가 있는데 굳이 레퍼런스를 만든 이유는, 포인터 자체가 가진 특수성 때문이다. 포인터는 메모리에 직접 접근하는 것이나, 프로그래머의 실력이 출중하지 않은 이상은 포인터 오류에 걸리기가 매우 쉽고, 또 그 사용법상 여러가지 문제(예를 들어 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++에서 확장된 공용체를 쓸 일은 거의 없다.

열거형은 열거 클래스라는 기존 열거형을 확장한 형태를 사용할 수 있게 되었다.

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

클래스

형태는 다음과 같다.

class [클래스 이름]
{
[접근지정자]:
...// 멤버
[접근지정자]:
...// 멤버
};

용어

접근지정자

const 멤버

static 멤버

생성자

일반 생성자

복사 생성자

이동 생성자

소멸자

연산자 오버로딩

상속

명시적 상속

암시적 상속

다중상속

동적 생성

예외처리

원리

사용법

템플릿

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

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

템플릿 함수

템플릿 클래스

람다

람다의 전신, 함수자

람다와 함수 포인터

람다의 사용법

고급 과정

각주

  1. 간단히 설명하자면, 문자를 정수로 바꾸는 등의 한 데이터 타입을 다른 타입으로 변환하는 것을 말한다.
  2. 이렇게 되면 프로그램을 여러번 실행시켰을 때 같은 조건에서도 항상 같은 결과가 나온다고 기대할 수 없다.
  3. 물론 완벽하게 빠른건 아니나, 일반적인 경우에는 Java보다 매우 빠르다.
  4. 대신 C에 비해 문법적으로 복잡하기 때문에 컴파일에 걸리는 시간은 일반적으로 훨씬 길다. 때문에 실무에서는 컴파일 타임을 줄이는 몇가지 방안을 사용한다.
  5. 주소를 저장하는 변수의 종류인 포인터, 레퍼런스를 이용해서 그 주소에 위치한 값을 호출하는 방식, 값에 의한 호출(Call-by-Value)와는 반대개념

틀:쉽게 배우는 프로그래밍 입문