Home C언어 (18) 문자열
Post
Cancel

C언어 (18) 문자열

C언어 (18) 문자열

C언어에서의 문자열은 상당히 복잡하고 신경 쓸 것이 많다.
그래서 python을 먼저 배우고 C언어를 배울 때 제일 헷갈려하는게 이 문자열 부분인 것 같다.
또 python이나 C++ string STL을 사용하면 문자열 다루기가 굉장히 편하다.

그럼에도 C언어로 문자열을 사용해야 하는 일이 있을 수도 있고, 여러 함수들도 같이 다루니까
간단하게 C언어에서의 문자열 특징과, 신경써줘야 할 것, 표준 함수들을 정리하자.

문자열

문자열은 “Hello”와 같은 문자들의 모임이다.
큰 따옴표로 표현하고, 문자형(char) 배열에 저장이 된다.

문자형 배열의 초기화

초기화하는 방법을 보기 전에 C언어가 문자열을 어떻게 저장하는지 보아야 한다.

1
char s1[10]= "hello";

이렇게 hello라는 문자열을 저장하는 코드가 있는데, 배열의 공간은 10개지만 실제로 쓰는 공간은 5개이다.
그런데 컴파일러는 배열을 얼마나 사용하고 있는지에 대한 정보가 없다.
이 정보가 없이 배열의 출력을 한다면 배열의 전체를 출력하고, 그러면 초기화되지 않은 영역이 출력되게 된다.
길이에 대한 정보를 매번 알려주는 것은 불편하기에, 우리는 문자열의 끝에 NULL 문자를 넣어줘 끝을 알려준다.

NULL 문자는 ‘\0’이고 문자열의 끝에는 항상 이 문자가 들어간다.
그렇기에 “hello”라는 문자열은 저장될 때 “hello\0”의 형태로 저장되고, 필요한 공간은 6byte이다.

image

문자형 배열을 초기화 하는 방법은 2가지가 있다.

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
	char s1[10]= "hello";
	char s2[10] = { 'h', 'e', 'l', 'l', 'o', '\0'};
	printf("%s\n", s1);
	printf("%s\n", s2);
	return 0;
}

첫 번째 방법인 ““를 통해 문자열을 저장하면 NULL문자를 명시적으로 안 써도 된다.
하지만 실제로는 NULL 문자가 들어가기에 배열의 최소 크기는 6이어야 한다.

1
char s1[5] = "hello"

와 같은 초기화는 NULL문자가 들어갈 공간이 없기 때문에 문제가 발생할 수 있다.
꼭 배열의 크기는 저장하려는 값보다 크게 만들어줘야 한다.

1
char s2[10] = { 'h', 'e', 'l', 'l', 'o', '\0'};

두 번째 방법은 문자를 하나씩 저장하는 것이다. 이 때는 NULL문자를 명시적으로 적어야 한다.
C언어에서 ‘‘작은따옴표는 문자 1개, ““큰따옴표는 한개 이상의 문자를 지정한다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
	char s1[10]= "String";
	char s2[10] = "is easy";
	char s3[15] = "C language";

	s2[3] = 'E';
	printf("%s %s %s", s1, s2, s3);
	return 0;
}

문자열도 배열이기 때문에 index로 접근해서 값을 바꿀 수 있다.
또, 공백을 의미하는 space bar도 ASCII 코드 32라는 값으로 저장되어 있어 문자열에 저장이 가능하다.

1
String is Easy C language

버퍼(buffer)

C언어는 입출력 작업을 효율적으로 하기 위해서 버퍼(buffer)라는 메모리 공간을 사용한다.
데이터를 입력 받을 때마다 처리를 하는 것이 아니라 작은 단위로 모아서 컴퓨터는 처리한다.

image

이런 규칙에 따라 우리가 입력한 것이 buffer에 들어갔다가 컴퓨터에 전달이 된다.
버퍼는 입력 버퍼와 출력 버퍼가 있고, scanf()같은 입력, printf()같은 출력을 할 때 이렇게 작동한다.

image

문자와 문자열의 입출력

우선 문자 1개를 입력 받는 함수들에 대해서 보자.

getchargetchputchar
입력입력입력
버퍼 사용버퍼 사용 안함버퍼 사용

이렇게 입력은 getchar, getch가 있고 출력은 putchar로 해보자.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
	char ch1;
	char ch2;
	ch1 = getchar();
	//ch2 = _getch();
	putchar(ch1);
	//putchar(ch2);
	return 0;
}
1
2
d
d

getchar()로 d라는 입력을 하면 버퍼에 d가 갔다가 ch1에 전달이 되고, ch1을 출력한다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
	char ch1;
	char ch2;
	//ch1 = getchar();
	ch2 = _getch();
	//putchar(ch1);
	putchar(ch2);
	return 0;
}
1
d

그런데 _getch()에 입력을 하면 버퍼가 없기에 키를 누른 순간 ch2에 d가 전달이 되고, 출력한다.
사용자가 엔터키를 누르지 않아도 바로 입력이 되는 것이다.

이번에는 문자열을 입력하고 출력을 해보자.
주로 사용하는 입력 함수에는 scanf()와 fgets()가 있다.

1
2
3
4
5
6
7
8
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
	char s1[20];
	scanf("%s", s1);
	printf("%s", s1);
	return 0;
}

우선 scanf부터 보면 이렇게 사용할 수 있다.
특이한 점은 원래 scanf로 입력 받는 변수에는 주소를 뜻하는 &연산자가 있는데 지금은 없다.
s1은 배열이기 때문에 이름이 배열의 주소와 같다. 그렇기 때문에 &연산자를 사용하지 않아도 된다.

프로그램을 실행시켜 scanf로 문자열을 입력 받으면 밑처럼 잘 입력이 되는 것 같지만

1
2
asdf
asdf
1
2
Hello world zz
Hello

띄어쓰기가 있는 문장은 받을 수 없다.
scanf는 공백, 탭, 엔터(개행 문자)로 입력을 구분하기 때문에 띄어쓰기를 하는 순간 입력이 끝난다.

fgets라는 함수로 문자열을 입력받아 보자.
fgets(입력받을 문자열, 크기, stdin(입력 받을 스트림))으로 사용한다.

gets는 scanf()와 비슷하게 보안 상의 문제가 있어서 추천하지 않는다.

1
2
3
4
5
6
7
#include <stdio.h>
int main() {
	char s1[20];
	fgets(s1, 15, stdin);
	printf("%s", s1);
	return 0;
}
1
2
3
hello world zz
hello world zz

띄어쓰기를 포함해 문자열을 잘 저장하는 것을 확인할 수 있다.

또한 scanf와 fgets는 입력을 처리하는 것에 차이가 있다.
우선 scanf와 fgets 모두 입력을 받으면 입력 버퍼에 저장했다가 한번에 처리한다.

scanf는 개행 문자(‘\n’)나 공백, 탭 등을 입력받으면 구분자 삼아 그 전에 입력된 내용만 저장한다.
“asd\n”을 입력받으면 asd까지 저장하고, 뒤에 \0 NULL문자를 추가한다. 그러면 입력 버퍼에는 ‘\n’이 남는다.

fgets는 개행 문자(‘\n’)까지 입력 받고 뒤에 ‘\0’ NULL문자를 추가해서 저장한다.
“asd\n”을 입력받으면 “asd\n\0”으로 개행 문자를 포함해 뒤에 NULL 문자까지 추가해 저장하는 것이다.

이 사실을 인지하고 다시 scanf와 fgets의 출력을 확인해보자.

이것이 scanf를 통한 입력이고
image

이게 fgets를 통한 입력이다.
image

scanf는 개행 문자를 기준으로 자르기에 출력하는 문자열에 ‘\n’가 포함되어 있지 않다.
그런데 fgets는 개행 문자를 포함하기에 출력에 한 줄 띄어져 있는 것을 볼 수 있다.
두 함수는 이런 차이가 있다.


scanf에서 입력 버퍼에 ‘\n’가 남는 것 때문에 문제가 생길 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
	char s1[20];
	char c;
	scanf("%s", s1);
	scanf("%c", &c);
	printf("%s", s1);
	printf("%c", c);
	return 0;
}

image

scanf(“%c”, &c); 부분이 실행되지 않고 넘어간 느낌이다.
자료형 부분에서도 말한 문제인데, 버퍼의 존재와 scanf()의 작동 방식을 알았으니 제대로 이해 할 수 있다.
문자열을 받고 남은 ‘\n’개행 문자가 c에 들어가며 입력을 받았다고 생각하고 프로그램이 종료가 된 것이다.

그렇기에 scanf를 통해서 한 문자(%c)를 받는 것은 추천되지 않는다.
하지만 꼭 받아야겠다고 하면 위에 있던 한글자만 받는 함수로 버퍼의 ‘\n’를 먹은 다음, 실행해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
	char s1[20];
	char c;
	scanf("%s", s1);
	getchar();
	scanf("%c", &c);
	printf("%s\n", s1);
	printf("%c", c);
	return 0;
}

image

원하는 동작이 잘 된다.


문자열의 입출력하는 방법을 알아봤고, 문자열을 처리하는 여러 함수들을 알아 보자.

This post is written by PRO.