1. 비트 연산자
비트 연산은 숫자의 비트를 직접 조작하는 연산으로, 비트 연산은 C, C++과 같은 저수준 프로그래밍 언어에서 알고리즘 최적화와 특정 계산 수행, 메모리 조작과 같은 다양한 목적에 유용하게 쓰인다.
1. 비트 AND('&')
비트 AND 연산 & 은 두 개의 숫자를 비트 단위로 비교하고
두 입력 숫자의 해당 비트가 설정된 경우 각 비트가 설정된 새 숫자를 반환하는 이진 연산이다.
0 & 0 = 0, 0 & 1 = 0
1 & 0 = 0, 1 & 1 = 1 로 두 값이 1인 경우에만 1을 반환한다.
예시 : int result = 5 & 3; 이라고 하면
result는 0000 0101 & 0000 0011 = 0000 0001 이 된다.
2. 비트 OR(' | ')
비트 OR 연산
0 | 0 = 0, 0 | 1 = 1
1 | 0 = 1, 1 | 1 = 1로 두 값중 하나라도 1이라면 1을 반환한다
예시 : int result = 5 | 3; 이라고 하면
result는 0000 0101 | 0000 0011 = 0000 0111 이 된다.
3. 비트 XOR(' ^ ')
비트 XOR(배타적 OR)
0 ^ 0 = 0, 0 ^ 1 = 1
1 ^ 0 = 1, 1 ^ 1 = 0 로 두 값이 서로 다른 경우에 1을 반환한다.
예시 : int result = 5 ^ 3;
result = 0000 0101 ^ 0000 0011 = 0000 0110 이 된다.
4. 비트 NOT(' ~ ')
비트 NOT 연산은 단일 숫자를 사용하는 단항 연산으로, 각 비트가 반전된 숫자를 반환한다.
~0 = 1, ~1 = 0
예시 : int result = ~5;
result = ~ 0000 0101 = 1111 1010 이 된다.
5. 비트 왼쪽 시프트 (' << ')
비트 오른쪽 시프트 연산은 두 개의 숫자를 지정된 시프트량 만큼 왼쪽으로 이동하여 새 숫자를 반환한다.
예시 : int result 5 << 1;
5의 비트열을 1칸씩 왼쪽으로 이동
result = 0000 0101 << 1 = 0000 1010 이 된다.
오른 쪽 시프트 >> 라면 그 반대로
result = 0000 0101 >> 1 = 0000 0010으로
5의 비트열을 1칸씩 오른쪽으로 이동한다.
2. 함수
함수는 특정 작업을 수행하는 명령문 그룹으로, 프로그램에서 별도의 단위로 구성되며, 함수는 코드를 더 작고 관리하기 쉽게하며, 재사용 가능한 블록으로 나누는 데에도 도움을 준다.
C++에서 함수는 주로 두 가지 유형의 함수가 존재한다.
1. 표준 라이브러리 함수 : C++ 표준 라이브러리에서 사용할 수 있는 미리 정의되어져 있는 함수로 sort(정렬),
sqrt(제곱) 등 표준라이브러리의 일부로 헤더파일을 추가하면 사용이 가능하다.
2. 사용자 정의 함수 : 특정 작업을 수행하기 위해 프로그래머가 직접 만든 함수로, 함수를 정의하고 해당 함수를
코드에서 호출하여 사용할 수 있다.
1. 람다 함수
람다 함수는 소스 코드 내에서 간결한 구문으로 정의되는 익명 즉 명명되지 않은 함수이다.
Lambda함수는 C++ 11에서 도입되었으며, 표준 라이브러리 알고리즘과 널리 사용된다.
일반적으로 함수를 한 번만 사용하거나 함수를 인자로 전달 해야하는 경우에 유용하게 사용 할 수 있다.
1.통사론(Syntax)
람다함수의 기본 문구이다.
[capture-list](parameters) -> return_type{ // function body};
capture-list : 람다 함수가 액세스할 수 있는 주변 범위의 변수 목록이다. parameters : 일반 함수와 마찬가지로 입력 매개 변수, 선택 사항 return_type : 람다 함수가 반환할 값의 유형으로, 선택 사항이며 대부분의 경우 이를 추론할 수 있다. function body : 람다 함수의 작업을 정의하는 코드이다.
예제
capture, parameters 또는 return이 없는 경우
parameters 가 있는 경우
값별 capture를 사용하는 경우
참조에 의한 capture를 사용하는 경우
참조로 capture를 사용할 경우 Lambda함수 내에서 capture된 변수에 대한 변경 사항은 주변 범위의 해당 값에 영향을 준다.
3. 데이터 형식
C++에서 데이터 형식은 프로그램이 처리할 수 있는 다양한 유형의 데이터를 분류하는데 사용한다.
변수가 보유할 수 있는 값의 유형과 변수가 차지할 메모리 공간을 결정하는 데 필수적이다.
1. 포인터
포인터는 변수의 메모리 주소를 저장하는 데 사용한다.
int num = 42;
int* pNum = #
2. 참조
참조는 변수 간에 메모리 위치를 공유하는 또 다른 방법으로, 다른 변수에 대한 별칭을 만들 수 있다.
int num = 42;
int& numRef = num;
3. 사용자 정의 데이터 형식
사용자 정의 데이터 형식을 프로그래머가 정의하는 형식이다.
1. Structures(구조체)
구조체는 단일 변수 아래에 서로 다른 데이터 유형을 저장하는 데 사용된다.
2. Class(클래스)
클래스는 구조체와 비슷하지만 멤버 데이터 및 함수의 액세스 가능성은 지정자에 의해 제어된다.
3. Union(공용체)
공용체는 동일한 메모리 위치에 서로 다른 데이터 형식을 저장하는 데 사용한다.
4. 정적 타이핑
C++ 에서 정적 형식 지정은 변수가 프로그램이 실행되기 전에 컴파일 타임에 결정됨을 의미한다.
즉, 변수는 특정 형식의 데이터에만 사용할 수 있으며 컴파일러는 변수로 수행된 작업이 해당 형식과 호환되도록 한다.
불일치가 있는 경우 컴파일러는 가능한 경우 변수의 데이터 유형을 다른 변수와 일치하도록 조정한다.
컴파일러가 유형을 변환할 수 없는 경우 코드를 컴파일하는 동안 오류가 발생한다
"Type Conversiong Invalid Type Conversion"
C++은 정적 형식 지정 언어이므로 정적 형식 지정을 사용하여 데이터 형식을 결정하고 컴파일 시간 동안
형식 검사를 수행한다. 이렇게 하면 형식 안전성을 보장하는 데 도움이 되며
프로그램 실행 중에 특정 유형의 오류가 발생하는 것을 방지할 수 있다.
예시
int num = 65;
double pi = 3.14
char c = 'c'
c = num 이라고 하였을떄 c는 아스키코드의 A로 저장되고
num = pi 라고 하면 num에는 3이 저장된다.
5. 동적 타이핑
C++은 정적 형식 언어로 알려져 있으며, 이는 변수의 데이터 형식이 컴파일 시간에 결정됨을 의미한다.
하지만 C++은 런타임에 변수의 데이터 유형을 결정하는 것을 의미하는 특정 수준의 동적 타이핑을 갖는 개념도 제공해준다.
C++에서 동적 타이핑을 달성하는 두 가지 방법
1. void* 포인터( generic 포인터)
모든 데이터 자료형을 가리킬 수 있는 포인터이다.
void*는 다른 변수를 가리킬 수 있지만, 다른 형식으로 캐스팅 된 경우에만 역참조가 되며
const 또는 volatile 키워드로 사용하여 선언 되지 않은 모든 변수를 가리길 수 있다.
void*는 모든 자료형을 가리키고 있다.
단 const, volatile 형식의 변수는 void*로 가리킬 수 없다.
하지만 출력을 할 경우 자료형의 객체를 알지 못하기에 역참조나 연산이 불가능하다.
역참조를 하기 위해서는 void*를 다른 포인터의 유형으로 명시적 형 변환을 해야한다.
void*을 사용시에는 새 값을 할당하기 전에 내부에 값이 있는지 확인해야하며, 이전에 할당한 값을 없에줘야 한다.
2. any
C++ 17에서 void*를 대체할 수 있는 type-safe 컨테이너인 any가 도입되었다.
사용시 <any> 헤더 파일을 추가하여 사용한다.
다양한 타입의 값을 저장할 수 있으며, 또는 빈 상태일 수도 있고, 저장된 객체의 타입을 기억한다.
컴파일 시 객체의 정확한 타입을 알 필요가 없다는게 가장 큰 이점이다.
any의 주요 목적은 타입 캐스팅 없이 다른 데이터 타입의 값을 하나의 컨테이너에 저장할 방법을 제공한다
객체에 저장된 값의 타입은 any_cast를 사용하여 얻으며, 저장된 값의 타입이 요청한 타입과 일치하지 않으면
bad-any_cast가 발생한다.
저장된 객체의 lifetime을 관리하며, any 객체가 유효 범위를 벗어날 때 저장된 객체가 차지하는 메모리를 해제한다.
또한 객체에 대한 복사 및 이동 시맨틱을 지원하여 다양한 유형의 값을 저장하고 검색하기 쉽게 해준다.
any에 저장되는 값이 기본 int 같은 베이직한 값이면 스택에 저장되고, 만약 조금 더 복잡한 객체라면 동적으로 힙에 저장된다.
3.RTTI (Run-Time Type Identification : 런타임 형식 식별)
RTTI는 기본 유형에 대한 사용 가능한 포인터 또는 참조에서 객체 유형을 동적으로 찾는 메커니즘이다.
이는 가상 함수 메커니즘에 의한 유형 식별에 의존하지 않으려는 상황에서 유용하다.
대부분의 클래스 라이브러리에는 런타임 유형 정보를 생성하는 가상 함수가 포함되어 있다.
typeid 연산자
객체의 유형에 대한 정보를 포함하는 유형의 객체에 대한 참조를 반환하는 연산자이다.
<typeinfo> 헤더 추가 필요
typeid 예시
dynamic-cast 연산자
런타임 형식 검사를 수행하고 기본 포인터 또는 참조를 파생 포인터 또는 참조로 안전하게 다운캐스트하는 형식 캐스팅 연산자이다. 캐스팅이 실패할 때 null을 반환하거나 bad_cast 예외를 throw해준다.
dynamic-cast 예시
RTTI를 사용하면 런타임 중에 컴파일러에서 생성된 추가 정보를 저장하고 처리해야 하기에 성능 오버헤드가 발생할 수 있다.
1. 런타임 캐스트
포인터나 참조를 사용해 객체의 런타임 유형을 결정하는 가장 간단한 방법은 캐스트가 유효한지 확인하는
런타임 캐스트를 사용하는 것이다.
객체의 캐스팅은 주로 클래스의 상속 계층을 다룰 때 필요한데, 여기서 객체 참조나 포인터를 다운 캐스팅할 수 있다.
2. 업캐스팅
업캐스팅은 파생 클래스 객체의 포인터나 참조를 기본 클래스 포인터로 처리하는 프로세스이다.
다운 캐스팅과 달리 포인터의 업캐스팅은 파생 클래스 포인터나 참조를 기본 클래스 포인터에 할당하여 자동으로 수행된다.
Cost c;
Cost* pCost = &c;
Name* pName = pCost;
기본 클래스(Name)의 포인터가 파생 클래스(Cost) 객체를 가리키고 있다.
이렇듯 업캐스팅을하면 파생 클래스의 객체를 기본 클래스의 객체처럼 다룰 수 있다,
3. 다운 캐스팅
다운 캐스팅은 기본 클래스 포인터나 참조를 파생 클래스 포인터로 변환하는 것이다.
Cost c;
Cost* pCost;
Name* pName = &c; // 업캐스팅
pName->setName("Down");
pName->showName();
pCost = (Cost*)pName; // 다운 캐스팅
pCost->setCost(100);
pCost->showCost();
다운 캐스팅을 할 경우에는 업캐스팅과는 다르게 강제형변환을 시켜줘야 한다.
다운 캐스팅 경우에는 주의 해야할 점이 있는데
Cost c;
Cost* pCost;
Name n;
Name* pName = &c; // 업캐스팅
pName->setName("Down");
pName->showName();
pCost = (Cost*)pName; // 다운 캐스팅
pCost->setCost(100);
pCost->showCost();
기본 클래스와 파생 클래스의 객체를 각각 생성해 준 뒤 바로 다움 캐스팅을 해주게 될 경우이다.
각각의 객체를 생성한 경우 pCost는 pName 객체를 가리키게 된다.
따라서 pCost에는 Name클래스에서 상속받은 멤버만 있는 상태가 되며
pCost에서 showCost()를 호출해도 없는 함수를 실행한것으로 되기에 런타임 에러가 발생한다.
참고 자료
'C++ Practice' 카테고리의 다른 글
Study C++ Developer RoadMap (3) (0) | 2025.02.18 |
---|---|
Study C++ Developer RoadMap (2) (0) | 2025.02.17 |
Set Library C++ (3) | 2024.11.12 |
String Library (1) | 2024.11.04 |
Bitset Library (0) | 2024.11.02 |