본문 바로가기
개발 Tools/C_개념

C 배열의 모든 것이었으면 좋겠다 ( 문자열, gets, puts, 2차원 배열, Call by value, Call by reference )

by 전컴반 2022. 6. 18.
반응형
배열

 

쉽게 말해서 변수를 한 번에 왕창 만들어주는 것이다. 근데 번호와 순서가 있는 변수를 선언해준다는 점이 기존 변수와 다른 점이다. 선언된 배열은 형이 같은 변수를 만드는데 int, float, char, static... 다 된다.  

 

 

10개의 변수를 int s1, int s2... 선언하는 것보다 int s[10] 이렇게 하나로 선언해주는 게 더 효율적이다. 그래서 배열이 필요하다. 각각 모두 고유의 이름이 있어야 하는데 번호로 구분한다. 

 

 

1차원 배열의 선언에는 여러 방법이 있다.

 

// 선언1
int num[4];

// 선언2
int num[4] = {0, 1, 2, 3};

// 선언3
int num[] = {0, 1, 2, 3};

 

선언 1은 그냥 선언만 해준다. 즉 공간만 할당해 준다는 의미다.

선언 2는 짝에 맞게 선언해준다. num [4]라는 의미는 4개의 값을 저장할 변수 4개를 만든다는 의미다.

선언 3은 4를 생략하여 사용한 것이다.

 


 

요소 하나하나를 어떻게 지칭하는지 보자

num[0] = 0

num[1] = 1

num[2] = 2

num[3] = 3

이런 식으로 0번째부터 시작하여 오름 순으로 짝짓는다.

num[0] 이라는 변수 이름을 가진 애는 "0"의 값을 가진다는 의미다.

 

이게 처음에는 줠라 헷갈리지만 계속 쓰다 보면 당연시 받아들여진다. 정리하면 선언할 때 [] 안에는 몇 개의 변수를 생성할 건지 적어주고 각각의 이름은 0번부터 시작된다. 

 

 

또한 선언한 배열의 크기와 자료의 수가 맞아야 한다. 무슨 말이냐면,

 

// 에러
int num[3] = {1, 2, 3, 4};

// 괜찮음
int num[3] = {1, 2};

 

배열의 크기가 3인데 자료의 수가 3보다 커지면 남는 자리가 없어서 에러가 뜬다 하지만 자료의 수가 배열의 크기보다 적으면 에러는 뜨지 않는다. 나머지 뒤에( num[2] )는 컴파일러 따라 0으로 초기화해주기도 한다.

 

 

scanf_s를 통해 배열 입력

 

우리가 몇 개의 요소가 필요할지 알지만 어떤 값이 들어갈지 모른다면, 입력을 통해 받을 수 있다.

간단히 코드를 짜 보자. 

 

#include <stdio.h>

int main(void)
{
	int num[4];
	for (int i = 0; i < 4; i++) {
		printf("숫자를 입력하세요 :");
		scanf_s("%d", &num[i]);
	}
	for (int i = 0; i < 4; i++) {
		printf("num의 %d번째 값 : %d\n",i, num[i]);
	}
	return 0;
}

 

크기 4의 배열을 생성하고 값을 입력받아 잘 들어갔는지 출력해보는 코드다.

이때 크기를 지정해주지 않으면 컴파일러는 메모리를 얼마나 할당해줘야 할지 모르기 때문에 에러를 띄운다. 

+scanf는 space(공백)을 입력으로 받지 않는다.

 

 

배열의 문자열

 

C는 문자열을 만들기가 다른 언어에 비해 열라 귀찮다. 파이썬은 그냥 " "안에 넣어버리면 그만인데 말이다.

로마에 가면 로마법을 따라야 하듯이 C의 법을 따라보자.

 

C에는 char형이 존재하지만 문자를 저장하는 데이터형이지 문자열을 저장할 순 없다. 그래서 배열을 이용하거나 포인터를 이용하여 쓸 수 있는데 포인터는 나중에 하자. 또한 이런 귀찮은걸 여러 라이브러리로 만들어놨으니 또 큰 걱정은 없다.

 

각설하고 배열과 문자열의 관계는 단순하다. 문자열이란 문자의 나열로 배열 하나하나를 문자로 만들어서 연결하면 그만이다. 근데 가장 중요한 건 "what"을 저장하려면 문자가 4개라서 크기를 4로 선언해주면 될 것 같지만 유감스럽게도 "\0"이라는 확장 문자가 보이지 않게 들어가 있기 때문에 크기를 5로 선언해줘야 한다.

+ "/0"은 문자가 끝났음을 알려주는 의미다.

 

// 방법 1
char str[5] = "what";

// 방법 2
char str[] = "what";

 


 

scanf_s를 통해 입력을 받아보자

 

#include <stdio.h>

int main(void)
{
	char str[5];
	printf("문자열을 입력하세요 :");
	scanf_s("%s", str, sizeof(str));
	for (int i = 0; i < 5; i++) {
		printf("str의 %d번째 값 : %c\n",i, str[i]);
	}

	return 0;
}

 

제일 중요한 부분이 있다.

scanf_s에서 원래는 주소 연산자인 "&"를 적어줬지만 문자열에서는 생략했다.

이유는 %s는 바로 주소로 접근해서 &를 안 해줘도 된다. 또한 기존에 없던 "sizeof()"가 생겼다. 원래 다른 int나 float를 입력받을 때도 적어줘야 하지만 자동으로 들어갔기 때문에 생략 가능했다.

하지만 문자열에서는 적어줘야 한다. 크기가 얼마나 들어가는지 모르기 때문이다. char형인 %c는 "&"를 적어줘야 한다.

 

 

정리하면 배열 str을 생성하면 str이 시작 주소가 된다. 주소가 되기 때문에 따로 "&"를 사용하지 않아도 되는 것이다.

단, str[0], str[1]과 같이 하나하나 접근할 때는 "&"를 사용해야 한다.     

 

+ str[5]가 아니라 str[4]라고 하면 어떻게 출력되는지 보자. 이상한 값이 들어가는 걸 볼 수 있다. 이렇듯 크기를 생각해서 올바르게 적어줘야 한다.

 

 

 

gets & puts

 

공백이 있는 문자열을 받고 싶으면 "gets()"을 사용하여 받는다.

 

#include <stdio.h>

int main(void)
{
	char str[10];
	printf("문자열을 입력하세요 : ");
	gets(str);
	printf("입력된 문자열 : %s\n", str);
	return 0;
}

 

puts는 " "안에 있는 문자열을 그대로 출력해준다.

char str[100] = "whatever it is, you're right";
puts("whatever it is, you're right");

 

 

2차원 배열

 

지금까지 1차원 배열을 봤다면 이번엔 2차원 배열을 보자. 2차원이란 행렬을 의미한다. 쉽게 말하면 x축 y축이다.

 

int s[10]; // 1 차원 배열
int s[3][10]; // 2 차원 배열
int s[5][3][10]; // 3 차원 배열

 

선언은 str[x][y]로 x는 row를 나타내고 y는 column을 나타낸다. 크기를 선언함으로 행렬의 크기를 지정하여 대입하면 된다. 3행 3열을 만들어서 하나하나 값을 집어넣는 코드를 보자

 

#include <stdio.h>
int main(void)
{
	int num[3][3] = { {1,2,3}, {4,5,6}, {7,8,9} };
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			printf("num[%d][%d] = %d  ", i, j, num[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 

보면 1행에 1, 2, 3이 들어간다. 크게 어렵지 않게 받아들일 수 있다 생각한다. 

 


2차원에서 문자열도 쉽게 접근할 수 있다.

예를 통해 알아보자.

 

#include <stdio.h>

int main(void)
{
	char nation[3][6] = { "Korea", "China", "USA" };
	for (int i = 0; i < 3; i++) {
		printf("%d번째 nation = %s\n", i, nation[i]);
	}
	return 0;
}

 

3개의 문자열을 적어놨는데 뒤에 열의 크기는 제일 긴 요소에 맞춘다. "Korea"에서 "a"에 접근하고 싶다면, nation[0][4]로 접근할 수 있다. 이때 각각 행의 이름은 nation[0], nation[1], nation[2]가 변수의 이름이다.

 

 

함수와 배열 

 

겁나게 중요하다. 

우리가 함수를 호출할 때 2가지 방법을 사용한다. 많이 들어봤을 call by value, call by reference ( address )가 있다.

 

함수에 호출될 변수를 "인수 ( argument )"라고 부르고

함수의 원형과 내부에서 사용하는 변수를 "인자 ( parameter )"라고 부른다.

 

Call by value는 간단히 말해서 인수를 복사해서 인자에 넘겨주는 것이다. 즉 인수는 변하지 않고 인자만 변한다. 그냥 함수에서 인수를 복사한 새로운 변수 인자를 만든 것이다.

뭔 개소린가 싶다. 예를 들어보면,

 

add(n)에서 우린 n ( 인수 )을 add함수의 인자인 x에 넘겨줘야 하는데. n의 값 10을 복사해서 변수 x ( 인자 )에 넣어준다.

이때 중요한 건 복사다 복사, 원본은 넘어가지 않는다. n은 그대로 10이고 x는 15로 바뀐다.

 

#include <stdio.h>

int add(int x) {
	printf("before x = %d\n", x);
	x = x + 5;
	printf("after x = %d\n", x);
}

int main(void)
{
    int n = 10;
    printf("before n = %d\n", n);
    add(n);
    printf("after n = %d\n", n);
    
    return 0;
}

 

n은 그대로 10인걸 볼 수 있다. 

만약 변수가 100만 개다?? 그럼 이걸 다 복사하고 앉아있으면 메모리가 동날 것이다. 그래서 복사가 아니라 직접 원본에서 동작하는 방법이 필요하다. 그게 Call by reference다.

 


 

Call by reference는 당연히 복사가 아니라 원본을 넘겨주는 동작일 것이다. 말 그대로 직접 참조하여 호출한다. 즉, 원본을 넘겨준다. 직접 호출하면 속도가 굉장히 빠르지만 원본이 훼손될 수 있다는 위험이 있다. 

 

직접 넘겨주려면 해당 변수의 주소를 넘겨주면 된다. 즉 포인터를 통해 넘겨준다는 말이다.

 

#include <stdio.h>
int add(int *x);

int main(void)
{
	int *n = 10;
	printf("before n = %d\n", n);
	add(&n);
	printf("after n = %d\n", n);

	return 0;
}

int add(int *x) {
	printf("before x = %d\n", *x);
	*x = *x + 5;
	printf("after x = %d\n", *x);
}

 

변수 n의 주소를 넘겨줘야 하니 포인터 변수로 선언해준다. 그리고 포인터 변수의 주소를 &통해 넘겨준다. 그러면 주소를 받았으니, 직접 연산을 통해 원본을 변경한다.

 

이렇게 주소를 통해 함수의 인자를 받을 수 있다. 

 


이 얘기를 왜 했냐면 배열을 함수의 인자로 넘겨줄 때는 Call by reference로 넘겨준다. 이유는 위에서 말했듯이 배열의 크기가 엄청 크다면 다 복사해야 하니 비효율적이다. 

 

배열을 어떻게 받는지 보자.

 

간단하다. 배열의 이름을 통해 인자를 받는다. 이때 배열을 받는다는 것만 알지 크기는 알지 못하기 때문에 arr[]로 받는다.

 

실제로 원본을 바꾸는지 보기 위해 함수에서 문자열 "agree?"를 붙여서 arr를 바꿨다. 그리고 다시 main문에서 str을 출력해보니 문자열이 바뀐 걸 확인할 수 있다. 

단 문자열이 변경되는 걸 방지하면 인자로 입력받을 때 "const char arr[]"처럼 받으면 된다.

 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h> 

void print_string(char arr[]);

int main(void)
{
	char str[100] = "Whatever it is, your're right";
	print_string(str);
	printf("반환 후 입력 문자열 : %s\n", str);
	return 0;
}

void print_string(char arr[]) {
	printf("입력 문자열 : % s\n", arr);
	char fetch[] = " agree?";
	strcat(arr, fetch);
}

 

+ strcat은 문자열을 합쳐주는 함수인데 에러가 떠서 확인해보니 "#define _CRT_SECURE_NO_WARNINGS"를 선언하여 에러를 없앴다.

 


 

위에는 1차원 배열을 입력으로 받았지만 2차원 배열은 어떻게 받을까?? 

간단히만 보자

 

// main
char str[5][9] = { "Whatever", "it", "is", "your're", "right" };
print_string(str);
    
// 함수    
void print_string(char arr[][9]) {
~}

 

뒤에 buffer의 크기만큼 받아온다. 이때 크기가 동일해야 에러가 안 뜬다. 

 

반응형

댓글