C언어/C언어 자료실

C언어 포인터로 문자열 제어하기

Chipmunks 2019. 4. 9. 15:52
728x90

< 코드 결과 사진 >


코드 개요

본 코드는 C언어 문자열을 포인터로 제어해보는 코드입니다.

입력받은 문자열을 모두 대문자, 소문자로 만들거나 문자열을 거꾸로 만들어주는 프로그램입니다.


작업 환경

처음에는 Mac OS X 환경에서 코드를 작성하고 테스트하였습니다.

Windows의 Visual Studio 2017 IDE로 빌드 결과 문자열 리터럴 부분에서 타입 오류가 났습니다.

그래서, myStrCopy(char*, char*) 함수의 시그니쳐를 myStrCopy(char*, const char*) 으로 변경하였습니다.


디버그시 scanf의 보안 이슈 알림을 중지하려고 소스 코드 상단에 _CRT_NO_SECURE_WARNINGS 매크로 넣었습니다.


원본 질문

본 질문은 N사의 지식인에 올라온 질문입니다. 원본 질문은 아래와 같습니다.


키보드로 문자열 입력받아 출력하는 코드인데요


[입력화면]

문자열을 입력하세요:

1)문자열을 모두 대문자로 출력하세요.

2)문자열을 모두 소문자로 출력하세요.

3)문자열을 거꾸로 출력하세요.

4)원본을 그대로 출력하세요.

5)프로그램을 종료합니다.


출력메뉴를 선택하세요:

[출력화면]

원본데이터는 [...]이고, [대문자or소문자or거꾸로or원본으]로 [...]입니다.

(or프로그램을 종료합니다.)


너무 코드가 길고 복잡해서 짜도 오류만 계속 뜹니다 ㅠㅠ

주석달아서 코드좀 짜주세요

reverse나 그런 라이브러리 쓰면 안됩니다.

배열이나 기본적인 명령어를 통해 짜야합니다 ㅠㅠ

내공 드립니다ㅠㅠ 내공 드립니다


출처 : https://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040101&docId=324869671


프로그램 코드

프로그램 코드는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// Written by Chipmunk, 2019 / 04 / 09
// Web site : https://itchipmunk.tistory.com
 
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
// 기본 문자열 크기
#define BUFFER_SIZE 1024
 
// 메뉴 열거
enum PRINT_MENU
{
    PRINT_UPPER = 1
    , PRINT_LOWER
    , PRINT_REVERSE
    , PRINT_ORIGINAL
    , EXIT
};
 
// 문자열 복사 함수
void myStrCopy(char* dest, const char* source);
 
// 프로그램 함수
char* InputStringMenu();
void OutputMenu(char* inputStr);
void Execute(int choice, char* inputStr);
 
// 기능 함수
char* GetUpper(char* source);
char* GetLower(char* source);
char* GetReverse(char* source);
char* GetOriginal(char* source);
 
int main(int argc, char* argv[])
{
    char* inputStr = InputStringMenu();
    OutputMenu(inputStr);
 
    return 0;
}
 
char* InputStringMenu()
{
    char* str = 0;
    char* tmpStr = 0;
 
    str = (char*)malloc(BUFFER_SIZE); // BUFFER_SIZE 만큼 메모리 주소를 할당하고 그 주소를 담음
    
    // 개행 포함 한 줄을 읽음
    do {
        printf("[입력화면]\n문자열을 입력하세요: ");
    } while (fgets(str, BUFFER_SIZE, stdin) == 0);
 
    // 마지막 개행을 지워줌
    for (tmpStr = str; *tmpStr != '\n'; tmpStr++);
    *tmpStr = '\0';
 
    return str;
}
 
void OutputMenu(char* inputStr)
{
    assert(inputStr != 0);
    int choice = 0;
 
    // 메뉴 출력
    printf("%d) 문자열을 모두 대문자로 출력하세요.\n", PRINT_UPPER);
    printf("%d) 문자열을 모두 소문자로 출력하세요.\n", PRINT_LOWER);
    printf("%d) 문자열을 거꾸로 출력하세요.\n", PRINT_REVERSE);
    printf("%d) 원본을 그대로 출력하세요.\n", PRINT_ORIGINAL);
    printf("%d) 프로그램을 종료합니다.\n\n", EXIT);
 
    // 유효한 메뉴를 받을 때까지 반복
    while (choice < 1 || choice > 5)
    {
        printf("출력메뉴를 선택하세요:");
        scanf("%d"&choice);
    }
 
    // 실행 함수에 메뉴와 입력 문자열 전달
    Execute(choice, inputStr);
}
 
void Execute(int choice, char* inputStr)
{
    assert(choice >= PRINT_UPPER && choice <= EXIT);
    assert(inputStr != 0);
 
    char* outputStr = 0;
    char typeStr[20= {};
 
    // 전달 받은 메뉴에 따라 기능 함수 실행
    switch (choice)
    {
    case PRINT_UPPER:
        myStrCopy(typeStr, "대문자");
        outputStr = GetUpper(inputStr);
        break;
    case PRINT_LOWER:
        myStrCopy(typeStr, "소문자");
        outputStr = GetLower(inputStr);
        break;
    case PRINT_REVERSE:
        myStrCopy(typeStr, "거꾸로");
        outputStr = GetReverse(inputStr);
        break;
    case PRINT_ORIGINAL:
        myStrCopy(typeStr, "원본으");
        outputStr = GetOriginal(inputStr);
        break;
    case EXIT:
        printf("프로그램을 종료합니다.\n");
 
        // 원본 데이터 메모리 공간 해제
        free(inputStr);
        return;
        break;
    }
 
    // 결과 출력
    printf("원본 데이터는 \"%s\"이고, %s로 \"%s\" 입니다.\n"
        , inputStr
        , typeStr
        , outputStr
    );
 
    // 원본 데이터, 출력 데이터 메모리 공간 해제
    free(inputStr);
    free(outputStr);
}
 
char* GetUpper(char* source)
{
    assert(source != 0);
 
    char* dest = 0;
    dest = (char*)malloc(BUFFER_SIZE);
 
    // 대상문자열의 주소 위치를 보존하기 위해 변경되는 주소를 담을 변수 생성
    char* tmpDest = dest;
 
    // 원본 문자열을 끝까지 돌며, 대상 문자열에 저장
    for (; *source != '\0'; source++, tmpDest++)
    {
        // 소문자인지 확인
        if (*source >= 'a' && *source <= 'z')
        {
            // 'a' 아스키 코드와의 차이를 'A' 아스키 코드에 더함
            *tmpDest = 'A' + (*source - 'a');
        }
        else
        {
            // 그 외에는 그대로 저장
            *tmpDest = *source;
        }
    }
 
    // 대상 문자열의 마지막에 널문자 추가
    *tmpDest = '\0';
 
    return dest;
}
 
char* GetLower(char* source)
{
    assert(source != 0);
 
    char* dest = 0;
    dest = (char*)malloc(BUFFER_SIZE);
 
    // 대상문자열의 주소 위치를 보존하기 위해 변경되는 주소를 담을 변수 생성
    char* tmpDest = dest;
 
    // 원본 문자열을 끝까지 돌며, 대상 문자열에 저장
    for (; *source != '\0'; source++, tmpDest++)
    {
        // 대문자인지 확인
        if (*source >= 'A' && *source <= 'Z')
        {
            // 'A' 아스키 코드와의 차이를 'a' 아스키 코드에 더함
            *tmpDest = 'a' + (*source - 'A');
        }
        else
        {
            // 그 외에는 그대로 저장
            *tmpDest = *source;
        }
    }
 
    // 대상 문자열의 마지막에 널문자 추가
    *tmpDest = '\0';
 
    return dest;
}
 
char* GetReverse(char* source)
{
    assert(source != 0);
 
    char* dest = 0;
    char* endInputStr = 0;
 
    dest = (char*)malloc(BUFFER_SIZE);
 
    // 원본과 대상문자열의 주소 위치를 보존하기 위해 변경되는 주소를 담을 변수 생성
    char* tmpSource = source;
    char* tmpDest = dest;
 
    // 임시 문자열의 위치를 마지막으로 옮김
    for (; *tmpSource != '\0'; tmpSource++)
    {
    }
 
    // 널문자 바로 앞으로 이동한 주소를 저장
    endInputStr = tmpSource - 1;
 
    // 원본 문자열의 주소 위치까지 되돌아가며 대상 문자열에 저장
    for (; endInputStr >= source; endInputStr--, tmpDest++)
    {
        *tmpDest = *endInputStr;
    }
 
    // 대상 문자열의 마지막에 널문자 추가
    *tmpDest = '\0';
 
    return dest;
}
 
char* GetOriginal(char* source)
{
    assert(source != 0);
 
    // 원본 문자열을 전부 복사하여 반환
    char* dest = 0;
    dest = (char*)malloc(BUFFER_SIZE);
 
    myStrCopy(dest, source);
 
    return dest;
}
 
void myStrCopy(char* dest, const char* source)
{
    assert(dest != 0);
    assert(source != 0);
 
    // 원본 문자열의 끝까지 돌며, 대상 문자열에 복사
    for (; *source != '\0'; source++, dest++)
    {
        *dest = *source;
    }
 
    // 대상 문자열의 마지막에 널문자 추가
    *dest = '\0';
}
cs


마무리

포인터로 문자열 다루는 코드를 복습하고 싶어 많이 생각하고 짰습니다.

scanf() 함수로 문자열을 받는 것은 범위를 넘어서는 위험한 보안 이슈가 있고, 공백을 포함하지 못하기 때문에 따로 처리를 했습니다. fgets() 함수로 버퍼의 크기를 입력하고 개행까지 개행을 포함하여 읽습니다. 그 다음에 마지막 개행을 지워주는 작업을 합니다.

메모리 이슈 관련해서도 언제 메모리를 해제해주면 좋을지도 생각했습니다.

처리 작업할 때 메모리를 새로 생성하게 되어 그 메모리 주소를 반환합니다. 그리고 그 메모리 주소를 해제하는거는 사용자 몫입니다. 사람들과 협업을 할 때에는 코딩 표준을 정해서 네이밍만으로 이 함수에서 반환하는 주소를 내가 꼭 해제해야 하는구나라는 것을 알도록 해야 할 것 같습니다.

코드에서 궁금한 점이나 보완할 점 있으면 언제든지 환영합니다!
아래 하트 버튼과 댓글 남겨주시면 답글 달아드리겠습니다.