이번 포스팅에서 우리가 배워볼 것은 다음과 같다.
- 문자열
- NULL 문자
- 문자 배열의 초기화 방법 종류
- 문자열 예제
- 문자 입출력 라이브러리
- 문자 처리 라이브러리 함수 & 예제
- string 클래스
1. 문자열
- 문자열(string) : 문자들이 여러 개 모인 것
- "A"
- "Hello World!"
- "변수 score의 값은 %d입니다."
- 문자열 상수
- "Hello World"
- "Hong"
- "string!#$"
- "guest123"
- 문자열 변수
- char형 배열
2. NULL 문자
- NULL 문자는 문자열의 끝을 나타낸다.
- "SEOUL" => S / E / O / U / L \n
- 문자열은 어디서 종료되는지 알 수가 없으므로 표시를 해주어야 한다.
3. 문자 배열의 초기화 방법 종류
- 문자 배열 원소들을 중괄호 안에 넣어주는 방법
- char str[6] = {'H', 'e', 'l', 'l', 'o', '\o'}; // \o : !
- 각각의 문자 배열 원소에 원하는 문자를 개별적으로 대입하는 방법
- str[0] = 'W';
- str[1] = 'o';
- str[2] = 'r';
- str[3] = 'l';
- str[4] = 'd';
- str[5] = '\o';
- 문자열 상수를 사용하여 초기화하는 방법
- char str[6] = "Hello";
- 배열을 크기를 지정하지 않을 때 : 컴파일러가 자동으로 배열의 크기를 초기화 값에 맞추어 설정하는 방법
- char str[] = "C Bible"; // 배열의 크기는 8이 된다.
- strcpy()를 사용하여 문자열을 문자 배열에 복사하는 방법
- strcpy(str, "World");
4. 문자열 예제
1. 문자열 중 일부 문자 변경하기
#include <iostream>
using namespace std;
int main() {
char str[] = "komputer";
int i;
for (i = 0; i < 8; i++) {
cout << str[i];
}
str[0] = 'c';
cout << endl;
for (i = 0; i < 8; i++) {
cout << str[i];
}
return 0;
}
5. 문자열 역순
#include <iostream>
using namespace std;
int main() {
char src[] = "Seoul";
char dst[6];
int i = 0;
cout << "원래 문자열 = " << src << endl;
while(src[i] != 0){
dst[i] = src[4 - i];
i++;
}
dst[i] = '\0'; // \0 : NULL
cout << "역순 문자열 = " << dst << endl;
return 0;
}
6. 문자열 길이 구하기
#include <iostream>
using namespace std;
int main() {
char str[30] = "C language is easy";
int i = 0;
while(str[i] != 0) {
i++;
}
cout << "문자열 " << str << "의 길이는 " << i << "입니다." << endl;
return 0;
}
5. 문자 입출력 라이브러리
0. 들어가기 전 알아야 할 상식
키보드로 문자를 입력하면 모든 문자는 일단 버퍼로 들어가게 된다. 버퍼에 문자가 들어가다가 엔터키(\n)가 입력되는 순간, 버퍼에 있는 모든 문자들이 프로그램으로 간다.
입출력 함수 | 설명 |
int getchar(void) | 하나의 문자를 읽어서 반환한다. |
int getch(void) | 하나의 문자를 읽어서 반환한다.(버퍼를 사용하지 않음). |
void putchar(int c) | 변수 c에 저장된 문자를 출력한다. |
void putch(int c) | 변수 c에 저장된 문자를 출력한다.(버퍼를 사용하지 않음.) |
scanf("%c", &c) | 하나의 문자를 읽어서 변수 c에 저장한다. |
printf("%c", c) | 변수 c에 저장된 문자를 출력한다. |
헤더파일 | 버퍼사용여부 | 에코여부 | 응답성 | 문자수정여부 | |
getchar() | <stdio.h> | 사용함 (엔터키를 눌러 입력됨) |
에코 | 줄단위 | 가능 |
getch() | <conio.h> | 사용하지 않음 | 에코하지 않음 | 문자단위 | 불가능 |
getche() | <conio.h> | 사용하지 않음 | 에코 | 문자단위 | 불가능 |
1. scanf(), printf() 문자열 입출력 & 예제
- scanf()의 사용법
- scanf()는 한번에 두 개 이상의 문자열도 받아들일 수 있다.
- 여기서는 예제로 scanf_s() 함수르 사용할 것이다. scanf()랑 별 차이가 없으며, 오버플로우 공격을 방지 위해서 scanf_s()를 사용하는 것이다.
#include <stdio.h>
int main() {
char str[10];
scanf_s("%s", str, sizeof(str));
char s1[10];
char s2[10];
char s3[10];
// 사용자가 one two three 와 같이 입력하면 s1에는 one이, s2에는 two가, s3에는 three가 할당된다.
scanf_s("%s%s%s", s1, sizeof(s1), s2, sizeof(s2), s3, sizeof(s3));
printf("str : %s\n", str);
printf("s1 : %s\n", s1);
printf("s2 : %s\n", s2);
printf("s3 : %s\n", s3);
return 0;
}
2. 문자열 수치 변환
문자열 "36.5\0" 을 수치인 36.5로 변환할 수 있다. 대표적으로 scanf() 함수와 <stdlib.h> 라이브러리의 함수가 있다.
scanf() 함수이 경우, 키보드로 "36.5\n"를 입력하면 scanf("%f", x); 를 통해 x 변수에 float 형인 36.5로 변환해준다.
stdlib.h 라이브러리의 경우 아래의 표에 있는 함수로 바꿀 수 있다.
함수 | 설명 |
int atoi(const char *str); | str을 int형으로 변환한다. |
long atol(const char *str); | str을 long형으로 바꾼다. |
double atof(const char *str); | str을 double형으로 변환한다. |
아래 예제를 통해서 확인해보자.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <typeinfo> // 타입 확인 라이브러리
int main(void) {
char s[10];
strcpy_s(s, "2000");
int a = atoi(s);
printf("a값 : %d\n", a);
printf("a의 타입 : %s", typeid(a).name());
return 0;
}
3. gets()와 puts() 문자열 입출력 & 예제
- gets()
- 표준 입력으로부터 enter키가 나올 때까지 한 줄의 라인을 입력한다.
- 문자열에 줄바꿈 문자('\n')는 포함되지 않으며 대신에 자동으로 NULL 문자('\o')를 추가한다.
- 입력 받은 문자열은 buffer가 가리키는 주소에 저장된다.
- 우리는 이번 포스팅에서 get_s() 함수로 사용할 것이다. get_s() 역시 C 언어에서 안전한 문자열 입력을 위해 사용되는 함수이다. 일반적으로 gets() 함수를 대체하여 사용되는데, gets()는 scanf_s()와 같이 입력 버퍼의 크기를 체크하지 않고 문자열을 입력받아 버퍼 오버플로우와 같은 보안 문제를 일으킬 수 있다. get_s()는 문자열을 입력 받을 때 문자열의 길이를 명시적으로 지정하여 버퍼 오버플로우를 방지하는데, 이 함수는 일반적으로 버퍼의 크기를 함께 입력받아 안전하게 문자열을 받아들인다. 이를 통해 입력 버퍼의 크기를 초과하는 문자열이 들어오는 것을 방지하고, 프로그램의 안전성을 높일 수 있다.
- 단, get_s()는 C11 표준에서는 포함되어 있지 않고, 일부 컴파일러에서만 지원될 수 있으며, gets() 함수보다 더 많은 처리를 필요로 하기 때문에 사용에 조심해야 한다. 그래서 문자열의 길이를 직접 제어할 수 있는 함수를 사용하는 것이 좋다.
- puts()
- str이 가리키는 문자열을 받아서 화면에 출력한다.
- 이 때 NULL 문자('\o')는 줄바꿈 문자('\n')로 변경된다.
- gets()와 puts()의 예제
- int main() { char str[21]; // 20개의 문자와 '\o'을 저장할 수 있다. printf("문자열을 입력하시오.\n"); gets_s(str); printf("입력된 라인은 다음과 같습니다.\n"); puts(str); return 0; }
4. getchar(), putchar() 사용 & 예제
#include <iostream>
using namespace std;
int main() {
int ch; // 정수형에 주의
while(1){
ch = getchar(); // 문자를 입력 받는다.
if(ch == 'q'){
break;
}
putchar(ch);
}
return 0;
}
5. getch(), putch() 사용 & 예제
#include <conio.h>
using namespace std;
int main() {
int ch; // 정수형에 주의
while (1) {
ch = _getch(); // 문자를 입력 받는다.(버퍼를 사용하지 않는다.)
if (ch == 'q') {
break;
}
_putch(ch);
}
return 0;
}
6. 문자 처리 라이브러리 함수 & 예제
#include <ctype.h>나 #include <string.h>로 문자 처리 라이브러리 함수를 사용할 수 있으며 주로 문자를 검사하거나 문자를 변환한다.
1. <ctype.h> 라이브러리 함수 목록 & 예제
함수 | 설명 |
isalnum() | 알파벳 또는 숫자인지 여부를 확인합니다. |
isalpha() | 알파벳 문자인지 여부를 확인합니다. |
isdigit() | 숫자인지 여부를 확인합니다. |
isxdigit() | 16진수(digit)인지 여부를 확인합니다. |
islower() | 소문자인지 여부를 확인합니다. |
isupper() | 대문자인지 여부를 확인합니다. |
isspace() | 공백 문자인지 여부를 확인합니다. |
ispunct() | 구두점(punctuation) 문자인지 여부를 확인합니다. |
isprint() | 출력 가능한 문자인지 여부를 확인합니다. |
isgraph() | 공백을 제외한 모든 출력 가능한 문자인지 여부를 확인합니다. |
isblank() | 공백 또는 탭 문자인지 여부를 확인합니다. |
tolower() | 대문자를 소문자로 변환합니다. |
toupper() | 소문자를 대문자로 변환합니다. |
isprint() | 출력 가능한 문자인지 여부를 확인합니다. |
ispunct() | 구두점(punctuation) 문자인지 여부를 확인합니다. |
isxdigit() | 16진수(digit)인지 여부를 확인합니다. |
isgraph() | 공백을 제외한 모든 출력 가능한 문자인지 여부를 확인합니다. |
isblank() | 공백 또는 탭 문자인지 여부를 확인합니다. |
isspace() | 공백 문자인지 여부를 확인합니다. |
아래 코드는 사용자로부터 입력을 받아 소문자를 대문자로 변환하여 출력하는 프로그램이다.
#include <stdio.h>
#include <ctype.h>
int main() {
int c;
while ((c = getchar()) != EOF) { // getchar() 함수를 사용하여 입력을 읽어들인다.
if (islower(c)) { // 소문자인지 검사
c = toupper(c); // 대문자로 변환
}
putchar(c); // putchar() 함수를 사용하여 변환된 문자를 출력한다.
}
return 0;
}
이 프로그램은 사용자가 입력한 소문자를 읽어 대문자로 변환하여 출력한다. 입력이 끝날 때까지 반복하여 동작하며, 입력이 끝나면 프로그램이 종료된다.
cf) 일반적으로 Windows 환경에서 'Ctrl + Z'를 누르면 입력 스트림을 종료하는 특별한 문자가 입력된다. 이는 파일의 끝(EOF, End of File)을 나타내는데, 위 코드에서 이를 감지하게 된다면 입력 루프를 빠져나가게 된다. 따라서 'Ctrl + Z'를 누르면 프로그램은 루프를 빠져나가고 종료된다. 이 동작은 주로 Windows 터미널에서 사용되며, Unix 기반 시스템에서는 'Ctrl + D'를 눌러 파일의 끝을 나타낸다.
2. <string.h> 함수 목록 & 예제
함수 | 설명 |
strcpy() | 문자열을 복사합니다. ex) char dst[6]; char src[6] = "Hello\o"; strcpy(dst, src); 결과 => dst[6] 에 "Hello\o" 값이 들어간다. |
strncpy() | 일부 문자열을 다른 문자열로 복사합니다. |
strlen() | 문자열의 길이를 반환합니다. ex) strlen("Hello")는 5를 반환 |
strcmp() | 두 문자열을 비교합니다. ex) int strcmp(const char *s1, const char *s2); |
strncmp() | 일부 문자열을 비교합니다. |
strcat() | 문자열을 연결합니다. ex) char dst[12] = "Hello\o"; char src[6] = "World\o"; strcat(dst, src); 결과 => dst[12] 에 "HelloWorld\o" 값이 들어간다. |
strncat() | 일부 문자열을 다른 문자열에 추가합니다. |
strchr() | 문자열에서 특정 문자를 찾습니다. |
strstr() | 문자열에서 특정 부분 문자열을 찾습니다. |
strtok() | 문자열을 구분자를 기준으로 토큰화합니다. |
이번에도 strcpy()와 strcat() 대신 strcpy_s() 함수와 strcat_s() 함수를 사용할 것이다. 이러한 함수들 시 버퍼 오버플로우와 같은 보안 취약점을 방지하고 안전한 문자열 조작을 지원하기 때문에 사용하는 것이다. strcpy_s와 strcat_s 함수들은 C11 표준에서 도입되었으며, 이전 버전의 C 표준 라이브러리에서는 지원되지 않을 수 있다. 따라서 어떤 컴파일러나 환경에서는 지원되지 않을 수 있으므로 사용 전에 해당 환경의 지원 여부를 확인해야 한다.
다음은 strcpy_s()와 strcat_s() 함수의 예제이다.
#include <stdio.h>
#include <string.h>
int main() {
char string[80];
strcpy_s(string, "Hello world from ");
strcat_s(string, "strcpy ");
strcat_s(string, "and ");
strcat_s(string, "strcat!");
printf("string = %s\n", string);
return 0;
}
1. 문자열 비교 & 예제
문자열 비교는 strcmp() 함수를 사용하며 구동 원리는 문자열 안의 각 문자들의 아스키 코드로 하나씩 비교하여 int형으로 반환하게 된다. 아래 예시를 들어보면 m은 p보다 아스키 코드값이 작으므로 음수가 반환되는 것이다.
s1 | s | t | r | c | m | p | \0 |
= | = | = | = | < |
s2 | s | t | r | c | p | y | \0 |
반환값 | s1과 s2의 관계 |
<0 | s1이 s2보다 작다 |
0 | s1이 s2와 같다. |
>0 | s1이 s2보다 크다. |
다음 예제를 통해 이해해보자.
#include <string.h>
#include <stdio.h>
int main(void) {
char s1[80]; // 첫 번째 단어를 저장할 문자배열
char s2[80]; // 두 번째 단어를 저장할 문자배열
int result;
printf("첫 번째 단어를 입력하시오. : ");
scanf_s("%s", s1, sizeof(s1));
printf("두 번째 단어를 입력하시오. : ");
scanf_s("%s", s2, sizeof(s2));
result = strcmp(s1, s2);
if (result < 0) {
printf("%s가 %s보다 앞에 있습니다. \n", s1, s2);
} else if (result == 0) {
printf("%s가 %s와 같습니다.\n", s1, s2);
} else{
printf("%s가 %s보다 뒤에 있습니다.\n", s1, s2);
}
return 0;
}
7. string 클래스
C/C++ 에서는 string이라는 라이브러리를 제공하고, 그 안에 string 클래스가 존재한다. string 클래스 안에는 문자열 저장 및 처리에 필요한 변수들과 함수들이 정의되어 있다.
cf) 클래스를 이용하여 변수를 정의한 것을 객체(object)라고 한다.
1. string 클래스 멤버 함수
멤버 함수 | 설명 |
s[i] | i번째 원소 |
s.empty() | s가 비어있으면 true 반환 |
s.insert(pos, s2) | s의 pos 위치에 s2를 삽입 |
s.remove(pos, len) | s의 pos 위치에 len만큼을 삭제 |
s.find(s2) | s에서 문자열 s2가 발견되는 첫번째 인덱스를 반환 |
s.find(pos, s2 | s가 pos 위치부터 문자열 s2가 발견되는 첫 번째 인덱스를 반환 |
2. string 클래스 예제
1. 문자열의 결합
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1 = "Slow", s2 = "steady";
string s3 = "the race.";
string s4;
s4 = s1 + " and " + s2 + " wins " + s3;
cout << s4 << endl;
return 0;
}
참고로 문자열 결합할 때 사용하는 '+' 기호는 우리가 봤을 때 당연한 것 같지만 누군가가 이렇게 문자열 합치도록 규칙을 정해놓은 것이다.
2. 문자열의 비교
string 객체로 작성된 문자열들은 ==, >, < 로 문자열을 비교할 수가 있다.
이게 당연한 것 같아 보이지만 내부에서는 알파벳 하나하나 돌아가면서 비교를 하기에 은근 복잡한 로직을 갖고 있다.
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1 = "Slow", s2 = "steady";
if (s1 == s2)
cout << "동일한 문자열입니다." << endl;
else
cout << "동일한 문자열이 아닙니다." << endl;
if (s1 > s2)
cout << "s1이 앞이 있습니다." << endl;
else
cout << "s2가 앞에 있습니다." << endl;
return 0;
}
3. 사용자로부터 이름과 주소를 받아서 친근하게 인사하는 프로그램을 작성해보자.
#include <iostream>
#include <string>
using namespace std;
int main() {
string name;
string address;
cout << "이름을 입력하시오 : ";
cin >> name;
cout << "주소를 입력하시오 : ";
cin >> address;
cout << address + "의 " + name + "씨 안녕하세요?" << endl;
return 0;
}
하지만 이렇게 작성하면 원하는 결과가 안나온다.
이 코드의 문제점은 이름과 주소에 공백이 포함될 경우 제대로 입력을 받지 못한다는 점이다. cin은 공백 문자를 기준으로 입력을 구분하기 때문에 공백이 포함된 문자열을 하나의 입력으로 처리하지 못한다. 이러한 경우 std::getline() 함수를 사용하여 한 줄 전체를 입력으로 받는 것이 좋습니다.
#include <iostream>
#include <string>
using namespace std;
int main() {
string name;
string address;
cout << "이름을 입력하시오 : ";
cin >> name;
cin.ignore(); // 엔터키를 없애기 위하여 필요하다.
cout << "주소를 입력하시오 : ";
getline(cin, address); // 주어진 입력 스트림에서 한 줄을 읽어들여 문자열로 반환한다. 일반적으로 키보드 입력이나 파일에서 데이터를 읽을 때 많이 사용된다.
cout << address + "의 " + name + "씨 안녕하세요?" << endl;
return 0;
}
3. 문자열 위치 찾기
문자열 "When in Rome, do as the Romans." 중에서 "Rome"이 몇 번째 위치에 있는지를 계산하는 프로그램을 만들어라.
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "When in Rome, do as the Romans.";
cout << s.find("Rome") << endl;
return 0;
}
4. 객체에서 문자 추출하기
사용자가 입력한 주민등록번호에서 '-' 문자를 삭제하는 프로그램을 작성하여 보자.
#include <iostream>
#include <string>
using namespace std;
int main() {
string number;
cout << "주민등록번호를 입력하세요.( - 포함) : ";
cin >> number;
cout << "-가 제거된 주민등록번호: ";
for (auto& c : number) {
if (c == '-') continue;
cout << c;
}
cout << endl;
}
5. 문자열의 배열
#include <iostream>
#include <string>
using namespace std;
int main() {
string list[] = { "철수", "영희", "길동" };
for (auto& x : list) {
cout << x + "야 안녕!" << endl;
}
return 0;
}
6. 해밍 거리 구하기
유전자를 나타내는 2개의 문자열을 받아서 동일한 위치에 틀린 글자가 몇 개나 있는지를 계산하는 프로그램을 작성해보자. 이것을 해밍 거리(Hamming distance)라고 한다.
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1, s2;
int count = 0;
cout << "DNA1: ";
cin >> s1;
cout << "DNA2: ";
cin >> s2;
if (s1.length() != s2.length())
cout << "오류 : 길이가 다름" << endl;
else {
for (int i = 0; i < s1.length(); i++) {
if (s1[i] != s2[i]) {
count += 1;
}
}
cout << "해밍 거리는 " << count << endl;
}
return 0;
}
7. 행맨 게임
#include <iostream>
#include <string>
using namespace std;
int main() {
char ch;
string solution;
string list[] = {
"the",
"c++",
"programming",
"language",
};
srand((unsigned)time(NULL));
int n = rand() % 4;
solution = list[n];
string guess(solution.length(), '_'); // solution.length() 개수만큼의 '_' 문자로 초기화된 문자열을 생성
while (true) {
cout << guess << endl;
cout << "글자를 입력하시오: ";
cin >> ch;
for (int i = 0; i < solution.length(); i++) {
if (ch == solution[i]) {
guess[i] = ch;
}
}
if (solution == guess) {
cout << solution << endl;
cout << "성공하였습니다.!";
break;
}
}
return 0;
}
'Develop > C, C++' 카테고리의 다른 글
[C/C++] 상속(Inheritance) (0) | 2023.12.19 |
---|---|
[C/C++] 동적할당(Dynamic Memory Allocation) (0) | 2023.12.18 |
[C, C++] 포인터(Pointer) (0) | 2023.12.18 |
[C/C++] 함수(Function) (0) | 2023.12.18 |
[C/C++] 함수와 포인터를 사용해서 문자열을 뒤집어보자! (0) | 2023.10.12 |