0-value, nullptr
: 포인터는 nullptr 이라는 값으로 0-value가 지정가능하다
0-value 란 각각의 변수가 가질 수 있는 무(zero) 값, 기본값, 최초값 등을 의미한다
0-value 에는 널문자, 0, false, nullptr 등이 있다
포인터도 역시 0-value를 가질 수 있으며 포인터에서의 0-value는 nullptr 이다
포인터에는 어떤 주소값이 들어가는데 이 주소값이 처음에는 초기화되지 않은 주소값을 가리키게 됨
이를 우리는 쓰레기 주소값이라고 함
포인터 변수를 만들게되면 이 변수의 값이 무슨 값이 들어있는지 프로그래머는 예측을 할 수가 없다
그래서 변수를 만들고 나서 0 value로 초기화를 해 줘야함
#include <cstdio> #include <cstdlib> int main() { int* pointer = nullptr; // 보통 변수 = 0; 으로 초기화를 해주는데 포인터에 대해서는 C++에서는 nullptr 을 활용해서 확인 해 주면 된다 // 변수에도 0-value가 있다, 보통 변수를 선언하고 0-value로 초기화해준다 pointer = (int*)malloc(sizeof(int)); // 한 개의 변수 크기만을 heap 영역에다가 초기화를 하게 되면 이 malloc에 의해서 int 포인터변수의 주소값이 젤 앞 pointer으로 전달이 되기 때문에 이제부터 이 포인터의 주소값은 nullptr이 아니게 됨 따라서 포인터는 실제 주소를 가리키고 있다고 출력됨 *pointer = 10; // 실제 주소의 값에 넣기 if (pointer == 0){ printf("제로 value를 갖고 있습니다"); } else { printf("포인터는 실제 주소를 가리키고 있습니다\n "); printf("%d", *pointer); } free(pointer); // 포인터의 값을 없애고 나면 포인터 안에 있는 주소값은 실제 값이 사라졌기 때문에 heap 영역에서 아예 사라져 실제값이 아닌 이상한 메모리 값이 출력됨 pointer = nullptr; // free를 하고 난 다음에는 이 포인터에 가리키고 있는 주소값이 잘 못 되지 않도록 nullptr로 만들어주는 절차가 중요 return 0; } |
0-value, 배열 초기화
배열의 모든 값을 0-value로 초기화를 하기 위해서는 다음의 표현이 가능하다
Type v[10] = { 0, };
혹은 포인터 타입의 배열일 경우
Type*v[10] = { nullptr, };
#include <cstdio> #include <cstdlib> struct Marine { int hp = 40; int atk = 6; }; int main() { Marine* marine[40] = {nullptr,}; // 구조체 포인터의 배열, Marine이라는 포인터를 40개 갖고 싶다, 즉, Marine이라는 유닛을 갖고 있는 이 구조체의 포인터를 40개를 갖고싶다 /* printf("%d", marine[0]); marine[0]은 주소값이 들어가 있고 Marine[0]의 주소값이 Marine 이라는 어떤 그런 heap 영역이나 stack 영역에 어떤 다른 실체를 갖고있다, 그래서 nullptr,로 초기화를 해주었다 */ for (int i = 0; i < 40; i++){ marine[i] = (Marine*)malloc(sizeof(Marine)); // 포인터의 배열임, marine이 40개가 있는 것 marine[i]->hp = 40; marine[i]->atk = 7; } for (int i = 0; i < 40; i++){ printf("marine[%d].atk = %d\n", i, marine[i] ->atk); // marine i번째 index에 있는 이 포인터타입에 실제값을 따라가서 atk를 출력을 해라 } return 0; } |
포인터의 포인터
: 포인터의 배열
이중포인터에서
- 기본형 데이터 타입일 경우 : 2차원 배열일 가능성이 높음
- 구조체 타입일 경우 : 포인터의 배열일 가능성이 높음
- int** v; // int의 2차원 동적배열일 가능성이 높음
- Type** v; // Type*의 1차원 동적배열일 가능성이 높음, 포인터의 배열인가 생각
Marine** marine; 은 동적할당된 일차원 배열 (즉, 포인터의 배열인가) 생각하는게 편한게
분명 Marine* marine[20]; 이런식으로 배열을 포인터의 배열을 쓰기 위해서 만들었는데
이 포인터의 배열을 동적으로 20으로 고정이 아니라
뭐 30, 15 이런 식으로 프로그램이 실행 될때마다 동적으로 만들고 싶기 때문에
Marine** marine; 으로 표현했을 것이다라고 생각하면 된다
기본형 데이터 타입이 int** 이런식으로 이렇게 두 개의 포인터를 활용해서 만들었다면
2차원 배열이라고 생각하면 된다 int** m; 이런식으로
int*** 이면 포인터의 3차원 배열, Marine*** marine; 이면 포인터의 2차원 배열이라고 생각
#include <cstdio> #include <cstdlib> struct Marine { int hp = 40; int atk = 6; }; int main() { Marine** marine; marine = (Marine**)malloc(sizeof(Marine*) * 20); // 모든 포인터는 4byte, 4byte의 size에 곱하기 20개, 즉 Marine* marine[20]; 이렇게 되는것, Marine** marine; 을 쓸 거를 굳이 더블포인터를 써서 이중포인터를 만든 거, 의미는 거의 같다고 보면 됨 for (int i = 0; i < 20; i++){ marine[i] = (Marine*)malloc(sizeof(Marine) * 4); // 동적할당된 클래스를 1차원 배열을 만들 거니까 for (int k = 0; k < 4; k++){ marine[i][k].atk = 9; } } for (int i = 0; i < 20; i++){ for (int k = 0; k < 4; k++){ printf("marine[%d][%d]->atk = %d\n", i, k, marine[i][k].atk); } } return 0; } /* 결국 malloc 이라는 것은 어떤 그런 실제값을 heap 영역에다가 만들어 준다는 것이고 이 크기를 얼마나 만드느냐 이거는 결국 곱하기에 의해서 만들어짐, 이 곱하기가 빠지게 되면 한 개만 만드는 것 한 개만 만들면 주소값을 따라가서 -> 표시로 그 주소값에 있는 값을 가져올 수 있지만 우리가 또 이차원배열을 만들어서 실제값을 위 예제 처럼 저장해서 요거를 heap 영역에서 활용할 수도 있다 */ |
함수의 파라미터의 포인터
파라미터의 포인터는 단순히 변수의 주소값을 받기 위함일 수 있지만
배열변수를 받기 위함일 수 도 있다.
구분하는 방법은 보통 배열일 경우 배열의 개수를 알려주는 변수를 따로 파라미터로 받게 된다.
- int foo( int* v, int size ); // 배열일 가능성이 높음
- int goo( int* v ); // 한개의 값만 필요로 할 가능성이 높음
#include <cstdio> #include <cstdlib> void foo(int* a, int count){ // 파라미터의 포인터는 함수, int*은 포인터의 값을 의미 하는 것일수도 있지만 배열을 집어넣어라 라는 의미일 수도, 배열일 때는 int count까지 받게 되는 경우가 많다, foo 라는 함수가 이 int 배열을 출력하는 함수다 for (int i = 0; i < count; i++){ printf("%d\n", a[i]); } } int main() { int myArray[5] = {4, 6, 10, 20, 100}; foo(myArray, 5); return 0; } // 파라미터로서의 포인터는 그 자체가 포인터의 값을 집어넣을 수도 있지만 함수의 동작에 따라서 이건 배열의 이름을 집어넣어라는 걸 수도 있다 // 분명이 이게 어떤 배열이다라고 가졍했을 때는 오른쪽에 꼭 int count로 숫자를 받는다, // 숫자를 받아서 그 숫자만큼 뭔가를 해야 되기 때문에 포인터를 받았으면 그 뒤에 배열의 크기를 받는지 확인하고 그 배열 크기를 받는다면 이거는 아마도 배열을 이용해서 출력을 한다던지 덧셈을 한다던지 그런 일을 하는 코드가 됨 |
const char*
: 하드코딩된 문자열의 타입
const char*는 하드코딩된 문자열(" ... ")을 넣기 위해 이콜 (=) 기호를 사용한다
const char*에는 strcpy를 사용할 수 없다
이 const char*의 경우 하드 코딩된 문자열을 넣기 위해 사용되고
이런 하드코딩된 문자열을 '파라미터로 받기 위해' 역시 정말 많이 사용됨
파라미터로 받을 수 있다는 의미는 이콜(=) 기호를 사용할 수 있다는 것
#include <cstdio> #include <cstdlib> void foo(const char* str){ printf("%c", str[1]); // 이거도 일종의 배열이라 str[1] 출력시 e만 출력될 것임 } int main() { foo("Hello World"); // 이 const char 포인터타입은 앞으로 이런 문자 literal을 받는데 많이 사용, 하드코딩된 "로 엮어진 문자열 return 0; } // 왜 이게 const char* 포인터인가? // 프로그램이 시작되면서 끝날 때까지 절대 변하지 않는 어떤 문자열이다, exe 파일에 그냥 박혀있다 // 그렇기 때문에 이건 상수고 그렇기 때문에 const를 붙여야 된다 |
파라미터의 const char*
하드코딩된 문자열을 넣을 수 있지만,
문자열의 배열도 파라미터로 집어넣을 수 있다
char*, char 배열 등등의 변수이름은
모두 const char*의 파라미터 형식으로 받을 수 있다
const char* 포인터가 char 포인터가 될 수는 없지만 char 포인터는 const char 포인터가 되는데 전혀 무리가 없다
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstdlib> #include <cstring> void foo(const char* str){ printf("%c", str[1]); } int main() { char* str = (char*)malloc(sizeof(char) * 12); strcpy(str, "Hello World"); foo(str); return 0; } // char 포인터가 const* char 포인터가 되는데엔 아무 문제 없다, 반대는 안됨 |
그냥 const* char 포인터는
이런 식으로 우리가 " 로 묶어서 하드코딩해서 들어가는 문자열을 집어넣는데 활용한다
라는 정도로만 이해하면됨
const* char 포인터는
대부분 파라미터로 많이 쓰게 되고
아니면 이제 변수로 쓰게 될 때는 배열이나 나중에 배우게 될 std string 같은 걸 많이 쓰게 된다
void*
어떠한 포인터도 될 수 있는 것이 void*
모든 포인터의 기본형
다른 포인터 타입에서 void*으로 변환될 땐 자동 형변환
void*에서 다른 포인터 타입으로 변환될 땐 형변환 명시
모든 포인터가 void* 포인터로 형변환이 될 수 있다, void* 포인터는 다른 포인터로 될 수도 있다
그래서 malloc 이라는 함수가 void 포인터를 return 함
void* 포인터를 리턴하기 때문에 이 값을 결국 size 크기만큼 byte를 할당해서 void 포인터로 return을 해 주고
그 void 포인터를 char 포인터로 형변환을 해서 집어넣게 됨
모든 포인터의 기본형 포인터이기 때문에
void* 타입에서 다른 포인터 타입이 될 때는 형변환을 프로그래머가 명시해야 함
때문에 malloc( ... ) 함수를 사용하고 포인터에 주소값을 넣을 때 malloc( ... ) 함수 앞에 형변환을 해줌
(malloc 함수의 리턴타입이 void*임)
'C언어' 카테고리의 다른 글
심화 C 언어 8 (함께 만드는 코딩 #1 - MBTI 성격검사 테스트) (0) | 2021.08.17 |
---|---|
심화 C 언어 7 (매크로 - C/C++의 독특한 매력) (0) | 2021.08.06 |
심화 C 언어 6 (포인터 3 - 메모리 동적할당 / 배열) (0) | 2021.08.03 |
심화 C 언어 5 (포인터 2 - 포인터를 쓰는 이유) (0) | 2021.08.01 |
심화 C 언어 4 (포인터 1 - 기초) (0) | 2021.07.31 |