const는 *(에스크리터) 기준으로 어디에 설정되어있냐에 따라 상수화되는 부분이 다르다.

(const 키워드는 바로 뒤에 있는 것을 상수화 시킨다.)

 

예)

int	a = 10;
int	b = 20;

int*	ptr = &a;
*ptr = 999;
ptr  = &b;

#1 const가 *(에스크리터) 앞에 붙는 경우 역참조를 통한 값 변경이 불가능하다.

하지만, 참조하고 있는 주소 값은 변경이 가능하다.

int a = 10;
int b = 20;

const int*	ptr = &a;
*ptr = 999;
ptr = &b;

#2 const가 *(에스크리터) 뒤에 붙는 경우 역참조를 통한 값 변경은 가능하다.

하지만, 참조하고 있는 주소 값은 변경이 불가능하다.

int a = 10;
int b = 20;

int* const	ptr = &a;
*ptr = 999;
ptr = &b;

포인터는 변수다
-> 포인터를 선언하면 메모리에 할당이 된다.
-> 포인터 변수도 할당된 주소를 가진다.
-> 이 주소를 저장할 수 있는 것은 이중 포인터이다.

 

에스크리터와 역참조 연산자는 1:1의 비율로 작동한다.
포인터 변수에 사용한 역참조는 포인터 변수 선언 시 추가한 에스크리터를 하나 제거한다.

만약, int* ptr2 = &ptr;이 가능하다면
ptr2에 역참조 연산자를 사용하면 int형의 데이터가 나와야 하는데, 나오는 결과는 int* 형의 데이터가 나온다.

따라서 int* ptr2 = &ptr; 은 불가능 하다고 할 수 있다.

 

이중 포인터
int** ptr2 = &ptr; 

	int a = 10;
	int* ptr = &a;
	int** ptr2 = &ptr;	

	cout << "&a: " << &a << endl;
	cout << "*ptr: " << *ptr << endl;
	cout << "&ptr: " << &ptr << endl;
	cout << "ptr2: " << ptr2 << endl;
	cout << "*ptr2: " << *ptr2 << endl;
	cout << "**ptr2: " << **ptr2 << endl;

int** 
-> ptr의 주소 값

 

int* 
-> a의 주소 값

 

int
-> a의 정수 값

'Programming > C++ Basic' 카테고리의 다른 글

C++ 2차원 배열  (0) 2020.08.28
C++ 배열과 포인터  (0) 2020.08.28
C++ 포인터  (0) 2020.08.26
C++ 재귀 함수  (0) 2020.08.25
C++ 함수 오버로딩, 디폴트 매개 변수  (0) 2020.08.25

포인터

-> 저장하는 값이 주소인 변수이다.

-> 다른 말로 "간접 참조"라고도 한다.

(프로그래밍에서 참조라는 말은 접근을 듯한다.)

-> 포인터는 변수에 직접적으로 접근하지 않고, 주소를 통해 접근을 한다.

 

주소 확인

&(어드레스 연산자)

-> 주소를 확인하고 하는 변수명 앞에 &를 붙여주면 주소를 확인할 수 있다.

int a = 10;

cout << "a: " << a << endl;
cout << "&a: " << &a << endl;

포인터 선언

*(에스크리터)

-> 포인터 선언은 자료형과 변수명 사이에 *를 추가한다.

 

포인터 변수의 초기화

int a = 10;
int* ptr = &a;

다음과 같이 포인터를 선언하게 될 경우 쓰레기 값이 초기화가 되버린다.

int* ptr;

쓰레기 값 초기화란
-> ptr이라는 변수에 쓰레기 값으로 초기화가 진행된다.
-> 초기화된 값을 주소로 읽으면 어디를 가리킬지 모르는 상황이 발생한다.
-> 만약, 해당 주소가 운영체제를 가동하는데 중요한 위치를 가리킨다면?
-> 그리고 그 값을 사용자가 변경한다면, 운영체제에 큰 문제가 발생할 가능성이 생긴다.
-> 단, 요즘의 운영체제는 할당받지 않은 메모리 접근의 시도가 있을 경우
-> 이를 감지하면 해당 프로그램을 강제로 종료시켜버린다고 한다.

 

포인터 변수를 초기화 할 때 값을 0으로 넣게되면 결과는 000000000 이런식으로 나오게 된다.

이것은 0번지 주소를 의미하는 것이 아니고, 아무런 곳도 가리키고 있지 않다는 의미이다.

그런데 만약 포인터 변수 초기화 값이 일반 변수의 초기화 값과 같다면 햇갈리는 경우가 생길 수가 있어,

이를 방지하기 위해 C++에는 nullptr이라는 것이 존재한다.

int*		ptr1 = 0;
int*		ptr2 = nullptr;

역참조 연산자

-> 포인터 변수 선언 시 사용한 *는 에스크리터이다.

-> 포인터 변수 선언 후 사용한 *는 역참조 연산자이다.

-> 변수명 앞에 역참조 연산자를 붙여주면, 해당 변수가 가지고 있는 주소로 찾아가 값을 확인해주는 역할을 수행한다.

 

역참조 연산자를 통해 원본 데이터에 접근하여 읽을 수 있다.

int a = 10;
int* ptr = &a;

cout << a << endl; //    결과 : 10
cout << &a << endl; //   결과 : 주소값

cout << ptr << endl; //  결과 : 주소값
cout << *ptr << endl; // 결과 : 10

포인터의 크기

-> 어떤 자료형이든 4byte의 크기를 가진다.

-> 개발 환경을 살펴보니 32bit 개발환경이다.

-> 32bit 운영체제에서 가용할 수 있는 메모리의 크기는 4GB이다.

-> 포인터는 1byte당 하나의 주소가 할당되니 4GB를 byte단위로 변화하면 약 42억 9천만개의 주소 개수가 나온다.

-> 각 주소를 저장하기 위해서 필요한 크기는 최대 4byte의 공간만 있으면 저장이 가능하다.

-> 4byte 정수 자료형의 저장 범위와 동일한 크기를 가진다.

 

확인 결과(32비트 기준)

	cout << sizeof(char*) << endl;
	cout << sizeof(bool*) << endl;
	cout << sizeof(short*) << endl;
	cout << sizeof(int*) << endl;
	cout << sizeof(long*) << endl;
	cout << sizeof(float*) << endl;
	cout << sizeof(long long*) << endl;
	cout << sizeof(double*) << endl;
	cout << sizeof(long double*) << endl;

int*에 float* 대입이 가능한가?

-> 포인터 변수로 확인할 수 있는 값은 시작 주소밖에 없다.

-> 시작 주소부터 얼마만큼의 크기를 어떤 식으로 읽을지 결정하는 것이 자료형에 정보가 있다.

예를 들어 다음과 같이 대입을 하게되면 빨간 줄이 표시가 된다.

이유는 간단하다.

포인터 간의 자료형의 크기는 같지만 읽는 방식이 다르기 때문이다.

->int -> 정수, float -> 실수

 

그렇다면 크기도 같고 읽는 방식도 같으면 가능할까?

결과는 동일하다.

왜냐하면 자료형은 운영체제에 따라 크기가 바뀔 수가 있는 경우가 생길 수도 있기 때문에

기존에 작성하던 코드에 오류가 발생할 수가 있다.

따라서 타입이 다른 주소 값은 대입 받을 수 없다.

 

사용 예)

포인터를 통해서 _coin 변수에 정수를 입력하여 할당된 주소 값에 저장하고,

다른 함수에서 새로운 일반 변수 하나를 만들어서 _coin의 주소 값에 저장된 정수 값을 불러오는 것도 가능하다.

void Input_Coin(int* _coin)
{
	cout << "소지금: ";
	cin >> *_coin;
}
void Func()
{
     int iCoin = 0
     Input_Coin(&iCoin);
}

 

어떤 이유로든 매개변수로 nullptr가 넘어올 수 있는 경우가 아니라면,

일반적으로 참조자( & )를 포인터로써 사용하도록 한다.

 

함수에서 매개변수를 통해 값을 반환할 때(out 매개변수)는 포인터를 사용하며,

매개변수 이름 앞에 out을 붙인다.

void GetScreenDimension(uint32_t* const outWidth, uint32_t* const  outHeight)
{
}

호출

uint32_t width;
uint32_t height;
GetScreenDimension(&width, &height);

위 항목의 out 매개 변수는 반드시 null이 아니어야 한다. (함수 내부에서 if 문 대신 assert를 사용할 것)

assert 함수는 #include<cassert>를 추가해야한다.

void GetScreenDimension(uint32_t* const outWidth, uint32_t* const  outHeight)
{
    Assert(outWidth);
    Assert(outHeight);
}

 

매개 변수가 클래스 내부에서 저장될 때는 포인터를 사용한다.

void AddMesh(Mesh* const mesh)
{
    mMeshCollection.push_back(mesh);
}

 

매개 변수가 void 포인터여야 하는 경우는 포인터를 사용한다.

void Update(void* const something)
{
}

 

'Programming > C++ Basic' 카테고리의 다른 글

C++ 배열과 포인터  (0) 2020.08.28
C++ const와 포인터  (0) 2020.08.28
C++ 재귀 함수  (0) 2020.08.25
C++ 함수 오버로딩, 디폴트 매개 변수  (0) 2020.08.25
C++ 함수의 선언부, 정의부  (0) 2020.08.25

재귀 함수
-> 함수 내부에서 자신을 다시 호출하는 함수.
-> 재귀 함수는 탈출 조건이 필수이다.

 

사용 예)

void Func(int _n)
{
	if (0 > _n)
		return;

	cout << "재귀 호출" << endl;

	Func(--_n);
}

void main()
{
	Func(3);
}

재귀 함수는 이름 뒤에 Recursive를 붙인다.

void ABCDRecursive();

 

'Programming > C++ Basic' 카테고리의 다른 글

C++ const와 포인터  (0) 2020.08.28
C++ 포인터  (0) 2020.08.26
C++ 함수 오버로딩, 디폴트 매개 변수  (0) 2020.08.25
C++ 함수의 선언부, 정의부  (0) 2020.08.25
C++ 지역 변수, 전역 변수, 정적 변수(static)  (0) 2020.08.24

함수 오버로딩

-> 함수를 재정의하는 문법.

-> 재정의 시 인자의 정보를 다르게 작성한다.

(함수의 이름을 동일하게 만들지만 인자 정보를 다르게 작성하는 방법)

-> 함수를 호출할 때 인자 정보에 따라 호출할 함수를 탐색하여 호출한다.

-> 단, 반환 타입에 따른 오버로딩은 존재하지 않는다.

 

함수의 오버로딩을 사용하지 않을 경우

int Add(int _a, int _b)
{
	return _a + _b;
}

float Add_float(float _a, float _b)
{
	return _a + _b;
}

double Add_double(double _a, double _b)
{
	return _a + _b;
}

void main()
{	
	cout << Add(10, 20) << endl;
	cout << Add_float(3.9f, 3.9f) << endl;
	cout << Add_double(3.9, 3.9) << endl;
}

 

함수의 오버로딩을 사용한 경우

int Add(int _a, int _b)
{
	cout << "int Add" << endl;
	return _a + _b;
}

float Add(float _a, float _b)
{
	cout << "float Add" << endl;
	return _a + _b;
}

double Add(double _a, double _b)
{
	cout << "double Add" << endl;
	return _a + _b;
}

void main()
{	
	cout << Add(10, 20) << endl;
	cout << Add(3.9f, 3.9f) << endl;
	cout << Add(3.9, 3.9) << endl;
}

하지만 이런 방식은 대부분 틀린 방식이라고 한다.

 

올바른 방식의 함수 오버로딩은 다음과 같다.

const File* GetFileIndex(const int index) const;
const File* GetFileName(const char* name) const;

 

const 반환을 위한 함수 오버로딩은 다음과 같다.

File* GetFileIndex(const int index);
const File* GetFileIndex(const int index) const;

const_cast의 직접적으로 사용하지 않는다.

대신 const인 개체를 수정 가능한 형태로 변환해서 반환하는 함수를 만든다.

 

main함수에서 Add의 값을 호출할 때 a, b, c, d의 값에서 a, b의 값만 호출하고 싶을 때,

다음과 같이 여러번 호출하게 되는 경우가 있다.

cout << Add(10, 20) << endl;
cout << Add(10, 20, 30) << endl;
cout << Add(10, 20, 30, 40) << endl;

이를 해결하기 위해 디폴트 매개 변수라는 것을 사용해보자.

 

디폴트 매개 변수

-> 매개변수의 값을 기본적으로 설정해 놓는 것.

int Add(int _a, int _b, int _c = 0, int _d = 0)

-> 함수 호출 시 넘겨주는 인자가 부족할 경우 기본적으로 설정한 값이 매칭된다.

다음과 같이 Add를 호출 할 경우 a + b + c + d = 30이 나오게 된다.

cout << Add(10, 20) << endl;

하지만, c의 자리에 30이란 정수 값을 넣어주게되면 0이 아닌 30이 들어가게되서

a + b + c + d = 60이 나오게 된다.

cout << Add(10, 20, 30) << endl;

-> 즉, 인자가 부족하지 않을 경우 넘겨주는 값으로 매칭된다.

 

디폴트 매개 변수 사용 시 주의사항

-> 우측 끝에서부터 순차적으로 채워나가야 한다.

 

가능

int Add(int _a, int _b, int _c = 0, int _d = 0) 

불가능

int Add(int _a, int _b = 0, int _c, int _d = 0)  

 

-> 선언부에서만 명시하고, 정의부에서는 명시할 수 없다.

 

가능

// 선언부
int Add(int _a, int _b, int _c = 0, int _d = 0) 

불가능

// 정의부
int Add(int _a, int _b, int _c = 0, int _d = 0)
{
	return _a + _b + _c + _d;
}

디폴트 매개 변수 대신 함수 오버로딩을 선호한다.

 

디폴트 매개 변수를 사용하는 경우, nullptr  false, 0 같이 비트 패턴이 0인 값을 사용한다.

'Programming > C++ Basic' 카테고리의 다른 글

C++ 포인터  (0) 2020.08.26
C++ 재귀 함수  (0) 2020.08.25
C++ 함수의 선언부, 정의부  (0) 2020.08.25
C++ 지역 변수, 전역 변수, 정적 변수(static)  (0) 2020.08.24
C++ 함수란?  (0) 2020.08.24

+ Recent posts