[C++] 열혈 프로그래밍

Joonseo·2023년 10월 5일

1.1 이름공간(namespace)의 사용

  • 'namespace' 키워드를 통해 고유의 'field'를 생성하는 개념이다.
    예를 들어 영어영문학과 김소연과 외교학과 김소연이 같은 술집에 있다고 해보자. 술집에서 김소연을 부르면 아마 둘다 쳐다보게 될 것이다.

  • 따라서 우리는 코드 상에서 이런 데이터 충돌을 방지하기 위해 'namespace' 키워드를 사용한다.

namespace English{
    char Kim[1000] = "Department of English Language and Literature";
}

namespace Diplomacy{
    char Kim[1000] = "Department of Diplomacy";
}
  • 그럼 어떻게 English와 Diplomacy 안에 정의되어있는 Property에 접근할 수 있을까? 여기서 필요한 것이 바로 범위지정연산자 ' :: '이다.

  • 그런데 이 키워드를 처음 본 것이 아닐 것이다. 우리는 입출력을 위해
    std::cout, std::endl와 같은 키워드를 사용하였다.

std::cout<<"contents"<<std::endl;
  • 해당 Line을 해석하면 'std'라는 이름공간에 있는 'cout' 프로퍼티를 사용해 "contents"를 출력하고 다시 한번 'std'라는 이름공간의 'endl' 프로퍼티를 이용해 다음 줄로 넘어가겠다. 정도로 해석할 수 있다.
 std::cout<<English::Kim;   
  • 따라서 우리는 해당 Line으로 'English'(영어영문학과)의 'Kim'(김소희)를 호출 할 수 있다.

1.2 Using 키워드 사용

  • 우리는 'cout'이나 'endl'를 호출하고 싶을 때 항상 'std ::'를 통해 호출하였다. 하지만 이러한 번거로움을 없애기 위해 c++에서는 using 키워드를 사용한다.
using namespace std;
  • 해당 라인을 최상단에 선언함으로써 앞으로의 std:: 키워드 호출을 생략할 수 있다.
#include <iostream>
using namespace std;
using namespace English;

namespace English{
    char Kim[1000] = "Department of English Language and Literature";
}

namespace Diplomacy{
    char Kim[1000] = "Department of Diplomacy";
}

int main(void){
    cout<<Kim<<endl;
}
  • main 함수에서 호출된 Kim은 English의 Kim일 것이다.
    (Diplomacy의 kim은 using 키워드로 선언되어있지 않기 때문에 여전히
    'Diplomacy::kim'으로 접근해야되기 때문.)

2.1 참조자(Reference)

  • 참조자(Reference)는 C++을 처음 공부하는 나한테 굉장히 생소한 키워드였다. 그 성격은 C의 포인터와 유사하지만 그 쓰임이나 활용 형태가 달라 집중해서 보아야했다.

  • 참조자는 보통 '별명'이라고 많이 부른다. 성격과 성질은 그대로인 또 다른 이름인 것이다. 사과를 apple이라고 부르는 것 처럼.

#include <iostream>
using namespace std;

int main(void){
    int val = 10;
    int &val2 = val; // (타입)(참조자_이름) = (참조할 변수);

    cout<<val<<endl;
    cout<<val2<<endl;

    return 0;
}
  • 값만 참조하고 있는 것이 아닐까? 라는 생각이 들 수 있다. 그럼 과연 값만 복사했는지 아니면 주소까지 같은지 확인해보자. 아래 코드를 추가해서 다시 실행시켜보면
    cout<<"val의 값: "<<val<<endl;
    cout<<"val2의 값: "<<val2<<endl;

    cout<<"val의 주소: "<<&val<<endl;
    cout<<"val2의 주소: "<<&val2<<endl;
val의 값: 10
val2의 값: 10
val의 주소: 0x61ff08
val2의 주소: 0x61ff08
  • 정말 값만 복사된 것이 아니라 변수의 메모리상의 위치+값까지 똑같이 복사가 되었다. 복사가 되었다기보단 그 메모리공간을 부르는 이름이 하나 더 생겼다고 보는게 좋다.

2.2 참조자(Reference)와 포인터(Pointer)

  • 자주 쓰이는 문법인지는 잘 모르겠지만 복잡한 연산을 이해하고 넘어가는 것이 좋을 것 같아 정리해본다.
#include <iostream>
using namespace std;

int main(void){
    int num = 20;
    int *ptr = &num;
    int**dptr = &ptr; 

    // 변수 'num'의 주소값은 포인터 ptr에 저장, 
    // 포인터'ptr'의 주소는 더블포인터 dptr에 저장 

    int &ref = num;
    int *(&pref) = ptr; 
    int **(&dpref) = dptr;  

    /**
     * 변수 'num'의 또다른 이름 참조자 'ref' 
     * 
     * 참조자의 주소를 담는 참조자 포인터 pref에 num의
     * 위치를 저장하고 있는 ptr 대입 -> ptr = pref
     * 
     * 참조자의 주소가 담겨있는 그 주소를 참조하는 참조자 
     * 더블포인터 dpref에 ptr의 위치를 저장하고 
     * 있는 dptr대입 -> dptr = dpref
     * 
    */

    cout<<ref<<endl;
    cout<<*pref<<endl;
    cout<<**dpref;

    return 0;
}
  • 복잡하긴 하지만 천천히 차례대로 보면 이해가 될 것이니 자세한 설명은 생략..

2.3 참조자와 함수

  1. 함수의 매개변수로 호출되는 경우
#include <iostream>
using namespace std;

void swap(int &ref1, int &ref2){
    int temp = ref1;
    ref1 = ref2;
    ref2 = temp;
}

int main(void){
    int num1 = 10;
    int num2 = 20;

    swap(num1, num2);

    cout<<"num1 = "<<num1<<endl;
    cout<<"num2 = "<<num2<<endl;

    return 0;
}
  • 처음 이 코드를 보았을 때 의문이 들었던건 swap 함수는 참조자를 매개변수로 하는 함수인데 main함수에서 왜 인자로 넘겨줄때 일반 정수변수를 넘겨줄까?였다.

  • 참조자를 매개변수로 하는 함수에 저렇게 일반 변수를 던져주게 되면 그 동시에 ref1과 ref2는 각각 num1, num2의 참조자로 초기화 된다.

  • 그렇다면 다른 변수로 또 swap함수를 호출하면 어떻게 될까가 궁금했다.
    다른 두 변수에 같은 참조자가 붙는건 말이 안되기 때문이다. 한 포인터가 두 개의 property를 참조할 수 없는 것 처럼.

#include <iostream>
using namespace std;

void swap(int &ref1, int &ref2){
    int temp = ref1;
    ref1 = ref2;
    ref2 = temp;
}

int main(void){
    int num1 = 10;
    int num2 = 20;

    swap(num1, num2);
    
    cout<<"num1 = "<<num1<<endl;
    cout<<"num2 = "<<num2<<endl;

    int num3 = 99;
    int num4 = 100;

    swap(num3, num4);

    cout<<"num3 = "<<num3<<endl;
    cout<<"num4 = "<<num4<<endl;

    return 0;
}
  • swap 함수에 breakpoint를 걸고 디버그해서 ref1과 ref2를 관찰해봤는데 num1,2,3,4 전부 값 교환이 정상적으로 이루어졌다.
    따라서 매개변수로 참조자가 있으면 인자를 던져줄 때 해당 인자의 참조자가 된다기보단 일시적으로 참조자가 된다는 표현이 맞는 것 같다.

  • 포인터를 사용하지 않고 call-by-reference를 구현할 수 있는 방법?이 적당한 표현인 것 같다.

2.4 반환형이 참조자인 경우

#include <iostream>
using namespace std;

int& incNum(int &ref){
    ref ++;
    return ref;
}

int main(void){
    int num1 = 10;
    int &num2 = incNum(num1);

    num1 += 2;
    num2 += 3;

    cout<<"num1 = "<<num1<<endl;
    cout<<"num2 = "<<num2<<endl;

    return 0; 
}
int &num2 = incNum(num1);

  • 실행결과를 보면 유추할 수 있듯이 num2는 num1의 참조자가 되었다.
profile
🧑🏻‍💻

0개의 댓글