카테고리 보관물: C언어

C언어를 사용한 단일 링크드 리스트 (Single Linked List)

  링크드 리스트는 데이터 요소들을 노드라 부르는 객체로 유지하는 자료구조이다. 이 노드들은 각자 다음 노드를 가리키는 포인터를 갖고 있어, 연결 리스트를 형성한다.

링크드 리스트의 기본 개념

  링크드 리스트는 순차적 접근이 용이하며, 동적 크기를 가지기 때문에 유연하게 데이터를 삽입 및 삭제할 수 있다.

노드 구조체

  링크드 리스트의 노드는 데이터와 다음 노드를 가리키는 포인터로 구성된 구조체이다. 예제 코드를 통해 쉽게 이해할 수 있다.

struct Node {
    int data;
    struct Node* next;
};

메모리 레이아웃

  링크드 리스트의 메모리 레이아웃은 다음과 같은 형태로 시각화할 수 있다:

[Head] -> [Data|Next] -> [Data|Next] -> [Data|Next] -> NULL

링크드 리스트의 초기화

  링크드 리스트를 구현하기 위해 먼저 리스트를 초기화해야 한다. 초기화하는 방법은 매우 간단하다.

struct Node* head = NULL;

노드 추가

  새로운 노드를 링크드 리스트에 추가하는 과정 역시 중요하다. 우리는 리스트의 끝에 노드를 추가하는 예제를 살펴볼 것이다.

가장 끝에 노드 추가하기

  리스트 끝에 노드를 추가하는 함수는 다음과 같다:

void append(struct Node** head_ref, int new_data) {
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    struct Node* last = *head_ref;
    new_node->data = new_data;
    new_node->next = NULL;
    if (*head_ref == NULL) {
        *head_ref = new_node;
        return;
    }
    while (last->next != NULL)
        last = last->next;
    last->next = new_node;
}

노드 삭제

  링크드 리스트에서 노드를 삭제하는 방법도 중요하다. 특정 값을 가진 노드를 삭제하는 예제를 보자.

특정 값의 노드 삭제하기

  삭제 함수는 다음과 같다:

void deleteNode(struct Node** head_ref, int key) {
    struct Node* temp = *head_ref, *prev;
    if (temp != NULL && temp->data == key) {
        *head_ref = temp->next;
        free(temp);
        return;
    }
    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }
    if (temp == NULL) return;
    prev->next = temp->next;
    free(temp);
}

링크드 리스트의 검색

  마지막으로, 링크드 리스트에서 특정 값을 가진 노드를 검색하는 방법을 알아보자.

노드 검색하기

  검색 함수는 다음과 같다:

struct Node* search(struct Node* head, int key) {
    struct Node* current = head;
    while (current != NULL) {
        if (current->data == key)
            return current;
        current = current->next;
    }
    return NULL;
}

예제 코드 종합

  위의 예제를 종합하여 통합적인 예제 코드를 작성해보자:

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node* next;
};

void append(struct Node** head_ref, int new_data) {
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    struct Node* last = *head_ref;
    new_node->data = new_data;
    new_node->next = NULL;
    if (*head_ref == NULL) {
        *head_ref = new_node;
        return;
    }
    while (last->next != NULL)
        last = last->next;
    last->next = new_node;
}

void deleteNode(struct Node** head_ref, int key) {
    struct Node* temp = *head_ref, *prev;
    if (temp != NULL && temp->data == key) {
        *head_ref = temp->next;
        free(temp);
        return;
    }
    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }
    if (temp == NULL) return;
    prev->next = temp->next;
    free(temp);
}

struct Node* search(struct Node* head, int key) {
    struct Node* current = head;
    while (current != NULL) {
        if (current->data == key)
            return current;
        current = current->next;
    }
    return NULL;
}

int main() {
    struct Node* head = NULL;
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);

    printf("Linked List created: ");
    struct Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");

    deleteNode(&head, 2);
    printf("Linked List after deletion of 2: ");
    temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");

    struct Node* found = search(head, 3);
    if (found != NULL) printf("Element 3 found in the list.\n");
    else printf("Element 3 not found in the list.\n");

    return 0;
}

C언어 조건문 정리

프로그래밍에서 조건문은 특정 조건에 따라 프로그램의 흐름을 제어하는 데 필수적인 요소입니다.

1. 조건문 이란?

조건문의 개념

조건문은 프로그램이 특정 조건을 검토하고 그 조건이 참인지 거짓인지에 따라 다른 코드 블록을 실행하게 합니다. C언어에서는 if, else if, else, 그리고 switch문이 이에 해당합니다.

조건문의 종류

  • if 문: 주어진 조건이 참일 때에만 코드 블록을 실행합니다.
  • else if 문: 앞의 조건이 거짓일 때에만 새로운 조건을 검토합니다.
  • else 문: 아무런 조건도 참이 아닐 때 실행됩니다.
  • switch 문: 변수의 값에 따라 여러 가지 가능성 중에서 하나를 실행합니다.

2. if 문

if 문의 사용법

if 문은 가장 기본적인 조건문입니다. 아래는 그 예시입니다:

#include <stdio.h>

int main() {
    int x = 10;
    if (x > 5) {
        printf("x는 5보다 큽니다.\n");
    }
    return 0;
}

위 코드는 실행하면 x는 5보다 큽니다.를 출력합니다.

if 문의 흐름도

if 문의 처리 흐름은 다음과 같이 나타낼 수 있습니다:

          [조건]
            ↓
참이면 실행→[코드 블록 실행]
            ↓
          [다음 코드]

3. else if 문

else if 문의 사용법

if 문에 더하여 추가 조건을 통해 다양한 경우를 처리하려면 else if 문을 사용합니다. 사용 예시는 아래와 같습니다:

#include <stdio.h>

int main() {
    int x = 10;
    if (x > 15) {
        printf("x는 15보다 큽니다.\n");
    } else if (x > 5) {
        printf("x는 5보다 크고 15보다 작습니다.\n");
    } else {
        printf("x는 5보다 작습니다.\n");
    }
    return 0;
}

위 코드를 실행하면 x는 5보다 크고 15보다 작습니다.를 출력합니다.

else if 문의 흐름도

else if 문의 처리 흐름은 다음과 같이 나타냅니다:

          [조건1]
           ↓
참→[코드 블록1 실행]
      ↓
거짓→[조건2]
           ↓
참→[코드 블록2 실행]
      ↓
거짓→[기본 블록 실행]

4. switch 문

switch 문의 사용법

switch 문은 특정 식을 판단하고 그 값에 따라 여러 코드 블록 중 하나를 실행합니다. 사용 예는 아래와 같습니다:

#include <stdio.h>

int main() {
    int number = 2;
    switch (number) {
        case 1:
            printf("1입니다.\n");
            break;
        case 2:
            printf("2입니다.\n");
            break;
        default:
            printf("1도 2도 아닙니다.\n");
    }
    return 0;
}

위 코드는 실행하면 2입니다.를 출력합니다.

switch 문의 흐름도

switch 문의 처리 흐름은 아래로 나타낼 수 있습니다:

         [표현식]
            ↓
      [값 1] ↔ [코드 블록1]
            ↓
      [값 2] ↔ [코드 블록2]
            ↓
 [default] ↔ [기본 블록 실행]

5. 논리 연산자

논리 연산자 종류

조건문에서 주로 사용되는 논리 연산자로는 AND(&&), OR(||), 그리고 NOT(!)이 있습니다.

  • AND (&&): 두 조건이 모두 참일 때에만 결과가 참이 됩니다.
  • OR (||): 두 조건 중 하나라도 참이면 결과가 참이 됩니다.
  • NOT (!): 원래의 조건 값과 반대되는 값을 가집니다.

논리 연산자의 사용법

다음은 논리 연산자를 사용한 예제입니다:

#include <stdio.h>

int main() {
    int a = 10, b = 20;
    if (a < 15 && b > 15) {
        printf("a는 15보다 작고 b는 15보다 큽니다.\n");
    }
    if (a < 5 || b > 15) {
        printf("a는 5보다 작거나 b는 15보다 큽니다.\n");
    }
    if (!(a > 15)) {
        printf("a는 15보다 크지 않습니다.\n");
    }
    return 0;
}

위 코드를 실행하면 다음과 같이 출력합니다:

 a는 15보다 작고 b는 15보다 큽니다.
 a는 5보다 작거나 b는 15보다 큽니다.
 a는 15보다 크지 않습니다.

C언어 포인터 정리

C언어의 포인터는 프로그래밍 세계에서 매우 중요한 역할을 합니다. 포인터를 제대로 이해하는 것이 고급 프로그래밍에 필수적입니다. 이 문서에서는 포인터의 기본 개념부터 다양한 활용법까지 정리하겠습니다.

포인터란 무엇인가

포인터의 정의

포인터는 메모리 주소를 저장하는 변수입니다. 변수의 메모리 주소를 가리킬 수 있으며, 간접적으로 변수 값을 조작할 수 있습니다.

int a = 10;
int *p = &a;
printf("a의 값: %d", *p); // 출력: a의 값: 10

포인터의 유형

포인터는 저장하는 데이터 타입에 따라 여러 유형이 있습니다. 예를 들어, int 포인터, float 포인터, char 포인터 등이 있습니다.

포인터의 선언 및 초기화

포인터 변수 선언

포인터 변수를 선언하려면 데이터 타입 뒤에 '*'을 사용합니다. 예를 들어, int 유형의 포인터는 int *p; 형식으로 선언합니다.

포인터의 초기화

포인터를 초기화하려면 변수의 주소를 할당합니다. 주소를 얻기 위해서는 '&' 연산자를 사용합니다.

int a = 5;
int *p = &a;

포인터와 배열

배열과 포인터의 관계

배열의 이름은 배열의 첫 번째 요소의 주소를 가리키는 포인터입니다. 따라서 배열 이름을 포인터로 사용할 수 있습니다.

int arr[3] = {1, 2, 3};
int *p = arr;
printf("첫 번째 값: %d", *p); // 출력: 첫 번째 값: 1

포인터와 배열의 접근

포인터를 사용하여 배열 요소에 접근할 수 있습니다.

int arr[3] = {4, 5, 6};
int *p = arr;
for (int i = 0; i < 3; i++) {
    printf("값: %d", *(p + i));
}

포인터와 함수

함수의 인수로서 포인터

포인터를 함수의 인수로 사용할 수 있습니다. 포인터를 사용하면 변수 값을 직접 수정할 수 있습니다.

void updateValue(int *p) {
    *p = 20;
}

int main() {
    int a = 10;
    updateValue(&a);
    printf("업데이트된 값: %d", a); // 출력: 업데이트된 값: 20
    return 0;
}

함수 포인터

함수 포인터는 함수의 주소를 가리킵니다. 이를 통해 함수도 변수처럼 전달할 수 있습니다.

#include <stdio.h>

void hello() {
    printf("Hello, World!\n");
}

int main() {
    void (*funcPtr)() = hello;
    funcPtr(); // 출력: Hello, World!
    return 0;
}

포인터 연산

포인터와 산술 연산

포인터는 덧셈과 뺄셈 같은 산술 연산을 수행할 수 있습니다. 포인터에 숫자를 더하거나 빼면, 해당 타입의 크기만큼 이동합니다.

int arr[3] = {7, 8, 9};
int *p = arr;

p += 2;
printf("세 번째 값: %d", *p); // 출력: 세 번째 값: 9

포인터 비교

포인터는 비교 연산을 사용할 수 있습니다. 같은 타입의 포인터 간에 크기를 비교할 수 있습니다.

int a = 10;
int b = 20;
int *p1 = &a;
int *p2 = &b;

if (p1 < p2) {
    printf("p1이 p2보다 작은 주소를 가리킵니다.\n");
} else {
    printf("p2가 p1보다 같거나 작은 주소를 가리킵니다.\n");
}

다중 포인터

이중 포인터

이중 포인터는 다른 포인터를 가리키는 포인터입니다. 보통 다차원 배열이나 동적 메모리 할당에 사용합니다.

int a = 10;
int *p1 = &a;
int **p2 = &p1;

printf("a의 값: %d", **p2); // 출력: a의 값: 10

다중 포인터의 활용

다중 포인터는 복잡한 데이터 구조를 처리할 때 유용합니다. 동적 메모리 할당이나 다차원 배열을 관리할 때 자주 사용됩니다.

int **arr;
arr = (int**)malloc(3 * sizeof(int*));
for (int i = 0; i < 3; i++) {
    arr[i] = (int*)malloc(3 * sizeof(int));
}

// 메모리 해제
for (int i = 0; i < 3; i++) {
    free(arr[i]);
}
free(arr);