혼공학습단 C 2주차📚

하영·2024년 1월 9일
0

혼공학습단

목록 보기
8/13
post-thumbnail

기본 미션 📣

3-2 확인문제 1번 | ③ scanf("%d%f", &in, &ft);
3-2 확인문제 2번 | ① fruit, ② &cnt
3-2 확인문제 3번 |

#include <stdio.h>

int main(void)
{
	char ch;

	printf("문자 입력 : ");
	scanf("%c", &ch);
	printf("%c문자의 아스키 코드 값은 %d입니다.\n", ch, ch);

	return 0;
}

4-2 확인문제 1번 |

#include <stdio.h>

int main(void)
{
	int res;
	res = sizeof(short) > sizeof(long);		
	printf("%s\n", (res == 1) ? "short" : "long");

	return 0;
}

4-2 확인문제 2번 |

#include <stdio.h>

int main(void)
{
	int seats = 70;			
	int audience = 65;		
	double rate;			
	  
	rate = (double)audience / (double)seats * 100.0;
	printf("입장률 : %.1lf%%\n", rate);		

	return 0;
}

4-2 확인문제 3번 |

#include <stdio.h>

int main(void)
{
	int hour, min, sec;		
	double time = 3.76;		
	 
	hour = (int)time;		
	time -= hour;			
	time *= 60.0;			
	min = (int)time;		
	time -= min;			
	time *= 60.0;			
	sec = (int)time;		
	printf("3.76시간은 %d시간 %d분 %d초입니다.\n", hour, min, sec);		
    
	return 0;
}

선택 미션 📣

4-2 도전 실전 예제 |

#include <stdio.h>

int main(void)
{
	double weight, height, BMI;

	printf("몸무게(kg)와 키(cm) 입력 : ");
	scanf("%lf%lf", &weight, &height);

	height = height / 100.0;
	BMI = weight / (height * height);

	printf("%s\n", (BMI >= 20.0) && (BMI < 25.0) ? "표준입니다." : "체중관리가 필요합니다.");

	return 0;
}

03-1 변수

소스 코드 3-1.c

#include <stdio.h>

int main(void)
{
	int a;
	int b, c;
	double da;
	char ch;

	a = 10;
	b = a;
	c = a + 20;
	da = 3.5;
	ch = 'A';

	printf("변수 a의 값 : %d\n", a);
	printf("변수 b의 값 : %d\n", b);
	printf("변수 c의 값 : %d\n", c);
	printf("변수 da의 값 : %.1lf\n", da);
	printf("변수 ch의 값 : %c\n", ch);

	return 0;
}

변수를 선언할 때는 자료형 변수명으로 다음과 같이 선언한다.

자료형 변수명;
int a;
double da;
char ch;

변수를 선언했다면 반드시 초기화를 해야하는데 초기화 방법은 다음과 같다.
여기서 '='은 대입 연산자로 오른쪽 값을 왼쪽에 할당한다는 의미이다.

a = 10;
b = a;
c = a + 20;
da = 3.5;
ch = 'A';

📍 정수 자료형

변수는 데이터를 저장하는 메모리의 공간이며 데이터 종류에 따라 다양한 형태를 사용한다.
이와 같은 변수의 형태를 자료형 또는 데이터형이라 한다.

같은 정수형이라도 메모리 저장 공간의 크기에 따라 char, short, int, long, long long으로 구분된다.
자료형의 저장 범위는 다음 공식에 따라 계산된다.

-2비트 수 -1 ~ 2비트 수 -1 -1

예를 들어 char형은 크기가 1바이트로 8비트이다.
따라서 값의 저장 범위는 -2⁷ ~ 2⁷-1, 결국 -128 ~ 127의 값을 저장할 수 있다.


소스 코드 3-2.c

#include <stdio.h>

int main(void)
{
	char ch1 = 'A';
	char ch2 = 65;

	printf("문자 %c의 아스키 코드 값 : %d\n", ch1, ch1);
	printf("아스키 코드 값이 %d인 문자 : %c\n", ch2, ch2);

	return 0;
}

5행은 ch1에 문자 'A'를 저장하지만, 그 아스키 코드 값이 65이므로 결국 ch1에 65를 저장한 것과 같은 효과를 낸다. 따라서 5행과 6행에서 각각 다른 방식으로 초기화했지만, 결국 같은 값이 저장된다.

	char ch1 = 'A';
	char ch2 = 65;

📍 unsigned

정수형은 보통 양수과 음수를 모두 저장하지만, 양수만 저장하면 두 배 더 넓은 범위의 값을 저장할 수 있다. 따라서 음수가 없는 데이터를 저장할 때는 unsigned 를 사용하면 된다.

unsigned 자료형을 사용할 때는 출력 시 변환 문자 사용에 주의해야 하는데, 다음 예제를 보면

소스 코드 3-4.c

#include <stdio.h>

int main(void)
{
	unsigned int a;

	a = 4294967295;
	printf("%d\n", a);		// 실행 결과 : -1
	a = -1;
	printf("%u\n", a);		// 실행 결과 : 4294967295
	
	return 0;
}

실행 결과가 다음과 같이 반대로 나오게 된다. 이유는 변환 문자에 있는데, %d는 부호까지 생각해서 10진수를 출력하고, %u는 부호 없는 10진수를 출력하는 변환 문자이기 때문이다.


이렇게 변환 문자에 따라 잘못된 값을 출력하고 결과가 예상과 다를 수 있기 때문에
unsigned 자료형을 사용할 때는 항상 양수만 저장하고 %u로 출력해야 한다.


📍 실수 자료형

실수를 나타내는 자료형은 가장 작은 float도 4바이트로 저장 범위가 큰 편이다.
실수 자료형은 값의 범위보다 유효 숫자의 개수에 주목해야 하는데,

소스 코드 3-5.c

#include <stdio.h>

int main(void)
{
	float ft = 1.234567890123456789;
	double db = 1.234567890123456789;

	printf("float형 변수의 값 : %.20f\n", ft);		// 실행 결과 : 1.23456788063049316406
	printf("double형 변수의 값 : %.20f\n", db);		// 실행 결과 : 1.23456789012345669043

	return 0;
}

실행 결과를 보면 결과 값이 초깃값과 다른 것을 볼 수 있다. 이렇게 값이 달라지는 이유는 컴퓨터에서 실수를 표현하는 방식에 오차가 있기 때문이다.

그렇기 때문에 실수형을 표현할 때는 유효 숫자 범위 내에서 값을 사용하는 것이 좋다.


📍 문자열

문자열을 저장할 때는 char형을 배열 형태로 만들어 저장한다.

char 배열명[문자열길이+1] = 문자열;

char fruit[6] = "apple";

여기서 문자열의 길이보다 배열의 크기를 하나 더 크게 잡는 이유는
컴파일러가 문자열의 끝에 ∖0(널 문자)을 자동으로 추가하기 때문이다.

또 배열에서는 대입 연산자를 사용할 수 없기 때문에, 초기화 이외에 문자열을
저장할 때는 strcpy(string copy) 함수를 사용한다.

소스 코드 3-7.c

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

int main(void)
{
	char fruit[20] = "strawberry";

	printf("%s\n", fruit);			// 실행 결과 : strawberry
	strcpy(fruit, "banana");
	printf("%s\n", fruit);			// 실행 결과 : banana

	return 0;
}

📍 const

const는 변수를 선언할 때 앞에 붙여 변수를 상수처럼 사용할 수 있게 해주는데,
const를 사용하면 이후에는 값을 바꿀 수 없으므로 반드시 선언과 동시에 초기화해야 한다.

const를 사용한 변수는 다음과 같은 형식으로 선언된다.

const 자료형 변수명 =;
const double tax_rate = 0.12;

const는 상수처럼 쓰이지만, C문법이 제공하는 변수의 특성을 모두 가지는 엄연한 변수이다.
따라서 상수의 특징을 가지면서도 변수의 특성을 이용할 수 있게 된다.


03-2 데이터 입력

📍 scanf

scanf 함수는 키보드에서 입력한 값을 변수에 저장할 때 사용하는 함수로,

scanf("%d", &a);

변수의 형태에 맞는 변환 문자를 사용하고 입력할 변수 앞에 &(앰퍼샌드)를 붙이면 된다.

scanf 함수를 이용할 때 주의할 점은 변환 문자와 입력 형태가 반드시 같아야 한다는 점이다.
예를 들어 %d를 지정했는데 abc를 입력하면 정수로 변환할 수 없으므로 함수 실행이 중단된다.
결국 입력에 실패하면 변수 a에 있던 쓰레기 값이 출력되게 된다.

그러니 scanf 함수를 사용할 땐 변환 문자와 입력 형태를 동일하게 사용해야 한다.


문자와 문자열을 입력할 때는 scanf의 입력 방식이 조금 달라지게 되는데,

char형 변수에 문자를 입력할 때는 키보드로 입력하는 모든 문자들, Space Bar (공백 문자) 나 Enter (개행 문자) 도 하나의 문자로 전달된다.

또 문자열은 char 배열에 %s 변환 문자를 사용해 입력하는데, 문자열을 입력할 때는 배열명에 &를 붙이지 않는다. 또한 스페이스나 엔터, 탭 등을 만나면 바로 전까지만 저장되므로 공백 없이 연속으로 입력해야 한다.

소스 코드 3-11.c

#include <stdio.h>

int main(void)
{
	char grade;
	char name[20];

	printf("학점 입력 : ");
	scanf("%c", &grade);
	printf("이름 입력 : ");
	scanf("%s", name);
	printf("%s의 학점은 %c입니다.\n", name, grade);

	return 0;
}

만약 학점을 입력할 때 Enter 만 누른다면 Enter 에 해당하는 제어 문자 ∖n이 grade변수에 저장되게 된다. 이름을 입력할 때도 '홍'과 '길동'을 분리해서 입력하면 배열에는 '홍'만 입력되게 된다.

실행 결과

학점 입력 :
이름 입력 : 홍 길동
홍의 학점은
입니다.


04-1 산술 연산자, 관계 연산자, 논리 연산자

📍 나누기 연산자와 나머지 연산자

나누기는 나누기 연산자인 /을 사용한다. 나누기 연산자를 사용할 때는 피연산자의 형태에 따라 결과가 달라지니 주의해야 한다.

소스 코드 4-2.c

#include <stdio.h>

int main(void)
{
	double apple;
	int banana;
	int orange;

	apple = 5.0 / 2.0;
	banana = 5 / 2;
	orange = 5 % 2;

	printf("apple : %.1lf\n", apple);		// 실행 결과 : 2.5
	printf("banana : %d\n", banana);		// 실행 결과 : 2
	printf("orange : %d\n", orange);		// 실행 결과 : 1
		
	return 0;
}

몫을 뺀 나머지만 구할 때는 나머지 연산자인 %를 사용한다.

	orange = 5 % 2;

실수 연산에는 나머지의 개념이 없으므로 나머지 연산자의 피연산자로는 반드시 정수만을 사용한다.

📍 증감 연산자

증감 연산자는 단항 연산자로 피연산자의 값을 1 증가시키거나 1 감소시킨다. 증감 연산자를 사용해서 a = a + 1을 ++a로 표현한다는 말이다.

소스 코드 4-3.c

#include <stdio.h>

int main(void)
{
	int a = 10, b = 10;

	++a;
	--b;

	printf("a : %d\n", a);		// 실행 결과 : a = 11
	printf("b : %d\n", b);		// 실행 결과 : b = 9

	return 0;
}

여기서 중요한 점은 증감연산자는 ++10과 같이 상수에 직접 증감 연산자를 사용할 수 없다.
10에 1을 더할 수는 있지만, 상수 10에 11을 저장할 수 없고 그 값을 바꿔서도 안 되기 때문이다.

증감연산자는 위치에 따라 피연산자 앞에 놓이면 전위 표기, 뒤에 놓이면 후위 표기라고 하는데, 전위 표기는 값이 증감하고 나서 연산에 사용하고, 후위 표기는 연산에 사용하고 나서 값이 증감한다는 차이가 있다.

소스 코드 4-4.c

#include <stdio.h>

int main(void)
{
	int a = 5, b = 5;
	int pre, post;

	pre = (++a) * 3;	// 전위형 증감 연산자
	post = (b++) * 3;	// 후위형 증감 연산자

	printf("증감 연산 후 초깃값 a = %d, b = %d\n", a, b);
	printf("전위형 : (++a) * 3 = %d, 후위형 : (b++) * 3 = %d\n", pre, post);

	return 0;
}

증감 연산자를 단독으로 사용할 때는 전위 표기든 후위 표기든 상관이 없지만, 다른 연산자와 함께 쓰이면 연산의 결과에 영향을 미치게 된다.

실행 결과

증감 연산 후 초깃값 a = 6, b = 6
전위형 : (++a) * 3 = 18, 후위형 : (b++) * 3 = 15

실행 결과를 보면 전위 표기를 사용한 식은 18이 나오고 후위 표기를 사용한 식은 15가 나온다. 물론 연산이 끝난 후에 a와 b의 값은 모두 1씩 증가된다.

📍 관계 연산자

관계 연산자에는 대소 관계 연산자와 동등 관계 연산자가 있다. 대소 관계는 < 또는 > 등의 기호를 사용하고, 동등 관계는 ==(같다)나 !=(같지 않다) 기호를 사용한다. 이들 연산자는 모두 피연산자를 2개 사용하며, 연산의 결괏값은 1 또는 0이다.

소스 코드 4-5.c

#include <stdio.h>

int main(void)
{
	int a = 10, b = 20, c = 10;
	int res;

	res = (a > b);						// 거짓이므로 결괏값은 0
	printf("a > b : %d\n", res);	 
	res = (a >= b);						// 거짓이므로 결괏값은 0
	printf("a >= b : %d\n", res);
	res = (a < b);						// 참이므로 결괏값은 1
	printf("a < b : %d\n", res);
	res = (a <= b);						// 참이므로 결괏값은 1
	printf("a <= b : %d\n", res);
	res = (a <= c);						// 참이므로 결괏값은 1
	printf("a <= c : %d\n", res);
	res = (a == b);						// 거짓이므로 결괏값은 0
	printf("a == b : %d\n", res);
	res = (a != c);						// 거짓이므로 결괏값은 0
	printf("a != c : %d\n", res);

	return 0;
}

📍 논리 연산자

논리 연산자는 논리 관계를 판단하는 데 사용하며 &&(AND), | |(OR), ! (NOT) 3가지이다.

&&는 논리곱(AND) 연산자로 2개의 피연산자가 모두 참일 때만 연산 결과가 참이 된다.
| |는 논리합(OR) 연산자로 둘 중에 하나라도 참이면 참이 됩니다.
!는 논리부정(NOT) 연산자이며 피연산자를 하나 사용해 그 참과 거짓을 바꿀 때 사용한다.

소스 코드 4-6.c

#include <stdio.h>

int main(void)
{
	int a = 30;
	int res;

	res = (a > 10) && (a < 20);						// 거짓이므로 결괏값은 0
	printf("(a > 10) && (a < 20) : %d\n", res);
	res = (a < 10) || (a > 20);						// 참이므로 결괏값은 1
	printf("(a < 10) || (a > 20) : %d\n", res);
	res = !(a >= 30);								// 거짓이므로 결괏값은 0
	printf("!(a >= 30) : %d\n", res);

	return 0;
}

&&와 | |는 숏 서킷 룰이 적용되는데, 숏 서킷 룰이란 좌항만으로 &&와 | | 연산 결과를 판별하는 기능이다.

예를 들어 &&는 좌항이 거짓이면 우항과 관계없이 결과는 거짓이므로 우항을 살펴볼 필요가 없다.
또한 | |는 좌항이 참이면 우항과 관계없이 결과가 참이다.

즉, && 연산 때 좌항이 거짓이거나, | | 연산 때 좌항이 참이면 우항은 아예 실행되지 않는다.

04-2 그 외 유용한 연산자

📍 형 변환 연산자

형 변환 연산자는 피연산자를 하나 가지며 피연산자의 값을 원하는 형태로 바꾼다. 형 변환 연산자를 사용해서 피연산자의 형태를 바꿀 때는 피연산자의 값을 복사해 일시적으로 형태를 바꾸므로 연산 후 메모리에 남아 있는 피연산자의 형태나 값은 변하지 않는다.

소스 코드 4-8.c

#include <stdio.h>

int main(void)
{
	int a = 20, b = 3;
	double res;

	res = ((double)a) / ((double)b);
	printf("a = %d, b = %d\n", a, b);
	printf("a / b의 결과 : %.1lf\n", res);

	a = (int)res;
	printf("(int) %.1lf의 결과 : %d\n", res, a);

	return 0;
}

정수인 값을 실수로 일시적으로 바꾸고 싶을 땐 변수명 앞에 괄호로 (double)을 넣어주면 된다.

res = ((double)a) / ((double)b);

처음부터 a와 b를 double형으로 선언하면 편할 듯하지만, double형은 저장 공간이 크고 연산 속도가 느리며 무엇보다도 오차가 발생하므로 int형을 기본적으로 사용하고 실수 연산 결과가 필요할 때만 형 변환해서 사용하는 편이 좋다.

이 밖에도 12행처럼 실수에서 정수 부분만을 추릴 때도 유용하게 쓰인다. 이때 소수점 이하의 값은 반올림하지 않고 무조건 버린다.

a = (int)res;

자동 형 변환

컴파일러는 컴파일 과정에서 피연산자의 형태가 다르면 형태를 일치시키는 작업을 수행하는데,
이를 자동 형 변환이라 한다. 기본 규칙은 데이터 크기가 작은 값이 크기가 큰 값으로 바뀌게 되는데, 예를 들어 정수(4바이트)와 실수(8바이트)를 연산하면 정수가 실수로 자동 변환되어 연산된다.

하지만 대입 연산은 메모리에 값을 저장하므로 무조건 좌항의 변수형에 맞게 저장된다. 자동 형 변환은 형태가 다른 데이터를 자유롭게 연산할 수 있도록 도와주지만, 예상치 못한 값의 변형이 생길 수 있으니 가능하면 피연산자의 형태를 같게 맞춰 사용하는 편이 좋다.

📍 sizeof 연산자

sizeof 연산자는 피연산자를 하나만 사용할 수 있으며 피연산자의 크기를 바이트 단위로 계산해서 알려 준다. sizeof(피연산자)로 표기하며 피연산자의 대상은 변수, 상수, 수식, 자료형 등이 될 수 있다.

이 연산자는 데이터의 크기를 확인하거나 메모리를 동적으로 할당하는 작업 등에 유용하게 사용된다.

📍 복합대입 연산자

복합대입 연산자는 a = a + 1과 같은 식을 a+=1로 나타낸다. 산술 복합대입 연산자는 +=, -=, *=, /=, %= 5가지가 있다.

소스 코드 4-10.c

#include <stdio.h>

int main(void)
{
	int a = 10, b = 20;
	int res = 2;

	a += 20;
	res *= b + 10;

	printf("a = %d, b = %d\n", a, b);
	printf("res = %d\n", res);

	return 0;
}

복합대입 연산자는 대입 연산자의 특징, 왼쪽 피연산자는 반드시 변수가 와야 하고 오른쪽 항의 계산이 모두 끝난 후 가장 마지막에 계산한다는 것을 그대로 가지고 간다.

res *= b + 10;

대입 연산자의 특징을 그대로 가지고 가기 때문에 9행도 b + 10을 먼저 수행한 후
그 결과와 res 값을 곱해 다시 res에 저장해야 한다.

📍 조건 연산자

조건 연산자는 유일한 삼항 연산자로 ?와 : 기호를 함께 사용해 표현한다. 조건 연산자는 첫 번째 피연산자가 참이면 두 번째 피연산자가 결괏값이 되고, 첫 번째 피연산자가 거짓이면 세 번째 피연산자가 결괏값이 된다.

(a > b) ? a : b; 		

즉, 조건식이 참이면 a를 선택하고 거짓이면 b를 선택한다.

소스 코드 4-12.c

#include <stdio.h>

int main(void)
{
	int a = 10, b = 20, res;

	res = (a > b) ? a : b;
	printf("큰 값 : %d\n", res);		// 실행 결과 : 20

	return 0;
}

📍 비트 연산자

비트 연산자는 데이터를 비트 단위로 연산한다. 비트 연산자에는 논리 연산을 수행하는 & , | , ^, 같은 비트 논리 연산자와 비트를 좌우로 움직이는 >>, << 같은 비트 이동 연산자가 있다. 비트 연산자는 데이터를 비트로 정확히 표현할 수 있는 정수에만 사용할 수 있다.

소스 코드 4-13.c

#include <stdio.h>

int main(void)
{
	int a = 10;
	int b = 12;

	printf("a & b : %d\n", a & b);
	printf("a ^ b : %d\n", a ^ b);
	printf("a | b : %d\n", a | b);
	printf("~a : %d\n", ~a);
	printf("a << 1 : %d\n", a << 1);
	printf("a >> 2 : %d\n", a >> 2);

	return 0;
}

실행 결과

a & b : 8
a ^ b : 6
a | b : 14
~a : -11
a << 1 : 20
a >> 2 : 2

비트별 논리곱 연산자

변수 a에 저장되어 있는 비트와 b에 저장되어 있는 비트의 각 위치별로 하나씩 피연산자로 생각하고 논리곱 연산을 수행한다. 0은 거짓이고 1은 참이다.

비트별 배타적 논리합 연산자

배타적 논리합의 진리값은 두 피연산자의 진리값이 서로 다를 때만 참이 된다.

비트별 논리합 연산자

두 피연산자를 각각 비트별로 논리합 연산( | )한다.

비트별 부정 연산자

비트별 부정 연산자(~)는 피연산자가 하나며, 피연산자의 비트를 반전시킨다. 따라서 양수인 경우 부호 비트가 0에서 1로 바뀌므로 음수가 된다.

비트 이동 연산자

<< 은 비트를 왼쪽으로 이동시키고, >> 은 오른쪽으로 이동시킨다.

<< 은 값을 왼쪽으로 한 비트씩 이동할 때마다 2가 곱해진다고 보면 된다.
이때 왼쪽으로 밀려나는 비트는 사라지고 오른쪽의 남는 비트는 0으로 채워진다.

반대로 >> 은 값이 2로 나눈 몫이 되고, 오른쪽으로 밀려나는 비트는 사라지며, 왼쪽의 남는 비트는 부호 비트로 채워진다. 따라서 비트 이동 연산을 수행한 후에도 a의 부호는 변하지 않는다. 다만 변수의 자료형이 unsigned로 선언되었다면 부호 비트의 의미가 없으므로 왼쪽의 남는 비트는 항상 0으로 채워진다.

0개의 댓글