cyphen156
Chapter3 변수와 상수, 대입문과 연산 본문
1. 변수(Variable)와 상수(Constant)
우리는 이전 챕터에서 C언어가 사용하는 자료형에 대해서 배웠습니다.
변수는 컴퓨터가 이 자료형들을 이용하기 위해 만들어놓은 임시 공간이라고 생각할 수 있고, 언제든 그 값(Value)를 바꿀 수 있는것을 말합니다.
상수는 변수와 유사하지만 한번 대입되거나 선언되고 나면 그 값을 바꿀 수 없는 것을 말합니다.
한마디로 일종의 고정불변의 진리가 되도록 만들어놓은 것이죠.
변수의 대입
이 변수라는 것에 자료들을 넣는 행위를 대입/할당한다고 합니다.
기호로는 = 를 사용하기 때문에 등호라고 생각하기 쉽지만 수학적으로 a = b와 같이 'a와 b는 같다'라는 뜻이 아니라 'a라는 곳에다가 b를 복사해서 집어넣어라'라는 의미를 가지고 있습니다.
한마디로 비교하는 의미가 아니라 원래 있던 내용 대신 덮어씌운다라는 개념을 갖는 것이 좋습니다.
비교연산자는 뒤에서 후술하겟지만 프로그래밍 언어에 따라 == 또는 ===를 사용합니다.
때문에 프로그래밍 언어에서는 L-value와 R-Value라는 개념이 있습니다. 단순히 왼쪽, 오른쪽의 의미를 가지고 있기도 하지만 L-value는 좀 더 특별한 의미를 가지고 있는데, 주소(Address/Location)의 의미를 가지고 있습니다. L-value가 위치한 메모리에 가면 대입시킨 변수가 존재한다는 의미입니다. 앞서 2챕터에서 입력 함수를 사용하거나 포인터 변수를 지정할 때 &변수명으로 했죠? 이것이 L-value의 주소개념과 대입입니다.
반대로 R-value는 주소와는 관계 없이 그 변수 또는 연산식 그리고 상수가 갖는 값에만 관심이 있습니다. L-value에 대입한 뒤 더는 사용하지 않아 메모리에서 없애 버리기 때문입니다.
한마디로 L-value는 저장해야 하는 위치이고, R-value는 저장해야하는 정보입니다.
따라서 L-value에는 변수나 함수만 올 수 있고, R-value는 초기화되지 않은 변수를 제외한 어떠한 것이든 올 수 있습니다. 1
#include <stdio.h>
int main() {
int my_age, your_age;
//L-Value R-Value
int her_age = 20; //her_age라는 변수에 20이라는 값을 대입합니다.
my_age = her_age + 1; //my_age는 20이라는 값을 가지고 있었지만 scanf()함수에 의해 사용자의 입력을 받아 다른 정수값을 대입하게 됩니다.
printf("My age is %d.\n", my_age);
my_age = your_age + 1; //초기화 되지 않은 변수를 사용하려 했기 때문에 에러가 발생합니다.
printf("My age is %d.\n", my_age);
your_age = NULL; //your_age를 NULL값으로 초기화합니다. == 0 또는 '\0'
my_age = your_age + 1; //변수를 NULL값으로 초기화 했을 경우 default는 0이므로 my_age는 1이 됩니다.
printf("My age is %d.\n", my_age); //하지만 0과 NULL은 다른것이니 주의해주세요
return 0;
}
#0과 NULL 포인터
0와 NULL 포인터는 아직 초기화 되지 않은 변수를 초기화 시킬때 사용합니다.
둘은 동일한 기능을 하는것 같이 사용되지만 엄밀히 말하면 다른것입니다.
예를 들어 int a = 0은 a라는 변수의 값에 0을 대입하여 메모리에 저장된 값이 00000000⑵(사용되는 운영 체제에 따라 다를 수 있습니다.)이 되지만
int* b = NULL;의 의미는 정수형 포인터 변수 b의 주소를 선언하지만 메모리 안에 저장된 값이 Void(텅 빈) 즉 아무것도 없기 때문에 어떠한 값도 가지고 있지 않은 상태가 됩니다.
절차적 언어(Sequential Language)
C언어는 절차적 언어입니다. 절차적이라 함은 프로그램 특별한 명령(함수호출 등)이 없다면 코드(명령문)를 위에서부터 아래로 순서대로 실행한다는 말입니다.
언뜻 보면 당연한 것 같지만 이 절차적이라는 말은 생각보다 중요합니다. 가령 소스코드에서 변수의 값을 차례로 대입해 나가면서 사용할 때 실수로 대입 연산의 순서를 반대로 수행하라고 명령한다면 디버거는 에러메세지를 출력합니다.
#include <stdio.h>
int main() {
int one, two, three;
//에러가 발생하는 코드영역 (Line 8~9)
one = 1;
three = two + 1;
two = one + 1;
printf("one: %d, two: %d, three: %d\n", one, two, three);
return 0;
}
에러가 발생하는 이유는 초기화 되지 않은 변수 two를 변수 three 대입 연산 명령에 사용하려 하고 있기 때문입니다.
이 문제를 해결하기 위해서는 two라는 변수의 값을 Null 또는 0으로 선언과 동시에 초기화해주거나 변수 three 대입연산에 사용하기 이전에 초기화 해주면 됩니다.
#include <stdio.h>
int main() {
//int two = null; 또는 int two = 0;으로 변수 선언과 동시에 변수를 초기화 할 수 있습니다.
int one, two, three;
//에러가 발생하지 않는 코드영역
one = 1;
two = one + 1;
three = two + 1;
printf("one: %d, two: %d, three: %d\n", one, two, three);
return 0;
}
자세히 보면 two대입연산과 three에 대한 대입연산식의 순서가 바뀌어 있다는 것을 알 수 있습니다. 이렇게 단순히 명령문 2줄의 순서가 바뀐 것 만으로도 에러가 해결됩니다.
연산자
위에서 변수를 만들고 변수에 값을 저장하는 방식에 대해 배웠습니다. 이렇게 생성된 변수들들의 값을 더하거나 빼고, 비교하고, 복제하면서 또 다른 변수를 만들거나 다른 곳에 활용하기 위한 행위들을 연산이라 하고, 이 연산을 위해 사용하는 기호를 연산자라고 합니다.
연산자에는 산술 연산자와 관계 연산자, 그리고 논리 연산자와 비트 연산자가 있습니다.
산술 연산자
산술 연산자는 수학식을 떠올리면 편합니다.
다른점은 곱하기의 연산자가 X가 아닌 *로 표시한다는 점과 나눗셈을 /, 나머지를 구하는 연산을 %로 표현한다는 점이다.
연산자 기호 | 수학 기호 | 의미 |
+ | + | plus |
- | - | minus |
* | X | Multiplication |
/ | ÷ | division, 정수연산(int)의 경우 몫만 나온다 |
% | ÷ | mod, 나머지 연산 |
++ | +1 | +1 증가 연산 |
-- | -1 | -1 감소 연산 |
복합 대입 연산자와 증감연산, 전위/후위 연산
복합 대입 연산자
복합 대입 연산자는 대입연산자와 산술 연산자가 붙어있는 것을 말합니다. 예를 들어 a = a + b;라는 식을 a += b;와 같이 줄여 쓸때 이를 복합대입연산자라 하며 둘의 연산 결과는 똑같습니다.
증감 연산
증감 연산은 증가(++)감소(--)연산자를 의미하는데 연산의 형태가 단항 연산인 경우에만 사용할 수 있습니다.
변수에 1을 더하거나 빼는 연산을 수행합니다.
전위/후위연산
전위연산은 증감연산을 수행한 이후 대입연산을 수행하는 것이고, 후위연산은 대입연산을 먼저 수행한 뒤 증감연산을 수행하는 것입니다.
#include <stdio.h>
int main() {
int a, b;
//1번
a = 0;
a++;
printf("after a++ = %d\n", a);
b = a++;
printf("after b = a++ == %d and a = %d\n\n", b, a);
//2번
a = 0;
++a;
printf("after ++a = %d\n", a);
b = ++a;
printf("after b = ++a == %d and a = %d\n\n", b, a);
//3번
a = b = 0;
printf("a = %d, b = %d\n", a, b);
printf("(1+a++)+2 = %d, ++b = %d\n", (1+a++) + 2, ++b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
왜 이런결과가 나왔을까요?
1번에서 후위연산을 수행한 이후 출력문을 통해 a의 값을 다시 찍었기 때문에 1이 먼저 튀어나옵니다. b에 a를 대입한 후 ++연산을 수행하는 후위연산을 사용한 뒤 출력문을 찍을 때에는 b는 1, a는 2가 출력되는 것 입니다.
2번에서는 전위연산을 이용하엿기 때문에 b와 a 모두 값이 먼저 증가한 후 출력되어 b = 2, a = 2가 출력된 것 입니다.
마지막 3번에서는 (1 + a++) + 2 == (1 + 0) + 2 = 3, ++b = 1을 먼저 수행하여 출력한 이후 a++연산이 수행되어 a의 값이 1이 되게됩니다.
관계 연산자
관계 연산자는 변수를 비교하기 위해 사용합니다. 수학식의 부등호를 생각하시면 편합니다.
변수가 다른변수 이상, 이하, 초과, 미만의 값을 갖거나 같은지, 같지 않은지를 비교합니다.
관계연산의 결과는 항상 참(true/1) 또는 거짓(false/0)이 됩니다.
※ 부동소수의 관계 연산은 피하는 것이 좋습니다. why? 부동소수의 표현 범위에 의해 항상 오차 가능성이 존재하기 때문에!
관계 연산자 | 수학 기호 | 의미 |
> | > | ~보다 크다, 초과이다 |
>= | ≥ | ~이상이다 |
< | < | ~보다 작다, 미만이다 |
<= | ≤ | ~이하이다 |
== | = | ~와 같다, 동일하다 |
!= | ≠ | ~와 같지 않다, 다르다 |
논리 연산자
논리 연산자는 부울 대수에 의한 논리 연산을 표현하기 위한 연산자입니다.
대표적으로 AND, OR, NOT이 있습니다.
논리 연산자 | 부울 대수 | 의미 |
&& | AND | Logical AND |
|| | OR | Logical OR |
! | NOT | Logical Not |
자세한 내용은 디지털 논리 회로에서 다루겠습니다.
'수학/디지털 논리 회로' 카테고리의 글 목록 (tistory.com)
비트 연산자
마지막으로 알아볼 연산자는 비트연산자입니다. 비트는 이진수의 기초 단위로 CPU의 연산을 직접 다루는 의미를 가지고 있습니다.
예를 들어 비트연산자는 8비트 00000000과 00000010을 더하게 되면 이진 가산기의 논리 연산과 유사한 형태로 어셈블리에 가까운 수준의 연산을 수행하는 것이라는 겁니다.
비트 연산자 | 논리 연산자 | 의미 |
& | && | bitwise AND |
| | || | bitwise OR |
^ | X | bitwise XOR |
~ | ! | bitwise NOT |
<< | X | bitwise Left shift |
>> | X | bitwise Right shift |
연산자 우선순위
수학의 연산식에도 위계가 있듯이 각 연산자들의 사이에는 우선순위라는 것이 있습니다.
대개 수학 연산 법칙을 따르지만 결합 순서가 다른 경우가 있으므로 꼭 인지해야 합니다.
우선 순위 | 연산자 | 의미 | 결합 방향 |
1 | ( ) [ ] . -> ++, -- |
괄호 배열 인덱스 멤버 선택 포인터에 의한 멤버 선택 후위 증감 연산 |
왼쪽->오른쪽 |
2 | ++, -- +, - !, ~ (type) sizeof & * |
전위 증감 단항 연산 논리/비트 NOT 형 변환 데이터 크기 연산 주소 연산자 참조 연산자 |
왼쪽 <- 오른쪽 |
3 | *, /, % | 곱셈, 나눗셈, 나머지 | 왼쪽->오른쪽 |
4 | +, - | 덧셈, 뺄셈 | |
5 | <<, >> | 비트 시프트 | |
6 | >, >=, <, <= | 관계 연산 | |
7 | & | 비트 AND | |
8 | ^ | 비트 XOR | |
9 | | | 비트 OR | |
10 | && | 논리 AND | |
11 | || | 논리 OR | |
12 | = += -= *= /= |
대입 복합 대입 |
왼쪽 <- 오른쪽 |
※※※오버플로우※※※
OverFlow는 변수의 형식지정자의 범위를 넘어가는 값이 연산 또는 대입되었을 때 발생합니다.
예를 들어 아래의 식을 실행시키면 콘솔창에 원하는 값이 아닌 엉뚱한 값이 출력되게 됩니다.
#include <stdio.h>
int main() {
short a = 32768;
short b = a / 72;
float c = 1E45;
float d = c / 2.0;
printf("a: %d, b: %d, c: %f, d: %f\n", a, b, c, d);
return 0;
}
정수의 경우 양수 값이 음수로 바뀌거나 엉뚱한 양수값이 나오게 되고, 부동소수 연산의 경우 inf라는 값이 출력됩니다.
오버플로우는 디버깅 수행시 에러가 출력되지 않고 정상작동되기 때문에 실제 프로그램을 구동시켯을 때 예기치 않은 에러를 발생시킬 위험이 있기 때문에 항상 오버플로우가 발생하지 않게 주의해야 합니다.
//모든 예제 소스는 한빛 미디어홈페이지에서 찾으실 수 있습니다.
IT CookBook, 전공자를 위한 C 언어 프로그래밍 (hanbit.co.kr)
또는 cyphen156/Work-space: Studying (github.com)에서 찾으실 수 있습니다.
- 초기화 : 변수에 메모리 주소를 할당함과 동시에 그 값으로 R-value를 저장하는 것, 컴파일시 변수의 값이 확정된다. 만약 초기화 되지 않은 변수를 다른 곳에 사용하려 할 경우 에러가 발생하기 때문에 보통 NUL 문자(포인터의 경우 NULL)을 사용해 초기화 해준다. [본문으로]
'프로그래밍 > C언어' 카테고리의 다른 글
Chapter4 함수Ⅰ (0) | 2023.02.09 |
---|---|
Chapter3 과제 (0) | 2023.02.03 |
Chapter2 과제 (0) | 2023.01.05 |
Chapter2 C언어의 자료형과 표준 입출력 함수 (0) | 2023.01.04 |
Chapter1 과제 (0) | 2022.12.22 |