본론에 앞서
이 블로그는 어디까지나 본인의 학습을 위해 정리를 해두는 곳이라 이미 잘 알고 있는 정보는 가볍게 서술하고 헷갈리거나 잊어버리기 쉬운 개념일수록 빨간색에 가깝게 표시해뒀다.
연산자
산술연산자: 사칙연산과 같은 숫자를 대상으로 사용하는 연산자 ex) +, -
관계연산자: 두 값을 비교하여 True, False 값을 반환 ex) >, <
논리연산자: True 혹은 False 값을 대상으로 쓴다. ex) &&, ||, !
이 세가지는 잘알고 있는 연산자지만 비트연산자는 생소할 수 있어 좀 더 자세히 다뤄본다.
비트연산자
비트연산자: 비트(bit) 단위로 연산을 수행하는 연산자
비트는 0과 1로 구성되어 있음, 다시 말해 2진수이다.
2진수를 나타내는 리터럴은
0b를 앞에 붙혀주면 된다. ex) int a = 0b1100; (1 x 0 + 2 x 0 + 4 x 1 + 8 x 1 = 12, 12를 의미)
16진수는 0x를 붙혀주면 된다. ex) 0x1A; (1 x A(10) + 16 x 1 = 26, 26을 의미한다)
1을 반환하는 기준은 아래와 같다.
&(AND): 두 비트 값이 모두 1일 때
|(OR): 두 비트 중 하나라도 1이라면
^(XOR): 두 비트 값이 서로 다를 때
~(NOT): 비트 값을 반대로 설정한다.
아래는 논리회로 수업 때 자주 썼던 표를 만들어 표시했다.
A | B | & | | | ^ | ~(A만) |
0 | 0 | 0 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 1 | 1 |
1 | 0 | 0 | 1 | 1 | 0 |
1 | 1 | 1 | 1 | 0 | 0 |
여기서 추가적으로 까먹기 쉬운 것이 '시프트'이다.
시프트 연산자
시프트 연산자도 비트 연산자의 한 종류로 비트를 이동시킬 때 쓴다.
<< : 왼쪽 시프트, 비트를 왼쪽으로 이동시킨다.
c << 2 라면
c가 이진수로 1100 생각하면 마지막에 1011. 으로 점을 찍어주고 오른쪽으로 점을 c << 2 이니 두 칸 이동시켜 101100. 이 된다. 왜냐면 왼쪽으로 이동하는 것은 비트지 점이 아니다. 고로 점을 왼쪽 시프트라면 오른쪽으로 이동시키면 된다.
>> : 오른쪽 시프트, 비트를 오른쪽으로 이동시킨다.
c >> 1이라면
만일 1011이 있다면 위에서 언급한대로 점을 마지막에 찍고 1011. 오른쪽 시프트니 점은 왼쪽으로 이동시킨다. 그러면 101.1 이 되는데 . 아래에 있는 것들은 그냥 버려주면 된다. 고로 101이 된다.
요약) 시프트 연산자는 마지막에 점을 찍고 시프트 방향과 반대로 점을 이동시켜준다. 왜? 비트가 시프트 되는 것이니 점은 반대로 이동한다.
복합 대입 연산자
뭔지는 알고 있는데 용어가 생소하니 알아두자 +=, -= 같은 것들이다.
다만 == 은 관계연산자다. 두 값을 비교해 True, False를 반환하기 때문!
증감 연산자
++ 혹은 --
연산자 우선순위
높은 순서대로 나열
1. 괄호
2. 단항 연산자 (++, --, +, -, ! 등) 주의: +는 더하기 +이 아니라 부호를 나타내는 것,
3. 산술 연산자, 산술 연산자 내에서의 우선순위는 수학에서의 우선순위를 생각하면 됨
4. 시프트 연산자
5. 관계 연산자
6. 논리 연산자, !는 논리연산자지만 단항 연산자이기에 우선순위가 높다.
7. 할당(대입) 연산자
=================================
이제 문자열 처리 기능 및 메서드에 대해 알아보자.
문자열 관련은 본인도 공부하면서 자주 까먹고 학교에서 자주 쓰던 C++과 문법적으로 차이가 있어 잘 알아둬야 할 것 같다.
문자열 생성
string str1 = "Hello, World!"; // 리터럴 문자열 사용
string str2 = new string('H', 5); // 문자 'H'를 5개로 구성된 문자열 생성
str2의 경우엔 "HHHHH"가 된다.
문자열 연결
string str1 = "I am ";
int age = 20;
string str2 = " years old";
string str3 = str1 + age + str2;
+ 연산자를 사용하여 문자열을 연결해주는 모습이다.
+ 로 문자열이 아닌 변수를 이어줄 때도 굳이 ToString 메서드를 호출 하지 않아도 내부적으로 호출하기 때문에 안써도 된다.
문자열 연결은 +로 연결하는 쉬운 방법이 있긴 하지만 많은 수의 문자열을 연결하거나 반복문을 내에서 연결하면 새로운 문자열 객체를 여러번 생성하니 부담을 줄 수가 있다.
고로 문자열 포맷팅이라는 방법을 쓴다.
문자열 포맷팅
문자열 포맷팅에는 '문자열 형식화'와 '문자열 보간'의 2가지의 방식이 있다.
- 문자열 형식화
string name = "bugmin";
int age = 20;
Console.WriteLine("Hello, My name is {0} and I'm {1} years old", name, age);
{0}과 {1}에 각각 name 과 age의 값이 삽입된다.
-문자열 보간
string name = "bugmin";
int age = 20;
Console.WriteLine($"Hello, My name is {name} and I'm {age} years old");
큰따옴표 이전에 $ 표시를 붙여 문자열 보간 기능을 사용하겠다 알리고 중괄호 안에 삽입하고자 하는 값에 해당되는 변수명(식별자)를 적어주면 된다.
그러면 + 로 문자열을 연결하면 안되는가?
스파르타코딩클럽의 튜터님의 답변에 따르면 최대한 자제하는 것이 좋다고 한다. 귀찮을 때 +로 하고 리팩토링 할 때 고치는 경우가 많으며 실무에선 미리 만들어둔 문자열 확장 메서드를 써라 하는 경우도 있다고 한다.
StringBuilder도 많이쓰며 코딩테스트 문제를 풀때 특히 StringBuilder를 쓰는 것이 좋다고 조언해주셨다.
문자열 분할
string str = "Hello, World!";
string[] words = str.Split(',');
string의 Split 메서드는 특정한 구분자 값으로 문자열을 나누어준다.
위의 코드에선 쉼표를 구분자로 문자열을 나누어 주었기에
words[0] == "Hello"
words[1] == " World!"
구분자를 포함하진 않지만 공백은 포함해야한다. (구분자가 공백이면
문자열 검색
string str = "Hello, World!";
int index = str.IndexOf("World");
string의 IndexOf 메소드는 특정 문자의 시작위치 인덱스를 가져온다.
위의 예시에선 "Hello, World!" 이라는 문자열 속에 "World"의 시작위치 인덱스인 7을 가져오게 된다.
표에서 볼 수 있듯이 World는 7번째 index서 시작한다.
문자열 대체
string str = "Hello, World!";
string newStr = str.Replace("World", "Universe");
Replace는 내가 원하는 하는 단어를 다른 단어로 치환하는 것이다.
위의 코드에선 "World" 단어를 "Universe"로 바꾼 것을 newStr 변수에 저장해주고 있다.
하지만!
원본은 그대로 있다.
즉 위에 코드에선 str을 출력하면 그대로 "Hello, World!"가 나오고 newStr을 출력해야 "Hello, Universe"가 출력됨
문자열을 숫자로 변환
int num = int.Parse(Console.ReadLine());
이전글에서 다루었던 내용이지만 연관되어 다시 서술한다.
콘솔 입력을 받는 메서드가 ReadLine 메서드라 했으며 이는 string만을 반환한다.
이 string 값을 int로 쓰고 싶다면
string의 경우엔 형변환이 아니라 변환을 하고자 하는 자료형한테 요청을 해야한다. 고로 int의 기능인 Parse를 써 정수형으로 보여지는 문자열을 정수로 변환을 시킨다. float도 Parse 함수를 이용할 수 있다.
근데 만일 Parse를 하려하는데 엉뚱한 값이 들어올 수도 있다.
int로 변환하려는데 string 값이 "bugmin" 이런 거면 곤란하다.
이러한 점을 방지하기 위한 함수가 따로 있다.
TryParse
int num;
if(int.TryParse(Console.ReadLine(), out num))
{
Console.WriteLine($"입력된 숫자는 {num}");
}
else
{
Console.WriteLine("정수형 숫자가 아닙니다!");
}
TryParse를 이용하면 자료형에 알맞은 입력이 들어와야만 변수에 값을 넣고 True 값을 반환한다.
만일 자료형에 맞지 않는 입력이 들어온다면 변수에 값을 넣지 않으며 False 값을 반환한다.
만일 해당 코드서 10을 입력하면 변수 num에 10이 들어갈 것이고 정수형 숫자가 아닌 3.14 같은 값을 입력하면 else로 가 "정수형 숫자가 아닙니다" 라는 문구를 출력할 것이다.
보다 안전한 처리를 원한다면 TryParse를 사용하는 것이 좋을 것이다.
튜터님에 답변에 따르면 반환값이 있기에 if문을 통해 더 추가적인 로직을 구사할 수 있는 것도 장점이라 실무에서도 TryParse를 더 많이 쓴다고 한다. 유니티서도 GetComponent 대신 예외처리가 가능한 TryGetComponent를 쓴다고 한다.
숫자를 문자열로 변환
int num = 123;
string str = num.ToString();
알고 있듯이 ToString 메서드를 이용하면 된다.
문자열 비교
string str1 = "Apple";
string str2 = "Banana";
if(str1 == str2) Console.WriteLine("두 단어는 같습니다");
else Console.WriteLine("두 단어는 다릅니다");
int compare = string.Compare(str1, str2);
if(compare == 0) Console.WriteLine("두 단어는 같습니다 by Compare");
else if(compare > 0) Console.WriteLine("첫 번째 단어가 사전 순으로 뒤에 옵니다");
else Console.WriteLine("두 번째 단어가 사전 순으로 뒤에 옵니다");
문자열 비교는 관계연산자인 "==" 를 써도 되고 Compare 함수를 써도 된다.
Java에선 "=="를 쓰면 안되는 걸로 기억하는데 C#에선 성능차이도 크게 없기에 상관이 없다.
"==" 가 편한데 굳이 Compare 함수를 써야하나...?
Compare 함수는 단순히 같은지 다른지만 판별하는 것이 아니라 문자열의 대소 비교도 가능하다.
대소 비교는 사전 순으로 사전의 뒤에 나올 수록 더 큰 것이다. 문자열의 길이가 중요한 것이 아니라 사전의 우선순위가 중요한 것이다.
string.Compare(str1, str2)라면 str1 - str2 로 생각하면 편하다.
우리가 양수간의 뺄셈을 생각해보면 큰 수에서 작은 수를 빼면 양수가 나오고 작은 수에서 큰 수를 빼면 음수가 나오고 같은 수를 빼면 0이 나온다. Compare 함수가 그러하다.
즉 str1이 str2보다 사전 순 우선순위가 높으면 양수가 나오고 둘이 같으면 0이 나오고 str1이 str2보다 우선순위가 낮으면 음수가 나온다.
그 값을 int로 반환하기에 반환 받은 int 값이 양수면 첫 번째 문자열이 더 큰 거고 0이면 둘이 같은 것이고 음수면 두 번째 문자열이 더 큰 것이다. 아까도 말했듯이 크다는 것은 사전 순으로 뒤에 온다는 것이다.
하지만 튜터님 말에 의하면 문자열을 대소 비교하는 상황은 많이 없다고 한다. 검 아이템을 비교할 때 id를 두고 비교하지 검의 이름으로 비교하지는 않듯이 말이다.
'C#' 카테고리의 다른 글
5/1 TIL - Delegate, Func, Action, 람다 (2) | 2024.05.01 |
---|---|
C# 인터페이스와 열거형 (1) | 2024.04.26 |
C# 고급 문법 및 기능 (0) | 2024.04.25 |
C# OOP와 클래스 (0) | 2024.04.24 |
C# 입출력과 각종 용어들 (0) | 2024.04.22 |