[C] C언어 공부하기 3. 변수

2019. 12. 30. 14:47C,C++

출처 : http://www.soen.kr/

 

SoEn:소프트웨어 공학 연구소

 

www.soen.kr

3-1 변수

3-1-가. 변수의 정의

 변수에서 말하는 수(數)를 좀 더 일반적으로 표현하면 데이터(data)이며 한국말로 알아듣기 쉽게 설명하면 값이다.

 컴퓨터는 연산 대상 메모리의 위치를 구분하기 위해 숫자로 된 번지(Address)라는 개념을 사용한다.

번지는 값이 크기 때문에 통상 8자리의 16진수로 표현하는데 사람들이 흔히 쓰는 10진수를 쓰면 자리수가 가변적이어서 오히려 더 혼란스럽다.

예를 들어 어떤 값이 0x183c7eda번지에 저장되어 있다면 사람이 이 위치를 암기하여 사용하는 것은 거의 불가능하다. 이런 값을 10개만 사용해도 사람의 머리로는 각 값을 구분할 수 없다. 즉 번지라는 것은 전혀 인간적이지 않은 기계의 값이다.

그래서 번지를 직접 사용하는 대신 좀 더 기억하기 쉬운 변수를 사용한다.

 흔히 변수는 값을 저장하는 상자에 비유되곤 한다. 상자에 물건을 넣어 두듯이 변수를 하나 만들어 놓으면 여기에 값을 저장하거나 다시 꺼내올 수 있다.

 변수명을 작성하는 일반적인 몇 가지 법칙이 있다.

 의미를 잘 설명할 수 있는 이름을 주는 것이 좋다

 변수명의 길이에는 제약이 없지만 3~10자 내외로 작성하는 것이 좋다.

 대소문자 구성을 일관되게 하는 것이 좋다.

 변수명은 보통 짧은 영어 단어를 활용한다.

 

3-1-나. 변수의 종류

예를 들어 Num=56이라는 대입문이 있다고 하자. 컴파일러는 이 명령을 다음 둘 중 어떤 것으로 해석할까?

 Num이 가리키는 번지를 56으로 바꾸어라.

 Num이 가리키는 번지에 들어있는 값을 56으로 바꾸어라.

상식적으로 생각해 봐도 후자가 맞다. 변수는 내부적으로 번지에 대한 위치를 가리키지만 컴파일러는 변수를 번지에 기억된 값으로 참조한다.

변수로부터 값을 정확하게 읽으려면 변수의 형태에 대해서도 알아야 한다.

변수는 저장된 메모리 위치와 함께 길이와 형태에 대한 정보를 가지는데 이런 변수의 특성을 타입(Type)이라고 한다. 

기본형이란 하나의 단일 값을 기억하는 단순한 타입이다

유도형은 기본형으로부터 만들어지는 타입이다.

 

3-1-다. 변수의 선언

 선언(Declaration)이란 컴파일러에게 앞으로 이런 이런 이름을 가진 어떤 타입의 변수를 사용하겠으니 준비해 달라고 부탁하는 것이다. 

여러 개의 변수를 같은 타입으로 한꺼번에 선언할 수도 있는데 콤마로 구분된 변수명을 계속 나열하면 된다. 이때도 = 구분자로 그 중 일부를 초기화할 수 있다.

 

int a,b=3,c,d;

4개의 정수형 변수가 선언되며 이 중 b는 3으로 초기화된다. 

 

다른 타입의 변수는 한꺼번에 같이 선언할 수 없으며 각각 따로 선언해야 한다. 다음은 정수형 변수 두 개와 실수형 변수 하나를 선언한 것이다.

 

int angle, radius;

double ratio;

 

3-2. 입출력

 

3-2-가. printf

 

3-2-나 scanf

scanf(스캔에프라고 읽는다)는 사용자로부터 정보를 입력받는 기본 함수이다.

실수 서식인 %f가 printf는 float, double에 모두 대응되는데 비해 scanf는 float와만 대응된다는 점만 다르다. scanf로 double값을 입력받으려면 %lf 서식을 사용해야 한다. 

이때 scanf로 입력받을 변수를 지정할 때 변수명앞에 & 연산자를 붙여야 한다. 이유는 변수의 값이 아닌 번지를 전달해야 scanf가 이 변수의 값을 변경할 수 있기 때문이다.

 키보드로 값을 타이프해서 입력하는 시대는 한참 전에 지났으며 그래서 도스용 프로그램을 작성하지 않는 한 scanf 함수로 값을 입력받아야 하는 경우는 거의 없다.

 

3-2-다. 그외의 입출력 함수들

1. clrscr()

Clear Screen의 약자이며 이름 그대로 화면을 깔끔하게 지우고 커서를 화면 좌상단으로 옮긴다. 화면에 이미 출력된 내용을 지우고 새로운 내용을 출력하고자 할 때 이 명령을 사용한다.

Turbo C compiler에서는 conio.h를 추가함으로써 쓸 수 있으나 GCC linux에서는 사용할 수 없음.

1
2
3
4
5
void clrscr(void)
 
{
    system("clear");
}
cs


#include <stdlib.h>를 추가하고 위 코드를 쓰면 된다.

 

2. gotoxy(x,y)

솔은 80*25의 바둑판같은 좌표 공간이라고 할 수 있는데 각 위치는 x축과 y축의 좌표값을 가진다. 그림으로 그려 보면 다음과 같을 것이다.

gotoxy 함수는 커서의 위치, 그러니까 다음 문자열이 출력될 좌표값을 바꾼다.

1
2
3
4
void gotoxy(int x,int y)    
{
    printf("%c[%d;%df",0x1B,y,x);
}
cs

 

코드

 

 

실행결과

 

3. wherex(), wherey()

: gotoxy가 화면의 현재 위치를 바꾸는데 비해 이 두 함수는 화면의 현재 위치를 조사한다.

 

4.puts("문자열")

puts 함수는 문자열만 출력할 때 사용한다. printf로도 문자열을 출력할 수 있지만 puts는 서식을 다루지 않기 때문에 훨씬 더 간편하며 속도도 빠르다는 장점이 있다.화면에 "korea"라는 문자열을 출력하고 싶다면 puts("korea")를 호출하면 된다.

puts는 문자열을 출력한 후 항상 개행을 하므로 \n을 일부러 붙이지 않아도 된다.

 

5.gets(변수)

gets 함수는 문자열을 입력받아 인수로 주어진 변수에 저장하는데 gets(str)은 scanf("%s",str)과 유사하다.

그러나 scanf는 문자열을 공백에서 끊어 버리기 때문에 긴 문자열을 입력받을 수 없는데 비해 gets는 개행 코드 이전의 모든 문자를 입력받는다는 점이 다르다. 

즉 scanf의 %s 서식은 단어를 입력받고 gets는 문장을 입력받는다.

 

6. putch(c)

문자 하나만 출력하는 함수이다. putch('C')를 호출하면 현재 커서 위치에 C 문자가 출력될 것이고 putch('*')를 호출하면 *가 출력될 것이다.

 

7.getch()

getch 함수는 문자 하나만 입력받는다. 

scanf는 값을 입력한 후 반드시 Enter를 눌러야 하는데(투터치) 비해 getch는 키를 누르는 즉시(원터치) 눌러진 문자를 조사하므로 Enter키를 누르지 않아도 된다는 것이 장점

 커서 이동키나 펑션키같이 문자가 아닌 키를 누를 경우 getch 함수는 확장키라는 의미의 0xE0 또는 0을 돌려준다. 이럴 때는 getch 함수를 한 번 더 호출하여 확장키의 키코드를 조사할 수 있다.

 

8.delay(n)

이 함수는 출력 함수는 아니지만 출력과 관계가 있다. 인수로 주어진 n만큼 시간을 지연시키는데 1/1000초 단위로 아무 것도 하지 않고 대기한다.

 

9.exit(0)

프로그램을 강제로 종료한다. 이 함수를 일부러 호출하지 않더라도 main 함수가 끝나면 프로그램도 자동으로 종료되지만 치명적인 에러나 사용자의 명시적인 명령에 의해 실행 중간에 종료하고자 할 때는 이 함수를 호출한다.

exit의 괄호 안에는 프로그램 자체의 리턴값을 적는데 정상적으로 종료했을 때 0을 넘기며 실행 중 에러가 발생했을 때 0이 아닌 에러 코드(주로 -1)을 넘기도록 약속되어 있다

 

10.kbhit()

키보드의 키가 눌러져 있는지 아닌지만을 조사한다. 눌러졌으면 참의 값을 리턴하고 그렇지 않을 경우에는 거짓의 값을 리턴한다.

 

11.setcursortype(커서형태)

콘솔창에서는 주로 문자를 입출력하는데 다음 입출력될 위치는 커서가 가리킨다. 

 

3-3 정수형

3-3-가. 정의

정수(Integer)란 부호는 있지만 소수점 이하를 표현하지 못하는 수이다

정수형이란 이런 정수값을 저장할 수 있는 타입이다

부호가 있는 정수(signed)는 제일 왼쪽의 비트(MSB라고 한다)를 부호 비트로 사용하며 이 비트가 0이면 양수이고 1이면 음수가 된다

MSB를 부호 비트로 사용하면 값을 기억하는 비트 하나가 줄어들게 되므로 표현할 수 있는 최대값은 절반으로 줄어드는 대신 음의 값을 표현할 수 있다. 

 

 

 부호에 대한 수식어가 생략되면 signed가 적용되어 부호가 있는 것으로 선언된다.

 int앞에 수식어가 있을 경우 int는 생략할 수 있다.  unsigned int는 unsigned로 간단하게 쓸 수 있으며 long int는 long과 같다. 부호있는 4바이트 정수형은 signed int라고 쓰는 것이 원칙이나 signed를 생략하고 int로 쓸 수도 있고 int를 생략하고 signed로 쓸 수도 있다. 

 

3-3-나. 정수형의 길이

 

 변수의 저장 용량을 넘어서는 현상을 오버플로우(Overflow)라고 한다.

수학적인 연산을 할 때는 항상 이 점을 주의해야 한다. 아주 간단할 것 같은 연산도 정확한 타입과 함께 사용해야만 결과가 제대로 나온다. 메모리가 지극히 부족한 상황이 아닌 한은 정수가 필요할 때 부호 있는 4바이트 정수인 int를 사용하면 별 문제가 없다. 

C 언어의 타입 정의에 int 형은 "CPU의 레지스터와 동일한 크기를 가지는 타입"으로 정의되어 있다. 

 

 64비트 CPU가 나오면(이미 나와 있다) 그때는 int형이 64비트(8바이트)가 될 것이다. << 여전히 int는 4바이트임. 옛날 글이라 사이트의 오류인 것 같음.

 

64비트 CPU가 발표되고 점점 더 큰 수를 다룰 일들이 많아지면서부터 C언어도 64비트의 정수를 지원하기 시작했다. 비주얼 C++과 Dev-C++은 __int64라는 타입을 지원하며 이 타입을 사용하면 무려 1800경(264)이라는 엄청난 수를 표현할 수 있다.

 

3-3-다. 정수형 상수

 8진수 : 0으로 시작하면 8진수로 인식되며 027, 032등과 같이 표현한다. 09같은 상수는 에러로 처리되는데 9라는 숫자는 8진수에서 쓸 수 없는 숫자다.

 16진수 : 0x 또는 0X로 시작하면 16진수이다. 0x12ab, 0x3f와 같이 표현한다. 16진수에서 10 이상의 값을 표현하는 A~F는 대소문자에 상관없이 아무 문자나 사용할 수 있다

 

3-4 실수형

3-4-가. 종류

float는 4바이트의 작은 실수형이며 double은 8바이트의 큰 실수형이다. 실수형의 값을 기억할 변수가 필요하다면 double d; 형식으로 선언하면 된다.

컴퓨터는 원래 정수만 다룰 수 있기 때문에 실수를 기억하는 방법이 아주 독특하다. 

실수는 부동 소수점이라는 좀 특이한 방법으로 저장한다. 부동(浮動) 소수점이란 실수를 정수부와 소수부로 나누는 것이 아니라 지수부와 가수부로 나누어 기억하는 방식이다.

가수부는 값의 모양을 표현하며 지수부는 10의 거듭승으로 값의 크기를 표현한다. 실수 123.456을 부동 소수점 형식으로 표현하면 1.23456*102으로 표현할 수 있으며 이를 공학적 표기법으로 바꾸어 1.23456E2로 표현하기도 한다. 이 예에서 가수는 123456이고 지수는 2이다. 정수부와 소수부를 기억하는 방식보다 부동 소수점 방식으로 실수를 기억하면 훨씬 더 큰 수를 표현할 수 있고 정밀도도 높아진다.

지수부와 가수부의 크기는 float의 경우 8비트, 23비트이며 double형의 경우 11비트 52비트이다. 그래서 float보다는 double이 두 배의 크기를 가지는 대신 훨씬 더 큰 수를 정확하게 표현할 수 있다.

 

 

3-5 문자형

 

3-5-가. 문자

문자형이란 문자 하나를 표현하는 자료형이다

문자형은 문자 하나를 저장할 수 있는 적당한 길이를 가진다는 뜻으로 붙여진 이름이지 오로지 문자만 저장할 수 있다는 뜻은 아니다. char 타입은 실제로 8비트의 정수형이므로 크기가 작은 정수를 저장하는 용도로도 사용할 수 있다.

 

3-5-나. 확장열

또한 문자 상수를 표현할 때 사용하는 홑 따옴표 구두점도 문자 상수로 바로 표현할 수 없다. ''' 이렇게 쓰면 두 번째 '가 닫는 따옴표인지 문자 '를 나타내는지 컴파일러가 구분할 수 없다. 그래서 키보드로 직접 입력할 수 없는 문자들은 좀 특수한 방법으로 표현하는데 이를 확장열(Escape Sequence)이라고 한다.

 

3-5-다. 문자열

문자열(String)은 일련의 문자가 연속되어 있는 것이며 문자의 집합이 곧 문자열이다

문자열 상수는 문자 상수와 달리 겹따옴표로 감싸서 표현한다.

 

"Korea", "문자열"

 

C언어는 별도의 문자열 타입을 제공하지 않는다. 왜냐하면 문자열이란 문자형 변수의 배열로 표현할 수 있고 포인터와 함께 사용하면 훨씬 더 유연하게 활용할 수 있기 때문이다.

그러나 처음 배우는 사람에게 문자열 변수가 없다는 것은 상당히 불편한 점이다. 그래서 C보다 상위 언어인 C++은 string이라는 클래스를 제공하며 MFC 라이브러리에도 CString이라는 문자열 클래스가 정의되어 있다.

 

문자열 상수를 쓰면 컴파일러가 상수의 끝에 널 종료 문자를 자동으로 붙여 준다. 그래서 "Korea"라는 다섯 글자를 저장하기 위해서는 널 종료 문자의 길이까지 고려하여 배열 크기를 6으로 선언해야 한다. 

 

3-5-라. 3중 문자

3중 문자(Trigraph)란 세 개의 연속된 문자를 하나의 문자로 대체하는 표현이다.

3-6 열거형

 

3-6-가. 정의

열거형(Enumeration)이란 변수가 가질 수 있는 가능한 값들을 나열해 놓은 타입이다. 어떤 변수가 가질 수 있는 값의 종류가 일정한 범위로 정해져 있다면 정수형 대신 열거형을 쓰는 것이 더 편리하다. 

enum { 멤버, 멤버, ... } 변수;

 

열거형으로 가능한 값들을 열거 멤버라고 하는데 { } 괄호안에 값의 이름을 나열하면 된다. 구체적인 예를 들어 보자.

 

enum { EAST, WEST, SOUTH, NORTH } mark;

 

열거형은 내부적으로 정수로 처리되며 각 열거 멤버는 0부터 1씩 증가하는 정수값을 가진다. 위 예에서 EAST는 0이고 WEST는 1이고 SOUTH, NORTH는 각각 2와 3이다. 컴파일러는 열거형의 멤버들이 어떤 정수값을 가지는지 기억해 두었다가 열거 멤버를 만나면 실제값을 적용한다. 

 

열거형의 장점

 기억이 용이하다. 

 소스의 가독성(Readability)이 높아진다.

 열거형은 정수형보다 안전하다. 

 

만약 열거 멤버의 값을 특정한 값으로 분명히 지정하고 싶다면 =다음에 원하는 값을 직접 적어준다.

 

enum { EAST=5, WEST=10, SOUTH, NORTH} mark;

 

열거 멤버는 일종의 명칭이므로 다른 변수명과 중복되어서는 안되며 유일한 이름을 가져야 한다. 한 열거형내에서 열거 멤버끼리 중복되는 것도 물론 허용되지 않는다.

 

3-6-나. 태그

열거형 타입에 붙여지는 이름을 태그(tag)라고 하며, 타입의 자격을 가진다.

enum origin { EAST, WEST, SOUTH, NORTH };

origin mask;

 

위 예제는 origin이라는 태그 이름으로 방향에 대한 열거 타입을 정의하였다.

이 태그로 변수를 여러 개 선언할 수도 있고 함수의 인수로 열거형을 넘길 수도 있다.

열거형을 이용해서 C에서 C++의 bool타입을 구현할 수 있다.

 

3-7 유도형의 소개

3-7-가. 배열

우선 배열의 정의부터 문장화해 보면 동일한 타입을 가지는 자료들의 집합으로 정의된다. 동일한 타입이라는 뜻은 정수형이면 정수형끼리만, 문자형이면 문자형끼리만 모여야 배열이 된다는 뜻이다.

배열의 각 요소는 배열이라는 큰 집합의 일부분이라는 것 외에는 같은 타입의 변수와 완전히 동일한 자격을 가진다. array[3]이라는 요소는 정수형 변수이며 정수형 변수와 똑같이 사용한다.

 

3-7-나. 구조체

동일한 타입의 집합인 배열과는 달리 구조체(Structure)는 서로 다른 타입의 집합이다. 이때 구조체에 속하는 개별 변수들을 멤버(Member)라고 한다. 정수형 변수와 실수형, 문자형 등의 기본형 변수뿐만 아니라 배열이나 구조체같은 큰 변수도 멤버가 될 수 있다. 

또한 이런 구조체 여러 개를 모아 구조체 배열을 만들면 복잡한 정보의 집합을 다룰 수도 있다. Friend 구조체는 한 사람에 대한 신상을 기억하는데 이런 타입의 배열을 만들면 주소록이 된다. Friend[100]의 형식으로 구조체 배열을 작성하면 최대 100명의 친구 신상을 기억시킬 수 있을 것이다.

공용체(Union)는 구조체와 유사하지만 멤버끼리 기억 공간을 같이 공유한다는 점이 조금 다르다. struct 키워드 대신 union키워드를 사용한다.

 

3-7-다. 포인터

 C의 문법중에서 가장 어렵고 난해한 주제이면서 또한 C를 가장 강력한 언어로 만들어주는 일등 공신이기도 하다.

포인터를 직접 다룰 수 있기 때문에 C언어를 고급언어가 아닌 중급언어로 분류하며 어셈블리와 같은 수준의 시스템 프로그래밍까지도 가능하다.

. 만약 변수가 할당되어 있는 번지값을 알고 싶거나 직접 다루고 싶다면 이때 포인터라는 것이 필요하다. 포인터란 변수의 값이 아닌 변수가 저장되어 있는 메모리의 번지를 기억하는 타입이다. 포인터는 다음과 같이 선언한다.

 

타입 *변수명;

 

* : 포인터가 가리키는 번지의 값을 읽는다.

& : 변수가 기억되어 있는 메모리 번지를 읽는다.

 

왜 이렇게 포인터라는 간접적인 방법을 사용하는가? 한단계 더 중간 과정을 거치게 되면 그 중간 과정에서 많은 유용한 조작이 가능해지기 때문이다. 소프트웨어 공학에서는 융통성을 위해 중간 과정(전문 용어로 레이어라고 한다)을 삽입하는 경우가 아주 빈번하며 한 번 더 과정을 거침으로써 많은 것들이 가능해진다. 예를 들어 비디오 드라이버, 자바 가상 머신 등등이 레이어의 좋은 응용예이며 네트워크는 무려 7개의 레이어로 구성되어 있다.

 

3-7-라. 사용자 정의형

typedef 타입정의 타입이름;

 

3-7-마. 논리형

C문법은 논리형을 별도의 기본형 타입으로 인정하지 않으므로 C 컴파일러들은 열거형이나 사용자 정의형으로 논리형 타입인 BOOL을 만들어 사용한다.

typedef in BOOL;

#define TRUE 1

#define FALSE 0

 

BOOL형을 사용하면 실제로는 정수형이기 때문에 크기가 4바이트이다. 참 또는 거짓이라는 값을 기억하는데 4바이트를 쓰면 비효율적이므로 C++에서는 논리형을 별도의 기본 타입으로 정의하고 있다.