블로그 이미지

Kamangs

누군가는 프로그래밍이라는 것은 조건과 반복이라고 한다.


우리가 컴퓨터를 쓰는 이유는 결국에는


"많은 작업들을 시키기 위해서"컴퓨터를 쓰는 것이다.

 


여기서 조건이라는게 뭘까?


사실 여러분은 앞에서 진리값과 논리연산, 비교연산, 조건연산을 배웠다.


그래서 이미 알고있는 개념이지만 좀 더 명확하게 이야기하도록해보면


경우에 따라 실행되는 로직이라고 볼 수 있다.

 

가령 위의 그림을 예로들면 조건에 따라 도서관에 갈수도 있고 PC방에 갈수도 있다.

 

시험기간이라면 도서관에 갈것이고 아니면 PC방에 갈 것이다.

 

이를 프로그래밍으로 할려면 어떻게 해야할까?


이때까지 우리가 배웠던 개념 들 중에서는 그 무엇도 이 문제를 해결할 수 없다.


그래서 해결하려면 사용하려면 새로운 개념이 필요하고 그게 바로 조건문이다.

 

조건문이 존재하기 때문에 우리는 특정 상황에서의 액션을 해결할 수 있다.


이를 뒤집어서 이야기하자면 특정 상황이 아닐 때의 액션역시 해결할 수 있다.

 

조건 4천왕

조건 문은 크게 보면 4가지의 종류가 있다.


사실 3가지는 함께 묶이고 나머지 하나는 좀 이질적이다.


여기서는 3가지 시리즈만 함께 소개하려고한다.


그래도 이름만 언급하자면 if, else, else if, switch가 있으며


여기서는 switch는 아직 다루지 않고 나머지 if와 else, else if만 다뤄붜자.

 

 

이번에 조건문(Conditional Statement)과 스코프(Scope), 섀도잉(Shadowing)에 대해서 사용할 것이다.

 

사실 스코프와 섀도잉은 자세히 이야기할 것은 아니다.

 

왜냐하면 공통 개념이기 때문이다.

 

조건 연산자라는 삼항연산자를 기억할 것이다.

 

조건 연산자((<조건>? <조건이 참일 때>: <조건이 거짓일 때>)) - 삼항 연산자, 조건부로 데이터를 출력

이때 잘보면 조건이 맞으면 ?뒤를, 조건이 틀리면 :뒤를 반환한다.


예시를 들자면 이런경우에 사용할 수 있다.

 

#include <stdio.h>

int main(){
  int num;
  scanf("%d", &num);
  printf("num이 10보다 크면1, 아니면0\n",(10>num?1:0));
}

위의 코드를 num을 바꿔가면서 테스트 해보면 이제 조건연산자라는 뭔지 감이 잡힐것이다.


조건 연산자가 뭔지 알았으니 조건문이 뭔지는 더 정확하게 알 수 있다.


조건문은 조건 연산자가 코드블럭 단위로 바뀐것이다.


조건연산자는 특정 값만 조건을 정할 수 있으나


조건문은 값이 아닌 행위(코드)의 조건을 정할 수 있다.

 

if문은 위와같이 사용한다.

 

이제 여러분은 처음으로 main을 제외한 첫 블럭을 보게 된 것이다.

 

이렇게 {}안에 감싸지는 녀석을 C언어 및 다른 언어에서는 블럭(Block)이라고 부른다.

 

그리고 C언어에서의 블럭은 스코프(Scope)와 동일한 뜻이다.

 

이 블럭이라는 개념은 너무 중요하기 때문에 나중에 또 알려줄 것 이다.

 

지금은 간략하게 하고 넘어가도록 하자.

 

#include <stdio.h>

int main() {
	int num = 5;
	if (10 > num)
		printf("10보다 %d가 작습니다.\n", num);
	return 0;
}

if문의 양태는 저 블럭을 약식으로 쓰느냐 아니면 정식으로 쓰느냐 두가지로 나뉜다.

 

위의 예시는 블럭을 약식으로, 즉 명시적으로 블럭을 선언하지 않았다.

 

그렇기 때문에 위의 예제에서는 단 한줄밖에 적을 수 없다.

 

 

그럼 위의 예제가 어떻게 돌아갈지 생각해보자.

 

그냥 영어를 읽는다고 생각하고 읽으면

 

만약, 10이 num보다 크다면 이라고 읽으면 된다.

 

해당 조건이 true가 되면 아래 문장을 실행하게 된다.

 

if (10 > num)printf("10보다 %d가 작습니다.\n", num);

if (10 > num)
printf("10보다 %d가 작습니다.\n", num);

if (10 > num)

printf("10보다 %d가 작습니다.\n", num);

또한 if문의 경우 띄어쓰기 여부와는 상관이 없다.

 

사실 이는 if문의 성격이 아니라 C언어 자체의 특징이다.

 

C언어는 공백문자(엔터, 스패이스, 탭)가 한개 이상만 들어간다면 이는 모두 동일하게 간주한다.

 

가령 위의 3개의 예제는 모두 동등하다.

 

이러한 약식 조건문은 솔직히 말하면 좋지는 않다.

 

사람의 스타일에 따라 다르겠지만 보통은 한문장을 쓰더라도 정식을 쓰는게 낫다.

 

하지만 뭐 스타일 차이이니 여러분이 하고싶은데로 하면된다.

 

#include <stdio.h>

int main() {
	int num = 5;
	if (10 > num) {
		printf("10보다 %d가 작습니다.\n", num);
		printf("%d는 10보다 작습니다.\n", num);
	}
	return 0;
}

만약 두 문장이상을 쓰고싶다면 반드시 block을 명시적으로 선언해야한다.

 

만약 그렇지 않고 쓰면 어떻게 될까?

 

해봐라. 무슨말인지 알것이다.

 

 

물론 한가지의 액션만 취하려고 괄호를 사용할 수 있다.

 

이제 여러분은 if를 알게 됨으로써 융통성있는 코딩이 가능해졌다.

 

이는 이걸 보는 여러분의 프로그래밍 인생에 가장 큰 사건중 하나이다.

 

 

그럼 이제 이렇게 생각해보자.

 

조건이 맞는경우 A를 실행한다, 그런데 조건에 안맞으면 B를 실행한다.

 

이런 코딩은 도대체 어떻게 할 수 있을까?

 

우리가 알고있는 지식으로도 충분히 만들 수 있다.

 

바로 아래처럼 만들면 된다.

 

#include <stdio.h>

int main() {
	int num = 5;
	if (10 > num) {
		printf("10보다 %d가 작습니다.\n", num);
	}

	if (!(10 > num)) {
		printf("10보다 %d가 큽니다.\n", num);
	}
	return 0;
}

num을 바꾸면서 테스트해보자.

 

아니면 scanf로 num으 ㄹ받는것도 좋은 방법중 하나다.

 

사실 이렇게 사용해도 문제가 없지만

 

C언어에서는 이런 불쌍한 중생들을 구제하기위해서 획기적은 해결책을 내놨다.

 

이렇게 없어도 되지만 있으면 편한 문법들을 문법적 설탕(Syntax Sugar)라고 불린다.

 

그 대표적인 예제는 바로 else문이다.

 

이제 여러분들은 조건이 거짓일 때 더 심플하게 사용할 수 있는

 

else문에 대해서 알아보도록 해보자.

 

else문은 반드시 if문과 연합을 이루어야한다.

 

그리고 if문처럼 else도 괄호를 써도 되고 안써도된다.

 

다시 말하지만 이는 블럭들의 공통사항이다.

 

#include <stdio.h>

int main() {
	int num = 5;
	if (10 > num) {
		printf("10보다 %d가 작습니다.\n", num);
	}
	else {
		printf("10보다 %d가 큽니다.\n", num);
	}
	return 0;
}

위의 코드는 이제 else문을 써서 훨씬 깔끔하게 됬다.

 

사실 엄밀히 말하면 if를 반대조건으로 겹쳐쓰는거랑 if-else문은 조금 다르다.

 

그러나 여기서 언급하지는 않겠다.

 

 

우리는 if와 else라는 아주 좋고 유용한 무기를 얻었다.

 

하지만 이러한 단순한 조건만으로는 복잡한 갈래를 당연히 처리할 수 없다.

 

하지만 당연히 이를 해결할 수 있는 방법을 만들어 놨다.

 

 

바로 if와 else를 중첩해서 해결하는 것이다.


이를 이용해서 고차원적인 작업을 할 수 있다.


가령 if와 else를 중첩할수도 있다.


이를 첩 조건문(Nested Conditional Statement)이라고 부른다.

 

 

사실 거창하게 제목까지 있고 중요하게 다루긴 하지만 특별한 문법이 있는건 아니다.

 

else는 당연히 필수가 아니며 마찬가지로 각각의 if와 else안에 추가적인 중첩을 할 수 있다.

 

문법적으로 중첩된 상태를 따로 지원하는건 아니지만 프로그래밍의 분기를 정하기 때문에

 

아주아주아주 중요한 개념중 하나이다.

 


주로 예시를 드는건 아주 고리타분하지만


달력 예제를 예시로 들 수 있다.


사골로 끓여서 이제 뼈도 안남았을만한 예제지만 아주 좋은 예제기도 하다.



흔히 2월달은 28일까지 존재한다. 하지만 윤년에는 29일까지 존재하게된다.


윤년은 보통 4의 배수로 떨어지는 해를 윤년이라고 한다.


하지만 이 중에서 특별하게 100으로 나누어떨어지는 해는 윤년이 아니다.


이렇게 윤년이 아닌 해를 평년이라고 한다.


즉 4의 배수지만 100의 배수는 아니어야 윤년이다.


그런데 또 여기서 100의 배수로 떨어지지만 400의 배수로 떨어지는 해는 윤년이다.


그러니까 4의 배수들은 윤년이지만, 그중에서 100의 배수는 평년이고,


또 그중에서 400의 배수는 윤년이다.


이를 프로그래밍으로 구현하는 예제는 학교에서 자주하는 예제이다.


그러나 아주 중요한 예제이고 이를 구현해보자.

 

아래 코드를 보기전에 혼자 생각해보고 구현해보는게 좋다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int num;
	scanf("%d", &num);
	if (num % 4 == 0) {
		if (num % 100 == 0) {
			if (num % 400 == 0) {
				printf("윤년\n");
			}
			else {
				printf("평년\n");
			}
		}
		else {
			printf("윤년\n");
		}
	}
	else {
		printf("평년\n");
	}
	return 0;
}

이번에는 scanf를 통해서 받아서 해보자.

 

여러분이 생각보다 이걸 만들기 힘들었을 것이다.

 

아니였으면말고.

 

보면 if와 else가 여러분 중첩하는 코드를 짜게된다.

 

이로 인해서 엄청나게 복잡한 코드를 짤 수 도있다.

 

 

하지만 이런 여러갈래의 문제를 반드시 중첩 반복문으로만 해결할 필요는 없다.

 

바로 else if라는 세번째 무기가 존재하기 때문이다.

 

바로 위의 코드를 else if로 고쳐보자.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int num;
	scanf("%d", &num);
	if (num % 400 == 0) {
		printf("윤년\n");
	}
	else if (num % 100 == 0) {
		printf("평년\n");
	}
	else if (num % 4 == 0) {
		printf("윤년\n");
	}
	else {
		printf("평년\n");
	}
	return 0;
}

else if를 사용하면 비슷하게 만들 수 있다.

 

먼저 else if의 특성을 보자면

 

if->else if->else순으로 순서대로 확인을 한다.

 

그중에 위에가 하나라도 참이라면, 즉 if가 참이면 else if조건이 마찬가지로 참일지라도

 

확인하지 않는다.

 

이는 같은 else if끼리도 마찬가지다. 즉 위의 else if가 무조건 우선순위를 갖는다.

 

만약 거짓이라면 문제가 없지만 참이 되면 아래의 참여부는 확인하지 않는다.

 

 

많은 예제에서 중첩된 조건문을 해결하기 위해서  else if가 존재하는것 처럼 서술하는 경우가 많다.

 

실제로 그런 경우들도 있지만 사실 아닌 경우도 많다.

 

이는 여러분이 앞으로 프로그래밍 하면서도 알게될 것이기 떄문에

 

재미를 위해서 자세한 설명은 안하도록 하겠다.

 

아 물론 귀찮아서 안하는건 아니다.

Posted by Kamangs

부울 대수를 사용할 때 여러분이 결과적으로는 반드시 알아야할 것이 있다.

바로 단축평가(Short-Circuit Evaluation)라는 녀석이다.

놀라운 사실은 이 단축평가에 대해서 모르는 사람들도 꽤 많다는 것이다.

 

이런 평가인가?

사실 고민했던게 이게 꼭 필수인가??

이게 좀 답하기는 애매한데 결론은 "지금은 몰라도 언젠간 반드시 알아야한다."

라는 것이다.

 


그래서 본문에 넣을까 부록으로 빼버릴까 고민을 하다가

본문에는 그래도 최대한 쉽고 필수적인것만 넣자고 해서 빼버렸다.

이번에는 이 단축평가에 대해서 알아보도록 하자.

 

우리는 이항 논리 연산자(AND, OR)을 굉장히 자주 사용한다

예컨데 아래와 같은 코드는 흔히 볼 수 있는 것이다.

 

#include <stdio.h>

int main() {
	int a = 10;
	if (1 <= a && a <= 10) {
		printf("SUCCESS");
	}
	return 0;
}

위의 코드는 a가 1이상이면서 10이하일 때 SUCCESS라는 문자열을 출력하라는 코드이다.

이 코드에서는 우리가 AND연산자인 &&을 사용했다.

자, 여기서 우리가 이 AND연산자를 어떻게 참인지 판단하는가?

먼저 좌변의 조건이 참인지 확인하고 우변의 조건이 참인지 확인하게 된다.

그래서 둘다 참이면 참이라고 하였다.


그런데 만약, 그렇다면 좌변이 거짓이면 뒤를 확인할 필요가 있을까?

어짜피 뒤를 확인하나 안하나 거짓인게 확정되게 된다.

이 경우 우리는 어떻게 해야할까?

거짓인지 확정됫지만 그래도 우변을 확인해야할까?


이제 우리가 무슨 말을 할려는지 감이 올것이다.

AND연산이건 OR연산이건 좌변을 보고 판단이 이미 끝났을 경우,

C언어에서는 우변을 확인하지 않는다.

이를 프로그래밍 용어로 단축평가(Short-Circuit Evaluation)라고한다.

 

단축평가(Short-Circuit Evaluation) - 최종 평가를 진행 할때 부분평가로 이미 최종 평가가 결정되었다면 더 이상 평가를 진행하지 않는다.

이는 경우에 따라서 중요할 수도, 중요하지 않을 수도 있다.

왜냐하면 일반적으로 if문, for문 혹은 조건연산자, 비교연산자, 논리연산자 같이

참과 거짓과 관련된 부분에서는 "함수사용 혹은 변수의 증감식이 권장되지 않기"때문이다.

그래서 경우에 따라서 정말로 단축평가에 대해서 모르는 사람도 있다.

 

 

이 특성은 경우에 따라서 여러분의 코드가 의도한대로 동작하지 않는 원인이 되기도한다.

가령 아래의 상황을 보자.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	int money = 0;
	bool inMarket = false;
	if (inMarket && ++money) {
		printf("엄마한테 돈받아서 물건 사야지!\n");
	} else {
		printf("물건은 못사지만 엄마한테 돈이라도 받아야지!\n");
	}
	printf("현재 내가 가진 돈은 %d원이다.\n", money);
	return 0;
}

상황을 가정해보자.

내가 매장안에 있으면 엄마한테 돈을 받아서 물건을 산다.

그리고 매장에 없어도 엄마한테 돈은 받는다.

이런 상황에서 위와 같은 코드는 여러분이 원하는 효과를 낼까?

 

 

애석하게도 여러분이 원하는대로 동작하지 않는다.

왜냐하면 단축평가가 이루어져서 inMarket이 true인지 false인지 확인을 하는데

inMarket이 true였다면 뒤를 확인해야할 의무가 생기지만

inMarket이 false였기에 뒤를 굳이 확인할 필요가 없기 때문이다.

그래서 money++이라는 구문은 동작하지 않는다.

그러면 저기 inMarket을 true로 교체해 보자.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	int money = 0;
	bool inMarket = true;
	if (inMarket && ++money) {
		printf("엄마한테 돈받아서 물건 사야지!\n");
	} else {
		printf("물건은 못사지만 엄마한테 돈이라도 받아야지!\n");
	}
	printf("현재 내가 가진 돈은 %d원이다.\n", money);
	return 0;
}

이럴 경우에는 inMarket이 true더라도 &&의 특성상 반드시 뒤를 확인해야한다.

 

 

그래서 결과는 money++이 실행되게 된다.


이는 AND뿐만아니라 OR도 마찬가지이다.

다만 AND는 앞이 false면 더이상 가치판단을 하지 않지만,

반대로 OR은 앞이 true면 더이상 가치판단을 하지 않는다.

조금만 생각해보면 당연하긴 하다.

하지만 AND보다 OR이 더 쉴수하기는 쉽다.

가령 아래의 코드를 보자.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	bool washing = false;
	bool sleeping = false;
	washing = !washing;
	sleeping = !sleeping;
	if (washing || sleeping) {
		printf("자거나 씻거나 씻고 자거나!\n");
	}
	printf("씻기:%d, 자기:%d\n", washing, sleeping);
	return 0;
}

여기서 여러분은 씻지 않았고 자지 않았다는걸 알 수 있다.

그래서 씻고나서 자려고한다.

이 경우 아무문제가 없다.

정상적으로 씻고나서 잠에 들고 printf는 출력될 것이다.

문제는 이를 좀 어떻게 깔끔하게 적어보겠다고 한줄에 적거나

if문에 넣거나 하면서 문제는 벌어진다.

 

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	bool washing = false;
	bool sleeping = false;
	if ((washing = !washing) || (sleeping = !sleeping)) {
		printf("자거나 씻거나 씻고 자거나!\n");
	}
	printf("씻기:%d, 자기:%d\n", washing, sleeping);
	return 0;
}

여기서 여러분은 씻고 잘것인가 씻기만 할것인가 자기만 할것인가?

 

 

그결과 여러분은 씻기만 하고 자지는 않을 것이다.

그 이유는 위에서 언급했지만 washing이 값이 반전되서 true가 되었고

OR은 앞이 true면 뒤는 확인하지 않기 때문이다.


이는 함수를 사용하는 경우에도 마찬가지이다.

 

#include <stdio.h>
#include <stdbool.h>


bool areYouHuman() {
	printf("당신은 휴먼입니까?\n");
	return true;
}

int main() {
	if (true || areYouHuman()) {

	}

	if (false || areYouHuman()) {

	}
	return 0;
}

함수를 쓰면서도 이문제는 꽤 심각하게 일어난다.

위의 경우에서 areYouHuman은 무조건 true를 리턴하므로

사용자는 "이 if문은 무조건 동작할거야"라는 믿음을 준다.

근데 문제는 그걸 뛰어 넘어서 "이 함수는 무조건 동작할거야"

라는 그릇된 생각까지 번지는 경우가 있다.

그래서 if문에서는 사실 함수사용이나 변수의 변화식은 지양되는 편이다.

 

 

하지만 장점이 없는건 아니다. 식이 더 간결해 질 수 있고

단축평가에 익숙해지면 오히려 코드를 보는데 더 편해질 수 있다.

 

if (false || areYouHuman()) {

}

가령 위의 구문만 봐도 단축 평가에 익숙해지면

"앞조건이 참이면 뒤를 실행안하고 앞조건이 거짓이면 뒤를 실행하는군"

이라고 생각하게 된다는 것이다.

 

 

결론 - 단축평가를 이해하고 쓰자!
Posted by Kamangs

4장 자료형에서도 짧막하게 진리값을 언급했고

 

6장 연산자에서 논리연산자와 비교연산자를 언급하면서 진리값을 또 언급했다.

 

하지만 기억에 잘 안날수도 있고 어려워서 이해못했을 수도 있다.

 

안다, 충분히 이해한다. 어짜피 여기서 다시 설명할거기 때문에 깊게 설명하지도 않았다.

 

판사의 판결은 무죄 혹은 유죄, 두가지 상태만을 가진다.

부울 대수와 진리값이라는 것을 여러분은 알아야한다.

 

어찌보면 작은 부분이라서 짧게 언급하고 넘어가도 될것 같고

 

여러분이 고등학교때까지 배웠던 집합론과 아주 유사한부분이 많지만

 

그걸 프로그래밍에 맞게 다시 알고 가야한다.

 

쉬운부분이지만 중요한 부분이기 때문이다.

 

이번에는 진리값(Truth Value)부울 대수(Boolean Algebra)에 대해서 알게 될 것이다.

 

그리고 아래에는 진리값과 부울린이 혼용되서 사용되고 있는데 둘은 엄밀히 말하면 다르지만

 

같은 개념이라고 접근해도 크게 무리는 없다.

 

둘의 엄밀한 차이는 아래에 따로 언급되고 있으니 확인하면 좋을 것이다.

 

 

진리값은 두 종류의 값만 오로지 가진다. true와 false이며

프로그래밍 관습상 true는 1로 매핑되고 false는 0에 매핑된다.

하지만 우리가 진리값을 쓰고 싶다고해도 그냥 쓸 수 없다.

이 자료형은 과거에는 없고 비교적 최근(이라고 해도 좀 옜날이긴한데)

추가되었기 때문에 그 흔적으로 우리가 이걸 사용하려면 추가적인 작업이 필요하다.

 

stdbool을 include 해줘야만 쓸 수 있다.

 

지효의 질문

 

여러분이 예제를 하는데 stdbool.h를 include하지 않았는데도 되는 경우가 있다.
이는 여러분의 확장자를 잘확인해보자. 아마 cpp상태일 것이다.
c++에서는 bool이 기본형으로 들어가 있기 때문에 include안해도 사용가능하기 때문이다.

 

여튼 이녀석을 include하면 아래처럼 사용할 수 있다.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	bool real = true;
	bool fake = false;
	printf("%d\n", real);
	printf("%d\n", fake);
	return 0;
}

이렇게 boolean 변수는 bool 지시자로 선언할 수 있으며

 

해당 값은 오로지 true와 false만 가질 수 있다.

 

출력을 해보면 true는 1로, false는 0이라는 걸 알 수 있다.

여기서 여러분이 착각해야할것은 boolean변수의 true, false와

 

진리값 참, 거짓은 조금은 다르다는 것이다.

과거에도 언급했지만 bool은 오로지 true와 false, 단 두가지, 즉 1과 0만 가질 수 있다.

하지만 진리값의 참과 거짓은 반드시 1과 0일 필요는 없다.

진리값에서 false는 0을 의미하고 true는 0을 제외한 모든 숫자를 의미한다.

가령 예를 들어보자.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	printf("%d\n", 1 && -1);
	return 0;
}

위 코드가 주어진다면 결과는 1(참)으로 나올것인가 거짓으로 나올 것인가?


여기서는 1도 참이고 -1도 참이므로 참이 된다.

 

 

즉 1도 참(true)이고 -1도 참(true)이라고 본것이다.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	printf("%d\n", 1 && 0);
	return 0;
}

그럼 이 코드는 어떠한가? 참인가?


우리가 0은 거짓으로 본다고했다.


그렇기 때문에 출력 결과 거짓으로 나타난다.

 

 

이는 실수를 비교해도 마찬가지이다.

그럼 bool에 정수를 매핑하면 어떻게 될까?

bool은 오직 true와 false만 가질 수 있다고 하였는데 정말 그럴까?

한번 해보자.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	bool a = 1;
	bool b = -1;
	bool c = 3.14f;
	bool d = 0;
	printf("%d\n", a);
	printf("%d\n", b);
	printf("%d\n", c);
	printf("%d\n", d);
	return 0;
}

bool에 온갖 값들을 대입해보자.


그러면 결과가 어떻게 나올지 예측을 해보자.

 

뭐 의외라고 생각할 수도 있고 예상한 대로일 수도 있다.


bool은 오직 1과 0만 가진다.


그 중에서 false가 되는 조건은 오직 0뿐이며


나머지 값은 모두 true인 1로 강제 형변환된다.

 

a에서 1은 true이므로 당연히 a의 값은 true인 1이 된다.

 

b에서 -1은 0이 아니므로 true가 되어 b의 값은 true인 1이 된다.

 

c의 3.14는 0이 아니므로 true가 되어 1이 된다.

 

마지막 d는 0이므로 false가 된다.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	bool tpt = true + true;
	bool tmt = true - true;
	bool tp10 = true + 10;
	bool fm10 = false - 10;
	printf("%d\n", tpt);
	printf("%d\n", tmt);
	printf("%d\n", tp10);
	printf("%d\n", fm10);
	return 0;
}

그러면 부울린의 연산은 어떻게 될까?


부울린은 true와 false라고 했다.


그래서 그냥 1과 0을 대체해서 계산하면된다.



위에서 부터 차례대로 생각해보면,


tpt는 2가 될것이고


tmt는 0이 될것이고


tp10은 11이 될것이고


fm10은 -10이 될 것이다.



하지만 bool이라는 변수는 1과 0밖에 못가진다는건 우리는 알고있다.

 

그래서 이 계산값을 bool이라는 값에 담게되는순간 0이면 그대로,


나머지는 1로 바뀌게 된다. 즉 다시 참과 거짓만 가지게 된다는 것이다.


이 특성은 아주 유용한 특성임과 동시에 여러 일어날 수 있는 부작용들을 해소하게 해준다.


그러한 부작용들에 대해 설명하려면 시간이 길어지므로 생략하도록 하겠다.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	printf("%d\n", true + true);
	printf("%d\n", true - true);
	printf("%d\n", true + 10);
	printf("%d\n", false - 10);
	return 0;
}

물론 이는 변수에 담는 행위를 했을때의 이야기고


위처럼 그냥 연산하면 사정은 좀 달라진다.


부울린과의 연산은 무조건 정수로 나오게되는데


여튼 여기에 대해서 설명하려면 조금 복잡하다.

 

왜 이렇게 되는지 정확하게 알고 싶다면 부록6.1의 정수 승격부분을 확인하도록하자.

 

이제 부울 대수와 진리표에 대해서 알아보자


부울 대수는 깊게 알아보려면 굉장히 복잡하기 때문에 자세한건 이야기 하지 않겠다.


간단히 이야기하면 프로그래밍을 할때 참과 거짓을 판별하는 것을 부울대수라고한다.


그냥 뭐 진리값과 동치라고 생각해도 무방하다.

 


가령 노력하면서 똑똑하면 성공한다라는 명제가 존재한다고 해보자.


이 경우 (노력 AND 똑똑 = 성공)이라는 부울 대수식이 성립한다.


이 때 노력만 해서도 성공할 수 없고 똑똑하기만 해서도 성공할수 없다.


노력하면서 똑똑해야만 성공할 수 있다.



만약 노력하거나 똑똑하거나하면 성공할수 있다라는 명제라면


(노력 OR 똑똑 = 성공)이라고 하면된다.


이 경우 둘중 하나만 되면 성공하는 것이다.

 

 

우리가 자주쓰는 부울 연산은 총 7가지가 존재하며 이를 알아보자.

1. NOT - true를 false로, false를 true로 바꾼다
2. AND - 양 값이 둘다 참이어야만 참이고 나머지는 거짓
3. OR - 양 값중 하나만 참이어여도 참, 둘다 거짓이면 거짓
4. XOR - 양 값이 서로 달라야 참, 서로 같으면 거짓
5. NAND - AND의 반전
6. NOR - OR의 반전
7. XNOR - XOR의 반전

5,6,7은 2,3,4의 각각 반전이므로 1,2,3,4만 알아보자.

 

유일하게 양값을 비교하는게 아니라 자신값만 비교하는 단항연산자이다.

 

그냥 진리값을 반전시키면된다. 간단하지만 강력하다.

 

특정 명제가 참이 되기위해서는 두개의 조건이 모두 참이 되어야한다.

 

"대한민국 국민이면서 남자면 군대를 가야한다"라는 명제가 있다면 이는 AND연산으로 묶여있다고 불 수 있다.

 

특정 명제가 참이 되기위해서는 두개의 조건 중 하나만 참이 되면된다.

 

"신분증이 있거나 여권이 있으면 신원을 확인할 수 있다"라는 명제가 있다면

 

이는 OR연산으로 묶여있다고 볼 수 있다.

 

물론 둘다 참이여도 참이다.

 

OR중에서 조금 특이한 상황인데 둘중 하나만 참이어야 하는 상황이다.

 

예를들어 현재 내가 1000원이 있고 소고기와 닭고기, 가스버너가 각각 500원이라고 가정해보자.

 

이 때 "닭고기만 사거나 소고기만 사야 고기를 먹을 수 있다"라는 명제가 존재한다면

 

이는 XOR연산으로 묶여있다고 볼 수 있다.

 

만약 소고기와 닭고기를 동시에 사면 가스버너를 못사서 고기를 어짜피 못먹게 되기 때문이다.

 

아니면 날걸로 먹던가.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	bool hasGirlFriend = false;
	bool hasMoney, isHandsome;
	printf("1이면 여자친구 있는 거임\n0이면 없음\n\n");

	hasMoney = true;
	isHandsome = false;
	hasGirlFriend = hasMoney || isHandsome;
	printf("돈이 있지만 못생길 경우 : %d\n",hasGirlFriend);

	hasMoney = false;
	isHandsome = true;
	hasGirlFriend = hasMoney || isHandsome;
	printf("잘생겼지만 돈이 없는 경우 : %d\n", hasGirlFriend);

	hasMoney = false;
	isHandsome = false;
	hasGirlFriend = hasMoney || isHandsome;
	printf("못생기고 돈이 없는 경우 : %d\n", hasGirlFriend);
	return 0;
}

 

진리값들은 이런식으로 사용할 수 있다. 위는 OR연산을 사용한 예제이다.

 

 

C언어에서 논리연산의 경우 XOR을 제외한 NOT과 AND, OR을 제공한다.

 

비트 단위에서는 XOR까지 제공하지만 논리연산에서는 제공하지 않는다.

 

만약 XOR이 필요하다면 직접 만들어서 써야한다.

 

#include <stdio.h>
#include <stdbool.h>

int main() {
	bool a = true;
	bool b = true;
	bool result = (a != b) && (a || b);
	printf("%d\n", result);
	return 0;
}

result를 보면 어떻게 결과가 나오는지 확인할 수 있다.

 

a와 b를 바꿔서 테스트 하면서 여러분이 원하는 결과가 나오는지 확인해보자.

 

(a != b) && (a || b)

 결국 둘이 다른 경우에만 OR연산을 수행한다면 이는 XOR연산이라고 할 수 있다.

 

사실 별 연관없는데 그냥 넣어봄

진리값과 부울 대수를 배우면서 여러분이 알아야할 연산자는 논리 연산자 뿐만이 아니다.

 

여러분은 비교연산자도 알아야한다.

 

비교연산자와 논리연산자는 연산자들 중에서

 

결과값이 유일하게 진리값으로 나오는 연산자들이기 때문이다.

 

사실 뭐 그리 중요하지 않을 수 있다.

 

직관적으로 해결되기 때문이다.

 

하지만 뭐든지 알고 쓰는게 중요하다고 생각한다.

 

 

이번에도 비교적 짧게 혹은 비교적 자세하게 이야기 해봤지만

 

당연히 첫술에 배부를리 없고 앞으로 더 확인해봐야할게 많을 것이다.

 

일단 이정도를 알고 있으면 조건문을 배울 때 이제 많이 도움이 될 것이다.

Posted by Kamangs

 

C언어를 사용하면서 자료형이 무조건 같은 경우만 있지 않다.

 

다른 형끼리의 연산도 충분히 자주 사용한다는 것이다.

 

이에 대해서 큰 이해없이 사용을... 해도 상관없긴한데 알면 그래도 좋지 않을까?

 

그래서 부록으로 다른 자료형 끼리의 연산은 도대체 어떻게 이루어지는지

 

그리고 여기서 우리가 뭘 조심해야할지에 대해서 부록으로 한번 다루어 보도록 하자.

 

노파심에서 하는 이야기이지만 부록 부분은 초심자는 안읽어도 된다.

 

중요한 부분이긴하지만 지금 알 필요는 없는 부분이기 때문이다.

 

그리고 좀 어렵기도 하고.

 

이번에 반복문이라는 키워드를 반드시 알아야한다.

 

아직 반복문에 대해서 이야기한적은 없기 때문에 모른다면 뛰어넘어도 되고,

 

아니면 반복문만 잠시 알아보고 와도 좋다.

 

그리고 추가적으로 정수 승격(Integer Promotion)이라는 개념도 배우게 될 것이다.

 

이 개념을 알려주는 경우도 있고 건너 뛰는 경우도 있지만 기왕이면 알고가는게 좋을 것이다.

 

우리는 먼저 타입군을 3가지를 정하자, 정수와 양의 정수와 실수로 타입군을 나눈다.

 

이 때의 경우 다른 타입군끼리의 연산이 어떠할지, 혹은 같은 타입군 끼리의 연산이 어떠할지를 알아보자.

 

먼저 다른 타입군끼리의 연산에 대해서 한번 알아보도록 하자.

 

다른 타입끼리의 연산이 어떻게 돌아가게 될지를 아는건 아주 중요하다.

 

사실 알고 넘어가는게 좋다.

 

왜냐하면 어떻게 서로 다른 녀석들을 통합시킬지에 대한 논의가 필요하기 떄문이다.

 

경우의 수는 3종류가 존재한다.

 

각각의 종류가 어떻게 진행될지 알아보도록 하자.

 

여기서 여러분들이 알아야할 놀라운 사실이 하나 있다...


바로 C언어는 사실은 양의 정수와 정수를 데이터적으로는 구별하지 않는다는 사실이다.

 

이 이야기를 하면 놀래는 사람들이 꽤 있다.

 

엄밀히 말하면 아예 구별을 하지 않는다는 것은 아니고 데이터는 동일하게 저장된다는 것이다.

 

이를 한번 우리가 확인해보도록 하자.

 

#include <stdio.h>

int main() {
	int a = 10;
	unsigned int b = 2147483647;
	int c = 2147483647;

	printf("%d\n", b);
	printf("%d\n", c);
	printf("%d\n", b + a);
	printf("%d\n", c + a);
	printf("%d\n", b - a);
	printf("%d\n", c - a);

	return 0;
}

위와 같은 코드를 보도록하자.

 

b와 c는 int가 표현할수 있는 최대의 수를 가지고 있다.

 

(2147483647은 int가 표현할 수 있는 최대의 수이다.)

 

여기서 1만 더해도 b의 경우는 표현범위 안이라서 상관없지만

 

c는 표현 범위를 넘어가므로 음수가 될 것이고 예측 못하는 수...가 된다고 아는 사람들이 있다.

 

하지만 실상은 이와 다르게 명백하게 예측 할 수 있다.

 

일단 위의 코드를 실행해보자.

 

그러나 왠걸?

 

둘은 완전하게 동일한 값을 출력하는걸 알 수 있다.

 

이는 %d때문 아니냐?라고 반문할 수 있다.

 

뭐 맞는말이긴한데 문제는 저장된 데이터자체가 같다는 뜻이다.

 

#include <stdio.h>

int main() {
	int a = 10;
	unsigned int b = 2147483647;
	int c = 2147483647;

	printf("%u\n", b);
	printf("%u\n", c);
	printf("%u\n", b + a);
	printf("%u\n", c + a);
	printf("%u\n", b - a);
	printf("%u\n", c - a);

	return 0;
}

그러면 이러한 코드로 바꿔보자.

 

크게 바뀐건 없고 출력을 양의 정수형인 %u로 바꾸어 보았다.

 

이제는 int형을 넘은 범위를 출력을 하는 것을 확인할 수 있다.

 

그런데 여기서도 둘의 데이터는 완전하게 동일한걸 알 수 있다.

 

이를통해서 우리가 알 수 있는것은 int와 unsigned int는 사실 내부적으로 unsigned int형의 연산으로 동작하게 된다는 것이다.

 

그리고 출력 형식에 맞춰서 거기에 맞는 값을 보여주게 된다.

 

출력형식을 정수형(%d)로 하면 해당 타입을 int의 범위인 -2147483648에서 2147483647로 표현하고


출력형식을 양의 정수형(%u)로 하면 해당 타입을 unsigned int의 범위인 0에서 4294967295로 표현하게 된다.

 

 

사실 여러분은 이게 좀 신기할 것이다. 어떻게 이런게 가능할까? 음수가 있는데도?


이는 음수는 사실 보수화 연산을 통해서 저장된다고 했는데 이게 절묘하게 맞아 떨어지게 된다.

 

이 사실을 잠깐 이해하고 넘어가자.

 

컴퓨터에서 왜 음수 표시를 2의 보수화를 채택을 해서 했는지를 알 수 있고

 

태초에 프로그래밍 언어를 설계한 사람들의 천재성을 여기서 엿볼 수 있다.

 

unsigned int a = -10; //4294967286

C언어에서는 위 같은 코드가 가능하다.

 

정말로 -10이 저장되는건 아니고 4294967286이 저장되는데

 

실질적으로 -10과 4294967286는 4바이트 정수(int, unsigned int 등)끼리의 처리에서는

 

내부에서는 같은 수로 취급하게된다.

 

따라서 말장난 같지만 -10이 저장된다고 생각해도 무방하고 4294967286이 저장된다고 생각해도 무방하다.

 

#include <stdio.h>

int main() {
	int a = 5;
	int b = -10;
	unsigned int c = -10;
	unsigned int d = 4294967286;
	printf("%u\n", a + b);
	printf("%u\n", a + c);
	printf("%u\n", a + d);

	printf("%d\n", a + b);
	printf("%d\n", a + c);
	printf("%d\n", a + d);
	return 0;
}

그럼 위와 같은 코드를 예상해보자.


b, c, d는 이론상으로는 셋다 같은 숫자가 저장되어 있다.


심지어 b는 int형이다. 이 경우 셋의 결과가 같을까?

 

셋다 결과가 같다.

 

이로서 프로그래밍에서 정수끼리의 연산에서 unsigned와 signed는 연산에서

 

차이가 없이 똑같은 로직이 적용된다는걸 알 수 있다.

 


물론 그렇다고 해서 unsigned와 signed가 동일하다는 이야기는 아니다.

 
둘은 사칙연산에서는 동일하게 작동하지만 그 외의 상황에서는 다르게 작동하는 경우도 있다.


가령 아래의 예를 들어보자.

 

#include <stdio.h>

int main() {
	for (int a = 10; a >= 0; a--) {
		printf("%d\n", a);
	}
	return 0;
}

정상적인 for문이다. 이러한 코드는 아무문제 없는 코드다.


굳이 해석하자면 a는 10부터 0이 될때까지 a를 출력하는 코드다.

 

우리 생각대로 0에서 정지하는걸 확인할 수 있다.

 

#include <stdio.h>

int main() {
	for (unsigned int a = 10; a >= 0; a--) {
		printf("%d\n", a);
	}
	return 0;
}

하지만 이 코드는 어떻게 될까?


unsigned int는 음수가 되지 않는다.


따라서 위 코드는 영원히 무한루프를 돌리게된다.

 

따라서 unsigned int와 int는 연산때는 동일하게 작용하지만


내부적으로 unsigned int는 뚜렷하게 양수로 인식되고 있음을 알 수 있다.

 

사실 이문제는 아주 중요한 문제이다.

 

for문에 unsigned형을 쓰게 되는건 아주 위험하다.

 

그렇기 때문에 만약 for문에 unsigned형을 써야한다면 int나 long long형으로 치환한 변수를 쓰는게 좋다.

 

정수와 실수의 경우 정수를 실수로 바꾼다.


해당 정수를 다른 피연산 타입의 실수로 변환한다.


그리고 실수와 실수 연산으로 바뀌게된다.


가령 int와 float의 연산이면 int를 먼저 float으로 변환하고


그 다음 float과 float의 연산을 수행하게 된다.


물론 int와 double의 연산이면 int를 먼저 double로 변환한다.


사실 크게 특이한건 없으므로 이건 넘어가겠다.

 

이 부분역시 양의 정수와 정수가 구별된은 부분이다.


양의 정수는 음수가 없기 때문에 음수 타입으로 지정해봤자 무조건 양수로 인식한다.

#include <stdio.h>

int main() {
	int a = -10;
	unsigned int b = -10;
	float fa = 1.5f + a;
	float fb = 1.5f + b;
	printf("%f\n", fa);
	printf("%f\n", fb);
	return 0;
}

위의 코드가 주어졌을 때 우리가 생각해보자.

 

내부적으로 a와 b는 같은 값이다.

 

그러나 a는 음수고 b는 음수의 탈을 쓴 양수이다.(unsigned형은 음수가 없다.)

 

여기서 실수형의 연산의 결과는 어떻게 될까?

정상적으로 동작하는걸 확인할 수 있다.

 

int형은 -10이 존재하므로 이를 -10으로 전환시킨다.

 

반대로 unsigned int는 -10이 없기에 거기에 매칭된 양수로 치환되며

 

이를 바탕으로 계산하는걸 알 수 있다.

 

사실 직관적으로 이는 당연한 일이므로

 

(양수가 양수로 전환되는게 원래는 당연하잖아)

 

그리 깊게 생각할 필요는 없다.

 

다른 타입군 끼리의 연산은 충분히 이야기 했으므로

 

같은 타입군 끼리의 연산을 한번 보도록 하자.

 

사실 이는 직관에 의존하면 대부분 문제가 안일어나기에 중요도는 떨어진다.

 

그러나 여러분이 C언어를 깊게 알고싶다면 이 부분을 알아두는것도 좋다.

 

#include <stdio.h>

int main() {
	char c1 = 1;
	printf("%d\n", sizeof(c1));
	return 0;
}

sizeof 연산자는 해당 변수가 몇 바이트인지를 알려준다.


이게 정확히 char인지 short인지는 알 수 없지만 바이트 출력으로 무슨 변수인지


우회적으로는 알 수 있다.


c1의 크기는 char이니까 1이 출력될 것이라는 것은 쉽게 예상할 수 있다.

 

그럼 이건 예상할 수 있을까?

 

#include <stdio.h>

int main() {
	char c1 = 1;
	char c2 = 2;
	printf("%d\n", sizeof(c1 - c2));
	return 0;
}

char와 char의 연산결과는 과연 몇바이트일까?


우리가 일반적으로 char와 char의 연산이니까 1바이트일거라는 생각을 할 수 있다.

 

하지만 실제로 출력해보면 4바이트가 나온다.


놀라울 수 있는데 다른 타입이 아니라

 

같은 타입인 char끼리의 연산의 결과가 int로 바뀐다는것을 알 수 있다.

 

#include <stdio.h>

int main() {
	char c = 1;
	short s = 2;
	printf("%d\n", sizeof(c - s));
	return 0;
}

그럼 이제 이 코드를 예상할 수 있는가?


short와 char의 연산은 더 큰타입은 short에 맞춰질까?


그럼 출력은 2일까? 아니면 앞에서 char끼리의 연산이 int로 바뀌었으니까


이 연산도 int로 나올까?

 

이 경우에도 int가 되는걸 확인할 수 있다.

여기서 우리가 알 수 있는 사실은 정수끼리의 연산은 int형이 나오게 된다는 것이다.

 

정수 승격(Integer Promotion) - int이하의 타입의 경우 연산결과 int로 맞춰진다.

즉 int 이하의 타입(4바이트 이하의 모든 정수)끼리의 연산은 바드시 int타입으로 나오게된다.

 

정수 승격은 int와 char 같은 정수형 타입 뿐만이 아니라 진리값(bool)에도 적용된다.

 

그래서 bool + bool의 연산을 시행할 경우 이는 int로 결과가 나오게된다.

 


이는 경우에 따라서 문제를 일으킬 소지를 볼 수도 있지만 사실 일상적인 상황에서 큰 문제는 없다.


이걸 평생 모르는 사람들도 있다.


하지만 비트단위로 사용하다보면 문제가 생길 수 있는데

 

딱히 지금은 중요한 내용은 아니므로 여기에서 설명하지는 않겠다.

 


왜 일반적일 때 문제가 없을까?

 

#include <stdio.h>

int main() {
	char c1 = 1;
	char c2 = 2;
	short s = c1 - c2;
	printf("%d\n", s);
	printf("%d\n", sizeof(s));
	return 0;
}

가령 이런 코드가 존재한다고 해보자.


c1 - c2는 정수 승격이 일어나므로 short에는 int가 대입될 것이다.


그러나 좌변이 short이면 우변은 short크기로 잘라버린다고 했다.


더 정확하게 말하면 우변은 강제적으로 타입 캐스팅이 일어나게 된다.


우변을 short로 강제로 타입캐스팅을 해버린다.


따라서 우리는 일상생활에서 특정한 코드들을 제외하고는


정수 승격의 존재를 느끼기가 힘들다.

 


그러면 int 보다 더 큰타입들은 어떻게 될까?

 

#include <stdio.h>

int main() {
	char c = 1;
	long long ll = 2L;
	printf("%d\n", sizeof(c - ll));
	return 0;
}

char와 long long int형을 비교해보자.


이는 정수승격은 일어날 수 없다. 정수승격은 int형 이하에서만 일어나니까.


일반적으로 생각해보면 char와 long long int형의 연산은


long long int형으로 수렴하는게 합리적이라는 생각을 해볼 수 있다.

 

결과를 보면 알 겠지만 long long int로 상승한걸 알 수 있다.


우리가 일반적으로 쉽게 생각할 수 있듯이 작은 수와 큰 수의 경우에는 큰수로 맞춰진다.


int와 long, long long의 경우 int형이 아니라 더 큰 타입으로 맞춰진다.

 

결론 - int 이하는 int로 고정, long 이상의 경우 더 큰 타입으로 맞춰진다.

 

#include <stdio.h>

int main() {
	float f = 3.14f;
	double d = 3.14;
	long double ld = 3.14L;
	printf("%d\n", sizeof(f - f));
	printf("%d\n", sizeof(f - d));
	printf("%d\n", sizeof(d - ld));
	printf("%d\n", sizeof(ld - f));
	return 0;
}

float과 double, long double로 3가지의 자료형이 있다.

 

그럼 각각을 비교해보도록하고 결과를 보자.

 

우리가 흔히 예측하는게 float < double <= long double이니 더 큰쪽으로 맞춰질것이라고 생각한다.

 

그리고 그 결과는 정확하게 일치하게 된다.

 

실수형끼리의 연산에서는 더 큰타입으로 작은타입이 타입캐스팅을 하게된다.

 

즉 float과 float은 float이 되지만


float과 double은 double로, dobule과 long double은 long double이 된다.

 

 

사실 실수에 대해서 할 이야기가 아주 많기는 하지만 이는 다른 포스팅에서 뵙도록 하겠다.

Posted by Kamangs

프로그래밍은 기초에는 수학을 계산하기 위해서 나온 장치이다.


그래서 영어로도 컴퓨터(Computer: 계산하는 것)일 것이다.


그렇기 때문에 우리는 이제 연산자라는것을 무조건 알아야할 필요가 있다.

 

연산...군

수학을 배웠으면 연산자에 대해서 어느정도 알것이다.


그런데 저번에 말했던것과 마찬가지로 수학의 연산자와 프로그래밍의 연산자는 조금은 다르다.


여러분이 취해야할 스탠스는

 

수학과 프로그램의 연산자는 기본적으로는 같지만 차이가 있다

 

라고 보면 된다.

 

공원소녀-레나

연산자는 크게보면 세가지로 나뉜다.

 

단항, 이항, 삼항 연산자 이다.

 

당연하다고 생각할 수 있다.

 

C언어에서는 각 연산자가 단항인지 다항인지는 그리 중요하지는 않다.

 

C++에서는 연산자 오버로딩 때문에 꽤 중요해지지만 말이다.

 

이제 연산자에 대해서 하나씩 알아보도록 하자.

 

부호 연산자(+, -) - 단항 연산자, 수의 부호를 결정
#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a = 10;
	printf("%d\n", +a);
	printf("%d\n", -a);
	printf("%d\n", -(-a));
	return 0;
}

 

부호 연산자는 숫자의 부호를 결정한다.

 

당연한 이야기이지만 일반적으로 +부호는 큰 의미는 없다.

 

만약 음수 부호를 두번 하고 싶다면 수학 수식처럼 괄호를 감싸줘야한다.

 

양수 부호는 실제로는 거의? 아예? 쓰이지 않는다고 보면된다.

 

산술 연산자(- , +, *, /, %) - 이항 연산자, 사칙 연산을 담당
#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a = 10;
	int b = 3;
	int sum = a + b;
	int sub = a - b;
	int mul = a * b;
	int div = a / b;
	int rem = a % b;
	printf("%d + %d = %d\n", a, b, sum);
	printf("%d - %d = %d\n", a, b, sub);
	printf("%d * %d = %d\n", a, b, mul);
	printf("%d / %d = %d\n", a, b, div);
	printf("%d %% %d = %d\n", a, b, rem);
	return 0;
}

사칙연산자는 우리가 아는 그 사칙 연산자 맞다.

 

다만 곱하기와 나누기는 ×와 ÷가 아닌 *와 /이다.

 

이 기호를 각각 애스터리스크(Asterisk)슬래시(Slash)라고 부른다.

 

sum은 더하기, sub은 빼기, mul은 곱하기, div는 나누기를 하는 변수이다.

 

그러면 결과를 보도록 하자.

 

 

결과를 보면 알겠지만 하나가 예상대로 되지 않았다.

 

바로 나누기이다.

 

원래라면 3.333333이 되야하겠지만 그렇게 되지 않았다.

 

이제 여러분은 여기에 대해서 익숙해져야한다.

 

 

그 이유에 대해서 설명하기 전에 이야기를 하자면

 

쉽게 이야기해서 일반적인 나누기는

 

결과적으로 초등학생때 쓰는 나누기 연산이라고 생각하면된다.

 

초등학교때 나누면 몫과 나머지를 남기는 나누기 연산을 한다.

 

컴퓨터의 int끼리 연산도 결과적으로는 그렇게 된다는 것이다.

 

그리고 나머지를 알 수 있는 연산까지 제공되어 있다.

 

%기호의 연산자이다, 이를 mod연산이라고 많이들 부른다.

 

이 연산을 시행하면 결과를 나눈 나머지 값을 얻을 수 있다.

 

 

그럼 여러분이 원하는게

 

3.3333이라고 하고 그 결과를 출력하고 싶다면 어디를 손봐야하는지 알려주겠다.

 

일단 %d도 정수형, div역시 정수형이다.

 

그렇기 때문에 설사 값이 실수로 나왔다고 할지라도 정수로 나올 수 밖에 없다.

 

출력을 실수형으로 해야하니 우리는 이를 실수형으로 바꿔준다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a = 10;
	int b = 3;
	int sum = a + b;
	int sub = a - b;
	int mul = a * b;
	float div = a / b;
	printf("%d + %d = %d\n", a, b, sum);
	printf("%d - %d = %d\n", a, b, sub);
	printf("%d * %d = %d\n", a, b, mul);
	printf("%d / %d = %f\n", a, b, div);
	return 0;
}

div를 float으로 바꿔줘서 실수를 담을 수 있는 변수로 바꿨다.

 

출력 포맷을 float을 출력가능한 %f로 바꾸었다.

 

 

하지만 출력해도 값이 그대로 3인것을 확인할 수 있다.

 

그 이유를 아는 것은 여러분이 프로그래밍을 하는데 아주 중요하다.

 

정수와 정수의 연산 결과는 반드시 정수로 나온다.

 

a와 b는 정수이기 때문에 a와 b의 연산결과는 정수가 되고

 

따라서 div의 값은 이미 정수화된 a/b를 float으로 바꿀 뿐이다.

 

3을 float으로 바꿔봤자 3.000000이 될것임은 자명하다.

 

왜냐하면 실수 3도 당연히 존재할거 아닌가.

 

그래서 a/b는 정수와 정수의 연산이 아닌 다른 형태끼리의 연산이 필요해지게 된다.

 

 

그러면 두가지의 경우의 수가 생긴다.

 

정수와 실수의 연산, 실수와 실수의 연산이다.

 

사실 궁금하긴 할것이다. 실수와 실수의 연산은 당연히 실수다.

 

실수와 정수의 연산은 어떻게 되는 것인가?

 

그리고 이를 유도하려면 어떻게 해야하는가?

 

 

실수와 정수의 연산의 결과는 실수가 된다.

 

따라서 a / b에서 한쪽이 실수이면 이 문제는 간단하게 해결된다.

 

문제는 a나 b 둘중하나를 어떻게 하면 실수를 바꿀 수 있느냐인데...

 

다행이도 C언어에서는 이를 지원해준다.

 

바로 캐스팅 연산자 이다.

 

캐스팅 연산자((char), (int), (byte), ...) - 단항 연산자, 데이터 타입을 바꿈
#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a = 10;
	int b = 3;
	int sum = a + b;
	int sub = a - b;
	int mul = a * b;
	float div = (float)a / b;
	printf("%d + %d = %d\n", a, b, sum);
	printf("%d - %d = %d\n", a, b, sub);
	printf("%d * %d = %d\n", a, b, mul);
	printf("%d / %d = %f\n", a, b, div);
	return 0;
}

 

캐스팅 연산자는 강제로 타입을 바꿔버린다.

 

항상... 강제라는 것에 주의해야하지만 지금은 고민할 필요 없다.

 

캐스팅 연산자는 현재 사용할 수 있는 어떠한 타입도 다 가져다가 사용할 수 있다.

 

이를 변수 앞에 붙혀주면 강제로 타입이 변형된다.

 

위의 경우 a는 int지만 float으로 강제로 바꾸었다.

 

이제 a/b는 더이상 정수, 정수의 연산이 아니라 실수와 정수의 연산이다.

 

따라서 결과는 float이 된다.

 

 

드디어 우리가 원하는 값을 얻을 수 있게 되었다.

 

 

다른 타입 끼리의 변환과 거기에 대한 이해는 조금 내용이 많다.

 

그래서 부록으로 언급할 테니 여기서는 여기까지만 다루도록 하겠다.

 

비트 연산자(&, |, ^, <<, >>, ~) - 대체적으로 이항 연산자, 비트 단위로 연산을 함, 단 ~는 유일한 단항 연산자

비트 연산자는 당연하지만 비트단위로 연산을 해주는 연산자이다.

 

비트에 대한 이야기는 4-1장 부록 변수는 어떻게 표현되는가에 상세하게 설명되어 있다.

 

그렇기 때문에 비트에 대한 이야기는 설명을 생략하고 각각의 연산자들에 대해서 알아보자.

 

 

하지만 이 마저도 상세한 설명은 하지 않겠다.

 

여기서는 대충 설명을 듣고 이러이러하구나 정도로만 이해해도된다.

 

아직 쓸일도 없을 뿐더러 추후 11장에서 비트에 대해서 상세히 설명하도록 하겠다.

 

& - AND연산을 비트 단위로 시행한다.
| = OR연산을 비트 단위로 시행한다.
^ = XOR연산을 비트 단위로 시행한다.
<< = 비트를 왼쪽으로 한칸씩 민다(Left Shift). 새로운 칸에는 0을 채운다.
>> = 비트를 오른쪽으로 한칸씩 민다(Right Shift). 새로운 칸에는 0을 채운다.
~ = NOT연산을 비트 단위로 시행한다. 결과적으로 보수가 된다.

 

코드 보기↓

더보기
#include <stdio.h>
#pragma warning(disable:4996)

void bin(char n, int cnt);

int main() {
	unsigned char a = 0b00101011;
	unsigned char b = 0b00011010;
	int c = 1;
	printf("a : ");
	bin(a, 1);
	printf("\n");

	printf("b : ");
	bin(b, 1);
	printf("\n\n");

	printf("a & b : ");
	bin(a & b, 1);
	printf("\n");

	printf("a | b : ");
	bin(a | b, 1);
	printf("\n");

	printf("a ^ b : ");
	bin(a ^ b, 1);
	printf("\n");

	printf("a << c : ");
	bin(a << c, 1);
	printf("\n");

	printf("a >> c : ");
	bin(a >> c, 1);
	printf("\n");

	printf("~a : ");
	bin(~a, 1);
	printf("\n");
	return 0;
}

void bin(unsigned char n, int cnt) {
	if (n == 1) {
		printf("%d", n);
		if (cnt % 4 == 1) {
			printf(",");
		}
	}
	else if (n > 1) {
		bin(n / 2, cnt + 1);
		printf("%d", n % 2);
		if (cnt % 4 == 1 && cnt != 1) {
			printf(",");
		}
	}
}

 

 

상세한 코드는 아주 길다.

 

그리고 함수를 알아야 이해할 수 있다.

 

따라서 중요한 개념적인 부분만 보자.

 

상세하게 보고 싶다면 위에 접은 글을 봐주기를 바란다.

 

int main() {
	unsigned char a = 0b00101011;
	unsigned char b = 0b00011010;
	int c = 1;
	printf("a : ");
	bin(a, 1);
	printf("\n");

	printf("b : ");
	bin(b, 1);
	printf("\n\n");

	printf("a & b : ");
	bin(a & b, 1);
	printf("\n");

	printf("a | b : ");
	bin(a | b, 1);
	printf("\n");

	printf("a ^ b : ");
	bin(a ^ b, 1);
	printf("\n");

	printf("a << c : ");
	bin(a << c, 1);
	printf("\n");

	printf("a >> c : ");
	bin(a >> c, 1);
	printf("\n");

	printf("~a : ");
	bin(~a, 1);
	printf("\n");
	return 0;
}

 

지금은 상세한 설명대신 결과만 보도록하자.

 

 

결과에 대한 상세한 피드백을 해주려면 내용이 길다.

 

그래서 11장에서 다시 언급하도록 하겠다.

대입 연산자(=, +=, -=, /=, *=, %=, &=, |=, ^=, <<=, >>=) - 이항 연산자, 값에 대입하는 연산자

꽤나 여러 종류의 연산자가 있는게 대입 연산자이다.

 

그 이유는 일반적인 대입연산자와 산술연산이 포함된 대입연산자가 존재하기 때문이다.

 

비트 연산이 없는 대입 연산자 들만 보도록하자.

 

비트 연산이 포함된 대입 연산자들인 11장에서 다시 이야기하도록 하겠다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a;
	a = 10;
	printf("= 10 결과 : %d\n", a);
	a += 3;
	printf("+= 3 결과 : %d\n", a);
	a -= 2;
	printf("-= 2 결과 : %d\n", a);
	a *= 4;
	printf("*= 4 결과 : %d\n", a);
	a /= 2;
	printf("/= 2 결과 : %d\n", a);
	a %= 7;
	printf("%%= 7 결과 : %d\n", a);
	return 0;
}

대입 연산자에서 =은 아주 많이 봐왔다.

 

그래서 설명이 필요없다.

 

10을 대입한다는 뜻이지 않겠는가?

 

그런데 =앞에 사칙연산이 붙어있는 경우가 있다.

 

이 대입 연산자들의 결과를 보도록 하자.

 

 

결과를 보면 똑똑한 우리 여러분들은 충분히 이해할 수 있다.

 

<a> += <b> : <a>=<a>+<b>와 같다
<a> -= <b> : <a>=<a>-<b>와 같다
<a> *= <b> : <a>=<a>*<b>와 같다
<a> /= <b> : <a>=<a>/<b>와 같다
<a> %= <b> : <a>=<a>%<b>와 같다

그런데 왜 이런게 가능할까?

 

A = A + B는 사실 수학적으로 성립하지 않는 식이다.

 

하지만 수학의 =과 동일하게 생각하면 문제가 있다.

 

수학의 =는 좌변과 우변이 같다는 뜻이다.

 

그러나 프로그래밍의 =는 좌변에 우변을 대입해라는 뜻이다.

 

그래서 a = a + b라는 구문이 존재한다면,

 

면저 a + b라는 구문을 시행한다.

 

그 다음 a에 a+b의 값을 넣게 되는 것이다.

 

이걸 더 간편히 표현한게 a += b라는 구문이다.

 

증감 연산자(++(data), --(data), (data)++, (data)--) - 단항 연산자, 값을 1만큼을 변동 시킨다.

이 연산자는 다른 대입 연산자와 궤를 같이한다.

 

쉽게 이야기해서 값을 1을 변동시키는 연산자이다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a = 10;
	a++;
	printf("a++ : %d\n", a);
	++a;
	printf("++a : %d\n", a);
	a--;
	printf("a-- : %d\n", a);
	--a;
	printf("--a : %d\n", a);
	return 0;
}

증감 연산자는 값을 1씩 증가시켜서 값을 아예 저장하게 된다.

 

a++과 ++a는 1을 증가시킨다.

 

a--와 --a는 1을 감소시킨다.

 

 

a++, ++a는 a = a + 1 혹은 a += 1과 동일하다.

 

a--, --a는 a = a - 1 혹은 a -= 1과 동일하다.

 

이런식으로 ++이나 --가 변수 앞에 적히는 형태를 전위 연산자라고 한다.

 

반대로 ++이나 --가 변수 뒤에 적히는 형태를 후위 연산자라고한다.

 

전위 연산자와 후위 연산자는 차이가 있다.

 

위의 예제에서는 차이를 확인할 수 없다. 조금 다른 예제에서는 확인이 가능하다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a, b, c, d;
	a = b = c = d = 10;
	printf("++a : %d\n", ++a);
	printf("--b : %d\n", --b);

	printf("c++ : %d\n", c++);
	printf("d-- : %d\n", d--);

	printf("c : %d\n", c);
	printf("d : %d\n", d);
	return 0;
}

코드에서는 a, b, c, d를 10으로 초기화 시켰다.

 

그 다음 a와 b는 전위 연산자이고, c와 d는 후위 연산자를 사용하였다.

 

코드를 실행하면 둘의 차이를 명백히 알 수 있다.

 

여기서 a와 b는 바로 값이 변동한걸 확인할 수 있다.

 

그런데 c와 d는 값이 바로 바뀌지 않고 다음 호출에 바뀐걸 알 수 있다.

 

그 이유는 전위 연산자는 그 즉시 값이 바뀌고

 

후위 연산자는 다음 호출 부터 값이 바뀌게 된다.

 

즉 c와 d는 값 변경 예약이라고 생각하면된다.

 

위의 코드를 알아먹기 쉽게 바꾸면 아래와 같다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int a, b, c, d;
	a = b = c = d = 10;
	a += 1;
	printf("++a : %d\n", a);
	b -= 1;
	printf("--b : %d\n", b);

	printf("c++ : %d\n", c);
	c += 1;
	printf("d-- : %d\n", d);
	d -= 1;

	printf("c : %d\n", c);
	printf("d : %d\n", d);
	return 0;
}

 

논리 연산자(&&, ||, !) - &&와 ||는 이항 연산자, 결과가 논리값으로 출력, 단 !는 단항 연산자

논리 연산자는 저번에 말했던 진리값과 관련이 있다.

 

진리 값에 대한 이야기는 4-1장 부록, 변수는 어떻게 표현되는가에서 언급했었다.

 

일단 진리값과 논리 연산에 대해서 짤막하게 언급하겠다.

 

AND연산 - C언어에서는 &&로 표현됨, 두 조건이 모두 참이어야 참이된다.
ex) 밥을 많이 먹고 일찍 자면 키가 큰다. 여기서 밥만 많이먹으면 돼지가 되고, 일찍 자기만하면 멸치가 된다. 따라서 키가 크려면 밥을 많이 먹는 조건도 참이고 일찍 자야되는 조건도 참이어야 한다.

OR연산 - C언어에서는 ||로 표현됨, 두 조건 중 하나라도 참이면 참이된다.
ex) 잘생기거나 돈이 많으면 여자친구가 생긴다. 여기서 잘생기지 않아도 돈이 많으면 여자친구가 생기고, 돈이 없어도 잘생기면 여자친구가 생긴다. 그래서 난 여자친구가 없다.

NOT연산 - C언어에서는 !로 표현됨, 단항 연산자이며 진리값을 반대로 바꾼다.
#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)

int main() {
	bool hasGirlFriend = false;
	bool hasMoney, isHandsome;
	printf("1이면 여자친구 있는 거임\n0이면 없음\n\n");

	hasMoney = true;
	isHandsome = false;
	hasGirlFriend = hasMoney || isHandsome;
	printf("돈이 있지만 못생길 경우 : %d\n",hasGirlFriend);

	hasMoney = false;
	isHandsome = true;
	hasGirlFriend = hasMoney || isHandsome;
	printf("잘생겼지만 돈이 없는 경우 : %d\n", hasGirlFriend);

	hasMoney = false;
	isHandsome = false;
	hasGirlFriend = hasMoney || isHandsome;
	printf("못생기고 돈이 없는 경우 : %d\n", hasGirlFriend);
	return 0;
}

코드를 보면 bool을 사용하기 위해서 stdbool.h를 가져온것을 볼 수 있다.

 

bool값은 화면에 출력하면 true, false로 출력되지 않고 1, 0으로 출력된다.

 

즉 1이면 true고 0이면 false이다.

 

위의 예제는 or연산자를 사용한 예제이다.

 

 

보다시피 정직한 결과를 얻을 수 있다.

 

and와 or은 이항 연산자로 두 조건이 모두 참인걸 확인하고 싶다면 and를,

 

두 연산자중 하나만 참인걸 알고 싶다면 or을 사용하면 된다.

 

#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)

int main() {
	bool b = true;
	int num = 10;
	printf("bool을 뒤집었을 경우 : %d\n", !b);
	printf("int를 뒤집었을 경우 : %d\n", !num);
	return 0;
}

위의 예제는 !연산자를 사용한 예제이다.

 

그런데 이 녀석은 조금 조심해서 사용해야하는데,

 

논리 연산자는 결과값이 항상 true와 false, 즉 1과 0으로 나온다.

 

C언어에서는 0을 제외한 모든 값을 참으로 간주한다.

 

따라서 10이라는 숫자도 참이라고 간주해서 !를 먹이면 0이 된다.

 

비교 연산자(==, !=, <=, >=, <, >) - 이항 연산자, 둘의 동등성을 비교
#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)

int main() {
	int a = 10;
	int b = 10;
	printf("a == b : %d\n", a == b);
	printf("a != b : %d\n", a != b);
	printf("a > b : %d\n",a>b);
	printf("a >= b : %d\n", a >= b);
	printf("a < b : %d\n", a < b);
	printf("a <= b : %d\n", a <= b);
	return 0;
}

아까 =는 대입 연산자라고 했었다.

 

그러면 비교를 하려면 다른 연산자를 사용해야한다.

 

바로 == 이다. 그 외의 다른 연산자는 여러분이 생각한 그대로의 효과를 가지고 있다.

 

결과를 보면 똑똑한 여러분은 이해할 수 있겠지만 그래도 다시 한번 이야기하도록 하자.

 

== - 양쪽의 값이 같으면 참이다. 다만 나중에 나올 문자열을 이 연산자로 비교하면 절대 안된다.
!= - 양쪽의 값이 다르면 참이다. 마찬가지로 나중에 나올 문자열을 이 연산자로 비교하면 절대 안된다.
> - 왼쪽이 오른쪽 보다 크면 참이다.
>= - 왼쪽이 오른쪽 보다 크거나 같으면 참이다.
< - 왼쪽이 오른쪽 보다 작으면 참이다.
<= - 왼쪽이 오른쪽 보다 작거나 같으면 참이다.

 

양변이 같은지, 작은지, 큰지를 비교할 수 있다.

 

다만 주의 해야할점은 아직까지 배우진 않았지만 문자열을 이 연산자로 비교하면안된다.

 

아직 문자열을 배우지 않았으므로 이렇게 짧막하게 이야기 하고 넘어가겠다.

 

이까지 보다 보면 알겠지만 사실 <와 >는 취향차이이다.

 

A>B로 쓰는 사람이 있고 B<A로 쓰는 사람이 있다.

 

그래서 코드를 보다보면 일관되게 하나로만 쓰는 사람이 많다.

 

그리고 A!=B라는 코드는 !(A==B)와 동일하다.

 

그래서 이 녀석도 취향을 좀 타는것 같다.

 

조건 연산자((<조건>? <조건이 참일 때>: <조건이 거짓일 때>)) - 삼항 연산자, 조건부로 데이터를 출력

데이터를 조건부로 출력할 수 있다. 즉 분기가 가능하다.

 

아주 중요한 연산자라고도 할 수 있지만 실전에서는 약간 취향차이로 등장한다.

 

그 이유는 아직은 이야기 안했지만 뒤에 이야기할 분기문이 있기 때문이다.

 

어쨋든 조건부로 데이터를 출력한다는건 아주 중요하다.

 

#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)

int main() {
	bool isSale = true;
	int shoesPrice = 50;
	printf("신발 가격 : %d\n", isSale ? shoesPrice / 2 : shoesPrice);
	return 0;
}

가령 세일 중에는 신발을 50%가격으로 판다고 해보자.

 

그러면 위와 같은 코드를 짤 수 있을 것이다.

 

세일중이라면 25원이 될거고 아니면 50원으로 팔것이다.

 

위의 조건식에 의하면 isSale의 값이 true이면 shoePrice를 절반으로,

 

아니면 shoesPrice를 온전히 출력하게 할 것이다.

 

 

위에 신발 가격은 세일 중이므로 가격이 깎이게 된다.

 

과연 조건을 정말로 타는지는 isSale의 값을 바꾸면서 테스트해보자.

 

sizeof(<타입 혹은 변수 혹은 값>) - 단항연산자, 해당 피연산자가 시스템에서 몇 바이트인지 출력
#include <stdio.h>

int main() {
    int a = 10;

    printf("%d\n", sizeof(int));
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(10));
    return 0;
}

C언어에서는 특정 변수 혹은 타입 혹은 값의 크기가 얼마인지 확인할 수 있는 변수가 있다.

 

바로 sizeof연산자이다.

해당 연산자는 타입으로 사용할 경우(sizeof(int)) 해당 타입이 시스템에서 몇 바이트인지 알려준다.

 

그리고 변수 이름이나 값으로 할 경우에도 해당 변수나 값이 현재 시스템에서 몇 바이트에

 

저장이 되고 있는지를 확인할 수 있다.

 

사실 이런 확인차 쓰는 용도보다 더 실용적으로 사용하는 상황이 있긴 하지만

 

이는 그 때가서 확인하도록 하자.

 

#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)

int main() {
	bool a, b, c, d, e, f;
	a = b = c = d = e = f = 2;
	printf("%d\n", a++ + b-- * c / (d + e));
	return 0;
}

위와 같은 식이 있다고 가정해보자

 

과연 결과를 알 수 있을까?

 

처음 보는 사람은 이해를 못할 거다.

 

사실 복잡해지면 필자도 잘 모른다.

 

세세한 규칙이 있긴한데 필자도 다 모르기 때문에 아는것만 이야기하려고한다.

 

연산자 우선순위

1- 대부분의 단항 연산자
2 - *, /, %
3 - +, -
4 - 대부분의 이항 연산자
5 - 관계 연산자(부등호가 있는 식)
6 - 관계 연산자(부등호가 없는 식)
7 - 비트 연산자
8 - &&
9 - ||
10 - 삼항 연산자
11 - 대입 연산자

※ 같은 등급의 연산자는 쪽에서 오른쪽으로 우선순위가 결정된다. 그리고 괄호는 항상 우선순위가 앞선다. 그리고 대입 연산자와 삼항 연산자는 오른쪽에서 왼쪽으로 우선순위가 결정된다. 이건 단항 연산자들도 마찬가지

자세한 등급은 이 사이트를 참고해주면 자세히 나와있다. 

 

사실 필자도 항상 외우고 다니는건 아니라서 경우에 따라서 애매해질때가 있다.

 

그래서 우선순위 애매하다고 싶으면 바로 괄호를 사용해주면된다.

 

설사 우선순위가 문법적으로 확실할지라도

 

경우에 따라서 괄호를 써주는게 사람들이 보기 좋을 때가 많다.

 

결론은 항상 우선순위를 외우는것보단

 

그냥 괄호 적당히 사용해서 관습적으로 쓰는것이 좋다는 것이다.

Posted by Kamangs

주의!!

 

아래의 내용은 안배운 개념들을 많이 써야하고,


중요하긴 하지만 중요하지 않기 때문에?


그냥 띄어넘어도 상관없다. 정말로 몰라도 사는데 별 지장없다.


다만 조금 원리를 이해하고 쓰면 특별한 상황에서 편하기 때문에,


그리고 우리 갓갓 교수님이 어렵게 내는 경우도 있기 때문에,


부득이하게 설명을 하고 넘어가려고한다.

 

복사기 누가 고장냈...

scanf라는 함수를 쓸 때 생각보다 문제가 자주 일어 날 것이다.


이 놈의 특징이라면 "너가 알아서 재주껏 조심해서 써라"라는 것이다.


그런데 당연히 입문자들 입장에서는 뭘 알아야 쓰지 않을 것인가?


그래서 필자도 좀 대책없다고 생각할 때가 많다.


사실 이걸 제대로 알 필요가 있을까 생각들 때도 있다.


어짜피... C안쓸거 잖아...


그러니까 그냥 잘 피해서 쓰면 되지 않나?


그런데 이렇게 말하는건 내가 대책없는거 같아서 좀 상세히 설명하려고 한다.


상세히 설명하려다 보니 안배운 개념들을 아주 많이 동원해야한다.


그런데 어쩌겠나.. 동원해야지...

 

 

우리가 알아야 될것은 문자(Character), 문자열(String), 포인터(Pointer), 주소(Address), 배열(Array), 버퍼(Buffer)이다.


이는 추후에 블로그에 언급이 될 녀석들이고 이를 알아야만 아래의 내용을 이해할 수 있다.


만약 모른다면?? 그냥 안보는걸 추천한다.

 

위에도 말했지만 몰라도 인생 사는데 지장없다.

 

 

scanf는 버퍼(Buffer)를 사용하는 함수이다.


이를 좀더 풀어서 설명하자면 입력을 바로 변수에 저장하는게 아니라


잠시 버퍼라는 배열(Array)에 저장한다. 그 후에 변수로 만들 때 적절히 빼내게 된다.


문제는 이 적절히라는 부분이다.


이 적절한것은 룰이 있지만 초보자들은 당연히 이 룰을 이해하지 못한다.


정확히 말하면 모른다는게 맞겠지.

 

아래는 서식지정자가 %c가 아닌, 즉 문자가 아닐 경우의 이야기이다.


일단 scanf는 동작시에는 버퍼가 차있는지 비어있는지 확인한다.


여기서 말하는 비어있다는 정말로 버퍼의 크기가 0인것 뿐만 아니라,


\n, \t, 공백등의 문자열도 비어있다고 가정한다.

 

버퍼의 갈림길에 선 그녀


일반적으로 맨처음에는 당연히 버퍼는 비어있다.


그렇기 때문에 버퍼를 채우는 작업을 제일 먼저한다.


버퍼를 채우는 방법은 무엇일까?


뭐 예상했겠지만 사용자의 입력이다.


사용자의 입력은 당연히 키보드로 받게된다.


사용자의 입력의 종료는 강제개행(\n: Line Feed)으로 판단한다.


여기서 강제개행은 키보드의 엔터를 의미한다.(넓은 의미에서 보면 조금 다르지만.)


즉 사용자가 입력하고 나서 엔터를 치게 되면 종료가 된다.


버퍼가 채워지고나면 이제 버퍼에서 필요한 부분만큼 적절히 자른다.


또... 적절하다는 이야기가 나왔는데 여튼 적절하다는 이유는 상황마다 조금 다르기 때문이다.


그래서 그 자른 부분을 다시 변수화 시킨다.


요약하면 아래와 같다.

 

1. 버퍼가 비었는지 확인한다. 차있다면 2번을 생략하고 3번으로 간다.
2. 버퍼가 비어있다면 사용자에게 입력을 받아 버퍼를 채운다.
3. 버퍼에서 자료형에 맞게 필요한 데이터까지 적절히 빼서 변수화 시킨다.

 

그럼 이제 상황을 들어서 설명을 듣도록하자.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int num;
	scanf("%d", &num);
	return 0;
}

이러한 코드는 아주 흔하다.

여기서 만약 100을 입력하는 상황이라고 가정해보자.

 

사실 버퍼는 사용자의 입력을 무조건적으로 문자(charachter)로 인식한다.


즉 위의 경우 숫자 100이 아니라, 문자 (1, 0, 0, \n)로 인식하게 된다.


마지막에 \n이 들어가는 이유는 우리가 마지막에 \n(엔터)을 입력했기 때문이다.


일단 버퍼에 입력되어있으니 num을 숫자로 채워야한다.


그러면 과연 어디까지 숫자인지 판단해서 그 숫자만큼 채워넣게 된다.


위의 경우 1,0,0까지는 숫자이므로 이 까지 버퍼에서 빠진다.


그런데 4번째의 경우 강제개행문자이므로 이 녀석을 버퍼에서 빼지는 않고


앞에 100까지만 잘라서 가져간다.


그래서 최종적으로 num에는 100이 저장되게 된다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
  int num1;
  int num2;
  scanf("%d", &num1);
  scanf("%d", &num2);
  printf("%d %d", num1, num2);
  return 0;
}

그러면 위같은 상황은 어떻게 될까?


이 경우 일반적으로 문제없이 동작하는데 그 이유는 진행과정을 보면 알 수 있다.

 

1. 버퍼가 비어있으므로 사용자의 입력을 받는다. [1,0,0,\n]

2. 그 다음 숫자부분까지만 버퍼에서 빼내서 변수로 만든다 [\n]

3. 다시 scanf를 사용했기 때문에 버퍼가 비어있는지 확인한다.
일반적으로는 비어있지 않지만 공백 문자들은 비어있다고 가정하므로 버퍼에서 빼낸뒤 []
-> 다시 숫자를 채운다 [3,0,0,\n]

4. 그 다음 숫자부분까지만 버퍼에서 빼내서 변수로 만든다 [\n]

 

이는 정수 뿐만이 실수와 문자열도 동일하다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	char str1[100];
	char str2[100];
	scanf("%s", str1);
	scanf("%s", str2);
	printf("%s %s", str1, str2);
	return 0;
}

이 경우에도 정수와 동일하게 동작하는걸 확인할 수 있다.

 


문자열을 받을 때는 별 문제가 없다.

 

왜냐하면 문자열은 받을 수 있는 값의 제한이 없기 때문이다.


어짜피 사용자가 받은 입력을 고스란이 저장하기만 하면 되니까.


문제는 정수와 실수를 입력받을 때이다.


정수를 넣어야하는데 실수를 넣거나


실수를 넣어야하는데 문자열을 넣는 상황이 벌어졌을 때 실제로 어떻게 되느냐일 것이다.


이러한 문제는 꽤 심각한데 그 이유는 아래와 같다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int num1;
	int num2;
	printf("입력하세요 : ");
	scanf("%d", &num1);
	printf("입력한 값은 : %d\n", num1);
	printf("입력하세요 : ");
	scanf("%d", &num2);
	printf("입력한 값은 : %d\n", num);
	return 0;
}

이 예제는 입력받고 출력, 입력받고 출력하는 예제이다.

 

num1과 num2는 모두 정수를 입력받게 되어있는데

 

만약 여기에 실수를 입력한다면 어떠한 일이 벌어질까?

 

일단 세가지 문제점이 있는데

 

첫번째로는 3.14가 아니라 3이 변수에 입력되었다는거,

 

두번째로는 두번째 scanf는 동작하지 않았다는거,

 

세번째로는 num2는 초기화 되지 않았다는 점이다.

 

복기를 해보자.

 

문제점은 .은 정수로 전환할 수 없다는 점이다.

 

이제 적절하다는 것의 비밀이 풀렸을 것이다.

 

정수는 흔히 우리가 아는 정수 형태(ex: 10, +5, 0, -1),

 

실수는 흔히 우리가 아는 실수 형태(ex: .5, 1.5, -3, -3.14)

 

만을 받을 수 있다는 점이다.

 

만약 받을 수 없는 형태가 온다면??

 

그 값에서 종료해 버린다.

 

이제 문제가 왜 일어나는지 파악했을 것이다.

 

그래서 scanf는 항상 자신에게 맞는 타입을 받아야만 한다.

 

또한 항상 scanf를 동작시키려면 scanf를 동작시킨다음에 반드시 버퍼를 비워준다.

 

근데 문제는 버퍼를 어떻게 비우냐이다.

 

그 방법 역시 알려주겠다.

 

문자를 받는 상황은 특이한 상황인데 왜냐하면 문자는 못받는 타입이 없기 때문이다.

 

어짜피 입력 그자체를 저장한다는 특성 때문에

 

문자를 받는 상황은 항상 값을 받을 수 있다는 기대가 있다.

 

가령 아래의 예제를 보자.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	char c1, c2, c3;
	printf("값을 입력 : ");
	scanf("%c", &c1);
	printf("값을 입력 : ");
	scanf("%c", &c2);
	printf("값을 입력 : ");
	scanf("%c", &c3);
	printf("입력한 값은 : %c %c %c\n", c1, c2, c3);
	return 0;
}

이 예제는 마치 문자를 3개 받는 예제처럼 보인다.

 

실제로 이런 의도로 작성하는 사람이 많을 것이다.

 

그런데 실제로 이러한 코드는 생각한것 처럼 동작하지는 않을 것이다.

 

가령 입력으로 a,b,c를 받고 출력하는 상황을 가정하자.

 

하지만 생각처럼 잘 안되지않아?

 

그 이유는 버퍼 상황을 보도록하자.

 

쉽게 이야기해서 c1을 입력할때는 버퍼가 비어있어서 사용자에게 입력을 받았다.

 

그리고 a를 빼내서 c1의 값을 초기화 한다.

 

그 후 두번째 scanf를 호출 했으나 공백문자가 아직 들어있다.

 

그래서 사용자에게 입력을 받지 못한다.

 

이게 정수, 실수, 문자열에서는 "비어 있다고 판단하지만"

 

문자는 공백문자도 "비어 있다고 판단하지 않는다"

 

그러다보니 c1은 a, c2는 \n, c3는 b가 들어가게 된다.

 

그러면 원래대로 우리가 의도한대로 할려면 어떻게 해야할까?

 

이를 역이용하면된다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	char c1, c2, c3, tmp;
	printf("값을 입력 : ");
	scanf("%c", &c1);
	scanf("%c", &tmp);
	printf("값을 입력 : ");
	scanf("%c", &c2);
	scanf("%c", &tmp);
	printf("값을 입력 : ");
	scanf("%c", &c3);
	scanf("%c", &tmp);
	printf("입력한 값은 : %c %c %c\n", c1, c2, c3);
	return 0;
}

바로 맨 끝에 받는 공백 문자를 강제로 뽑아버리는 방법이다.

 

tmp라는 변수를 한번 더 호출해서 \n을 호출하는 방법인데 효과가 발군이다.

 

다만 이 방법은 모든 상황에서 완벽하지는 않다는 점을 알아뒀으면 한다.

 

이 방법으로 잘못 들어온 값을 지워버리는것 역시 가능하다.

 

힌트는 항상 마지막 값에는 공백문자(\n, \t, 스페이스 등)이 들어 있다는 점이다.

 

이를 예제로 담고 싶지만 이걸 알려면 반복문을 알아야하기 때문에...

 

부득이 하게 숙제로 남겨두겠다.

Posted by Kamangs

입력과 출력

일반적으로 입력은 우리가 컴퓨터에게 명령어를 줄 수 있는 모든 방식을 말한다.


전통적으로는 키보드, 마우스, 마이크 등이 있고 그 외에도 아주 많다.


가령 카메라, 열전도 센서, 광센서 등도 입력장치라 할 수 있다.



그리고 출력은 우리가 컴퓨터에게 정보를 받을 수 있다면 모두 출력이라고 할 수 있다.


프린터, 플로터, 3D프린터 등도 당연히 출력장치 이며


스피커, 헤드셋등의 사운드 장치, 게임기의 진동등도 당연히 출력장치,


흔히 표시장치라 말하는 모니터 역시 출력장치이다.



물론 우리는 모든 종류의 입력과 출력을 다룰 순 없다.


엄청나게 복잡하고 많은 종류가 있기 때문이다.


그래서 우리는 기본적으로 입출력은 콘솔의 입출력에 대해서 다룬다.


당연히 입력은 키보드로, 출력은 모니터로 하게된다.

 

그러면 콘솔이라는게 뭔가?

 

시스템을 물리적으로 조작할 수 있는 체계 - 어떠한 전공 책

Edvard Munch의 Blue Monday

저렇게 말하면 알아듣기 힘드니 쉽게 말해서 검은 화면에서 만지작 거린다고 생각하면 된다.

 

이제 그러면 우리가 지겹도록 봐왔던 출력 예제를 보도록하자.

 

#include <stdio.h>

int main() {
	int num = 28;
	printf("내 나이 %d살!\n", num);
	return 0;
}

추가적인 설명이 필요할까?

 

너무 많이 봐서 지겨운 예시이다.


그러면 이러한 지겨운 예제는 넘어가고 입력에 대해서 알아보도록 하자.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int num;
	scanf("%d", &num);
	printf("내 나이 %d살!\n", num);
	return 0;
}

 

앞으로 지겹게 볼 코드가 하나 더 추가되었다.

 

입력을 할때 눈여겨 볼 것은 3가지가 있다.

 

1. scanf라는 완전 새로운 함수를 사용한다는 것
2. 변수에 &를 사용한다는 것이다
3. 상단에 #pragma~로 시작되는 무언가가 존재한다는 것

여기서 하나씩 보도록 해보자.

 

 

scanf는 입력을 받는 함수이다.

 

정말 역사가 유려하게 깊은 함수이지만 살짝 문제점이 있다.

 

&는 뭔지는 아직 알 필요가 없다.


다만 아직은 간단하게 scanf를 사용할때는 &를 쓴다고 생각하면 된다.


추후에는 &를 쓰지 않는 예시도 나오지만 그건 그때가서 알아보도록 하자.

 

마지막으로 pragma에 대해서 간략하게 설명하자면 저건 윈도우에서만 사용하는건데

 

전처리 명령어...인데 이렇게 이야기하면 좀 길고

 

간단히 말하면 scanf를 일반적으로는 사용할 수 없지만 강제로 사용하겠어!!

 

라고 생각하면된다.

 

scanf는 사실 보안문제가 있는 함수라서 사용자가 꽤 신경써줘야하는 함수인데

 

그래서 윈도우에서는 일반적으로느 사용못하게 막는다.

 

그걸 해제하는 방법이 저 pragma이다.

 

다만 리눅스나 맥에서는 저 구문이 없어도 동작을 잘한다.

 

&num을 사용해서 지정하게 되면 값을 입력받는 상태가 된다.

 

scanf를 맞닥트리게 되면 프로그램은 종료되지 않고 사용자의 입력을 무한정 기다린다.


입력의 끝은 엔터로 판단하는데 중요한건 엔터를 친다고 끝난게 아니라,


값을 입력하고 엔터를 쳐야 입력의 종료로 인식한다.


여튼 허점이 많기 때문에 입력할때도 조심히 사용해야한다.

 

입력이 끝나고나면 초기화 되지 않았던 num이 내가 입력한 값으로 초기화 된다.

 

 

여기서 scanf를 사용할때도 우리는 서식 지정자를 사용했다.


scanf의 서식지정자는 어떤의미에서는 printf의 서식지정자보다 훨씬 중요하다.


그 이유는 출력을 잘못한다고 변수가 바뀌지 않지만


scanf의 경우 입력을 잘못하면 변수 자체가 바뀌기 때문이다.


그래서 입력될값의 서식지정자를 명확히 해야한다.


아직 문자를 배우진 않았지만 문자를 입력할때도 서식을 정확히 지켜줘야한다.

 

#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	char ch;
	scanf("%c", &ch);
	printf("내 형액형 %c형!\n", ch);
	return 0;
}

문자에 대해서 아직 우리는 배우지 않았지만 그래도 맛보기로 보도록하자.

 

사용하는 방식은 똑같다.

 

scanf에서 주의해야할 점이 있다.

 

바로 입력 타입을 맞게 해줘야한다는 점이다.

 

입력 타입을 맞춰서 입력하지 않으면 프로그램이 원하지 않게 동작하게 된다.

 

이건 버그나 에러가 아니라 scanf의 동작로직 때문에 어쩔수 없는 것이다.

 

설명하려면 아주아주아주 길고 어렵기 때문에 타입을 지켜야한다는것만 알아두자.

 

그 이유는 몇가지 예제를 통해서 실험할 수 있다.

 

정수를 받을려고 했지만 문자를 받는 경우
#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int num;
	printf("입력 하세요 : ");
	scanf("%d", &num);
	printf("뭐가 출력이 될까 : %d\n", num);
	return 0;
}

가령 위와 같은 코드가 존재한다고 하자.

 

그러면 우리는 num에 입력한 숫자를 출력하게 될 것이다.

 

만약 정수를 받으려고 했지만 문자를 받게되면 어떻게 될까?

결과는 받아지지 않는다이다.

 

비주얼 스튜디오에어서(윈도우)는 변수가 자동으로 0으로 초기화되는데

 

사실은 초기화 자체를 실행하지 않는다고 생각하면된다.

 

정수를 받을려고 했지만 실수를 받는 경우
#include <stdio.h>
#pragma warning(disable:4996)

int main() {
	int num;
	printf("입력 하세요 : ");
	scanf("%d", &num);
	printf("뭐가 출력이 될까 : %d\n", num);

	printf("입력 하세요 : ");
	scanf("%d", &num);
	printf("뭐가 출력이 될까 : %d\n", num);
	return 0;
}

가령 위와 같은 코드가 있다고 가정하자.

 

 

일반적으로 생각하면 입력 한번 출력 한번 입력 한번 출력 한번일 것이다.

 

그리고 뭐 틀린말은 아니다.

 

하지만 이 때 실수가 들어가는 경우 어떻게 될까?

 

보면알겠지만 입력은 단 한번만 받고,

 

출력은 계속 된다. 그리고 입력을 추가적으로 받지 않는다.

 

이 이유를 설명... 하고 싶긴한데 사실 공수에 비해서 얻는게 별로 없다.

 

그리고 이 때까지 배우지 않은 개념을 들어서 설명해야하기 때문에 조금 어렵다.

 

그래서 일단은 이 궁금증을 띄어 넘고 6장으로 넘어가는 것을 권한다.

 

다만 여기에 대해서 심도깊은 이해를 필요로 하는 사람이 존재하기에

 

부득이 하게 5-1장을 만들어서 설명하려고하니 이해가 필요한 사람들은 봐주기를 바란다.

 

C언어는 오래되다보니 scanf를 아주 오랜기간동안 써왔다.

 

그러나 scanf는 보안 문제가 있다고 이야기하였다.

 

사실 문자나 정수등의 변수들을 사용할때는 scanf_s가 별 의미가 없다.

 

문자열을 사용할 때나 의미가 있다.

 

https://devdocs.io/c/io/fscanf

C언어 표준에서 보면 2011년에 포함된 표준이라는걸 알 수 있다.

 

즉 따끈따끈한 녀석이다. (그런데 지금은 2019...)

 

#include <stdio.h>

int main() {
	int num;
	char ch[100];
	printf("입력 하세요 : ");
	scanf_s("%d",&num);
	printf("%d 이지롱!\n",num);
	return 0;
}

사용예제를 보아도 scanf랑 똑같다.

 

다만 사라진 점이 pragma를 더이상 쓰지 않아도 된다는 점이다.

 

그 이유는 scanf가 위험한 함수라고 쓰지말라고 했는데 그걸 안써서 이제 에러가 안나는 것이다.

 

그리고 현재는 C언어의 표준으로 지정된 상태이므로 사실 scanf_s를 쓰는게 더 나을것이다.

 

#include <stdio.h>

int main() {
	int num;
	char ch[100];
	printf("입력 하세요 : ");
	scanf_s("%d %s",&num ,ch, sizeof(ch));
	printf("%d %s\n",num, ch);
	return 0;
}

 

scanf_s는 문자열과 함께 쓸 때 문제가 된다.

 

scanf와 scanf_s의 차이점은 문자열에서 크기를 넘겨주느냐 마느냐이다.

 

그런데 우리는 아직 문자열에 대해서 배우지 않았으므로 이는 추후에 언급하도록 하겠다.

Posted by Kamangs

아래의 내용은 현재 배운 개념만으로는 아직 다 이해 못할 수 있다.

 

어자피 중요한 것은 앞으로도 계속 추가적인 언급이 있을것이므로 이 장을 띄어 넘어도 좋다.

 

다만 추후에 다시 서식지정자에 대해 조금 알고 싶다면

 

다시 이 포스팅으로 돌아와서 확인하도록 하자.

 

 

뭔가에 대해서 출력할 때는 서식지정자라는걸 사용한다고했고


몇가지 서식지정자를 보았다. 대표적인게 %d와 %f일 것이다.

 


그런데 서식지정자가 그것밖에 없나?


아니다 많다. 애당초 서식지정자는 간단하지만은 않다.


다만 이를 모두 외울 필요는 없는데 그이유는 사실 90%이상의 서식지정자는


몰라도 어떻게 의쌰의쌰?하면 해결할 수 있기 때문이다.


이는 나중에 배우게될 조건문과, 분기문을 조합해서 몰라도 구현할 수 있다.



그래서 현업에서도 보면 이 서식 지정자를 다 모르고 사용하는 사람도 많다.


그런데 별 문제없이 사용한다.


그래서 사실 이걸 할까 말까 고민하다가 결국 사이드로빼서 하게 되었다.

 


여기서는 아직 배우지 못한 문자와 문자열이라는 개념도 존재한다.


그래서 자세히 설명하지는 않겠다.

 

센세... 쉬는 시간이에요...

 

자세히 설명하려면 길어지기 때문이다.


선생님이 수업 5분남기고 중요한거 있으니까 조금만 더한다고


한다음에 쉬는시간 다써버리는 경우 있잖아.


난 그런 경우를 만들고 싶지는 않다.

 

일단 정수를 출력하는건 좀 많이 써봤다.


%d, %i, %f는 써봤는데 더 있을까?


불행하게도... 더있다. 생각보다는 많이 있다 심지어.


종류는 아래와 같다.

 

변환 지정자(Conversion Specifier)

 

이러한 것을 변환 지정자(Conversion Specifier)라고 한다.

 

우리가 써봤던 것들도 있고 안써봤던것도 있다.

 

디버깅용으로 자주 쓰는건 8진수, 16진수, 주소 출력을 주로 쓴다.

 

또한 옵션도 존재한다.

 

 

위는 한번도 사용한적 없겠지만 서식지정자 옵션이다.

 

서식 지정자 옵션을 이용해서 여러가지 상황을 만들 수 있다.

 

이렇게 보면 이해가 잘 안될 수 있다.

 

그런데 예시로 풀려면 너무 양이 많아지므로 자주 쓰는것을 위주로 이야기를 하려고한다.

 

#include <stdio.h>

int main() {
	char c = 65;
	printf("%d\n", c);
	printf("%c\n", c);
	return 0;
}

이 때까지 %d만 막 썼는데 %c는 문자를 출력한다고 했다.

한번 결과를 보자.

 

 

일단 두가지 문제점이 있다.

 

1. 왜 char인데 %hhd가 아니라 %d이냐?

2. 같은 값인데 왜 출력이 다르냐?

 

하나하나 답변을 해드리겠다.

 

char인데 %d를 출력해도 상관없다.

 

그 이유는 정수형의 경우 작은 크기의 단위는 큰 크기의 단위로 항상 출력할 수 있다.

 

즉 %d뿐만 아니라 %ld, %lld모두 사용 가능하다.

 

그리고 부작용도 없다.

 

그렇기 때문에 사실은 %hhd는 거의 쓸 필요가 없다.

 

그냥 정수를 출력할때는 %d라고 생각하면 된다.

 

그리고 같은 값인데 출력이 다른 이유는 컴퓨터는 어짜피 값을 모르기 때문이다.

 

이는 4장 1편 포스팅인 변수의 표현에서 재확인할 수 있다.

 

 

이제 진수표현법을 알려드리도록 하겠다.

 

C언어에서 표현가능한 진수는 총 4가지이다.

 

표현 할때는 앞에 접두사(Prefix)를 사용하게 된다.

 

2진수는 0b, 8진수는 0, 10진수는 없고 16진수는 0x를 붙힌다.

 

진수 표현을 알아줬으니 과연 결과가 같을지 코드로 보자.

 

#include <stdio.h>

int main() {
	int num_bin = 0b01000001;
	int num_dec = 65;
	int num_oct = 0101;
	int num_hex = 0x41;
	printf("%d\n",num_bin);
	printf("%d\n", num_dec);
	printf("%d\n", num_oct);
	printf("%d\n", num_hex);
	return 0;
}

 

계산해보면 저 넷은 같아야한다.

 

그러면 과연 넷은 같을까??

 

보면 알겠지만 값이 같다.

 

사실 진수를 적는건 내부적으로는 별로 중요하지않다.

 

2, 8, 10, 16진수는 사람이 보는거고 내부는 어짜피 2진수로 저장된다.

 

그래서 출력할때는 포맷형식만 정해주면 그 포맷형식에 맞게 출력된다.

 

하지만 가끔은 출력자체를 진수에 맞게 하고싶을 때가 있다.

 

과연 방법이 없을까??? 라고 생각했을때 위의 서식지정자를 보면 있다는걸 알 수 있다!

 

#include <stdio.h>

int main() {
	int num = 65;
	printf("%d\n", num);
	printf("%o\n", num);
	printf("%x\n", num);
	return 0;
}

 

위의 코드를 보면 65를 저장한 num을 여러가지 포맷으로 출력하는걸 확인할 수 있다.

 

그리고 각각 진수에 맞게 출력된는걸 확인할 수 있다.

 

그런데 2진수는 없어서 이상할 것이다...

 

그렇다 사실 2진수는 출력 포맷이 지원되지 않는다.

 

그래서 여러분이 만들어서 써야한다.

 

근데 이거 출력하는게 꽤 난이도가 있는데 나중에 이야기하도록 하겠다.

 

아직 변환 지정자가 더 있지만 다 하기에는 양도 많고

 

새로 배워야할것도 많으니 빨리 빨리 옵션으로 넘어가자.

 

사실 위에 배운것만으로도 충분히 잘 활용할 수 있다고 생각한다.

 

 

위에서 봤지만 까먹었을테니 다시 보도록하자.

 

저게 뭘 의미하는지는 예시를 보면 알 수 있다.

 

자주쓰는것만 보여드릴테니 나머지는 직접 해보길 바란다.

 

#include <stdio.h>

int main() {
	int num = 65;
	printf("%5d\n", num);
	return 0;
}

서식 지정자 앞에 숫자를 붙힐 수 있다.

 

이걸 서식 필드(Format Field)라고 부르는데 기본적으로 서식 필드의 크기는 0이다.

 

다만 출력 값이 서식 필드의 크기를 넘어설 경우 출력값이 우선시된다.

 

정리하자면 서식 필드가 더 크면 서식 필드의 크기를, 작으면 값의 크기가 출력 크기가 된다.

 

위의 경우 서식 필드를 5로 명시적으로 지정했으므로 필드는 5개의 공간을 가지게된다.

 

65는 2개의 필드를 차지하는데 우리는 5개를 지정했으므로 출력 필드는 5가 된다.

 

이 경우 나머지 3개의 칸은 공백으로 채워지게된다.

 

그리고 숫자는 우측정렬이 된다.

 

#include <stdio.h>

int main() {
	int num = 65;
	printf("%-5d!!\n", num);
	return 0;
}

이 때 -옵션을 붙혀주면 좌측 정렬이 된다.

 

보면 필드의 좌측으로 정렬된걸 볼 수 있다.

 

#include <stdio.h>

int main() {
	int num1 = 65;
	int num2 = -65;
	printf("%+5d\n", num1);
	printf("%+5d\n", num2);
	return 0;
}

여기서 +옵션을 붙히면 양수도 기호를 표현하게된다.

 

원래는 음수만 기호를 표현했는데 양수도 표현된다.

 

중요한 것은 0도 양수로 취급해서 기호를 붙힌다는 점이다.

 

결과를 봤을때 기호도 필드를 한칸 차지한다는걸 알 수 있다.

 

그리고 일반적으로 회계쪽이 아니라면 양수에는 기호를 붙히지 않는다.

 

그런데 이는 가끔가다 자리를 맞춰서 출력하는 상황에서 문제가 생길 수 있다.

 

그래서 이를 위한 또다른 옵션이 있다.

 

#include <stdio.h>

int main() {
	int num1 = 65;
	int num2 = -65;
	printf("% d\n", num1);
	printf("% d\n", num2);
	return 0;
}

옵션에 공백옵션이 있는데 이를 넣으면 부호가 양수일 경우 공백을 넣게된다.

 

결과를 보면 알겠지만 양수도 한자릴 비워둔걸 확인할 수 있다.

 

나머지는 자주 사용을 안하거나 보기만 해도 이해가 될것이므로 여기까지 알아보도록하자.

Posted by Kamangs