열혈C - Chapter 11 1차원 배열

2024. 10. 3. 17:59Programming Language/C

내가 모두의 코드에서 갑자기 윤성우님의 열혈 C로 와서 동영상으로 강의를 듣게 된 원인이다.

이전에 배열과 포인터를 배울때도 참 어려웠는데 C에서 배열과 포인터에 대한 이해는 정말 몇번을 봐도 이해가 안되는 부분들이 많은것 같다.

내가 개발공부를 시작하고 취직을 하고 개발에 대해 조금은 접한 실무자라고 생각했지만 이 포인터 개념을 접하는 순간 머리가 하얘진다

그리고 한참은 멀었다는 생각을 하게 되는 원인이 된것도 같다.

이제 윤성우님의 배열과 포인터에 대해서 공부해보면서 조금의 근심, 걱정을 날릴 수 있는 해결책이 되었으면 좋겠다.

 

11-1 배열의 이해와 배열의 선언 및 초기화 방법

변수의 선언법과 배열의 선언법은 아래와 같다.

//변수의 선언법
변수타입 변수명;
ex) int num;

//배열의 선언법
배열타입 배열명 [배열의 길이]
ex) int arr[7]

 

배열을 선언하면 배열에 들어 있는 요소가 나란히 메모리 공간에 할당이 이루어진다

그래서 길이가 7인 int형 배열의 경우는 int형이 4바이트를 갖기에 4 x 7인 28의 크기를 가진다

배열의 크기는 sizeof 연산자를 통해서 값을 확인할 수 있다.

 

배열을 선언하는 방법이 매우 간단하기에 미리 설명을 해보았다.

이제 배열에 대해서 이해해보자.

 

배열이란 무엇인가?

다수의 변수를 선언하기 위해서 다수의 코드를 사용하는 방식은 매우 번거롭다.

그렇기에 다수의 변수 선언을 쉽게 하기 위해 배열이란것을 제공해준다.

물론 배열은 단순히 다수의 변수를 선언하기 위함은 아니다.

다수의 변수가 하지 못하는 기능을 배열이 제공해주기 때문이다.

이 배열은 1차원의 형태뿐만 아니라 다차원의 형태로도 선언이 가능하다.

 

1차원 배열 선언에 필요한 세 가지

1차원 배열을 선언하는 방법은 위에서 말했던것 처럼

// 배열을 이루는 요소(변수)의 자료형
// 배열의 이름
// 배열의 길이

// 위 세가지를 나열한 방식으로 선언하며 그 순서는 아래와 같다.
자료형 배열의이름 [배열의길이]

ex) int arr[7]

이렇게 생성한 배열의 형태는 

int arr[7]이 구성된 형태

처럼 나란히 메모리 공간에 위치하게 된다.

그럼 이렇게 선언한 배열은 어떻게 접근해야할까

 

선언된 1차원 배열의 접근

// 1차원 배열 접근 방법
배열명[접근하고자 하는 요소의 순번] = 저장하고자 하는 값

// 이걸 코드로 표준화 해보자면 아래와 같이 작성할 수 있다.

arr[index] = n // 배열 arr의 index번째 요소에 n을 저장해라 


// 1차원 배열 접근 방법의 예

arr[0] = 10	//배열 arr의 1번째 요소에 10을 저장해라
arr[1] = 15	//배열 arr의 2번째 요소에 15을 저장해라
arr[2] = 20	//배열 arr의 3번째 요소에 20을 저장해라

 

위 배열 arr은 이미 선언이 되었다는 가정하에 작성했다.

배열은 접근할때의 index값은 1부터 시작하는게 아니라 0부터 시작한다.

물론 배열을 선언할때 넣는 배열의 크기는 0부터 계산하는게 아니라 1부터 계산해서 사용한다

int arr[7]로 선언했다면 arr이란 배열의 길이는 7이 되고 접근할때의 index는 0부터 시작해서, 첫번째 요소에 접근하기 위해서는 arr[0]으로 접근해야한다는 소리이다.

 

배열, 선언과 동시에 초기화하기

배열을 선언하면서 값을 넣어 초기화도 가능하다.

이렇게 초기화하면 

int arr[5] = {1, 2, 3, 4, 5}로 초기화한 배열의 형태

이런 형태의 배열이 생성된다.

 

또 선언과 동시에 초기화 할때 배열의 크기보다 작게 값을 넣어 초기화 하는 경우 

값이 순서대로 초기화 된 후에 없는 값은 0으로 채워 준다.

int arr[5] = {1, 2}로 초기화한 배열의 형태

또한 배열의 수를 지정하지 않고 값을 넣으면 

컴파일러가 배열의 길이를 넣은 값의 길이에 맞춰 배열을 생성해준다.

int arr[] = {1, 2, 3, 4, 5, 6, 7}로 초기화한 배열의 형태

 

배열을 생성하는데 이 세가지 케이스가 아닌 경우가 없으니 이 세가지 케이스를 알고 있어야 한다.

 

11-2 배열을 이용한 문자열 변수의 표현

char형 배열의 문자열 저장과 널 문자

char형 배열에 문자열을 저장하는 방법은 아래와 같다.

이렇게 저장한 문자열은 메모리에 

이 형태로 존재하게 된다.

문자열 끝에 붙어 있는 널문자 라고 불리는 \0이 삽입되었음에 주목해야한다.

이 널문자는 문자열의 끝을 의미한다.

 

마지막에 널문자가 들어가기에 Good morning!은 13개의 문자열이지만 14의 길이를 가진 char 배열을 생성해야만 한다.

 

그리고 우리가 

이렇게 printf문을 사용해서 문자열을 전달할때도 문자열이 메모리 공간에 저장이 되고 저장 될때도 \0(널문자)를 저장한다.

차이점은 char str[14] = "Good morning!"은 char 배열에 저장된다는 것이고 printf의 경우는 메모리 공간에 저장해두고 나서 printf 함수가 호출이 되는 것이다.

 

메모리 공간에 저장되지 않은것은 인자로 전달할 수 없고 그 대상을 갖고 연산도 불가능하다.

 

아무튼 우리가 " "를 통해서 표현을 해도 메모리 공간에 저장이 되는데 char 배열은 배열에 저장되는것이 다른점이다.

 

이건 데이터를 어디엔가 있게 끔 저장하는 것과 내가 어디에 지정하고 저장하는 것과는 매우 다르고, 다시 활용이 가능한지 불가능한지를 판가름하는 원인이 된다.

 

아무튼 결과적으로 어떻게 표현하든 문자열을 표현하면 그 마지막에는 무조건 문자열의 끝을 표현하는 널문자(\0)가 끝에 붙는다는 것이다.

 

그럼 이 널 문자는 왜 붙는 것일까?

컴퓨터는 이 char형 배열의 문자열의 종료지점을 인식할 수 없기 때문이다.

예를 들어 

이런 char형 문자배열이 생성되었다고 보자.

만약 널 문자가 없다면 이 문자열의 시작점은 배열의 시작점이기 때문에 인식할 수 있으나 컴퓨터가 이 문자열의 종료지점을 인식할 수 없다.

왜냐면 결국 우리가 넣은 문자열도 아스키 코드 값으로 들어 있을 것이고 그것 또한 결국 2진 데이터로 들어 있을텐데 우리가 넣은 문자열을 제외한 그 뒤에 들어가는 값이 쓰레기값이던 0으로 체워지던 그 값이 문자열인지, 문자열이 종료된것인지, 숫자인지 알 길이 없다는 것이다.

그렇기에 우리는 이 문법에서 서로가 공통적으로 사용할 약속을 지정해야만 했고 그게 바로 문자열의 종료를 알리기위한 널문자(\0)인것이다.

 

이 코드를 보면 널 문자를 문자형으로 출력했을때 값이 나오지 않은 것을 볼 수 있고 정수형으로 출력했을때 0이 나온것을 볼 수 있다.

결국 널문자(\0)의 경우는 ASCII 코드값으로 0이고 문자형으로 출력했을때는 값이 나오지 않는 것이라고 볼 수 있다.

그런데 오해하는 부분이 널 문자는 공백이구나 라고 생각할 수 있는데 이건 아니다.

널 문자는 문자형으로 출력했을때 값이 나오지 않는 값이고, 공백 문자 또한 결국 아스키코드값으로 0이 아닌 32라는 값으로 존재하고 있다.

그렇기에 널문자는 공백문자와는 전혀 다른 값임을 인지해야한다.

 

scanf 함수를 이용한 문자열의 입력

위 코드는 문자열을 입력받아 출력하는 코드이다.

 

보면 scanf에서 %c가 아니가 %s로 값을 받고 &로 주소값을 넣는게 아니라 str 배열의 이름을 그대로 사용하고 있다.

이건 str이 sizeof나 &연산자를 만나지 않는 한에서는 str[0]를 가리키는 포인터로서 기능을 수행하기 때문이다.

 

scanf의 %s로 입력 받은 값은 메모리의 어떤 공간에 저장될 것이고 이 문자열의 가장 첫번째 값 메모리 주소를 str에 넣어주는 것이다.

str은 결국 str[0]를 가리키는 포인터이기 때문에 메모리에 저장된 문자열의 가장 첫번째 요소의 주소값이 str에 들어가면서 str은 이제 입력받은 문자를 가리키게 된다.

그 말은 char str[]의 배열은 입력받은 문자열과 연결이 된다는 의미와 같다.

결국 char str[] = 입력받은 문자열;로 초기화한것과 동일하다는 의미이다.

그리고 printf에서 %s를 넣고 str을 넣으면 전체 문자열을 모두 출력하는 것은 printf에서의 %s는 문자열의 시작부분의 주소값을 전달받아 처리하라고 지시를 내리고 printf는 널 문자(\0)를 만날때까지 내부적으로 루프를 실행하여 모든 문자를 순차적으로 출력해준다.

그래서 문자열의 첫번째 요소의 주소값을 가진 str(char str[] 문자 배열의 이름)을 전달한 것이고 

그래서 위와 같이 첫번배 배열요소의 주소값을 전달해서도 

동일한 결과를 출력받을 수 있는 것을 볼 수 있다.

 

정리하자면 scanf의 %s를 통해 값을 전달 받아서 char str[idx]에 값을 넣을 때에는 배열의 이름만 전달해도 되고, printf의 %s를 사용할때도 배열의 이름만 전달해줘도 전체 문자열을 출력해준다.

 

또 하나는 문자배열과 문자열이 구분된다는 점이다.

 

그리고 scanf를 통해 문자열을 입력받을 때 주의해야할점은

 

이 코드를 통해서 내가 입력 하고자하는 값이 만약 공백문자를 포함한 값이라면 (ex. Good morning!)

scanf는 공백을 기준으로 입력할 데이터의 종료를 인식하기 때문에 공백기준 이전 문자열만 입력받고 저장하게 된다.

추후에 공백을 포함한 문자열의 입력에 사용하는 함수를 별도로 설명하니 공백을 어떻게 넣을지에 대해서 고민하지 말자!