벡터 v에서 i번째 원소를 삭제하고 싶다면

erase 함수를 사용하면 된다.

erase 함수의 인자는 iterator 즉, 지우고 싶은 원소의 주소이다.

http://www.cplusplus.com/reference/vector/vector/erase/

 

vector::erase - C++ Reference

123456789101112131415161718192021222324 // erasing from vector #include #include int main () { std::vector myvector; // set some values (from 1 to 10) for (int i=1; i<=10; i++) myvector.push_back(i); // erase the 6th element myvector.erase (myvector.begin(

www.cplusplus.com

벡터 v의 시작 주소는 v.begin()이다. i번째 원소의 주소는 v.begin() + i 라고 하면 된다.

따라서 벡터 v의 i번째 인덱스에 있는 원소를 삭제하고 싶을 때에는

v.erase(v.begin() + i);

이렇게 쓰면 된다.

 

v[5]를 지우고 싶다면

v.erase(v.begin() + 5);

이렇게 하면 된다.

코드로 보자면 아래 코드를 실행하면

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

int main() {
	vector<int> v(10);
	for (int i = 0; i < 10; i++) {
		v[i] = i;
	}
	cout << "지우기 전:          ";
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
	v.erase(v.begin() + 5);
	cout << "5번 인덱스 지운 후: ";
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
	return 0;
}

다음과 같이 5번 인덱스에 저장되어 있던 5가 삭제된다.

벡터 클래스의 더 많은 함수들을 알고 싶다면 아래 링크에 있는 포스팅을 참고하면 된다.

https://breakcoding.tistory.com/139

 

[C++] 라이브러리, 벡터 클래스, 동적 할당

만약에 입력으로 n을 입력받아 크기가 n인 배열을 만들고 싶다면 어떻게 해야 할까? int n; cin >> n; int arr[n]; 이렇게 하면 될까? 이렇게 선언하면 컴파일 에러가 나는 것을 볼 수 있다. 배열의 크기를 나타..

breakcoding.tistory.com

 

지난 번에 올린 이 글에 이어지는 내용이다. (링크↓)

https://breakcoding.tistory.com/294

 

[C++] 포인터와 배열 (포인터 심화)

지난 번에 올린 이 글에 이어지는 내용이다. (링크↓) https://breakcoding.tistory.com/81 [C++] 포인터 포인터를 어려워하는 분들이 많은 것 같다. 물론 나도 겁먹었었다. 하지만 전혀 어렵지 않다는 것을..

breakcoding.tistory.com

이전 포스팅의 마지막 부분에 함수의 리턴값으로 배열을 반환할 수는 없지만 배열의 주소 즉 포인터를 리턴할 수는 있다고 말했었다.

그러면 한 번 실험해보자.

 

이 함수는 반환값으로 포인터를 return하므로 반환타입은 int*이다.

#include <iostream>
using namespace std;

int* makeArray() {
	int arr[5] = { 1, 2, 3, 4, 5 };
	return arr; //배열의 주소 반환
}

int main() {
	int* myArray = makeArray(); //myArray라는 포인터변수에 배열의 주소를 받음
	for (int i = 0; i < 5; i++) {
		cout << myArray[i] << endl;
	}
	return 0;
}

이 코드를 실행하면

 

위 코드의 실행 결과

 

1, 2, 3, 4, 5가 아니라 엉뚱한 쓰레기값을 출력하는 것을 볼 수 있다.

이런 결과가 나오는 이유는 함수가 호출될 때 메모리의 스택이 어떻게 동작하는지를 알면 알 수 있다.

 

#include <iostream>
using namespace std;

int add(int num1, int num2) {
	int sum = num1 + num2;
	return sum;
}

int main() {
	int a = 5;
	int b = 2;
	int aPlusB = add(a, b);
	return 0;
}

이런 코드가 있다고 하자.

 

이 코드가 실행될 때 스택의 모습은 다음과 같다.

 

 

 

 

이것과 마찬가지로 위에서 본 이 코드는

#include <iostream>
using namespace std;

int* makeArray() {
	int arr[5] = { 1, 2, 3, 4, 5 };
	return arr; //배열의 주소 반환
}

int main() {
	int* myArray = makeArray(); //myArray라는 포인터변수에 배열의 주소를 받음
	for (int i = 0; i < 5; i++) {
		cout << myArray[i] << endl;
	}
	return 0;
}

 

 

 

위 코드가 실행될 때 스택의 모습

 

이 그림에서 4번째 스택 모습에서 문제가 생기는 것이다.

 

 

바로 이 상태이다.

makeArray() 함수가 종료되면서 배열 arr의 시작 주소인 1000번지만 넘겨주고 배열 arr은 스택에서 사라졌는데 1000번지에 가서 1000번지에 있는 쓰레기 값, 1004번지에 있는 쓰레기 값, 1008번지에 있는 쓰레기 값, 1012번지에 있는 쓰레기 값, 1016번지에 있는 쓰레기 값을 출력하고 종료하는 것이다.

 

 

바로 이렇게 말이다.

1은 우연히 스택에서 makeArray 함수가 끝나도 그 메모리가 쓰던 곳은 손상되지 않고 남아있어서 아주 운 좋게 1이 출력된 것이다.

 

일반적으로 프로그램을 실행하면서 필요한 변수들은 대부분 메모리의 스택 영역에 할당된다. 이것을 활성레코드라고 한다. 프로그램이 실행될 때에는 활성레코드 부분이 늘었다 줄었다 하면서 실행되는 것이다. 위에서 본 스택 그림처럼 말이다. 이 문제는 배열이 이러한 스택 영역에 할당되었기 때문에 생기는 문제이다.

 

이러한 문제를 해결할 수 있는 것이 바로 동적할당이다. 이게 바로 동적 할당이 필요한 이유이다.

스택에 저장된 활성레코드는 그 부분이 실행되고 나면 사라진다. 하지만 메모리에는 스택 영역 말고 힙(heap)이라는 영구 저장소가 있다. 동적 할당을 하면 힙(heap)에 저장이 된다. 힙(heap)에 저장된 것들은 delete를 사용하여 명시적으로 제거하지 않는 이상 프로그램이 종료될 때까지 존재한다.

 

동적할당은 다음과 같이 new 연산자를 사용하여 한다.

int* p = new int;

new 연산자를 사용하여 동적 배열을 생성하고 싶다면

int* list = new int[SIZE];

이렇게 하면 된다.

동적할당은 포인터 없이는 할 수 없다.

 

변수에는 동적으로 할당되는 변수가 있고 정적으로 할당되는 변수가 있다. 컴파일러는 컴파일을 할 때 이 프로그램에서 사용할 메모리를 대충 계산한다. 그 다음에 이거를 운영체제에게 알려준다. 예를 들어 컴파일러가 대충 계산했을 때 800바이트가 필요하다 하면 OS는 800바이트 이상을 스택에 여유있게 잡아놓는다. 이게 정적으로 할당하는 것이다.

 

그렇기 때문에 다음과 같은 코드는 컴파일 에러가 나는 것이다.

#include <iostream>
using namespace std;

int main() {
	int size;
	cin >> size;
	int arr[size];
	return 0;
}

사용자에게 size를 입력받아서 그 크기만큼 배열을 잡고 싶은 건데 이러한 코드는 불가능하다. 컴파일 할 때 필요한 메모리를 계산을 해야 하는데 size는 사용자에게 입력받는 것이기 때문에 컴파일 할 때에는 알 수가 없고 실행될 때 값이 결정된다. 따라서 컴파일러는 arr 배열을 메모리에 얼만큼 잡아야 하는지 모르므로 컴파일 에러가 나는 것이다. 따라서 배열의 크기를 정하는 변수는 반드시 const 변수여야 한다.

#include <iostream>
using namespace std;

int main() {
	int size = 5;
	int arr[size];
	return 0;
}

이 코드를 실행해도 컴파일에러가 난다.

배열의 크기를 정하는 저 자리에는 상수 혹은 const 변수(상수 변수)만 들어갈 수 있다.

#include <iostream>
using namespace std;

int main() {
	const int size = 5;
	int arr[size];
	return 0;
}

컴파일에러가 나지 않는다.

이렇게 const를 써줘야만 컴파일 에러가 나지 않는다.

 

그러면 사용자에게 크기를 입력 받아 그 크기 만큼의 배열을 생성하고 싶다면 어떻게 해야 할까?

 

동적할당을 사용하면 된다.

#include <iostream>
using namespace std;

int main() {
	int size;
	cin >> size;
	int* arr = new int[size];
	return 0;
}

동적할당으로 배열을 생성하면 이렇게 메모리를 얼만큼 잡을지 메모리의 크기를 실행 도중에 정할 수가 있다.

(컴퓨터에서 '동적'이라는 단어가 나오면 실행 도중이라는 뜻이고 '정적'이라는 것은 프로그램 실행 전을 말한다.)

 

위에서 봤던 코드를 다시 보자.

int* p = new int;

여기에서 포인터변수 p는 정적으로 할당된 변수이다. 포인터변수이므로 컴파일러가 4바이트 만큼을 메모리의 스택 부분에 잡는다. (int라서 4바이트를 잡는 것이 아니다. 이 부분이 헷갈리면 포인터 글을 다시 참고하면 된다.)

하지만 포인터변수 p가 가리키는 int는 동적으로 실행 중에 메모리의 힙(heap) 영역에 잡히게 된다.

.

.

곧 이어서 쓰겠습니다.

C++에서 두 수 a, b 중에 어떤 수가 더 큰지, 작은지 알고 싶다면

#include <algorithm>으로 <algorithm> 헤더를 추가하고

min(a, b);

또는

max(a, b);

이렇게 하면 된다.

하지만 배열이나 벡터의 원소들 중에서 최댓값을 찾고 싶다면 min_element, max_element 함수를 사용하면 된다. 이 함수들도 <algorithm> 라이브러리에 있기 때문에 <algorithm> 헤더를 포함하면 된다.

크기가 10인 배열 arr에서 최댓값을 찾아서 maxNum이라는 변수에 저장하고 싶다면

int maxNum = *max_element(arr, arr + 10);

이렇게 하면 된다. 앞에 역참조 연산자 *를 붙이는 이유는 max_element, min_element 함수의 반환값은 최댓값이 저장된 곳의 메모리 "주소"이기 때문이다. 따라서 값을 알고 싶다면 역참조연산자를 이용하면 된다.

 

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	int arr[10] = { 10, 200, 340, 28, 50, 189, 84, 39, 1, 248 };
	cout << "최댓값: " << *max_element(arr, arr + 10) << endl;
	cout << "최솟값: " << *min_element(arr, arr + 10) << endl;
	return 0;
}

이 코드를 실행하면

 

위 코드의 실행 결과

 

이렇게 배열 arr의 원소 중 최댓값과 최솟값을 알아낼 수 있다.

 

벡터도 마찬가지이다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
	vector<int> v = { 10, 200, 340, 28, 50, 189, 84, 39, 1, 248 };
	cout << "최댓값: " << *max_element(v.begin(), v.end()) << endl;
	cout << "최솟값: " << *min_element(v.begin(), v.end()) << endl;
	return 0;
}

 

위 코드의 실행결과

 

지난 번에 올린 이 글에 이어지는 내용이다. (링크↓)

https://breakcoding.tistory.com/81

 

[C++] 포인터

포인터를 어려워하는 분들이 많은 것 같다. 물론 나도 겁먹었었다. 하지만 전혀 어렵지 않다는 것을 알게 되었고 다른 사람들에게도 포인터가 어렵지 않다는 것을 알려주고 싶다. 일반적으로 변수는 정수, 실수,..

breakcoding.tistory.com

 

사실 포인터는 배열과 매우 밀접한 관련이 있다.

배열의 이름은 배열의 시작 주소이다.

이게 정말인지 궁금하다면 출력해보면 된다.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	cout << arr << endl;
	return 0;
}

 

위 코드의 실행결과

 

배열의 이름을 출력해보니 메모리 주소가 저장된 것을 볼 수 있다. 이런 의미에서 배열은 근본적으로 포인터이다.

하지만 저게 배열의 시작 주소인지는 아직 알 수 없다.

 

지난 글에서 말했듯이 주소만 있다면 역참조 연산자 *를 통해 그 주소에 저장된 값(데이터)을 읽어올 수 있다.

역참조 연산자를 이용해서 배열이름이 정말 배열의 첫 번째 원소가 저장되어 있는 메모리 주소가 맞는지 확인해보자.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	cout << *arr << endl;
	return 0;
}

 

위 코드의 실행 결과

 

또 다른 방법으로도 확인할 수 있다.

주소를 확인하는 방법은 &연산자를 쓰면 된다. &arr[0]과 arr이 같은지 확인해보자.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	cout << "배열의 첫 번재 원소의 주소: " << &arr[0] << endl;
	cout << "배열 이름 arr:              " << arr << endl;
	return 0;
}

 

위 코드의 실행 결과

 

이제 배열의 이름 arr이 배열의 시작주소 즉, 배열의 첫 번째 원소인 1이 저장되어 있는 메모리 주소라는 것을 직접 확인해보았다.

 

배열의 시작 주소를 이용해서 배열의 원소에 접근하는 방법을 알아보자.

배열의 0번째 인덱스에 저장된 원소에 접근하려면 *(arr + 0), 배열의 1번 인덱스에 저장된 원소에 접근하려면 *(arr + 1), 2번 인덱스는 *(arr + 2), 3번 인덱스는 *(arr + 3) 이렇게 접근하면 된다.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	cout << *(arr + 0) << endl;
	cout << *(arr + 1) << endl;
	cout << *(arr + 2) << endl;
	cout << *(arr + 3) << endl;
	cout << *(arr + 4) << endl;
	return 0;
}

 

위 코드의 실행 결과

 

즉 arr[3]과 *(arr + 3)은 같은 말이다.

arr에서 3 x 4(arr은 int 배열이므로 원소 하나는 4바이트)바이트만큼 떨어진 곳의 메모리주소에 저장된 내용을 읽어오라(역참조 연산자)는 것이다.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	cout << arr[0] << " " << *(arr + 0) << endl;
	cout << arr[1] << " " << *(arr + 1) << endl;
	cout << arr[2] << " " << *(arr + 2) << endl;
	cout << arr[3] << " " << *(arr + 3) << endl;
	cout << arr[4] << " " << *(arr + 4) << endl;
	return 0;
}

 

위 코드의 실행 결과

 

arr[i]와 *(arr + i)는 똑같은 표현이라는 것을 알 수 있다.

그리고 &arr[i]와 arr + i도 똑같은 표현이다. arr[i]가 저장된 메모리 주소를 나타낸다.

 

정리하자면 배열의 첫 번째 주소는 '배열의 이름'으로 항상 노출되어 있고 나머지 원소는 첫 번째 원소의 주소 + offset 이런 방식으로 접근하는 것이다.

 

그러면

int* list = arr;

이렇게 해버리면 어떻게 될까?

list[1] 이런 식으로 list 포인터를 통해 arr과 똑같이 배열의 원소에 접근할 수 있게 된다.

 

다음 코드를 실행해보자.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	int* list = arr;
	for (int i = 0; i < 5; i++) {
		cout << "주소: " << list + i << " *(list + " << i << "): " << *(list + i) << " list[" << i << "]: " << list[i] << " *(arr + " << i << "): " << *(arr + i) << " arr[" << i << "]: " << arr[i] << endl;
	}
	return 0;
}

 

 

*(list + i), *(arr + i), list[i], arr[i] 이렇게 4개가 모두 똑같은 값을 출력하는 것을 볼 수 있다.

그리고 int 타입의 배열이기 때문에 주소는 4바이트씩 차이나는 것도 볼 수 있다. arr과 list은 거의 똑같다고 보면 된다.

굳이 차이점을 꼽자면 arr은 arr 자체가 005CFC34인거고 list는 4바이트 메모리 공간이 있고 그 공간에 005CFC34가 저장되어 있는 포인터 변수이다.

 

여기에서 주의해야 할 점은 arr나 list는 배열 전체를 가리키는 것이 아니다.

list는 arr이라는 배열의 첫 번째 원소를 가리키는 포인터 변수이다. arr의 시작 주소를 복사해서 list라는 포인터 변수에 그 주소를 저장한 것이다.

배열의 시작 주소(첫 번째 원소의 주소)는 가지고 있기 때문에 그 주소를 기준으로 얼마만큼 떨어져 있는 메모리에 접근하는 것이다. 그렇게 때문에 시작 주소 하나만 가지고 있어도 배열의 모든 원소에 접근이 가능하다.

따라서

cout << list[7];

이렇게 인덱스를 벗어나서 접근을 해도 아무런 오류가 나지 않는다.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	int* list = arr;
	cout << list[7] << endl; //배열 인덱스를 벗어남
	return 0;
}

 

 

배열의 인덱스를 벗어나도 컴파일 에러, 실행 오류 모두 나지 않고 그 메모리에 저장되어 있던 아무 의미 없는 쓰레기값을 출력한다. 아니면 다른 프로그램에서 쓰고 있는 데이터를 마음대로 접근해서 출력한 것이다.

 

지금은 출력만 했으니 망정이지

list[7] = 0;

이렇게 그 메모리에 저장된 값을 마음대로 변경해버리면 정말 심각한 상황이 발생할 수도 있다.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	int* list = arr;
	list[7] = 0; //이 주소에 저장된 값을 마음대로 바꿈
	cout << list[7] << endl;
	return 0;
}

이렇게 심각한 짓을 저질러도 아무런 오류 없이 이렇게

 

 

0을 출력한다.

 

아무튼 배열의 시작주소를 가리키는 포인터는 배열 전체를 가리키는 게 아니라는 것을 꼭 기억해야 한다.

 

번외로 배열과 포인터에서 정말 흥미로운 것을 발견할 수 있다.

덧셈은 교환법칙이 성립한다.

따라서 arr + i와 i + a는 똑같고 *(arr + i)나 *(i + a)는 똑같다

즉, arr[i]와 i[arr]은 똑같다.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 1,2,3,4,5 };
	cout << "arr[2]: " << arr[2] << endl;
	cout << "2[arr]: " << 2[arr] << endl;
	return 0;
}

 

위 코드의 실행 결과

 

2[arr]과 같은 요상한 코드를 실행해도 컴파일러는 컴파일할 때 *(2 + arr)로 하기 때문에 아무런 문제가 없는 것이다.

 

또 다른 흥미로운 것이 있다.

배열을 함수의 인자로 넘길 때 우리는

함수이름(배열이름, 배열의 크기);

이렇게 호출한다.

 

예를 들어 크기가 5인 int형 배열 arr을 함수 print의 인자로 넘겨주려면

print(arr, 5);

이렇게 호출하면 된다.

 

그러면 받는 쪽에서는

void print(int arr[], int size) {
	for(int i = 0; i < size; i++) {
    	cout << arr[i] << endl;
    }
}

int arr[] 이렇게 받는다. 여기에서 받은 arr[]은 배열 arr의 시작주소이다. 인자로 넘겨줄 때 arr을 인자로 준다는 것은 배열의 주소를 넘겨준 것이다.

그런데 주소는 포인터이기 때문에 함수를 정의할 때

void print(int* arr, int size) {
	for(int i = 0; i < size; i++) {
    	cout << arr[i] << endl;
    }
}

int arr* 이렇게 포인터로 받아도 된다.

int arr[]로 받으나 포인터인 int arr*로 받으나 함수 내용을 바꾸지 않아도 멀쩡히 잘 동작한다.

함수의 인자로 배열을 넘겨줄 때에는 복사본이 넘어가는 것이 아니라 원본이 넘어간다는 말을 많이 들었을 것이다. (일반적인 변수는 복사본이 넘어간다. call by value)

 

배열은 함수로 넘길 때 원본이 넘어간다는 것은 다음 코드의 실행결과를 보면 알 수 있다.

#include <iostream>
using namespace std;

void changeArray(int* arr, int size) {
	arr[2] = 10;
}

int main() {
	int arr[5] = { 1,2,3,4,5 };
	changeArray(arr, 5);
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << endl;
	}
	return 0;
}

 

위 코드의 실행 결과

 

함수를 호출한 뒤에 배열 arr의 원소를 출력해봤더니 arr[2]가 10으로 변경된 것을 알 수 있다.

즉 배열은 원본이 넘어간다. (배열 전체를 매개변수로 넘기면 너무 용량이 커서 메모리를 너무 많이 차지하기 때문에 이렇게 한 것이다.)

 

그냥 일반 변수의 경우

#include <iostream>
using namespace std;

void changeA(int aa) {
	aa = 10;
}

int main() {
	int a = 5;
	changeA(a);
	cout << a << endl;
	return 0;
}

 

 

 

함수를 호출한 후에도 a는 변경되지 않는다.

changeA 함수가 호출되면 그 인자로 들어온 a의 값 5를 매개변수 aa에 복사하는 것이다. aa = 5; 이렇게.

그다음 그 aa를 10으로 바꾸는 것이다. 그러니까 당연히 원본 a는 변하지 않는다.

 

아무튼 배열은 일반 변수와 달리 복사본이 아닌 원본이 넘어간다고 알고 있을 텐데 이것은 엄밀히 말하면 정확하지 않다.

함수를 호출할 때에는 배열의 이름 즉 배열의 시작 주소를 넘기고

호출된 함수에서는 매개변수로 그 배열의 주소를 받는데 그 주소의 복사본이 넘어가는 것이다.

아까 그 일반 변수 a가 넘어간 것과 같이 배열의 주소가 하나의 변수로 넘어간 것이다.

그리고 나서 함수 내부에서는 그 주소를 통해 배열에 접근하는 것이다. 따라서 배열의 원본이 넘어간 것과 같은 효과를 하지만 사실은 배열의 원본이 넘어간 것이 아니라 배열의 주소가 복사본으로 넘어간 것이다.

 

프로그래밍 기초를 배울 때에는 포인터도 아직 안 배운 학생에게 이런 얘기까지 하면 너무 어렵기 때문에 일단은 그냥 배열은 원본이 넘어간다고 설명하는 것 같다.

 

그리고 배열에서 포인터를 썼을 때의 장점이 또 있다. 함수의 리턴 값으로 배열을 반환할 수는 없다. 하지만 포인터 즉 주소를 함수의 반환값으로 반환할 수는 있다.

 

이것에 대한 포스팅은 다음 포스팅에 이어가겠습니다. (링크 ↓)

https://breakcoding.tistory.com/310

 

[C++] 포인터와 동적 메모리 할당 (포인터 심화2)

지난 번에 올린 이 글에 이어지는 내용이다. (링크↓) https://breakcoding.tistory.com/294 [C++] 포인터와 배열 (포인터 심화) 지난 번에 올린 이 글에 이어지는 내용이다. (링크↓) https://breakcoding.tistor..

breakcoding.tistory.com

 

 

C++에서 배열을 선언하고 초기화를 해주지 않으면 배열은 쓰레기 값으로 채워져있다.

 

아래의 코드를 실행해보면

#include <iostream>
using namespace std;

int main() {
	int arr[5];
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

 

이렇게 쓰레기 값으로 채워져있는 것을 볼 수 있다.

따라서 배열을 선언하면 초기화를 꼭 해주는 것이 좋다.

 

배열 arr의 원소를 모두 0으로 초기화하고 싶다면

int arr[5] = {};

이렇게 해주면 된다.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = {};
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

위 코드의 실행 결과

배열 arr의 원소 5개가 모두 0으로 초기화된 것을 볼 수 있다.

 

만약에 배열의 0번째 원소만 10으로 초기화하고 나머지는 다 0으로 초기화하고 싶다면?

int arr[5] = { 10 };

또는

int arr[5] = { 10, };

이렇게 해주면 된다.

여기에서 주의할 점은 위 코드는 모든 원소를 10으로 초기화하는 것이 아니라 첫 번째 원소만 10으로 초기화하는 것이라는 것이다. 이 부분을 잘못 알고 있기 쉬우므로 꼭 기억하자.

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 10 };
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

위 코드의 실행 결과

첫 번째 원소만 10으로 초기화된 것을 볼 수 있다.

 

첫 번째 원소를 10으로, 두 번째 원소를 3으로, 나머지는 0으로 초기화하고 싶다면

int arr[5] = { 10, 3 }

이렇게 하면 된다.

 

#include <iostream>
using namespace std;

int main() {
	int arr[5] = { 10, 3 };
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

위 코드의 실행 결과

첫 번째 원소가 10, 두 번째 원소가 3으로, 나머지는 0으로 초기화 된 것을 볼 수 있다.

 

그런데 만약 모든 원소를 0이 아닌 특정 값으로 초기화하고 싶다면?

배열의 크기가 5 정도로 작다면

int arr[5] = { 10, 10, 10, 10, 10 }

이렇게 직접 초기화 해줄 수 있지만 배열의 크기가 1000, 10000, 100000 등 큰 수라면 이렇게 직접 초기화 하기에는 무리가 있다.

 

그럴 때 일반적으로 이렇게 반복문으로 초기화를 시킨다.

#include <iostream>
using namespace std;

int main() {
	int arr[5];
	for (int i = 0; i < 5; i++) {
		arr[i] = 10;
	}
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

이렇게 10으로 초기화된 것을 볼 수 있다.

 

하지만 함수를 이용해서 더 편리하게 초기화할 수 있는 방법이 있다.

바로 <algorithm> 헤더에 있는 fill 함수이다.

http://www.cplusplus.com/reference/algorithm/fill/

 

fill - C++ Reference

12345678 template void fill (ForwardIterator first, ForwardIterator last, const T& val) { while (first != last) { *first = val; ++first; } }

www.cplusplus.com

fill 함수를 사용하려면 일단 <algorithm> 헤더를 포함해줘야 한다.

사용 방법은

fill(초기화 시키고 싶은 부분의 시작 주소, 초기화시키고 싶은 부분의 끝 주소, 초기화할 값);

이렇게 사용하면 된다.

배열이라면

fill(arr, arr + 5, 10);

이렇게 하면 되고

 

벡터라면

fill(v.begin(), v.end(), 10);

이렇게 해주면 된다.

 

아래 코드를 실행하면

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	int arr[5];
	fill(arr, arr + 5, 10);
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

이렇게 모든 원소가 10으로 초기화되어 있는 것을 볼 수 있다.

 

for(int i = 0; i < 5; i++) {
	arr[i] = 10;
}

이 for문을 사용한 코드와

fill(arr, arr + 5, 10);

이 한 줄은 같은 역할을 한다.

 

fill(arr, arr + 2, 10);
fill(arr + 2, arr + 5, 20);

이렇게 부분 부분씩 초기화시켜줄 수도 있다.

 

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	int arr[5];
	fill(arr, arr + 2, 10);
	fill(arr + 2, arr + 5, 20);
	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

전체 코드이다.

위 코드의 실행 결과

 

벡터도 마찬가지이다.

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
	vector<int> v(5);
	fill(v.begin(), v.end(), 10);
	for (int i = 0; i < 5; i++) {
		cout << v[i] << " ";
	}
	cout << endl;
	return 0;
}

이 코드를 실행하면

이렇게 10으로 초기화된다.

 

하지만 벡터는 굳이 fill 함수를 안 쓰고

vector<int> v(5, 10);

이렇게 선언과 동시에 10으로 초기화하는 것이 더 편할 것이다.

(벡터 초기화 및 사용법↓)

https://breakcoding.tistory.com/139

 

[C++] 라이브러리, 벡터 클래스, 동적 할당

만약에 입력으로 n을 입력받아 크기가 n인 배열을 만들고 싶다면 어떻게 해야 할까? int n; cin >> n; int arr[n]; 이렇게 하면 될까? 이렇게 선언하면 컴파일 에러가 나는 것을 볼 수 있다. 배열의 크기를 나타..

breakcoding.tistory.com

 

순열은 재귀함수로 구현할 수 있다. 그리 어렵진 않지만 약간 까다로운 부분도 있고 재귀함수가 어려울 수도 있다.

C++ <algorithm> 헤더에 정의되어 있는 편리한 함수인 next_permutation을 사용하면 순열을 쉽게 구할 수 있다.

(재귀를 이용한 순열 구하는 법 링크↓)

https://breakcoding.tistory.com/13

 

[알고리즘] 순열 (Permutation)

nPr의 모든 경우를 출력하는 코드이다. import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); //1부터 n까지의..

breakcoding.tistory.com

http://www.cplusplus.com/reference/algorithm/next_permutation/

 

next_permutation - C++ Reference

function template std::next_permutation default (1)template bool next_permutation (BidirectionalIterator first, BidirectionalIterator last); custom (2)template bool next_permutation (BidirectionalIterator first, BidirectionalIterator last, Comp

www.cplusplus.com

순열을 구할 때에는 재귀로 구하는 방법과 직접 배열의 원소를 바꿔가면서 구하는 방법이 있는데 next_permutaion 함수가 바로 두 번째 방법이다. next_permutation 함수는 배열의 원소를 직접 바꾼다.

그리고 next_permutation 함수로 순열을 구하기 전에 전제 조건은 배열 또는 벡터가 오름차순으로 정렬되어 있어야 한다.

next_permutation 함수를 호출하면 bool 타입을 리턴한다. 모든 순열을 다 구했다면 false를 리턴한다.

next_permutation 함수의 매개변수는 두 개인데 순열을 구하고 싶은 배열의 범위를 주소로 지정해주면 된다.

sort() 함수와 똑같이 [시작주소, 끝 주소) 이렇게 두 번째 인자는 열린 범위로 주면 된다.

배열이라면 next_permutation(arr, arr + 8), 벡터라면 next_permutation(v.begin(), v.end()) 이렇게 하면 된다.

 

1, 2, 3, 4를 가지고 모든 순열을 구하는 코드는 다음과 같다.

#include <iostream>
#include <algorithm>
using namespace std;


int main() {
	int arr[] = { 1, 2, 3, 4 };
	do {
		for (int i = 0; i < 4; i++) {
			cout << arr[i] << " ";
		}
		cout << endl;
	} while (next_permutation(arr, arr + 4));
	return 0;
}

위 코드의 실행 결과

이렇게 do-while문을 벗어나면 배열은 next_permutation을 호출하기 전 처음 상태가 된다.

do-while문 바깥에 이 코드를 추가해주면

cout << "다 돌고 난 뒤: ";
for (int i = 0; i < 4; i++) {
	cout << arr[i] << " ";
}
cout << endl;

호출하기 전 상태인 1 2 3 4로 다시 돌아와있는 것을 볼 수 있다.

 

C++로 트리 자료구조를 구현할 때에는 보통 왼쪽 자식을 가리키는 포인터, 오른쪽 자식을 가리키는 포인터, 내 데이터 이렇게 3가지 정보를 가지고 있는 Node라는 구조체를 만들어서 구현한다.

그런데 이 방법은 알고리즘 문제를 풀 때 직접 구현하기에는 너무 복잡하고 포인터를 이용하기 때문에 nullpointer 예외가 나기도 쉽다.

이진트리는 C++ STL인 map과 pair를 이용해서 쉽게 구현할 수 있다.

(만약 map을 잘 모르신다면 이 글을 먼저 보고 오는 것을 추천한다. 링크↓)

https://breakcoding.tistory.com/154

 

[C++] STL

라이브러리

map은 데이터를 key-value 형태로 저장하는 자료구조이다. http://www.cplusplus.com/reference/map/map/ map - C++ Reference difference_typea signed integral type, identical to: iterator_traits ::differen..

breakcoding.tistory.com

 

다음과 같은 이진트리가 있다고 하자.

이진 트리

 

일단 트리를 저장할 map 객체를 선언한다. 이 트리에는문자가 저장되어 있기 때문에 char 타입으로 선언했다.

pair에는 왼쪽 자식 노드와 오른쪽 자식 노드를 집어넣을 것이다.

map<char, pair<char, char>> tree;

A의 오른쪽 자식은 B, 왼쪽 자식은 C이므로 A 노드를 다음과 같이 집어넣어준다.

tree['A'] = make_pair('B', 'C');

B의 왼쪽 자식 노드는 D, 오른쪽 자식 노드는 없다. 자식이 없을 경우 .으로 표현하기로 하자. (NULL로도 표현해도 되고 내가 원하는 방식으로 표현해주면 된다. 대신 데이터로는 절대 나올 수 없는 것으로 선택해야 한다.)

tree['B'] = make_pair('D', '.');

D는 왼쪽, 오른쪽 자식이 모두 없으므로 tree['D']에는 이렇게 저장해준다.

tree['D'] = make_pair('.', '.');

이런식으로 모든 노드들을 tree라는 map에 저장해준다.

tree['C'] = make_pair('E', 'F');
tree['E'] = make_pair('.', '.');
tree['F'] = make_pair('.', 'G');
tree['G'] = make_pair('.', '.');

이렇게 노드의 개수만큼 map에 저장하는 것을 반복해주면 이진트리가 완성된다.

A 노드의 왼쪽 자식을 알고 싶으면

tree['A'].first

A의 오른쪽 자식을 알고 싶으면

tree['A'].second

이렇게 해주면 된다.

 

(pair 사용법이 낯설다면 이전 포스팅을 보고 오는 것을 추천한다. 링크↓)

https://breakcoding.tistory.com/96

 

[C++] 라이브러리 - tuple, pair

코딩을 하다가 (x, y) 이런 순서쌍이 필요할 수도 있고 세 가지 정보를 묶어서 저장해야 할 수도 있다. 이럴 때 라이브러리를 사용하면 편리하다. 2-tuple은 주로 pair라고 부른다. 튜플에는 2-tuple(pair..

breakcoding.tistory.com

 

일반적인 이진트리 구현방법인 포인터를 이용한 구조체로 트리를 구현했다면 무조건 가장 아래쪽에 있는 노드 D, 노드 E, 노드 G부터 만들어주고 위로 올라가야 한다. 따라서 그냥 이음선의 정보만 주어진다면 트리를 구현하기 위해서 위상정렬까지 해야 하는 상황이 발생한다. 하지만 이렇게 map을 이용해서 이진트리를 구현한다면 순서와 상관없이 이렇게 이음선의 정보만 map에 추가해주면 되기 때문에 트리를 쉽게 구현할 수 있다.

 

그러면 이렇게 map으로 구현한 이진트리를 전위순회, 중위순회, 후위순회 하는 방법을 알아보자.

void preorder(char node) {//전위순회
	cout << node << " "; //현재 노드의 데이터 출력
	if (m[node].first != '.') { //왼쪽 자식이 있다면
		preorder(m[node].first);
	}
	if (m[node].second != '.') { //오른쪽 자식이 있다면
		preorder(m[node].second);
	}
}
void inorder(char node) {//중위순회
	if (m[node].first != '.') { //왼쪽 자식이 있다면
		inorder(m[node].first);
	}
	cout << node << " "; //현재 노드의 데이터 출력
	if (m[node].second != '.') { //오른쪽 자식이 있다면
		inorder(m[node].second);
	}
}
void postorder(char node) { //후위순회
	if (m[node].first != '.') { //왼쪽 자식이 있다면
		postorder(m[node].first);
	}
	if (m[node].second != '.') { //오른쪽 자식이 있다면
		postorder(m[node].second);
	}
	cout << node << " "; //현재 노드의 데이터 출력
}

이렇게 재귀함수로 쉽게 순회할 수 있다. 만약 자식 노드가 없을 때에 NULL로 저장했다면

왼쪽 자식이 있는지 체크하는 부분을 m[node].first != NULL로

오른쪽 자식이 있는지 체크하는 부분을 m[node].second != NULL로 해주면 된다.

숫자처럼 생긴 문자열을 숫자로 바꿔야 할 때가 있다. 아니면 숫자를 문자열로 바꿔야 할 때도 있을 수 있다.

그럴 때 쓰는 것이 stoi, stof, stol, stod, stold, stoll 함수이다.

이 함수들은 string 클래스에 정의되어 있기 때문에 이 함수들을 사용하려면 <string> 헤더파일을 포함해야 한다.

+stoi(s: string): int 문자열을 int로 바꾼다
+stol(s: string): long 문자열을 long으로 바꾼다.
+stof(s: string): float 문자열을 float로 바꾼다.
+stod(s: string): double 문자열을 double로 바꾼다.
+stold(s: string): long double 문자열을 long double로 바꾼다.
+stoll(s: string): long long 문자열을 long long으로 바꾼다.
#include <iostream>
#include <string>
using namespace std;


int main() {
	string s = "100";
	int a = stoi(s); //문자열 s를 정수형으로 바꿈
	cout << a + 45 << endl;
	return 0;
}

문자열일 때에는 정수와 더하기가 불가능했는데 int형으로 바꾸니 더하기가 멀쩡하게 되는 것을 볼 수 있다.

 

그렇다면 char형을 정수로 바꾸고 싶다면?

char형은 정수이기 때문에 0의 아스키코드인 48을 빼주면 된다. 0의 아스키코드를 기억하기 힘들다면 '0'을 빼주면 된다.

#include <iostream>
using namespace std;


int main() {
	char c = '9';
	int a = c - '0';
	cout << a + 10 << endl;
	return 0;
}

C++로 코딩을 하다가 이진탐색을 할 필요가 있다면 직접 구현할 필요없이 <algorithm> 헤더에 정의되어 있는 binary_search() 함수를 사용하면 된다. C++에서 이진탐색을 어떻게 하는지 알아보자.

 

일단 이진탐색이라는 것은 데이터가 정렬되어 있다는 전제하에 가능하다.

정렬을 하려면 sort() 함수를 이용하면 된다.

정렬을 설명하기엔 너무 길어질 것 같으니 sort() 함수 사용법은 이전 포스팅을 참고하면 된다.(↓링크)

https://breakcoding.tistory.com/117?category=876361

 

[C++] vector (벡터) 정렬, 배열 정렬하기

정렬을 하려면 라이브러리의 sort() 함수를 쓰면 된다. 따라서 헤더파일을 포함해줘야 한다. sort() 함수의 첫번째 두번째 매개변수는 iterator, 즉 포인터이다. sort - C++ Reference cu..

breakcoding.tistory.com

binary_search 함수의 사용법은 간단하다.

http://www.cplusplus.com/reference/algorithm/binary_search/

 

binary_search - C++ Reference

function template std::binary_search default (1)template bool binary_search (ForwardIterator first, ForwardIterator last, const T& val); custom (2)template bool binary_search (ForwardIterator first, ForwardIterator last, const T& val, Compare c

www.cplusplus.com

binary_search() 함수의 첫 번째, 두 번째 인자는 내가 어디에서 찾고 싶은지 (배열이든 벡터든) 그 자료구조의 시작 주소끝나는 주소를 넣어주면 된다. 주의해야 할 것은 주소를 넣어줘야 한다는 것이다.

찾고 싶은 곳이 크기가 6인 arr라는 배열이라면 arr를 첫 번째 인자로, arr+6을 두 번째 인자로 넣어주면 된다.

찾고 싶은 곳이 v라는 벡터라면 v.begin()을 첫 번째 인자로, v.end()를 두 번째 인자로 넣어주면 된다.

세 번째 인자는 그 곳에 있는지 없는지 궁금한 내가 찾고 싶은 대상이다.

반환 타입은 bool 타입으로, 있으면 1(true), 없으면 0(false)를 반환한다.

 

크기가 10인 정렬된 배열 arr에 3이 있는지 없는지 알고 싶다면

bool isIn = binary_search(arr, arr + 10, 3);

정렬된 벡터 v에 3이 있는지 없는지 알고 싶다면

bool isIn = binray_search(v.begin(), v.end(), 3);

이렇게 해주면 된다.

arr 배열에 또는 v 벡터에 3이 있다면 isIn에는 true가 저장되고 3이 없다면 isIn에는 false가 저장된다.

 

#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;


int main() {
	int arr[] = { 3, 16, 7, 1, 20, 8, 3, 10, 25, 18 };
	sort(arr, arr + 6); //이진탐색 전에 반드시 정렬을 해줘야 한다.
	cout << "arr에 8이 있나요? " << boolalpha << binary_search(arr, arr + 6, 8) << endl;
	cout << "arr에 2가 있나요? " << boolalpha << binary_search(arr, arr + 6, 2) << endl;
	return 0;
}

8은 배열 v에 있으니 true를, 2는 배열 arr에 없으니 false를 반환하는 것을 볼 수 있다.

 

벡터도 마찬가지이다.

#include <iostream>
#include <algorithm>
#include <iomanip>
#include <vector>
using namespace std;


int main() {
	vector<int> v = { 3, 16, 7, 1, 20, 8, 3, 10, 25, 18 };
	sort(v.begin(), v.end()); //이진탐색 전에 반드시 정렬을 해줘야 한다.
	cout << "arr에 8이 있나요? " << boolalpha << binary_search(v.begin(), v.end(), 8) << endl;
	cout << "arr에 2가 있나요? " << boolalpha << binary_search(v.begin(), v.end(), 2) << endl;
	return 0;
}

 

하지만 있는지 없는지 true, false로 반환을 해줄 뿐 어느 인덱스에 있는지는 binary_search 함수로는 알 수 없다.

그래서 사용하는 것이 upper_bound, lower_bound 함수이다.

upper_bound와 lower_bound의 첫 번째, 두 번째 인자는 내가 찾고 싶은 범위를 주소로 지정해주면 된다. 꼭 배열 전체, 벡터 전체일 필요는 없다. 해당 범위를 지정해주고 그 안에 있는지만 찾고 싶으면 그렇게 지정해주면 된다. binary_search 함수의 인자와 똑같다.

세 번째 인자도 binary_search의 세 번째 인자처럼 몇 개 있는지 알고 싶은 그 대상을 적어주면 된다.

 

lower_bound 함수는 내가 찾고자 하는 그 대상이 저장된 인덱스 중 가장 작은 인덱스를 반환한다.

upper_bound 함수는 내가 찾고자 하는 그 대상이 저장된 인덱스 중 가장 큰 인덱스+1을 반환한다.

 

예를 들어 정렬된 arr 배열에 다음과 같이 저장되어 있었다고 하자.

1 3 4 4 7 10 10 10 13 17

lower_bound(arr, arr + 10, 10)을 한다면 10이 처음 등장하는 인덱스인 인덱스 5의 주소를 반환한다.

upper_bound(arr, arr + 10, 10)을 한다면 10이 마지막으로 등장하는 인덱스+1인 인덱스 8의 주소을 반환한다.

iterator(메모리 주소)를 반환하기 때문에 몇 번째 인덱스인지 알아보고 싶으면 배열이나 벡터의 시작주소를 빼보면 된다.

 

일단 lower_bound와 upper_bound 함수의 반환값을 알아보기 위해서 그냥 출력해보면

#include <iostream>
#include <algorithm>
using namespace std;


int main() {
	int arr[] = { 1, 3, 4, 4, 7, 10, 10, 10, 13, 17 };
	sort(arr, arr + 10);
	cout << "lower_bound: " << lower_bound(arr, arr + 10, 10) << endl;
	cout << "upper_bound: " << upper_bound(arr, arr + 10, 10) << endl;
	return 0;
}

이렇게 주소를 반환하는 것을 알 수 있다. 004FFBE4에서 004FFBD4를 빼면 12이다. arr가 int 타입이므로 인덱스 5의 주소와 8의 주소는 3 x 4byte 총 12바이트 차이나는 것이다.

 

이렇게 주소 말고 인덱스로 보고 싶다면 이렇게 시작주소를 빼주면 된다. 

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;


int main() {
	vector<int> v = { 1, 3, 4, 4, 7, 10, 10, 10, 13, 17 };
	sort(v.begin(), v.end());
	cout << "lower_bound: " << lower_bound(v.begin(), v.end(), 10) - v.begin() << endl;
	cout << "upper_bound: " << upper_bound(v.begin(), v.end(), 10) - v.begin() << endl;
	return 0;
}

#include <iostream>
#include <algorithm>
using namespace std;


int main() {
	int arr[] = { 1, 3, 4, 4, 7, 10, 10, 10, 13, 17 };
	sort(arr, arr + 10);
	cout << "lower_bound: " << lower_bound(arr, arr + 10, 10) - arr << endl;
	cout << "upper_bound: " << upper_bound(arr, arr + 10, 10) - arr << endl;
	return 0;
}

이렇게 10의 시작인덱스, 10의 마지막 인덱스+1을 출력하는 것을 볼 수 있다. 그 인덱스에 뭐가 들어있는지 알아보고 싶으면 역참조 연산자 *로 데이터에 접근해서 출력해보면 된다. 10의 마지막 인덱스 한 칸 뒤에는 13이 저장되어 있으니 13이 출력될 것을 알 수 있다.

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;


int main() {
	int arr[] = { 1, 3, 4, 4, 7, 10, 10, 10, 13, 17 };
	sort(arr, arr + 10);
	cout << "lower_bound: " << *lower_bound(arr, arr + 10, 10) << endl;
	cout << "upper_bound: " << *upper_bound(arr, arr + 10, 10) << endl;
	return 0;
}

역참조 연산자가 익숙치 않다면 이전 포스팅을 참고하면 도움이 될 것이다. (↓링크)

https://breakcoding.tistory.com/81

 

[C++] 포인터

포인터를 어려워하는 분들이 많은 것 같다. 물론 나도 겁먹었었다. 하지만 전혀 어렵지 않다는 것을 알게 되었고 다른 사람들에게도 포인터가 어렵지 않다는 것을 알려주고 싶다. 일반적으로 변수는 정수, 실수,..

breakcoding.tistory.com

10이 arr에 몇 개가 있는지 알고 싶다면 upper_bound에서 lower_bound를 빼보면 된다.

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
	int arr[] = { 1, 3, 4, 4, 7, 10, 10, 10, 13, 17 };
	sort(arr, arr + 10);
	cout << "arr에 10은 " << upper_bound(arr, arr + 10, 10) - lower_bound(arr, arr + 10, 10) << "개가 있다" << endl;
	return 0;
}

C++에는 조금 더 보기 좋게 출력하게 하기 위해 스트림 조종자(manipulator)가 있다.

스트림 조종자는 <iomanip>과 <iostream> 헤더파일을 포함하면 사용할 수 있다.

사실 boolalpha, showbase, showpoint, dec, hex, oct는 <ios>에 있는 함수들인데 iostream은 ios의 자식 클래스이기 때문에 우리가 C++로 코드를 짤 때 거의 항상 포함하는 <iostream>을 포함해도 사용할 수가 있기 때문에 <iostream>이 있는데 굳이 <ios>를 따로 포함해줄 필요는 없다.

 

이 표는 manipulator와 그 기능이다.

setprecision(int n) 실수의 정밀도 설정
setw(int n) 출력되는 영역의 폭 설정
setfill(char c) 출력하고 남은 부분을 c로 채움
fixed 실수를 고정된 부동소수점 형식으로
showpoint 실수의 소수점 부분이 없어도 소수점 아래에 0을 표시
left 왼쪽 정렬
right 오른쪽 정렬
boolalpha bool 값을 1, 0이 아닌 true, false로 출력
showbase 진법기호(16진수의 경우 0x, 8진수의 경우 0)도 출력
dec 정수를 10진수로 출력
hex 정수를 16진수로 출력
oct 정수를 8진수로 출력

다음 코드를 실행하면

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	double a = 3.14159265358979323;
	double b = 31.4159265358979323;
	double c = 314.159265358979323;
	double d = 3141.59265358979323;
	double e = 31415.9265358979323;
	double f = 314159.265358979323;
	double g = 3141592.65358979323;
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << d << endl;
	cout << e << endl;
	cout << f << endl;
	cout << g << endl;
	return 0;
}

이런 결과가 나온다. 즉 실수는 기본적으로 소수점의 위치와는 상관 없이 6자리까지만 표기된다.

그리고 6자리가 넘어가면 3141592.65358979323처럼 3.14159e+06 이렇게 과학적 표기법으로 표기되는데 이게 싫다면 fixed를 이용하면 된다.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	double a = 3.14159265358979323;
	double b = 31.4159265358979323;
	double c = 314.159265358979323;
	double d = 3141.59265358979323;
	double e = 31415.9265358979323;
	double f = 314159.265358979323;
	double g = 3141592.65358979323;
	cout << fixed << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << d << endl;
	cout << e << endl;
	cout << f << endl;
	cout << g << endl;
	return 0;
}

이렇게 fixed만 한 번 써주면

e를 사용한 과학적 표기법이 아니라 우리가 일상에서 쓰는 소수점 표현으로 출력된다. 그리고 fixed를 사용해주니 전체 자리수가 6자리까지가 아니라 소수점 아래 6자리까지 출력된다. fixed는 기본이 소수점 아래 6자리이다.

 

그런데 실제로 내가 저장한 숫자보다 출력된 숫자가 소수점 아래가 잘려서 정확도가 너무 떨어진다면 setprecision 함수를 사용해서 몇 자리를 표기할지 정해주면 된다.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	double a = 3.14159265358979323;
	double b = 31.4159265358979323;
	double c = 314.159265358979323;
	double d = 3141.59265358979323;
	double e = 31415.9265358979323;
	double f = 314159.265358979323;
	double g = 3141592.65358979323;
	cout << setprecision(18) << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << d << endl;
	cout << e << endl;
	cout << f << endl;
	cout << g << endl;
	return 0;
}

 

이렇게 setprecision() 함수로 정해주면 18자리가 모두  나타난 것을 볼 수 있다. setprecision() 함수는 한 번만 써주면 setprecision() 함수로 다시 바꾸기 전까지는 계속 유효하다.

하지만 소수점 아래쪽으로 갈수록 정확도가 떨어지는 것을 볼 수 있다.

 

그럼 setprecision() 함수를 fixed와 같이 써보자.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	double a = 3.14159265358979323;
	double b = 31.4159265358979323;
	double c = 314.159265358979323;
	double d = 3141.59265358979323;
	double e = 31415.9265358979323;
	double f = 314159.265358979323;
	double g = 3141592.65358979323;
	cout << fixed << setprecision(18) << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << d << endl;
	cout << e << endl;
	cout << f << endl;
	cout << g << endl;
	return 0;
}

내가 저장했던 숫자인 314159265358979323까지는 모두 아주 정확하게 출력된 것을 볼 수 있다. 그 아래 자리는 아무 숫자나 출력되었다.

fixed를 사용하고 setprecision() 함수를 사용하면 소수점 아래 몇 번째 자리까지 나타낼 것인지를 정해주는 것이다. 그냥 setprecision() 함수를 사용했을 때에는 소수점과 상관없이 숫자 몇 개를 나타낼지를 정해주는 거였다. 둘의 차이를 기억하자.

그리고 setprecision()을사용할 때에는 원래 숫자의 자릿수보다 더 큰 숫자를 넣으면 저렇게 이상한 숫자가 출력될 수도 있다. 실수는 정수와 달리 저장할 때 정확도가 떨어진다. 모든 정수는 모두 정확한 이진수로 바꿀 수 있지만 실수는 소수점 아래를 정확한 이진수로 표현하기 힘든 경우가 있기 때문이다.

 

하지만 위의 예처럼 소수점 아래가 매우 많은 숫자 말고 이 정도 숫자는 정확하게 출력한다.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	double a = 3.14;
	double b = 3.1415;
	double c = 3.1415926;
	cout << setprecision(5) << a << endl;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

그리고 setprecision() 함수를 사용해서 잘린 부분은 그 뒤의 수에서 반올림해서 출력된다.

 

그런데 만약 모든 수의 소수점 아래 자리가 동일하고 3.14와 같 소수점 아래 숫자가 짧은 것들은 0으로 채우고 싶다면? 그러면 showpoint를 사용하면 된다.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	double i = 3.0;
	double a = 3.14;
	double b = 3.1415;
	double c = 3.1415926;
	cout << i << endl;
	cout << showpoint << setprecision(7) << i << endl;
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

showpoint를 사용하면 이렇게 3.0과 같은 소수점 아래가 없는 숫자도 내가 정해준 만큼 0을 출력할 수 있다.

 

그러면 이제 다른 조종자를 배워보자.

#include <iostream>
using namespace std;

int main() {
	int arr[10][10];
	int num = 1;
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			arr[i][j] = num;
			num++;
		}
	}
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			cout << arr[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

이 코드를 실행하면

이렇게 줄이 안 맞아서 보기 안 좋게 출력된다.

이 때 setw()를 사용하면 줄 맞춰서 예쁘게 출력할 수 있다.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	int arr[10][10];
	int num = 1;
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			arr[i][j] = num;
			num++;
		}
	}
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			cout << setw(3) << arr[i][j];
		}
		cout << endl;
	}
	return 0;
}

setw()를 이용했더니 이렇게 줄맞춰서 예쁘게 출력되었다.

setw(n)은 n칸을 확보해놓는다는 것이다. 확보해놓고 그 안에서 내가 출력하고 싶은 것을 출력하는 것이다. 확보해놓은 칸보다 내가 출력하고 싶은 것이 더 많은 칸을 차지한다면 자동으로 칸이 증가되기 때문에 잘릴 걱정은 하지 않아도 된다.

setw()를 쓸 때 주의해야 할 것은 setw()를 쓴 직후의 출력에만 유효하다는 것이다.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	cout << setw(7) << "orange" << "cat" << "peach" << endl;
	return 0;
}

이렇게 setw(7)을 orange 직전에 써주면

이렇게 orange에만 유효하다.

따라서 이렇게 출력하기 전에 매번 써줘야 한다.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	cout << setw(7) << "orange" << setw(7) << "cat" << setw(7) << "peach" << endl;
	return 0;
}

showpoint, fixed, setprecision과는 달리 딱 한 번만 유효하다는 거 기억하자.

 

그런데 setw()를 쓰면 자동으로 오른쪽으로 정렬이 되었는데 왼쪽으로 정렬하고 싶다면?

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	int arr[10][10];
	int num = 1;
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			arr[i][j] = num;
			num++;
		}
	}
	cout << left;
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			cout << setw(3) << arr[i][j];
		}
		cout << endl;
	}
	return 0;
}

이렇게 left를 써주면 왼쪽으로 정렬되어 출력된다.

오른쪽으로 정렬하고 싶다면 left 대신 right를 써주면 된다. (right가 기본값이다.)

left도 setprecision, showpoint, fixed처럼 한 번만 써주면 right로 바꾸기 전까지는 유효하다.

(딱 한 번만 유효한 것은 setw()이다.)

 

그런데 setw()로 확보하느라 생기는 공백을 다른 문자로 채우고 싶다면?

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	int arr[10][10];
	int num = 1;
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			arr[i][j] = num;
			num++;
		}
	}
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			cout << setfill('#') << setw(3) << arr[i][j];
		}
		cout << endl;
	}
	return 0;
}

이렇게 setw 전에 setfill(채우고 싶은 문자)를 써주면 된다.

주의할 것은 문자열은 안 되고 char형 문자를 써줘야 한다.

 

표에 있지만 설명하지 않은 것들은 그냥 보면 어떻게 쓰면 되는지 간단하기 때문에 누구나 쉽게 쓸 수 있다.

이 외에도 더 많은 조종자를 알고 싶다면

http://www.cplusplus.com/reference/ios/

 

- C++ Reference

header Input-Output base classes Header providing base classes and types for the IOStream hierarchy of classes: Types Class templates basic_iosBase class for streams (type-dependent components) (class template )fposStream position class template (class tem

www.cplusplus.com

http://www.cplusplus.com/reference/iomanip/

 

- C++ Reference

 

www.cplusplus.com

여기에 들어가서 보는 것을 추천한다.

+ Recent posts