[C++ 기초] 함수 심화, 레퍼런스, 포인터, 배열

라멘커비·2023년 12월 14일
0

CPP 입문

목록 보기
3/25
post-thumbnail

함수 심화

LValue, RValue

int main()
{
    int Left = 1;
    int Right = 1;
    Left + Right; //RValue 혹은 리턴값이 존재
    
    Left + Right = 20; // X!!
}

  • 코드에 사용된 메모리는 8바이트가 아니다.
    Left + Right 연산의 결과값도 RValue, 리턴값로서 존재한다. int형의 메모리를 차지하기 때문에 12바이트가 사용되었다.

무언가 존재한다면 위치가 있어야 한다.
공짜는 없고 모든 것은 메모리를 차지한다.

  • 왼쪽에 올 수 있는 값은 LValue Type
    • 명시적 이름(주소, 번지, 포인터)이 있어서 메모리에 접근이 가능해야 한다.
  • 오른쪽에만 올 수 있는 타입은 RValue Type

변수(메모리)

int Left = 1;
위치 : main 실행 스택 안에 들어있다.
	  100번지
크기 : 4
형태 : 정수
값 : 1

함수 리턴값

  • 함수 내부의 리턴의 의미는 값을 리턴(반환)하고 함수를 즉각 종료시킨다는 의미다.
    즉 함수의 실행 스택메모리를 삭제한다.
  • 리턴은 여러번 할 수 없다. (한 번만 가능)
int Test() 
{
    // 함수를 종료하고 만약 리턴할 값이 있다면 외부에 리턴
    return 10; 
    
    // 이곳은 실행되지 않는다. 컴파일러가 지워버림
    int a = 0; 
}

int main()
{
    int Left = 1;

    Left = Test(); // Left와 Test()함수의 리턴값 -> 총 8바이트 존재
}

함수 인자(Parameter)

  • 인자란 외부의 값을 복사받아서 내부에서 사용하는 것을 의미한다.
  • 기본적으로 지역 변수이다.
  • 함수의 인자는 n개 넣을 수 있다.
void ParameterStart(int Value)
{
    Value = 99999;
}

int main()
{
    int TestValue = 10;
    ParameterStart(TestValue);  // TestValue의 값은 99999가 아닌 10이다.
}

코드 실행 과정

  1. main함수 스택이 쌓이고 TestValue의 메모리가 생긴다.

  2. ParameterStart함수가 실행되면서 ParameterStart함수 스택과 내부의 Value 변수 메모리가 생선된다. 이때 Value에는 TestValue를 ParameterStart함수에 인자로 넣었으므로 10이라는 값이 복사된다.

  3. ParameterStart 내부의 코드가 실행되면서 Value변수의 값이 99999로 변한다.

  4. 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

자료형이 달라도 바이트 덩어리이기 때문에 대입이 가능할 수는 있지만 크기가 다른 경우 값이 변형될 수 있다.

일반적으로 자료형은 정식적인 캐스팅이라는 것을 통해 변경해야 한다.
자료형이 다른데 대입이 문법적으로 허용되는 것을 암시적 형변환이라고 한다.
보통은 암시적 형변환은 정말 필요할 때가 아니면 피해야한다.

  • C스타일 형변환과 C++스타일 형변환이 있다.
    형변환을 명시하고 사용하는 것은 명시적 형변환이라고 한다.

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

nullptr은 0번지이다.
자료형은 nullptr_t형이다.

C 스타일

주소값이 0을 가리키는 것은 사용하지 않겠다는 뜻이다.

// c스타일, nullptr
int* Ptr = 0; 	//옛날에 표현하던 방식
				//"사용하지 않는 포인터는 0으로 값을 넣어놓자"

프로그램 메모리크러쉬 1순위인 nullptr exception(null 레퍼런스 exception)이다.

C++ 스타일

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형 배열 주소 4바이트씩 붙어있음
	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이 100번지라고 할 때, Ptr + 1100번지 + (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

  • 과제 1
    배열 포인터 연산 빼기 해보기?
int main()
{
    int MonsterHps[5] = { 11, 22, 33, 44, 55 };

    int* Ptr = MonsterHps + 3;

    int test = *(Ptr - 1); 	// test는 33

    return 0;
}
  • 과제 2
    함수 인자들의 주소를 보고 규칙성 찾아보기 (reinterpret_cast 사용)
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바이트이기 때문)


profile
일단 시작해보자

0개의 댓글

관련 채용 정보