본문 바로가기

프로그래밍/C++57

C++ ] CLI Loading indicator와 Progress bar 사용자에게 작업이 진행중임을 알리는 간단한 로딩 인디케이터와 프로그레스 바 만들어보기   로딩 인디케이터 #include #include using namespace std;bool running = true;void loading_indicator() { const char* states[] = {" ", ". ", ".. ", "... ", ".... ", "....."}; int index = 0; while (running) { cout      프로그레스 바 #include #include #include using namespace std;class ProgressBar {private: int barWidth; int progress;.. 2024. 10. 28.
C++ ] 가변 인자 템플릿(Variadic Templates) 활용 C++ 11에서 도입된 가변 인자 템플릿이란 함수가 불특정 다수의 여러 인자를 받을 수 있게 해주는 기능이다. 가변 인자 템플릿은 C의 stdarg.h에 있는 가변 인자 매크로들과 비슷한 역할을 한다고 볼 수 있는데 그 사용법에 있어서는 큰 차이가 있다. 이에 대해 한번 알아보자.   1. 템플릿(Templates)이란?템플릿은 C++에서 함수나 클래스를 정의할 때 그 타입을 일반화하여 코드 재사용성을 높이는 기능이다. 템플릿을 사용하면 특정 데이터 타입에 종속되지 않고, 다양한 타입에 대해 동일한 코드 구조를 사용할 수 있다. 템플릿을 선언할 때는 다음과 같은 구문을 사용한다. 여기서 T는 타입 매개변수로 함수나 클래스가 다양한 데이터 타입에 대해 동작하도록 일반화할 수 있게 해준다. template .. 2024. 9. 2.
C++ ] 문자열 탐색 및 조작 함수, find() 외 문자열 탐색 함수 find : 문자열/문자의 처음 등장 위치 찾기rfind : 문자열/문자의 마지막 등장 위치 찾기find_first_of : 문자 집합 중 하나라도 처음으로 등장하는 위치 찾기find_last_of : 문자 집합 중 하나라도 마지막으로 등장하는 위치 찾기find_first_not_of : 주어진 문자 집합에 없는 첫번째 문자 위치 find_last_not_of : 주어진 문자 집합에 없는 마지막 문자 위치 찾기 ✔️ 위의 문자열 탐색함수는 문자열 내에서 특정 조건을 만족하는 요소를 찾지 못했을 경우 string::npos를 반환한다. string::npos는 상수값으로 size_t 타입의 최대값이다.✔️ find와 rfind 함수는 주어진 문자열에서 특정 문자열 뿐만 아니라 단일 문자를.. 2024. 6. 11.
C++ ] std::condition_variable 사용법 std::condition_variable은 C++에서 스레드 간의 동기화를 위해 사용되는 매커니즘 중 하나로, 한 스레드가 특정 조건이 충족될 때까지 대기하도록 하고, 다른 스레드가 그 조건을 충족시켰을 때 대기 중인 스레드를 깨우는 방식으로 사용한다. std::condition_variable은 주로 std::mutex와 함께 사용되며, 뮤텍스의 소유권을 관리하기 위해 std::unique_lock 클래스와 결합하여 사용된다. std::condition_variable 사용 패턴 스레드는 std::unique_lock를 사용하여 뮤텍스를 잠근다. 스레드는 std::condition_variable의 대기 함수(wait, wait_for, wait_until)를 호출하여 특정 조건이 충족될 때까지 대기.. 2024. 5. 12.
C++ ] std::unique_lock과 std::lock_guard의 차이 std::unique_lock과 std::lock_guard의 차이 std::unique_lock과 std::lock_guard 모두 스코프 기반의 락 관리를 제공한다. 객체가 스코프에 진입할 때 자원을 획득(락을 잠금)하고, 스코프를 벗어날 때 소멸자를 호출해 자동으로 자원을 해제(락을 해제)하는 방식이다. 사용자가 { }를 사용하여 직접 스코프를 지정하는 패턴으로 사용할 수도 있다. 둘의 차이점은 다음과 같다. std::lock_guard : 생성될 때 자동으로 락을 획득하고 소멸될 때 락을 해제한다. 도중에 수동으로 락을 잠그거나 풀 수 있는 기능을 제공하지 않는다. std::unique_lock : 생성될 때 자동으로 락을 획득하고 소멸될 때 락을 해제하며 lock(), unlock() 메소드를 .. 2024. 5. 4.
C++ ] std::unordered_map과 std::map의 차이, unordered_map의 사용법 std::unordered_map unordered_map은 STL에서 제공하는 해시 테이블 기반의 키-값 쌍을 저장하는 컨테이너로, 사용법은 map과 유사하지만 몇 가지 차이점이 있다.  내부 구현: map은 균형 이진 검색 트리(레드-블랙 트리)를 사용하여 요소를 저장한다.unordered_map은 해시 테이블을 사용하여 요소를 저장한다. 접근 시간: map에서 키-값 쌍에 접근하는 데 걸리는 시간은 O(log n)이다.unordered_map은 평균적으로 상수 시간 O(1)에 키-값 쌍에 접근할 수 있다. 단, 해시 충돌이 발생할 경우 최악의 경우 시간 복잡도는 O(n)에 수렴한다. 정렬: map은 키에 대해 unordered_map은 입력된 순서나 키에 대한 정렬을 유지하지 않고, 해시 함수의 결.. 2024. 4. 26.
C++ ] std::set 사용법 std::set STL에 포함되는 std::set은 중복이 없이 정렬된 데이터를 쉽게 관리할 수 있게 해주는 컨테이너로 내부적으로는 균형 이진 검색 트리(레드-블랙 트리)를 사용하여 요소를 저장한다. 가장 큰 특징은 중복을 허용하지 않으므로 모든 요소는 유일하며, 요소를 추가, 삭제, 검색할 때 std::set은 O(log n)을 보장한다. 1. set에 요소 추가하기 set에 요소를 추가할 때는 .insert() 메소드를 사용한다. 삽입 시도 시 중복 요소가 없으면 성공적으로 추가되고, 중복 요소가 있으면 추가되지 않는다. #include #include int main() { std::set mySet; // 요소 추가 mySet.insert(3); mySet.insert(1); mySet.inser.. 2024. 4. 22.
C++ ] std::iota 사용법 처음엔 itoa의 오타인 줄 알았다. std::iota는 헤더에 정의된 C++ 표준 라이브러리 함수로 배열이나 컨테이너에 연속적 숫자를 할당한다. 시작 반복자, 종료 반복자, 그리고 초기 값의 세 개의 매개변수를 받는다. #include #include std::vector v(10); std::iota(v.begin(), v.end(), 0); // v는 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}로 초기화됨 for 반복문을 사용해서 동일한 작업을 수행할 수 있다. 둘 다 O(n) 시간 복잡도를 가지며 유의미한 성능차이는 없을 것 같다. std::vector v(10); for(int i = 0; i < v.size(); ++i) { v[i] = i; } 2024. 4. 19.
C++ ] leetCode 3005 - Count Elements With Maximum Frequency 리트코드 3005 문제 You are given an array nums consisting of positive integers. Return the total frequencies of elements in nums such that those elements all have the maximum frequency. The frequency of an element is the number of occurrences of that element in the array. Example 1: Input: nums = [1,2,2,3,1,4] Output: 4 Explanation: The elements 1 and 2 have a frequency of 2 which is the maximum frequ.. 2024. 4. 16.
C++ ] std::stack 사용법 C++의 std::stack은 STL(Standard Template Library)의 일부로 후입선출(LIFO: Last In, First Out) 방식을 따르는 자료구조이며 데이터의 삽입(push)과 삭제(pop)가 한 쪽 끝에서만 이루어지는 특징을 가지고 있다. 1. stack 헤더 파일 포함 #include 2. stack 객체 생성 std::stack myStack; 3. 요소 삽입 (push) myStack.push(10); 4. 요소 제거 (pop) myStack.pop(); 5. 최상위 요소 접근 (top) std::cout 2024. 3. 20.
C++ ] std::pair 사용법 C++에서 pair는 두 개의 값을 하나의 단위로 묶을 때 유용한 STL(Standard Template Library)의 일부이다. pair를 사용하면 문자열과 정수, 정수와 정수 등 다양한 데이터 타입의 두 값을 쌍으로 묶어 쉽게 관리할 수 있다. 1. std::pair 사용을 위한 헤더 포함 #include std::pair는 utility 헤더를 포함시키면 사용할 수 있지만 이미 vector나 map, algorithm 같이 자주쓰는 헤더에 utility 헤더가 포함되어 있기 때문에 별도로 utility 헤더를 포함시키지 않아도 쓸 수 있는 경우가 많다. 2. pair 생성하고 요소에 접근하기 #include #include #include using namespace std; int main().. 2024. 3. 1.
C++ ] std::sort 사용법 with 람다식 C++에서 제공하는 표준 라이브러리 함수 std::sort는 벡터, 리스트, 배열 등 다양한 컨테이너를 정렬하는 데 사용된다. 1. algorithm 헤더 포함 #include 2. std::sort 함수 원형 template void sort(RandomIt first, RandomIt last); template void sort(RandomIt first, RandomIt last, Compare comp); first: 정렬을 시작할 범위의 첫 번째 요소를 가리키는 반복자 last: 정렬을 종료할 범위의 마지막 다음 요소를 가리키는 반복자 comp: (optional) 정렬 기준을 제공하는 함수나 함수 객체로 람다식으로 표현될 수 있다 만약 비교함수 comp가 사용자에 의해 제공되지 않은 경우 기.. 2024. 2. 20.
C++ ] string -> char *, char * -> string 변환 C스타일 문자열 char *에서 string으로 변환하거나 반대로 변환하는 법 char * to string const char *pChar ="hello"; string str = pChar; 또는 string str(pChar); char *에서 string으로 바꾸는건 간단하다. = 연산자를 통해 초기화하거나 생성자를 통해 초기화하면 된다. sring to char * string str = "hello"; const char *pChar = str.c_str(); 또는 char buf[str.length() + 1]; strcpy(buf, str.c_str()); string에서 char *로 변환할 때는 string의 수명에 주의해야한다. 위의 변환 방법 중 첫번째 방법의 경우 string 변수.. 2024. 2. 18.
C++ 에서 구조체와 클래스의 차이 아래에서 보듯이 C++에서 구조체와 클래스는 사용방법과 기능이 매우 유사하다. C++의 구조체는 멤버함수를 추가할 수도 있으며, 명시적으로 생성자/소멸자를 추가할 수 있고 명시적으로 정의하지 않으면 수명주기에 따라 호출되는 기본 생성자/소멸자가 컴파일러에 의해 제공된다는 점이 클래스와 동일하다. 반면 구조체와 클래스의 주요 차이는 접근제어 지시자의 기본값이 다르다는 것이다. 접근제어 지시자를 따로 명시하지 않고 정의한 경우 구조체는 기본적으로 public 이고, 클래스는 기본적으로 private이다. #include using namespace std; struct Point { int x; int y; Point(int _x, int _y) : x(_x), y(_y) { cout 2024. 2. 14.
C++ ] 벡터의 최대값과 최소값 찾기, max_element, min_element C++에서는 헤더에 포함된 std::max_element와 std::min_element 함수를 사용하여 벡터의 최대값과 최소값을 찾을 수 있다. 리턴값은 가장 큰 또는 가장 작은 원소의 iterator이고 애스터리스크를 붙여 역참조해서 값을 가져올 수 있다. max_element 함수 사용 예시 #include #include #include int main() { std::vector numbers = {10, 5, 8, 3, 12, 7}; auto max_iterator = std::max_element(numbers.begin(), numbers.end()); int max_value = *max_iterator; std::cout 2024. 2. 10.
Win32 API ] 윈도우 프로그램의 기본 구조 MFC 프레임워크 안에 숨겨진 윈도우 프로그램의 기본 구조를 이해해보자. Windows에서 실행되는 애플리케이션의 종류 윈도우에서 실행되는 애플리케이션은 크게 콘솔 애플리케이션과 GUI 애플리케이션으로 나눌 수 있다. 콘솔 앱 또는 데스크톱 애플리케이션 만들기 프로젝트 생성 시 콘솔 또는 데스크톱 애플리케이션을 지정하여 생성할 수 있으며, 이미 생성한 프로젝트를 콘솔 또는 GUI 애플리케이션으로 변경하려면 프로젝트 우클릭 - 속성 - 링커 - 시스템 - 하위 시스템에서 변경 가능하다. Windows C/C++ 애플리케이션의 진입점 프로그램이 실행될 때 가장 먼저 호출되는 함수를 프로그램의 진입점(entry point)라고 하고, 이 진입점을 호출하는 함수는 C/C++ 런타임(CRT) 초기화를 담당하는 함.. 2023. 12. 13.
C/C++ ] 윈도우 콘솔 프로그램 아이콘 변경하기 + 무료 아이콘 찾기 콘솔 프로그램의 기본 아이콘을 변경해보자.      IconFinder 등의 사이트에서 무료 상업용 아이콘 파일을 찾는다. https://www.iconfinder.com/search?q=smile&price=free&license=gte__1 Icons - IconfinderDownload 7,594,057 icons. Available in PNG and SVG formats. Ready to be used in web design, mobile apps and presentations.www.iconfinder.com     Free icon 중에서도 Free for commercial use라고 되어있는 것을 찾는다.     ICO 파일이 아니라 PNG 파일로 다운받는데 128 px 이상으로 받는.. 2023. 12. 9.
C/C++ ] start 명령어 사용 새로운 프롬프트 창에서 시스템 명령 사용하기 start 명령어는 일반적으로 명령 프롬프트나 배치 파일에서 다른 프로그램 또는 명령을 시작하는 데 사용한다. 사용 방법 start [/d ] [/i] [{/min | /max}] [{/separate | /shared}] [{/low | /normal | /high | /realtime | /abovenormal | /belownormal}] [/node ] [/affinity ] [/wait] [/b] [/machine ] [ [... ] | [... ]] https://learn.microsoft.com/ko-kr/windows-server/administration/windows-commands/start start 지정된 프로그램 또는 명령을 실행하기 위해 별도의 명령 프롬프트 창을 시작하는 시.. 2023. 12. 8.
C/C++ ] 키보드 메시지 후킹을 사용한 간단 키로거 WH_KEYBOARD_LL 저수준 키보드 후크는 해당 애플리케이션 창이 비활성화되거나 포커스가 없는 상태에서도 모든 키보드 입력 이벤트를 후킹할 필요가 있을 때 사용한다. OS 시스템 메시지 큐에 있는 키 이벤트가 응용프로그램에 도달하기 전에 후킹 되므로 후크 프로시저에서 이벤트를 차단하거나 수정할 수 있다. #include #include #include #include HHOOK hHook; std::ofstream logfile; LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) { LPKBDLLHOOKSTRUCT key = (LPKBDLLHOOKSTRUCT)lParam; if (code >= 0 && wParam == WM.. 2023. 11. 21.
C++ ] mutex, lock_guard mutex mutex(뮤텍스)란 다중 스레드 환경에서 공유 데이터에 대한 동시 접근을 제어하기 위해 사용하는 동기화 기법 중 하나이다. mutual exclusion의 약자로 여러 스레드가 동시에 접근하지 못하도록 lock 하는 기능을 제공한다. mutex 사용 예시 #include #include #include using namespace std; mutex myMutex; int sharedVariable = 0; void incrementSharedVariableWithMutex(int iterations) { for (int i = 0; i < iterations; ++i) { myMutex.lock(); // 뮤텍스를 잠근다. // 공유 데이터에 대한 안전한 업데이트를 수행한다. ++share.. 2023. 10. 8.
C++ ] 인라인 함수 인라인 함수란? 보통 일반함수를 사용할 때는 함수의 선언은 헤더파일에 하고 정의는 소스파일에 분리한다. 하지만 함수를 헤더파일에 정의하고 inline 키워드를 사용하면 인라인 함수로 만들 수 있다. inline 키워드를 사용하면 컴파일러는 해당 함수를 호출하는 곳에 직접 인라인 함수 코드를 삽입한다. 이렇게 하면 보통 일반함수를 헤더에 정의했을 때 발생하는 오류인 함수 정의가 여러번 중복된다는 오류를 피할 수 있다. 인라인 함수 사용이 효과적인 경우 보통 인라인 함수는 함수 코드 크기가 작고 간단할 때 가장 효과적이다. 인라인 함수가 너무 크거나 빈번하게 호출되는 경우 해당 인라인 함수 코드가 함수 호출 부분에 삽입되기 때문에 코드 크기가 커져 메모리 사용량이 증가하고 캐시 효율성을 낮출 수 있다. 인라.. 2023. 10. 7.
C++ ] 연속된 메모리 공간에 저장되는 벡터의 메모리 재할당 빈도 C++의 std::vector는 동적 배열로 구현되어 있어, 내부적으로 메모리를 할당하고 관리하는데 요소를 연속된 메모리 공간에 저장하는 것을 보장한다. 때문에 데이터에 접근할 때 빠른 속도를 제공하고, C스타일 배열 접근 방식을 써서 효율적으로 사용할 수도 있다. vector에 요소를 계속 추가하면 vector는 내부적으로 메모리를 다시 할당하고 복사해 옮기는 작업을 수행하는데 이를 재할당이라고 한다. 재할당은 vector의 현재 capacity(용량)와 size(크기)를 비교하여 추가 요소를 수용하기 부족하다고 판단되는 경우 발생할 수 있다. 메모리 재할당은 비용이 높은 작업이고 벡터가 자주 재할당되면 성능이 저하될 수 있기 때문에 이를 최소화하기 위해 reserve()를 사용하여 용량을 예약하는 방.. 2023. 10. 6.
C++ ] enum class 사용법 enum class 선언 enum class를 정의할 때 명시적으로 기본 타입을 지정하지 않으면 int 형으로 설정된다. 또한 첫번째 멤버의 값을 지정하지 않은 경우 첫번째 멤버는 0으로 초기화되고 그 다음 멤버는 1씩 증가한다. enum class MyEnum { VALUE1, VALUE2, VALUE3 }; 열거형 멤버 접근 :: 연산자를 사용해 접근한다. MyEnum myVar = MyEnum::VALUE1; 정수값으로 변환 enum 클래스의 기본 타입이 int라고 해도 바로 정수값으로 변환되지는 않기 때문에 형변환 연산자를 사용해야 한다. int intValue = 0; MyEnum enumValue = MyEnum::VALUE1; if (intValue == enumValue) { // 오류! .. 2023. 10. 3.
C++ ] .ini 파일 포맷, 설정 파일 입출력 방법 .ini 파일 포맷 .ini 파일은 텍스트 파일로 주로 설정 데이터를 저장하는 데 사용된다. 그리고 각 설정은 섹션과 키-값 쌍으로 구성된다. [섹션] 키=값 [Database] ServerName=MyServer Port=5432 [User] Username=JohnDoe Password=Secret123 아래 소개할 함수들은 Windows API의 일부로 Windows.h를 포함해야 사용할 수 있으며, 설정 파일에서 구성 데이터를 읽어올 때나, 설정파일에 구성 데이터를 쓸 때 사용할 수 있다. GetPrivateProfileInt 섹션과 키를 사용하여 .INI 파일에서 설정 값을 찾으며, 설정 값을 정수(INT) 형태로 반환한다. 만약 해당 섹션이나 키를 찾지 못하면 기본값으로 설정된 nDefault.. 2023. 9. 27.
C++ ] 람다식 사용법 및 람다식으로 콜백 구현 람다식(lambda expression)을 사용하면 코드 내에서 명시적인 함수 정의 없이도 익명함수를 생성하고 사용할 수 있게 해준다. 이런 함수는 주로 한번만 사용하거나 특정 상황에서만 필요한 경우 유용하다. 람다식의 기본 구조 [캡처](매개변수) -> 반환형 { // 람다 함수 본문 // ... } [캡처] { // 람다 함수 본문 // ... } 1. [캡처] : 외부 변수를 사용하기 위해 외부 범위의 변수를 캡처할 수 있다. [ ] 안에 캡처할 변수를 지정한다. [] : 아무것도 캡처하지 않음 [변수] : 특정 변수를 값 복사로 캡처 [&변수] : 특정 변수를 레퍼런스로 캡처 [=] : 모든 외부 변수를 값 복사로 캡처 [&] : 모든 외부 변수를 레퍼런스로 캡처 2. (매개변수) : 람다 함수의.. 2023. 9. 26.
C++ ] std::thread 스레드 사용법 스레드 라이브러리 스레드를 사용하는 주요 목적은 병렬 프로그래밍을 통해 여러 작업을 동시에 실행시켜 프로그램의 효율성을 높이는 것이다. 예전에는 멀티스레드 프로그래밍을 하기 위해서든 윈도우의 경우 Win32 API를 사용하거나, 리눅스는 POSIX thread (pthread) API를 사용하었는데 C++ 11부터는 C++ 표준 라이브러리에서 스레드를 지원되기 때문에 헤더를 포함하고 std::thread 클래스를 사용하면 된다. 1. 기본 사용법 std::thread 생성자는 스레드가 시작될 때 실행할 함수를 인자로 받는다. 이 때 함수는 글로벌 함수, 멤버 함수, 람다 표현식 등 다양한 형태일 수 있는데 하나씩 살펴보자. 1.1 글로벌 함수 전달하기 #include #include using names.. 2023. 9. 25.
C++ ] 가변인자 받아서 string 또는 CString으로 출력하기 가변인자 받아서 string으로 출력하기 vsnprintf() 함수는 실제로 버퍼에 출력하지 않고도 필요한 버퍼 크기를 계산할 수 있으므로 char[] 를 사용하지 않고 string 자체의 내부 버퍼를 사용할 수 있다. vsnprintf()실제로 버퍼에 출력하지 않고도 필요한 버퍼 크기를 계산할 수 있으므로 일반적으로char[] 별도의 버퍼가 전혀 필요하지 않고 크기를 계산 std::string하고 해당 크기에 할당한 다음 std::string출력을 위해 의 자체 내부 버퍼를 사용하면 됩니다. , 예: string vString(const char* format, ...) { string line; va_list args; va_start(args, format); int len = vsnprintf(n.. 2023. 9. 24.
C++ ] std::map 자료구조 사용법 map은 키-값 쌍을 저장하고 검색하는 데 사용되는 자료구조 중 하나로 사용법은 아래와 같다. 1. map 헤더파일 포함 #include 2. map 객체 생성하기 생성시 키와 값의 자료형을 지정해야하는데 개인적으로는 문자열 키와 함수포인터 값을 가지는 map을 자주사용한다. map myMap; 3. 원소 추가하기 (insert) 원소를 추가할 때는 키-값 쌍을 pair 객체로 전달하여 추가하는데 insert 함수를 사용하는 방법도 있고 아래의 4번을 사용하는 방법도 있다. 각 키는 고유해야 하며, 각 키에 대응하는 값이 하나씩 존재하게 된다. pair 객체를 만들때는 pair로 생성하거나 make_pair 함수를 사용해도 되고 혹은 {} 중괄호를 사용하는 방법도 있다. 또한 map는 중복을 허용하지 않.. 2023. 9. 23.
C++ ] 레지스트리 등록하여 윈도우 시작시 앱 자동 실행 하기 레지스트리 등록하여 윈도우 시작시 앱 자동 실행 하기 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run HKEY_CURRENT_USER를 사용하면 현재 로그인한 사용자의 시작 프로그램으로 등록하고, HKEY_LOCAL_MACHINE을 사용하면 모든 사용자에게 적용된다. 위 경로에서 "새 문자열 값"을 선택하고 값 이름은 프로그램을 식별할 만한 이름으로 지정하고, 값 데이터에는 실행할 프로그램의 경로를 입력하면 윈도우 부팅 시 등록한 프로그램이 자동실행된다. // 실행파일의 경로 알아내기 wchar_t exePath[MAX_PA.. 2023. 9. 22.
Windows 프로그래밍 시 OutputDebugString 사용해 디버깅 정보 출력하기 + 가변인자 받아서 디버깅 정보 출력 OutputDebugString은 Windows 프로그래밍 환경에서 디버깅 세션에 디버깅 정보를 출력하는데 사용되는 함수이다. 이 함수는 프로젝트 문자집합 설정이 멀티바이트냐 유니코드냐에 따라 OutputDebugStringW 또는 OutputDebugStringA로 정의된다. #ifdef UNICODE #define OutputDebugString OutputDebugStringW #else #define OutputDebugString OutputDebugStringA #endif // !UNICODE 먼저 Windows.h 를 포함하고 OutputDebugString 함수를 호출해 디버깅 세션에 출력할 문자열을 넘긴다. ANSI 문자열을 사용하는 경우 그냥 출력하면 되고 유니코드 문자열을 출력할 땐.. 2023. 9. 21.