int main()
{
int Left = 1;
int Right = 1;
Left + Right; //RValue 혹은 리턴값이 존재
Left + Right = 20; // X!!
}
무언가 존재한다면 위치가 있어야 한다.
공짜는 없고 모든 것은 메모리를 차지한다.
int Left = 1;
위치 : main 실행 스택 안에 들어있다.
100번지
크기 : 4
형태 : 정수
값 : 1
int Test()
{
// 함수를 종료하고 만약 리턴할 값이 있다면 외부에 리턴
return 10;
// 이곳은 실행되지 않는다. 컴파일러가 지워버림
int a = 0;
}
int main()
{
int Left = 1;
Left = Test(); // Left와 Test()함수의 리턴값 -> 총 8바이트 존재
}
void ParameterStart(int Value)
{
Value = 99999;
}
int main()
{
int TestValue = 10;
ParameterStart(TestValue); // TestValue의 값은 99999가 아닌 10이다.
}
main함수 스택이 쌓이고 TestValue의 메모리가 생긴다.
ParameterStart함수가 실행되면서 ParameterStart함수 스택과 내부의 Value 변수 메모리가 생선된다. 이때 Value에는 TestValue를 ParameterStart함수에 인자로 넣었으므로 10이라는 값이 복사된다.
ParameterStart 내부의 코드가 실행되면서 Value변수의 값이 99999로 변한다.
ParameterStart함수가 종료하여 스택에서 사라진다. TestValue값은 그대로 10.
우리반 코딩 스탠다드
- 전역변수와 지역변수 이름 무조건 다르게 하기. ex) GlobalA, LocalA
- 무조건 초기값을 설정한다.
- 함수의 인자에는 앞에 언더바
_
를 붙인다.- 포인터를 초기화할 때 0을 사용하지 않는다.
- 경로 및 함수 변수 한글 절대 금지
- 바탕화면에 프로젝트 절대 금지 있으면 평가에 반영하겠음
누군가의 체력을 깎는 행동을 하는 함수를 만들었다.
반환값을 다시 MonsetHp로 대입하여 체력을 깎았지만 함수의 "행동"이 아니다.
int Damage(int _Hp, int _Att)
{
_Hp = _Hp + _Att;
return _Hp;
}
int main()
{
int MonsterHp = 100;
MonsterHp = Damage(MonsterHp, 10);
}
체력을 깎는 "행동"을 하는 함수를 만들기 위해서는 포인터가 필요하다.
변수의 주소값을 저장하기 위한 자료형이 포인터이다.
포인터는 포인터를 위한 연산자와 문법이 별개로 존재한다.
포인터 문법은 자료형 뒤에 *
을 붙이면 뭐든지 그 자료형의 포인터형이 된다.
int*
통째로 자료형.
주소값은 무조건 정수이다. (0.5바이트같은거 X)
주소는 어떤 변수나 메모리의 램에서의 정수 위치이다.
포인터는 주소값을 저장하는데 주소값이 int형(4바이트)으로 표현할 수 없기 때문에 더 크다. 포인터의 크기는 8바이트이다.
주소값은 일반적으로 16진수 표기법으로 표현된다.
__int64
는 8바이트짜리 정수형
포인터는 위험한 점이 많다. 그래서 일반적인 정수와는 다른 문법들이 많다.
int*는 번지를 넣어줘야 하는데 정수를 넣어주는 것은 C++ 문법적으로 막혀있다.
&
를 붙여야 함
int Value = 0;
int* ValuePtr = &Value;
int Value = 0;
int* ValuePtr = &Value;
int** ValuePtrPtr = &ValuePtr;
int main()
{
//위치 100번지
//크기 4바이트
//형태 int
//값 200
int MonsterHp = 200;
//위치 120번지
//크기 8바이트
//형태 int*
//값 100번지
int* MonsterHpPtr = &MonsterHp;
//가리키는 곳의 값을 바꾼다.
*MonsterHpPtr = 50;
return 0;
}
void Damage(int* _Hp, int _Att)
{
*_Hp = *_Hp - _Att;
}
int main()
{
int MonsterHp = 200;
Damage(&MonsterHp, 20); //MosterHp가 180이 됨
return 0;
}
포인터는 여러개 덧붙여(?) 쓸 수 있다.
자료형이 다르면 대입이 되면 안 된다. 그런데 될 때가 있고 안 될 때가 있다.
ex) bool Check = 0; // O
자료형이 달라도 바이트 덩어리이기 때문에 대입이 가능할 수는 있지만 크기가 다른 경우 값이 변형될 수 있다.
일반적으로 자료형은 정식적인 캐스팅이라는 것을 통해 변경해야 한다.
자료형이 다른데 대입이 문법적으로 허용되는 것을 암시적 형변환이라고 한다.
보통은 암시적 형변환은 정말 필요할 때가 아니면 피해야한다.
static_cast<타입>(바꿀변수)
reinterpret_cast<>()
//C스타일 형변환
{
int Value = 100;
bool Check = (bool)Value; // true
Value = (int)Check; // 값 변함
}
//C++스타일 형변환
//C++에는 참조형과 값형이 있다. 참조형 - 포인터와 레퍼런스, 값형 - 일반적인 변수
//값형 <=> 값형
{
int Value = 100;
bool Check = static_cast<bool>(Value); // true
Value = static_cast<int>(Check); // 다른 숫자 됨
}
//값형 <=> 참조형
{
int Value = 100;
int* Ptr = &Value;
__int64 Address = reinterpret_cast<__int64>(Ptr);
}
nullptr은 0번지이다.
자료형은 nullptr_t형이다.
주소값이 0을 가리키는 것은 사용하지 않겠다는 뜻이다.
// c스타일, nullptr
int* Ptr = 0; //옛날에 표현하던 방식
//"사용하지 않는 포인터는 0으로 값을 넣어놓자"
프로그램 메모리크러쉬 1순위인 nullptr exception(null 레퍼런스 exception)이다.
C++ 11부터는 nullptr이라는 상수가 아예 생겼다.
int* ptr = nullptr;
레퍼런스는 쉽게 설명해서 상시 *이 붙은 상태로 사용하는 포인터이다.
int MonsterHp = 100;
int& Ref = MonsterHp;
Ref = 300; // 변경 O
Ref = 100; // 변경 O
레퍼런스를 사용할 때는 무조건 값이 들어올 때 사용한다.
몬스터 체력이 무조건 있는 상황이라면 데미지 함수를 레퍼런스로 사용
void DamageRef(int& _MonsterHp, int _Att)
{
_MonsterHp = _MonsterHp - _Att;
}
포인터는 중간에 변경될 수 있다. 중간에 자신이 가리키는 대상을 바꿀 수 있다.
int Value0 = 10;
int Value1 = 20;
int* Ptr = &Value0;
*Ptr = 1000; //Value0의 값이 1000으로 바뀜
Ptr = &Value1;
*Ptr = 2000; //Value1의 값이 2000으로 바뀜
Ptr = nullptr; //Ptr이 널포인트가 됨
레퍼런스는 한 번만 초기화되면 가리키는 대상을 변경할 수 없다.
int Value0 = 10;
int Value1 = 20;
int& Ref = Value0; //Ref에 10
Ref = 1500; //Value0의 값이 1500으로 바뀜
Ref = Value1; //Ref와 Value0이 20으로 바뀜
Ref = 2000; //Ref와 Value0이 2000으로 바뀜
int MonsterHps[5] = { 100, 100, 100, 100, 100 };
//시작이 0임, 제로베이스
int MonsterHp1 = MonsterHps[0];
int MonsterHp2 = MonsterHps[1];
int MonsterHp3 = MonsterHps[2];
int MonsterHp4 = MonsterHps[3];
int MonsterHp5 = MonsterHps[4];
//MonsterHps[0] == int&
int& Ref = MonsterHps[0]; // Ref == MonsterHps[0]
int MonsterHps[5] = { 100, 100, 100, 100, 100 };
int* Ptr = MonsterHps; //int*와 int[]인데 가능하다.
//배열은 포인터 문법도 사용할 수 있다.
//포인터의 핵심 연산 중 하나인 주소이동
int MonsterHp1 = Ptr[0];
int MonsterHp2 = Ptr[1];
int MonsterHp3 = Ptr[2];
int MonsterHp4 = Ptr[3];
int MonsterHp5 = Ptr[4];
Ptr + 1
은 100번지 + (sizeof(int) * 1)
라는 의미이다.int MonsterHps[5] = { 11, 22, 33, 44, 55 };
//Ptr == 100번지 일 때
//Ptr + 1 == 100번지 + (sizeof(int) * 1) 번지
//Ptr + 2 == 100번지 + (sizeof(int) * 2) 번지
//Ptr + 3 == 100번지 + (sizeof(int) * 3) 번지
//Ptr + 4 == 100번지 + (sizeof(int) * 3) 번지
//Ptr + 5 == 100번지 + (sizeof(int) * 3) 번지
__int64 Address0 = reinterpret_cast<__int64>(Ptr + 0); //11
__int64 Address1 = reinterpret_cast<__int64>(Ptr + 1); //22
__int64 Address2 = reinterpret_cast<__int64>(Ptr + 2); //33
__int64 Address3 = reinterpret_cast<__int64>(Ptr + 3); //44
__int64 Address4 = reinterpret_cast<__int64>(Ptr + 4); //55
int MonsterHps[5] = { 11, 22, 33, 44, 55 };
int* Ptr = MonsterHps;
int ArrSize = sizeof(MonsterHps); // 20
int PtrSize = sizeof(Ptr); //8
int main()
{
int MonsterHps[5] = { 11, 22, 33, 44, 55 };
int* Ptr = MonsterHps + 3;
int test = *(Ptr - 1); // test는 33
return 0;
}
void Test(int _Value0, int _Value1, int _Value2, int _Value3)
{
__int64 Address0 = reinterpret_cast<__int64>(&_Value0);
__int64 Address1 = reinterpret_cast<__int64>(&_Value1);
__int64 Address2 = reinterpret_cast<__int64>(&_Value2);
__int64 Address3 = reinterpret_cast<__int64>(&_Value3);
}
int main()
{
Test(0, 1, 2, 3);
return 0;
}
->
배열처럼 주소가 붙어있음
주소가 8 바이트씩 차이남 (가장 큰 기본 자료형이 8바이트이기 때문)