C언어/C언어 이론
[C언어] 7주차 포인터의 활용
renne
2022. 6. 26. 15:54
포인터 배열
- 주소를 저장하는 배열
포인터 배열의 선언
데이터형 *배열이름[배열크기];
int *arr1[10];
char *arr2[20];
double *arr3[30];
STUDENT *arr4[40]; //구조체
포인터 배열의 사용
#include <stdio.h>
int main(){
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c}; //포인터 배열의 각 원소로 변수의 주소를 저장
for (int i = 0; i < 3; i++){
printf("원소: %d, ", arr[i]);
printf("원소가 가리키는 변수: %d\n", *arr[i]); //간접 참조 연산자 * 사용
}
return 0;
}
<실행 결과>
원소: 6487576, 원소가 가리키는 변수: 10
원소: 6487572, 원소가 가리키는 변수: 20
원소: 6487568, 원소가 가리키는 변수: 30
#include <stdio.h>
int main(){
int x[3] = {1, 2, 3};
int y[3] = {4, 5, 6};
int z[3] = {7, 8, 9};
int *arr[3] = {x, y, z}; //포인터 배열의 각 원소로 배열의 시작 주소를 저장
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
printf("%d ", arr[i][j]); //arr[i][j]와 *(arr[i]+j)는 같은 의미다.
}
printf("\n");
}
return 0;
}
<실행 결과>
1 2 3
4 5 6
7 8 9
구조체 포인터 배열
구조체 포인터 배열의 필요성
- 구조체 배열: 메모리를 많이 사용하므로 비효율적이다.
typedef struct student{
char name[20];
int kor, eng, math;
double average;
} STUDENT;
int main(){
STUDENT std[100]; //STUDENT 구조체가 100개 할당되므로 40*1000바이트가 필요하다.
}
- 구조체 포인터 배열: 구조체는 동적 메모리에 할당하고, 그 주소만 포인터 배열에 넣어두고 사용할 수 있다.
typedef struct student{
char name[20];
int kor, eng, math;
double average;
} STUDENT;
int main(){
STUDENT s1 = {"시리우스", 80, 90, 100, 0.0};
STUDENT s2 = {"레귤러스", 90, 85, 90, 0.0};
STUDENT *std[2] = {&s1, &s2}; //구조체 포인터 배열의 선언 및 초기화
for (int i = 0; i < 2; i++){
std[i] -> average = (double)((std[i] -> kor) + (std[i] -> eng) + (std[i] -> math)) / 3;
printf("%s의 평균: %5.2f\n", std[i] -> name, std[i] -> average);
}
return 0;
}
<실행 결과>
시리우스의 평균: 90.00
레귤러스의 평균: 88.33
구조체 포인터 배열의 메모리 구조
배열 포인터
- 배열 전체를 가리키는 포인터
배열 포인터의 선언
데이터형 (*포인터이름)[배열크기];
int (*p1)[10];
char (*p2)[20];
double (*p3)[30];
STUDENT (*p4)[40]; //구조체
배열 포인터의 사용
- 배열 포인터에는 배열 전체의 주소를 저장한다.
- 배열 포인터에는 크기가 같은 주소만 저장할 수 있다.
#include <stdio.h>
int main(){
int arr[5] = {1, 2, 3, 4, 5}; //배열 포인터에는 크기가 같은 주소만 저장할 수 있다.
int (*p)[5] = &arr; //배열 포인터에 배열 전체의 주소를 저장한다.
for (int i = 0; i < 5; i++){
printf("%d %d %d\n", (*p)[i], p[0][i], *((*p)+i));
}
return 0;
}
<실행 결과>
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
*p와 p[0]
- p는 배열 전체의 주소이고, *p는 p가 가리키는 배열이 된다.
- *p는 배열 이름처럼 사용할 수 있으므로, p가 가리키는 배열의 i번째 원소에 접근하려면 (*p)[i]를 사용한다.
- *p는 p[0]이므로, (*p)[i]는 p[0][i]와 같다.
배열 포인터와 이차원 배열
- 배열 포인터는 이차원 배열의 한 묶음을 가리키는 용도로 사용된다.
- 배열 포인터는 이차원 배열에 접근하기 위한 용도로 사용된다.
#include <stdio.h>
int main(){
int arr[2][4] = {{1, 2, 3, 4}, {10, 20, 30, 40}};
int (*p)[4] = &arr[0]; //&arr[0] 대신 arr로 초기화할 수도 있다.
for (int i = 0; i < 2; i++){
for (int j = 0; j < 4; j++){
printf("%2d ", p[i][j]); //p가 마치 이차원 배열명인 것처럼 사용한다.
}
printf("\n");
}
return 0;
}
<실행 결과>
1 2 3 4
10 20 30 40
배열 포인터와 배열의 원소를 가리키는 포인터
배열 포인터 | 배열의 원소를 가리키는 포인터 | |
선언할 때 | 데이터형 *(포인터명)[배열크기]; | 데이터형 *(포인터명); |
초기화할 때 | 이차원 배열의 주소로 초기화 | 일차원 배열의 주소로 초기화 |
사용할 때 | 이차원 배열처럼 사용 | 일차원 배열처럼 사용 |
증가할 때 | 이차원 배열의 제2크기만큼 증가 | 원소형 1개 크기만큼 증가 |
함수에 대한 포인터
- 함수의 주소를 저장하는 포인터
- 함수는 컴파일 및 링크 후에 메모리의 특정 번지에 할당된다.
- 실행 파일은 크게 코드 영역(함수들)과 데이터 영역(변수들)으로 나눠진다.
함수에 대한 포인터 변수의 선언
리턴형 (*포인터명)(매개변수); //예시 함수
int (*p1)(int); //int Factorial(int n);
char (*p2)(double, int); //char Grade(double avg, int rank);
//아직 가리키는 포인터가 없으면 널 포인터로 초기화한다.
int (*p3)(int) = NULL;
함수의 주소 구하기
//포인터명 = &함수명
p1 = &Factorial;
//포인터명 = 함수명
p1 = Factorial;
함수에 대한 포인터로 함수 호출하기
(*포인터명)(인자);
(*p1)(5); //p1이 가리키는 Factorial(5)를 호출한다.
포인터명(인자);
p1(5); //p1이 가리키는 Factorial(5)를 호출한다.
#include <stdio.h>
int Factorial(int n){
if (n < 2) return 1;
else return n * Factorial(n-1);
}
double Add(double x, double y){
return x + y;
}
int main(){
int (*p1)(int) = &Factorial;
double (*p2)(double, double) = Add;
int num;
scanf("%d", &num);
printf("%d! = %d\n", num, (*p1)(num));
printf("1.9 + 7.1 = %f", p2(1.9, 7.1));
return 0;
}
<실행 결과>
5
5! = 120
1.9 + 7.1 = 9.000000