C# [기본 문법 요소]
각 프로그래밍 언어는 저 마다 나름의 문법과 어휘 구조를 가지고 있다.
C#도 예외는 아니며, 한 가지 집고 넘어가야 할 것이 바로 '예약어(또는 키워드)', '식별자', '리터럴' 개념이다.
예약어, 키워드
예약어(reversed word) 또는 키워드(keyword)는 C# 언어에서 문법을 표현하기 위해 미리 예약된 단어를 의미한다.
예약어 예시)
- sbyte, byte, short, ushort, int, uint, long, ulong
- float, double, decimal
- char, string
- bool
식별자
식별자(identifier)는 프로그래밍을 하면서 임의로 선택해서 이름을 지을 수 있는 단어를 말한다.
namespace ConsoleApp2;
class Program
{
static void Main(string[] args)
{
string text = "Hello World";
Console.WriteLine(text);
}
}
굵게 표시된 부분이 명명할 수 있는 식별자에 해당하며, 자유롭게 이름을 바꾸는 것이 가능하다.
Main이라는 이름도 식별자에 해당하지만, .NET 응용 프로그램 구조의 정의에 따라 반드시 이 함수가 있어야만 프로그램을 실행할 수 있으므로 변경해서는 안된다.
식별자도 모든 문자를 사용할 수 있는 것은 아니다.
일정한 규칙 종류
- 식별자의 시작 문자는 숫자로 시작할 수 없고, 반드시 문자여야 한다.
- 잘못된 식별자의 예시) int 1n = 5;
- 올바른 식별자의 예시) int n1 = 5;
- 특수 문자 중에서 유일하게 _(밑줄:underscore) 문자만 시작 문자로 사용할 수 있다.
- int _n = 5;
- 유니코드 범위의 문자가 허용되기 때문에 '한글' 식별자도 가능하다. - 이 방식은 권장되지 않는다.
- int 변수 = 5;
- 예약어를 식별자로 사용할 수 없다. 식별자로 사용해야 한다면 '@' 문자를 접두사로 붙여 C# 컴파일러가 예약어가 아닌 식별자로 인식하게 할 수 있다.
- string @bool = "true";
- 흔한 경우는 아니지만 이스케이프 시퀸스로도 식별자를 사용할 수 있다.
- string \u0062ool = "true"
- Console.WriteLine(\u0062ool);
규칙이 많아 보여도 실무 관례상 알파벳과 숫자로 식별자를 명명하는 것이 보통이며 일반적으로 다른 사람이 기억하기 쉽게 의미 있는 문자열을 사용하면 된다.
회사나 특정 프로젝트 단위로 권장하는 '명명 규칙(Naming Rule)'이 있는 경우에는 그것을 준수하는 편이 좋다.
C#에서 사용되는 식별자로는 변수명, 네임스페이스명, 함수명, 클래스명 등이 있다.
리터럴
리터럴(literal)은 한글로 '문자상의, 문자 그대로의'와 같은 의미이지만, 프로그래밍 언어에서 마땅히 번역할 단어가 없어 영어 발음 그대로 쓰는 것이 보통이다.
리터럴을 굳이 번역하면 '소스코드에 포함된 값'이라고 할 수 있다.
변수
변수(variable)는 '식별자'의 하나로서 변수가 선언됐을 때 그와 함께 지정된 형식에 부합하는 저장소가 메모리에 할당돼 값을 담아 놓을 수 있는 역할을 한다.
n은 정수를 담을 수 있는 저장소에 대한 식별자로서 변수라고 하며, 5는 리터럴로 개발자가 코드에 기입한 값이다.
코드에서 다른 리터럴을 사용해 변수의 값을 바꾸면 어떻게 될까?
int n = 5;
n = 10;
5를 가리키던 변수 n은 다시 10을 가리킨다. 이 처럼 변수는 이름 자체가 '변하는 값'을 의미한다.
따라서 프로그램에서 특정 값을 담을 수 있는 저장소 가운데 값이 바뀌어도 되는 경우 변수를 사용하면 된다.
위의 코드에서 n이라는 식별자를 부여한 변수는 할당된 저장소를 가리키고 언제든 그 식별자가 가리키는 값은 바뀔 수 있다.
.NET에서 변수의 종류는 크게 두 가지인데, 값 형식(Value Type)을 가리키는 변수와 참조 형식(Reference Type)을 가리키는 변수가 있다.
두 가지 저장소 : 스택과 힙
값/참조 형식의 차이점을 이해하려면 반드시 스택(Stack)과 힙(Heap)을 이해해야 한다.
- 윈도우 프로그램은 기본적으로 하나의 스레드를 갖는다.
- 개별 스레드마다 전용으로 사용할 수 있는 저장소가 메모리에 할당되는데 그 영역을 스택이라 한다.
- 힙은 프로그램에서 필요에 의해 메모리를 사용하겠다고 요청했을 때 사용할 수 있는 저장소다.
- .NET에서는 CLR이 직접 프로그램에서 사용될 힙을 관리한다.
일반적으로 메모리를 사용한 후에는 그것을 더는 사용하지 않겠다는 표현이 필요하다.
시스템으로부터 메모리를 할당받았으면 그것을 해제할 수도 있어야 한다. 할당만 하고 해제하지 않으면 유한한 자원인 메모리는 일정 시간 후 더는 사용할 영역이 남아있지 않아 프로그램의 실행 과정에서 오류가 발생한다.
개발자 입장에서 스택은 변수를 선언하는 것 자체만으로 스택 내의 특정 메모리 영역을 사용할 수 있게 C# 컴파일러에 의해 자동 할당 및 해제된다. 따라서 스택의 메모리를 할당하고 해제하는 것은 걱정하지 않아도 된다.
하지만 힙에 메모리를 할당하는 것은 개발자가 명시적으로 요청한 것이므로 해제 과정이 필요하다.
이 해제 과정에서 네이티브 환경과 관리 환경이 구분된다.
기존의 네이티브 환경에서는 C/C++ 언어 등으로 프로그램을 만들면 메모리 할당과 해제를 반드시 쌍으로 맞춰야만 한다.
반면 C# 프로그램이 동작하는 관리 환경의 경우 개발자는 오직 할당만 하고 해제는 관리 환경 내의 특정 구성 요소가 담당한다.
이것을 가비지 수집기 (Garbege Collector)라고 한다.
값 형식을 가리키는 변수
'값 형식'을 가리키는 변수의 경우 '값 자체가 스택 영역에 할당되고 변수는 그 메모리를 가리키는 프로그램 내의 식별자다.
숫자 5는 스택에 저장된다.
이러한 '값 형식'에 속한 것
sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool이 있다.
참조 형식을 가리키는 변수
C#의 대표적인 참조 형식
- class (클래스)
- interface (인터페이스)
- delegate (델리게이트)
- object (모든 클래스의 최상위 타입 - C#에서 선언하는 모든 클래스는 자동으로 object 클래스를 기반)
- string (문자열도 참조 형식)
변수에서 '값 형식'과 '참조 형식'의 유일한 차이점은 '값'을 스택에 저장하느냐 힙에 저장하느냐에 있다.
'값 형식'은 그 변수의 타입에 해당하는 값을 스택에 저장하지만, '참조 형식'을 위한 변수는 그 값을 담기 위해 별도로 힙 영역의 메모리를 할당하고, 스택의 변수 값은 다시 힙의 데이터 주소를 가리키게 된다.
0x0400, 0x2000이라는 메모리 번지는 크게 의미 없는 숫자로서 단지 구분을 위해 사용한 임의의 값이다.
값을 할당하지 않은 string 변수는 어떤 값을 가지는가?
string text;
이 경우 스택 변수 값이 숫자 0을 담고 있다.
가리킬 수 있는 힙 주소가 없는 것인데, 참조형 변수의 경우 숫자 0을 대입할 수 없기에 이런 목적으로 c#은 특별하게 null 예약어를 준비해두고 있다.
- - 이 코드를 컴파일을 하게 되면 warning CS0618이라는 경고가 발생하는데, 이는 변수를 선언하고 사용되지 않았다는 컴파일 경고가 발생한다.
string text1;
string text2 = null;
이 두가지 스택 변수는 동일하게 숫자 0을 담고 있게 된다.
- - 이 코드를 컴파일 하게 되면 warning CS8600이라는 경고가 발생하는데, null 리터럴 또는 가능한 numm 값을 null을 허용하지 않는 형식으로 변환중이라는 경고가 발생한다. 이는 힙과 스택, #nullable지시자와 nullable 참조 형식을 알아야하는데, 단순히 경고를 없애려면 null 값을 허용하는 표현인 '?' 문자를 붙이거나
string? text = null;
- null 값이 아닌 문자열, 빈 문자열을 대입하는 식으로 바꿔 처리할 수 있다.
string text = "";
초기화되지 않은 모든 참조형 변수는 null 값을 가진다.
또는 참조형 변수가 더는 사용되지 않음을 명시하기 위해 null을 할당하기도 한다.
string name = "C#";
name = null;
값 형식과 참조 형식의 차이점이 잘 나타나는 경우 : 변수의 값을 또 다른 변수에 대입했을 때
int n1 = 5;
int n2 = n1;
Console.WriteLine(n1); //5출력
Console.WriteLine(n2); //5를 출력
string txt1 = "C#";
string txt2 = txt1;
Console.WriteLine(txt1); //C#을 출력
Console.WriteLine(txt2); //C#을 출력
n1과 n2, txt1과 txt2는 같은 값을 출력하지만, 값 형식과 참조 형식에 따라 메모리의 표현 방식은 서로 다르다.
값 형식의 n1, n2는 스택의 각각 다른 위치에 동일한 값이 복사돼 개별의 값을 가리킨다.
참조 형식의 txt1, txt2는 힙 메모리에 하나의 값만 위치한 상태에서 스택의 변수 값이 같은 힙 위치를 가리킨다.
기본값
값 형식을 가리키는 변수는 해당 자료형에 대해 무조건 메모리가 할당되므로 그 메모리 영역에 필연적으로 기본 값(default value)을 갖게 된다.
.NET은 자료형에 대한 메모리를 할당하면 해당 영역을 무조건 0으로 초기화 한다. 따라서 값 형식의 변수를 선언할 때 개발자가 명시적으로 값을 초기화하지 않았다면 숫자형은 0을, bool은 false를 기본 값으로 갖는다.
참조 형식의 경우 null 값을 가진다.
bool result; //변수 result = false
int n; //n = 0
string text; // text = null
상수
- 변수는 값이 바뀌는 식별자이다. 반대로 값이 절대로 바뀌지 않는 의미의 상수(constant)도 있다.
- 상수 식별자에는 값이 한 번 대입되면 그 이후로 다른 값을 대입할 수 없다.
- 상수를 정의하는 방법 : 변수를 정의하는 구문에 'const' 예약어만 붙여주면 된다.
const bool result = false;
const int n = 5;
const string text = "Hello";
result = true; //컴파일 오류 발생 -> const 상수 값은 바꿀 수 없다.
컴파일러 입장에서 보면 상수는 반드시 컴파일할 때 값이 결정되어야 한다.
int n = Math.Max(0, 5); //프로그램을 실행할 때 n의 값이 결정됨.
//0과 5 중에서 5가 크므로 변수 n에는 5가 대입
const int maxN = Math.Max(0, 5); //Math.Max 메서드가 실행된 이후에 값이 결정되고,
//컴파일러가 미리 그 값을 결정할 수 없으므로 오류가 발생
변수 n에는 정상적으로 5가 대입되지만, maxN의 경우에는 컴파일러가 오류를 발생시킨다.
Math.Max는 코드로서 실행되야 하므로 컴파일러 입장에서 어떤 값을 대입해야 할지 컴파일 시점에서는 알 수 없기 때문이다.
같은 코드임에도 컴파일러가 값을 컴파일 시 계산해서 대입할 수 있다면 상수에 대입하는 것이 가능하다.
const int n = 5 * 100 / 2; //이러한 단순 수식은 컴파일러가 값을 계산할 수 있다.
n은 상수이지만, '5 * 100 / 2'는 상수식(const expression)이라고 한다.
상수식은 컴파일할 때 결정되는 수식을 말한다.
연산자, 문장 부호
예약어. 식별자, 리터럴 변수 외에 C# 프로그램을 구성하는 요소로 연산자(operator)와 문장부호(punctuator)가 있다.
namespace ConsoleApp2;
class Program
{
static void Main(string[] args)
{
string text = "Hello World";
Console.WriteLine(text);
}
}
붉은 색의 문자는 모두 연산자나 문장 부호이다.
;(세미콜론)은 한 구문의 끝을 컴파일러에게 알리는 문장 부호이다.
한 줄에 두 개 이상의 유효한 코드 구문을 넣는 것이 가능하다.
string text = "Hello"; text = text + "World";
Console.WriteLine(text); //출력 결과 : Hello World;
대입 연산자(assignment operator)인 = 는 그 이름을 정확하게 붙이지 않았지만, 연산자이다.
직관적으로 알 수 있는 산술 연산자(arithmetic operator : +, -, *, /, %)가 있는데, 산술 연산자는 정수형 및 실수형 데이터 타입에서 사용할 수 있다.
int n = 5;
int divider = 3;
int mod = n % divider;
Console.WriteLine(mod); //출력결과 2
string 자료형에서는 + 연산이 가능하다. 이 연산자를 이용하면 두 문자열을 하나의 문자열로 만들 수 있는데, string 자료형이 아닌 데이터를 문자열과 더하는 것도 가능하다.
int n = 500;
Console.WriteLine("n = " + n); //출력 결과 n = 500
이 경우 출력 결과처럼 데이터의 값 자체가 문자열로 변환되어 처리된다.
Reference
시작하세요! C# 12 프로그래밍 기본 문법부터 실전 예제까지