Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
C/Array

C - 문자열, 다차원 배열(2차원, 문자열 배열)

by Toughie 2024. 2. 18.

 

C에서는 String 타입이 없다. (C++에는 표준 라이브러리로 존재한다.)

그렇기 때문에 '문자' '배열'을 통해서 문자열을 구현한다. (말그대로 문자열이다 ㅋㅋ)


NULL 종료 문자열

문자(char)는 1개지만, 문자열은 문자가 여러개이기 때문에 어디까지인지 알기 위해 종료 문자열 개념을 사용한다.

문자열의 끝에는 항상 널 문자 '\0' 가 들어간다. (아스키 코드상 0) _ 빈 공간은 0으로 초기화 된다는 말.

이는 널 문자의 자리를 꼭 마련해 줘야 한다는 뜻과 동일하다. 

종료 지점을 알려주지 않으면 메모리 주소 침범이 일어날 수 있기 때문이다. 

 

'a'는 char타입으로 1바이트 이지만, "a"는 1바이트 + 종료문자열 1바이트로 문자열 2바이트이다. 


문자열 입력 함수

표준 입력장치인 키보드로부터 문자열을 입력받는다.

C 표준 라이브러리 사용을 위해 <stdio.h>가 필요하다.

 

gets(주소)

엔터가 입력될 때 까지의 문자를 읽는다. 

엔터('\n')는 저장되지 않고, 문자열의 끝에 NULL이 추가되어 저장된다.

해당 메모리 주소에 문자열이 저장된다.(변수)

scanf("%s", 주소)

문자열을 읽어서 해당 주소의 변수에 저장한다.

공백문자를 포함할 수 없고 공백, 탭, 엔터로 문자열을 구분해서 저장한다.

(ex. kim toughie 입력후 엔터를 치면 kim만 저장된다)

 

공백문자까지 포함하고 싶으면 " %[^n]" 형식 지정자를 사용할 수 있다.

 

위 두 입력함수는 입력사이즈에 대한 컨트롤이 불가능 하기 때문에

더 안전한 입력(메모리 오버플로우 방지)을 지원하는 gets_s, scanf_s도 존재한다. 

다만 UNIX 계열의 컴파일러에서는 사용이 불가능하다.

scanf_s("%s", 주소, 입력 받을 문자열 사이즈);
gets_s(주소, 입력 받을 문자열 사이즈);

문자열 출력 함수

puts(주소)

주소에 저장된 문자열을 출력한다. 자동 개행. (new line)

printf("%s", 주소)

주소에 저장된 문자열을 출력한다. 자동 개행 X

 

문자열 입출력 예시코드

#include <stdio.h>

int main() {
    char name[20];
    char address[20];

    //input
    printf("이름을 입력해주세요: ");
    gets(name);
    printf("주소를 입력해주세요: ");
    scanf("%s",address);

    //output
    puts(name);
    printf("%s",address);
    return 0;
}

문자열 처리 함수

c에서는 다른 언어보다 문자열 처리에 있어 약간 더 신경써줘야 한다. (복사, 합치기, 길이, 비교)

다행히 라이브러리 함수가 있기 때문에 이 함수들의 활용법을 정리해 보자.

 

먼저 문자열 함수를 사용하기 위해 <string.h> 헤더 파일을 추가해준다. 

 

* c 프로그램의 실행 파일 생성 과정 (전처리, 컴파일, 링킹)

더보기

0. 소스코드 작성

main.c와 같은 파일 (개발자가 작성하는 코드)

 

1. 전처리 단계 (Preprocessing)

#include <string.h>와 같이 작성해주면 preprocessor가 헤더 파일을 찾아서 '소스 코드에 삽입'한다.

이 과정에서 헤더 파일의 내용이 '소스 코드에 복사'되기 때문에, 헤더 파일에 정의되어 있는 내용

(상수, 매크로, 함수 선언)을 사용 할 수 있게 된다.

main.i라는 임시 파일이 생성된다.

 

2. 컴파일 단계(Compile)

전처리된 소스 코드 파일이 '컴파일러에 의해 기계어로 번역'된다.

main.o 혹은 main.obj라는 오브젝트 파일이 생성된다.

 

3. 링킹 단계(Linking)

컴파일 단게를 거쳐 오브젝트 코드로 변환된 소스코드들을 링커가 하나의 실행 파일로 합친다. 

이 때 사용된 헤더 파일에 대한 실제 구현은 다른 소스 파일 혹은 라이브러리에 정의되어 있다. 

링커는 외부의 정의된 함수나 변수들의 '실제 위치를 찾아서 실행 파일에 연결'해주는 역할을 한다.

이를 통해 윈도우의 경우 .exe파일이 생성된다. 


strlen(주소)  

문자열의 길이를 반환한다. (NULL 문자 제외)

여기서 주소는 첨자없는 배열변수를 말한다. (문자열의 시작주소)

strcat(to 주소, from 주소)

문자열 concat 즉 합치기. 

from의 문자열이 to에 합쳐진다. (but 배열의 경계를 검사하지 않는다. _오버플로우 가능성 있음)

임시 변수와 조건문을 통해 오버플로우 체크를 해주는 것이 좋다.

strcpy(to 주소, from 주소) 

from의 문자열이 to에 복사된다.(copy) (but 배열의 경계를 검사하지 않는다. _오버플로우 가능성 있음)

원래 문자열은 배열이기 때문에 요소를 전부 순회하며 복사해야겠지만, 라이브러리 함수를 통해 간편하게 처리할 수 있다.

strcmp(str1 주소, str2 주소)

두 문자열을 비교해서 해당하는 정수를 반환한다.

 

0 : 두 문자열이 같다.

양수: str1이 str2보다 사전적으로 뒤에 있다. (아스키코드 값)

음수: str1이 str2보다 사전적으로 앞에 있다.

 

문자열 처리 함수 예시 코드

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

int main() {
    
    char name[10] = "toughie";
    char drink[10] = "coffee";

    char tmp[21];

    printf("배열의 크기: %d, 문자열의 길이: %d\n", sizeof(name), strlen(name)); //10, 7

    //문자열 복사
    strcpy(tmp, name);

    //문자열 합치기, 오버플로우 체크
    if ((strlen(tmp) + strlen(drink)) < sizeof(tmp)) {
    strcat(tmp, drink);
    }

    printf("%s\n", tmp); //toughiecoffee

    int cmp = strcmp(name,drink);
    printf("%d", cmp); // 17

    return 0;
}

다차원 배열 (Multidimensional Array)

배열의 요소 자체가 배열인 것을 다차원 배열이라고 한다. (Array of Array)

2차원 이상의 배열이 다차원 배열이다.


2차원 정수형 배열

// 2차원 배열 선언
type 배열 이름 [행 사이즈][열 사이즈]
int arr[2][3];

// 2차원 배열 초기화 방법
int arr[2][3] = {1,2,3,4,5,6};
int arr[2][3] = {{1,2,3},{4,5,6}};

//unsized의 경우 행의 사이즈만 생략할 수 있다.
int arr[][3] = {{1,2,3},{4,5,6}};

//빈 공간 초기화 - 남은 공간은 0으로 초기화 된다.
int arr[2][3] = {1, 2, 3};

 

2차원 배열은 행렬을 생각하면 된다. 

⭐️ 2차원 배열의 주소

첨자가 없는 배열 변수는 배열의 시작 주소라고 했다. (배열의 첫 요소의 시작 주소)

2차원 배열에서 배열변수명에 첨자가 하나만 붙은 경우 (ex. arr[0])는 배열의 '행'을 의미한다.

이는 '값'이 아니라 '행의 시작 주소'이다. 

즉 값을 제어하기 위해서는 꼭 행,열까지 첨자를 다 사용해야 한다. 

(ex. arr[0][0] -> 0행 0열의 값)

 

3차원 배열의 주소

arr[2][2][2]

첨자가 하나만 쓰인 경우 '면의 시작 주소' ->  arr[0] 첫 번째 면

첨자가 두개 쓰인 경우는 '행의 시작 주소' -> arr[0][1] 첫 번째 면 두 번째 행

첨자가 세개 쓰인 경우 비로소 열의 데이터에 접근할 수 있다. -> arr[0][1][1] 첫 번째 면 두 번째 행 두 번째 열

 

2차원 정수형 배열 예시 코드

#include <stdio.h>

int main() {

    int numbers[2][3] = {{1, 2, 3}, {4, 5, 6}};

    printf("정수 배열 전체 사이즈: %d, 한 행의 사이즈: %d, 열(원소)의 사이즈: %d\n",
     sizeof(numbers), sizeof(numbers[0]), sizeof(numbers[0][0])); // 24, 12, 4
    
    printf("\n배열의 시작 주소: %p, 0행 시작 주소: %p, 1행 2열 시작 주소: %p\n", numbers, &numbers[0], &numbers[1][2]);
    //배열의 각 요소를 순회하며 값 조작

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            numbers[i][j] = numbers[i][j] * 10;
        }
    }

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", numbers[i][j]);
        }
        printf("\n");
    }

    // 10 20 30
    // 40 50 60

    return 0;
}

2차원 문자열 배열 (문자열 테이블)

문자열 자체가 1차원 배열이기 때문에, 문자열이 요소인 배열은 2차원 문자열 배열이다.

정수형 2차원 배열과 마찬가지로 첨자가 없으면 문자열 배열의 시작 주소,

첨자가 1개면 행(문자열)의 시작 주소, 첨자가 2개면 문자열의 특정 문자이다. 

문자열의 경우 첨자 하나만 사용하는 경우가 많을 것이다. 

//문자열 배열 선언
char names[3][10];
//총 3행(3개)의 문자열이 들어있는 names라는 이름의 2차원 배열이다.
//각 행(문자열)은 최대 9자까지 들어갈 수 있다. (null 문자가 1자리를 차지하기 때문)

문자열 처리함수2

문자열을 처리할 수 있는 더 다양한 라이브러리 함수들을 알아보자.

문자열 처리 함수를 위해 <ctype.h> 헤더 파일을 추가해주자.

strstr(문자열1 주소, 문자열2 주소)

문자열 1번 안에 문자열2번이 있는 경우, 문자열2번의 시작주소를 반환한다. 

문자열이 포함되어 있지 않은 경우 NULL을 반환한다.

strupr(문자열 주소) ,  strlwr(문자열 주소)

문자열을 대/소문자로 변환하고 변환된 문자열의 시작주소를 반환한다. 

(원본 문자열을 변경하지 않는다) 

toupper(한문자), tolower(한문자)

문자를 대/소문자로 변환후 해당 아스키 코드 값을 반환한다.


문자열 처리함수 예시 코드

#include <stdio.h> //입출력
#include <string.h> //문자열 처리
#include <ctype.h> //문자열 처리

int main() {

    char animals[5][10] = {"Cat", "Dog", "Lion", "Tiger", "Panda"};
    char input[10];
    char tmp[10];
    char answer;
    int i;

    while (1) {
        printf("찾을 동물의 이름을 영어로 입력해 주세요: ");
        gets(input);

        for (i = 0; i < 5; i++) {
            strcpy(tmp,animals[i]); //원본 변경방지를 위해 임시값을 활용한다.
            if (strcmp(strupr(input), strupr(tmp)) == 0) { //대문자로 전부 변형 후 같으면?
                printf("%s은 %d번에 있습니다.\n", animals[i], i);
                break; //조기 조건 만족 시 루프 탈출
            }
        }
        //루프를 다 돌았음에도 발견하지 못했을 때 i == 5
        if (i == 5) {
            printf("%s은 찾을 수 없습니다.\n", input);
        }

        printf("\n다른 동물도 찾아볼까요?(y,n)\n");
        answer = getchar();
        if (toupper(answer) == 'N') {
            printf("프로그램을 종료합니다. \n");
            break;
        }
        //new line 을 입출력 버퍼에서 제거
        while (getchar() != '\n');
    }

    return 0;
}

문자열도 '배열'이기 때문에 처리함수의 파라미터도 포인터인 경우가 대부분이며, 

반복문에서 조회할 때도 '행의 주소'인지 '열의 값'인지 잘 구분하는 것이 중요해 보인다. 

(ex. %s 형식 지정자를 활용하는 경우는 문자열 행의 주소가 필요하듯)

'C > Array' 카테고리의 다른 글

C - Array 1차원 배열  (0) 2024.02.18