이 페이지 요약 

1. 공용체 

2. 리틀 엔디언

3. 구조체, 공용체 함께 사용

 

Unit 54. 공용체 사용하기

공용체는 다음 그림과 같이 모든 멤버가 공간을 공유한다.

공용체는 멤버중에 가장 큰 자료형의 공간을 공유한다.

54.1 공용체를 만들고 사용하기

공용체는 union 키워드를 사용하여 정의한다. 공용체는 다음과 같이 정의하고, 변수로 선언해서 사용한다.

union 공용체이름 {
    자료형 멤버이름;
};

union 공용체이름 변수이름;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
 
union Box{
    short candy;
    float snack;
    char doll[8];
};
 
int main()
{
    union Box b1;
    printf("%d\n"sizeof(b1));
 
    strcpy(b1.doll, "bear");
    
    printf("%d\n", b1.candy);
    printf("%f\n", b1.snack);
    printf("%s\n", b1.doll);
    return 0;
}
cs

union 키워드 뒤에 공용체 이름을 지정하고, 중괄호 안에 변수를 선언한다. 닫는 중괄호 뒤에는 반드시 세미클론을 붙여줘야 한다. 공용체는 보통 main함수 밖에 정의한다.

정의한 공용체를 사용하려면 공용체 변수를 선언해야 한다. 공용체 이름 앞에는 union 키워드를 붙이고, 공용체 이름과 공용체 변수 이름을 지정하여 선언한다. sizeof 연산자로 공용체 크기를 구하면 가장 큰 자료형의 크기가 공용체의 전체 크기로 나온다. 위 코드의 경우 char 8개 크기인 배열로 8바이트 이다.

공용체의 멤버에 접근할 때는 점을 사용한다. 공용체는 멤버중 가장 큰 자료형의 공간을 공유하기 때문에 어느 한 멤버에 값을 저장하면 나머지 멤버의 값은 사용할 수 없는 상태가 된다. 공용체의 멤버는 한 번에 하나씩 값을 쓰면 정상적으로 사용할 수 있다.

실무에서는 공용체에 값을 저장할 때 어떤 멤버를 사용할 지 미리 정해두고 꺼낼때도 정해둔 멤버에서 값을 꺼내는 식으로 사용한다. 여러 멤버에 동시에 접근하지 않는 경우 메모리 레이아웃에 멤버를 모아둘 때 사용한다. 임베디드 시스템이나 커널 모드 디바이스 드라이버 등에서 주로 사용하며 보통은 거의 쓰지 않는다.

54.2 공용체와 엔디언

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
union Data {
    char c1;
    short num1;
    int num2;
};
 
int main()
{
    union Data d1;
 
    d1.num2 = 0x12345678;
 
    printf("0x%x\n", d1.num2);
    printf("0x%x\n", d1.num1);
    printf("0x%x\n", d1.c1);
    printf("%d\n"sizeof(d1));
    return 0;
}
cs

d1의 멤버중 가장 큰 자료형인 num2에 0x12345678을 할당하고 다른 멤버들을 출력해보면 d1.num2는 저장한 숫자가 그대로 나오지만, 다른 멤버는 숫자중 일부만 나온다. 공용체는 값을 저장하는 공간은 공유하지만 가져올 때는 해당 자료형의 크기만큼만 가져오기 때문이다. 우리가 사용하는 CPU는 리틀 엔디언 방식으로 값을 메모리에 저장한다. 리틀 엔디언은 숫자를 1바이트씩 쪼개서 낮은 자릿수가 앞에 오도록 한다. 따라서 0x12345678을 리틀 엔디언 방식으로 메모리에 저장하면 78 56 34 12가 된다. 공용체는 앞에서부터 자료형의 크기만큼 값을 가져오므로 d1.num1은 앞의 2바이트 56 78을 가져오고, d1.c1은 앞의 1바이트인 78만 가져온다.

공용체도 구조체처럼 typedef로 별칭을 지정하고 별칭으로 선언하거나, 익명 공용체를 정의할 수 있다. 또한 다음과 같이 정의하는 동시에 변수를 선언할 수 있다.

union 공용체이름 {
    자료형 멤버이름;
} 변수;

54.3 공용체 포인터를 선언하고 메모리 할당하기

공용체도 포인터를 선언하고, malloc함수를 사용하여 동적 메모리를 할당할 수 있다.

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
union Box{
    short candy;
    float snack;
    char doll[8];
};
 
int main()
{
    union Box *b1 = malloc(sizeof(union Box));
 
    printf("%d\n"sizeof(union Box));
 
    strcpy(b1->doll, "bear");
 
    printf("%d\n", b1->candy);
    printf("%f\n", b1->snack);
    printf("%s\n", b1->doll);
 
    free(b1);
    return 0;
}
cs

malloc함수로 메모리를 할당할 때 크기를 알아야 하므로 sizeof(union Box)처럼 공용체의 크기를 구할 수 있다. 공용체 포인터도 멤버에 접근할 때는 화살표 연산자를 사용한다. strcpy함수로 공용체 포인터의 멤버에 접근한 뒤 문자열을 복사하고, 공용체 포인터도 멤버중 가장 큰 자료형의 공간을 공유함을 출력값을 통해 확인할 수 있다. 할당한 메모리는 사용이 끝났으면 꼭 free로 해제해줘야 한다.

54.4 퀴즈

정답은 long long int 자료형의 크기인 8 이다.

정답은 d이다.

정답은 0x1111 이다.

54.5 연습문제 : 정수 데이터 공용체 정의하기

정답은 다음 코드와 같다.

// 1.
union Data{
    char c1;
    short num1;
};

// 2. 
union Data

// 3.
d1.num1 = 0x5678;

0x5678 은 2바이트 이므로 short, 0x78은 1바이트이므로 char로 선언하고, 리틀 엔디언으로 값이 저장되기 때문에 0x5678을 num1멤버에 넣으면 위와 같은 결과가 나온다.

54.6 연습문제 : 공용체 포인터 사용하기

화살표 연산자를 사용했으므로 포인터를 선언하여 메모리를 할당해야 하기 때문에 정답은 union Data *d1 = malloc(sizeof(union Data)); 이다.

54.7 심사문제 : 정수 데이터 공용체 정의하기

정답은 다음 코드와 같다.

union Data{
    char c1;
    int num1;
};

공용체의 크기가 4가 나오므로 4바이트 크기를 갖는 자료형이 사용되야 한다.

54.8 심사문제 : 공용체 포인터 사용하기

정답은 다음 코드와 같다.

union Data *d1 = malloc(sizeof(union Data));
d1->num2 = 0x11111111;

화살표 연산자가 사용됬기 때문에 포인터를 선언한 후 메모리를 할당해야 한다. 공용체는 가장 큰 자료형의 공간을 공유하므로 가장 큰 멤버에만 0x111111111을 넣으면 된다.

Unit 55. 구조체와 공용체 활용하기

구조체 안에 구조체와 공용체가 들어갈 수 있고, 반대로 공용체 안에 구조체와 공용체가 들어갈 수도 있다.

55.1 구조체 안에서 구조체 멤버 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
struct Phone {
    int areacode;
    unsigned long long num;
};
 
struct Person {
    char name[20];
    int age;
    struct Phone phone;
};
 
int main()
{
    struct Person p1;
 
    p1.phone.areacode = 82;
    p1.phone.num = 12345678;
 
    printf("%d %llu\n", p1.phone.areacode, p1.phone.num);
    return 0;
}
cs

구조체를 멤버로 가지려면 구조체 안에서 구조체 변수를 선언하면 된다. 위 코드에서는 Person 구조체가 Phone 구조체를 멤버로 가지고 있다. 멤버에 접근할 때는 점을 사용하여 가지고 있는 구조체에 계층적으로 접근하면 된다. 위 코드와 같이 점을 두 번 사용하면 구조체의 areacode와 num 멤버에 접근할 수 있다.

구조체 안에 구조체를 정의할 때는 무조건 안에 들어가는 구조체를 먼저 선언해줘야 한다.

만약 Phone구조체를 다른 곳에서는 쓰지 않고 특정 구조체 안에서만 쓴다면 다음과 같이 구조체 안에 구조체를 정의하는게 더 편리하다. 이때 안에 정의하는 구조체는 정의하고 반드시 변수를 선언해 줘야 한다.

struct Person {   
    char name[20];    
    int age;          
    struct Phone {   
        int areacode;                 
        unsigned long long num;    
    } phone;                       
};

구조체 변수를 선언하며 안에 있는 구조체까지 초기화 하려면 중괄호 안에 중괄호를 사용하여 다음과 같이 초기화 하면 된다.

struct 구조체이름 변수이름 = { 값1, 값2, { 값3, 값4 } };

55.2 구조체 안의 구조체 멤버에 메모리 할당하기

다음은 구조체 안에 구조체 멤버가 변수로 있는 상태에서 메모리를 할당하여 사용하는 방법이다.

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
#include <stdio.h>
#include <stdlib.h>
 
struct Phone {
    int areacode;
    unsigned long long num;
};
 
struct Person {
    char name[20];
    int age;
    struct Phone phone;
};
 
int main()
{
    struct Person *p1 = malloc(sizeof(struct Person));
 
    p1->phone.areacode = 82;
    p1->phone.num = 12345678;
 
    printf("%d %llu\n", p1->phone.areacode, p1->phone.num);
 
    free(p1);
    return 0;
}
cs

Person 구조체에 메모리를 할당하면 각 구조체의 멤버에 접근하려면 p1은 포인터이기 때문에 화살표 연산자를 사용하여 멤버에 접근하고, phone은 포인터가 아닌 일반 변수이므로 점을 사용하여 멤버에 접근한다. 구조체 포인터 사용이 끝나면 free함수로 할당된 메모리를 해제해야 한다.

다음은 구조체의 포인터를 멤버로 갖는 상황이다.

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
#include <stdio.h>
#include <stdlib.h>
 
struct Phone {
    int areacode;
    unsigned long long num;
};
 
struct Person {
    char name[20];
    int age;
    struct Phone *phone;
};
 
int main()
{
    struct Person *p1 = malloc(sizeof(struct Person));
    p1->phone = malloc(sizeof(struct Phone));
 
    p1->phone->areacode = 82;
    p1->phone->num = 12345678;
 
    printf("%d %llu\n", p1->phone->areacode, p1->phone->num);
 
    free(p1->phone);
    free(p1);
    return 0;
}
cs

Person 구조체 안에는 구조체 포인터를 멤버로 갖고 있다. 먼저 바깥 구조체의 포인터에 메모리를 할당하고 멤버로 있는 구조체 포인터에 메모리를 할당해야 한다. 각 멤버에 접근하려면 p1은 포인터이고, 구조체 멤버 phone도 포인터이므로 화살표 연산자로 접근하면 된다. 구조체 포인터 사용이 끝나면 메모리를 해제해야 하는데, 안쪽에 있는 멤버부터 메모리를 해제해야 한다. 만약 바깥에 있는 구조체를 먼제 해제하게 되면 데이터가 사라져서 멤버에 더이상 접근할 수 없다. 멤버 포인터에 저장된 주소도 알 수 없기 때문에 해제도 할 수 없다. 그래서 ㅇㄴ쪽의 구조체 먼제 해제해야 한다.

55.3 익명 구조체와 익명 공용체 활용하기

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
#include <stdio.h>
 
struct Vector3 {
    union {
        struct {
            float x;
            float y;
            float z;
        };
        float v[3];
    };
};
 
int main()
{
    struct Vector3 pos;
 
    for(int i = 0; i < 3; i++)
    {
        pos.v[i] = 1.0f;
    }
 
    printf("%f %f %f\n", pos.x, pos.y, pos.z);
    return 0;
}
cs

제일 안에 있는 x, y, z는 float로 선언되어 있는 익명 구조체 이다. 익명 공용체가 x, y, z익명 구조체오 배열 v를 감싸고 있다. float x, y, z는 변수 3개이고, float v[3]도 배열의 요소가 3개이므로 자료형도 같고, 개수도 같으므로 같은 공간을 차지한다. 공용체로 묶어주면 x, y, z와 v[3]은 같은 공간을 공유하게 된다.

v는 배열이기 때문에 인덱스로 접근하여 값을 할당할 수 있다. 공용체에 넣었으므로 값은 같은 공간에 있기 때문에 x,y,z 멤버로도 접근할 수 있다.

 

55.4 퀴즈

정답은 d 이다.

정답은 c 이다.

정답은 b이다.

정답은 d 이다.

 

55.5 연습문제 : 게임 캐릭터 구조체 만들기

정답은 struct Stats stats 이다.

 

55.6 연습문제 : 게임 캐릭터 구조체 사용하기

화살표 연산자로 접근하고 있기 때문에 포인터로 만들어서 메모리를 할당시켜줘야 하므로 정답은 struct Champion *lux = malloc(sizeof(struct Champion)); 이다.

 

55.7 연습문제 : 장치 옵션 구조체 만들기

정답은 다음 코드와 같다.

union {
    unsigned long long option;
    struct {
        unsigned char boot[4];
        unsinged char interrupt[2];
        unsigned char bus[2];
    };
};

사용하는 공간이 총 8 바이트이기 때문에 공간을 공유하는 공용체를 8바이트로 만들면 코드에서 저장한 값들을 다 저장할 수 있다.

 

55.8 심사문제 : 게임 캐릭터 구조체 만들기

 

정답은 다음 코드와 같다.

struct Champion swain;

strcpy(swain.name, "Swain");
swain.stats.health = 463;

 

55.9 심사문제 : 게임 캐릭터 구조체 포인터 사용하기

정답은 다음 코드와 같다.

struct Champion *swain = malloc(sizeof(struct Champion));
swain->stats = malloc(sizeof(struct Stats));

strcpy(swain->name, "Swain");
swain->stats->health = 463;

 

55.10 심사문제 : 장치 옵션 구조체 만들기

정답은 다음 코드와 같다.

struct DeviceOption {
    union{
        unsigned short option;
        struct{
            unsigned char boot;
            unsigned char interrupt;
        };
    };
};

공용체는 모든 멤버의 공간을 공유하기 때문에 unsigned char이 두개 쓰였으므로 이 두개 모두 들어갈 수 있는 unsigned short 자료형을 사용하였다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] FTZ (8 ~11)  (0) 2021.03.27
[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24
[Project H4C] C언어 코딩도장(13)  (0) 2021.03.23
[Project H4C] PLT, GOT  (0) 2021.03.21
[Project H4C] SFP, RET  (0) 2021.03.21

이 페이지 요약

1. 구조체 멤버 정렬

2. 구조체 배열

3. 구조체 포인터 배열

 

Unit 51. 구조체 멤버 정렬 사용하기

구조체가 메모리에 올라갔을 때 멤버를 정렬하는 기능이다.

컴퓨터는 CPU가 메모리에 접근할 때 32비트는 4바이트 단위, 64비트는 8바이트 단위로 접근한다. 만약 32비트 CPU에서 4바이트보다 작은 데이터에 접근하게 되면 내부적으로 시프트 연산이 발생하여 효율이 떨어지기 때문에 C컴파일러에서 CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬한다. 구조체 크기가 15나 17바이트가 되면 접근 효율이 떨어지기 떼문에 2, 4, 8, 16바이트 단위로 정렬한다.

구조체의 멤버를 정렬하면 안되는 경우도 있다. 사진파일의 경우 정렬하게 되면 사진을 저장할 때 마다 조금씩 깨질 수 있다. 또한 네트워크로 데이터를 전송할 경우 몇 바이트씩 어떤 순서로 보낼지 규약을 정해놓았는데, 이때 정렬이 발생하면 정해놓은 규약에서 벗어나 받는 쪽에서 데이터를 알아볼 수 없게 된다.

51.1 구조체 크기 알아보기

구조체 전체 크기는 sizeof 연산자를 사용하여 알 수 있다. sizeof 연산자는 다음과 같이 사용할 수 있다.

  • sizeof(struct 구조체)
  • sizeof(구조체별칭)
  • sizeof(구조체변수)
  • sizeof 구조체변수

다음은 가상의 네트워크 구조체 PacketHeader를 정의하여 멤버의 크기와 구조체의 크기를 구한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
struct PacketHeader {
    char flags;
    int seq;
};
 
int main()
{
    struct PacketHeader header;
 
    printf("%d\n"sizeof(header.flags));
    printf("%d\n"sizeof(header.seq));
    printf("%d\n"sizeof(header));
    printf("%d\n"sizeof(struct PacketHeader));
 
    return 0;
}
cs

구조체의 크기를 구하려면 sizeof 연산자 안에 변수나 struct 키워드와 구조체 이름을 넣어주면 된다. PacketHeader 구조체 안에는 1바이트 크기의 char 변수와 4바이트 크기의 int 변수가 들어있어 전체 크기는 5가 나와야 할 것 같지만 8이 나왔다. C에서 구조체들을 정렬할 때는 멤버중에서 가장 큰 자료형 크기의 배수로 정렬한다. 위 구조체에서는 int가 가장 큰 자료형이므로 느끼는 4바이트를 기준으로 정렬하여 구조체 전체 크기는 8바이트가 되었다. 1바이트 크기인 char flags 뒤에는 4바이트를 맞추기 위해 남는 공간에 3바이트가 더 들어가게 되는데 이렇게 구조체를 정렬할 때 남는 공간을 채우는 것을 패딩이라고 한다.

다음 코드를 이용하여 구조체를 정렬한 뒤 멤버의 위치가 위 그림처럼 되는지 확인할 수 있다. 구조체에서 멤버의 위치(offset)를 구할 때는 offsetof 매크로를 사용하여 구할 수 있다. offsetof매크로는 stddef.h 헤더파일에 정의되어 있다.

  • offsetof(struct 구조체, 멤버)
  • offsetof(구조체별칭, 멤버)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <stdio.h>
    #include <stddef.h>
     
    struct PacketHeader {
        char flags;
        int seq;
    };
     
    int main()
    {  
        printf("%d\n", offsetof(struct PacketHeader, flags));
        printf("%d\n", offsetof(struct PacketHeader, seq));
        
        return 0;
    }
    c

offsetof 매크로에 구조체와 멤버를 지정하면 구조체에 해당 멤버의 상대 위치가 반환된다. 첫 멤버의 위치는 0이고 4바이트 단위로 정렬하므로 seq의 위치는 4가 나온다.

51.2 구조체 정렬 크기 조절하기

각 컴파일러에서 제공하는 특별한 지시자를 사용하여 구조체 정렬 크기를 조절할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
#pragma pack(push, 1)   // 1바이트 크기로 정렬
struct PacketHeader {
    char flags;
    int seq;
};
#pragma pack(pop)       // 정렬 상태를 이전 상태(기본값)로 되돌림
 
int main()
{
    struct PacketHeader header;
 
    printf("%d\n"sizeof(header.flags));
    printf("%d\n"sizeof(header.seq));
    printf("%d\n"sizeof(header));
 
    return 0;
}
cs

구조체를 정의할 때 위 아래로 #pragma pack(push, 1)과 #pragma pack(pop)을 넣었다. pack을 1로 설정하면 1바이트 단위로 정렬하여 남는 공간 없이 자료형 크기 그대로 메모리에 올라간다. #pragma pack(push, 1)을 한 번 사용하면 이후 모든 구조체에 영향을 주므로 정렬 한 뒤 #pragma pack(pop)를 사용하여 설정을 이전 상태로 되돌린다. 만약gcc버전이 4.0미만이면 __attribute__((aligned(1), packed))를 사용한다.
offsetof로 멤버의 위치를 확인하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stddef.h>
 
#pragma pack(push, 1)  
struct PacketHeader {
    char flags;
    int seq;
};
#pragma pack(pop
 
int main()
{
    printf("%d\n", offsetof(struct PacketHeader, flags));
    printf("%d\n", offsetof(struct PacketHeader, seq));
 
    return 0;
}
cs

구조체를 1바이트 단위로 정렬해서 seq의 상대 위치는 1이 나온다. char flags 바로 뒤에 int seq가 오기 때문이다. 구조체의 바이트 크기는 1 이외에도 2, 4, 8, 16등으로 지정할 수 있다.

51.3 퀴즈

정답은 b,d,e 이다.

정답은 e 이다.

정답은 d이다.

정답은 e이다.

51.4 연습문제 : 압축 헤더 크기 구하기

8이 나오려면 구조체의 전체 크기를 구해야 하므로 정답은 sizeof(header)이다.

51.5 연습문제 : 패킷 크기 조절하기

6이 출력되려면 legth의 자료형 크기가 2가 되어야 하므로 정답은 short이다.

51.5 심사문제 : 암호화 헤더 크기 구하기

12가 출력되야 하므로 가장 큰 자료형의 크기는 4가 되도록 하고, 구조체 멤버를 두개 만들면 된다.

int num1;
int num2;

51.7 심사문제 : 패킷 크기 조절하기

3이 출력되야 하므로 구조체 정렬 크기를 자료형에 맞게 조절하고, 1바이트인 char 자료형, 2 바이트인 short 자료형으로 멤버를 하나씩 만들면 된다.

#pragma pack(push,1)
struct Packet{
    char c;
    short s;
};
#pragma pack(pop)

Unit 52. 구조체와 메모리 활용하기

구조체도 변수를 선언하거나 메모리를 할당하게 되면 메모리 공간을 차지하므로 메모리 관련 함수를 사용할 수 있다.

52.1 구조체와 메모리를 간단하게 0으로 설정하기

구조체의 모든 멤버를 0으로 만들기 위해 각 멤버에 접근하여 0을 저장하는 것은 매우 번거로운 작업이다. 따라서 {0, }과 같이 중괄호를 사용하여 모두 0으로 초기화 할 수 있다. 이 방법은 변수에만 사용할 수 있고, malloc 함수로 할당한 메모리에는 사용할 수 없다.

struct 구조체이름 변수이름 = { 0, };

구조체 변수나 메모리의 내용에 한꺼번에 값을 설정하려면 다음과 같이 memset함수를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p1;
    
    memset(&p1, 0sizeof(struct Point2D));
    
    printf("%d %d\n", p1.x, p1.y);
    return 0;
}
cs

memset함수로 구조체 변수의 값을 설정할 때는 &p1과 같이 주소연산자 &을 사용하야 메모리 주소를 구해서 넣어줘야 한다. 그리고 설정할 값과 크기를 넣어준다. 위 코드에서는 구조체의 내용을 0으로 설정하고 Point2D구조체 크기만큼 설정했다. 그리고 각 멤버를 출력해보면 모두 설정한 0이 나온다.
다음과 같이 malloc함수로 설정한 동적 메모리에도 값을 설정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p1 = malloc(sizeof(struct Point2D));
    
    memset(p1, 0sizeof(struct Point2D));
 
    printf("%d %d\n", p1->x, p1->y);
 
    free(p1);
    return 0;
}
cs

memset 함수로 메모리를 설정할 때 구조체가 포인터 변수이기 때문에 &을 사용하지 않고 그대로 넣어준다. 설정할 값과 크기를 넣어주고 각 멤버를 출력해보면 모두 0이 나온다.

52.2 구조체와 메모리 복사하기

내용이 같은 구조체를 만들거나 이미 생성하여 값을 저장한 구조체나 메모리를 다른 곳에 복사할 경우 memcpy 함수를 사용하여 메모리의 내용을 다른 곳에 복사할 수 있다. string.h 헤더파일에 선언되어 있다. memcpy(목적지포인터, 원본포인터, 크기); 의 형태로 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p1;
    struct Point2D p2;
 
    p1.x = 10;
    p1.y = 20;
 
    memcpy(&p2, &p1, sizeof(struct Point2D));
 
    printf("%d %d\n", p2.x, p2.y);
    return 0;
}
cs

구조체 변수 p1, p2를 선언하고 p1의 멤버에만 값을 저장한 상태에서 memcpy 함수를 사용하여 p1의 내용을 p2에 복사하였다. memcpy함수를 사용할 때는 구조체 변수에 주소 연산자를 사용하여 변수의 메모리 주소를 넣고, 크기는 구조체의 크기를 넣어준다. 목적지 포인터와 원본포인터는 앞쪽이 목적지 포인터이고, 뒷쪽이 원본 포인터이다. p2의 각 멤버를 출력해보면 p1의 멤버에 저장했던 값이 나온다.
다음은 malloc함수로 할당한 동적 메모리끼리 복사하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p1 = malloc(sizeof(struct Point2D));
    struct Point2D *p2 = malloc(sizeof(struct Point2D));
 
    p1->= 10;
    p1->= 20;
    memcpy(p2, p1, sizeof(struct Point2D));
 
    printf("%d %d\n", p2->x, p2->y);
 
    free(p2);
    free(p1);
    return 0;
}
cs

구조체 포인터 p1, p2를 선언한 뒤 메모리를 할당하여 p1의 멤버에 값을 저장하고 , p2에는 memcpy함수를 사용하여 p1의 내용을 p2에 복사했다. p1과 p2는 메모리를 담고 있는 포인터이므로 주소연산자는 사용하지 않는다.

또한 구조체 변수에서 동적메모리로, 동적메모리에서 구조체 변수로도 내용을 복사할 수 있다.

52.3 퀴즈

정답은 b이다.

정답은 a이다.

52.4 연습문제 : 2차원 좌표 초기화하기

정답은 다음과 같다.

1. #include <string.h>

2. &p, 0, sizeof(struct Point2D)

3. ptr, 0, sizeof(struct Point2D)

52.5 연습문제 : 2차원 좌표 복제하기

p1의 멤버에만 값을 저장했기 때문에 p1의 값을 p2복제해야 하는데 p2는 메모리 주소이므로 정답은 p2, &p1, sizeof(struct Point2D) 이다.

52.6 심사문제 : 인적 정보 삭제하기

출력했을 때 아무 정보도 나오지 않도록 하려면 모든 멤버를 0으로 초기화해야 하므로 정답은 &p1, 0, sizeof(struct Person)이다.

52.7 심사문제 : 인적 정보 복제하기

p2는 구조체 변수이므로 주소 연산자를 사용해서 p1의 값을 복사해와야 한다. 정답은 &p2, p1, sizeof(struct Person) 이다.

Unit 53. 구조체 배열 사용하기

구조체 변수를 여러개 만들어야 할 경우 구조체 배열을 선언하여 여러개의 구조체 변수를 한 번에 만들 수 있다.

53.1 구조체 배열 선언하기

구조체 배열은 변수 이름 뒤에 [](대괄호)를 붙인 뒤 크기를 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p[3];
 
    p[0].x = 10;
    p[0].y = 20;
    p[1].x = 30;
    p[1].y = 40;
    p[2].x = 50;
    p[2].y = 60;
 
    printf("%d %d\n", p[0].x, p[0].y);
    printf("%d %d\n", p[1].x, p[1].y);
    printf("%d %d\n", p[2].x, p[2].y);
    return 0;
}
cs

구조체 변수를 선언할 때 대괄호 안에 크기를 넣어주면 배열로 선언할 수 있다. 배열에서 각 요소에 접근하려면 배열 뒤에 대괄호를 사용하며 대괄호 안에 인덱스를 지정해주면 된다. 이 상태에서 멤버에 접근하려면 점을 사용한다. p[0].x 구조체 배열의 첫 번째 요소의 멤버 x에 접근한다는 뜻이다.
구조체 배열을 선언하는 동시에 초기화하려면 다음과 같이 중괄호 안에 중괄호를 사용하면 된다.

  • struct구조체이름 변수이름[크기] = { { .멤버이름1 = 값1, .멤버이름2 = 값2 }, { .멤버이름1 = 값3, .멤버이름2 = 값4 } };
  • struct구조체이름 변수이름[크기] = { { 값1, 값2 }, { 값3, 값4 } };

모든 요소의 멤버를 0으로 초기화 하여면 구조체 배열을 선언하며 {0, }을 할당하면 된다.

53.2 구조체 포인터 배열 선언하기

구조체 포인터 배열을 만들고 malloc함수로 각 요소에 메모리를 할당하면 요소마다 메모리를 할당할 수 있다. 구조체 포인터 배열은 포인터 변수 이름 뒤에 대괄호를 붙인 뒤 크기를 설정한다.

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
#include <stdio.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p[3];
 
    for(int i  = 0; i < sizeof(p) / sizeof(struct Point2d *); i++)
    {
        p[i] = malloc(sizeof(struct Point2D));
    }
 
    p[0]->= 10;
    p[0]->= 20;
    p[1]->= 30;
    p[1]->= 40;
    p[2]->= 50;
    p[2]->= 60;
 
    printf("%d %d\n", p[0]->x, p[0]->y);
    printf("%d %d\n", p[1]->x, p[1]->y);
    printf("%d %d\n", p[2]->x, p[2]->y);
 
    for(int i  = 0; i < sizeof(p) / sizeof(struct Point2d *); i++)
    {
        free(p[i]);
    }
    return 0;
}
cs

구조체 포인터를 선언하고 사용하려면 메모리를 할당해야 한다. 배열의 요소 개수 만큼 반복하면서 각 요소에 구조체 크기 만큼 메모리를 할당해야 한다. 구조체 포인터 배열에는 포인터가 들어있으므로 요소 개수를 구하려면 구조체 포인터 배열의 전체 크기에서 구조체 포인터의 크기로 나눠주면 된다. sizeof(struct Point2D *)는 구조체 포인터의 크기이다. 멤버에 접근할 때는 배열안에 들어있는 요소가 포인터이므로 화살표 연산자를 사용하여 멤버에 접근해야 한다. 구조체 배열의 사용이 끝나면 배열 크기만큼 반복하며 각 요소에 할당된 동적 메모리를 해제해야 한다.

53.3 퀴즈

정답은 b이다.

정답은 b이다.

정답은 d이다.

정답은 f이다.

53.4 연습문제 : 2차원 좌표 출력하기

정답은 다음 코드와 같다.

// 1
struct Point2D p[3];

// 2
for(int i = 0; i < sizeof(p) / sizeof(struct Point2D); i++)
{
	printf("%d %d\n", p[i].x, p[i].y);
}

구조체 배열의 모든 요소를 출력할 때 0번 인덱스 부터 마지막 인덱스까지 for 문을 이용하여  반복하여 출력할 수 있다.

 

53.5 연습문제 : 인적 정보를 초기화하기

정답은 다음 코드와 같다.

// 1.
struct Person *p[3000];

// 2.
for(int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
	p[i] = malloc(sizeof(struct Person));
    memset(p[i], 0, sizeof(struct Person));
}

구조체 포인터 배열이므로 첫번째 인덱스 부터 마지막 인덱스까지 메모리를 할당해주고, 메모리를 0으로 초기화 해야한다.

 

 53.6 심사문제 : 선의 길이 구하기

정답은 다음 코드와 같다.

for(int i=0; i<(sizeof(p) / sizeof(struct Point2D))-1; i++)
{
    double len_x = p[i+1].x - p[i].x;
    double len_y = p[i+1].y - p[i].y;
    length += sqrt((len_x)*(len_x) + (len_y)*(len_y));
}

두 점 사이의 거리를 구하는 공식을 이용해서 거리를 구했고, math.h 헤더의 sqrt 함수를 이용하여 제곱근을 구했다.

 

53.7 심사문제 : 나이가 가장 많은 사람 찾기

정답은 다음 코드와 같다.

int big_age = 0;

for (int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
    p[i] = malloc(sizeof(struct Person));
    scanf("%s %d", p[i]->name, &p[i]->age);
    if(p[i]->age >= p[big_age]->age)
    {
        big_age = i;
    }
}

printf("%s\n", p[big_age]->name);

가장 큰 나이의 인덱스를 저장할 변수를 만들고, 배열 길이만큼 반복하면서 메모리를 할당하고, 할당한 메모리에 값을 입력받는다. 입력받은 나이가 저장되어있던 인덱스의 나이보다 크면 현재 인덱스를 저장한다. 반복문이 끝나면 저장된 인덱스에 해당한는 이름을 출력한다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24
[Project H4C] C언어 코딩도장(14)  (0) 2021.03.23
[Project H4C] PLT, GOT  (0) 2021.03.21
[Project H4C] SFP, RET  (0) 2021.03.21
[Project H4C] Buffer Overflow  (0) 2021.03.21

이 페이지 요약 

코딩도장 Unit 48 ~ 50

1. 구조체, 구조체에서 포인터 사용

2. 두 점 사이의 거리 구하기(math.h 헤더 파일의 함수들)

 

Unit 48. 구조체 사용하기

인적 정보를 처리할 경우 이름, 나이 , 주소 등을 저장할 변수가 필요하다. 각각 변수를 만들어 저장할 경우 한 사람의 정보만 저장할 수 있고, 여러명의 정보를 저장하려면 변수를 계속 만들어야 하므로 비효율 적이다.

구조체는 struct 키워드로 정의 하며 다음과 같이 사용할 수 있다.

struct Person {
    char name[20];        // 이름
    int age;              // 나이
    char address[100];    // 주소
};

이름, 나이, 주소 정보가 Person 이라는 구조체에 들어가 사람 단위로 정보를 처리할 수 있다.

구조체는 관련 정보를 하나의 의미로 묶을 때 사용한다. 목적에 맞는 자료형을 만들어서 사용하는데 기본 자료형을 조합하여 만든 자료형을 파생형이라 한다.

48.1 구조체를 만들고 사용하기

구조체는 struct 키워드로 정의한다. 정의 한 후 변수로 선언하여 사용한다.

다음은 인적 정보를 표현하는 구조체를 만들고 사용한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>
 
struct Person {
    char name[20];
    int age;
    int address[100];
};
 
int main()
{
    struct Person p1;
    
    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구");
 
    printf("이름 : %s\n", p1.name);
    printf("나이 : %d\n", p1.age);
    printf("주소 : %s\n", p1.address);
 
    return 0;
}
cs

struct 키워드 뒤에 구조체 이름을 지정하고 중괄호 안에 변수를 선언한다. 구조체 안에 들어있는 변수를 멤버라고 부른다. 구조체를 정의할 때는 }(닫는 중괄호) 뒤에는 반드시 ; (세미클론)을 붙여줘야 한다. 구조체는 보통 main 함수 바깥에 정의한다. 함수 안에 구조체를 정의하면 해당 함수 안에서만 구조체를 사용할 수 있다.

정의한 구조체를 사용하려면 구조체 변수를 선언해야 하며 구조체 이름 앞에 struct 키워드를 붙여줘야 한다.

구조체 멤버에 접근할 때는 .(점)을 사용한다. p1.age = 30; 과 같이 구조체 멤버에 접근한 뒤 값을 할당하고 값을 가져온다. 문자열 멤버는 할당연산자로 저장할 수 없으므로 strcpy함수를 사용하면 된다.

다음과 같이 중괄호와 세미클론 사이에 변수를 지정하면 구조체를 정의하는 동시에 변수를 선언할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
 
struct Person {
    char name[20];
    int age;
    int address[100];
} p1;
 
int main()
{   
    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구");
 
    printf("이름 : %s\n", p1.name);
    printf("나이 : %d\n", p1.age);
    printf("주소 : %s\n", p1.address);
 
    return 0;
}
cs

구조체를 정의하면서 닫는 중괄호와 세미클론 사이에 변수를 지정하면 구조체를 정의하는 동시에 변수가 선언된다. 이와 같이 선언된 변수 p1은 main함수 바깥에 선언되어 있으며 전역변수이다.

구조체 변수를 선언하는 동시에 값을 초기화 하려면 중괄호 안에 .(점)과 멤버 이름을 적고 값을 할당한다. 또한 멤버 이름과 할당 연산자 없이 값만 콤마로 구분하여 나열해도 되는데, 이러한 경우 처음부터 순서대로 값을 채워야 하며 중간에 있는 멤버만 값을 할당하거나 생략할 수 는 없다.

  • struct 구조체이름 변수이름 = { .멤버이름1 = 값1, .멤버이름2 = 값2 };
  • struct 구조체이름 변수이름 = {값1, 값2};

48.2 typedef로 struct 키워드 없이 구조체 선언하기

typedef로 구조체를 정의하며 별칭(alias)을 지정할 수 있다.

구조체 이름과 구조체 별칭은 겹쳐도 되지만 구분하기 위해 구조체 이름은 앞에 _을 붙이겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>
 
typedef struct Person_ {
    char name[20];
    int age;
    int address[100];
} Person;
 
int main()
{
    Person p1;
    
    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구");
 
    printf("이름 : %s\n", p1.name);
    printf("나이 : %d\n", p1.age);
    printf("주소 : %s\n", p1.address);
 
    return 0;
}
cs

구조체를 정의할 땐 맨 앞에 typedef를 붙이고, 구조체를 정의한다. 그리고 닫는 중괄호와 세미클론 사이에 구조체 별칭을 지정하면 된다.

구조체 변수를 선언할 때는 struct 키워드를 생략하고 구조체 별칭으로 바로 변수를 선언할 수 있다. 구조체 별칭으로 선언한 변수도 멤버에 접근할 때는 점을 사용한다.

struct 뒤에 붙는 구조체 이름은 원래 태그(tag)라 부른다.

48.3 익명 구조체 사용하기

typedef로 구조체를 정의하면서 이름을 생략할 수 있다. 변수는 구조체 별칭으로 선언하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>
 
typedef struct{
    char name[20];
    int age;
    int address[100];
} Person;
 
int main()
{
    Person p1;
    
    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구");
 
    printf("이름 : %s\n", p1.name);
    printf("나이 : %d\n", p1.age);
    printf("주소 : %s\n", p1.address);
 
    return 0;
}
cs

typedef struct 뒤에 이름을 지정하지 않고 바로 여는 중괄호로 시작하면 된다.

이때는 반드시 구조체 별칭을 지정해야 한다.

구조체 변수는 구조체 별칭으로 선언하고, 멤버에 접근할 때는 점으로 접근한다.

48.4 퀴즈

정답은 c이다.

정답은 c이다.

정답은 d 이다.

정답은 익명 구조체 이다.

48.5 연습문제 : 좌표 구조체 정의하기

정답은 다음 코드와 같다.

// 1.
 Point2D{
    int x;
    int y;
};

// 2.
struct point2D

// 3.
p1.x = 10;

48.6 연습문제 : typedef로 좌표 구조체 정의하기

정답은 다음 코드와 같다.

// 1. 
struct _Point2D{
    int x;
    int y;
} Point2D;

// 2.
p1

// 3.
p1.y = 20;

48.7 연습문제 : 익명 구조체로 좌표 구조체 정의하기

정답은 } Point2D; 이다.

48.8 심사문제 : 자동차 계기판 구조체 선언하기

정답은 다음코드와 같다.

struct Dashboard d1;

d1.speed = 80;
d1.fuel = 'F';
d1.mileage = 5821.442871f;
d1.engineTemp = 200;
d1.rpm = 1830;

48.9 심사문제 : 자동차 계기판 구조체 정의하기

정답은 다음 코드와 같다.

typedef struct _Dashboard{
    int speed;
    char fuel;
    float mileage;
    int engineTemp;
    int rpm;
}Dashboard;

Unit 49. 구조체 포인터 사용하기

구조체는 멤버 변수가 여러 개 들어 있어 크기가 큰 편이기 때문에 구조체 변수를 일일이 선언해서 사용하는 것 보다 포인터에 메모리를 할당하여 사용하는 것이 효율적이다.

49.1 구조체 포인터를 선언하고 메모리 할당하기

다른 자료형 처럼 구조체도 포인터를 선언할 수 있으며 구조체 포인터에는 malloc함수를 사용하여 동적 메모리를 할당할 수 있다.

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Person {
    char name[20];
    int age;
    char address[100];
};
 
int main()
{
    struct Person *p1 = malloc(sizeof(struct Person));
 
    strcpy(p1->name, "홍길동");
    p1->age = 30;
    strcpy(p1->address, "서울시 용산구");
 
    printf("이름 : %s\n", p1->name);
    printf("나이 : %d\n", p1->age);
    printf("주소 : %s\n", p1->address);
 
    free(p1);
    return 0;
}
cs

struct 키워드와 구조체 이름을 사용하여 구조체 포인터를 선언한다. 포인터 변수 이므로 반드시 *을 붙인다. malloc함수로 메모리를 할당 할 때 sizeof(struct Person)과 같이 구조체 크기를 구하여 넣어준다.

구조체의 멤버에 접근할 때는 점이 아닌 -> (화살표 연산자)를 사용한다.

p1->name 같은 문자열 멤버는 할당연산자로 저장할 수 없기 때문에 strcpy 함수를 사용하면 된다.

사용이 끝났으면 free 함수를 사용하여 할당한 메모리를 해제한다.

(*p1).age 처럼 역참조를 사용하면 점으로 멤버에 접근할 수 있다.

49.2 구조체 별칭으로 포인터를 선언하고 메모리 할당하기

구조체별칭 *포인터이름 = malloc(sizeof(구조체별칭)); 의 형태로 포인터를 선언하고 메모리를 할당할 수 있다.

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
typedef struct _Person {
    char name[20];
    int age;
    char address[100];
} Person;
 
int main()
{
    Person *p1 = malloc(sizeof(Person));
 
    strcpy(p1->name, "홍길동");
    p1->age = 30;
    strcpy(p1->address, "서울시 용산구");
 
    printf("이름 : %s\n", p1->name);
    printf("나이 : %d\n", p1->age);
    printf("주소 : %s\n", p1->address);
 
    free(p1);
    return 0;
}
cs

구조체 별칭을 사용하여 포인터를 바로 선언한 뒤 malloc 함수로 메모리를 할당하면 된다. 할당하는 메모리 크기도 구조체 별칭으로 바로 구할 수 있다. 구조페를 다 사용했으면 free 함수로 메모리를 해제해주면 된다.

익명 구조체도 사용하려면 구조체 별칭을 지정해줘야 하므로 메모리 할당 방법은 위와 동일하다.

49.3 구조체 포인터에 구조체 변수의 주소 할당하기

동적 메모리를 할당하지 않고 구조체 변수에 &(주소 연산자)를 사용하여 구조체 포인터를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Person {
    char name[20];
    int age;
    char address[100];
};
 
int main()
{
    struct Person p1;
    struct Person *ptr;
 
    ptr = &p1;
    
    ptr->age = 30;
 
    printf("나이 : %d\n", p1.age);
    printf("나이 : %d\n", ptr->age);
    return 0;
}
cs

먼저 구조체 변수를 선언하고, 구조체 포인터를 선언한다.

구조체 변수는 주소 연산자 &를 사용하여 메모리 주소를 구할 수 있으며 메모리 주소는 구조체 포인터에 할당할 수 있다.

ptr은 구조체 포인터이므로 -> 으로 멤버에 접근하여 값을 할당하였다.

ptr은 p1의 메모리 주소 이므로 ptr의 멤버를 수정하면 결국 p1의 멤버도 바뀐다.

49.4 퀴즈

정답은 4 이다.

정답은 d 이다.

정답은 d 이다.

49.5 연습문제 : 학생 구조체 포인터에 메모리 할당하기

정답은 다음코드와 같다.

// 1.
malloc(sizeof(struct Student))

// 2.
strcpy(s1->name, "고길동")
s1->grade = 1;
s1->class = 3;
s1->average = 65.389999f;

49.6 연습문제 : 3차원 좌표 구조체 포인터에 메모리 할당하기

정답은 다음 코드와 같다.

// 1. 
malloc(sizeof(Point3D));

// 2.
p1->x = 10.0f;
p1->y = 20.0f;
p1->z = 30.0f;

49.7 연습문제 : 구조체 포인터에 구조체 주소 할당하기

정답은 다음 코드와 같다.

// 1. 
struct Item *ptr;

// 2.
ptr = &item1;

49.8 심사문제 : 사람과 자동차 구조체 포인터에 메모리 할당하기

정답은 다음 코드와 같다.

struct Person *p1 = malloc(sizeof(struct Person));
Car *c1 = malloc(sizeof(Car));

strcpy(p1->name, "고길동");
p1->age = 40;
strcpy(p1->address, "서울시 서초구 반포동");

strcpy(c1->name, "스텔라");
c1->number = 3421;
c1->displacement = 2000;

49.9 심사문제 : 구조체 포인터에 구조체 변수의 주소 할당하기

정답은 다음 코드와 같다.

ptr = &p1;

Unit 50. 두 점 사이의 거이 구하기

50.1 두 점 사이의 거리 구하기

2차원 평면에서 위치를 표현하려면 x와 y값이 필요하다. 구조체를 이용하여 한 점에 x와 y값을 선언하여 점을 표현할 수 있다.

두 점 사이의 거리를 구하려면 피카고라스의 정리를 이용하면 된다.

점 두 개가 있을 때 직각 삼각형을 그리면 다음과 같다.

피타고라스의 정리를 이용하여면 먼저 선 a 와 b의 길이를 구해야 하는데 구조체 변수에 두 점의 좌표가 들어있다고 가정하면 다음과 같이 구할 수 있다.

int a = p2.x - p1.x;    // 선 a의 길이
int b = p2.y - p1.y;    // 선 b의 길이

피타고라스 정리에서 c의 길이를 구하려면 제곱근을 구해야 한다.

C에서 루트는 math.h 헤더파일에 선언된 sqrt 함수를 사용하여 구할 수 있다.

C 코드로 두 점의 거리를 구하는 방법은 다음같다.

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
#include <stdio.h>
#include <math.h>
 
struct Point2D {
    int x;
    int y;
};
 
int main()
{
    struct Point2D p1;
    struct Point2D p2;
 
    p1.x = 30;
    p1.y = 20;
 
    p2.x = 60;
    p2.y = 50;
 
    int a = p2.x - p1.x;
    int b = p2.y - p1.y;
 
    double c = sqrt((a*a) + (b*b));
    
    printf("%f\n", c);
    return 0;
}
cs

sqrt 함수를 사용하면 넣은 값의 제곱근을 구할 수 있다. 제곱근은 소수로 나오기 때문에 double형 변수에 저장했다.

제곱을 구할 때는 math.h 헤더파일에 선언되있는 pow 함수를 사용해도 된다. a^2를 구하고 싶으면 pow(a,2)와 같이 사용한다.

abs, fabs, fabsf 함수를 사용하면 각각 정수, double형 실수, float형 실수에 대한 절댓값을 구할 수 있다.

 

50.2 연습문제 : 사각형의 넓이 구하기

정답은 다음 코드와 같다.

int w = abs(rect.x2 - rect.x1);
int h = abs(rect.y2 - rect.y1);
area = w * h;

밑변과 높이를 구하면서 음수가 나올수 있기 때문에 절댓값을 구하는 abs 함수를 사용하였다.

 

50.3 심사문제 : 두 점 사이의 거리 구하기

정답은 다음 코드와 같다.

int a = p2.x - p1.x;
int b = p2.y - p1.y;
distance = sqrt((a*a) + (b*b));

이 페이지 내용 요약

1. 문자열 관련 함수( 문자열 자르기, 문자열 <-> 숫자 변환)

2. 회문, N-gram 구현 

 

Unit 45. 문자열 자르기

45.1 문자를 기준으로 문자열 자르기

strtok 함수로 특정 문자를 기준으로 문자열을 자를 수 있다. string.h 헤더 파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS    // strtok 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[30= "The Littel Prince";
    char *ptr = strtok(s1, " ");
 
    while(ptr != NULL)
    {
        printf("%s\n", ptr);
        ptr = strtok(NULL" ");
    }
 
    return 0;
}
cs

strtok함수는 지정된 문자를 기준으로 문자열을 자른 다. 기준 문자는 큰 따옴표로 묶어야 한다. 위의 경우 공백 문자를 넣어 공백을 기준으로 잘랐다. 잘린 문자열을 한 번에 얻을 수 없어서 while 반복문으로 문자열을 계속 자르다가 문자열이 나오지 않으면 반복문을 끝내는 방식을 사용한다.

while 반복문 안의 strtok 함수는 자를 문자열 부분에 NULL을 넣어 주었다. NULL은 직전 strtok 함수에서 처리했던 문자열에서 잘린 문자열 만큼 다음 문자료 이동한 뒤 다음 문자열을 자른다. ptr = strtok(ptr," "); 처럼 잘린 문자열의 포인터를 다시 넣으면 다음 문자로 이동하지 못하고 처음에 나오는 문자열만 자르게 된다. strtok 함수를 사용할 때는 처음에만 자를 문자열을 넣어주고 그 다음부터는 NULL을 넣어줘야 한다.

처음 호출되는 strtok는 공백문자를 찾아서 NULL로 채운 뒤 문자열의 첫 부분인 The를 자른다.

반복문 안의 strtok에 NULL을 넣어주면 앞에서 잘린 문자열 만큼 다음 문자로 이동한 뒤 NULL로 채운뒤 Little를 자른다. 이런 식으로 반복하여 문자열 맨 끝에 있는 NULL문자를 만나면 반복문을 종료한다.

strtok 함수는 문자열을 새로 생성해서 반환하는 것이 아니라 자르는 부분으로 널문자로 채운 뒤 잘린 문자열의 포인터를 반환하는 것이다. 원본 문자열의 내용을 바꾸므로 사용에 주의해야 한다.

45.2 문자열 포인터 자르기

문자열 포인터에 문자열 리터럴이 들어 있어서 읽기 전용인 상태이면 strtok 함수는 사용할 수 없다.

문자열 포인터를 자를때는 동적 메모리를 할당하고, 문자열을 복사하여 이 문제를 해결할 수 있다.

1
2
3
4
5
6
7
8
9
char *s1 = malloc(sizeof(char* 30);    
strcpy(s1, "The Little Prince");  
char *ptr = strtok(s1, " ");   
while (ptr != NULL)
{
   printf("%s\n", ptr);
   ptr = strtok(NULL" ");
}
free(s1);    
cs

45.3 날짜와 시간값 자르기

strtok함수는 다양한 특수문자와 알파벳 영문자를 기준으로 문자열을 자를 수 있다. 또한 기준 문자는 한 번에 여러개를 지정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS    // strtok 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[30= "2015-06-10T15:32:19";
    char *ptr = strtok(s1, "-T:");
 
    while(ptr != NULL)
    {
        printf("%s\n", ptr);
        ptr = strtok(NULL"-T:");
    }
 
    return 0;
}
cs

-, T, : 기준으로 문자열을 자르므로 기준 문자를 여러 개 넣을 수 있다.

45.4 자른 문자열 보관하기

문자열을 자르는 while 반복문 안에서 모든 처리를 할 수 없는 상황이 있을 수 있기 때문에 자른 문자열을 보관해야 한다. 다음 코드와 같이 문자열 포인터 배열에 자른 문자열을 보관할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS    // strtok 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
int main()
{
    char s1[30= "The Little Prince";
    char *sArr[10= {NULL, };
    int i = 0;
    char *ptr = strtok(s1, " ");
    while (ptr != NULL)
    {
        sArr[i] = ptr;
        i++;
        ptr = strtok(NULL" ");
    }
    for(int i = 0; i < 10; i++)
    {
        if(sArr[i] != NULL)
            printf("%s\n", sArr[i]);
    }
    return 0;   
}
cs

while 반복문 안에서는 자른 문자열의 메모리 주소를 배열에 저장하고, 배열의 인덱스를 증가시킨다.

ptr에 저장된 메모리 주소가 바뀌기 전에 다른 곳에 보관하면 자른 문자열을 나중에도 계속 사용할 수 있다. 이후 for 반복문에서 배열을 출력하였다.

*45.5 퀴즈 *

정답은 d 이다.

정답은 a, c이다. 기준 문자열은 여러 개 지정할 수 있으며 잘린 문자열의 포인터를 반환한다.

정답은 b 이다.

45.6 연습문제 : 문자열 자르기

정답은 다음 코드와 같다.

1. char *ptr = strtok(s1. " ");

2. while(ptr != NULL)

3. ptr = strtok(NULL, " ");

45.7 심사문제 : 문자열 자르기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s[61];
    scanf("%s", s);
    
    char *ptr = strtok(s, ".");
    
    while(ptr != NULL)
    {
        printf("%s\n", ptr);
        ptr = strtok(NULL".");
    }
    
    return 0;
}
cs

45.8 심사문제 : 특정 단어 개수 세기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>
 
int main()
{
    int cnt = 0;
    char s[1001];
    scanf("%[^\n]s", s);
    
    char *ptr = strtok(s, " .,");
    
    while(ptr != NULL)
    {
        if(strcmp(ptr, "the"== 0)
        {
            cnt++;
        }
        ptr = strtok(NULL" .,");
    }
    
    printf("%d\n", cnt);
    return 0;
}
cs

공백, 점, 쉼표를 기준으로 문자열을 잘랐고 strcmp 함수를 이용해 자른 문자열이 the 이면 cnt 변수에 1씩 누적하고, 마지막에 cnt 변수를 출력하였다.

Unit 46. 문자열과 숫자를 서로 변환하기

46.1 문자열을 정수로 변환하기

atoi 함수를 사용하면 10진법으로 표기된 문자열을 정수로 바꿀 수 있다. stdlib.h 헤더파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = "283";
    int num1;
    
    num1 = atoi(s1);
    printf("%d\n", num1);
    return 0;
}
cs

atoi 함수에 문자열을 넣으면 정수가 반환된다. 문자열 정수로 되어 있어야 하며 알파벳, 특수문자 등이 포함되면 해당 문자부터는 변환하지 않는다. 또한 처음부터 숫자가 아니면 0으로 반환된다. 다음은 정수에 영문자, 특수문자가 섞여 있을 때 반환 예 이다.

46.2 특정 진법으로 표기된 문자열을 정수로 변환하기

strtol 함수를 사용하여 16진법으로 표기된 문자열을 정수로 바꿀 수 있다. strtol(문자열, 끝포인터, 진법)의 형태로 사용한다. stdlib.h 헤더파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = "0xaf10";
    int num1;
 
    num1 = strtol(s1, NULL16);
    printf("%x %d\n", num1, num1);
    return 0;
}
cs

변환할 문자열을 저장한 배열을 넣어주고 16을 지정하면 16진법으로 표기된 문자열을 정수로 변환할 수 있다. 10을 지정하면 10진법으로 표기된 문자열을 정수로 변환할 수 있다.

두 번째 인수는 여러개의 정수로 된 문자열을 변환할 때 사용한다. 여러개의 정수로된 문자열을 각각 변환하는 것은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = "0xaf10 42 0x27C 9952";
    int num1;
    int num2;
    int num3;
    int num4;
    char *end;
 
    num1 = strtol(s1, &end16);
    num2 = strtol(end&end10);
    num3 = strtol(end&end16);
    num4 = strtol(endNULL10);
 
    printf("%x\n%d\n%X\n%d\n", num1,num2,num3,num4);
    return 0;
}
cs

s1에는 16진법으로 표기된 숫자 2개와 10진법으로 표기된 숫자 2개사 있다. 두 번째 인수로 끝 포인터를 선언하여 메모리 주소를 넣으면 strtol 함수가 실행된 뒤에는 끝 포인터가 " 42 0x27C 9952" 처럼 이전 숫자의 끝 부분부터 시작하게 된다.

두번째 부터는 문자열에 end를 넣어 이전 숫자의 끝 부분부터 변환하면 된다. 이 과정을 그림으로 확인하면 다음과 같다.

46.3 문자열을 실수로 변환하기

atof 함수를 사용하여 문자열을 실수로 바꿀 수 있다. stdlib.h 헤더 파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = "35.212345";
    float num1;
 
    num1 = atof(s1);
 
    printf("%f\n", num1);
    return 0;
}
cs

atof 함수에 문자열을 넣으면 실수가 반환된다. 문자열은 실수로 되어있어야 하며 알파벳 영문자, 특수문자가 포함되면 해당 문자부터는 변환을 하지 않는다. 또한 처음부터 숫자가 아니면 0으로 변환된다.

다음과 같이 알파벳 e를 사용한 지수 표기법으로 된 문자열도 실수로 바꿀 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = "3.e5";
    float num1;
 
    num1 = atof(s1);
 
    printf("%e %f\n", num1, num1);
    return 0;
}
cs

strtof 함수를 사용하여 여러개의 실수로 된 문자열을 실수로 바꿀수 있다. stdlib.h 헤더파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>   
 
int main()
{
    char *s1 = "35.283672 3.e5 9.281772 7.e-5";    
    float num1;
    float num2;
    float num3;
    float num4;
    char *end;   
 
    num1 = strtof(s1, &end);     
    num2 = strtof(end&end);    
    num3 = strtof(end&end);   
    num4 = strtof(endNULL);    
 
    printf("%f\n", num1);   
    printf("%e\n", num2);    
    printf("%f\n", num3);  
    printf("%e\n", num4);    
    return 0;
}
cs

처음에는 s1을 넣어 문자열을 실수로 변환하고, 끝 포인터는 &end처럼 메모리 주소를 넣어 strtof함수가 실행된 후 끝 포인터가 이전 숫자의 끝 부분부터 시작하게 했다. 두 번째 부터는 end를 넣어 이전 숫자의 끝 부분버터 변환한다. double 형 실수로 변환하는 strtod 함수도 있다.

46.4 정수를 문자열로 변환하기

sprintf 함수를 사용하여 정수를 문자열로 변환할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    char s1[10];
    int num1 = 283;
 
    sprintf(s1, "%d", num1);
    printf("%s", s1);
    return 0;
}
cs

변환한 문자열을 저장할 배열을 선언하고, sprintf 함수에 서식지정자로 %d를 설정한 뒤 정수를 문자열로 저장하였다. atoi를 반대로 변환하는 itoa 같은 함수도 있지만 C 표준 함수는 아니다.

다음과 같이 16진법으로 표기된 문자열로 변환하려면 서식지정자 %x를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    char s1[10];
    int num1 = 283;
 
    sprintf(s1, "0x%x", num1);
    printf("%s\n", s1);
    return 0;
}
cs

sprintf 함수에서 서식지정자로 %x를 사용하면 16진법으로 표기된 문자열로 변환할 수 있다. 16진수라는 것을 명확하게 나타내기 위해 앞에 0x를 붙여준 것이다. 서식지정자를 %X로 지정하면 16진수 알파벳 부분이 대문자로 저장된다.

46.5 실수를 문자열로 변환하기

실수를 문자열로 변환할 때도 sprintf 함수를 사용한다. 다음과 같이 서식지정자로 %f를 지정하면 된다. 또한 %e를 지정하여 지수 표기법으로 된 문자열로 변환할 수도 있다.

sprintf(s1, "%f", num1);
sprintf(s1, "%e", num1);

변환된 문자열이 길어질 수 있기 때문에 배열의 크기 또는 동적메모리로 할당한 크기를 잘 확인해야 한다.

46.6 퀴즈

정답은 b이다.

정답은 b이다.

정답은 c 이다.

정답은 e 이다.

정답은 c, g 이다.

정답은 b이다.

46.7 연습문제: 문자열을 10진 정수로 변환하기

정답은 다음 코드와 같다.

1. #include <stdlib.h>

2. num1 = atoi(s1);

46.8 연습문제 : 문자열을 16진 정수로 변환하기

정답은 다음 코드와 같다.

1. num1 = strtol(s1, NULL, 16)

2. "0x%X\n"

대문자로 출력하므로 서식지정자 %X를 사용한다.

46.9 연습문제 : 문자열을 실수로 변환하기

정답은 다음과 같다.

1. "97.527824"

2. num1

46.10 연습문제: 여러개의 실수로 된 문자열을 실수로 변환하기

정답은 다음 코드와 같다.

1. num1 = strtof(s1, &end);

2. num2 = strtof(end, NULL);

46.11 연습문제 : 숫자를 문자열로 변환하기

정답은 다음 코드와 같다.

1. char s1[30];

2. sprintf(s1, "%f 0x%x", num1, num2);

46.12 심사문제 : 문자열을 정수와 실수로 변환하기

정답은 다음 코드와 같다.

num1 = strtol(s1, &end, 16);
num2 = strtol(end, &end, 10);
num3 = strtof(end, NULL);

46.3 심사문제 : 정수와 실수를 문자열로 변환하기

정답은 다음 코드와 같다.

sprintf(s1, "%d", num1);
sprintf(s2, "%f", num2);

Unit 47. 회문 판별과 N-gram 만들기

회문은 유전자 염기 서열 분석에서 많이 쓰이고, N-gram은 빅데이터 분석, 검색 엔진에서 많이 쓰인다.

47.1 회문 판별

회문은 level, SOS 등과 같이 순서를 거꾸로 읽어도 제대로 읽은 것과 같은 단어와 문장을 말한다.

회문인지 판별하려면 첫번째 글자와 마지막 글자가 같고, 안쪽으로 한 글자씩 좁혔을 때 글자가 서로 같으면 회문이다.

다음은 문자열을 배열에 넣은뒤 반복문으로 각 글자를 검사한 것이다.

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
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
 
int main()
{
    char word[30];
    int length;
    bool isPalindrome = true;
 
    printf("단어를 입력하세요 : ");
    scanf("%s", word);
 
    length = strlen(word);
 
    for(int i = 0; i < length / 2; i++)
    {
        if(word[i] != word[length - 1 -i])
        {
            isPalindrome = false;
            break;
        }
    }
 
    printf("%d\n", isPalindrome);
    return 0;
}
cs

회문일 경우 1이 출력되고, 회문이 아닐 경우 0이 출력된다.

회문 판별에서 가장 중요한 것은 문자열의 길이이다.

문자열 길이의 절반만큼 반복하며 왼쪽 문자와 오른쪽 문자들을 검색한다. 반복문 안에서 왼쪽문자와 오른쪽 문자를 비교하며 문자가 다르면 isPalindrome 에 false를 넣고 반복문을 끝낸다. 문자열의 마지막 문자는 word[length-1] 이므로 인덱스를 i 만큼 빼주면 오른쪽에서 왼쪽으로 진행할 수 있다.

47.2 N-gram 만들기

N-gram은 문자열에서 N개의 연속된 요소를 추출하는 방법이다. Hello의 경우 2-gram으로 추출하면 다음과 같다.

문자열의 처음부터 끝까지 한 글자씩 이동하며 2 글자씩 추출한다.

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

int main()
{
    char text[30] = "Hello";
    int length;

    length = strlen(text);

    for(int i = 0; i < length - 1; i++)
    {
        printf("%c%c\n", text[i], text[i+1]);
    }

    return 0;
}

2-gram이므로 문자열의 끝에서 한 글자 앞까지만 반복하며 현재 문자와 그 다음문자 두 글자씩 출력한다.

만약 3-gram이라면 조건식은 i < length -2가 될 것이고, 문자열 끝에서 두 글자 앞까지 반복하면 된다.

단어 단위 N-gram도 있다. 다음은 공백을 기준으로 구분하여 단어단위 2-gram을 출력하는 코드이다.

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
#include <stdio.h>
#include <string.h>
 
int main()
{
    char text[100= "this is c language";
    char *tokens[30= {NULL, };
 
    int cnt = 0;
    char *ptr = strtok(text, " ");
 
    while (ptr != NULL)
    {
        tokens[cnt] = ptr;
        cnt++;
        ptr = strtok(NULL" ");
    }
 
    for(int i = 0; i < cnt -1; i++)
    {
        printf("%s %s\n", tokens[i], tokens[i+1]);
    }
 
    return 0;
}
cs

strtok 함수로 문자열을 자른 뒤 각 단어들을 배열에 넣고, 배열의 마지막에서 요소 한 개 앞까지만 반복하면서 현재 문자열과 그 다음 문자열을 출력하면 된다.

47.3 연습문제 : 정수 회문 판별하기

정답은 다음 코드와 같다.

1. sprintf(text, "%lld", num1)

2. begin++;

3. end--;

입력값이 정수 이므로 sprintf 함수로 문자열로 변환하여 회문을 판별한다.

47.4 연습문제 : 4-gram 만들기

정답은 다음 코드와 같다.

1. length < n

2. i < length - (n-1)

3. j < n

문자열을 반복할 때는 배열의 범위를 벗어나지 않도록 i < length(n-1)로 조건식을 사용했다.

47.5 심사문제 : 공백이 포함된 회문 판별

정답은 다음 코드와 같다.

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
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
 
int main()
{
    char str[31];
    int length;
    bool isP = true;
    
    scanf("%[^\n]s", str);
    length = strlen(str);
    
    for(int i = 0, j = length-1; i < j; i++, j--)
    {
        while(str[i] == ' ')
            i++;
        while(str[j] == ' ')
            j--;
        if(str[i] != str[j])
        {
            isP = false;
            break;
        }
    }
    printf("%d\n", isP);
    return 0;
}
cs

반복문에서 문자열의 처음부터, 끝에서부터 반복시키며 공백이 있을 경우 한 칸씩 증가, 감소 시켜서 공백을 지우고, 문자열 앞에서 부터 온 문자와 뒤에서 부터 온 문자가 다르면 회문을 거짓으로 하고, 반복문을 끝낸다.

47.6 심사문제 : N-gram 만들기

정답은 다음 코드와 같다.

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
#include <stdio.h>
#include <string.h>
 
int main()
{
    char text[11];
    int n;
    scanf("%d %s"&n, text);
    int length = strlen(text);
    
    if(length < n)
    {
        printf("wrong\n");
    }
    else
    {
        for(int i = 0; i < length - (n-1); i++ )
        {
            for(int j = 0; j < n; j++)
            {
                printf("%c", text[i+j]);
            }
            printf("\n");
        }
    } 
    
    return 0;
}
cs

2중 for문의 안쪽 반복문은 입력한 숫자만큼 반복하므로 입력한 숫자만큼의 문자를 출력할 수 있고, 바깥쪽 반복문으로 1씩 증가시키면서 문자열에서 문자를 한 칸씩 앞으로 가도록 했다.

 

 

이번 페이지 요약

코딩도장 C언어 unit 41~44

1. 문자열 관련 함수(길이, 복사, 문자열 만들기, 검색)

 

Unit 41. 문자열의 길이를 구하고 비교하기

41.1 문자열의 길이 구하기

문자열의 길이는 strlen 함수로 구할 수 있으며 string.h 헤더 파일에 선언되어 있다. 문자열 포인터와 문자 배열의 길이 모두 구할 수 있으며 사용법은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>
 
int main()
{
    char *s1 = "Hello";
    char s2[10= "Hello";
 
    printf("%d\n", strlen(s1));
    printf("%d\n", strlen(s2));
    return 0;
}
cs

strlen에 문자열 포인터나 문자 배열을 넣으면 문자열의 길이가 반환된다 Hello는 5글자 이기 때문에 5가 출력되었으며 NULL 부분은 포함하지 않는다. 배열의 경우 배열의 크기는 관계없이 문자열의 길이만 구한다. 위 코드의 경우도 배열의 크기는 10이지만 출력값은 문자열의 길이인 5가 나왔다.

41.2 문자열 비교하기

strcmp 함수를 사용하면 두 문자열이 같은 지 비교할 수 있다. string.h 헤더 파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
 
int main()
{
    char *s1 = "Hello";
    char s2[10= "Hello";
    
    int ret = strcmp(s1, s2);
 
    printf("%d\n", ret);
    return 0;
}
cs

strcmp 함수에 비교할 문자열을 넣으면 결과를 정수로 반환하며 반환 되는 값의 종류는 다음과 같다.

  • -1: ASCII 코드 기준으로 문자열2(s2)가 클 때
  • 0: ASCII 코드 기준으로 두 문자열이 같을 때
  • 1: ASCII 코드 기준으로 문자열1(s1)이 클 때

배열 문자열, 문자열 포인터 등 문자열의 저장 방식은 문자열 비교에 영향을 주지 않는다.

strcmp 함수는 문자열에서 첫 번째 문자부터 차례대로 비교하며 비교 기준은 각 문자의 ASCII 코드이다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <string.h>
 
int main()
{
    printf("%d\n", strcmp("aaa""aaa"));
    printf("%d\n", strcmp("aab""aaa"));
    printf("%d\n", strcmp("aab""aac"));
    return 0;
}
cs

aaa는 아스키 코드로 97 97 97 이고, aab 는 97 97 98 이기 때문에 aab 가 더 크다. 또한 aac는 97 97 99 이므로 aac가 aab 보다 크다. 앞의 것이 크면 1, 뒤의 것이 크면 -1을 반환한다. 이것은 윈도우 기준이고, 리눅스나 OS X에서는 코드값의 차이를 반환한다. 따라서 입력받는 두 문자열의 크기를 비교할 땐 윈도우에서는 switch문을 사용하면 되고, 리눅스나 OS X 에서는 if 문을 사용해야 한다.

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
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[20];
    char s2[20];
 
    printf("문자열 두 개를 입력하세요 : ");
    scanf("%s %s", s1, s2);
 
    int ret = strcmp(s1, s2);
    printf("반환값 : %d\n", ret);
 
    if(ret == 0)
    {
        printf("같음\n");
    }
    else if(ret > 0)
    {
        printf("%s가 %s보다 큼\n", s2, s1);
    }
    else if(ret < 0)
    {
        printf("%s가 %s보다 큼\n", s1, s2);
    }
 
    return 0;
}
cs

리눅스나 OS X에서 strcmp는 문자열1에서 문자열 2의 아스키 코드값을 뺏을 때 양수가 나오면 1이 크고, 음수가 나오면 2가 크다. 두 문자열이 같으면 0이 나온다.

41.3 퀴즈

정답은 c 이다.

정답은 c,d,f 이다.

41.4 연습문제 : 문자열 길이 구하기

정답은 strlen(s1) 이다.

41.5 연습문제 : 문자열 비교하기

정답은 char *s1 = "Pachelbel Canon"; 이다.

41.6 심사문제 : 문자열 길이 구하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s[30];
    scanf("%s", s);
    printf("%d", strlen(s));
    return 0;
}
cs

41.7 심사문제 : 문자열 비교하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[30];
    char s2[30];
    
    scanf("%s %s", s1, s2);
    
    printf("%d", strcmp(s1, s2));
    return 0;
}
cs

Unit 42. 문자열을 복사하고 붙이기

42.1 문자열 복사하기

strcpy 함수를 이용하여 문자열을 다른 배열이나 포인터(메모리)로 복사할 수 있다. string.h 헤더 파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define _CRT_SECURE_NO_WARNINGS    // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[10= "Hello";
    char s2[10];
 
    strcpy(s2, s1);
 
    printf("%s\n", s2);
    return 0;
}
cs

배열 s2에는 아무것도 들어있지 않았지만 strcpy 함수로 s1의 문자열이 복사되어 s2에도 저장되었다.

복사된 결과가 저장된 배열의 크기는 반드시 NULL까지 들어갈 수 있는 크기여야 한다. 따라서 Hello 라는 문자열이 복사되려면 NULL문자까지 크기는 최소 6 이상이 되어야 한다.

문자열 포인터는 복사할 공간도 없으며 읽기만 가능하기 때문에 문자열을 복사할 수 없다. 문자열 포인터에 문자열을 복사하려면 다음과 같이 메모리를 할당한 뒤 복사해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRT_SECURE_NO_WARNINGS    // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = "Hello";
    char *s2 = malloc(sizeof(char* 10);
 
    strcpy(s2, s1);
 
    printf("%s\n", s2);
    free(s2);
    return 0;
}
cs

s2에 char 10개 크기만큼 동적 메모리를 할당하여 문자열을 복사하고, free로 할당한 메모리를 해제하였다.

42.2 문자열 붙이기

문자열은 strcat 함수를 이용하여 서로 붙일 수 있다. string.h 헤더파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define _CRT_SECURE_NO_WARNINGS    // strcat 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[10= "world";
    char s2[20= "Hello";
 
    strcat(s2, s1);
 
    printf("%s\n", s2);
    return 0;
}
cs

strcat함수에 최종 결과가 나올 문자열과 붙일 문자열을 넣는다. 위의 경우 s2 뒤에 s1 이 붙어서 Helloworld가 나온다.

문자열을 붙이더라도 배열이 모자라지 않도록 최종 결과가 나올 문자열의 배열 크기를 넉넉하게 만들어야 한다.

문자열 포인터에 사용하려면 붙일 문자열 포인터에 동적 메모리를 할당해 줘야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS  
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = "world";
    char *s2 = malloc(sizeof(char* 20);
 
    strcpy(s2, "Hello");
    strcat(s2, s1);
 
    printf("%s\n", s2);
    free(s2);
    return 0;
}
cs

최종 결과가 나올 문자열 포인터에 char 20개 크기만큼 동적 메모리를 할당하고, 메모리가 할당된 문자열 포인터는 문자열을 직접 할당할 수 없으므로 strcpy 함수를 이용하여 "Hello"를 복사하고, s2 뒤에 s1을 붙인다. 문자열 사용이 끝났으면 free로 동적 메모리를 해제한다.

42.3 배열 형태의 문자열을 문자열 포인터에 복사하기

다음과 같이 배열 형태의 문자열을 문자열 포인터로 복사할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRT_SECURE_NO_WARNINGS  
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{
    char s1[10= "Hello";
    char *s2 = malloc(sizeof(char* 10);
 
    strcpy(s2, s1);
 
    printf("%s\n", s2);
    free(s2);
    return 0;
}
cs

malloc 함수로 s2에 메모리를 복사할 문자열이 충분히 들어갈 수 있을 정도로 할당하고, strcpy로 복사하면 된다.

42.4 배열 형태의 문자열을 문자열 포인터에 붙이기

다음과 같이 배열 문자열을 문자열 포인터에 붙일 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS   
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{
    char s1[10= "world";
    char *s2 = malloc(sizeof(char* 20);
 
    strcpy(s2, "Hello");
    strcat(s2, s1);
 
    printf("%s\n", s2);
    free(s2);
    return 0;
}
cs

문자열 포인터 s2 뒤에 문자열을 붙여야 하므로 메모리가 부족하지 않도록 넉넉히 할당하고, strcpy함수로 s2에 "Hello"를 복사한다. 이후 s2 뒤에 strcat 함수로 문자열 배열 s1을 붙이면 된다. 문자열 사용이 끝나면 free로 메모리를 해제해줘야 한다.

42.5 퀴즈

정답은 NULL문자까지 6이다.

정답은 NULL문자까지 11바이트 이다.

정답은 c 이다.

42.6 연습문제 : 문자열 포인터를 배열에 복사하기

문자열을 복사하는 것이므로 strcpy 함수를 사용하여 정답은 strcpy(s2, s1); 이다.

42.7 연습문제 : 문자열 포인터를 동적 메모리에 복사하기

정답은 다음 코드와 같다.

1. malloc(sizeof(char) * 20);

2. strcpy(s2, s1);

42.8 연습문제 : 문자 배열을 붙이기

정답은 strcat(s2, s1); 이다.

42.9 연습문제 : 문자열 리터럴과 동적 메모리 붙이기

정답은 다음 코드와 같다.

1. strcpy(s2, "Alice in");

2. strcat(s2, s1);

42.10 심사문제 : 문자 배열 복사하기

정답은 다음 코드와 같다.

scanf("%s", s1);
strcpy(s2, s1);

s1에는 값을 입력받고, s2에는 s1의 값을 복사해서 가져왔다.

42.11 심사문제 : 두 문자열 붙이기

scanf("%s", s1);
strcat(s1, "th");

문자열을 입력받아 strcat 함수로 문자열 끝에 th를 붙였다.

Unit 43. 문자열 만들기

43.1 서식을 지정하여 배열 형태로 문자열 만들기

sprintf 함수를 사용하면 서식을 지정하여 문자열을 만들 수 있다. sprintf(배열, 서식, 값);의 형태로 사용하며 서식에 값이 들어간 것을 배열에 저장할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
#define _CRT_SECURE_NO_WARNINGS    // sprintf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
 
int main()
{
    char s1[20];
 
    sprintf(s1, "Hello %s""world!");
 
    printf("%s\n", s1);
    return 0;
}
cs

sprintf 함수에 문자열을 저장할 배열과 문자열을 만들 서식, 문자열을 만들 값(문자열)을 순서대로 넣어 %s부분이 "word!"로 바뀌게 된다. 위 코드에서 s1과 같이 문자열을 저장할 빈 배열을 버퍼(buffer)라고 부른다. 서식지정자로 C언어의 다양한 자료형들을 문자열로 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main()
{
    char s1[30];
 
    sprintf(s1, "%c %d %f %e"'a'103.2f, 1.123456e-21f);
    printf("%s\n", s1);
    return 0;
}
cs

sprintf()함수에 %c, %d, %f, %e를 지정하여 각 서식지정자에 맞는 문자, 정수, 소수점 표기 실수, 지수 표기법 실수를 넣었다. 위 코드에서는 함수에 값을 바로 넣었지만, 변수를 넣어도 된다.

43.2 서식을 지정하여 문자열 포인터에 문자열 만들기

문자열 포인터를 사용하려면 malloc 함수로 메모리를 할당한 뒤 sprintf함수로 문자열을 만들면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = malloc(sizeof(char* 20);
    sprintf(s1, "Hello, %s""world!");
    
    printf("%s\n", s1);
    free(s1);
    return 0;
}
cs

char 20개 크기의 동적 메모리를 할당하고 sprintf함수에 서식을 지정하여 문자열을 만들면 된다. 배열과 마찬가지로 문자열을 생성할 메모리 공간은 버퍼이다. 문자열 사용이 끝나면 free 함수 동적 할당한 메모리를 해제 해야 한다.

다음과 같이 다양한 종류의 서식지정자를 사용하여 다양한 자료형도 문자열로 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = malloc(sizeof(char* 30);
    sprintf(s1, "%c %d %f %e"'a'103.1f, 1.23e-23f);
    
    printf("%s\n", s1);
    free(s1);
    return 0;
}
cs

char 30개 크기만큼 동적으로 메모리를 할당하고, sprintf함수에 서식을 지정하여 문자, 정수, 소수점 표기 실수, 지수 표기 실수를 문자열로 만들었다. 문자열 사용이 끝나면 free함수로 할당한 메모리를 해제해야 한다.

43.3 퀴즈

정답은 d이다.

정답은 "%c %d" 이다.

정답은 버퍼 이다.

43.4 연습문제 : 숫자와 문자열을 조합하여 문자열 만들기

숫자와 문자열이 순서대로 들어가므로 정답은 9, "Symphony" 이다.

43.5 연습문제 : 서식에 맞게 문자열 만들기

정수 3개 ,문자 1개 정수 1개가 순서대로 들어가므로 정답은 "%d %d %d %c %d" 이다.

43.6 심사문제 : 서수 줄임말 문자열 만들기

정답은 다음코드와 같다.

scanf("%d %s", &number, name);

if(number == 1)
    sprintf(result, "%dst %s", number, name);
else if(number == 2)
    sprintf(result, "%dnd %s", number, name);
else if(number == 3)
    sprintf(result, "%drd %s", number, name);
else
    sprintf(result, "%dth %s", number, name);

숫자에 따라 숫자 뒤에 붙는것이 다르기 때문에 if 조건문을 사용하여 조건에 맞게 출력하도록 하였다.

Unit 44. 문자열 검색하기

44.1 문자열 안에서 문자로 검색하기

strchr 함수를 사용하여 문자열에서 특정 문자로 검색할 수 있다. string.h 헤더 파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[30= "A Garden Diary";
    char *ptr = strchr(s1, 'a');
 
    while(ptr != NULL)
    {
        printf("%s\n", ptr);
        ptr = strchr(ptr + 1'a');
    }
    return 0;
}
cs

strchr 함수에 문자열과 검색할 문자를 넣어주면 해당 문자로 시작하는 문자열의 위치(포인터)를 반환한다.

'a' 로 시작하는 부분을 더 찾기 위해 while 반복문을 사용하여 검색된 문자여릐 포인터에 1을 더해 다음부터 또 검색해서 찾는다. NULL이 나오면 검색할 문자열이 없는 것이므로 반복을 끝낸다.

'a' 가 들어간 문자를 처음 찾으면 arden Diary를 찾을 수 있다. 포인터에 1을 더하면 rden Diary 가 되므로 다음번 a를 찾을 수 있게 된다.

strchr 함수는 대소문자를 구분하므로 A는 찾지 않고 넘어간다.

44.2 문자열의 오른쪽 끝부터 문자로 검색하기

strrchr 함수는 문자열의 끝에서 부터 문자를 검색한다. string.h 헤더 파일에 선언되어 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[30= "A Garden Diary";
    char *ptr = strrchr(s1, 'a');
 
    printf("%s\n", ptr);
    return 0;
}
cs

strrchr 함수에 문자열과 검색할 문자를 넣으면 문자열 끝에서 부터 검색하여 'a'로 시작하는 문자열의 포인터를 반환한다. strrchr함수도 대소문자를 구분한다.

44.3 문자열 안에서 문자열로 검색하기

strstr 함수는 문자열 안에서 문자열을 검색할 수 있는 함수이며 string.h 헤더 파일에 선언되어 있다.

워드나 메모장에서 검색할 때 단어로 검색하는 경우가 많은것처럼 프로그램을 만들때도 문자열 찾는 함수를 더 많이 사용한다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[30= "A Garden Diary";
    char *ptr = strstr(s1, "den");
 
    printf("%s\n", ptr);
    return 0;
}
cs

strstr 함수에 검색할 문자열을 넣으면 해당 문자열로 시작하는 문자열의 위치(포인터)를 반환한다. 검색한 문자만 나오지 않고, 검색한 문자를 포함하여 뒤에 오는 모든 문자열이 나온다. strstr 함수도 대소문자를 구분하며 다음과 같이 반복문을 사용하여 문자열을 계속 검색할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[100= "A Garden Diary A Garden Diary A Garden Diary";
    char *ptr = strstr(s1, "den");
 
    while(ptr != NULL)
    {
        printf("%s\n", ptr);
        ptr = strstr(ptr+1"den");
    }
    return 0;
}
cs

44.4 퀴즈

정답은 c, e 이다.

정답은 b 이다.

44.5 연습문제 : 문자열 안에서 문자로 검색하기

정답은 다음 코드와 같다.

1. char *ptr = strchr(s1, 'n');

2. while(ptr != NULL)

3. ptr = strchr(ptr + 1, 'n');

44.6 연습문제 : 문자열의 오른쪽 끝 부터 문자로 검색하기

정답은 char *ptr = strrchr(s1, 'i'); 이다. 마지막의 ince만 출력하려면 오른쪽 부터 검색하는 strrchr 함수를 사용해야 한다.

44.7 심사문제 : 공백 개수 세기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
 
int main()
{
    char str[1001];
    scanf("%[^\n]s", str);
    char *ptr = strchr(str, ' ');
    int cnt = 0;
    
    while(ptr != NULL)
    {
        cnt++;
        ptr = strchr(ptr + 1' ');
    }
    printf("%d\n", cnt);
    return 0;
}
cs

정수형 변수를 선언한 후 공백을 찾을 때 마다 1씩 누적하고 반복문이 끝난 뒤 누적한 값을 출력하였다.

 

이번 페이지 요약 : 

코딩도장 C언어 Unit 39, 40

1. 문자열 사용하기

2. 입력값 문자열에 저장 

 

Unit 39. 문자열 사용하기

c언어 문자를 저장하는 자료형은 있지만 문자열을 저장하는 자료형은 없다. 그래서 char 자료형에 문자열을 저장하면 컴파일은 되지만 실행은 되지 않는다. 컴파일을 한 뒤 디버깅 모드로 보면 다음과 같은 에러가 발생한다.

39.1 문자와 문자열 포인터 알아보기

문자열은 다음과 같이 char 포인터 형식으로 사용한다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    char c1 = 'a';
    char *s1 = "Hello";
 
    printf("%c\n", c1);
    printf("%s\n", s1);
    return 0;
}
cs

문자는 글자가 하나만 있는 상태이고, 문자열은 글자 여러 개가 계속 이어진 상태를 의미한다. 문자는 1바이트 크기라 char에 저장할 수있지만, 문자열은 1바이트가 넘어서므로 char에 저장할 수 없다. 따라서 문자열은 변수에 직접 저장하지 않고 포인터를 이용하여 저장한다. 포인터는 메모리 주소를 저장하기 때문에 메모리 주소를 보고 문자열이 저장된 위치로 이동한다. 문자열 리터럴이 있는 메모리 주소는 읽기 전용이기 때문에 다른 문자열을 덮어 쓸 수 없다. 문자열 리터럴이 저장되는 위치는 컴파일러가 결정한다.

문자열은 ""(큰따옴표)로 묶어야 하며 문자열을 출력할 때는 서식지정자 %s를 사용해야 한다.

C에서 문자열은 마지막 끝에 항상 널문자가 붙는다. NULL은 0으로 표현할 수 있으며 문자열의 끝을 나타낸다.

문자열을 출력할때는 문자들을 순서대로 계속 출력하다가 NULL을 만나면 출력을 끝낸다.

39.2 문자열 포인터에서 인덱스로 문자에 접근하기

문자열도 다음과 같이 대괄호를 사용하여 인덱스로 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    char *s1 = "Hello";
 
    printf("%c\n", s1[1]);
    printf("%c\n", s1[4]);
    printf("%c\n", s1[5]);
    return 0;
}
cs

문자 하나를 출력할 때는 %c 서식지정자를 사용하며 맨 뒤 문자열인 5번째 인덱스를 가져오면 NULL 이기 때문에 출력해도 화면에 표시되지 않는다.

문자열 포인터에서 인덱스로 문자를 가져오는 것은 가능하지만 인덱스로 문자를 할당하는것은 불가능하다. 문자를 할당하려고 에러가 발생한다. 문자열 리터럴이 있는 메모리 주소는 읽기 전용이기 때문이다.

38.3 배열 형태로 문자열 선언하기

문자열은 다음과 같이 문자 배열에 저장할 수 도 있다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main()
{
    char s1[10= "Hello";
    
    printf("%s\n", s1);
    return 0;
}
cs

위 코드는 크기가 10인 배열을 선언한 뒤 문자열을 할당한 것이다.

문자열에 배열을 저장하는 방식은 배열 요소 하나하나에 문자가 저장된다. 배열이기 때문에 인덱스는 0부터 시작하고, 문자열의 맨 뒤에 NULL이 들어간다. 남는 공간에도 모두 NULL이 들어간다.

배열로 문자열을 사용할 때는 무조건 배열을 선언한 즉시 문자열로 초기화해야 한다. 이미 선언한 배열에는 문자열을 할당할 수 없으며 정 할당하고 싶으면 요소에 문자 하나 하나를 넣으면 된다.

배열을 선언할 때는 배열의 크기를 할당할 문자열보다 크게 지정해야 한다. 문자열 문자 개수보다 배열의 크기가 작다면 컴파일은 되지만 문자열이 제대로 출력되지 않는다. 문자열은 마지막에 NULL이 꼭 들어가야 하므로 문자 개수에 하나 더한 크기가 문자열을 저장할 수 있는 배열의 최소 크기 이다.

다음과 같이 문자열을 바로 할당할 때는 배열의 크기를 생략할 수 있다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main()
{
    char s1[] = "Hello";
    
    printf("%s\n", s1);
    return 0;
}
cs

이와 같은 경우 배열의 크기는 문자열의 문자 개수에 맞게 자동으로 지정된다. 위 경우 문자 5개에 NULL을 더해 6이 된다.

39.4 배열 형태의 문자열에서 인덱스로 문자에 접근하기

다음과 같이 인덱스로 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    char s1[] = "Hello";
    
    printf("%c\n", s1[1]);
    printf("%c\n", s1[4]);
    printf("%c\n", s1[5]);
    return 0;
}
cs

배열을 인덱스로 접근하면 char과 같기 때문에 %c로 출력할 수 있다. 배열로 저장된 문자열 맨 뒤에는 NULL이 있어 출력값에는 보이지 않는다.

배열 형태의 문자열은 다음과 같이 인덱스로 접근하여 문자를 할당할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    char s1[] = "Hello";
    
    s1[0= 'A';
    
    printf("%s\n", s1);
    return 0;
}
cs

Hello 가 들어있는 문자 배열의 0번 인덱스에 A를 할당해서 Aello가 되었다. 배열 형태의 문자열은 배열에 문자열이 모두 복사되었기 때문에 인덱스로 접근하여 내용을 변경할 수 있다.

39.5 퀴즈

정답은 b,e,g,h이다.

정답은 d 이다.

정답은 b 이다.

정답은 a, d 이다.

39.6 연습문제 : 문자열 만들기

정답은 char s1[] 이다.

39.7 연습문제 : 문자열 요소 출력

정답은 s1[10] 이다.

39.8 심사문제 : 문자열 만들기

정답은 다음 코드와 같다.

char s1[] = "Beethoven\n9th\nSymphony";

출력에 줄바꿈이 있으므로 개행문자 \n을 사용했다.

Unit 40. 입력 값을 문자열에 저장하기

40.1 입력 값을 배열 형태의 문자열에 저장하기

scanf 함수에서 서식지정자 %s를 사용하면 입력값을 배열 형태의 문자열에 저장할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    char s1[10];
 
    printf("문자열을 입력하세요 : ");
    scanf("%s", s1);
 
    printf("%s\n", s1);
    return 0;
}
cs

scanf 함수에 서식지정자로 %s를 넣어서 문자열을 입력받을 수 있다. 매개변수는 입력값을 저장할 배열을 넣는데, 배열 앞에는 일반 변수와 달리 &를 붙이지 않는다. 위 코드에서는 크기가 10인 배열을 선언했으므로 문자열 맨 뒤에 붙는 널문자 까지 포함하여 총 10개의 문자를 저장할 수 있다. 따라서 실제 저장할 수 있는 문자는 9개이다.

scanf에서 %s로 문자열을 저장할 때 입력된 문자열에 공백이 있다면 배열에는 공백 직전까지만 저장된다. 공백을 포함하여 저장하고 싶다면 서식지정자로 %[^\n]s를 사용하면 된다.

40.2 입력 값을 문자열 포인터에 저장하기

문자열 포인터를 선언한 뒤 scanf 함수로 입력 값을 문자열 포인터에 저장하고 실행하면 에러가 발생한다. 이미 할당된 문자열 포인터에 저장된 메모리 주소는 읽기만 할 수 있고, 쓰기가 막혀있기 때문이다.

입력값을 문자열 포인터에 저장하려면 문자열이 들어갈 공간을 따로 만들어야 한다. malloc 함수로 메모리를 할당한 뒤 문자열을 저장하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char *s1 = malloc(sizeof(char* 10);
    
    printf("문자열을 입력하세요 : ");
    scanf("%s", s1);
 
    printf("%s\n", s1);
    free(s1);
    return 0;
}
cs

char 10개 크기 만큼 동적 메모리를 할당하여 입력값을 문자열 포인터 s1에 저장할 수 있다. 입력 받을 때 s1은 포인터이므로 &를 붙이지 않는다. 문자열 사용이 끝나면 free 함수로 메모리를 해제해야 한다. 문자열 중간에 공백이 있다면 문자열 포인터에는 공백 직전까지만 저장된다.

40.3 문자열을 여러 개 입력받기

다음과 같이 공백으로 구분된 문자열 여러개를 입력받을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    char s1[10];
    char s2[10];
 
    printf("문자열을 두 개 입력하세요 : ");
    scanf("%s %s", s1, s2);
 
    printf("%s\n", s1);
    printf("%s\n", s2);
    return 0;
}
cs

문자열 두 개를 저장하기 위한 공간을 선언하고, scanf 함수에서 서식지정자로 %s를 두 개 넣어준다. 문자열이 저장될 배열을 두 개 넣어주면 공백으로 구분된 문자열을 입력받을 수 있다. 문자열을 더 입력받고 싶으면 %s와 입력받을 배열(또는 문자열 포인터)의 개수를 늘려주면 된다.

40.4 퀴즈

정답은 c, e, g 이다.

정답은 9 이다.

40.5 연습문제 : 입력받은 문자열을 배열에 저장하기

정답은 scanf("%s", s1); 이다.

40.6 연습문제 : 입력받은 문자열을 동적 메모리에 저장하기

정답은 다음 코드와 같다.

1. char *s1 = malloc(sizeof(char) * 10);

2. scanf("%s", s1);

40.7 연습문제 : 문자열 세 개 입력받기

정답은 scanf("%s %s %s", s1, s2, s3); 이다.

40.8 심사문제 : 문자열 4 개 입력받기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    char s1[30];
    char s2[30];
    char s3[30];
    char s4[30];
    
    scanf("%s %s %s %s", s1, s2, s3, s4);
    
    printf("%s\n%s\n%s\n%s\n", s1, s2, s3, s4);
    return 0;
}
cs

0부터 9까지 몇번 사용됬는지 저장할 공간을 int형 포인터에 int 자료형 크기로 10개 할당하였다.

자연수 세개를 입력받아, 입력받은 자연수 3개를 곱한다.

곱한 값이 0보다 클때까지 반복하고 반복문 안에는 곱한 값을 10으로 나눈 나머지를 배열의 인덱스로 하여 그 숫자가 나올때 마다 해당 인덱스에 1씩 누적하고 곱한 값을 10으로 나눈다.

할당한 메모리를 해제하고 종료한다.

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int a, b, c, mul, i;
    int *numPtr = (int*)malloc(sizeof(int* 10);
    scanf("%d %d %d"&a, &b, &c);
    mul = a * b * c;
    
    while(mul > 0)
    {
        i = mul % 10;
        numPtr[i] = numPtr[i] + 1;
        mul /= 10;
    }
    
    for(int i=0; i<10; i++
    {
        printf("%d\n", numPtr[i]);
    }
    free(numPtr);
    return 0;
}
cs

과목의 개수를 저장할 변수, 평균을 저장할 변수, 점수 중 최댓값을 저장할 변수를 선언하고 점수들을 저장할 공간의 포인터를 선언하고, 과목의 개수를 입력받는다.

점수를 고치는 연산을 하며 소수가 될 수 있으므로 double 자료형 크기의 과목 개수만큼 저장할 수 있도록 메모리를 할당한다.

과목의 개수 만큼 반복하면서 최댓값을 찾고, 또 과목의 개수만큼 반복하면서 각 점수를 최댓값을 사용하여 문제에서 주어진 식으로 연산하여 저장한다.

연산한 점수들을 평균을 저장할 avg변수에 누적하여 저장하고, avg를 과목의 개수로 나누어 평균을 구한다.

메모리를 해제하고, 구한 평균을 출력한다.

정답 코드는 다음과 같다.

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
#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int subNum = 0;
    double avg = 0, big = 0;
    double *subArr;
    
    scanf("%d"&subNum);
    
    subArr = (double*)malloc(sizeof(double* subNum);
    
    for(int i = 0; i < subNum; i++)
    {
        int tmp;
        scanf("%d"&tmp);
        subArr[i] = double(tmp);
        
    }
 
    for(int i = 0; i < subNum; i++)
    {
        if(subArr[i] > big)
            big = subArr[i];
    }
 
    for(int i = 0; i < subNum; i++)
    {
        subArr[i] = subArr[i]/big*100;
    }
 
    for(int i = 0; i < subNum; i++)
    {
        avg += subArr[i];
    }
    avg = avg / subNum;
    
    free(subArr);
    printf("%lf", avg);
    return 0;
}
cs

크기가 9인 int형 배열을 선언하고, 그 배열을 포인터에 저장한다.

최댓값과 최댓값의 위치를 저장할 변수를 선언하고 0으로 초기화한다.

반복문으로 9번 반복하면서 포인터에 저장된 주솟값으로 배열에 값을 저장한다.

반복문으로 0부터 9까지 반복하면서 각 인덱스가 max보다 큰지 비교하고, 크면 max에 그 값을 저장하고, loc에 그 인덱스를 저장한다.

반복문이 끝나면 max값과 인덱스는 0부터 시작하기 때문에 loc + 1을 출력한다.

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
int main()
{
    int arr[9];
    int *numList = arr;
    int max = 0, loc = 0;
    
    for(int i = 0; i < 9; i++)
    {
        scanf("%d"&numList[i]);
    }
    for(int i = 0; i < 9; i++)
    {
        if(numList[i] > max)
        {
            max = numList[i];
            loc = i;
        }
    }
    
    printf("%d\n%d", max, loc+1);
    return 0;
}
cs

테스트 케이스의 개수를 입력받고, 개수 만큼 반복한다.

반복문 안에는 char 자료형 크기 80개를 저장할 수 있는 메모리 공간을 할당하고, 그 공간에 문자열을 입력받는다.

점수를 누적할 변수 sum과 점수를 다음 추가할 점수를 저장할 변수 score를 만들고 sum은 0으로, score는 1로 초기화 한다.

입력받은 문자열의 길이 만큼 반복하여 'O'이면 score의 값을 sum에 누적시키고 score의 값을 1 증가시킨다. 'X'면 score의 값을 1로 초기화 한다.

입력 받은 문자열의 개수는 string.h헤더파일의 strlen()함수를 이용하면 구할 수 있다.

정답은 다음 코드와 같다.

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
 
int main()
{
    int cnt;
    scanf("%d",&cnt);
    
    for(int i=0; i<cnt; i++)
    {
        char *result = malloc(sizeof(char* 80);
        int score = 1,sum = 0;
        scanf("%s",result);
        
        for(int j=0; j < strlen(result); j++)
        {
            if(result[j]=='O')
            {
                sum += score;
                score++;
            }
            else
            {
                score = 1;
            }
        }
        printf("%d\n",sum);
        free(result); 
    }
    
    return 0;
}
 
cs

+ Recent posts