2차원 배열

-> 배열의 원소로 배열을 가지는 것.

-> 2차원 배열은 뒤에서부터 읽으면 편하게 읽을 수 있다.

 

 

2차원 배열의 형태

자료형 변수명[행][열]

int iArr[2][3];

 

2차원 배열의 초기화

1차원 배열의 초기화와 같다.

int iArr[2][3] = 
{
  { 1, 2, 3 },
  { 4, 5, 6 }
};

 

2차원 배열의 모든 원소 접근

for (int i = 0; i < 2; ++i)
{
	for (int j = 0; j < 3; ++j)
		cout << iArr[i][j] << '\t';
	cout << endl;
}

 

2차원 배열의 시작 주소

int iArr[2][3] = { 1, 2, 3, 4, 5, 6 };

cout << iArr << endl;

 

2차원 배열의 첫 번째 원소(배열)의 값

-> 배열의 값은 == 배열의 시작 주소

-> 첫 번째 배열의 시작 주소

cout << iArr[0] << endl;

 

2차원 배열의 두 번째 원소의 값

-> 두 번째 배열의 시작 주소

cout << iArr[1] << endl;

 

매개 변수의 int* _iArr 와 int _iArr[]는 같은 의미이다.

void Func(int _iArr[]) == void Func(int* _iArr)

같은 의미를 갖는 이유는 해당 매개 변수가 포인터인지 배열인지 구분을 하기 위해서이다.

 

배열을 함수 인자로 전달하는 방법

int iArr1[5] = { 1, 2, 3, 4, 5 };
int iArr2[10] = { 1, 2, 3, 4, 5 };

Func(iArr1, 5);
Func(iArr2, 10);

 

1차원 배열을 2차원 배열처럼 출력하기

int iArr[25] = {};

for (int i = 0; i < 25; ++i)
{
	iArr[i] = i + 1;
}

for (int i = 0; i < 5 ; ++i)
{
	for (int j = 0; j < 5 ; ++j)
		cout << iArr[j + (i * 5)] << '\t';
	cout << endl;
}

2차원 배열 포인터 형

int iArr[2][3] = { 1, 2, 3, 4, 5, 6 };

cout << iArr << endl; // 2차원 배열의 시작 주소 
cout << iArr[0] << endl; // 2차원 배열의 첫 번째 원소 -> 1차원 배열 -> 주소 
cout << *iArr << endl; 
cout << **iArr << endl;

역참조 연산자를 2번 사용하니, 포인터 변수에 에스크리터 2개를 추가하여 대입이 가능한가?

2차원 배열은 2중 포인터가 아니기때문에 불가능하다.

2중 포인터가 아닌 이유는 포인터 연산 증가 값의 차이점을 통해 알아보자.

 

ptr이 100번지라고 가정할 경우

ptr + 1은 몇 번지를 가리킬까?

ptr은 주소를 저장한다.

 

double*의 주소를 저장한다.

double*는 4byte의 크기를 가진다.

(증가는 4byte 만큼만 증가 한다.)

double** ptr = nullptr;

ptr + 1

2차원 배열의 증가 값은

-> 자료형 * 열의 개수 만큼 증가한다.

int iArr[2][3] = { 1, 2, 3, 4, 5, 6 };

	cout << iArr << endl;
	cout << iArr + 1 << endl;

이중 포인터와 2차원 배열은 증가 값이 다르다

-> 이중 포인터에 2차원 배열의 주소를 저장 할 수 없다.

 

2차원 배열의 포인터형

-> int형 주소를 저장할 공간 3개를 만들고, 포인터 변수에 해당 공간을 대입해보자.

빨간줄이 뜬 이유?

-> 2차원 배열의 포인터 형은 기존의 배열이랑 똑같이 선언을 하게되면 오류가 발생한다.

2차원 배열의 포인터 형은 다음과 같다.

기존 방식에서 *ptr 부분을 가로로 감싸주면 끝이다.

 

3차원 배열

-> 3차원 배열은 대부분 사용을 하지 않는다고 한다.

-> 그러므로 사용 방법만 간단하게 적어보자.

int	iArr[2][3][4] = 
{
  {
	{ 11, 12, 13, 14 },
	{ 15, 16, 17, 18 },
	{ 19, 20, 21, 22 }
  },
  {
	{ 23, 24, 25, 26 },
	{ 27, 28, 29, 30 },
	{ 31, 32, 33, 34 }
  }
};

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

C++ 문자열 함수  (0) 2020.08.31
C++ 문자 배열과 문자열 상수  (0) 2020.08.28
C++ 배열과 포인터  (0) 2020.08.28
C++ const와 포인터  (0) 2020.08.28
C++ 포인터  (0) 2020.08.26

배열

-> 동일한 특성을 가지며, 일정한 규칙에 따라 나열되어 있는 데이터 집합

동일한 특성 : 자료형

일정한 규칙 : 연속된 메모리

 

배열의 형태

자료형 변수명 [ 배열의 수]

int iArr[5];

int형을 사용하는 변수 5개를 만든다는 의미.

 

배열의 길이

int iArr[] = {1,2,3,4,5};
cout << sizeof(iArr) / sizeof(int) << endl;

 

배열의 초기화

-> 다음과 같이 선언을 하게되면 5개의 변수가 모두 쓰레기 값으로 초기화가 진행된다.

int iArr[5];

배열의 초기화 방법

int iArr[5] = {}

{} 안에 명시한 값들이 순차적으로 채워진다.

int iArr[5] = { 1, 2, 3, 4, 5 };

1 > 2 > 3 > 4 > 5

 

만약 다음과 같이 선언 했을 경우 {} 안에 명시한 값들이 순차적으로 채워지고 뒤에 나머지 값들은 0으로 채워진다.

int iArr[5] = { 1, 2, 3 };

배열의 원소 접근

-> 변수명 [ 인덱스 ]

-> 배열 선언 시 사용한 []는 배열의 크기를 설정하는 것 (1부터시작)

-> 배열 선헌 후 사용한 []는 원소에 접근하는 것. (0부터시작)

int iArr[5] = { 1, 2, 3, 4, 5 };

for (int i = 0; i < 5; ++i)
		cout << iArr[i] << endl;

배열을 사용하다보면 {1, 2, 3, 4, 5} 처럼 원소를 일일이 지정하기 귀찮을 때가 있다.

이럴 때는 for문을 활용하면 좀 더 간편하게 원소를 지정할 수가 있다.

int iArr[5] = {};

for(int i = 0; i < 5; ++i)
{
   iArr[i] = i + 1;
}

 

배열의 크기는 상수로만 설정이 된다.

-> 변수로는 설정이 불가능하다.

이는 정적 배열을 뜻한다.

 

다음과 같이 입력을 통해 배열의 크기를 직접 설정하는 것은 불가능하다.

int iSize = 0;
 cin >> iSize;

 int iArr[iSize]; // 불가능

 

배열의 변수명만 출력할 경우 주소가 나온다.

-> 이 주소는 첫 번째 원소의 주소와 같다.

배열의 이름은 시작 주소를 저장하고 있다. 즉, 시작 주소를 저장하는 포인터라고 할 수 있다.

int iArr[5] = { 1, 2, 3, 4, 5 };
	cout << iArr << endl;
	cout << "==============================" << endl;
	for (int i = 0; i < 5; ++i)
		cout << &iArr[i] << endl;

포인터 연산

-> 주소를 대상으로 + 연산을 수행한다.

-> 단, 포인터 연산 시 연속 된 메모리를 사용해야만 한다.

-> 포인터 연산 시 증가 값은 자료형의 크기만큼 증가한다.

int	iArr[5] = { 1, 2, 3, 4, 5 };
int*	ptr = iArr;

인덱스 연산자는 *( + n)와 같은 말이다.

예)

*(ptr + 0) == iArr[0] 
*(ptr + 1) == iArr[1]
*(ptr + 2) == iArr[2]
*(ptr + 3) == iArr[3]
*(ptr + 4) == iArr[4]

 

인덱스 연산자는 주소 값을 대상으로 포인터 연산을 수행 후 역참조를 사용한다.

cout << ptr[3] << endl;

-> 만약, 해당 연산이 성립할 경우 iArr가 할당된 공간을 잃어버리게 된다.

-> 배열의 시작 주소를 잃어버리면 배열에 접근을 할 수가 없게 된다.

-> 이를 안전하게 사용하게 하기 위해 배열의 이름은 상수 포인터로 설정되어 있다.

 

배열로 매개변수 선언

void Func(int* _ptest)   // 포인터
void Func(int  _ptest[]) // 배열

-> 포인터 형식으로 선언을 해도 배열을 참조할 수가 있다.

-> 작업을 하면서 해당 매개변수가 포인터인지 배열인지 구분을 하기위해 나눠져 있다.

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

C++ 문자 배열과 문자열 상수  (0) 2020.08.28
C++ 2차원 배열  (0) 2020.08.28
C++ const와 포인터  (0) 2020.08.28
C++ 포인터  (0) 2020.08.26
C++ 재귀 함수  (0) 2020.08.25

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

+ Recent posts