Ch06. 복합데이터 - 01. 배열(Array)
C++에서 배열(array)은 같은 자료형의 값들을 연속된 메모리 공간에 저장하는 자료 구조로 정적(static)으로 메모리가 할당되기 때문에, 크기가 고정되고 컴파일 타임에 결정되는 배열을 가장 기본적으로 사용한다.
1. 기본 배열의 선언 및 초기화
배열의 선언
배열의 기본 선언의 형태는
타입 변수명[배열의크기];
와 같이 선언한다.
int arr [10];
이때 배열의 크기는 상수로만 올 수 있고 변수를 넣어서 선언은 불가능하다.
만약 이렇게 변수를 넣어서 사이즈를 넣기 위해서는 변수를 상수로 전환시켜야만 한다.
또 다른 방법으로는 메크로를 사용해서 넣어주는 방법도 있다.
보통 메크로를 사용하는 방법보다는 const로 상수로써 size를 전달하는 방식이 조금 더 많이 사용된다.
배열의 초기화
배열의 경우 초기화가 되지 않은 값의 경우
쓰레기 값을 가지고 있다.
배열의 초기화 방법은
int arr[3] = {1, 2, 3};
과 같이 사이즈에 맞게 데이터를 넣어서 초기화할 수 도 있고
int arr[3] = {1};
이렇게 일부만 초기화 시키는 것도 가능하다.
물론 위 처럼 초기화 하는 경우 순서대로 값이 할당 되고 초기화 되지 않은 index의 값은 0으로 초기화가 된다.
그리고 size를 지정하지 않고 할당하는 값을 지정해줌으로 배열의 크기를 지정해줄 수 도 있다
이 경우에는 배열에 초기화한 값에 맞게 사이즈가 지정되고 그 값을 초과하는 index에는 쓰레기 값이 표출된다.
그리고 직접 index를 지정해서 값을 초기화해줄 수 도 있다.
이 경우에는 초기에 값을 일부만 할당했을때와는 다르게 할당하지 않은 값에는 우선적으로 쓰레기값이 할당되어 있음을 확인할 수 있다.
그리고 사실 = 이 없어도 초기화가 가능하다.
그리고 값을 넣지 않고
이렇게 초기화도 가능하다.
이때는 모두 0으로 초기화를 시켜준다.
물론 위에서 사이즈를 지정하지 않고 값으로 초기화해줄때에도 = 을 넣지 않아도 초기화가 가능하다.
//배열의 선언 및 초기화 방법
// 기본적인 배열의 선언
int arr[3]; // => 기본으로 쓰레기값으로 초기화
int arr[3]{}; // => 기본으로 0으로 초기화
// 배열의 초기화 방법:사이즈 설정 및 전체 값 초기화
int arr[3] = {1, 2 ,3}
int arr[3]{1, 2, 3} // = 연산자 없이 초기화
// 배열의 초기화 방법:사이즈 설정 및 일부 값 초기화
int arr[3] = {1} // => 할당되지 않은 값 0으로 초기화
int arr[3]{1} // = 연산자 없이 초기화 => 할당되지 않은 값 0으로 초기화
// 배열의 초기화 방법:사이즈 설정 없이 및 값 초기화
int arr[] = {1, 2, 3}
int arr[]{1, 2, 3} // = 연산자 없이 초기화
// 배열의 선언과 초기화의 분리 : 할당되지 않은 값은 쓰레기값으로 초기화되어 있음
int arr[3];
arr[0] = 1;
arr[1] = 2;
배열의 사이즈 계산하기
배열의 사이즈를 확인하기 위해서는 sizeof연산자를 사용하면 된다.
sizeof연산자의 경우는 변수의 크기를 바이트로 확인시켜주는데 이러면 배열의 경우 타입의 바이트 * 사이즈의 크기를 가지게 된다.
그러니 sizeof(배열 변수)로 넣어준 다음에 sizeof(배열의 타입)으로 나눠주면 그 배열의 크기가 나온다.
그래서 배열의 값의 전체를 출력할때 이 sizeof 연산자를 통해서 배열의 크기를 찾아서 반복문을 이용해서 배열의 값을 출력하는 방법을 많이 사용하곤 한다.
근데 std안에 size라는 함수가 있고 이걸 통해서 간단하게 이 크기를 만들어 내는것도 가능하다.
2. 배열의 활용
1) 배열의 복사
배열을 복사하는 것은 그냥 변수를 그대로 복사하듯이
이렇게 하면 될것 같지만 이렇게 배열을 그대로 복사해오는것은 불가능 하다.
그 이유는 배열의 이름만 사용하는 경우 배열의 가장 앞에 있는 주소값을 전달하는 것이기 때문이다.
그렇기에 배열에 저렇게 주소값을 그냥 그대로 저장하는 것은 불가능하다.
그럼 주소값으로 덮어 씌우면 되지않은가 싶지만 지금은 조금 이해가 안될 수 있어도 나중에 포인터를 배울때 다시 공부하기로 하자.
그래서 대입하고자 한다면 for문을 이용해서 값을 그대로 전달해줘야만 한다.
이 방법 말고도 std에 있는 memcpy라는 함수를 통해서도 배열의 복사가 가능하다
memcpy는 C++ 표준 라이브러리의 메모리 복사 함수로 메모리 블록을 바이트 단위로 복사할 때 사용한다
memcpy의 함수 원형은
void* std::memcpy(void* dest, const void* src, std::size_t count);
이며 각 매개변수는
- dest : 복사한 데이터를 저장할 목적지 메모리 포인터
- src : 복사할 데이터의 원본 메모리 포인터
- count : 복사할 바이트 수
로 구성되어 있다.

여기서 memcpy의 세번째 매개변수에 index의 값이 아니라 그냥 sizeof를 전달한 이유는 이건 바이트 단위로 값을 복사해오기 때문에 그 바이트 값을 전달해줘야 하기 때문이다.
추가적으로 memcpy말고 또 std에 copy라는 함수를 사용해서도 배열을 복사할 수 도 있다.
std::copy는 C++ 표준 라이브러리 <algorithm> 헤더에 정의된 범위 기반 복사 알고리즘으로, 두 개의 반복자 범위 사이에서 요소들을 복사할 때 사용되는 함수이다.
memcpy는 C스타일의 함수인데 이거랑 다르게 타입 안정성을 보장하고 객체 단위로 복사하기에 C++ 스타일 프로그래밍에 조금 더 적합하다고 볼 수 있다.
copy를 사용하기 이전에 <algorithm>을 include 해주면 사용이 가능하다.(보니까 iostream include 해주니까 안해줘도 되고 있음.. 근데 이게 당연한건지는 모르겠음)
함수의 원형은
template <class InputIt, class OutputIt>
OutputIt copy(InputIt first, InputIt last, OutputIt d_first);
와 같이 생겼으며 각각의 매개변수는
- first: 복사할 시작 위치 반복자
- last: 복사할 끝 위치 반복자 (마지막 요소 다음 위치)
- d_first: 복사 대상의 시작 반복자
로 실제 배열을 상대로 사용할때는
- first: 복사할 배열의 시작점 (arr[3]의 경우는 arr에 해당함)
- last: 복사할 배열의 종료점 (arr[3]의 경우는 arr + 3 에 해당함 )
- d_first: 복해서 넣어줄 배열의 시작점(arr1[3]의 경우는 arr1에 해당함)
과 같이 사용하면 된다.
이렇게도 복사가 가능하다.
2) 배열의 사용 예시: XOR
우리가 이전에 XOR를 사용할때 홀수 개가 나온 값을 특정해서 뽑아낼 수가 있었는데 배열을 사용하면 조금 더 간단하게 이걸 표현할 수 도 있다.
이건 이전에 했던 그냥
이거랑 같은거라고 보면 된다
사실 활용이라고 보기엔 조금 애매하나 이런걸로도 사용이 가능하다는 것을 보여주기 위해서 사용해봤다.
3) 배열의 사용 예시: 버블 정렬
버블 정렬란 정렬 알고리즘 중 하나로, 인접한 두 요소를 비교하여 교환하는 방식을 반복하면서 전체 배열을 정렬하는 매우 간단한 정렬 방식이다.
정렬 알고리즘 중에서도 가장 기본적이고 직관적인 알고리즘이지만, 성능이 좋지 않아서 실제로는 잘 사용되지 않으나 알고리즘 공부 초반에는 원리를 이해하기에 아주 좋기에 알아두면 좋다.
버블 정렬의 정렬 방식은 아래와 같다
- 첫 번째 요소부터 시작해서 인접한 두 요소를 비교
- 순서가 잘못되어 있으면 두 값을 교환
- 맨 끝까지 가면 가장 큰 값이 맨 뒤로 "거품처럼" 올라감
- 이 과정을 계속 반복 → 매번 가장 큰 수가 뒤로 밀려남
직접 숫자로 이걸 보여주면
배열 - [5, 3, 8, 4, 2]
- 1회전 (가장 큰 수를 맨 뒤로 보냄)
[5, 3] 비교 → 5 > 3 → 교환 → [3, 5, 8, 4, 2]
[5, 8] 비교 → OK
[8, 4] 비교 → 8 > 4 → 교환 → [3, 5, 4, 8, 2]
[8, 2] 비교 → 8 > 2 → 교환 → [3, 5, 4, 2, 8]
2회전
[3, 5] → OK
[5, 4] → 교환 → [3, 4, 5, 2, 8]
[5, 2] → 교환 → [3, 4, 2, 5, 8]
[5, 8] → OK
...
이런식으로 최대 배열의 크기만큼 회전 하면 모든 배열이 정렬이 되게 된다.
두개씩 차근 차근 비교해가면서 가장 큰 값을 뒤로 밀어내는 과정으로 정렬하는 것이 버블정렬이다.
이걸 코드로 순서대로 보여주자면 먼저 배열을 선언하고
for문을 하나 추가해주고
첫번째 요소와 두번째 요소를 비교해준 후에 큰 값을 뒤로 이동시키는데 잠깐 이동시킬 값을 담아둘 temp를 먼저 선언해준 다음에 조건 절을 추가하자
그리고 첫번재 요소를 temp에 담아두고 첫번째 요소를 두번째 요소에 넣은 후에 temp에 있는 값을 첫번째 요소로 넣어주자
이렇게 하면 가장 큰 값을 가장 뒤로 보낼 수 있게 된다.
변경된 배열을 실행시켜보면
이렇게 가장 큰 값이 맨 뒤로 밀려난것을 볼 수 있다.
이렇게 배열의 크기만큼 for문을 외부에 한번 더 감싸주면
이렇게 전체가 정렬되는 것을 볼 수 있다.
배열의 숫자를 다 변경하거나 크기를 늘려가면서 확인해봐도
잘 정렬하는 것을 볼 수 있다.
근데 사실 보면 첫번째 돌아갈때 가장 큰 값은 맨 뒤로 가고, 그 다음번에는 가장 큰 값이 이미 맨 뒤에 있으니 배열의 크기 - 돌은 회차만큼만 돌면 그 다음 큰 값이 순서대로 배치가 될 수 있다.
그래서 내부 for 문의 경우는 배열의 크기 - (돌은 회차 + 1) 의 크기만큼 돌리면 된다는 것을 알 수 있다
(+1을 하는 이유는 내부 포문의 경우는 배열의 크기를 하나 더 나간 arr[i + 1]을 돌리기 때문에 최대로 돌릴 수 있는 값은 i -1 이기 때문임)
이러면 낭비 없이 for문을 돌리면서 결과를 효율적으로 얻을 수 있게 된다.