C › C언어의 문법 두 번째(Week4_Day3)
어제에 이어서 C의 문법에 대해 이어 공부하였다.
if 문
if문
지금까지의 코드들은 위에서부터 아래로 순서대로 실행되었었다.
허나 조건문에서는 조건에 따라 실행되는 것이 달라 진다.
코드
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main(){
int i;
printf("입력하고 싶은 숫자를 입력하세요! : ");
scanf("%d", &i);
if(i == 7) {
printf("당신은 행운의 숫자 7 을 입력했습니다");
}
return 0;
}
파이썬과 다른 부분만 짚고 넘어가겠다.
사실관계연산자는 연산 후에 참이면 1, 거짓이면 0을 나타낸다.
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main() {
double i, j;
printf("나누고 싶은 두 정수를 입력하세요 : ");
scanf("%lf %lf", &i, &j);
printf("%f 를 %f 로 나눈 결과는 : %f \n", i, j, i / j);
return 0;
}
그냥 if 문으로 조건 처리하면 0으로 나눌 경우를 거를 수 있다는 걸 보여주기 위한 예문인데 파이썬과 조금 차이가 있는 부분이 있었다.
파이썬에는 0으로 나눗셈을 하면 division by 0라고 에러를 뱉는다.
C에서는 int형일 경우 마찬가지로 에러를 뱉지만, double형일 경우 inf를 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
int num;
printf("아무 숫자나 입력해 보세요 : ");
scanf("%d", &num);
if (num == 7) {
printf("행운의 숫자 7 이군요!\n");
} else if (num == 4) {
printf("죽음의 숫자 4 인가요 ;;; \n");
} else {
printf("그냥 평범한 숫자 %d \n", num);
}
return 0;
}
형태가 살짝 다르니 그냥 한 번 읽어만 보면 될 것 같다.
“>” “<” “>=” “<=” “==” “!=” 등의 비교연산자는 그대로 사용가능하다.
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>
int main() {
int num;
printf("아무 숫자나 입력해 보세요 : ");
scanf("%d", &num);
if (num == 7) {
printf("a 행운의 숫자 7 이군요!\n");
} else if (num == 7) {
printf("b 행운의 숫자 7 이군요! \n");
}
// 비교
if (num == 7) {
printf("c 행운의 숫자 7 이군요!\n");
}
if (num == 7) {
printf("d 행운의 숫자 7 이군요! \n");
}
return 0;
}
여기서 하나 볼만한게 있는 것 같은데 if-else와 if if의 차이를 보여주고 있다.
모두 7을 입력했을 때 조건을 만족할 수 있지만, if-else의 경우 else문은 if조건이 아닐때~이기 때문에 b는 출력되지 않는다.
1
2
3
4
5
6
7
#include <stdio.h>
int main() {
int a = 31, b = 15;
printf("a & b = %d\n", a & b);
printf("a && b = %d\n", a && b);
}
여기서는 비교연산자 &, &&의 차이를 보여주고 있다.
&의 경우 비트단위로 31과 15를 비교하지만, &&은 31이라는 참, 15 라는 참을 비교하기 때문에 1이 나오게 된다.
파이썬의 AND와 비슷하다고 보면 될 것 같다.
또한 0<= n < N의 표현은 불가능하다.
위 예시에 있었듯이 n >= 0 and n < N 으로 표현해야한다.
for문
파이썬의 for문과 형태만 살짝 다르다.
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
int i, sum = 0;
for (i = 0; i < 20; ++i) {
sum = sum + i;
}
printf("1 부터 19 까지의 합 : %d", sum);
return 0;
}
여기서 볼 수 있는 두가지는 먼저 sum += i라는 표현은 가능하다.
두 번째로 int i, sum = 0;에서 혹시 i = 0, sum = 0으로 초기화 되나 싶었지만, i는 int자료형만 주어지고, sum 만 0으로 초기화 된다. 이후 for 문 내에서 i = 0으로 초기화 되어 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main(){
int usranswer;
printf("컴퓨터가 생각한 숫자를 맞추어 보세요! \n");
for(;;) {
scanf("%d", &usranswer);
if(usranswer == 3) {
printf("맞춤! \n");
break;
} else {
printf("틀렸어요! \n");
}
}
return 0;
}
여기서는 처음 보는 문법을 봤다.
for문안에 조건을 명시하지 않고 사용하여 python while True:와 같은 효과를 내는 것이였다.
while문과 마찬가지로 break문으로 종료조건을 주지 않으면 무한 루프에 갇히게 된다.
break : break아래로는 실행되지 않는다.
continue : for문을 탈출하지 않고 진행하던 부분을 그 부분에서 끝내고 바로 다음을 진행한다.
While문
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main() {
int i = 1, sum = 0;
while (i <= 100) {
sum += i;
i++;
}
printf("1 부터 100 까지의 합 : %d \n", sum);
return 0;
}
while 문에서는 특이점은 없었다.
while True를 사용하고 싶으면 0이 아닌 값은 true로 취급되기 때문에 조건 대신에 넣으면 된다.
do-while 문
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main() {
int i = 1, sum = 0;
do {
sum += i;
i++;
} while (i < 1);
printf(" sum : %d \n", sum);
return 0;
}
기본적으로 while문이 뒤집어 졌다고 생각하면 편하다. 코드블럭이 위로오고, 조건검사부가 밑에있기 때문에 while 문과의 가장 큰 차이점은 조건을 통과하던 못하던 처음의 한 번은 무조건 실행 된다는 점이다.
switch 문
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
#include <stdio.h>
int main() {
int input;
printf("마이펫 \n");
printf("무엇을 하실 것인지 입력하세요 \n");
printf("1. 밥주기 \n");
printf("2. 씻기기 \n");
printf("3. 재우기 \n");
scanf("%d", &input);
if (input == 1) {
printf("아이 맛있어 \n");
} else if (input == 2) {
printf("아이 시원해 \n");
} else if (input == 3) {
printf("zzz \n");
} else {
printf("무슨 명령인지 못 알아 듣겠어. 왈왈 \n");
}
return 0;
}
여기서 볼 수 있듯이 명령을 더 추가하려면 else if가 계속 해서 늘어나게 된다.
이를 위해 사용하는게 switch문이다.
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
#include <stdio.h>
int main() {
int input;
printf("마이펫 업그레이드\n");
printf("무엇을 하실 것인지 입력하세요 \n");
printf("1. 밥주기 \n");
printf("2. 씻기기 \n");
printf("3. 재우기 \n");
scanf("%d", &input);
switch (input) {
case 1:
printf("아이 맛있어 \n");
break;
case 2:
printf("아이 시원해 \n");
break;
case 3:
printf("zzz \n");
break;
default:
printf("무슨 명령인지 못 알아 듣겠어. 왈왈 \n");
break;
}
return 0;
}
기본적으로 위의 if문과 같은 결과가 나오지만 코드가 훨씬 가독성이 좋다.
주의해야 할 점은 매 case마다 break를 꼭 걸어줘야 한다는 것이다.
만약 case1에서 break를 빼먹게 된다면 아이 맛있어 와 아이 시원해가 모두 출력 될 것이다.
즉 break를 써주지 않으면 다음 케이스까지 넘어간다는 뜻이다.
만약 모든 case에 해당하지 않는다면 default로 들어가 코드블럭이 실행 된다.
또 주의해야 할 점은 input으로 사용 될 변수는 항상 정수 데이터를 보관해야 한다는 것이다.
즉 char, short, int, long을 사용할 수 있다.
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>
int main() {
char input;
printf("(소문자) 알파벳 읽기\n");
printf("알파벳 : ");
scanf("%c", &input);
switch (input) {
case 'a':
printf("에이 \n");
break;
case 'b':
printf("비 \n");
break;
case 'c':
printf("씨 \n");
break;
default:
printf("죄송해요.. 머리가 나빠서 못 읽어요 \n");
break;
}
return 0;
}
그렇다면 여기서는 왜 문자를 받는가?? 그 이유는 어제 공부했듯이 컴퓨터는 문자인지 숫자인지 구분을 하지 못하기 때문이다.
if문과 switch문의 작동과정은 csapp을 읽으면서도 잠깐 정리했었는데 간략하게 적어보자면,
if문의 경우에는 각 경우마다 값을 비교하기 때문에 최악의 경우에는 모든 case에 대해 값을 비교하는 CMP연산을 하게 된다.
반면 switch의 경우 jump를 하기 때문에, switch문이 실행 되기 전에 컴파일러에서 jump table의 구조를 만들고 어셈블러가 값을 채워넣는다.
이때 이렇기 때문에 input값에 변수가 들어가면 jump table에 어떤 값을 넣어야 할 지 알 수가 없어서 넣으면 안되는 것이다.
jump table에는 각 케이스별 코드블럭이 어디에 저장 되어있는지를 가리키는 포인터 배열이나 메모리 주소 배열이 저장되고, 위에서 부터 조건을 평가하면서 코드블럭에 접근하는 것이 아니라 배열의 인덱싱을 하듯 메모리 주소를 가지고 직접적으로 jump를 하기 때문에 훨씬 효율적이다.
형변환(캐스팅)
변수에 값을 대입하다보면 다른 자료형 간에 대입을 하게 될 경우가 생긴다.
이 때 경고가 발생하지 않도록 대입을 하기 위해서 서로의 형을 맞추는 것을 형변환 이라고 한다.
형태는 (바꾸려는 형) 변수이름 으로 사용하면 되지만 영구적으로 변수의 자료형이 바뀌는 것이 아닌, 대입하는 순간에만 일시적으로 바꾸는 것이다.
배열
1차원 배열
여러 개의 변수를 한꺼번에 정의하는 법으로 배열(Array)라고 부른다.
1
2
3
4
5
6
7
#include <stdio.h>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("Array 3 번째 원소 : %d \n", arr[2]);
return 0;
}
배열은 특정한 타입의 변수들의 집합이며 선언할 때는
1
(배열의 타입) (배열의 이름)[원소 개수];)
이렇게 사용하면 된다.
배열을 정의 할 때 위 코드에서 처럼 ={} 를 하고 안에 원소를 넣어주면 순차적으로 들어간다.
이는 처음 배열을 선언할 때만 가능하다는 점에 주의 하자.
또한 이렇게 배열을 선언 할 때는 원소의 개수를 명시하지 않고 [] 로 두어도 뒤 {} 속의 원소의 수 만큼 알아서 컴파일러가 정의해 준다.
c의 배열은 단순히 해당 타입의 변수들의 나열이기 때문에, 배열의 크기에 대한 정보를 가지고 있지 않는다.
인덱싱을 통해 arr[3]으로 접근하면 아 처음으로 부터 4번째 위치에 있겠구나 할 뿐이다. 그래서 인덱스가 초과되더라도 접근이 가능해져 버린다.
이는 원하지 않는 값을 참조할 뿐더러 해당 값을 변조할 수도 있기 때문에 조심해야 한다.
만약
1
int arr[3] = {1}
라고 한다면 자동적으로 컴파일러는 나머지 2개의 원소를 0으로 지정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main() {
int arr[5]; // 성적을 저장하는 배열
int i, ave = 0;
for (i = 0; i < 5; i++) // 학생들의 성적을 입력받는 부분
{
printf("%d 번째 학생의 성적은? ", i + 1);
scanf("%d", &arr[i]);
}
for (i = 0; i < 5; i++) // 전체 학생 성적의 합을 구하는 부분
{
ave = ave + arr[i];
}
// 평균이므로 5 로 나누어 준다.
printf("전체 학생의 평균은 : %d \n", ave / 5);
return 0;
}
배열의 원소에 접근할 때는 arr[i]인 것, prinf에서는 &(arr[i]) 가 아니라 &arr[i]로 해도 충분한 것.
또한
1
2
int total = 3;
int arr[total];
이런식으로 해서 배열의 크기를 할당하는 것은 금지되어 있다.
이후 동적 할당으로 해결이 가능하다고 한다.
2차원배열
형태는
1
2
(배열의 형) (배열의 이름)[?][?];
int arr[2][3];
이런 식이다. 이는 각 원소 2개가 3개의 원소를 가짐을 의미한다. 이 때
1
2
int arr[2][3] = 1, 4;
// | | 배포오류때문에 추가한 {}이니까 빼고 생각
이건 바로 이해가 가지만 이 뿐만 아니라
1
int arr[2][3] = {1, 2, 3, 4, 5, 6};
이것도 같은 2차원배열이라는 것을 이해해야 한다.
메모리에서는 2차원배열이나 1차원배열이나 모두 1차원에 존재하고, 2차원배열도 메모리 상에서 연속적으로 쭈욱 존재한다.
앞에서
1
arr[] = {1, 2, 3}
도 유효하다고 했었다.
1
2
int arr[][3] = 4, 7;
// | | 배포오류때문에 추가한 {}이니까 빼고 생각
그럼 이것도 유효하다는 것을 알 수 있다.
1
2
int arr[][2] = 1, 3, 5, 7};
//| | | | | | | |배포오류때문에 추가한 {}이니까 빼고 생각
이 또한 유효하며 arr[3][1] = 0 이 된다.
하지만 주의할 점은
1
2
int arr[2][] = 4, 7;
// | | | |배포오류때문에 추가한 {}이니까 빼고 생각
이건 불가능하다. 다차원 배열의 경우 맨 앞의 크기를 제외한 나머지 크기들은 정확하게 지정해주어야 한다.
상수
1
2
3
4
5
6
7
#include <stdio.h>
int main() {
const int a = 3;
printf("%d", a);
return 0;
}
변수와 다르게 상수는 처음 정의 시 그 값이 바로 주어지고, 영원히 바뀌지 않는다.
형태는
1
const (상수의 타입) (상수의 이름) = (상수의 값);
이다.
함수
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int return_func() {
printf("난 실행된다 \n");
return 0;
printf("난 안돼 ㅠㅠ \n");
}
int main() {
return_func();
return 0;
}
함수 호출하는 것은 파이썬과 거의 비슷하고, 선언을 할 때 형식만 눈에 익히면 될 것 같다.
1
2
3
4
5
6
int main() {
int a = ret();
printf("ret() 함수의 반환값 : %d \n", a);
return 0;
}
이런식으로 변수에 함수의 리턴 값을 저장할 수도 있고,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int magicbox() {
i += 4;
return 0;
}
int main() {
int i;
printf("마술 상자에 집어넣을 값 : ");
scanf("%d", &i);
magicbox();
printf("마술 상자를 지나면 : %d \n", i);
return 0;
}
이걸 보고서 기억해야 할 점은 호출 된 함수는 호출한 함수의 어떠한 정보도 알지 못한다는 점이다.
그렇기 때문에 위의 코드는 i가 정의 되지 않았다는 에러를 반환하게 된다.
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int slave(int master_money) {
master_money += 10000;
return master_money;
}
int main() {
int my_money = 100000;
printf("2009.12.12 재산 : $%d \n", slave(my_money));
return 0;
}
그렇기 때문에 매개변수로서 전달하는 것은 파이썬과 동일하다.
출처 : 씹어먹는 C언어