# C언어 (2)

## 1\. 입력함수

### 1-1. scanf

`printf` 가 화면에 출력하는 함수라면, `scanf` 는 사용자로부터 값을 입력받는 함수다.

```c
int age;
printf("나이를 입력하세요: ");
scanf("%d", &age);
printf("입력한 나이: %d\n", age);
```

`scanf` 의 첫 번째 인자는 서식 문자(`%d`, `%f`, `%c` 등)이고, 두 번째 인자부터는 입력값을 저장할 변수의 **주소** 를 넘긨다. 여러 값을 한 번에 입력받을 수도 있다.

```c
int x, y;
scanf("%d %d", &x, &y);
```

이때 사용자는 값 사이에 공백이나 엔터를 넣어서 구분하면 된다.

주의할 점이 하나 있다. 문자(`%c`)를 입력받을 때는 이전 입력에서 남은 개행문자(`\n`)가 버퍼에 남아있어서 의도하지 않은 값이 들어갈 수 있다. 이 경우 `scanf` 의 서식 문자 앞에 공백을 넣어주면 해결된다.

```c
char ch;
scanf(" %c", &ch);  // %c 앞에 공백 추가
```

### 1-2. & 를 사용하는 이유

`scanf` 에서 변수 앞에 `&` 를 붙이는 이유는 **메모리 주소를 넘겨야 하기 때문** 이다.

`scanf` 는 사용자가 입력한 값을 특정 메모리 공간에 써넣는 함수다. 그러려면 "어디에 쓸지"를 알아야 하고, 그 정보가 바로 변수의 주소다. `&` 연산자는 변수의 메모리 주소를 반환한다.

```c
int x;
scanf("%d", &x);   // x의 주소를 넘겨서 그 위치에 값을 저장
```

만약 `&` 를 빼면 `scanf` 는 `x` 에 들어있는 **값** 을 주소로 해석하려 한다. 초기화되지 않은 변수라면 쓰레기값이 주소로 쓰이면서 프로그램이 비정상 종료될 수 있다.

단, 배열 이름은 그 자체가 배열 첫 번째 원소의 주소이므로 `&` 를 붙이지 않는다.

```c
char name[20];
scanf("%s", name);  // 배열 이름 자체가 주소이므로 & 불필요
```

* * *

## 2\. 연산자

### 2-1. 산술 연산자

사칙연산과 나머지를 구하는 연산자다.

`+` 는 덧셈, `-` 는 뺄셈, `*` 는 곱셈, `/` 는 나눗셈, `%` 는 나머지를 구한다.

```c
int a = 10, b = 3;
printf("%d\n", a + b);   // 13
printf("%d\n", a - b);   // 7
printf("%d\n", a * b);   // 30
printf("%d\n", a / b);   // 3 (정수끼리 나누면 소수점 버림)
printf("%d\n", a % b);   // 1
```

여기서 주의할 점은 **정수끼리의 나눗셈** 이다. `10 / 3` 의 결과는 `3.333...` 이 아니라 `3` 이다. 소수점 이하가 버려지기 때문이다. 실수 결과를 얻으려면 피연산자 중 하나를 실수형으로 만들어야 한다.

```c
printf("%f\n", 10.0 / 3);    // 3.333333
printf("%f\n", (double)10 / 3);  // 캐스팅으로도 가능
```

### 2-2. 대입 연산자

변수에 값을 저장하는 연산자다. `=` 가 기본이고, 산술 연산과 결합한 복합 대입 연산자도 있다.

```c
int x = 10;
x += 5;   // x = x + 5  → 15
x -= 3;   // x = x - 3  → 12
x *= 2;   // x = x * 2  → 24
x /= 4;   // x = x / 4  → 6
x %= 4;   // x = x % 4  → 2
```

`x += 5` 는 `x = x + 5` 와 같다. 코드가 짧아지고 의도도 더 명확해진다.

### 2-3. 증감 연산자

변수의 값을 1 증가시키거나 1 감소시키는 연산자다. `++` 가 증가, `--` 가 감소다.

```c
int a = 5;
a++;   // a = 6
a--;   // a = 5
```

중요한 건 **전위(prefix)와 후위(postfix)의 차이** 다.

```c
int a = 5;
int b = a++;  // b = 5, a = 6 (먼저 대입하고 나중에 증가)
int c = ++a;  // c = 7, a = 7 (먼저 증가하고 나중에 대입)
```

`a++` 는 현재 값을 먼저 사용한 뒤 증가시키고, `++a` 는 먼저 증가시킨 뒤 그 값을 사용한다. 단독으로 `a++;` 처럼 쓸 때는 차이가 없지만, 다른 연산과 함께 쓸 때는 결과가 달라지므로 주의해야 한다.

### 2-4. 관계 연산자

두 값을 비교해서 참(`1`) 또는 거짓(`0`)을 반환한다. 조건문과 반복문에서 핵심적으로 사용된다.

`==` 는 같은지, `!=` 는 다른지, `>` 는 큰지, `<` 는 작은지, `>=` 는 크거나 같은지, `<=` 는 작거나 같은지 비교한다.

```c
int a = 5, b = 3;
printf("%d\n", a == b);  // 0 (거짓)
printf("%d\n", a != b);  // 1 (참)
printf("%d\n", a > b);   // 1 (참)
```

C언어에는 `boolean` 타입이 기본적으로 없다. 대신 `0` 이면 거짓, **0이 아닌 모든 값** 이면 참으로 취급한다. C99부터 `<stdbool.h>` 를 포함하면 `true`, `false` 를 사용할 수 있긴 하다.

흔한 실수로 `==`(비교)와 `=`(대입)를 혼동하는 경우가 있다. `if (a = 5)` 는 `a` 에 5를 대입하고 항상 참이 되므로, `if (a == 5)` 로 써야 한다.

### 2-5. 논리 연산자

여러 조건을 조합할 때 사용한다.

`&&` 는 논리 AND(둘 다 참일 때 참), `||` 는 논리 OR(하나라도 참이면 참), `!` 는 논리 NOT(참과 거짓을 반전)이다.

```c
int age = 25;
int score = 80;

if (age >= 20 && score >= 70) {
    printf("합격\n");
}

if (age < 18 || score < 50) {
    printf("불합격\n");
}

if (!0) {
    printf("0의 NOT은 참\n");
}
```

**단축 평가(Short-circuit evaluation)** 라는 특성이 있다. `&&` 에서 왼쪽이 거짓이면 오른쪽은 평가하지 않는다. 어차피 결과가 거짓이기 때문이다. 마찬가지로 `||` 에서 왼쪽이 참이면 오른쪽은 평가하지 않는다. JavaScript의 `&&`, `||` 와 동일한 동작이다.

* * *

## 3\. 분기문

프로그램의 실행 흐름을 조건에 따라 나누는 것이 분기문이다.

### 3-1. if 문

조건이 참일 때만 특정 코드를 실행한다.

```c
int score = 85;

if (score >= 90) {
    printf("A학점\n");
}
```

조건식이 0이 아닌 값을 반환하면 참으로 판단하고 블록 안의 코드를 실행한다. 실행할 문장이 한 줄이면 중괄호를 생략할 수 있지만, 가독성과 버그 방지를 위해 항상 붙이는 것이 좋다.

### 3-2. else 문과 else if

`if` 의 조건이 거짓일 때 실행할 코드를 지정한다. `else if` 로 여러 조건을 순차적으로 검사할 수도 있다.

```c
int score = 75;

if (score >= 90) {
    printf("A학점\n");
} else if (score >= 80) {
    printf("B학점\n");
} else if (score >= 70) {
    printf("C학점\n");
} else {
    printf("F학점\n");
}
```

`else if` 는 위에서부터 순서대로 검사하고, 처음으로 참인 조건을 만나면 해당 블록만 실행한 뒤 나머지는 건너뛴다. 어떤 조건도 만족하지 않으면 `else` 블록이 실행된다.

### 3-3. switch 문

하나의 변수 값에 따라 여러 갈래로 분기할 때 사용한다. `if-else if` 체인이 길어질 때 더 깔끔하게 쓸 수 있다.

```c
int menu = 2;

switch (menu) {
    case 1:
        printf("짜장면\n");
        break;
    case 2:
        printf("짬뽕\n");
        break;
    case 3:
        printf("볶음밥\n");
        break;
    default:
        printf("메뉴에 없습니다\n");
        break;
}
```

`switch` 는 괄호 안의 값과 일치하는 `case` 로 점프한다. `break` **를 빼먹으면** 일치하는 `case` 이후의 모든 코드가 연달아 실행되는 **fall-through** 가 발생하므로 주의해야 한다.

```c
// break 없으면 이렇게 됨
switch (1) {
    case 1:
        printf("1\n");   // 실행됨
    case 2:
        printf("2\n");   // 이것도 실행됨 (fall-through)
    case 3:
        printf("3\n");   // 이것도 실행됨
}
```

`default` 는 어떤 `case` 에도 해당하지 않을 때 실행되며, `if-else` 에서의 `else` 와 같은 역할이다.

`switch` 는 정수형과 문자형(`char`)에만 사용할 수 있다. 실수형이나 문자열은 사용할 수 없으므로 그런 경우에는 `if-else if` 를 써야 한다.

* * *

## 4\. 반복문

같은 코드를 여러 번 실행해야 할 때 사용한다.

### 4-1. while 문

조건이 참인 동안 계속 반복한다.

```c
int i = 0;
while (i < 5) {
    printf("%d\n", i);
    i++;
}
// 출력: 0, 1, 2, 3, 4
```

동작 순서는 **조건 검사 → 실행 → 조건 검사 → 실행 → ...** 이다. 조건이 처음부터 거짓이면 한 번도 실행되지 않는다.

무한 루프를 만들 때는 `while (1)` 을 사용한다. 서버 프로그램이나 게임 루프처럼 프로그램이 계속 돌아야 하는 경우에 쓰인다.

```c
while (1) {
    // 무한 반복
    // break로 탈출
}
```

### 4-2. do~while 문

`while` 과 거의 같지만, **최소 한 번은 무조건 실행된다** 는 차이가 있다. 조건 검사가 뒤에 오기 때문이다.

```c
int num;
do {
    printf("양수를 입력하세요: ");
    scanf("%d", &num);
} while (num <= 0);
```

동작 순서는 **실행 → 조건 검사 → 실행 → 조건 검사 → ...** 이다. 위 예시처럼 일단 입력을 받고 나서 유효한지 검사해야 하는 경우에 유용하다. 조건이 처음부터 거짓이라도 블록 안의 코드가 한 번은 실행된다는 점이 `while` 과의 핵심 차이다.

`do~while` 문 끝에는 반드시 세미콜론(`;`)을 붙여야 한다.

### 4-3. for 문

반복 횟수가 명확할 때 가장 많이 사용하는 반복문이다. 초기식, 조건식, 증감식을 한 줄에 모아서 쓴다.

```c
for (int i = 0; i < 5; i++) {
    printf("%d\n", i);
}
// 출력: 0, 1, 2, 3, 4
```

실행 순서는 **초기식 → 조건 검사 → 실행 → 증감식 → 조건 검사 → 실행 → 증감식 → ...** 이다. 초기식은 맨 처음 한 번만 실행된다.

중첩 `for` 문으로 2차원 반복도 가능하다.

```c
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        printf("(%d, %d) ", i, j);
    }
    printf("\n");
}
```

`for`문의 세 가지 구성요소(초기식, 조건식, 증감식)는 전부 생략할 수 있다. `for (;;)` 는 `while (1)` 과 같은 무한 루프다.

### 4-4. break

반복문을 즉시 탈출한다. 가장 가까운 반복문 하나만 빠져나온다.

```c
for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;
    }
    printf("%d\n", i);
}
// 출력: 0, 1, 2, 3, 4 (5에서 탈출)
```

중첩 반복문에서 바깥쪽 루프까지 한 번에 빠져나오고 싶다면, `break` 만으로는 안 된다. 플래그 변수를 사용하거나, 해당 로직을 함수로 분리해서 `return` 으로 탈출하는 방법이 있다.

```c
int found = 0;
for (int i = 0; i < 10 && !found; i++) {
    for (int j = 0; j < 10; j++) {
        if (arr[i][j] == target) {
            found = 1;
            break;  // 안쪽 루프 탈출
        }
    }
    // found가 1이면 바깥 루프 조건도 거짓 → 탈출
}
```

### 4-5. continue

현재 반복을 건너뛰고 다음 반복으로 넘어간다. `break` 와 달리 반복문 자체를 탈출하지는 않는다.

```c
for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue;  // 짝수면 건너뜀
    }
    printf("%d\n", i);
}
// 출력: 1, 3, 5, 7, 9
```

`for` 문에서 `continue` 를 만나면 증감식으로 점프한다. `while` 문에서는 조건식으로 점프한다. `while` 에서 `continue` 를 쓸 때 증감식을 `continue` 아래에 둔 경우, 증감이 실행되지 않아 무한 루프에 빠질 수 있으므로 주의해야 한다.

```c
// 위험한 패턴
int i = 0;
while (i < 10) {
    if (i == 5) {
        continue;  // i++에 도달 못 함 → 무한 루프
    }
    printf("%d\n", i);
    i++;
}

// 안전한 패턴
int i = 0;
while (i < 10) {
    i++;
    if (i == 5) {
        continue;  // 문제없음
    }
    printf("%d\n", i);
}
```

* * *

## 마무리

입력함수로 데이터를 받고, 연산자로 데이터를 처리하고, 분기문으로 흐름을 나누고, 반복문으로 반복 작업을 수행한다. 이 네 가지가 C언어 프로그래밍의 기본 뼈대다. 특히 `&` 의 의미, 전위/후위 증감의 차이, `break` 와 `continue` 의 동작 방식은 헷갈리기 쉬우니 직접 코드를 돌려보면서 익히는 것이 좋다.
