ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C언어 문자열 예제 (인프런)
    CS/C언어 2020. 1. 10. 20:51

    ※ 인프런 무료강좌 C로 배우는 자료구조(권오흠 교수님)를 보고 개인적인 복습을 위해 정리한 내용입니다.


    첫번째 연습문제

    사용자가 입력한 문자열을 공백포함 그대로 출력하고, 문자열의 길이(공백포함 길이)도 같이 출력한다.

     

    풀이 1

    #include <stdion.h>
    #include <string.h>
    
    #define BUFFER_SIZE 20
    
    int main() {
    	char buffer[40];
    	
    	while(1) {
    		printf("$ ");
    		fgets(buffer, BUFFER_SIZE, stdin);
    		buffer[strlen(buffer)-1] = '\0';
    		printf("%s:%d\n", buffer, strlen(buffer));
    }

     

    "$ " : 프롬프트(prompt) 문자라고 한다. 사용자의 입력을 받겠다는 의미이다.

     

    scanf 함수는 공백을 기준으로 단어를 구분하고, 단어를 하나씩만 입력받기 때문에

    공백을 포함한 문자열을 입력받기 위해서는 다른 함수를 사용해야 한다.

     

    gets 함수는 C 표준 라이브러리 함수로, 라인 단위로 입력을 받는다. (\n 기준으로 입력값을 구분)

    라인 단위로 입력을 받기 때문에 공백을 포함한 문자열을 입력받기에 적합하다.

    그러나 gets 함수는 안전하지 않다는 문제가 있다. ( gets 자체를 지원하지 않는 컴파일러도 점점 늘어나고 있다. )

    gets는 무조건 문장의 끝까지 다 읽어버리기 때문에, 정해진 buffer size를 초과해서 입력을 받는 일이 일어날 수 있다.

     

    이러한 gets 함수의 안전성 문제를 해결하기 위해 gets 대신 fgets 함수를 사용하는 것을 고려해볼 수 있다.

    fgets는 데이터를 읽어서 저장할 배열뿐 아니라 배열의 크기도 매개변수로 받기 때문에

    배열의 크기를 초과해 데이터를 읽는 경우가 없다. (메모리 위반이 일어나지 않음)

    fgets(buffer, BUFFER_SIZE, stdin);

    BUFFER_SIZE : 변수 buffer의 size를 매크로를 이용해 상수화한 것(프로그램을 더 안정적으로 만듦)

    stdin : 세번째인자로는 파일 포인터를 넣어줘야 하는데, 키보드로 입력한 값을 받을 것이므로 표준입력파일의 파일 포인터인 stdin을 인자로 넣으면 된다. (키보드 = 표준입력파일)

    (fgets 함수는 표준 입력파일 뿐만 아니라 임의의 파일로부터도 입력값을 받을 수 있는 함수이다)

     

    그러나, fgets는 gets와 달리 new line charactor('\n') 까지 읽고 문자열에 포함시키기 때문에

    의도하지 않은 결과를 얻게 될 수 있다.

    입력받은 결과에서 '\n'을 제외하고 싶다면, 문자열의 끝에 저장된 '\n'을 '\0'(null charactor)로 바꿔주어야 한다.

    buffer[strlen(buffer)-1] = '\0';

     

    그 외에도 문제가 되는 부분이 있는데,

    fgets 함수는 정해진 사이즈를 초과하는 부분의 문자열은 읽어들이지 않고 넘어간 다음,

    다음 순서에 나머지 문자열을 마저 읽어들인다는 것이다.

    이는 예제에서 원하는 결과가 아니다.

     

    이런식으로 c언어는 다양한 표준 입력 함수를 제공하고, 함수마다 조금씩 차이가 있는데

    그런 차이들을 일일히 다 숙지하고 의도에 맞는 적절한 함수를 선택하기가

    어려울 수 있다.

    그래서 라인 단위로 입력을 받거나 할 때, 그냥 직접 함수를 만들어서 사용하는 경우가 많다.

    상황에 따라 조금씩 원하는 바가 다르므로 표준 입력함수를 쓰는 대신 원하는 것을 잘 반영하는 함수를 직접 만들어 사용하는 것도 괜찮은 방법이라고 볼 수 있다.

     

    직접 만든 함수 예시

    int read_line( char str[], int limit) {
    	int ch, i = 0;
    
    	while ((ch = getchar() != '\n')
    		if (i < limit)
    			str[i++] = ch;
    
    	str[i] = '\0';
    	return i;
    
    }

     

     

    풀이 2

    직접 만든 함수 활용해서 예제 코드 수정하기

    #include <stdion.h>
    #include <string.h>
    
    #define BUFFER_SIZE 100
    
    int read_line(char str[], int limit);
    
    
    int main() {
    	char buffer[BUFFER_SIZE];
    	int len;
    	while(1) {
    		printf("$ ");
    		len = read_line(buffer, BUFFER_SIZE);
    		printf("%s:%d\n", buffer, len);
    	}
    
    	return 0;
    }
    
    
    int read_line(char str[], int limit) {
    	int ch, i = 0;
    
    	while ((ch = getchar()) != '\n')
    		if (i < limit)
    			str[i++] = ch;
    
    	str[i] = '\0';
    	return i;
    
    }

     

    read_line 함수는 배열과 배열의 최대 크기를 매개변수로 받는다.

     

    while 반복문에서 getchar 함수(표준라이브러리함수)는

    한 글자씩 읽어서 ch에 담고, ch에 담긴 문자가 '\n'이 아닌 경우 while반복문이 실행된다.

     

    while 반복문에서는 i가 limit를 초과하지 않을 경우,

    ch에 담긴 문자를 배열 str의 원소(str[i])에 저장하고,

    i를 1증가시키는(i++) 과정을 반복한다.

     

    두번째 연습문제

    첫번째 연습문제와 달리 사용자가 입력한 문장을 공백 포함 그대로 출력하지 않고,

    불필요한 공백을 제거하고 출력한다.

    문장의 앞뒤에 있는 공백을 제거하고, 단어 사이 두 개 이상의 연속된 공백은 하나의 공백 문자로 대체하라.

     

    풀이

    사용자가 입력한 문장을 라인 단위로 통째로 가져오지 않고,

    한 문자씩 읽어서 가져오는 과정에서 불필요한 공백들은 저장하지 않고 필요한 것들만 문자 배열에 저장하여 출력하는 방식으로 풀 것이다.

    맨 앞의 공백들을 건너 뛰고, 공백이 아닌 문자부터 저장하기 시작한다.

    문자를 하나씩 저장할 때마다 인덱스 변수 i 값은 1씩 증가시킨다.

    문자 다음에 오는 첫번째 공백은 저장하고, 그 다음 공백들은 건너뛴다.

     

    코드 보기

    #include <ctype.h> //isspace 함수 제공 라이브러리
    
    int main() {
    
    	char line[80];
    	while(1) {
    		printf("$ ");
    		int length = read_line_with_compression(line, 80);
    		printf("%s:%d\n", line, length);
    	}
    	
    	return 0;
    }
    
    
    int read_line_with_compression(char compressed[], int limit) {
    	int ch, i = 0;
    	while ((ch = getchar() != '\n') {
    		if (i < limit-1 && (!isspace(ch)) || (i > 0 && !isspace(compressed[i-1])))
    			compressed[i++] = ch;
    	}
    
    	if (i > 0 && isspace(compressed[i-1]))
    		i--;
    	compressed[i] = '\0';2
    			
    	}

    read_line_with_compression 함수

     

    1) isspace 함수

    white space(공백/탭) 문자인지 검사하는 함수, ctype.h 라이브러리에서 제공

    !isspace(ch) : white space 문자인지 체크 (공백, 탭 체크)

    ch ! = " " : 공백인지 체크

     

    2) while문 내 if문 조건 (한글자씩 검사하며 불필요한 공백인지 체크)

    i < limit-1 && (!isspace(ch)) || (i > 0 && !isspace(compressed[i-1]))

    우선 i가 limit-1 미만이어야 한다. (마지막 문자(compressed[limit-1])로는 '\0'이 들어가야하기 때문)

    그리고 ch가 공백이 아닐 때 while문을 실행한다.

    혹은 ch가 공백이지만 이전 문자가 공백이 아니고(!isspace(compressed[i-1])), 첫번째 칸이 아닐 때(i>0)에도 while문을 실행한다.

     

    3) while문 실행 (불필요한 공백이 아니라면 저장)

    compressed[i]ch를 저장하고, i값을 1 증가시킨다(i++)

     

    4) while문 종료 후 if문 실행 (마지막 문자가 공백인지 확인)

    ch가 '\n'일 때 while문이 종료된다.

    이 때 '\n'직전에 저장한 문자 compressed[i-1]이 공백인 경우, 한 칸 앞당겨서(i--)

    '\0'을 저장한다.

    compressed[i] = '\0';

    댓글

Designed by Tistory.