Map

자료구조 중 하나인 Map은 키(key)와 값(value)를 쌍으로 갖는 STL입니다. Map의 특징 중 하나는 키 값이 중복되지 않는 다는 것입니다. C++에 있는 Map은 레드블랙트리로 이루어져있으며 검색, 삽입, 삭제가 O(log n)입니다.

코드를 보면서 어떻게 사용할 수 있는지 확인해보도록 하겠습니다. map을 사용하기 위해서는 아래와 같이 map 헤더파일을 include해주어야합니다.

#include <map>

 

1. 데이터 삽입, 조회, 변경

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
	map<string, string> m;
	m["seoul"] = "02";
	m["kyungki"] = "031";
	m["daegu"] = "051";
	m["incheon"] = "032";
	
	//맵의 모든 데이터를 순환
    //first, second를 보아 pair객체를 사용하는 것을 알 수 있다.
	for (auto iter : m) {
		cout << iter.first << "의 지역번호:" << iter.second << endl;
	}
	cout << endl;

	//[]로도 접근 가능
	cout <<"seoul:"<< m["seoul"] << endl;

	//변경
	m["daegu"] = "053";
	cout << "daegu:" << m["daegu"] << endl;

}

배열 인덱스를 다루듯이 사용할 수가 있습니다.

아래는 결과화면입니다.

 

그런데 한가지 유심히 보면 map의 키,값을 순회할때 키가 오름차순으로 나오고 있네요(daegu - incheon - kyungki - seoul).  내림차순으로 map을 구성하고 싶다면 아래와 같이 사용하세요.

map<string, string,greater<string>> m;

그리고 insert()함수를 통해서 삽입할 수도 있습니다. Map은 내부적으로 pair 객체를 이용하여 키와 값을 저장하는데요. first는 키, second는 값이 들어가게 됩니다.

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
	map<string, string> users;
	users.insert({ "010-3343-1111","reakwon" });
	users.insert({ "010-1133-0000","lee" });
	users.insert({ "010-1999-9991","huh" });
	users.insert({ "010-1192-1928","so" });
	users.insert(make_pair<string, string>("010-4444-4455", "kim"));
	
	for (auto it : users)
		cout << "name:" << it.second << ", phone:" << it.first << endl;

}

 

2. 키가 존재하는지 확인

find함수로 키가 존재하는지 확인할 수 있습니다. iterator로 내부적으로 순환하면서 비교하고 찾기 때문에 만약 키가 존재하지 않으면 iterator의 end()를 반환하게 됩니다.

	if (users.find("010-1133-0000") != users.end()) {
		cout << "키가 존재" << endl;
	}
	else {
		cout << "키가 존재하지 않음" << endl;
	}

 

3. 키-값 삭제

삭제 연산은 erase함수로 키과 값 쌍을 삭제할 수 있습니다. 

3-1. 입력된 키와 같은 pair를 삭제

users.erase("010-3343-1111");

 

3-2. 데이터 모두 삭제

users.erase() 혹은 clear() 함수로 데이터를 모두 삭제할 수 있습니다. 아래와 같이 사용합니다.

users.erase(users.begin(),users.end());
users.clear();

 

4. 저장된 키-값 수 

size() 함수를 사용하면 현재 map에 데이터가 저장된 수를 알 수 있습니다.

users.size()

 

Map을 이용한 문제풀이

Map을 사용하면 아래의 문제를 쉽게 풀수가 있습니다. 한번 문제보시면서 풀어보시구요. 정답 코드는 아래에 있습니다.

https://www.acmicpc.net/problem/1764

 

1764번: 듣보잡

첫째 줄에 듣도 못한 사람의 수 N, 보도 못한 사람의 수 M이 주어진다. 이어서 둘째 줄부터 N개의 줄에 걸쳐 듣도 못한 사람의 이름과, N+2째 줄부터 보도 못한 사람의 이름이 순서대로 주어진다.

www.acmicpc.net

#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;

int n, m;
int main() {
	cin >> n >> m;
	//값은 0으로 default로 설정됨
	map<string, int> outsiders;
	vector<string> ans;
	
	for (int i = 0; i < n+m; i++) {	//듣+보 전부 한꺼번에 입력받음(n+m)
		string name;
		cin >> name;		//이름이 2번 등장하면 outsiders[name] = 2가 된다
		outsiders[name]++;	
	}
	//2면 정답
	for (auto it : outsiders) {
		if (it.second == 2) ans.push_back(it.first);
	}
	
	cout << ans.size() << endl;
	for (int i = 0; i < ans.size(); i++)
		cout << ans[i] << endl;

}

 

이상으로 포스팅 마치도록 하겠습니다~

반응형
블로그 이미지

REAKWON

와나진짜

,

Vector

Standard Template Library의 컨테이너로 정의된 클래스인데요. 배열과 비슷한 특징이 있습니다만, 동적으로 계속하여 뒤에 원소를 추가할 수 있습니다. 배열을 다루는 사용자의 불편함을 vector를 사용하면 어느정도 편리하게 사용할 수 있습니다. 이 포스팅에서는 vector의 사용방법에 대해서 다룹니다.

C++에서 vector를 사용하기 위해서는 아래와 같이 vector 헤더파일을 추가시키시면 됩니다.

#include <vector>

 

1. 초기화

배열과 비슷하다고 했습니다만 초기화 방법에서는 약간 차이가 있습니다. 아래의 코드는 초기화 방식을 설명합니다. 

vector<int> v1;			//아무것도 없는 비어있는 vector
vector<int> v2(5);		//5개의 int형을 저장하는 vector(전부 0으로 초기화)
vector<int> v3(5,1);	//5개의 int형을 저장하는 vector(전부 1로 초기화)
vector<int> v4 = { 1,2,3,4,5 };	//배열과 같은 초기화
vector<int> v5(v4);		//v4의 벡터 요소를 복사해서 초기화

 

2. 크기와 용량(size & capacity)

vector는 현재 가지고 있는 데이터의 수를 나타내는 크기(size)와 얼만큼의 데이터를 담을 수 있는지에 대한 용량(capacity)가 있습니다. 만약 용량이 전부 꽉 차게 되면 용량을 동적으로 더 늘려서 데이터를 추가할 수 있습니다. vector의 용량은 항상 size보다 크거나 같습니다. 아래의 그림처럼 capacity가 모자라게 되면 늘리게 되는거죠.

 

코드로 직접 확인해보세요.

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v;
	for (int i = 0; i < 20; i++) {
		v.push_back(i + 1);
		cout << " 용량 :" << v.capacity();
		cout << " 크기 :" << v.size();
		cout << " 데이터: " << v[i] << endl;
	}
	return 0;
}

 

 

3. 데이터 읽기

데이터읽는 방법은 배열과 같이 []로 접근하는 방법과 at() 으로 접근하는 방법이 있습니다. 둘은 같은 값을 나타내줍니다. 아래의 코드를 보고 결과가 같은지 확인해봅시다.

int main() {
	vector<int> v = { 1,5,3,6,8 };
	cout << "v[1]:" << v[1] << endl;
	cout << "v.at(1):" << v.at(1) << endl;
	cout << "v[3]:" << v[3] << endl;
	cout << "v.at(3):" << v.at(3) << endl;

	return 0;
}

 

둘은 같은 값을 나타내고 있죠? 하지만 차이는 없을까요? 있겠죠. 만약 배열 접근 기호([])로 10번째 요소를 읽어봅시다. 현재 5번째까지 초기화했고, 10번째는 아직 접근할 수 없기 때문에 아래와 같은 에러를 보이고 종료하고 맙니다. at()통해서도 마찬가지일거에요. 하지만 둘의 차이는 예외를 뜨게해서 처리할수 있게 만들었느냐 아니냐입니다.

아래의 코드를 실행시켜보시고, 바꿔서 윗줄은 주석처리, 아랫줄은 주석 해제하여 실행해보세요. 차이점을 알 수 있습니다.

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v = { 1,5,3,6,8 };
	try {
		cout << v[10] << endl;
		//cout << v.at(10) << endl;
	}
	catch (out_of_range& e) {
		cout << "예외 발생 처리 " << endl;
	}

	return 0;
}

 

프로그래밍을 at()으로 하면 더 안전하게 사용할 수 있는 대신 검사때문에 []를 이용하는 방법보다는 느립니다. 두 방식 중 알맞게 선택하여 사용하세요.

4. 데이터쓰기

데이터쓰기는 너무 편합니다. 그냥 배열과 같이 사용하면 됩니다.

	vector<int> v = { 5,3,1,6,7 };
	v[2] = 3;

 

5. 데이터 뒤에 추가 및 뒤에서 삭제

push_back()을 사용하면 아래와 같이 vector 뒤에 차곡차곡 데이터를 추가합니다. 반대로 삭제하려면 pop_back()을 사용하시면 됩니다. pop_back()은 데이터를 return하지는 않고, 단지 꺼내주기만 합니다. 가장 마지막 원소를 가져오려면 back()을 이용하세요.

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v;

	v.push_back(10);
	v.push_back(13);
	v.push_back(15);
	v.push_back(20);

	int size = v.size();
	for (int i = 0; i < size; i++) {
		cout << "back():" << v.back() << endl;
		v.pop_back();
	}

	return 0;
}

 

6. Iterator 

Iterator를 통해서 for문을 돌수도 있습니다. vector의 begin()은 vector의 처음 요소를 가리키고 있습니다. vector의 end()는 vector의 마지막 요소 다음을 가리키고 있습니다. 마지막 요소 다음이지 마지막 요소를 가리키는게 아닙니다.

그래서 아래와 같이 for문을 사용하는 방법이 가능합니다.

vector<int> v = { 0,9,21,1,0,29 };
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) 
	cout << *it << endl;

 

너무 복잡하죠? 아래와 같이 auto로 코드를 줄일 수 있습니다.

for (auto it = v.begin(); it != v.end(); it++)
	cout << *it << endl;

 

7. reserve

용량을 지정한 수대로 동적할당을 미리 시켜놓습니다. 

int main() {
	vector<int> v = { 0,9,21,1,0,29,2022 };
	cout << "capacity:" << v.capacity() << endl;
	v.reserve(10);
	cout << "capacity:" << v.capacity() << endl;
	v.reserve(15);
	cout << "capacity:" << v.capacity() << endl;

	return 0;
}

 

8. insert

insert는 iterator를 통해서 원소를 삽입하는 방식입니다. 아래와 같이 사용가능합니다. 

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v = { 0,9,21,1,0,29,2022 };
	vector<int>::iterator it=v.begin();	//맨앞
	//v.insert(it, 90);	//맨앞에 90삽입
	v.insert(it + 4, 90);	//4번째 원소에 90삽입

	for (auto it = v.begin(); it != v.end(); it++)
		cout << *it << endl;

	return 0;
}

이 밖에도 insert는 오버로딩이 되어있으므로 찾아보셔서 알맞는 것을 사용하면 됩니다.

 

9. 정렬

vector는 algorithm 헤더파일의 sort()함수로 정렬이 가능합니다. 다만 기본 자료형만 가능하다는 점이고, 클래스같은 경우는 별도로 비교 함수를 사용하여 delegator 방식으로 처리해야합니다.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
	vector<int> v = { 0,9,21,1,0,29,2022 };
	sort(v.begin(), v.end());
	for (auto it = v.begin(); it != v.end(); it++)
		cout << *it << endl;

	return 0;
}

이 밖에도 insert는 오버로딩이 되어있으므로 찾아보셔서 알맞는 것을 사용하면 됩니다.

 

그외에도 여러가지 vector 관련 함수들이 있는데요. 어렵지 않은 함수들이니까 나중에 직접 찾아서 활용해보시기 바랍니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

하한(Lower bound), 상한(Upper bound)

하한, 상한은 배열이 이미 정렬된 상태에서 적용되어야합니다. 상한, 하한은 모두 이진 탐색(Binary Search)로 구현이 되어있기 때문이죠. 여기서 이진 탐색은 아래의 코드인 것을 알고 계시겠죠? 이진 탐색의 기본 코드는 아래와 같습니다. 이진 탐색의 탐색 속도는 O(lg 2)의 속도를 자랑하지요. 

 

 

int BinarySearch(int s, int e, int number)
{
    int left=s,right=e,mid;
    while (left <= right)
    {
        mid = (left + right) / 2;
        if (arr[mid] == number) return mid;
        if (arr[mid] > n) right = mid -1;
        else left = mid + 1;
    }
    return -1;
}

 

하한이란? (Lower Bound)

특정 값 K보다 같거나 큰 값이 처음 나오는 위치가 됩니다. 즉, K값 이상인 값이 맨 처음 나오는 위치여야합니다. 아래는 그 예를 나타내는 표입니다. 이때 맨 앞 요소의 index는 0으로 시작합니다.

배열
1 2 2 4 4 5 6 6 6 9 9
lower_bound
lower bound( 4 )  3
lower bound( 5 )  5
lower bound( 9 )  9

하한을 Binary Search방식으로 구현한 코드는 아래와 같은데요.  number값을 이상인 값이 mid 위치에서 발견되었다면  왼쪽으로 더 가다보면 아예 작은 수를 만나게 되고 left와 right의 역전 현상이 발생되어 멈춥니다. 멈추기 전에 우리는 가장 마지막에 arr[mid] >= number인 상황을 lower_bnd에 저장한 상황이죠. 

int Binary_Search_Lower_Bound(int s, int e, int number)
{
    int left=s,right=e,mid;
    int lower_bnd = -1;
    while (left <= right)
    {
        mid = (left + right) / 2;
        if (arr[mid] >= number)
        {
            lower_bnd = mid;
            right = mid - 1;
        }
        else left = mid + 1;
    }
    return lower_bnd;
}

범위를 좁혀가는 상황을 그림으로 표현했습니다.

 

- C++의 lower_bound 사용

C++에서는 라이브러리 함수 형태로 lower bound를 지원합니다. 아래와 같은 형식을 사용하게 되는데요. 사용법은 이렇습니다. 


lower_bound( 정렬한 배열 포인터, 정렬한 배열 포인터의 끝 주소, 값) - 정렬한 배열 포인터

여기서 정렬한 배열의 포인터는 알겠는데, 정렬한 배열 포인터의 끝 주소값은 어떻게 구할까요? 그냥 마지막 원소의 주소를 넣어주거나 배열의 주소 + 배열의 사이즈로 넣어주면 됩니다. 

lower_bound의 반환 값은 하한값의 주소를 돌려주기 때문에 만약 index를 얻으려면 정렬한 배열 포인터를 빼주면 됩니다. 이 함수의 사용법은 밑의 예제 코드에서 upper_bound의 함수와 같이 사용해보도록 하겠습니다.

상한이란? (Upper Bound)

특정 값 K보다 큰 값이 처음으로 나오는 위치가 됩니다. 즉, K값 초과인 값이 맨 처음으로 나오는 위치입니다. 몇 가지 예를 들어볼까요?

배열
1 2 2 4 4 5 6 6 6 9 9
upper_bound
upper bound( 4 ) 5
upper bound( 5 ) 6
upper bound( 9 ) -1 

이 역시 Binary Search로 구현되는데요. 구현한 모습은 아래와 같습니다. 중간의 비교문이 반대로 됐음을 알 수 있죠? 이것은 arr[mid]의 값이 number의 이하일때 number와 최대한 같은 값의 index를 찾기 위함입니다.

 

 

int Binary_Search_Upper_Bound(int s, int e, int number)
{
    int left=s,right=e,mid;
    int upper_bnd=-1;
    while (left <= right)
    {
        mid = (left + right) / 2;
        if (arr[mid] <= number)
        {
            upper_bnd = mid;
            left = mid + 1;
        }
        else right = mid - 1;
    }
    return upper_bnd+1 >= N ? -1: upper_bnd+1;
}

 

만약 이렇게 돼서 while루프가 멈추었다면 arr[upper_bnd]값은 number값 초과 직전의 값일 것입니다. 그렇다면 arr[upper_bnd+1]의 값은 number보다 큰 값일 테지요. 그래서 마지막 return에서 +1을 해주는데, 이것이 배열의 사이즈를 넘어갈 수 있으니까 사이즈 체크하여 return합니다. 

C++에서 당연히 lower_bound함수가 존재하듯 upper_bound의 함수 역시 존재하며 사용법은 동일합니다. 단, 아래와 같이 사용할때 상한 값을 못찾아줄때도 있는데, 이때는 배열의 인덱스를 넘어가는 값을 갖게됩니다.


  upper_bound(정렬한 배열 포인터, 정렬한 배열 포인터의 끝 주소, 값) - 정렬한 배열 포인터

 

이제까지 설명한 내용을 예제 코드를 통해서 확인해보세요. 직접 구현한 BinarySearch 형식의 upper_bound, lower_bound도 같이 구현되어있습니다.

#include <iostream>
#include <algorithm>

using namespace std;

int arr[100];
int N = 11;
int BinarySearchLowerBound(int s, int e, int number)
{
    int left=s,right=e,mid;
    int lower_bnd = -1;
    while (left <= right)
    {
        mid = (left + right) / 2;
        if (arr[mid] >= number)
        {
            lower_bnd = mid;
            right = mid - 1;
        }
        else left = mid + 1;
    }
    return lower_bnd;
}

int BinarySearchUpperBound(int s, int e, int number)
{
    int left=s,right=e,mid;
    int upper_bnd=-1;
    while (left <= right)
    {
        mid = (left + right) / 2;
        if (arr[mid] <= number)
        {
            upper_bnd = mid;
            left = mid + 1;
        }
        else right = mid - 1;
    }
    return upper_bnd+1 >= N ? -1: upper_bnd+1;
}


int main(void) {


    arr[0] = 4; arr[1] = 2; arr[2] = 4; arr[3] = 1; arr[4] = 9;
    arr[5] = 2; arr[6] = 6; arr[7] = 6; arr[8] = 6; arr[9] = 9, arr[10] = 5;
    
    //정렬
    sort(arr, arr + N);

    printf("정렬된 배열\n");
    for (int i = 0; i < N; i++) printf("%d ",arr[i]);
    printf("\n");
    
    printf("Lower Bound\n");

    printf("%d lower bound:%d\n", 4, BinarySearchLowerBound(0, N - 1, 4));
    printf("%d lower bound:%d\n", 5, BinarySearchLowerBound(0, N - 1, 5));
    printf("%d lower bound:%d\n", 2, BinarySearchLowerBound(0, N - 1, 2));
    printf("%d lower bound:%d\n", 1, BinarySearchLowerBound(0, N - 1, 1));
    printf("%d lower bound:%d\n", 9, BinarySearchLowerBound(0, N - 1, 9));

    printf("%d lower bound:%d\n", 4, lower_bound(arr, arr + N, 4) - arr);
    printf("%d lower bound:%d\n", 5, lower_bound(arr, arr + N, 5) - arr);
    printf("%d lower bound:%d\n", 2, lower_bound(arr, arr + N, 2) - arr);
    printf("%d lower bound:%d\n", 1, lower_bound(arr, arr + N, 1) - arr);
    printf("%d lower bound:%d\n", 9, lower_bound(arr, arr + N, 9) - arr);
  

    printf("Upper Bound\n");

    printf("%d upper bound:%d\n", 4, BinarySearchUpperBound(0, N - 1, 4));
    printf("%d upper bound:%d\n", 5, BinarySearchUpperBound(0, N - 1, 5));
    printf("%d upper bound:%d\n", 2, BinarySearchUpperBound(0, N - 1, 2));
    printf("%d upper bound:%d\n", 1, BinarySearchUpperBound(0, N - 1, 1));
    printf("%d upper bound:%d\n", 9, BinarySearchUpperBound(0, N - 1, 9));

    printf("%d upper bound:%d\n", 4, upper_bound(arr, arr + N, 4) - arr);
    printf("%d upper bound:%d\n", 5, upper_bound(arr, arr + N, 5) - arr);
    printf("%d upper bound:%d\n", 2, upper_bound(arr, arr + N, 2) - arr);
    printf("%d upper bound:%d\n", 1, upper_bound(arr, arr + N, 1) - arr);
    printf("%d upper bound:%d\n", 9, upper_bound(arr, arr + N, 9) - arr);   //배열의 인덱스 초과된 값이 나옴
  
}

 

 

그리고 위 코드의 결과입니다. 마지막 upper_bound의 값이 다름을 확인해보세요.

 

 

전체 코드의 결과

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

동적 메모리 할당,해제를 맡는 new, delete 키워드

C에서 메모리의 동적할당을 할때 malloc 함수를 사용하지요. 동적할당에 대해 모르신다면 저의 동적할당에 대한 포스팅을 보고 오시기 바랍니다.

reakwon.tistory.com/20

 

[C언어] 동적 메모리 할당의 세가지 방법 malloc, calloc, realloc

동적 메모리 할당 우리는 이제껏 메모리를 할당할때 정적으로 할당했습니다. 어떤 것이냐면 int arr[100]; 이렇게 할당을 했었죠. 뭐 문제없습니다. 실행도 잘 되구요. 하지만 이런 상황은 조금 불

reakwon.tistory.com

 

C언어에서 malloc을 사용할때 반환하는 void*를 형변환하여 사용하였습니다. 그리고 동적할당된 메모리를 해제할땐 free를 사용했습니다. C++에서는 이러한 동적할당에 관한 사용이 자주 이루어지기 때문에 아예 키워드로 지정해놓았습니다. malloc을 대체하는 키워드는 new, free를 대체하는 키워드는 delete입니다. 사용되는 방식은 다음과 같습니다.

MyClass* myclass = new MyClass();
delete(myclass);

여기서 malloc과 new는 동적할당하는 것은 같지만 malloc은 함수를 call하는 것이고 new는 C++에서 기본적으로 제공하는 키워드이기 때문에 별도의 라이브러리를 추가할 필요가 없습니다. 또한 new는 생성자를 자동호출하는 특징이 있지요. 이제 C++에서는 객체를 할당할때 malloc으로 하지 않고 new를 이용하여 할당합니다.

오버라이딩(Overriding)

Class의 상속에 대해서 배우셨나요? 그렇다면 이제 다형성을 배워볼 차례입니다. 다형성이라하면 여러 형태를 갖는 객체의 특징을 말하는데요. 아래의 코드를 가지고 다형성과 관련한 클래스의 특징들을 천천히 살펴보도록 합시다.

#include<iostream>
using namespace std;

class Animal {
private:
	int height;
	int weight;
public:
	Animal(int _height, int _weight) :height(_height), weight(_weight) {}
	void printInfo() {
		cout << "==============정보=============" << endl; ;
		cout << "키:" << height << "무게:" << weight << endl;
	}
	int getHeight() {
		return height;
	}
	int getWeight() {
		return weight;
	}
};

class Human :public Animal {
private:
	int race;
public:
	Human(int _height, int _weight, int _race) :Animal(_height, _weight) {
		race = _race;
	}
	void printInfo() {
		cout << "==============정보=============" << endl;
		cout << "키:" << getHeight() << "무게:" << getWeight() << endl;
		cout << "인종:";
		if (race == 0)
			cout << "황인" << endl;
		else if (race == 1)
			cout << "흑인" << endl;
		else if (race == 2)
			cout << "백인" << endl;
		else
			cout << "혼혈" << endl;
	}
};
int main() {
	
	Animal* animal = new Animal(50, 20);
	animal->printInfo();

	Human* human = new Human(150, 80, 3);
	human->printInfo();
	delete(animal);
	delete(human);
}

 

여기서 Animal클래스는 Human클래스의 부모 클래스입니다. main에서는 둘 다 new 키워드로 생성해서 정보를 출력하는 코드네요. Human 클래스에서 printInfo 메소드를 보면 cout 두줄이 정확히 Animal의 printInfo클래스의 메소드와 같은 것을 알 수 있습니다. 

어차피 Human클래스는 Animal클래스를 상속받고 있으니, Animal클래스의 printInfo를 재활용 할 순 없을까요? 그럴때 부모클래스::메소드명 을 호출하여 부모클래스의 메소드를 사용할 수 있습니다. Human 클래스의 메소드를 아래처럼 바꿔봅시다.

void printInfo() {
	Animal::printInfo();
	cout << "인종:";
	if (race == 0)
		cout << "황인" << endl;
	else if (race == 1)
		cout << "흑인" << endl;
	else if (race == 2)
		cout << "백인" << endl;
	else
		cout << "혼혈" << endl;
}

 

이처럼 부모클래스의 함수를 이용하여 자식 클래스에서 같은 메소드 이름으로 새로운 기능을 덧붙이는 방식을 오버라이딩이라고 합니다.

 

 

다형성(Polymorphism)

OOP(Object-Oriented Programming)은 현실 세계를 반영한다는 슬로건을 가지고 있습니다. 위의 코드를 현실세계와 연관지어 생각해봅시다. 우리 사람은 동물의 한 종이죠. 그래서 동물의 속성을 갖고 있습니다. 하지만 동물은 사람이 아니죠. 동물에는 사람외에도 개와 고양이 등 많이 있기 때문인데요. 정리하면 이렇게 되겠네요.

사람은 동물이다.(O)

동물은 사람이다.(X)

위에서 성립이 되는 상황(사람은 동물)을 C++에서는 아래와 같이 표현할 수 있습니다. main함수안의 내용을 아래와 같이 바꿔서 실행해보시기 바랍니다.

Animal* human = new Human(150, 80, 3);
human->printInfo();

 

이렇게 부모클래스를 통해 만들어진 객체가 자신을 상속받는 여러 클래스의 객체로 모양을 띄는 것을 다형성이라고 합니다. OOP에서 매우 중요한 개념입니다.

위의 코드를 수행한 결과에서 한가지 불편한 점이 있는데요. 저희는 human->printInfo()가 당연히 Human클래스의 printInfo를 출력한다는 믿음으로 프로그래밍을 했는데, 막상 결과는 Animal의 printInfo가 호출됩니다. 어떻게 해야지 우리가 원하는 동작을 할 수 있을까요?

virtual 키워드

만약 자신을 상속받는 자식 클래스의 객체가 자신의 메소드를 사용하는데, 이것을 오버라이딩했다면 자식 클래스의 객체 메소드를 호출하라고 지정하는 방법은 메소드 앞에 virtual 키워드를 사용하는 것입니다. 자신의 메소드는 가상으로 만들어져 있으니 자식의 메소드를 호출하라는 의미가 되겠죠. 그래서 우리의 목표를 달성하기 위해서는 Animal 클래스의 printInfo메소드 앞에 virtual 키워드를 추가하면 됩니다. 그렇게 되면 Human의 printInfo 메소드를 호출하게 됩니다.

class Animal {
private:
	int height;
	int weight;
public:
	Animal(int _height, int _weight) :height(_height), weight(_weight) {}
	virtual void printInfo() {
		cout << "==============정보=============" << endl; ;
		cout << "키:" << height << "무게:" << weight << endl;
	}
	int getHeight() {
		return height;
	}
	int getWeight() {
		return weight;
	}
};

 

virual키워드를 사용해야 자식 객체의 메소드를 사용한다고 했죠? 그렇다면 다음의 상황을 예측해봅시다.

class Human :public Animal {
private:
	int race;
public:
	//...생략...//
	void printInfo() {
		//...생략...//
	}
};

class Student :public Human {
private:
	char grade;
public:
	Student(int _height, int _weight, int _race,char _grade) :Human(_height, _weight, _race) {
		grade = _grade;
	}

	void printInfo() {
		Human::printInfo();
		cout << "성적:" << grade << endl;
	}
};

 

Human 클래스는 printInfo에 virtual키워드를 넣지 않았습니다. Student는 Human클래스를 상속받고 있는데, 이때 printInfo는 Human의 printInfo를 호출할까요? 메소드의 virtual이 지정되면 이후 자식은 자동으로 virtual 키워드가 적용이 됩니다. 그래서 결론은 Student의 printInfo가 호출이 되지요. 하지만 명시적으로 virtual을 지정해주는 것이 관례입니다. 코드의 가독성과 이해를 돕기 위해서지요.

자, 이런 다형성은 언제 필요할까요? 예를 들면 함수에서 해당 클래스를 상속하는 모든 객체를 받을 때가 그 예가 됩니다. 아래의 함수에서 변수로 animal를 가리키는 포인터를 받습니다. 이 함수의 목적은 animal인 객체의 printInfo를 출력하는 것인데, Animal클래스를 상속하는 모든 객체를 받을 수 있습니다. 

void printInfo(Animal *animal) {
	animal->printInfo();
}

 

 

this 포인터

객체가 생성될때 자기 자신을 가리키는 포인터가 this 포인터라고 합니다. 만일 아래와 같은 상황에서 this를 사용할 수 있습니다. 저는 생성자에서 객체의 grade를 전달받은 grade의 값으로 입력하고 싶어서 아래와 같은 코드를 사용했습니다.

class Student :public Human {
private:
	char grade;
	Student(int _height, int _weight, int _race,char grade) :Human(_height, _weight, _race) {
		grade = grade;
	}
//...생략...//
};

 

여기서 객체의 grade는 절대 변할 수 없습니다. 객체의 grade보다 매개 변수인 grade가 우선시 되기 때문에 매개변수 grade에 다시 매개변수의 grade의 값을 넣기 때문입니다. 이럴때 사용할 수 있는 것이 바로 this포인터를 사용하는 것이죠.

Student(int _height, int _weight, int _race,char grade) :Human(_height, _weight, _race) {
	this->grade = grade;
}

this포인터는 객체가 생성되고 난 이후에 그 효력을 발휘합니다. 그 객체의 주소를 나타내야하기 때문이죠. 아래와 같이 한번 주소를 찍어봅시다. 정확히 같은 곳을 가리키는 것을 알 수 있습니다.

class Student :public Human {
private:
	char grade;
public:
	Student(int _height, int _weight, int _race,char grade) :Human(_height, _weight, _race) {
		cout <<"this 포인터의 주소"<< this << endl;
		this->grade = grade;
	}
	//...생략...//
};

int main() {
	
	Animal* student = new Student(165, 55, 0, 'A');
	cout <<"student의 주소"<< student << endl;
	student->printInfo();
	delete(student);
}

 

이상 다형성과 관련된 포스팅을 마치도록 하겠습니다. 이해가 되지 않는 부분은 댓글로 남겨주세요.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

C++ 상속(Inheritance)

다른 여타의 객체지향언어와 같이 C++ 역시 상속을 할 수 있습니다. 여러분은 현실 생활에서 상속이라는 개념을 알고있으신가요? 부모님으로부터 100억을 상속을 받으셨다면 이 포스팅을 볼 필요없으십니다. 저랑 친구해요.

객체지향 프로그래밍에서는 부모 클래스의 맴버 변수와 메소드를 자식 클래스가 재사용하는 개념으로 알고 계시면 됩니다. 이 맴버 변수나 메소드에 접근 제한자를 더하여 아무나 접근할 수 없도록 할 수 있습니다. C++에서 상속하는 방법은 ' : ' 콜론을 사용하여 상속을 받을 수 있습니다. JAVA에서 extends를 사용하여 상속을 하는 것과 같습니다. 형식은 아래와 같습니다.

class 클래스_이름 : 접근제한자 부모_클래스명{
	//.. 내용 ..//
}

 

접근 제한자

접근 제한자는 세가지가 있습니다. private, protected, public이라는 접근 제한자가 있지요. 

접근 제한자 설명
private 오직 자신의 클래스안에서만 접근 가능한 멤버 변수, 메소드.
protected 자신을 상속하는 클래스까지만 접근 가능
public 어떤 클래스에서나 접근 가능

접근 범위

 

생성자 (Constructor)

클래스가 객체를 생성할때 항상 호출하는 일종의 메소드가 있습니다. 이 메소드는 생성자라는 이름을 갖고 있습니다. 여러분이 객체를 생성할때 초기화하는 방법을 생성자에 기재합니다. 이를 테면, 각 맴버 변수들의 초기화 등이 있지요. 형식은 아래와 같습니다. 자신의 클래스명과 동일하지만 반환이 없는 메소드가 바로 생성자입니다.  생성자를 사용하려면 반드시 public으로 해야합니다. 모든 곳에서 접근 가능해야 초기화를 진행할 수 있기 때문이죠. 

여러분들이 굳이 생성자를 정의해놓지 않아도 C++은 알아서 기본 생성자(Default Constructor) 만들어줍니다. 비록 아무런 동작을 하지 않는 껍데기에 불과하지만요.

class 클래스명 {
public :
	클래스명(파라미터){
    	//..동작 ..//
    }
}

 

소멸자(Destructor)

소멸자는 객체가 소멸할때 마지막 최종작업을 기록하는 메소드를 말하며 아래 형식과 같습니다. 소멸자는 반환값, 전달받는 인자가 없고 ~클래스이름() 형태여야합니다. 주로 free, delete 등의 메모리 누수가 발생하지 않게 메모리 해제 작업등이 여기에 포함됩니다.

class A{
public:
	~A(){
    	//..메모리 해제등의 작업 ..//
    }
}

 

 

 

이제 여기서 간단한 예를 들어보도록 하겠습니다. 

#include <iostream>
using namespace std;

class A {
private:
	int a;
	int b;
public:
	A(int _a, int _b){
		a = _a;
		b = _b;
	}
	int add() {
		return a + b;
	}
};

class B : A {
public :
	B(int _a, int _b) : A(_a, _b) {}
	void printResult() {
		//A를 상속받았으니, 부모클래스의 add라는 메소드를 사용할 수 있음.
		printf("%d\n", add());
	}
};

int main() {
	B b(1, 2);
	b.printResult();
}

 

A는 자신만이 접근한 맴버 변수 a와 b를 가지고 있습니다. 그리고 생성자를 갖고 있는데, 여기서 a와 b를 초기화시켜줍니다. 그리고 누구나 접근 가능한(public) 메소드 add를 갖고 있습니다. 이 메소드는 단순 a와 b를 더한 값을 전달하지요. 맴버를 초기화할 수 있는 방법은 이런 방법도 있습니다. 

public :
	A(int _a, int _b):a(_a),b(_b) {
		
	}

B는 A 클래스를 부모 클래스(또는 Super Class라고 합니다.)로 상속을 받습니다. 이때 부모 클래스의 초기화를 하는 방법은 : 부모클래스명(매개변수명1, 매개변수명2 ...)으로 초기화 시킬 수 있습니다. 자, 여기서 눈여겨 보셔야할 점은 B는 A클래스를 상속받았기 때문에 a와 b는 갖고 있지만, 접근할 수 없습니다. 

printResult라는 메소드에서는 add()라는 메소드를 사용할 수 있는데 A의 클래스에서 정의하고 public으로 접근할 수 있게 만들었기 때문에 B클래스에서 사용할 수 있습니다.

접근 제한자를 통한 상속

우리는 위의 예제에서 접근 제한자는 따로 지정하지 않고 상속을 받았습니다. 상속받는 부분에서 접근 제한자를 지정하게 되면 부모 클래스의 맴버 변수, 메소드를 지정한 접근 제한자보다 범위가 넓은 맴버 변수, 메소드에 접근할 수가 없습니다. public을 명시적으로 지정해주어야만 다형성의 속성을 사용할 수 있습니다.

private 상속 

#include <iostream>
using namespace std;

class A {
private:
	int a;
protected:
	int b;
public:
	int c;
};

class B : private A { //b,c맴버 변수는 private 맴버로 접근 범위 졻혀짐

};


int main() {
	B b;
    //a = private, b = private, c = private
	b.a;
	b.b;
	b.c;
}

 

 

 

B는 A를 private로 상속받습니다. A의 맴버변수 b,c는 private보다 범위가 넓으므로 b,c는 private로 제한합니다. 따라서 a,b,c 모두 접근할 수 없습니다.

protected 상속

#include <iostream>
using namespace std;

class A {
private:
	int a;
protected:
	int b;
public:
	int c;
};

class B : protected A { //c맴버 변수는 protected 맴버로 접근 범위 졻혀짐

};


int main() {
	B b;

	//a = private, b = protected, c = protected
	b.a;
	b.b;
	b.c;
}

A의 맴버 변수 중에 protected보다 범위가 넓은 맴버는 protected로 접근이 제한됩니다.

public 상속

#include <iostream>
using namespace std;

class A {
private:
	int a;
protected:
	int b;
public:
	int c;
};

class B : public A { //맴버 변수의 접근 제한에 변화없음

};


int main() {
	B b;

	//a = private, b = protected, c = public
	b.a;
	b.b;
	b.c;
}

public보다 접근 제한이 넓은 맴버는 public으로 맞춰지는데, 의미없죠? public보다 큰 제한자는 없으니까요. 

 

캡슐화(Encapsulation)

캡슐화라는 것은 맴버 변수를 직접 변경할 수 없도록 캡슐처럼 껍데기를 둘러싸는 과정을 말합니다. 캡슐안에서 특정 로직에 따라 맴버 변수가 적절하게 변경되어야 프로그램이 안전할 수 있기 때문이죠. 아래의 코드가 그 예를 보여줍니다.

#include <iostream>
using namespace std;

class A {
private:
	int a;
	int b;
public:
	void setA(int _a) {
		if (_a > 50)
			_a = 50;
		a = _a;
	}
	void setB(int _b) {
		if (_b > 100)
			_b = 100;
		b = _b;
	}

	int getA() {
		return a;
	}
	int getB() {
		return b;
	}
};

class B : A {
public :
	void setAB(int a,int b) {
		setA(a);
		setB(b);
	}
	void printResult() {
		printf("%d + %d = %d\n", getA(), getB(), getA() + getB());
	}
};


int main() {
	B b;
	b.setAB(100, 200);
	b.printResult();
}

 

간단한 코드인데요. B는 A를 상속받습니다. 이때 a,b는 직접 설정할 수 없게 private로 접근을 제한했구요. 메인 함수에서는 b에서 setAB를 호출해서 a의 값을 100, b의 값을 200으로 바꾸려고 합니다. 하지만 A 클래스는 그것을 용납하지 않습니다. a의 최대값은 50, b의 최대값은 100으로 제한을 해놓았기 때문이죠. B가 a와 b를 변경할 수 있는 수단은 A 클래스의 setA와 setB를 호출하여 인자 전달을 하는 방법밖에 없습니다. 이렇게 맴버의 값을 함부로 변경할 수 없도록 맴버 함수로 껍질을 입히는 작업을 캡슐화라고 합니다. 이렇게 Get, Set 메소드를 Getter, Setter메소드라고 합니다.

객체지향 언어에서 반드시 필요한 속성임을 기억하세요.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

C언어와는 조금 다르게 C++의 함수는 조금 더 특별한 기능이 추가되었습니다. 뭐가 다를까요?

 

1. C언어에서는 함수명이 같으면 컴파일 에러가 나지만 C++에서는 함수명이 같아도 매개변수의 자료형, 갯수가 다르다면 같은 함수명을 쓸 수 있습니다. 이것이 함수 오버로딩(overloading)이라고 하지요.

2. C언어에서는 함수 인자에 default값을 줄 수 없습니다. 하지만 C++에서는 사용자가 매개변수에 값을 넘겨주지 않으면 자동으로 dafault값이 지정됩니다.

3. C언어에서와 다르게 참조자(reference)가 등장합니다. 변수의 별칭이라고나 할까요?

4. 함수의 호출시간을 줄이고자 inline함수가 등장합니다. inline함수는 일반 함수와는 다르게 함수의 호출부에 코드를 직접 삽입함으로써 함수가 호출되는 과정이 없습니다. 그러니까 실행속도가 일반함수 호출하는 것보다 빠르지요.

 

대략적인 설명은 여기까지 하겠습니다. 이제 하나하나 자세하게 살펴보도록 합시다.

 

 

1. 함수오버로딩(Overloading)

함수오버로딩은 개념이 꽤나 간단합니다. 예를 들어 두 수를 더해서 반환하는 sum이라는 함수가 있다고 합시다. 우리는 정수형, 실수형 모두 sum이라는 함수로 이름을 짓고 싶습니다. 왜냐면 자료형만 다를뿐 같은 기능을 하기 때문이죠. 이렇게 오버로딩의 개념이 등장합니다.

아래의 코드가 오버로딩을 보여줍니다.

 

#include <iostream>

using namespace std;

int sum(int a, int b) {
	return a + b;
}

double sum(double a, double b) {
	return a + b;
}

int sum(int a, int b, int c) {
	return a + b + c;
}

double sum(double a, double b, double c) {
	return a + b + c;
}

int main(void) {
	cout << sum(10, 20) << endl;
	cout << sum(10.1, 20.1) << endl;
	cout << sum(10, 20,30) << endl;
	cout << sum(10.1, 20.1,30.1) << endl;
}

 

함수의 인자들이 자료형이 다른 것도 있고, 인자의 갯수가 다른 것이 있습니다. 함수의 이름은 같은 것을 알 수 있네요.

여기서 주의해야할 점은 반환형이 달라도 오버로딩이 되지 않습니다. 항상 매개변수의 타입 또는 갯수가 달라야 오버로딩이 된다는 것을 알아두세요.

 

2. default 인자 값

인자를 전달하지 않으면 default로 지정된 값으로 인자 변수가 초기화됩니다. 이것도 역시 어렵지 않습니다. 

 

#include <iostream>
using namespace std;

int sum(int a, int b = 10) {
	return a + b;
}
int main(void) {
	cout << sum(20) << endl;
	cout << sum(20, 20) << endl;
}

 

함수의 인자 갯수는 2개이지만 sum(20)이라는 호출이 가능한 이유는 b가 10으로 default 값을 사용하기 떄문입니다. 또는 보통의 함수 호출과 같이 인자를 2개 주어서 사용할 수도 있습니다.

 

디폴트 인자값을 사용할때 주의할 점이라고 한다면 디폴트 인자값은 항상 오른쪽부터 왼쪽으로 써줘야한다는 것입니다. 

 

int sum(int a, int b = 10,int c) {
	return a + b + c;
}

int sum(int a, int b = 10, int c = 20) {
	return a + b + c;
}

 

첫번째 처럼 중간이 디폴트 인자값이 있다면 오류가 나게되고 항상 밑의 함수처럼 오른쪽부터 왼쪽으로 디폴트값을 써줘야한다는 겁니다.

 

 

3. 참조자(Reference)

참조자는 C++에서 새롭게 등장한 기능입니다. 변수의 별명이라고 생각하면 될 것 같습니다. 우리가 어떤 사람을 부를때도 별명을 부르곤 하잖아요? reference를 사용할땐 &를 사용하여 참조자라는 것을 알려줍니다.

예를 들어 이렇게 사용하죠.

 

int a=100;

int &b=a;

 

이렇게 b는 a와 같은 데이터를 가리키며 주소 역시 같습니다. 마치 포인터처럼요.

이것을 함수에 사용하게 되면 call-by-reference를 구현할 수 있게 됩니다. 우리는 call-by-reference를 C언어에서 포인터로 시험해봤지요.

이제 참조자를 통해서 구현해보도록 합시다.

 

#include <iostream>

using namespace std;

void swap(int &a, int &b) {
	int temp = a;
	a = b;
	b = temp;
}

int main(void) {
	int a = 100;
	int b = 200;

	cout << "a:" << a << ",b:" << b << endl;
	swap(a, b);
	cout << "a:" << a << ",b:" << b << endl;
}

 

실행시켜보면 아래와 같이 값이 바뀐 것을 알 수 있습니다. a와 b에 주소값을 swap함수에 넘기지 않고 말이죠.

 

결과

a:100,b:200
a:200,b:100
계속하려면 아무 키나 누르십시오 . . .

 

이 같이 참조자는 크기가 큰 구조체나 데이터를 매개변수로 전달할 때 유용합니다.

 

4. inline 함수

보통의 함수 호출 과정은 다음과 같습니다.

 

스택에 다시 돌아갈 주소를 저장. 매개변수 전달 -> 스택에 지역 변수 할당 -> 함수 실행 -> 반환값 전달 -> 다시 돌아갈 주소로 이동

 

함수의 실행부분이 꽤나 길다면 뭐 호출하는 시간은 신경쓰지 않아도 되지만 함수의 실행부가 짧지만 자주 호출된다면 호출되는 시간을 무시할 순 없겠죠.

 

inline함수는 함수의 호출시간을 줄여 보다 더 빠른 성능을 내기 위함입니다. inline함수는 함수 호출부에 함수 정의 부분을 직접 삽입하여 호출하는 방식으로 매크로 함수와 비슷합니다. 하지만 매크로 함수를 쓰면 괄호의 늪에 빠질 수도 있죠..

인라인 함수는 간단합니다. 기존의 함수를 선언하거나 정의하는 부분에 inline 키워드만 붙여주면 되니까요.

inline키워드는 선언 또는 정의하는 부분에 한번만 선언해주어도 됩니다.

 

 

#include <iostream>
using namespace std;

inline int sum(int, int);

int sum(int a, int b) {
	return a + b;
}
int main(void) {
	int sumValue;
    sumValue = sum(10, 20);
	cout << sumValue << endl;
}

 

실제 이 코드는 이렇게 동작하게 됩니다.

 

#include <iostream>
using namespace std;

int main(void) {

	int sumValue;
	{
		int a = 10; int b = 20;
		sumValue = a + b;
	}
	cout << sumValue << endl;
}

 

정말 단지 inline함수의 내용이 코드에 직접 삽입되는 형태지요.

 

지금까지 C++에서 함수의 특징에 대해서 알아보았습니다. 딱히 어려운건 없었죠?

반응형
블로그 이미지

REAKWON

와나진짜

,

이름공간(namespace)


C언어에서 한 단계 더 발전한 언어, 바로 C++이 C언어와 차이점을 두고 있는 것은 무엇일까요?


우리는 그 차이점 중에서 한가지를 이야기해보려 합니다.

우선 바로 코드를 보면서 어떤 주제에 대해서 설명할 지 감을 잡아보겠습니다.



#include <iostream>

int main() {
        char input[50];
	std::cin >> input;
	std::cout << input << std::endl;
}



이 짧은 코드를 보며 C언어에서 보지 못한 것이 보이시나요?


몇가지 보입니다. 우선 iostream이라는 파일을 include하고, std::in과 std::cout이 보이네요. std::endl라는 것도 보입니다.




하나씩 보도록 하지요.


● #include <iostream>

원래는 iostream.h 헤더 파일 입니다만 c++에서는 .h확장자를 붙이지 않아도 됩니다. 그래서 파일명만 써주도록 하는 거죠. 우리는 이 헤더파일을 include 해주어야 콘솔 출력, 입력을 할 수 있습니다(물론 다른 헤더파일을 써도 할 수는 있습니다..). (아, iostream은 input, output stream이라는 뜻이라는 거~)



● std::cin

cin은 콘솔 입력을 담당하는 객체입니다. >>은 오퍼레이터로 연산자를 의미합니다. cin에서 >>은 콘솔로 입력할때 쓰이는 연산자입니다. 음 그냥 cin>>은 이 후에 데이터에 콘솔로 입력하여라라는 것입니다(in앞에 c는 console의 c입니다.).  C언어에서 scanf와 아주 비슷한 역할을 하는 녀석입니다.

그 앞의 std는 우리가 오늘 이야기할 네임스페이스라고 합니다.


● std::cout

cin을 알았으니 cout도 대충 눈치채셨겠죠? <<역시 연산자를 의미합니다. 

cout은 콘솔출력을 담당하는 객체입니다. 누군가가 cout 보고 카우트라고 하는데 그럼 cin은 킨이냐?


● std::endl

endl은  endline으로 줄을 바꾸어 줍니다. 


namespace

여기서 자주 등장하는 키워드는 무엇일까요? 바로 std입니다. 

std는 네임스페이스의 이름입니다. 네임스페이스는 이름 공간으로 그 변수를 구분해주는 역할을 합니다. 


왜 쓰일까요?

어떤 개발자 A, B 두명이 있습니다. 서로 협동하여 프로젝트를 진행하고 있지요. 

프로젝트를 진행하던 중 개발자A는 함수를 func(int a,int b)해서 함수를 선언했습니다. 개발자 B는 생각없이 func(int a,int b)로 다른 기능을 하지만 함수 이름은 A가 정한것과 같이 정의해버렸습니다.


나중에 프로젝트가 진행될때 같은 함수이름과 같은 매개변수 때문에 오류가 나게 되지요.


그렇게 등장한게 네임스페이스입니다. 개발자 A는 namespace의 이름을 namespaceA, 그리고 개발자 B는 namespaceB로 자신의 함수를 선언합니다. 이렇게 하면 충돌할 확률이 줄어들겠죠??


와 그런데도 namespace이름마저 같으면 답없다 너네 둘은


네임스페이스를 정의하는 것은 너무 쉽습니다. 그저 namespace 키워드를 써주고 namespace의 이름을 정해 중괄호({, })로 묶어주기만 하면 끝입니다.


namespace 네임스페이스 이름{

...

변수나 함수

...

}


어때요? 참 쉽지 않나요? 그래서 네임스페이스에 존재하는 함수와 이름을 사용하려면 네임스페이스이름::함수 또는 변수 로 써주면 됩니다. 


예를 한번 볼까요?





#include <iostream>

namespace myNamespace {
	int a, b;
	int sum(int a, int b) {
		return a + b;
	}
}
int main() {
	myNamespace::a = 30;
	myNamespace::b = 40;
	std::cout << "30+40="<< myNamespace::sum(myNamespace::a, myNamespace::b) << std::endl;
}


myNamespace라는 네임스페이스에서는 a,b라는 변수, sum이라는 함수가 있네요. 얘네들을 써먹어서 a+b를 sum함수로 값을 도출해냅니다. 그러니 myNamespace::가 앞에 붙어있죠.



원하는 결과가 나오죠?


네임스페이스안에 네임스페이스를 지정할 수도 있습니다. 그렇게 되면 충돌확률은 현저히 줄어들겠네요. 하지만 쓸 일이 거의 없습니다. 이름도 길어지고.


namespace 네임스페이스 이름{

namespace 네임스페이스 이름1{

...

변수나 함수

...

}

namespace 네임스페이스 이름2{

...

변수나 함수

...

}

namespace 네임스페이스 이름3{

...

변수나 함수

...

}

}



접근하는 방법도 역시 똑같습니다.

네임스페이스::네임스페이스이름n::함수나 변수명



좋은것 같은 데 귀찮아

근데요. 사실 우리는 공부하는데 일일이 std::붙어주는 게 여간 귀찮은게 아니죠.


그런 경우에는 using namespace 네임스페이스이름 을 써주기만 하면 namespace의 이름으로 구분할 것 없이 바로 변수, 함수 등의 데이터를 사용할 수 있습니다.


이렇게 말입니다.


#include <iostream>

using namespace std;
namespace myNamespace {
	int a, b;
	int sum(int a, int b) {
		return a + b;
	}
}

using namespace myNamespace;
int main() {
	a = 30;
	b = 40;
	cout << "30+40="<< sum(a, b) << endl;
}


바로 위의 코드를 네임스페이스를 사용하지 않고 구현했습니다. 코드가 확 줄고 편해졌지요?


이제까지 네임스페이스를 알아보았습니다.


다음에 봐요~



반응형
블로그 이미지

REAKWON

와나진짜

,