부스트 코딩뉴비챌린지
- 5주차 라이브 강의 수강 완료! 이로써 챌린지가 2주 남았다.
- 지금까지 미션 코드도 나름 짜 보고, 팀원 분들 독려도 틈틈이 해가면서 열심히 해왔는데 끝이 보이니 약간은 시원섭섭하다ㅠ 주차마다 현업에 종사하시는 분들이 해주시는 라이브 강의도 정말 유익했는데 이제 2회밖에 안 남았다니 아쉽다.
- 6주차 자료구조 관련 미션이 오픈되었다! 빨리 코딩하고 싶은데, 내일 갑자기 아침 일찍 타지에 갈 일이 생겨서 오늘은 동기부여 영상까지만 수강했다ㅠ 6주차 CS50 강의 먼저 듣고 후딱 코딩할 생각에 설렌다(ㅎㅎ)
윤성우의 열혈 C 프로그래밍
17-1 포인터의 포인터에 대한 이해
- 포인터의 포인터란, 포인터 변수를 가리키는 또 다른 포인터 변수를 뜻하는 것으로 이중 포인터 혹은 더블 포인터라고 부른다.
- 더블 포인터 변수의 선언:
자료형 ** dptr;
double num = 3.14;
double * ptr = #
double ** dptr = &ptr;
- 위와 같은 경우,
*dptr
은 포인터 변수 ptr
을 의미하며 *(*dptr)
은 변수 num
을 의미한다. *(*dptr)
에서 괄호는 생략 가능하여 **dptr
로 표현 가능하며 이것이 보다 일반적인 표현이다.
- 포인터 변수 대상의 Call-by-reference : 포인터 변수가 참조하는 대상을 변경하기 위한 사용자 함수를 선언하려면 이중 포인터 변수가 필요하다. int형 변수를 대상으로 하는 swap 함수에서 싱글 포인터 변수를 사용했다면 int형 포인터 변수를 대상으로 하는 swap 함수에서는 더블 포인터 변수를 사용해야 함!
- 포인터 배열의 이름은 더블 포인터이다.
17-2 다중 포인터 변수와 포인터의 필요성
- 삼중 포인터 변수: 이중 포인터 변수의 주소 값을 저장, 사용되는 예는 그리 많지 않다.
- 포인터의 필요성: 함수 내에서 함수 외부에 선언된 변수에 접근하는 방법을 제시해 준다. 자료구조를 공부하게 되면 더 자세히 알게 될 것이다!
문제 17-1 이중 포인터 변수의 활용
#include <stdio.h>
#define SIZEOFARR 5
void MaxAndMin(int* arr, int** max, int** min);
int main() {
int* maxPtr;
int* minPtr;
int arr[5] = { 11,10,8,1,19 };
MaxAndMin(arr, &maxPtr, &minPtr);
printf("max: %d, min: %d\n", *maxPtr, *minPtr);
}
void MaxAndMin(int* arr, int** max, int** min) {
int* maxtmp = arr;
int* mintmp = arr;
for (int i = 1; i < SIZEOFARR; i++) {
if (*maxtmp < *(arr + i))
maxtmp = arr + i;
if (*mintmp > * (arr + i))
mintmp = arr + i;
}
*max = maxtmp;
*min = mintmp;
}
18-1 2차원 배열 이름의 포인터 형
- 포인터 배열의 이름이 더블 포인터 형임을 기억하자. 따라서 2차원 배열의 이름은 더블 포인터 형이 아니다!!
- 2차원 배열의 이름이 가리키는 것
int arr2d[3][3]
과 같이 선언된 2차원 배열이 있다고 가정하면, 배열 이름이 가리키는 것은 [0][0]에 위치한 요소이다.
또한 2차원 배열의 경우 arr2d[0], arr2d[1], arr2d[2]
도 의미를 갖는다. 이들은 각각 1, 2, 3행의 첫 번째 요소를 가리킨다.
그렇다면 arr2d, arr2d[0]
은 같을까? 답은 아니다. sizeof 연산을 하는 경우 배열 이름인 arr2d
를 대상으로 하면 배열 전체의 크기를 반환하지만, arr2d[0]
을 대상으로 하면 1행의 크기만을 반환한다. 따라서 arr2d, arr2d[0]
는 둘 다 2차원 배열의 첫 번째 주소값을 가리키기는 하지만 arr2d
는 첫 번째 요소를 가리키며 배열 전체를 의미하고, arr2d[0]
는 첫 번째 요소를 가리키되 1행만을 의미한다는 것을 알 수 있다.
- 배열 이름 기반의 포인터 연산: 배열 이름에 1을 더한 결과는?
int iarr[3];
과 같이 선언된 배열의 경우, iarr은 int형 포인터이며 printf("%p", iarr + 1);
을 실행하면 iarr+sizeof(int)의 결과가 출력된다.
그렇다면 2차원 배열 이름의 포인터 형을 결정짓기 위해 2차원 배열 이름을 대상으로 증가연산을 진행해 보면 어떻게 될까?
실제로 증가연산을 해 보면 arr가 1행의 첫 번째 요소를 가리킨다면 arr+1은 2행의 첫 번째 요소를 가리키게 된다는 것을 알 수 있다. 즉 연산 결과는 배열의 열 수와 상관없이 각 행의 첫 번째 요소의 주소 값이 된다. -> 2차원 배열 이름의 포인터 형은 배열의 가로 길이에 의해서도 달라진다.
- 그렇다면 2차원 배열 이름의 포인터 형은 어떻게 결정할까?
2차원 배열 이름의 포인터 형에는 다음 두 가지 정보가 함께 담겨야 한다.
+ 가리키는 대상이 무엇인가
+ 배열 이름을 대상으로 증가 및 감소연산 시 실제로 주소 값이 얼마나 증가 혹은 감소하는가
따라서 int arr[3][4];
와 같은 배열의 포인터 형을 묻는다면, 가리키는 대상이 int형 변수이며 (arr+1)과 같은 증가연산 시 실제로 주솟값이 sizeof(int)X4 만큼 증가하는 포인터 형이라고 할 수 있을 것이다.
이는 다음과 같은 형태로 선언하기로 약속되었다.
int (*ptr) [4];
해석해 보면, "int형 변수를 가리키는 포인터 ptr이며 포인터 연산 시 (int형 변수를) 4칸씩 건너뛰는 포인터"라고 할 수 있다.
이러한 포인터 변수는 2차원 배열을 가리키는 용도로만 사용되며 배열 포인터 변수
라고 부른다.
18-2 2차원 배열 이름의 특성과 주의사항
- 주의! 배열 포인터와 포인터 배열을 헷갈리지 말자.
int * whoA [4];
int (*whoB) [4];
- 2차원 배열을 함수의 인자로 전달하기
: void func(int (*arr) [3])
혹은 void func(int arr[][3])
물론 이 둘은 매개변수의 선언에서만 같은 의미이다.
- 2차원 배열에서도
arr[i] == *(arr+i)
문제 18-1 2차원 배열에 대한 종합점검
문제 1
int** ptr1 = arr1;
int* (*ptr2) [5] = arr2;
문제 2
void Fun1(int* arr1, int* arr2);
void Fun2(int (*arr3) [4], int (*arr4) [4]);
문제 3
void Fun1(int** arr1, int* (*arr2) [5]);
void Fun2(int*** arr3, int*** (*arr4) [5]);
문제 4
3 2
6 4
5 2
1 1
문제 5
(*(arr+1))[0][1]
(*(*(arr+1)+0))[1]
(*(*(*(arr+1)+0)+1))
*(arr[1][0]+1)
*(*(arr[1]+0)+1)
(*arr[1]+0)[1]
19-1 함수 포인터와 void 포인터
- 함수 포인터의 이해: 사용저 정의 함수의 이름은 메모리상에 저장된 함수의 주소 값을 의미한다. 함수의 이름은 그 형태가 상수인데, 이러한 함수의 주소 값 저장을 위해 선언된 포인터 변수를 가리켜
함수 포인터 변수
라고 한다.
- 함수 포인터 변수의 선언: 반환형과 매개변수 선언을 통해 결정된다.
int Func(int num);
과 같이 선언된 함수일 경우 함수 이름 Func는 "반환형이 int이고 매개변수로 int형 변수가 하나 선언된 포인터 형"이다.
- 적절한 함수 포인터 변수의 선언
int (*fptr) (int);
: 반환형이 int이고 매개변수 선언이 int 하나인 함수 포인터
int (*fptr) (int, int);
: 반환형이 int이고 매개변수 선언이 int 2개인 함수 포인터
이 함수 포인터 변수에 함수 Func의 주소 값을 저장하려면 fptr = Func
과 같이 대입연산을 진행하면 된다. 대입연산 후에는 fptr과 Func에 동일한 값이 저장되어 상수인지 변수인지의 차이밖에 없게 된다.
- 함수의 매개변수 선언으로도 함수 포인터 변수가 올 수 있다. 인자 전달을 통해 함수를 실행하여 반환된 값을 재반환할 수도 있다.
- 형이 존재하지 않는 void 포인터:
void * ptr;
과 같이 선언되는 포인터 변수를 가리키며, 어떠한 변수의 주소 값이든 담을 수 있다(함수의 주소 값도 담을 수 있다).
But 무엇이든 담을 수는 있지만, 가리키는 대상에 대한 형(type) 정보가 담겨있지 않아 포인터 연산과 변경, 참조 등이 모두 불가능하다.
-> 일단 주소 값에만 의의를 두고 포인터 형은 나중에 생각할 때 유용
19-2 main 함수로의 인자전달
- 프로그램 실행 시 main 함수로도 인자를 전달할 수 있고, 매개변수 선언이 가능하다.
- 인자의 형성과정:
int main(int argc, char* argv[]) { . . . }
와 같이 main 함수를 선언하고 코드를 컴파일하여 Code.exe
가 생성되었다고 하자. 명령 프롬프트에서 Code Hello World
를 입력해 프로그램을 실행한다면 main 함수로 Code
, Hello
, World
와 같이 3개의 문자열 정보가 전달된다. 이후 main 함수는 main(3, argv)
와 같은 형태로 호출된다. 여기에서 추가로 알아야 할 것은 char* argv
배열의 마지막에는 NULL이 삽입된다는 점이다.
- 위와 같이 함수를 선언하고 코드를 컴파일한 후
Code "Hello World"
를 입력하여 프로그램을 실행하면 Hello World
가 큰따옴표로 묶여 하나의 인자로서 전달된다.
오늘 공부하면서 느낀 점
- 인프런 git 강의는 오늘도 pass... 반성한다ㅠㅠ
- 열혈 C는 곧 학교 개강하면 이 책으로 수업하는 과목을 들어야 하기 때문에 최대한 빠르게 예습 한 번 하고 싶어서 공부를 나름 열심히 하고 있다. 이제 도전 프로그래밍3만 다 하면 Part04 C언어의 깊은 이해 로 넘어갈 수 있다. 중간에 여러 일들과 고민들로 학습 진도가 정체되었을 때도 있었고 내가 잘 할 수 있을지, 잘 하고 있는지 걱정도 많이 했지만 어느덧 책의 2/3을 끝냈다..! 나 자신을 칭찬해주기 위해 내일은 맛있는 거 먹어야겠다ㅎㅎ
- 오늘 C언어 공부한 건 복습이 좀 필요할 것 같다.
- 부스트 코딩뉴비챌린지 강의는 주말 내에 다 들을 것이다 꼭!!