변수와 데이터 타입
값을 담는 상자에 이름을 붙이는 법, 그리고 C#이 그 상자의 모양(타입)을 왜 미리 정해두라고 하는지 알아봅니다.
변수, 그리고 타입을 미리 정하는 이유
변수(variable)는 값을 저장하는, 이름이 붙은 메모리 공간입니다.
C#은 정적 타입 언어라서 변수를 선언할 때 어떤 종류의 값을 담을지
타입을 함께 정해야 합니다. 컴파일러가 이 정보를 미리 알기 때문에, 실행 전에
int에 문자열을 넣는 것 같은 실수를 잡아낼 수 있습니다.
타입을 적는 것이 번거로워 보이지만, 잘못을 실행하기 전에 잡아 준다는 것이 그 값을 합니다. 타입을 정해 두지 않는 언어에서는 같은 실수가 프로그램이 한참 돌다가, 그것도 엉뚱한 자리에서 드러납니다.
C#의 타입은 크게 두 가지로 나뉩니다.
-
값 형식(value type) —
int,double,bool처럼 변수 자체에 값이 직접 저장됩니다. - 참조 형식(reference type) — 배열이나 클래스처럼 변수에는 실제 데이터가 있는 위치만 저장됩니다.
이 차이는 다른 변수에 담을 때 드러납니다. 아래 상세 사용법에서 직접 보겠습니다.
기본 타입으로 변수 선언하기
아래 코드는 브라우저 안에서 실제로 실행됩니다. 고쳐서 눌러 보셔도 됩니다.
int age = 27; double price = 19.99; bool isStudent = true; string name = "홍길동"; Console.WriteLine($"{name}님은 {age}살이고, 학생 여부는 {isStudent}입니다.");
코드는 고쳐서 실행해 볼 수 있습니다. 처음 누를 때만 실행기를 내려받느라 잠시 걸립니다. 적은 코드는 서버로 나가지 않습니다.
true 라고 담았는데 화면에는
True 로 나옵니다. 코드에 적는 말과 글자로 바뀐 결과가
다른 것인데, 나중에 이 값을 글자로 견줄 일이 있다면 이 점을 기억해두면 좋습니다.
자주 사용하는 기본 타입
| 타입 | 설명 | 예시 |
|---|---|---|
| int | 정수 (약 -21억 ~ 21억) | int count = 3; |
| long | 더 큰 범위의 정수 | long big = 10000000000; |
| double | 실수 (소수점 값) | double pi = 3.14; |
| decimal | 정밀한 소수 (금액 계산용) | decimal price = 1990.50m; |
| bool | 참/거짓 | bool done = false; |
| char | 문자 하나 | char grade = 'A'; |
| string | 문자열 | string city = "서울"; |
돈을 다룰 때는 double 이 아니라
decimal 입니다.
double 은 2진법으로 소수를 담느라 0.1 같은 값을 정확히
담지 못해, 더하다 보면 아주 작은 오차가 쌓입니다. 금액에서는 그 오차가 그대로
틀린 값이 됩니다.
담을 때와 옮길 때가 다릅니다
앞에서 타입이 두 가지로 나뉜다고 했습니다. 그 차이는 한 변수를 다른 변수에 담을 때 드러납니다. 값 형식은 값이 통째로 복사되지만, 참조 형식은 위치만 복사되어 둘이 같은 것을 가리키게 됩니다.
int a = 10; int b = a; // 값을 복사 b = 99; // a 는 10 // b 는 99
int[] a = { 10, 20 }; int[] b = a; // 위치를 복사 b[0] = 99; // a[0] 도 99 // 둘이 같은 것을 가리킵니다
오른쪽에서 a 를 건드리지 않았는데
a[0] 이 바뀐 것이 핵심입니다.
b 는 값을 복사해 온 것이 아니라 같은 데이터를
가리키는 다른 이름일 뿐입니다.
여기서 사용한 대괄호는 값을 여럿 담아 두는 방법이며 1.7에서 자세히 다룹니다. 지금은 값 형식과 참조 형식이 다르게 움직인다는 것만 보면 됩니다. 이 차이는 1.8에서 메서드에 값을 넘길 때, 그리고 2부에서 클래스를 다룰 때 다시 나옵니다.
타입을 적는 수고를 더는 var
var 키워드를 사용하면 오른쪽 값을 보고 컴파일러가 타입을
자동으로 추론합니다. 타입이 사라지는 게 아니라, 타입을 적는 수고만 줄어드는 것입니다.
한번 정해진 뒤에는 다른 타입을 담을 수 없습니다.
var age = 27; // 컴파일러가 int로 추론 var name = "홍길동"; // 컴파일러가 string으로 추론 age = "스물일곱"; // 컴파일 오류 — 이미 int 로 정해졌습니다
오른쪽만 보고 무엇인지 알기 어렵다면 타입을 적는 편이 낫습니다.
var n = Get(); 는 짧지만, 읽는 사람이
Get 을 찾아가 보아야 무엇이 담기는지 압니다.
값이 없을 수도 있을 때
값 형식은 기본적으로 값을 반드시 가져야 합니다. 물음표를 붙이면
(int?) "값이 없을 수도 있음"을 허용할 수 있습니다.
아직 입력받지 않았거나 해당 사항이 없는 자리를 나타낼 때 사용합니다.
int? age = null; // 아직 모릅니다 if (age.HasValue) { Console.WriteLine(age.Value); } else { Console.WriteLine("나이를 아직 적지 않았습니다."); }
이 문법, 몇 버전부터 사용할 수 있나요
- 2.0int? — nullable 값 형식
- 3.0var — 타입 추론 키워드
var는 C# 3.0(2007)부터 사용할 수 있습니다. 그 이전
버전에서는 모든 변수에 타입을 직접 명시해야 했습니다.
int? 같은 nullable 값 형식은 더 이전인
C# 2.0(2005)부터 가능합니다. 참조 형식에도 같은 표시를 할 수 있게 된 것은
한참 뒤인 C# 8.0입니다.
직접 해보기
이름(string), 나이(int), 키(double), 학생 여부(bool)를 각각 변수로 선언하고,
Console.WriteLine으로 한 문장에 출력해보세요.
정수 변수 하나와 값 두 개짜리 배열 하나를 만들고, 각각을 다른 변수에 담은 뒤 담은 쪽만 고쳐보세요. 그리고 원래 변수가 어떻게 되었는지 함께 출력해 차이를 확인해보세요.
- 변수는
타입 이름 = 값;형태로 선언합니다. - 값 형식은 담을 때 값이 복사되고, 참조 형식은 위치가 복사되어 둘이 같은 것을 가리킵니다.
var는 타입을 없애는 게 아니라, 컴파일러가 대신 추론하게 할 뿐입니다.- 돈을 다룰 때는
double이 아니라decimal을 사용합니다.
C# 버전별 변경 이력 에서 각 버전이 무엇을 더했는지 볼 수 있습니다.