안녕하세여!
이번 정리는 문자열을 배열에 저장하거나 포인터 문자열 출력을 해보도록 하겠습니다.
문자열 | 문자열의 초기화 문자열 입력하여 저장 |
문자열의 확장기능 | 문자열 길이 문자열 비교 문자열 복사 문자열 붙여넣기 문자열로 변환 문자열 검색 문자열 자르기 문자열 정수, 실수로 변환 |
회문, N-gram | 간단한 회문과 N-gram 설명 |
구조체 | 구조체 정의 구조체 별칭 구조체 포인터 간단한 구조체 예제 |
문자열의 사용
C언어에서 문자열을 사용하기 위해서는 아래와 같이 기본적인 형태로 문자열을 선언하거나 초기화가 가능합니다.
#include <stdio.h>
int main()
{
char* ptr1 = "string";
char* ptr2[] = { "string" };
char arr1[] = "string";
char arr2[10] = "string";
char arr3[10];
printf("%d\n", sizeof(ptr1)/sizeof(ptr1[0]));
printf("%d\n", sizeof(ptr2) / sizeof(ptr2[0]));
printf("%d\n", sizeof(arr1)/sizeof(arr1[0]));
printf("%d\n", sizeof(arr2) / sizeof(arr2[0]));
printf("%d\n", sizeof(arr3) / sizeof(arr3[0]));
}
문자열을 저장한 각 배열의 요소 개수를 비교하면 다음과 같이 확인할 수 있으며 포인터 형태로 선언된 문자열 리터럴은 문자열 리터럴이 있는 메모리 주소를 가리키는 것이기에 요소의 개수는 포인터의 크기로 나오게 됩니다.
문자는 1바이트 크기의 char에 저장할 수 있지만 문자열은 크기가 1바이트가 넘기에 위의 코드처럼 변수에 직접 저장하지 않고 포인터를 이용하여 저장하거나 크기를 지정하여 저장할 수 있습니다.
문자열의 마지막에는 항상 null(\0) 문자가 들어가 요소의 개수는 문자열 개수보다 하나 더 많이 필요하며 널문자는 문자열의 끝을 나타내기도 합니다.
메모리 주소를 다루는 포인터 연산에서 문자열의 앞과 뒤는 낮은 주소가 뒤가 되고 높은 주소가 앞쪽이 되는 것을 유의해야 합니다. 뒤쪽으로 가는 것을 역방향, 앞쪽으로 가는 것을 순방향이라 합니다.
크기를 지정하여 선언할 수 있기에 배열 형태로 인덱스로 접근하여 문자를 가져올 수 있습니다.
#include <stdio.h>
int main()
{
char* ptr1 = "string";
char arr1[] = "string";
char arr2[10] = "string";
char arr3[10];
arr3[0] = 's';
printf("%c\n", ptr1[0]);
printf("%c\n", arr1[1]);
printf("%c\n", arr2[2]);
printf("%c\n", arr3[3]);
//arr3의 3번 인덱스는 초기화되어 있지 않아 출력하면 쓰레기 값이 출력됩니다.
}
서식 지정자 %c를 통해 문자를 가져올 수 있으며 배열 형태로 선언하면 선언 즉시 문자열을 초기화하여야 배열에 값이 저장되며 선언 후 할당하기 위해서는 인덱스에 하나씩 접근하여 할당할 수 있습니다.
문자열을 포인터 형태로 선언하면 인덱스를 지정하여 문자를 변경하지 못하는데 문자열 리터럴이 있는 메모리 주소는 읽기 주소이기 때문입니다.
문자열 입력 값을 저장하기 위해서는 아래의 형식을 맞추어 입력하면 됩니다.
scanf("%s", 배열);
- int scanf(char const *const _Format, ...);
배열을 선언하고 입력받는 함수에 %s 서식 지정자를 통해 배열에 입력받은 문자열을 저장시킬 수 있습니다.
%s 서식 지정자를 이용할 때 공백을 이용한다면 배열에는 공백 직전까지 저장되는데 %[^\n]s 서식 지정자를 통해 공백을 포함할 수 있습니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char* ptr1 = "string";
char arr1[] = "string";
char arr2[10] = "string";
char arr3[10];
scanf("%s", arr3);
getchar();
scanf("%[^\n]s", arr1);
printf("%c\n", ptr1[0]);
printf("%c\n", arr1[1]);
printf("%c\n", arr2[2]);
printf("%c\n", arr3[3]);
}
입력값을 문자열 포인터에 저장하려면 문자열이 들어갈 공간을 확보해야 하는데 malloc함수를 이용하여 동적 할당을 사용하면 메모리 공간을 확보할 수 있습니다. 메모리 공간을 전부 사용 후 free함수로 메모리를 해제해야 합니다.
문자열을 여러 개 입력받을 수 있는데 아래와 같은 형식으로 선언하며 문자열 포인터는 메모리를 할당받은 포인터만 가능합니다. 문자열 리터럴이 할당된 포인터는 읽기 전용이기에 그렇습니다.
scanf("%s %s ...", 배열, 배열, ...);
scanf("%s %s ...", 문자열 포인터, 문자열 포인터, ...);
문자열의 부가적인 기능
문자열을 이용하여 다양한 형태로 표현할 수 있는데 첫 번째로 문자열의 길이 구하기입니다.
strlen(문자열 포인터);
strlen(문자 배열);
- size_t strlen(const *_Str);
함수의 이름은 string length이며 string.h헤더 파일에 선언되어 있습니다. 문자열의 길이만 구하기에 널문자(\0)는 길이에 추가되지 않습니다.
두 번째로 문자열의 비교하는 데 사용되는 함수입니다.
strcmp(문자열, 문자열);
- int strcmp(const *_Str1, char const *_Str2);
string compare에서 따온 함수 이름이며 string.h헤더 파일에 선언되어 있습니다. ASCII코드 기준으로 문자열이 같으면 0을 출력하되, 왼쪽 문자열(Str1)이 크면 양수인 1을 반환하고 오른쪽 문자열(Str2)이 크면 음수인 -1을 반환하게 됩니다.
여기서 비교 문자열 포인터에 널문자(\0)가 들어있으면 오류가 발생하기에 주의해야 합니다. 또한, strcmp함수의 반환 값은 C표준 라이브러리에 따라 다릅니다.
문자열 복사 부분입니다. 문자열 복사는 string copy에서 함수 이름을 따왔으며 string.h헤더 파일에 선언되어 있습니다.
strcpy(대상 문자열, 원본 문자열);
- char *strcpy(char *_Dest, char const *_Source);
원본 문자열은 복사할 문자열이고 대상 문자열은 복사된 결과가 저장될 문자열입니다.
문자열을 붙이는 방법은 다음과 같습니다.
strcat(최종 문자열, 붙인 문자열);
- char *strcat(char *_Destination, char const *_Source);
최종 문자열 포인터를 반환하며 string concatenate에서 함수 이름을 가져왔습니다. 이 함수를 사용하기 위해 string.h헤더 파일에 선언되어 있어 이 헤더 파일을 선언해야 합니다.
서식을 지정하여 문자열을 만들 수 있는데 printf함수를 활용한 함수입니다.
sprintf(배열, 서식, 값);
sprintf(배열, 서식, 값, 값, ...);
- int sprintf(char *const _Buffer, char const *const _Format, ...);
stdio.h헤더 파일에 선언되어 있으며 서식 부분에 서식 지정자를 넣어 값을 가져오는 형태로 만들 수 있습니다. 빈 문자열 변수를 Buffer라고 하며 서식 부분에 서식 지정자를 이용하면 지정된 서식에 맞춰 다양한 서식의 값을 문자열로 만들 수 있습니다.
포인터 방식도 서식 지정자를 이용한 문자열을 만들 수 있는데 위의 형태에서 배열을 문자열 포인터로 변경하기만 하면 됩니다. 배열과 방법이 같지만 포인터는 동적으로 할당하여 문자열을 저장하여야 합니다.
문자열에서 검색 기능을 제공하는 함수를 이용하여 문자열들을 찾을 수 있는데 대표적으로 아래 3가지가 있어 순서대로 표현하겠습니다. 우선 처음으로 특정 문자부터 검색하는 string character의 뜻을 가진 함수를 사용하면 됩니다.
strchr(대상 문자열, 검색할 문자);
- char *strchr(char *const _String, int const _Ch);
strrchr(대상 문자열, 검색할 문자);
- char *strrchr(char *const _String, int const _Ch);
strstr(대상 문자열, 검색할 문자열);
- char *strstr(char *const _String, int const _SubString);
문자열에서 검색할 문자를 찾으면 문자로 시작하는 문자열의 포인터를 반환하는 형식으로 포인터를 이용하는 함수입니다. 또한, 해당 문자를 검색하고 검색할 문자가 없으면 널문자(\0)를 반환합니다.
string.h헤더 파일에 선언되어 있으며 예제는 다음과 같습니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char* str = "Programming Language";
char* ptr = strchr(str, 'g');
while (ptr != NULL)
{
printf("%s\n", ptr);
ptr = strchr(ptr + 1, 'g');
}
}
이러한 방법으로 해당 문자부터 출력하도록 사용되며 반복문에서 ptr을 1 증가시키므로 포인터의 위치를 변경하도록 할 수 있습니다.
문자열의 오른쪽 끝부터 검색하는 방법은 strrchr함수를 사용하면 됩니다. string (find from the right) character에서 따왔으며 string.h헤더 파일에 선언되어 있습니다.
문자열의 끝에서부터 역순으로 검색하여 위의 특정 문자부터 검색하는 것처럼 출력합니다.
문자열 안에서 문자열로 특정하여 검색할 수도 있는데 strstr함수를 사용하면 됩니다. 검색할 문자열을 큰 따옴표로 묶어 검색하는 것이고 string (find) string에서 따왔습니다. 이 함수도 string.h헤더 파일에 선언되어 있으며 함수를 사용하면 검색할 문자열로 시작하는 문자열의 포인터를 반환합니다.
여기서 해당 문자열만 출력되는 것이 아니고 뒤에 올 문자열도 모두 출력되는 것을 유의해야 합니다.
문자열을 잘라 원하는 내용을 반환받을 수 있는데 string tokenize의 뜻을 가진 포인터를 이용하는 함수를 사용하면 됩니다. string.h헤더 파일에 선언되어 있으며 특정 문자를 기준으로 문자열을 분리하게 됩니다.
strtok(대상 문자열, 기준 문자);
- char *strtok(char *_String, char const *_Delimiter);
대상 문자열에서 기준 문자로 자른 문자열을 반환하고 자를 문자열이 없으면 NULL을 반환합니다. 자른 문자 위치에는 널문자(\0)를 넣는 방식을 사용합니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char str[30] = "Programming Language";
char* ptr = strtok(str, " ");
while (ptr != NULL)
{
printf("%s\n", ptr);
ptr = strtok(NULL, " ");
}
for (int i = 0; i < 30; i++) {
printf("%c %d\n", str[i], i);
}
}
크기를 지정하지 않고 동적 할당으로도 가능하며 기준 문자를 여러 개 지정하여 문자열을 구분할 수 있습니다.
기준 문자 부분에 다양한 특수문자와 알파벳을 한 번에 지정해주면 됩니다. 예로 날짜와 시간 값 자르기가 되며 strtok(대상 문자열, ":-");와 같이 ':', '-' 기준으로 자르도록 만들 수 있습니다.
자른 문자열을 보관하여 다시 사용하는 경우도 있기에 저장할 다른 포인터 배열이나 배열을 NULL값을 가지도록 초기화한 후 strtok함수를 실행한 값을 배열 인덱스마다 저장시켜주면 됩니다.
sprintf함수를 통해 여러 서식을 문자열로 만들었는데 반대로 문자열을 정수와 실수로 변환할 수 있습니다. 아래의 유형들이 있으며 순서대로 정리해보겠습니다.
atoi(문자열);
- int atoi(char const *_String);
strtol(문자열, 끝 포인터, 진법);
- long strtol(char const *_String, char **_EndPtr, int _Radix);
atof(문자열);
- double atof(char const *_String);
strtof(문자열, 끝 포인터);
- float strtof(char const *_String, char **_EndPtr);
atoi함수는 ASCII string to integer에서 따왔으며 10진수로 표현된 문자열을 정수로 변환할 수 있습니다. stdlib.h헤더 파일에 선언되어 있으며 알파벳과 특수문자가 들어있으면 해당 문자부터는 변환하지 않아 처음부터 숫자형 문자보다 앞에 있는 문자열은 0을 반환합니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[30] = "123";
int num;
num = atoi(str);
printf("%d\n", num);
}
특정 진법으로 표기된 문자열을 정수로 변환하기 위해서는 strtol함수를 사용하면 됩니다. string to long에서 따왔으며 stdlib.h헤더 파일에 선언되어 있습니다.
바꿔야 하는 진법이 다양하다면 두 번째 인자인 EndPtr을 사용하면 되는데 문자형 포인터 변수를 선언하고 두 번째 인자에 주소 연산자(&) 포인터 변수를 지정하여 끝 포인터에 해당 포인터를 넣어주면 됩니다.
이렇게 되면 앞에 숫자를 제외한 숫자가 남게 되는데 문자열을 포인터 변수를 지정하고 마지막 숫자까지 반복하다 변환할 문자가 없으면 끝 포인터 부분에 NULL을 넣어주면 됩니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[30] = "123 0x12";
int num1, num2;
char* end;
num1 = strtol(str, &end, 10);
num2 = strtol(end, NULL, 16);
printf("%d\n0x%x", num1, num2);
}
문자열을 실수로 변환하기 위해 필요한 함수는 atof함수입니다. ASCII string to float에서 따왔으며 stdlib.h헤더 파일에 선언되어 있습니다.
실수와 관련된 소수점(.)이나 지수(e)를 제외한 문자는 인식하지 않고 0을 반환합니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[30] = "123.12";
float num1;
num1 = atof(str);
printf("%f\n", num1);
}
여러 개의 실수로 입력되어 있는 문자열은 실수로 변환하기 위해 strtof함수를 사용합니다. string to float에서 따왔으며 stdlib.h헤더 파일에 선언되어 있습니다.
여러 개의 문자열 변환은 정수 변환에서 진법을 제외하고 같은 방법으로 동작되기에 따로 예시 코드는 작성하지 않겠습니다.
회문과 N-gram
우선, 회문(palindrome)은 순서를 거꾸로 하여도 같은 단어 문장이 되는 것을 뜻합니다. 즉, 문자열의 길이를 반으로 나누어 반복하여 처음과 끝에서 중간으로 가까워지며 비교했을 때 같은 문자열인 것을 말합니다.
문자열 길이는 strlen함수를 이용하면 되고, 문자열 길이를 2로 나눈만큼 반복하며 처음 인덱스와 문자열을 표시하는 끝 인덱스를 비교하면 회문을 판별할 수 있을 것입니다.
N-gram은 문자열에서 N개의 연속된 요소를 추출하는 방법으로 빅데이터 분석, 검색 엔진에 많이 사용됩니다.
문자 단위의 N-gram을 만들기 위해 문자들이 겹치도록 하면되는데 문자열의 길이를 이용하여 구할 수 있을 것입니다. 단어단위의 N-gram도 있는데 이대는 strtok함수를 사용하여 공백을 구분으로 다른 배열에 저장하여 다시 출력하는 방법을 사용하면 됩니다.
구조체
여러 정보를 저장하기 위해 변수를 계속 추가하는데 한계가 있어 구조체를 사용하는데 구조체는 struct로 정의하며 data structure(자료 구조)의 약어입니다.
struct 구조체 이름
{
자료형 멤버이름;
};
이렇듯 구조체는 관련정보를 하나로 묶을 때 사용하며 기본 자료형(char, int, float)등을 조합하여 만든 자료형을 파생형(derived type)이라 합니다.
구조체도 변수로 선언하여 사용하며 구조체 정의 시 닫는 중괄호(})에 세미콜론(;)을 붙여야 합니다. 변수를 선언할 때는 아래와 같이 선언합니다.
struct 구조체이름 변수이름;
변수를 사용할 때 구조체 멤버에 접근하기 위해 변수이름.멤버이름으로 사용하며 main함수 바깥에서 전역변수로 많이 사용됩니다. 함수 안에 정의하여 해당 함수에서만 구조체를 사용할 수 있도록 지역변수로 만들기도 합니다.
또한 구조체를 정의할 때 아래와 같이 끝부분에 변수를 지정하여 정의하면 변수 이름을 함수 안에서 선언할 필요가 없어집니다.
struct 구조체 이름
{
자료형 멤버이름;
}변수;
typedef로 struct키워드를 생략할 수 있는데 별칭(alias)개념으로 정의되는 것입니다.
typedef struct _구조체 이름
{
자료형 멤버이름;
}구조체 별칭;
구조체 이름과 구조체 별칭을 중복하여도 괜찮고 관례상 구조체 이름 앞에 밑줄(_)을 붙입니다. 함수 안에서 사용하기 위해서는 [구조체 별칭 변수이름;]과 같은 형식으로 선언하면 됩니다.
또한 구조체 이름을 생략할 수 있는데 이때는 구조체 별칭을 설정해야합니다.
구조체를 동적메모리 할당방식으로 선언할 수 있는데 [struct 구조체 이름 *포인터 이름 = malloc(sizeof(struct 구조체 이름));]형식으로 사용하면 됩니다.
구조체 포인터의 멤버에 접근하는 방법이 조금 다른데 점(.) 대신 화살표(->)모양을 이용하여 사용합니다. 구조체 별칭으로 포인터를 선언하고 메모리를 할당합니다. [구조체 별칭 *포인터 이름 = malloc(sizeof(구조체 별칭));]이며 익명 구조체는 구조체 이름만 지워주면 됩니다.
구조체 포인터를 사용하는 다른 방법은 구조체 변수에 주소 연산자(&)를 사용하면 됩니다. [구조체 포인터 = &구조체 변수;]방식이며 [struct 구조체 이름 *구조체 변수;]로 구조체 변수 선언 후 구조체 변수의 주소값을 포인터 변수에 넣어 활용해 값을 바꿀 수 있습니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct Person {
char name[10];
int age;
};
int main()
{
struct Person p1;
struct Person* ptr;
ptr = &p1;
ptr->age = 30;
printf("나이 %d\n", ptr->age);
return 0;
}
구조체를 활용하여 2차원 평면의 거리를 구하는 문제를 풀어볼 수 있습니다. 두 점을 x, y를 구조체로 표현하고 두 구조체 변수를 만들어 각 각의 x, y 값을 구한 후 피타고라스 정리를 이용하여 두 점 사이의 거리를 구하면 됩니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
struct Point {
int x;
int y;
};
int main()
{
int a, b;
double len;
struct Point p1;
struct Point p2;
p1.x = 30;
p1.y = 20;
p2.x = 40;
p2.y = 30;
a = p2.x - p1.x;
b = p2.y - p1.y;
len = sqrt((a * a) + (b * b));
printf("두 점 사이의 거리 %lf\n", len);
}
루트를 구하기 위해 sqrt함수를 사용하면 됩니다. 제곱근을 반환하며 square root에서 따왔습니다. sqrt함수는 math.h헤더 파일에 선언되어 있습니다.
sqrt(값)
- double sqrt(double _X);
pow함수를 사용하면 어떤 수의 거듭제곱을 구할 수 있습니다. [pow(어떤수, 거듭제곱);]
이상으로 코딩도장에서 unit50까지 풀어보았습니다. 다음은 해커스쿨에서 FTZ를 풀어보도록 하겠습니다.
'TeamH4C_ProjectH4C > 프로그래밍 언어' 카테고리의 다른 글
[TeamH4C_C언어] 구조체 추가 정리(비트 필드) (0) | 2021.04.11 |
---|---|
[TeamH4C_C언어] 구조체와 공용체 사용 (0) | 2021.03.28 |
[TeamH4C_C언어] 포인터를 활용한 문제 (0) | 2021.03.06 |
[TeamH4C_C언어] 포인터의 활용 (0) | 2021.03.06 |
[TeamH4C_C언어] 다시 정리하는 CodeUp (0) | 2021.02.26 |