컴퓨터 프로그램에는 관할 데이터 외에도 동작해야 할 논리를 표현하는 문법이 필요합니다. C#에서는 프로그램 논리를 표현하기 위해 연산자와 식, 문 등을 제공합니다. 다른 언어를 접한 적이 있다면 속독으로 보셔도 되는 부분입니다.
2.2.1 연산자
연산자는 연산 기호와 피연산자를 사용하였을 때 수행할 행위와 결과 형식에 대한 약속입니다. 다음은 C#에서 제공되는 연산자들과 간략한 사용 예입니다.
▶ + (단항 연산자)
모든 숫자 형식에 제공이 됩니다. 연산 결과는 단순히 피연산자의 값으로 아무 의미가 없습니다.
static void Main(string[] args) { int a = 3; Console.WriteLine("a:{0} +a:{1}", a, +a); }
▶ + (이항 연산자)
숫자 형식 사이에 오면 두 수의 합을 계산합니다.
static void Main(string[] args) { int i = 2; int j = 3; Console.WriteLine("{0}+{1}={2}", i, j, i+j); }
문자열 사이에 오면 두 개의 문자열의 문자 컬렉션을 합한 문자열을 반환합니다.
static void Main(string[] args) { string s1 = "hello"; string s2 = "yahoo"; Console.WriteLine("{0}+{1}={2}", s1, s2, s1+s2); }
대리자 사이에 오면 두 개의 대리자에 위임된 행위를 위임한 대리자를 반환합니다. 이 부분은 9장 대리자와 이벤트에서 자세히 다루겠습니다.
delegate void ExamDele(); class Program { static void Main(string[] args) { ExamDele dele1 = new ExamDele(FunA); ExamDele dele2 = new ExamDele(FunB); ExamDele dele3 = dele1 + dele2; dele3(); } static void FunA() { Console.WriteLine("FunA "); } static void FunB() { Console.WriteLine("FunB "); } }
▶ – (단항 연산자)
모든 숫자 형식에 제공이 됩니다. 연산 결과는 단순히 피연산자의 -1을 곱한 값입니다.
static void Main(string[] args) { int a = 3; Console.WriteLine("a:{0} -a:{1}", a, -a); }
▶ – (이항 연산자)
숫자 형식 사이에 오면 두 수의 차를 계산합니다.
static void Main(string[] args) { int i = 2; int j = 3; Console.WriteLine("{0}-{1}={2}", i, j, i-j); }
열거형 형식 사이에 오면 두 열거형 값의 차를 계산합니다. 열거형은 4장 값 형식에서 자세히 다루겠습니다.
enum DemoEnum{ A=3, B=8 } static void Main(string[] args) { DemoEnum e1 = DemoEnum.A; DemoEnum e2 = DemoEnum.B; Console.WriteLine("{0} - {1} = {2}", e1, e2, e1 - e2); }
▶ * (단항 연산자)
안전하지 않은 컨텍스트에서만 사용이 가능합니다. 포인터 변수 선언문에 사용하거나 피연산자로 포인터 형식이 오면 간접연산이 수행됩니다. 안전하지 않은 컨텍스트와 포인터에 대한 사항은 MSDN을 참고하세요. 참고로 C#에서 안전하지 않은 코드를 사용하기 위해서는 [프로젝트]=>[속성]=>[빌드 탭]을 선택한 후에 안전하지 않은 코드 허용 체크 박스를 선택하여 빌드해야 합니다.
static void Main(string[] args) { unsafe { int a=3; int* p = &a; *p = 4; Console.WriteLine(a.ToString()); } }
▶ * (이항 연산자)
숫자 형식 사이에 오면 두 수의 곱을 계산합니다.
static void Main(string[] args) { int i = 2; int j = 3; Console.WriteLine("{0}*{1}={2}", i, j, i * j); }
▶ / (이항 연산자)
숫자 형식 사이에 오면 두 수의 나눗셈을 계산합니다. 두 개의 피연산자가 정수형일 때에는 우항에 0이 오면 DivideZeroException이 발생합니다. 두 개의 피연산자가 정수형일 때에는 내림 법칙에 의해 계산됩니다.
피연산자 중의 하나라도 실수가 오면 연산 결과는 실수가 됩니다. 피연사자 중의 하나라도 실수일 때 우항에 0이 오면 연산 결과는 Infinity가 됩니다. 피연산자 중의 하나라도 실수일 때 좌우항이 모두 0이면 연산 결과는 NaN이 됩니다.
static void Main(string[] args) { int i = 3; int j = 2; int intzero = 0; Console.WriteLine("{0}/{1}={2}", i, j, i / j); //결과: 1 ,내림 법칙 적용됨 //Console.WriteLine("{0}/{1}={2}", i, intzero, i / intzero); DivideZeroException 발생 float floatzero = 0.0f; Console.WriteLine("{0}/{1}={2}", i, floatzero, i / floatzero); //결과: Infinity Console.WriteLine("{0}/{1}={2}", intzero, floatzero, intzero / floatzero); //결과: Nan }
▶ 결과
3/2=1 3/0=Infinity 0/0=NaN
▶ % (이항 연산자)
숫자 형식 사이에 오면 두 수를 나누었을 때 나머지를 계산합니다.
static void Main(string[] args) { Console.WriteLine(3 % 2); Console.WriteLine(-4 % 10 ); Console.WriteLine(6.2 % 10); Console.WriteLine(-5.2 % 2.1); }
▶결과
1 -4 6.2 -1
▶ & (단항 연산자)
안전하지 않은 컨텍스트에서만 사용이 가능하며 피연산자의 주소를 계산하여 반환합니다.
static void Main(string[] args) { unsafe { int i=2; int* p = &i; Console.WriteLine("{0}", *p); } }
▶ & (이항 연산자)
정수 형식 사이에 오면 비트논리곱을 계산합니다. bool 형식 사이에 오면 논리곱을 계산합니다. 예를 들어 6&3은 0110(2진수)&0011(2진수)이므로 피연산자의 같은 자리의 비트가 1인 곳만 1이므로 0010(2진수)가 되어 연산 결과는 2가 됩니다.
static void Main(string[] args) { Console.WriteLine("{0}&{1}={2}", 6, 3, 6 & 3); Console.WriteLine("{0}&{1}={2}", true, false, true & false); }
▶결과
6&3=2 True&False=False
▶ | (이항 연산자)
정수 형식 사이에 오면 비트논리합을 계산합니다. bool 형식 사이에 오면 논리합을 계산합니다. 예를 들어 6 | 3은 0110(2진수) | 0011(2진수)이므로 피연산자 중 어느 하나라도 1인 자리는 1이 되므로 0111(2진수)가 되어 연산 결과는 7이 됩니다.
static void Main(string[] args) { Console.WriteLine("{0}|{1}={2}", 6, 3, 6 | 3); Console.WriteLine("{0}|{1}={2}", true, false, true | false); }
▶결과
6|3=7 True|False=True
▶ ^ (이항 연산자)
정수 형식 사이에 오면 비트 논리 배타합(xor) 연산을 계산합니다. bool 형식 사이에 오면 논리 배타합 연산을 계산합니다. 배타합이란 서로 다를 때 참이고 같을 때 거짓을 말합니다. 예를 들어 6 ^ 3은 0110(2진수) ^ 0011(2진수)이므로 피연산자의 같은 자리의 비트가 서로 다르면 1이 되므로 0101(2진수)가 되어 연산 결과는 5입니다.
static void Main(string[] args) { Console.WriteLine("{0}^{1}={2}", 6, 3, 6 ^ 3); Console.WriteLine("{0}^{1}={2}", true, false, true ^ false); }
▶결과
6^3=5 True^False=True
▶ << (이항 연산자)
왼쪽 피연산자를 둘째 피연산자(int) 만큼 왼쪽으로 비트 이동합니다. 왼쪽 피연산자의 자료형이 32비트 정수일 때는 오른쪽 피연산자의 하위 5비트만 유효합니다. 64비트 정수일 때는 오른쪽 피연산자의 하위 6비트만 유효합니다. 그리고 비트 이동으로 빈 자리는 0으로 채워집니다.
예를 들어 int형 변수 i의 값이 5일 때 i<<2를 요청하면 2비트 이동하여 연산 결과가 20이 됩니다.
그리고 int형 변수 i의 값이 5일 때 i<<33을 요청하면 33이 0100001(2진수)이므로 하위 5비트인 00001(2진수)만큼 비트 이동하여 연산 결과가 a(16진수)가 됩니다.
만약, long형 변수 l의 값이 5일 때 l<<33을 요청하면 0100001(2진수)이므로 하위 6비트인 100001(2진수)만큼 비트 이동하여 연산 결과가 a00000000(16진수)가 됩니다.
그리고 long형 변수 l의 값이 5일 때 l<<65를 요청하면 01000001(2진수)이므로 하위 6비트인 000001(2진수)만큼 비트 이동하여 연산 결과가 a(16진수)가 됩니다.
static void Main(string[] args) { int i = 5; long l = 5; Console.WriteLine("int: {0}<<{1}={2}", i, 2, i << 2); Console.WriteLine("int: {0}<<{1}={2:X}", i, 33, i << 33); Console.WriteLine("long: {0}<<{1}={2:X}", l, 33, l << 33); Console.WriteLine("long: {0}<<{1}={2:X}", l, 65, l << 65); }
▶결과
int: 5<<2=20 int: 5<<33=a long: 5<<33=a00000000 long: 5<<65=a
▶ >> (이항 연산자)
왼쪽 피연산자가 부호 있는 정수(int, long) 형식이면 빈 자리는 부호 비트로 채워지고 부호 없는 정수(uint, ulong) 형식이면 빈자리는 0으로 채워집니다.
static void Main(string[] args) { int i = -5; uint u = 5; Console.WriteLine("{0}>>{1}={2}", i, 2, i >> 2); Console.WriteLine("{0}>>{1}={2}", u, 2, u >> 2); }
▶결과
-5>>2=-2 5>>2=1
▶ ~ (단항 연산자)
피연산자를 비트 보수 연산을 합니다. 피연사자는 int, uint, long, ulong 형식이 올 수 있습니다.
static void Main(string[] args) { int i = -9; uint u = 9; Console.WriteLine("~{0:X8} => {1:X8}", i, ~i); Console.WriteLine("~{0:X8} => {1:X8}", u, ~u); }
▶결과
~FFFFFFF7 => 00000008 ~00000009 => FFFFFFF6
▶ = (이항 연산자)
피연산자는 같은 형식이거나 오른쪽 피연산자를 암시적으로 왼쪽 피연산자 형식으로 변환 가능해야 합니다.
static void Main(string[] args) { int i = 3; int re = 0; re = i; Console.WriteLine("re:{0}", re); }
▶ +=, -=, *=, /=,%=,&=,|=,^=,<<=,>>= (이항 연산자)
왼쪽 피연산자와 오른쪽 피연산자를 더하기(빼기, 곱하기,…)연산 결과를 왼쪽 피연산자에 대입합니다.
static void Main(string[] args) { int i = 3; int re = 0; re += i; Console.WriteLine("re+={0} 수행 후 re:{1}", i,re); re -= i; Console.WriteLine("re-={0} 수행 후 re:{1}", i, re); }
▶ ++, — (단항 연산자)
자기 자신을 1 증가시키거나 1 감소시킵니다. 연산자가 앞에 오면 연산 결과는 연산을 수행한 후의 자기 자신입니다. 연산자가 뒤에 오면 연산 결과는 연산을 수행하기 전의 값입니다.
static void Main(string[] args) { int i = 3; int re; Console.WriteLine("현재 i:{0}", i); re = ++i; Console.WriteLine("++i, 연산결과 {0}, i:{1} ", re,i); re = i++; Console.WriteLine("++i, 연산결과 {0}, i:{1} ", re, i); }
▶ ==, != (단항 연산자)
두 개의 피연산자가 같은지 다른지를 판별하여 bool 형식 결과 값을 반환합니다. 값 형식과 string 형식이 피연산자로 올 경우에는 값이 같은지를 판별합니다. string 형식을 제외한 참조 형식이 피연산자로 올 경우에는 참조하는 개체가 같은지를 판별합니다.
static void Main(string[] args) { int i = 3; int j = 3; Console.WriteLine("i:{0} j:{1}, == 결과:{2}", i, j, i == j); object o1 = 3; object o2 = 3; Console.WriteLine("o1:{0} o2:{1}, == 결과:{2}", o1, o2, o1 == o2); }
▶ 결과
i:3 j:3, == 결과:True o1:3 o2:3, == 결과:False
▶ <, <=, >, >= (이항 연산자)
피연산자의 크기를 비교한 결과를 bool 형식 값으로 반환합니다. 모든 숫자 형식과 열거형이 피 연산자로 올 수 있습니다.
static void Main(string[] args) { int i = 2; int j = 3; Console.WriteLine("{0}<{1} => {2}", i, j, i < j); }
▶ &&, || (이항 연산자)
피연산자로 bool 형식이 올 수 있으며 논리곱과 논리합을 수행합니다. && 연산은 왼쪽 피연산자가 거짓인 때 뒤에 코드를 수행하지 않습니다. || 연산은 왼쪽 피연산자 참이면 뒤에 코드를 수행하지 않습니다.
static void Main(string[] args) { bool re = TestA() && TestB(); Console.WriteLine("TestA() && TestB() 결과:{0}", re); re = TestB() || TestA(); Console.WriteLine("TestB() || TestA() 결과:{0}", re); } private static bool TestB() { Console.WriteLine("TestB"); return true; } private static bool TestA() { Console.WriteLine("TestA"); return false; }
▶ 결과
TestA TestA()&&TestB() 결과:False TestB TestB() || TestA() 결과:True
▶ ! (단항 연산자)
bool 형식이 피연산자로 올 수 있으며 부정을 반환합니다.
static void Main(string[] args) { bool flag = true; Console.WriteLine("!{0}=>{1}", flag, !flag); }
▶ [] (이항 연산자)
배열의 요소에 접근할 때 사용할 수 있습니다. 또한, 요소 개체를 보관하는 컬렉션에서 요소를 접근하는 인덱서로 사용이 됩니다. 인덱서는 5장 캡슐화에서 다루겠습니다. 또한, 단항 연산자로 특성(Attribute)에 사용이 됩니다. 이 책에서는 특성은 다루지 않습니다.
static void Main(string[] args) { int[] arr = new int[10]; for (int i = 0; i < arr.Length; i++) { arr[i] = i + 1; } foreach (int i in arr) { Console.WriteLine(i.ToString()); } }
이 외에도 C#에서는 개체를 생성할 때 사용하는 new 연산자를 비롯하여 삼항 조건 연산자인 ?:와 왼쪽 피연산자가 null이 아닐 때 연산 결과로 왼쪽 피연산자를 반환하고 null일 때 오른쪽 피연산자를 반환하는 ??를 비롯하여 멤버에 접근하기 위한 .연산자 등을 제공합니다. 여기에 소개되지 않은 더 많은 연산자는 MSDN을 참고하시기 바랍니다.