1. Home
  2. 프로그래밍 강좌/C
  3. [C언어 강좌] #16-1 문자열 표준 함수와 기타 표준 함수

[C언어 강좌] #16-1 문자열 표준 함수와 기타 표준 함수

이전에 문자열에 대해 배운적이 있습니다.

프로그램을 만들면서 문자열을 다루는 상황이 많게 되므로 

문자열은 상당히 유용한데요.

 

C언어에서 문자열을 구현하는 방법 2가지 다시 리마인드하고 넘어가자면

"Hello" 라는 문자열이 있다면

 

1. 배열을 통한 문자열

char string[] = "Hello" 와 같은 형식으로 선언하고

각 배열 요소 string[0] = 'H', string[1] = 'e'.. string[5] = '\0' (널문자)가

문자인 것으로 구현됩니다.

 

2. 포인터를 통한 문자열

char * string = "Hello" 와 같은 형식으로 선언하고

"Hello"라는 문자열이 읽기 전용 메모리 공간에 할당되며 string이라는 char형 포인터 변수가

"Hello"라는 문자열의 시작주소를 가리켜서 1바이트씩 접근하게 됩니다.

 

(char 형 포인터로 선언했기 때문에 1바이트씩 접근, 포인터 변수 string의 크기는 포인터 변수기 때문에 8바이트 입니다. * 64비트 컴퓨터 기준)

 

각각의 자세한 차이는 #13-1 포인터편 참고해주세요.

 


C언어에서는 다른 객체지향 언어처럼 문자열 객체 string을 지원하지 않고

포인터, 배열로써 문자열을 표현하는 방법이 2가지이기 때문에

일단 문자열을 다루는게 매우 귀찮고 힘이 듭니다.

예를 들어서 char * string1 = "Hello"; char string2[] = "World";

이렇게 2개의 변수가 있을때 string2에 string1을 복사해 넣으려면

 

단순히 string2 = string1; 와 같은 방법으로 대입해서 복사할 수 없습니다.

왜냐면 string2라는 배열 이름은 배열의 시작주소인데 거기에 "Hello" 라는 문자열을 넣을 수 없습니다.

 

아마 Python을 선행해서 배우고 C를 배우는 분들한텐 문자열을 쓸때 뭐 이딴게 다있지? 탄식이 나올 수 있습니다.

 

그래도 이런 문자열을 조금이라도 편하게 다루기 위해 사용하는 문자열 표준 함수들이 있는데

우리가 문자열을 제어할때는 이 함수들을 호출해서 사용만 하면 됩니다.

 

방금전에 string2에 string1을 복사하는 문제도 문자열 처리함수인 strcpy()라는 함수를 쓰면

매우 간편하게 문자열 복사가 가능합니다.

 

오늘은 이에 대해 알아봅시다.

 

문자열 처리 함수 1

표준 함수들 중에 가장 많이 사용하는 것이 바로 문자열 처리 함수입니다.

 

gets() 함수와 puts() 함수

문자열 입력함수 gets()와 puts()는 stdio.h에 원형이 선언되어 있습니다.

우리가 평상시에 쓰던 printf와 scanf역시 stdio.h에 선언되어 있어서

항상 습관적으로 #include <stdio.h> 를 써왔기에

 

gets()와 puts()는 바로 사용하실 수 있습니다.

사용전에 특징을 알아봅시다.

 

함수 원형 예시 설명

#include <stdio.h>
char * gets(char * s)
char array1[10];
gets(array);
전달된 메모리 주소에 문자열 저장
성공 : 입력된 문자열 반환
실패 : NULL 반환

방식 : 개행(엔터, \n)이 들어올때까지 문자열을 입력받고 개행 문자가 들어오면 개행 문자를 포함시키지 않고 짜르고 뒤에 널문자(\0) 를 붙여줌.
#include
int * puts(const char * s)
char array1[10] = "Hello C";
puts(array);
전달된 메모리 주소에 문자열 출력
성공 : 0아닌 값 반환
실패 : EOF 반환

방식 : 인자값으로 전달받은 메모리(배열)의 주소를 참고해 NULL 문자를 만날때까지 문자열을 출력, 출력하면서 자동으로 개행문자를 삽입함.

위 표에서 주목할점은 일단 gets()와 puts()는 각각 인자값이 포인터로 주소값을 넘겨받는다는 점입니다.

 

외우는 방법은 영어단어 뜻 그대로인데

 

get -> 가져오다 = 입력받음

put -> 두다 = 출력하다

로 외워주시면 됩니다.

 

그리고 방식에 주목해야 하는데 한번 꼭 읽어보시길 바랍니다.

 

"Hello World"라는 문자열을 입력받을때

gets()로 문자열을 가져올때와 scanf("%s", &변수)로 문자열을 가져올때 중요한 차이점은

gets()로 가져오면 공백이 포함되어 있어도 관계없이 줄바꿈(=엔터, 개행) 문자까지 읽기 때문에 문제없이 Hello World라는 문자열 전체를 저장할 수 있지만,

 

scanf의 경우 스페이스바로 입력을 구분받기 때문에 Hello 뒤에 공백이 있어서 입력 종료로 이해하고 "Hello"라는 문자열만을 저장하게 됩니다.

 

물론 scanf의 고급 기능을 사용해서 문자열을 받을 수 있지만 일반적으로 %s만써서는 입력받을 수 없다는것을 기억해야 합니다.

 

아래 예제를 통해 gets()puts()에 대해 이해해보겠습니다.

 

#include <stdio.h>



int main(){
	char array1[10];
	char array2[10] = "Hello C";
	
	puts("문자열을 입력해주세요"); //== printf("문자열을 입력해주세요\n");
	gets(array1); //문자열 입력받기 
	puts(array1);
	
	puts(array2);
	puts("Thank You Sir.");
	return 0;
}

문자열을 입력해주세요
문자열 입력 01
문자열 입력 01
Hello C
Thank You Sir.

--------------------------------
Process exited after 4.31 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

10바이트 메모리 공간을 가진 9글자를 저장할 수 있는 char형 배열 array1, array2 를 만들었습니다.

여기에 문자열을 저장할 것입니다.

 

우선 puts()로 출력을 하면 아까 puts()의 특징에서 보았듯이 끝에 자동으로 개행 문자를 삽입해줍니다.

그러니 puts("문자열을 입력해주세요"); 는 printf("문자열을 입력해주세요\n"); 와 동일한 표현입니다.

 

보통 출력을 할때 printf 로 출력을 하면 줄바꿈이 안되서 맨날 \n써주고 귀찮을때가 많았는데 puts()로 하면 이러한 것을 간편하게 출력할 수 있습니다.

 

파이썬 해보신분들이라면 파이썬의 print()함수와 비슷한 함수라고 생각하시면 됩니다. 안해보셨으면 넘어가시구요.

 

그리고 gets(array1); 로 array1의 시작주소를 넘기면 문자열 입력을 받고 array1로 찾아가서 문자열을 저장해줍니다.

scanf처럼 스페이스바가 아닌 엔터칠때까지 이므로 "문자열 입력01" 처럼 공백까지 정상적으로 받을 수 있습니다

 

#include <stdio.h>



int main(){
	char array1[100];
	
	scanf("%s", array1);
	printf("%s", array1);
	
	return 0;
}

Hello World
Hello
--------------------------------
Process exited after 1.454 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

scanf의 %s를 이용해 문자열을 입력받는 예제입니다.

보시면 Hello World를 입력했으나 scanf는 종료 문자가 공백문자이기 때문에

Hello 에서 문자열이 짤린걸 알 수 있습니다.

 

이정도면 puts()와 gets()를 쓰는 장점을 알 수 있습니다.

일단 소스코드가 굉장히 간결해지고 %s같이 서식문자를 기억하고 신경써야 할필요가 없습니다.

그리고 공백도 입력받아서 문자열에 저장할 수 있구요.

 

gets()함수의 위험성

gets()함수는 장점만 있는거 같지만 버퍼 오버플로우라는 중대한 보안 문제가 있습니다.

우선 본문에서는 표준 문자열 처리 함수만을 학습중이므로

이에 대해 다루지 않을것이지만 알아두시는게 좋습니다.

 

scanf() 또한 이런 문제가 있어서 Visual Studio로 C언어 코딩을 하면 scanf()가 아닌 scanf_s() 사용을 권장합니다.

 

요새는 상업 프로그램이 저 High Level한 고급 언어로 짜여지는 추세이지만 그래도 C, C++은 여전히 세계에서 가장 많이 사용되고 있기 때문에 주의할 문제기도 합니다.

 

http://andyader.blogspot.com/2014/03/gets.html

 

gets 함수는 사용하지 말아야 한다.

C 프로그래밍을 하는 고수 프로그래머의 대부분(아니 거의 전부) 는 다들 gets 함수를 사용하지 말라고 조언한다. 그 이유에 대해서 1988년 엄청난 피해를 입혔던 모리스 웜(Morris Worm) 에 대해 언급

andyader.blogspot.com

 

https://www.youtube.com/watch?v=SlXEdxnR9Jg 

관련 링크 첨부하니 꼭 읽어보세요!

 

 

EOF(End of File)란?

아까 puts() 함수가 실패시 EOF 반환 이라고 나와있습니다.

 

라고 나와있습니다.

EOFEnd of File의 약자파일의 끝을 의미하며 헤더 파일 <stdio.h> 에 #define EOF (-1)로 선언되어 있습니다. 즉 EOF는 상수 -1을 의미하는데 다음 예에서 볼 수 있듯이 함수 호출 시 에러가 있는지 없는지에 대한 반환값을 검사할때 사용되어 보다 신뢰성 있는 코드를 작성하게 해줍니다.

 

또 다른 경우는 파일을 입출력할때 파일의 끝에 도달하면 EOF가 반환될 수 있습니다. EOF에 대한 자세한 내용은 파일 입출력 부분에서 다루겠습니다.

 

#include <stdio.h>



int main(){
	char array[10];
	gets(array);
	
	if(puts(array)==EOF){ //== if(puts(array)==-1)
		printf("문자열 출력에 실패했습니다 :("); 
	}
	return 0;
}

다음과 같이 puts()함수를 사용시 EOF가 반환되는지 체크해서 문자열 출력이 제대로 됬는지 안됬는지 확인 가능합니다. EOF는 C언어에서 그 정의상 -1이므로 -1이 반환됬나 확인하는것과 동일합니다.

 

strlen() 함수

strlen() 함수는 문자열의 길이(글자수)를 알려주는 함수입니다.

지금까지 gets()와 puts()는 stdio.h에 선언되어 있었지만 strlen()을 포함해 지금부터 쓸 함수들은 string.h에 선언되어 있어서

#include <string.h>로 헤더파일을 인클루드 해줘야 합니다.

 

함수 원형 예시 설명
#include <string.h>
size_t strlen(const char * s)
char array[] = "Hello";
int length = strlen(array);
전달된 메모리 주소 array로부터 종료 문자를 만날때까지 저장된 문자열의 길이를 반환

 

strlen() 함수는 전달받은 메모리 주소에 저장된 문자부터 종료 문자(널 문자)를 만날때까지 저장된 문자열의 길이를 반환하는 함수입니다. (=문자열 글자수 계산)

문자열 길이를 계산할때 문자열 끝의 널문자는 길이에 포함하지 않고 널문자 직전까지의 갯수를 샙니다.

 

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


int main(){
	char * string1 = "Hello";
	char string2[] = "안녕하세요";
	
	printf("%d %d", strlen(string1), strlen(string2));
	return 0;
}

5 10
--------------------------------
Process exited after 0.004389 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

다음 예제를 통해 strlen()의 사용법을 알 수 있습니다.

포인터로 구현된 문자열, 배열로 구분된 문자열 상관없이 주소만 넘기면 되므로 인자값으로 문자열을 제공하면

길이를 계산해서 줍니다.

 

영문은 한글자당 1바이트, 한글은 한글자당 2바이트 이므로 각각 5바이트, 10바이트로 다르게 나타납니다.

 

유니코드

유니코드는 다국어를 지원할 목적으로 만들어진 코드 체계입니다. 영문 알파벳 한글자를 표기 할때는 ASCII 코드를 이용하면 되고 char 자료형이 이를 위해 만들어졌으므로 1바이트 메모리 공간으로 충분히 가능합니다.

 

그러나 한글, 한자, 일본어와 같은 문자를 표기할때는 2바이트의 메모리 공간이 필요합니다. 컴퓨터가 전 세계에서 사용하는 장치가 된 만큼 기존의 ASCII 코드 체계로는 한계가 있어서 등장한것이 유니코드입니다.

 

자세한 내용은 인코딩으로

https://vigli.tistory.com/52

 

문자 인코딩이란?

글 내용이 많지만 문자 인코딩이 궁금하다면 꼭 읽기 바란다. 우선 인코딩을 위키 백과에 나온 설명으로 보면 '정보의 형태나 형식을 변환하는 처리나 처리 방식이다.' 라고 정의 되어 있다. (

vigli.tistory.com

 

strcpy() 함수와 strncpy() 함수

strcpy() 함수와 strncpy() 함수는 문자열을 복사하는 함수입니다.

헤더파일 string.h 를 인클루드 해야 사용 가능합니다.

 

함수 원형 예시 설명
#include <string.h>
char * strcpy(char * dest, const char * src);
char array1[10] = "Good Luck";
char array2[10];
strcpy(array2, array1);
array2에 array1의 문자열을 복사
성공 : 복사된 문자열의 시작 주소 반환
#include <string.h>
char * strncpy(char * dest, const char * src, size_t n);
char array1[10] = "Good Luck";
char array2[10];
strncpy(array2, array1, 3);
array2에 array1의 문자열을 3바이트 만큼 복사
성공 : 복사된 문자열의 시작 주소 반환

 

strcpy() 를 사용하여 문자열을 복사할때 알아두어야 할 점은 strcpy(array2, array1); 라고 쓰면 array2에 array1의 문자열을 복사한다는 점입니다. array2가 복사받는 대상이고, array1이 복사할 원본입니다.

 

strcpy()는 복사할때 널문자 까지 포함시켜서 복사합니다.

 

strncpy()도 동일하나 몇바이트씩 복사할지 지정해서 복사할 수 있다는 특징이 있습니다.

 

strncpy()는 몇바이트씩 복사한 후 끝에 자동으로 널문자를 붙여주지 않습니다. (주의)

 

strcpy()로 복사할때 주의할점은 strcpy()로 복사할때 복사받을 대상은 원본보다 메모리 크기가 더 크거나 같아야 합니다.

예를 들어서 char array1[10] = "Good Luck" 이것을 array2에 복사할때 array2의 크기는 최소 10바이트 이상이여야 합니다. (9글자 + 널문자 = 10바이트)

 

만약에 복사받을 대상이 원본보다 메모리 크기가 더 작다면 복사가 되더라도 널문자가 짤리거나 하여 제대로 출력이 되지 않습니다.

 

strcpy()로 복사할때는 무조건 문자열을 통째로 복사하기 때문에 몇바이트씩 복사할지 지정할 수 없습니다만,

strncpy()로 복사를 하면 몇바이트씩만 복사할 수 있다는 이점이 있습니다.

 

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


int main(){
	char array1[12] = "Hello World";
	char array2[12];
	char array3[12];
	
	strcpy(array2, array1); //array2에 array1의 내용 복사 
	strncpy(array3, array1, strlen(array1)+1);  //==strcpy(array3, array1, 12);
	
	puts(array1);
	puts(array2);
	puts(array3);
	return 0;
}

Hello World
Hello World
Hello World

--------------------------------
Process exited after 0.004639 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

간단한 사용 예시입니다.

 

strncpy(array3, array1, strlen(array1)+1); 이 코드는 array1의 글자수가 strlen()의 동작원리상 null문자를 제외한 총 11글자로 계산하기 때문에 널문자까지 복사하기 위해서 1바이트 더 복사하기 위해 1을 더해주고

 

array3에 array1의 내용을 array1의 글자수 + 1인 12바이트만큼 복사하는 예시입니다.

 

strcpy(array3, array1, 12); 이렇게 안써주고 위에 strlen()으로 따로 계산해서 하는 이유는 array1의 크기가 매번 달라지거나 하면 그때그때 크기를 계산해서 넣어줘야 하기 때문에 strlen() 함수를 이용해서 자동으로 계산하게끔 하는 것 입니다.

 

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


int main() {
	char array1[6] = "Hello";
	char array2[3];

	strncpy(array2, array1, 3);

	puts(array2);
	return 0;
}

Hel儆儆儆儆儆儆儆儆儆儆儆儆?㈕[?

C:\Users\pgh26\source\repos\Project1\x64\Debug\Project1.exe(프로세스 21472개)이(가) 종료되었습니다(코드: 0개).
디버깅이 중지될 때 콘솔을 자동으로 닫으려면 [도구] -> [옵션] -> [디버깅] > [디버깅이 중지되면 자동으로 콘솔 닫기]를 사용하도록 설정합니다.
이 창을 닫으려면 아무 키나 누르세요...

그리고 strncpy()를 사용할때 주의할점. 아까도 짧게 언급했지만,

strncpy는 매개변수값에 주어진 N바이트만을 복사할뿐 복사되는 대상 끝에 널문자는 자동으로 삽입시켜주지 않습니다.

 

그런데 array2는 지역 변수로 선언된 배열이므로 초기화되지 않아 쓰레기값이 들어있을 것인데,

지금 강의 할때 사용하는 ide인 dev c++로 코딩을 하면 가끔 쓰레기값이 어쩌다가 0이 들어가서 NULL문자로 인식을 하여 (NULL문자 ASCII 코드가 0입니다.)

 

strncpy로 복사를 해도 제대로 출력이 되는 경우가 있습니다.

그래서 가끔 혼란스러운 상황이 생길것인데 위 예제는 Visual Studio 로 실행해본 것 입니다.

Visual Studio 는 깐깐하게 컴파일이 되서 왠만하면 dev c++처럼 어쩌다가 0으로 초기화 된다던가 그런 문제가 거의 없습니다.

 

그러나 본 강의에서는 편의를 위해 dev c++을 쓰기로 했으므로 참고하시길 바랍니다.

C언어 실력이 많이 늘었다고 생각하면 조금 더 무겁더라도 코드 블럭, Visual Studio 같은 IDE를 진행하여 프로젝트를 진행하는것이 더 좋습니다.

 

그래서 strncpy를 쓸때 아래 예제처럼 수동으로 널문자를 붙여줘야 합니다.

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


int main(){
	char array1[6] = "Hello";
	char array2[3];
	
	int n = 3; //3바이트만큼 복사 
	
	strncpy(array2, array1, n); 
	array2[n] = '\0'; //널문자 삽입 
	
	puts(array2);
	return 0;
}

0부터 새는 zero-based indexing에 의해 배열의 3번째는 (1부터 세는 기준으로) 실제 4번째 이므로

 

3바이트, 즉 3글자 복사후 4번째 글자에 널문자를 넣어주시면 됩니다.

 

널문자도 하나의 문자니깐 작은따옴표 유의해주세요.

(문자 -> 작은따옴표, 문자열 -> 큰따옴표)

 

strcat() 함수와 strncat() 함수

strcat()함수와 strncat()함수는 문자열을 끝에 붙여주는 함수입니다.

역시 string.h 헤더파일에 속해있어 include가 필요합니다.

함수 원형 예시 설명
#include <string.h>
char * strcat(char * dest, const char * src)
char array1[10] = "Good"
char array2[5] = "luck";
strcat(array1, array2);
array1의 끝에 array2를 이어다 붙입니다.
성공 : 결합된 문자열의 시작 주소 반환
#include <string.h>
char * strncat(char * dest, const char * src, size_t n)
char array1[10] = "Good"
char array2[5] = "luck";
strncat(array1, array2, 3);
array1의 끝에 array2의 3바이트 만큼 이어다 붙입니다.
성공 : 결합된 문자열의 시작 주소 반환

 

strcat(array1, array2); 라고 쓰면 array1의 끝에 array2의 문자열을 이어다가 붙입니다.

 

붙이는 방식은 복사를 받는 대상인 array1 끝에 널문자는 때버리고 array2를 쭉이어다가 붙입니다.

array2 의 널문자까지 쭉이어다가 붙이므로 끝에 널문자가 붙지 않을 걱정을 할필요가 없습니다.

 

ex) Good\0 + luck\0 => Goodluck\0

(Good\0에서 \0을 기준으로 붙이는데 \0은 떼버리고 luck\0을 이어다가 붙임.)

 

strncat() 함수의 경우에도 마찬가지인데 strncat(array1, array2, 3); 라고 쓰면 array1에 array2의 3바이트만큼 짤라다가 이어다가 붙입니다.

 

그런데 아까 strncpy()의 경우 복사시 널문자는 자동으로 붙여주지 않았는데

strncat()은 n바이트만큼 짤라다가 붙여도 자동으로 널문자가 복사대상에 붙습니다.

 

ex) Good\0 + luck\0 => Goodluc\0

(붙이는 방식은 아까 strcat()과 마찬가지로 \0 기준으로 붙이며 \0을 떼버리고 붙여버린다, 3바이트=영어 3글자 이므로 luc를 붙이고 끝에 널문자까지 자동으로 붙여준다.)

 

strncat(), strncat() 사용시 주의할점은 아까 strcpy(), strncpy() 사용시와 비슷한데

만약에 array1에 array2의 내용을 붙일때, array1에 array2의 내용을 붙이고도 array1의 내용은 충분히 남아야 합니다.

 

즉, array1 + array2 를 했을때 최소한 array2 끝의 널문자가 짤리지 않을정도로 array1의 크기가 커야 합니다.

(괜히 머리아플일을 줄이려면 붙일 대상은 메모리를 넉넉하게 잡는게 좋습니다.)

 

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


int main(){
	char array1[100];
	char array2[100]; 
	
	printf("첫 번째 문자열 입력 : ");
	gets(array1);
	
	printf("두 번째 문자열 입력 : ");
	gets(array2);
	
	strcat(array1, array2); //array1에 array2 결합 
	puts(array1);
	

	return 0;
}

첫 번째 문자열 입력 : I love
두 번째 문자열 입력 : C programming
I loveC programming

--------------------------------
Process exited after 3.681 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

strcat()을 활용하는 간단한 예제입니다.

array1과 array2에 각각 문자열을 입력받고 strcat() 을 이용하여 array1에 array2의 내용을 붙였습니다.

그리고 array1을 출력하고 있습니다.

 

array1에 값을 입력할때 I love 끝에 공백을 주지 않았으므로 I loveC programming 두 문자열이 붙어서 출력됩니다.

 

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


int main(){
	char array1[100];
	char array2[100]; 
	
	printf("첫 번째 문자열 입력 : ");
	gets(array1);
	
	printf("두 번째 문자열 입력 : ");
	gets(array2);
	
	strncat(array1, array2, 4); //array1에 array2의 4바이트 만큼 결합 
	puts(array1);
	

	return 0;
}

첫 번째 문자열 입력 : I love
두 번째 문자열 입력 : math and science
I lovemath

--------------------------------
Process exited after 3.289 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

다음 예제는 strncat()에 관한 사용법입니다.

보시면 아까 strcat() 에서 함수 이름을 strncat()으로 하고 인자값 끝에 몇바이트씩 결합할건지 추가한 것밖에 없습니다.

 

strncat(array1, array2, 4); 라고 쓰면

array1에 array2의 4바이트 만큼 결합시키는 것 입니다.

 

컴퓨터에서 문자 하나를 표현할때는 1바이트의 char자료형을 쓰기로 약속했고, 표현 범위 내의 숫자를 ASCII 코드로 매칭시켜서 표시합니다.

 

그래서 영어, 숫자는 1글자당 1바이트를 차지합니다. 즉 4바이트 = 4글자 입니다.

물론 한글, 일본어 같은 경우엔 한 글자를 ASCII 코드에 매칭시킬 수 없어서 2바이트로 표현을 한다고 배웠습니다. (유니코드)

 

위 예제의 출력값을 보시면 array2의 저장된 math and science중 앞 4글자인 math만 복사된 것을 알 수 있습니다.

그리고 strncat()의 경우는 몇글자씩 잘라다가 붙여도 붙인곳 끝에 널문자를 자동으로 붙여준다고 했습니다.

문자열을 이어다 붙인 후 puts()로 array1을 출력해보니 잘 출력됩니다.

 

strncpy()의 경우엔 그렇지 않아서 인덱스 맨끝에 사용자가 수동으로 널문자를 삽입해줘야 했죠.

 

strcat() 함수와 strncat() 함수 직접 구현

 

모종의 이유로 문자열을 이어 붙일때 strcat()이나 strncat()과 같은 레퍼런스를 이용하지 못하게 될 수 있습니다.

(학부 과제라던지...)

 

만약에 이 함수들을 직접 구현해야 할땐 어떻게 해야할까요?

위에 arr1, arr2가 주어져 있고 arr1에 arr2를 붙이는 문제가 나왔다고 가정해봅시다.

 

우리는 지금까지 문자열에 대해 공부했고 그 핵심인 널문자에 대해 압니다.

문자열에 대해 제대로 이해했고, strcat()과 strncat()의 원리까지 이미 알고 있으므로

 

직접 구현하는건 strcat(), strncat()의 방식을 그대로 따라하면 될 것 입니다.

소스코드는 작성하지 않고 간단히 알고리즘 형태로만 아래에 작성해보겠습니다.

 

1. While문으로 인덱스를 증가시키며 arr1의 한글자 한글자씩 반복을 하고 널문자를 만나면 종료한다.

2. 널문자를 만나고 종료했으므로 증가된 인덱스가 곧 널문자의 위치 일 것이다. (혹은 인덱스 + 1)

3. arr1의 파악한 널문자의 위치를 기반으로 arr2의 널문자가 만날때까지 반복을 돌면서 arr1 뒤에 한글자씩 arr2의 글자 하나씩을 삽입해준다. (arr1의 널문자 위치부터 지워가면서 arr2의 글자를 채워준다.)

4. 혹시 arr1 끝에 널문자가 제대로 들어가지 않았으면 삽입해준다.

 

본 강의에선 문자열 처리 함수를 다루고 있으므로 직접적인 구현은 생략합니다.

인터넷에 직접 구현한 좋은 예제가 많으니 참고해주세요!

 

strcmp() 함수와 strncmp() 함수

문자열이 같다는건 일반적으로 문자열을 이루는 모든 문자의 집합이 모두 같은것을 의미합니다.

예를 들어서 "Hello" 와 "Hello" 라는 문자열은 모든 구성이 같아서 "같다" 고 할 수 있습니다.

 

그런데 "Hello" 와 "Hellp"는 앞까진 전부 동일하지만 끝문자 o와 p가 달라서 같다고 할 수 없습니다.

 

만약에 문자열이 같은지 확인하는 함수를 만들려면 어떻게 해야할까요?

char array1[100] = "Hello";
char array2[100] = "Hello"; 

array1==array2;

다음과 같이 같음 연산자를 사용하는게 유효해보일 수 있습니다만

지금 입아프게 말해왔듯이 배열의 이름은 배열의 시작주소(그 배열의 첫번째 요소 주소)이므로 주소가 같은걸 비교하는건 문자열 비교와 연관이 없습니다.

 

그런데 strcmp() 함수와 strncmp() 함수를 이용하면 간편하게 문자열을 비교할 수 있습니다.

이들 함수를 사용하려면 string.h 헤더 파일을 인클루드 해야 합니다.

 

함수 원형 예시 설명
#include <string.h>
int strcmp(const char * s1, const char * s2)
char array1[10] = "Good"
char array2[5] = "luck";
strcat(array1, array2);
array1의 문자열과 array2의 문자열을 비교
성공 : array1과 array2의 비교 결과 반환
#include <string.h>
int strncmp(const char * s1, const char * s2, size_t n)
char array1[10] = "Good"
char array2[5] = "luck";
strncat(array1, array2, 3);
array1의 문자열과 array2의 문자열 3글자를 비교
성공 : array1과 array2의 비교 결과 반환

strcmp()는 비교할 문자열 2개, strncmp()는 비교할 문자열 2개 및 char *s2의 몇글자씩 비교할 것인지 매개변수값으로 제공해주면 됩니다.

지금까지 사용했던 문자열 처리함수와 생긴건 거의 동일합니다.

 

그리고 strncmp()의 원형을 보시면 지금까지 설명드리진 않았으나 size_t가 몇글자씩 비교할것인지에 대한 자료형입니다. 자료형 size_t는 unsinged int로 정의되어 있습니다.

부호가 없는 정수형태이므로 음수는 존재하지 않고 0부터 존재하므로 0보다는 큰값이 들어와야 합니다.

 

만약에 array1이 5글자, array2가 10글자고 strncmp()에 n에 10을넣던, 100을 넣던 array1이 5글자라 5개까지의 문자만 비교하게 됩니다.

 

중요한건 성공시 array1과 array2의 비교 결과 반환 인데요. 어떤 비교 결과를 반환하는지 알아봅시다.

 

반환값 설명
양수(0보다 큰 값, 꼭 1이 나오는건 아님.) array1의 문자열이 array2의 문자열보다 크다.
0 array1과 array2의 문자열이 같다
음수(0보다 작은 값, 꼭 -1이 나오는건 아님.) array1의 문자열이 array2의 문자열보다 작다.

 

 

문자열이 같다는건 이해가 되는데 문자열이 크다, 작다는 무슨 의미일까요?

 

strcmp()과 strncmp()의 기본적인 원리는 앞에서부터 각각 문자를 비교하는데 ASCII 코드값으로 비교를 하게 됩니다.

문자열을 비교한다 == 문자들의 ASCII 값들을 비교한다 와 동일한 표현이라고 보시면 됩니다.

 

참고를 위한 ASCII 코드 테이블입니다.

아래 예제를 보면서 간단하게 익혀봅시다.

char array1[100] = "Hello";
char array2[100] = "Hello"; 
int result = strcmp(array1, array2); //array1, array2 비교
printf("%d", result);

다음 result의 출력값은 0입니다.

(같은 문자열이므로 strcmp의 반환값은 0)

 

char array1[100] = "Hello Aorld";
char array2[100] = "Hello Borld"; 
int result = strcmp(array1, array2); //array1, array2 비교
printf("%d", result);

다음 result의 출력값은 -1(음수)입니다.

Hello(공백) 까지는 똑같으나

A와 B부턴 array1과 array2가 달라집니다

 

ASCII코드 표를 보시면 A가 65, B가 66로

문자 하나를 놓고 비교했을때 array1 < array2 입니다.

그래서 출력값이 -1 입니다.

 

(ASCII코드표에서 대소문자는 시작 번호가 다르니 주의하세요. 소문자 a -> 97, 대문자 A -> 65)

 

char array1[100] = "Hello Zorld";
char array2[100] = "Hello Yorld"; 
int result = strcmp(array1, array2); //array1, array2 비교
printf("%d", result);

다음 result의 출력값은 1(양수) 입니다.

Z > Y 입니다.

 

문자열이 크다, 작다에 대해 이해하셨을거라고 봅니다.

 


그럼

 

"Hello"

"Hello World"

 

다음과 같은 문자열을 비교하면 어떻게 될까요?

위 문자열은 "Hello\0" 와 "Hello World\0"를 비교하는것과 같습니다.

 

앞의 Hello까진 같지만 그 뒤부턴 \0(널문자)와 공백문자를 비교하게 됩니다.

널문자는 ASCII 코드표 0번이고 공백은 32번입니다.

 

ASCII 코드 표를 참고하시면 알겠지만 ASCII코드 표는 기본적으로 0~127번까지만 사용합니다.

(char형의 표현 범위)

그래서 0보다 작은 숫자는 없습니다. 코드 비교시 널문자보다 작은건 없다 이거죠.

 

일반적으로 짧은 문자는 긴문자에 비해 널문자가 빨리 등장하므로 strcmp("Hello",  "Hello World"); 와 같은식으로 비교를 하면 무조건 음수가 나오게 됩니다.

 

(비교를 거꾸로 돌리면 양수가 나오게 되겠죠. -- "Hello World"와 "Hello" 비교)

 

물론 strcmp()는 문자열이 같은지 비교하는것이 대부분일 것이고 0이면 같은거고, 0이 아니면 같지 않은것입니다만 그래도 왜 음수랑 양수가 나오는지 정확히 알면 좋으므로 설명드렸습니다.

 

이제 strncmp()함수를 보겠습니다.

 

char array1[100] = "Hello";
char array2[100] = "Hello World";
int result = strncmp(array1, array2, 5); //array1, array2 5글자 비교
printf("%d", result);

다음 출력결과는 0입니다.

array1의 Hello와 array2의 5바이트인 Hello 까지 모두 동일합니다.

 

char array1[100] = "Hello Porld";
char array2[100] = "Hello World";
int result = strncmp(array1, array2, 0); 
printf("%d", result);

n에 0이 제공되면 무조건 0이 출력됩니다.

 

char array1[100] = "Hello Porld";
char array2[100] = "Hello World";
int result = strncmp(array1, array2, -1);
printf("%d", result);

-1이 들어가면 unsinged int타입에서 언더플로우가 일어나 자료형의 최대값이 들어가므로 문자열 끝까지 비교합니다.

또한 n에 아주큰 값을 넣어도 array1, array2의 최대 길이까지 비교합니다.

 

*여기서 끝까지 비교한다는 것은 다른것이 나올때까지 비교하거나 널문자를 만나서 끝을 감지할때까지 입니다.

이렇게 비교를 할때 양수, 음수가 반환되는 성질을 이용해서 문자열을 정렬하는데 이용도 가능합니다.

 

strupr() 함수와 strlwr() 함수

strupr() 함수와 strlwr() 함수를 이용하면 문자열을 통째로 소문자 또는 대문자로 바꿀 수 있습니다.

 

함수 원형 예시 설명
#include <string.h>
char * strupr(const char * s)
char array1[10] = "Good"
strupr(array1);
array1에 저장된 문자열을 대문자로 변환
성공 : 변환된 문자열의 시작 주소 반환
#include <string.h>
char * strlwr(const char * s)
char array1[10] = "Good"
strlwr(array1);
array1에 저장된 문자열을 소문자로 변환
성공 : 변환된 문자열의 시작 주소 반환

 

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


int main(){
	char array1[10] = "abcdefg";
	char * p1 = strupr(array1);
	char * p2 = strlwr(array1);
	
	puts(p1);
	puts(p2);

	return 0;
}

abcdefg
abcdefg

--------------------------------
Process exited after 0.004559 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

strupr(), strlwr()의 반환 형태가 대문자, 소문자로 바뀐 문자열의 주소기 때문에

문자열 포인터로 받아내야 합니다.

 

그런데 출력값을 보시면 소문자만 출력됩니다.

사실 strupr()이던 strlwr()이던 변경된 문자열은 매개변수로 전달된 문자열이기 때문에 매개 변수에 제공한 주소와 반환된 주소가 결국 동일합니다.

 

즉 대문자로 변환하는 과정이 array1에 들어가서 원본이 변경되었고, 다시 소문자로 바꿔서 p1 p2 둘다 array1의 주소를 가리키게 되어 같은 값이 출력되었습니다.

 

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


int main(){
	char array1[10] = "abcdefg";
	char array2[10] = "abcdefg";
	
	char * p1 = strupr(array1);
	char * p2 = strlwr(array2);
	
	puts(p1);
	puts(p2);

	return 0;
}

ABCDEFG
abcdefg

--------------------------------
Process exited after 0.005115 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

문제 해결을 위해선 이렇게 배열을 두개 만들어주면 됩니다.

 

문자 분류 함수

문자열 처리시 문자의 유형에 따라 구분해서 처리해야 하는 경우가 있습니다. 이때 문자 분류 함수를 사용하면 문제를 조금더 쉽게 처리할 수 있습니다. 문자 분류 함수는 ctype.h를 인클루드 해야합니다.

 

함수설명문자 검사문자 변환

int isalnum ( int c ); c가 알파벳 또는 숫자이면 0이 아닌 값을 반환한다.
int isalpha ( int c ); c가 알파벳이면 0이 아닌 값을 반환한다.
int iscntrl ( int c ); c가 제어 문자이면 0이 아닌 값을 반환한다.
int isdigit ( int c ); c가 숫자이면 0이 아닌 값을 반환한다.
int isgraph ( int c ); c가 그래픽 문자이면 0이 아닌 값을 반환한다.
int islower ( int c ); c가 소문자이면 0이 아닌 값을 반환한다.
int isprint ( int c ); c가 출력할 수 있는 문자이면 0이 아닌 값을 반환한다.
int ispunct ( int c ); c가 구두점 문자이면 0이 아닌 값을 반환한다.
int isspace ( int c ); c가 공백 문자이면 0이 아닌 값을 반환한다.
int isupper ( int c ); c가 대문자이면 0이 아닌 값을 반환한다.
int isxdigit ( int c ); c가 16진 숫자이면 0이 아닌 값을 반환한다.
int tolower ( int c ); c를 소문자로 변환한다.
int toupper ( int c ); c를 대문자로 변환한다.
int __toascii ( int c ); c를 아스키 코드로 변환한다.

출처 : https://ko.wikipedia.org/wiki/C_%EB%AC%B8%EC%9E%90_%EB%B6%84%EB%A5%98

 

"문자열" 이 아닌 "문자" 분류 함수라서 매개변수값이 문자 하나임에 유의하시고, 자세한 내용은 인터넷에 많은 레퍼런스가 있으니 참고 부탁드립니다.

 

 

SNS 공유하기
네이버밴드
카카오톡
페이스북
X(트위터)

최근글
인기글
이모티콘창 닫기
울음
안녕
감사
당황
피폐