Unit 39. 문자열 사용하기
전부터 느껴왔던 건데 이상하게 c언어에는 string, str 같은 자료형이 없다. char로 문자열을 넣고 실행하면 실행 오류가 뜨는데, 이번 Unit에서는 c언어로 문자열을 저장하는 방법에 대해 다룬다.
-문자열 사용-
문자열을 사용하는 방법은 char를 포인터 형식으로 사용하면 된다.
char *<변수명> = "<문자열>";
#include <stdio.h>
int main()
{
char *a = "Hello dypar!"; // 포인터 a에 Hello dypar! 문자열 저장
printf("%s", a); // a의 문자열 저장
}
위처럼 char를 포인터형으로 선언하고 출력 시 서식 지정자를 %s로 지정해주면 문자열이 그대로 출력된다.
1바이트인 char형에 문자열을 저장할 수 있는 원리는 바로 배열과 비슷하다. 포인터형으로 char를 선언하고 문자열을 넣으면 배열처럼 값들을 sizeof(char)+i 위치에 하나하나 저장하기 때문에 문자열 저장이 가능한 것이다.
위는 문자, 문자열의 저장 방식에 관한 코딩도장의 예시 사진이다. c1은 문자를, s1은 문자열을 저장한다.
참고로 문자는 변수 안에 그대로 저장되지만, 문자열은 변수 안에 저장되는게 아닌 문자열이 있는 곳의 메모리 주소가 저장되는 것이다. 문자열 저장 위치는 컴파일러가 결정하기에 신경 쓸 필요가 없다.
항상 문자열은 맨 마지막에 NULL이 붙는데 이 NULL이 문자열의 끝을 나타낸다. 그래서 printf, %s로 문자열을 출력할 때 문자를 계속 출력하다가 NULL에서 출력을 끝내서 문자열을 출력하는 것이다.
-문자열 인덱스
문자열도 배열과 유사하고 포인터와 똑같으므로 인덱스를 통해 요소에 접근 가능하다.
#include <stdio.h>
int main()
{
char *a = "Hello dypar!"; // 포인터 a에 Hello dypar! 문자열 저장
printf("%c\n", a[1]);
printf("%c\n", a[2]);
printf("%c", a[3]);
// a의 1, 2, 3 인덱스의 문자 출력
}
위는 인덱스를 통해 포인터 a에 담긴 문자들에 접근해 출력하는 코드다.
결과를 보면 인덱스에 해당하는 문자 요소가 잘 출력되는 것을 볼 수 있다.
#include <stdio.h>
int main()
{
char *a = "Hello dypar!"; // 포인터 a에 Hello dypar! 문자열 저장
printf("%c", a[12]); // a의 12 인덱스 요소 출력
}
참고로 12는 원래 인덱스 범위 밖이지만 문자열의 경우 NULL이 저장된 곳이다. 이 부분을 출력해보면
아무것도 나오지 않는 것을 볼 수 있다.
#include <stdio.h>
int main()
{
char *a = "Hello dypar!"; // 포인터 a에 Hello dypar! 문자열 저장
a[0] = 'A'; // a의 0 인덱스 요소를 바꾸려 시도
printf("%s", a); // 바뀐 a의 문자열 출력
}
반대로 인덱스에 해당하는 요소의 값을 바꾸려 시도하면 실행은 잘 되지만 NULL이 출력되는 것처럼 아무것도 나오지 않는다.
F5를 눌러 디버깅 모드로 실행하면 액세스 위반이라는 예외가 발생하는 것을 볼 수 있다.
이유는 문자열 리터럴이 있는 메모리 주소는 마치 상수처럼 읽기 전용이기 때문이다. 따라서 문자열 포인터는 인덱스로 접근하여 문자를 할당할 수 없다.
-배열로 문자열 할당
배열로도 문자열을 할당 받을 수 있다.
#include <stdio.h>
int main()
{
char a[10] = "Hello"; // 10 크기의 char형 배열에 Hello 문자열 저장
printf("%s", a); // a의 문자열 출력
}
위는 10 크기의 char형 배열 a에 "Hello" 문자열을 저장하고 출력하는 코드다.
당연히 잘 출력된다.
배열로 문자열을 저장할 때 주의할점은 문자열의 맨 끝에는 추가로 NULL이 들어가기 때문에 문자열보다 최소한 배열의 크기가 1 이상 커야한다. 남는 공간 역시 NULL로 채워진다고 한다.
주의할 점은 배열을 선언한 즉시 문자열로 초기화해야 한다는 점이다. 배열을 미리 선언하고 문자열을 나중에 할당하려 시도하면 컴파일 에러가 발생하는 것을 볼 수 있다. 다만 배열의 요소에 문자를 하나하나 집어 넣는 것은 가능하다.
#include <stdio.h>
int main()
{
char a[] = "Hello"; // char형 배열 a에 Hello 문자열 저장
printf("%s\n", a); // a의 문자열 출력
printf("%d", sizeof(a) / sizeof(char)); // a 배열의 크기 출력
}
위는 배열 a의 크기를 지정하지 않고 문자열을 저장한 후 a의 문자열과 크기를 출력하는 코드다.
첫 줄에는 문자열 Hello가 잘 출력되고, 두 번째 줄에는 Hello의 길이(5) + NULL(1) 한 크기 6이 출력되는 것을 볼 수 있다.
포인터와 마찬가지로 배열 또한 인덱스로 값을 출력할 수 있고, 또한 포인터와 달리 인덱스를 지정해서 해당 요소의 값을 바꿀 수도 있다. 이 부분은 다른 자료형의 배열들과 마찬가지이므로 따로 실습을 하진 않겠다.
Unit 40 입력 값을 문자열에 저장하기
-입력 문자열을 배열에 저장-
scanf에서 %s 서식 지정자로 입력을 받는다면 배열 형태로 문자열을 저장할 수 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a[9]; // 9크기의 char형 배열 선언
scanf("%s", a); // 입력
printf("%s", a); // a의 문자열 출력
}
위는 9크기의 char형 배열 a를 선언하고 scanf로 a에 문자열을 입력 받고 출력하는 코드다.
제대로 출력되는 것을 볼 수 있다.
입력 역시 문자열 끝에는 NULL이 포함되어 있으므로 배열의 크기는 입력 문자열 보다 1 이상 커야한다.
Hello World! 같은 공백이 포함된 문자열을 넣으면 Hello까지만 저장되는 것을 볼 수 있다. 그 이유는 공백이 NULL로 취급되서 문자열 입력이 끝났다고 판단하는게 아닌가 싶다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a[20]; // 20 크기의 char 배열 선언
scanf("%[^\n]s", a); // 공백까지 포함하여 입력
printf("%s", a); // 입력된 문자열 출력
}
%[^\n]s 서식 지정자를 사용하면 공백까지 포함하여 문자열을 입력 받을 수 있다.
공백까지 포함하여 입력이 잘 된 것을 볼 수 있다.
-입력 문자열을 포인터에 저장-
마찬가지로 포인터에서도 문자열을 입력 받을 수 있다.
다만 일반적인 포인터에는 입력 받을 수 없다. 포인터에 주소가 저장되어 있어야 scanf에서 입력을 받을 수 있는데, 주소를 저장하려면 초기화시 문자열을 입력해줘야하는데, 문자열을 입력해두면 읽기 전용이 되기 때문에 scanf를 통해 입력 받아 쓸 수 없다.
앞서 정리했던 malloc을 사용한다면 이 문제를 해결할 수 있다. malloc은 메모리 공간만 확보하고 그 주소만 포인터에 넣어주기 때문에 위에서 말한 문제들을 일으키지 않는다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *a = malloc(sizeof(char) * 10);
// 10byte만큼의 메모리 확보 후 포인터 a에 그 주소를 저장
scanf("%s", a); // 문자열 입력
printf("%s", a); // a의 문자열 출력
free(a); // a 주소의 메모리 해제
}
위는 malloc으로 동적으로 메모리를 할당해서 그 주소를 포인터 a에 저장한 후, 이 주소에 입력 문자열을 저장하고 출력한 코드다.
입력한 값이 잘 출력되는 것을 볼 수 있다.
마찬가지로 공백이 나온 경우 공백 전까지의 문자열만 저장하지만, %[^\n]s 서식 지정자를 사용하면 공백 이후의 문자열도 저장할 수 있다.
참고로 포인터 크기를 초과한 만큼의 문자열을 입력 받을 수는 있지만, 이럴 경우 다른 메모리에 침범할 수 있어서 프로그램이 이상하게 작동되거나 강제 종료될 수도 있다.(유사 힙 오버플로우)
Unit 41 문자열의 길이를 구하고 비교하기
-문자열 길이 구하고 비교하기-
string.h에 선언돼 있는 함수 strlen을 이용한다면 문자열의 길이를 구할 수 있고, 마찬가지로 string.h에 선언돼 있는 함수 strcmp를 이용한다면 문자열을 비교할 수 있다.
-strlen
strlen은 문자 배열 또는 문자열 포인터에서 문자열의 길이를 구해주는 역할을 하는 함수다. 사용하려면 string.h 헤더를 include 해야한다.
사용은
strlen(<문자열 포인터>);
strlen(<문자 배열>);
로 하면 된다.
#include <stdio.h>
#include <string.h>
int main()
{
char *a = "dypar"; // dypar 문자열을 char형 포인터에 저장
char b[10] = "dypar"; // 10 크기의 char형 배열에 dypar 문자열 저장
printf("%d %d", strlen(a), strlen(b));
// a, b에 저장된 문자열의 길이를 출력
}
위는 포인터 a, 배열 b에 문자열 "dypar"를 각각 저장하고 strlen 함수로 a, b에 저장된 문자열의 길이를 각각 출력해주는 코드다.
NULL문자도 포함하지 않은 순수 문자열의 길이가 출력되는 것을 볼 수 있다.
-strcmp
strcmp 함수는 두 문자열이 같은지 비교할 수 있는 함수로 string.h 헤더를 include 해야지 사용 가능하다.
사용은 strcmp(<문자열1>, <문자열2>);로 하면 된다.
반환 값은 아스키 코드 기준으로 문자열 2가 더 크다면 -1을, 두 문자열의 아스키 코드가 같다면 0, 문자열 1이 더 크다면 1을 반환한다.
#include <stdio.h>
#include <string.h>
int main()
{
char *a = "dypar"; // char형 포인터 a에 문자열 dypar 저장
char b[10] = "dypar"; // 10 크기의 char형 배열 b에 문자열 dypar 저장
printf("%d", strcmp(a,b)); // a, b에 저장된 문자열을 비교 후 결과 출력
}
위는 dypar이 들어간 두 변수에 저장된 문자열을 strcmp로 비교한 결과 값을 출력하는 코드다.
두 변수에 저장된 문자열은 같으므로(아스키 코드가 각각 같으므로) 0이 출력된다.
#include <stdio.h>
#include <string.h>
int main()
{
char *a = "ab";
char b[10] = "bb";
// 두 문자열 변수에 ab, bb를 각각 저장
printf("%d\n", strcmp(a,b)); // a, b를 비교한 결과 출력
printf("%d", strcmp(b,a)); // b, a를 비교한 결과 출력
}
위는 반환 값 테스트를 위해 일부러 서로 다른 문자열이 저장된 변수를 순서를 바꿔가며 비교한 결과를 출력하는 코드다.
a에 저장된 문자열이 b에 저장된 문자열 보다 아스키 값이 작으므로 첫 줄에는 -1이, 두 번째 줄은 순서를 바꿔서 비교했기에 b의 문자열이 더 크기 때문에 1이 출력됐다.
이 strcmp와 if 같은 조건문을 연계하면 문자열의 아스키 값을 비교하는 프로그램도 작성 가능할 것 같다.'
참고로 리눅스 OS에서는 strcmp 함수가 1, 0, -1을 반환하는게 아닌 아스키 코드 값의 차이를 출력한다고 한다.
Unit 39 ~ Unit 41 문제
a : char에는 문자 하나만 들어갈 수 있다. X
c : 문자열은 " "로 감싸줘야하고, 애초에 char에는 문자 하나만 들어갈 수 있다. X
d : 문자열에 비해 배열의 크기가 너무 작다. X
f : 문자열은 " "로 감싸줘야한다. X
답 : b, e, g, h
문자열을 출력하는 서식 지정자는 %s다.
답 : d
문자를 출력하는 서식 지정자는 %c다.
답 : b
문자열에 마지막에 붙는 것은 NULL이다. 이를 나타내는 문자는 NULL과 \0이다.
답 : a, d
문자열을 담는 변수를 선언하는게 목적이므로 배열이나 포인터를 이용한 char형 변수를 선언하면 될 것 같다.
#include <stdio.h>
int main()
{
char s1[] = "Beethoven 9th Symphony";
// 배열로 문자열을 저장
printf("%s\n", s1);
return 0;
}
s1의 문자열에서 9는 11번째에 위치하므로 10 인덱스의 요소에 접근해 출력하면 될 것 같다.
#include <stdio.h>
int main()
{
char s1[30] = "Beethoven 9th Symphony";
printf("%c\n", s1[10]); // 인덱스 10의 요소 출력
return 0;
}
결과 예시와 같이 3줄에 거쳐서 문자열을 출력하는 것이 목적이다. 주어진 문자를 입력하되 개행 부분은 \n을 넣어주면 될 것 같다. 또한 문자열을 담는 변수는 포인터나 배열로 선언 하면 될 것 같다.
#include <stdio.h>
int main()
{
char s1[] = "Beethoven\n9th\nSymphony";
// 개행 문자 \n를 이용해 개행이 포함된 문자열을 저장
printf("%s\n", s1);
return 0;
}
a : 서식 지정자가 앞에 와야한다. X
b : 배열에는 주소가 저장돼 있으므로 &를 써주면 안된다. X
d : 입력 문자열을 저장할 변수가 없다.
f : 서식 지정자가 없다.
답 : c, e, g
입력 받는 배열의 크기가 10이므로 NULL 문자 1byte를 뺀 9 글자까지 입력받을 수 있다.
답 : 9
입력 받는 부분만 빠져 있으므로 scanf와 서식 지정자 %s, 그리고 char형 배열 s1을 이용해 입력을 받는 코드를 작성하면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char s1[10];
printf("문자열을 입력하세요: ");
scanf("%s", s1); // 서식 지정자 %s로 s1에 문자열을 입력 받음
printf("%s\n", s1);
return 0;
}
1번 칸에 동적으로 할당받는 char형 포인터 변수를 생성하면 될 것 같고, 2번 칸에는 scanf, %s, s1으로 입력 받는 코드를 작성하면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *s1 = malloc(sizeof(char)*10);
// 넉넉하게 10byte 크기의 메모리 확보 및 주소 저장
printf("문자열을 입력하세요: ");
scanf("%s", s1); // s1에 문자열 입력
printf("%s\n", s1);
free(s1);
return 0;
}
문자열을 공백으로 구분해 3개를 입력 받아야하므로 %s 서식 지정자를 3번 쓰면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *s1 = malloc(sizeof(char) * 10);
char *s2 = malloc(sizeof(char) * 10);
char *s3 = malloc(sizeof(char) * 10);
printf("문자열을 세 개 입력하세요: ");
scanf("%s %s %s", s1, s2, s3); // %s를 3번 써줬다.
printf("%s\n", s1);
printf("%s\n", s2);
printf("%s\n", s3);
free(s1);
free(s2);
free(s3);
return 0;
}
배열이나 포인터 동적할당 중 골라서 NULL문자 포함 31 크기의 담을 변수를 4개 만들면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *s1 = malloc(sizeof(char) * 31);
char *s2 = malloc(sizeof(char) * 31);
char *s3 = malloc(sizeof(char) * 31);
char *s4 = malloc(sizeof(char) * 31);
// 문자열 길이가 30 이하이므로 NULL 문자 포함 31만큼 동적 할당해주고 주소를 각 포인터에 저장
scanf("%s %s %s %s", s1, s2, s3, s4); // %s를 4번 써줘서 각 포인터에 입력받은 문자열 저장
printf("%s\n", s1);
printf("%s\n", s2);
printf("%s\n", s3);
printf("%s", s4);
// 행을 바꿔서 각 문자열 출력
free(s1);
free(s2);
free(s3);
free(s4);
// 각 포인터가 가리키는 주소의 메모리 해제
return 0;
}
31 만큼 동적할당한 주소를 받는 포인터를 4개 선언 후 scanf로 %s를 4개 써줘서 각 문자열을 각 포인터에 저장되게 한다. 그리고 행을 바꿔서 각 포인터의 문자열을 출력하고, free로 메모리를 해제 시켜줬다.
문자열 길이를 구하는 함수는 strlen이다.
답 : c
strcmp는 첫 번째 인자의 아스키 코드가 크면 1을, 같으면 0, 두 번째 인자의 아스키 코드가 크면 -1을 출력한다.
답 : c, d, g
문자열의 길이를 구하는 것이 목적이다. 빈칸에 문자열의 길이를 구해주는 strlen 함수를 넣어주면 될 것 같다.
#include <stdio.h>
#include <string.h>
int main()
{
char *s1 = "C Language";
printf("%d\n", strlen(s1)); // s1의 문자열 길이 출력
return 0;
}
0이 출력되는 것이 목적인데, 출력문을 보면 strcmp로 s1, s2 문자열을 비교한 값을 출력하는 것을 볼 수 있다. strcmp는 두 문자열이 같을 경우 0을 출력하므로 똑같은 문자열이 담긴 s1 변수를 선언하면 될 것 같다.
#include <stdio.h>
#include <string.h>
int main()
{
char *s1 = "Pachelbel Canon"; //s2와 똑같은 문자열이 저장된 변수 선언
char *s2 = "Pachelbel Canon";
int ret = strcmp(s1, s2);
printf("%d", ret);
return 0;
}
입력된 문자열의 길이를 구하는 것이 목적이다.
#include <stdio.h>
#include <string.h>
int main()
{
char b[31]; // 31 크기의 배열 선언
scanf("%s", b); // 입력된 문자열을 b에 저장
printf("%d", strlen(b)); // 입력된 문자열의 길이 출력
}
입력 문자열의 길이가 30 이하이므로 NULL 문자 포함 31 크기의 배열 b를 선언하고 scanf와 %s로 입력된 문자열을 b에 저장한다. 그리고 strlen 함수로 b에 저장된 문자열의 길이를 출력하도록 작성했다.
리눅스에서 시험이 진행되서 반환값이 좀 달라지지만 그냥 비교 결과를 정수로 출력하는 것이 목적이라 별 다른 점이 없을 것 같다. strcmp를 사용하면 될 것 같다.
#include <stdio.h>
#include <string.h>
int main()
{
char a[31];
char b[31];
// 31 크기의 문자열 변수 2개 선언
scanf("%s %s", a, b); // 입력된 문자열을 a, b에 각각 저장
printf("%d", strcmp(a, b)); // 비교 결과를 출력
}
30 이하의 문자열이므로 NULL를 포함해 31 크기의 배열 a, b를 선언하고 scanf로 입력된 문자열을 a, b에 저장한 후 strcmp로 a, b를 비교한 결과를 출력했다.
'Old (2021.01 ~ 2021.12) > Programming' 카테고리의 다른 글
c언어 코딩도장 Unit 45 ~ Unit 47 (0) | 2021.03.13 |
---|---|
c언어 코딩도장 Unit 42 ~ Unit 44 (0) | 2021.03.13 |
c언어 코딩도장 Unit 36 ~ Unit 38 (0) | 2021.03.06 |
c언어 코딩도장 Unit 34 ~ Unit 35 (0) | 2021.03.01 |
c언어 코딩도장 Unit 24 ~ 33 (0) | 2021.02.21 |