본문 바로가기
공부/Programming Language

C++ 1일차

by Piva 2021. 4. 28.

  쇠뿔도 단 김에 빼랬다.

(※ 개인 정리를 위한 것이므로, 내용 생략다수 + 불친절함 주의)

오늘의 진도

 


 

1장

  C++은 개인적으로는 흥미로운 언어다(배우는 것이 즐거운 것과는 별개로). C언어와 생긴 것은 비슷하면서도 C가 갖고 있지 않은 '객체 지향'이라는 특징을 갖고 있다는 점이 무엇보다 그러하다. 부족하다만 어쨌든 C를 해보았기에 C++를 처음 잡았을 때의 느낌은 <5년만에 돌아온 고향이 환골탈태 되어있었다> 정도의 기분이었던 것 같다. 무언가... 새롭다. 많다.

 

 

Visual Studio 2019에서 C++사용

  C언어를 처음 배울 때도 Visual Studio를 사용하여 학습을 진행했었는데, 이번에도 그렇게 진행한다. 다만 내 Visual Studio는 얼마 전에 새로이 만난 2019버전이고, 2019버전에서 C++ 프로그래밍을 진행하기 위해서는 별도의 설치가 필요하다.

 

설치 관리자

  Visual Studio를 처음 설치한다면 설치 중 나오는 위의 창에서 바로 C++ 개발 워크로드를 설치하면 되지만, 나처럼 이미 Visual Studio가 깔려있다면 [도구]→[도구 및 기능 가져오기]에서 위의 창을 만날 수 있다.

 

  C++ 실습을 위해 [콘솔 앱]을 선택하여 새 프로젝트를 만든다. 기본적으로 "Hello World"가 적혀 있는 새 프로젝트가 만들어진다.

 

 

2장

 

#include <iostream>

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

  첫 프로그램의 짜임새는 대충 이러하다. main함수의 생김새도 그렇고, 출력문이 조금 생소하다는 것 이외에는 사실 C언어와의 차이는 보이지 않는다. 조금 눈여겨보아야 할 곳만 대충 적자면,

 

  • include 문 : C언어에서와 똑같이 헤더파일을 가져와 사용하기 위해 사용된다. 여기서 <iostream>이란, C++에서 표준 입출력을 수행하기 위해 필요한 것. C언어와의 또다른 차이점은 ".h"가 붙지 않는다는 것이다.
  • std::cout / std::endl : 본 코드에서는 "Hello, World!"를 출력하기 위해 사용된다. 자세한 내용은 후술.

 

Namespace(이름공간)

  책에서도 그렇고 자료에서도 그렇고, 이름공간은 대체로 이름의 '성씨'에 자주 비유되는 듯 하다. 그리고 이는 상당히 잘 맞는 표현이라고 생각한다. 

  

  이름공간, 네임 스페이스는 어느 정의된 객체에 대해서 '어디에 소속되어 있는지'를 지정해주는 역할을 한다. 세상에 '현빈'이라는 이름을 가진 연예인은 둘이나 있지만, '성이 박 씨인 쪽'이라고 분명히 언급하면 누구를 말하는지 금방 구분할 수 있듯, 네임 스페이스는 아무리 이름이 같아도 소속된 곳이 다르면 별개로 취급할 수 있게 해주는 역할을 한다.

 

std::cout << "Hello World" << std::endl;

  해당 코드에서는 'std'가 그 역할을 하고 있는 것이다. 이를 생략하면 오류가 나기 때문에, 네임 스페이스를 분명히 기재해주어야 문제 없이 컴파일이 될 수 있다. 

 

  그렇다면 서로 다른 네임 스페이스 안의 같은 이름을 가진 것들을 어떻게 불러서 쓸 수 있냐하면, 사람 이름을 부르는 것만큼이나 간단하다. 성(네임 스페이스)를 붙여주면 된다.

 

//서로 다른 헤더 파일에 있다는 가정 하에
namespace Kim {
	int foo();
    char bar();
}

namespace Park {
	int foo();
    char bar();
}

  다음과 같은 경우를 상정하면, foo와 bar함수를 구분하고 싶을 때 간단하게 Kim:: 과 Park::을 붙여서 구분하면 된다는 이야기다.

 

#include "Kim.h"
namespace Kim {
	int func() {
    	foo();
    }
}

//Park의 foo를 쓰고 싶을 경우
#include "Kim.h"
namespace Kim {
	int func() {
    	Park::foo();
    }
}

  Kim에서 Kim의 foo()를 사용하고 싶다면, 그냥 네임스페이스의 언급 없이 바로 사용하면 된다. (김씨 집안 안에서는 김씨를 부를 것이 당연하듯) 다만 Kim에서 Park의 foo()를 호출하고 싶다면, 언급이 필요하다. 그래서 Park:;이 붙게 된다.

 

#include "Kim.h"
#include "Park.h"

using Kim::foo;
int main() {
	foo(); //Kim의 foo()가 호출됨
}

//아예 Kim 속 전체를 별도의 언급없이 사용하고 싶을 경우
#include "Kim.h"
#include "Park.h"

using namespace Kim;
int main() {
	foo(); //Kim의 foo()가 호출됨
}

  일일이 네임 스페이스를 언급하는 것이 귀찮다면 'using'을 사용하여 별도의 명시 없이 사용할 네임 스페이스를 적어주면 된다. 물론 이 경우에도, 언급만 한다면 다른 네임 스페이스에 속해있는 함수들을 사용할 수 있다.

 

※ 다만 using의 사용에 있어서는 조심해야 하는데, 네임 스페이스의 특성상 서로 다른 네임 스페이스 속 함수들 간의 이름 중복이 다수 발생할 수 있으며, 이런 때에 using을 사용하게 되면 이들간의 오류가 발생할 가능성이 높아진다. 따라서 좀 귀찮더라도 네임 스페이스를 명시하여 사용하는 것이 오류를 줄이는 좋은 방안이 될 수 있을 것이다.

 

  추가로, 네임 스페이스는 '이름을 명시하지 않아도 된다'. 따라서 밑과 같은 경우도 가능하다.

namespace {
	int foo();
    char bar();
}

  이렇게 선언된 네임 스페이스는 static 을 사용한 것과 같은 효과를 내며, 해당 파일 안에서만 사용(접근)할 수 있게 된다.

 

 

기본 문법구조(생략)

: C언어와 거의 차이점이 없기 때문에 생략.

 

 

참조자(레퍼런스)

  C언어의 난이도를 급격하게 높이는 것은 언제나 포인터 였던 것 같다. 후배들도, 선배들도 C언어 학습에서 난이도가 높아지는 부분은 포인터라고 입을 모아 말했던 기억이 있고, 과제를 하면서도 포인터에 대해서는 늘 애매모호한 이해도를 갖고 임했던 기억이 있다. 

 

  C언어에서 '포인터'란 어떤 변수를 '가리키는' 변수로, 자신이 가리키는 변수의 주소값을 가지고 있으며 해당 변수를 포인터를 통해 접근하고 바꾸는 것이 가능했다. C++에서는 이러한 포인터와 비슷한 존재로 참조자(Reference)라는 것이 존재한다. 

 

  참조자의 정의는 어딘가 포인터를 연상시키면서 간단하다.

#include <iostream>

int main() {
	int p = 0;
    int &refP = p;
    
    refP = 10;
    
    std::cout << p << std::endl << refP << std::endl;
}
//Result :
//10
//10

  참조자는 포인터와 비슷하게 타입 뒤에 &를 붙여서 정의할 수 있다. 심지어는 포인터의 참조자를 만드는 것도 가능하다고 한다. ('int*&' 와 같은 형식이 된다)

 

  네임 스페이스를 성씨에 비유했다면, 참조자는 '별명'이라고 할 수 있다. 즉 위의 코드에서 refP는 p의 '별명'이 되는 것이며, refP를 향한 조작은 곧 p를 향한 조작이 된다.

 

레퍼런스와 포인터의 차이점

  레퍼런스와 포인터는 비슷해보이지만, 그 차이점이 분명히 존재한다.

 

1. 선언의 차이
  포인터는 처음 선언할 때 누구를 가리킬 것인지 먼저 언급하지 않아도 되었다. 다만, 레퍼런스는 선언 시 누구를 가리킬 것인지 명시해야 한다.
  즉, 다음과 같이 선언되어야 한다.

int * p; //이건 ok
int & q; //이건 아웃

//아래의 형식을 따라야 함.
int q;
int & refQ = q

2. 레퍼런스는 가리키는 대상을 바꿀 수 없다.

  포인터의 경우, 자신이 가리킬 대상을 얼마든지 바꿀 수 있다. 다만, 레퍼런스는 그것이 불가능하다.
(재치있게도, 저자분은 이를 '학창시절 별명이 평생 가는 것과 같다' 라고 언급했다.)

 

3. 레퍼런스는 메모리 상에 존재하지 않을 수 있다.

  레퍼런스는 별명이다. 그렇기에 굳이 메모리 상의 공간을 차지해야할 필요가 없을 때가 존재한다.

int a;
int & refA = a;

  이런 경우, 굳이 refA를 위한 공간을 따로 지정할 필요가 없다. refA는 어디까지나 a의 별명이므로, refA의 자리를 a로 써주면 된다. 물론 레퍼런스를 위한 공간이 아예 존재하지 않는 것은 아니다. 바로 함수의 인자로 레퍼런스가 올 때이다.

 

include <iostream>
int change_val(int &p) {
	p = 3;
	return 0;
}
int main() {
	int number = 5;
    std::cout << number << std::endl;
    change_val(number);
    std::cout << number << std::endl;
}

  예제 코드를 그대로 가져왔다. 위의 코드를 실행하면, 5였던 number 변수가 change_val에 의해 3으로 바뀌게 된다. change_val에 number를 넘겨주어 p가 number의 별명(참조자)이 되도록 하고, 함수 내에서 p의 값을 바꿈으로써 결과적으로는 number를 바꾸는 것이 된다. change_val함수에 number를 전달할 때, 포인터가 함수의 인자가 될 때와는 달리 number가 그대로 오는 것도 확인할 수 있다.

 

 

상수 참조자

  상수에 대한 참조자를 설정하는 것도 '일단은' 가능하다. 다만, 일반적으로는 안 되고 별도의 방법을 써야한다.

int main() {
	const int &ref = 4;
}

  위와 같은 식으로 const를 붙여 '상수 참조자'로 선언해야만 상수에 대한 참조자를 만드는 것이 가능하다.

(const.. 다시 공부해야 겠다 OTL)

 

 

레퍼런스 배열 & 배열 레퍼런스

  먼저 언급하고 넘어가자면, C++ 규정으로 '레퍼런스의 레퍼런스, 레퍼런스의 배열, 레퍼런스의 포인터는 존재할 수 없다'고 명시되어있다고 한다.

 

  이유는 C언어의 배열을 생각해보면 나온다. C언어에서 배열 속 원소에 접근하는 방법이 무엇이었던가. arr라는 이름의 배열이 있다고 가정하였을 때, arr[1]도 가능할 것이고, * (arr + 1)도 가능했었다. 배열의 이름 자체가 배열의 주소값으로 변환이 가능했기 때문이다. 즉, 주소값이 있기에 메모리 상에 공간이 할당된 상태라는 의미이다. 그러나 레퍼런스는, 메모리 상에 공간을 차지하지 않는 경우가 존재한다. 그렇기에 배열에 사용할 수 없는 것이다.

 

  다만, 배열의 레퍼런스는 구현이 가능하다.

#include <iostream>
int main() {
	int arr[3] = {1, 2, 3};
    int (&ref)[3] = arr;
    
    ref[0] = 2;
    ref[1] = 4;
    ref[2] = 6;
    
    return 0;
}

  예제 코드를 가져왔다. ref가 arr의 레퍼런스로 기능하고 있으며, ref를 통해 arr의 요소를 바꾸는 것을 확인할 수 있다. 대신 배열 레퍼런스를 선언하여 사용하기 위해서는 배열의 크기를 명시하는 과정이 필요하다.

 

 

함수 레퍼런스

  함수에서의 참조자를 사용할 때의 경우를 정리한다. 여기서는 간단히 표로 정리하고 넘어간다. (쓸 내용이 너무 많아서)

  함수에서 값 리턴(int f()) 함수에서 참조자 리턴(int& f())
값 타입으로 받음
(int a = f())
값 복사됨 값 복사됨.
(지역변수 레퍼런스를 리턴하지 않게 조심.)
참조자 타입으로 받음
(int & a = f())
컴파일 오류 가능
( " )
상수 참조자 타입으로 받음
(const int & a = f())
가능 가능
( " )

 

'공부 > Programming Language' 카테고리의 다른 글

C++ 3일차  (1) 2021.05.03
C++ 2일차  (0) 2021.04.30
C++ 공부  (0) 2021.04.28
Kotlin 기본 문법 - 2  (0) 2021.04.02
Kotlin 기본 문법 - 1  (0) 2021.03.23