혼공학습단 C 5주차📚

하영·2024년 1월 30일
0

혼공학습단

목록 보기
11/13
post-thumbnail

기본 미션 📣

배열 개념 정리하기 | 완료

선택 미션 📣

널 문자의 정의, 용도와 표기법 | 널 문자는 아스키 코드 값이 0인 문자를 말하며, 문자 상수로는 ∖0로 표현된다. 아스키 코드 값이 0인 문자를 널 문자라고 특별히 사용하는 이유는 널 문자가 문자열의 끝을 표시하는 용도로 쓰이기 때문이다. 만약 문자열의 끝에 널 문자가 없다면 문자열의 끝을 인식하지 못해 쓰레기 값을 출력할 뿐 아니라 이어지는 메모리 영역까지 침범할 수 있기 때문에 문자열을 사용할 때 널 문자는 중요한 역할을 한다.


08-1 배열의 선언과 사용

동일한 자료형의 변수가 많이 필요한 경우 배열을 사용하면 편리한데,
배열은 같은 형태의 데이터를 반복문으로 처리하기 위해서 메모리에 연속적으로 저장해 놓고 쪼개서 사용하는 방법이다.

📍 배열의 선언

배열도 다른 자료형처럼 선언을 통해서 저장 공간을 확보하는데, 약간의 차이점은 기존처럼 변수를 하나씩 선언하지 않고, 저장 공간의 개수와 상관없이 하나의 이름으로 메모리를 한꺼번에 확보한다.

배열 선언법은 간단한데, 요소의 자료형에 이름을 붙이고 필요한 요소의 개수를 표시한다.

int ary[5];

이렇게 선언한 배열은 int형 변수 5개를 하나씩 선언하는 것과 같은 저장 공간의 크기를 갖는다.

그러나 메모리를 할당하는 방식에 차이가 있다.
변수를 선언하면 각 변수는 독립적인 저장 공간을 가지며 각각의 이름을 사용하는데,
배열은 저장 공간이 연속으로 할당되며 배열명이 전체 공간의 이름이 된다.

예를 들어 int형의 데이터 5개가 필요할 때, 변수로 선언하면 각각 4바이트씩에 독립적인 저장 공간이 할당되고, 배열로 선언하면 4바이트가 5개 연속으로 할당되어 저장공간이 20바이트가 할당된다.

여기서 배열은 20바이트의 공간을 4바이트씩 나누어 사용하게 되는 것인데, 이렇게 나누어진 조각을 배열 요소라고 한다. 배열 요소는 배열명에 첨자(index)를 붙여 표현하며 첨자는 0부터 시작한다.

여기서 주의할 점은 배열을 선언할 때와 배열 요소를 사용할 때 대괄호[ ] 속 숫자의 의미가 다르다. 선언할 때는 배열 요소의 전체 개수를 표시하고, 사용할 때는 각 요소가 배열에서 몇 번째에 있는지, 배열의 위치를 의미하는데, 배열의 위치를 나타내는 것이 첨자인 것이다. 그런데 첨자는 0부터 시작한다고 했으므로 배열 요소의 개수가 5개면 첨자는 0부터 4까지만 사용하는 것이다.

📍 배열 초기화

배열도 변수와 마찬가지로 최초 할당된 저장 공간에는 쓰레기 값이 저장되기 때문에, 원하는 값을 가지려면 꼭 선언과 동시에 초기화를 해야 한다.

초기화 방법은 다음과 같이 여러가지가 있다.

1 ) 기본적인 초기화

int ary[5] = {1, 2, 3, 4, 5};

2 ) 초깃값이 배열 요소의 개수보다 적은 경우
이때 초기화하고 남은 배열 요소는 모두 0으로 채워진다.

int ary[5] = {1, 2, 3};

3 ) 자동 초기화
이러면 배열 요소 개수가 많아도 모든 요소를 쉽게 0으로 초기화할 수 있다.

int ary[1000] = {0};

4 ) 배열 요소 개수가 생략
이 경우 컴파일러는 초깃값 개수만큼 배열 요소 개수를 정하고 할당한다.

int ary[] = {1, 2, 3};

5 ) 자료형의 맞는 초기화
각각의 자료형에 맞게 double형은 실수값으로, char형은 문자로 초기화한다.

double ary[5] = {1.0, 2.1, 3.2, 4.3, 5.4};
char ary[5] = {'a', 'p', 'p', 'l', 'e'};

배열의 초기화는 선언할 때만 한 번에 초기화할 수 있고, 그 이후에는 배열 요소에 일일이 값을 대입해야 한다.

📍 배열과 반복문

앞서 배열은 반복문을 이용할 수 있기 때문에 변수보다 편리하게 사용할 수 있다고 하였다.

소스 코드 8-2.c

#include <stdio.h>

int main(void)
{
	int score[5];
	int i;
	int total = 0;
	double avg;

	for(i = 0; i < 5; i++)
	{
		scanf("%d", &score[i]);
	}

	for (i = 0; i < 5; i++)
	{
		total += score[i];
	}
	avg = total / 5.0;

	for (i = 0; i < 5; i++)
	{
		printf("%5d", score[i]);
	}
	printf("\n");

	printf("평균 : %.1lf\n", avg);

	return 0;
}

이 예제를 보면 제어 변수 i를 배열 요소의 첨자로 써 반복 과정에서 모든 배열 요소에 값을 입력할 수 있게 하였다. 만약 변수로 일일이 선언했었다면 더하는 것도 일일이 계산했어야 했을 것이다.

이렇게 아무리 많은 데이터라도 배열과 반복문만 잘 사용하면 데이터의 양에 따라 유연하고 손쉬운 대응이 가능하게 된다.

📍 sizeof 연산자를 활용한 배열 처리

보통 많은 양의 데이터를 처리하므로 배열을 다룰 때는 반복문 사용이 필수적이다. 그렇기 때문에 배열 요소의 개수가 바뀌면 배열을 처리하는 반복문을 모두 수정해야 하는 부담이 있는데, 이 문제의 해결책은 sizeof 연산자를 활용하는 것이다.

소스 코드 8-3.c

#include <stdio.h>

int main(void)
{
	int score[5];
	int i;
	int total = 0;
	double avg;
	int count;

	count = sizeof(score) / sizeof(score[0]);		// 11행

	for (i = 0; i < count; i++)
	{
		scanf("%d", &score[i]);
	}

	for (i = 0; i < count; i++)
	{
		total += score[i];
	}
	avg = total / (double)count;

	for (i = 0; i < count; i++)
	{
		printf("%5d", score[i]);
	}
	printf("\n");

	printf("평균 : %.1lf\n", avg);

	return 0;
}

예제 코드 11행처럼 배열 요소의 개수를 sizeof 연산자를 이용해 직접 계산할 수 있다.

sizeof(배열명) / sizeof(배열 요소)
count = sizeof(score) / sizeof(score[0]);		// 20바이트 / 4바이트 = 5

sizeof 연산자를 배열명에 사용하면 배열 전체의 크기를 바이트 단위로 계산하게 되는데, 이 값을 배열 요소 하나의 크기로 나누어 개수를 구하는 것이다.

이렇게 구한 배열 요소의 개수를 각 반복문의 반복 횟수로 사용하면,
나중에 배열 선언문에서 요소의 개수가 바꾸더라도 나머지 코드를 수정할 필요가 없어지게 된다.

개발자는 프로그램의 유지보수도 항상 생각해야 하기 때문에 한 끗 차이에 생각으로 현명한 개발자가 되자!

08-2 문자를 저장하는 배열

배열은 데이터를 메모리의 연속된 공간에 저장할 수 있어서 순서에 따라 단어의 뜻이 달라지는 특성을 가지고 있는 문자열을 저장하는 용도로 제격이다.

📍 char형 배열의 선언과 초기화

char형 배열을 선언할 때 중요한 점은 배열을 선언할 때 저장할 문자열의 길이보다 최소한 하나 이상 크게 배열을 선언해야 한다. 이러한 이유는 널 문자( ∖0 )를 저장하기 위해서이다.

char형 배열 초기화는 int형 배열이나 double형 배열처럼 중괄호를 사용해 문자를 나열하면 되는데, 문자 상수는 다른 배열에 비해 초기화 방법이 번거로워서 중괄호 없이 문자열 상수로 직접 초기화하는 방법을 많이 사용한다.

char str[80] = {'a', 'p', 'p', 'l', 'e'};

char str[80] = "apple";

📍 널 문자의 용도

초기화한 문자는 배열의 처음부터 차례로 저장되어 문자열을 만든다. 이때 남는 배열 요소에는 자동으로 0으로 채워지는데, 여기서 저장된 0을 특별히 널 문자라고 부른다. 앞서 말한 것처럼 널문자는 문자열의 끝의 저장되어 문자열의 끝을 표시하는 용도로 쓰인다.

소스 코드 8-4.c

#include <stdio.h>

int main(void)
{
	char str[80] = "applejam";

	printf("최초 문자열 : %s\n", str);			// 7행
	printf("문자열 입력 : ");
	scanf("%s", str);
	printf("입력 후 문자열 : %s\n", str);			// 10 행

	return 0;
}

예제 코드 7행처럼 printf 함수가 배열의 크기와 관계없이 초기화된 문자열만을 정확히 출력하는 것도 널 문자가 있기 때문이다. printf 함수는 char형 배열에서 널 문자가 나올 때까지만 출력하도록 만들어졌는데, 이러한 규칙은 문자열을 처리하는 모든 함수에 적용된다.

scanf 함수도 마찬가지인데, 9행에서 새로운 문자열 grape를 입력했을 때 출력되는 값이 grapejam이 아니라 grape인 이유도 scanf함수가 사용자가 입력한 문자열 다음에 자동으로 널 문자를 추가해 문자열의 끝을 표시하기 때문이다.

그런데 char형 배열 선언 시 초기화를 하지 않고, 배열 요소에 문자를 직접 대입한다면 조심해야 할 점이 반드시 마지막 문자 다음에는 널 문자를 직접 대입해줘야 한다.

char str[80];
str[0] = 'a';
str[1] = 'p';
str[2] = 'p';
str[3] = 'l';
str[4] = 'e';
str[5] = '\0';

📍 문자열 대입

char형 배열은 문자열을 저장하는 변수의 역할을 하기 때문에 초기화 이후에도 얼마든지 새로운 문자열을 저장할 수 있다. 단, 문자열의 길이가 다를 수 있으므로 일반 변수처럼 대입 연산자를 사용하는 것은 안 되고, strcpy 함수를 사용해야 한다.

strcpy 함수는 저장할 문자열의 길이를 파악해 딱 그 길이만큼 char형 배열에 문자열을 복사한다. 물론 문자열 끝에 널문자도 자동으로 붙여 준다.

strcpy 함수에 접근하기 위해서는 string.h를 선언해줘야 하는데, string.h는 문자열을 다루는 함수들의 원형을 모아 놓은 헤더 파일이다.

함수의 기본 사용법은 다음과 같은데,

strcpy(저장될 배열명, 저장할 문자열)

첫 번째 인수는 문자열을 모두 저장할 수 있도록 충분히 커야 한다. 아울러 첫 번째 인수에는 문자열 상수를 사용할 수 없는데, 상수가 바뀌지 않는 값이라 대입 연산자 왼편에 올 수 없는 것과 같은 이유에서이다.

두 번째 인수는 문자열 상수뿐 아니라 char형 배열의 배열명도 사용할 수 있다. 이때는 배열에 저장된 문자열을 다른 char형 배열에 복사한다.

strcpy(str2, str1);

📍 문자열 전용 입출력 함수 : gets, puts

그런데 strcpy 함수는 한 가지 큰 문제점이 있다. 바로 문자열 중간에 빈칸이 있는 경우 빈칸 전까지만 입력이 되는 것이다. 이 문제를 해결하기 위해서 문자열 전용 입력 함수인 gets 함수가 있는데, gets 함수는 빈칸을 포함해 한 줄 전체를 문자열로 입력할 수 있다.

소스 코드 8-6.c

#include <stdio.h>

int main(void)
{
	char str[80];

	printf("문자열 입력 : ");
	gets(str);
	puts("입력된 문자열 : ");
	puts(str);

	return 0;
}

gets 함수는 문자열 입력 중간에 빈칸이나 탭 문자를 사용할 수 있으며, Enter를 누르기 전까지 전체를 하나의 문자열로 배열에 저장한다.

하지만 gets 함수를 사용할 때 주의할 점이 있는데, 입력할 배열의 크기를 검사하지 않으므로 배열의 크기보다 긴 문자열을 입력하면 배열을 벗어난 메모리 영역을 침범할 가능성이 있다. 그래서 항상 입력할 때 배열의 크기를 고려해야 한다. 이 문제는 scanf 함수나 strcpy 함수에도 해당되며 컴파일러에 따라 제한을 받기도 한다.

gets와 짝을 이루어 문자열을 출력하는 함수에는 puts 함수가 있는데, puts 함수는 문자열 상수나 char형 배열의 배열명을 주면 문자열을 화면에 출력한다. printf 함수의 문자열 출력 기능과 어느 정도 비슷하지만 puts 함수는 문자열을 출력한 후에 자동으로 줄을 바꾸는 차이가 있다.

0개의 댓글