이번 단원에 알아두어야 할 핵심개념 : call-by-reference, call-by-value
함수의 인자로 배열 전달하기
기본적인 인자 전달 방식 : 값의 복사에 의한 전달
정확히는 val을 전달하는 게 아닌, val이 지니고 있는 값을 전달
'복사'해서 a를 초기화하고 있다.
배열의 함수 인자 전달 방식 : 배열이름(배열주소, 포인터)에 의한 전달
배열전체를 복사하여 함수의 인자로 넘길 수는 없다 (나중에 구조체로는 가능)
그래서 주소값만 복사해 전달하여 사용한다.
배열 전달의 예
#include <stdio.h>
void fct(int *arr2);
int main ()
{
int arr1[2] = { 1, 2 }; //arr1 배열이름이자 포인터는 int형 포인터, 주소를 가리킨다
fct (arr1); // arr1이 지닌 주소값을 배열의이름으로 전달하면서 fct 함수를 호출하면서 전달한다
printf ("%d\n", arr1[0]);
return 0;
}
void fct(int *arr2)
{
// arr2는 arr1의 주소값을 받는다
// int형 변수의 주소값이기 때문에 int형 포인터여야 한다 **
printf("%d\n", arr2[0]);
// *(arr2+0)와 같은 문장...
// *(arr2+0)이라는 주소값이 가리키는 메모리공간을 참조하라
// 1 출력
arr2[0]=3;
// *(arr2+0) = 3; 과 같은
}
배열이름, 포인터의 sizeof 연산
배열이름 : 배열전체 크기를 바이트로 반환
포인터 : 포인터의 크기를 바이트로 반환
#include <stdio.h>
int main()
{
int arr[5];
int* pArr=arr;
printf("%d\n", sizeof(arr)); //배열 이름 : 배열전체 크기를 바이트단위로 반환 (20)
printf("%d\n", sizeof(pArr)); // 포인터의 크기를 바이트단위로 반환(4 or 8)
//printf("%d\n", sizeof(*pArr)); //4
printf("%d\n", sizeof(int)); // 4byte
return 0;
}
배열의 길이는 인자로 전달받은 함수 내에서 계산이 불가능하고
함수를 호출할 때 아래처럼(sizeof(배열)/sizeof(자료형) 계산하여 전달을 해야 한다...
#include <stdio.h>
int ArrAdder(int* pArr, int n);
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int SumOfArr;
// 배열의 길이가 바뀌어도 아래 코드는 그대로 쓸 수 있다.
SumOfArr = ArrAdder(arr, sizeof(arr)/sizeof(int));
// SumOfArr = ArrAdder(arr, 10); //하드코딩
// sizeof(int) int의 크기는 시대에 따라 다를 수 있다
printf("배열의 길이체크 : %d\n", sizeof(arr)); //40
printf("배열의 총 합 : %d\n", SumOfArr);
return 0;
}
//배열의 시작번지, 배열의 길이
int ArrAdder(int* pArr, int n)
{
int sum = 0;
int i;
// n 대신 sizeof(pArr)를 쓰면 배열의 길이가 아니고 포인터 크기가 반환
for(i=0; i<n; i++)
sum+= pArr[i];
return sum;
}
int* pArr vs int pArr[ ]
int* pArr을 많이 쓰기를 권해드림. 배열과 포인터를 혼동하기 때문이다.
둘은 같은 의미이나, 선언 int pArr[ ] 은 함수의 매개 변수 선언 시에만 사용 가능
매개변수 선언 시가 아니면 불가
// int function(int* pArr)
// 변수의 주소값 전달인지, 배열의 이름 전달인지 알수가 없다
// 배열이 인자로 전달됨을 알 수 있다 -- 의미명확
// 매개변수 선언 시 아래 방식을 선호한다
// 배열의 이름이 아니고 진짜 포인터다
// sizeof 연산 시 포인터 크기가 나온다
int function(int pArr[])
{
int a = 10;
pArr = &a;
return *pArr;
}
배열이 갖고있는 요소 중 최대값을 반환하는 예
#include <stdio.h>
int MaxVal(int pArr[], int n);
int main(void)
{
int arr[10] = {4,8,3,7,2};
int max;
max = MaxVal(arr, sizeof(arr)/sizeof(int));
printf("최대값 : %d\n", max);
return 0;
}
// 배열의 시작번지, 배열의 길이
// int pArr[]은 포인터변수
// int MaxVal(int *pArr, int n)
int MaxVal(int pArr[], int n)
{
int max, i;
printf("sizeof(pArr): %d\n", sizeof(pArr));
max=pArr[0];
for(i=1; i<n; i++)
if(max<pArr[i])
max=pArr[i];
return max;
}
#include <stdio.h>
int MaxVal(int pArr[], int n);
int main(void)
{
int arr[10] = {4,8,3,7,2};
int max;
//int *p;
//int p[]; //error - 배열 선언인데 size 생략했다고 판단됨
max = MaxVal(arr, sizeof(arr)/sizeof(int));
printf("최대값 : %d\n", max);
return 0;
}
// 배열의 시작번지, 배열의 길이
// int pArr[]은 포인터변수
// int MaxVal(int *pArr, int n)
int MaxVal(int pArr[], int n)
{
int max, i;
printf("sizeof(pArr): %d\n", sizeof(pArr));
max=pArr[0];
for(i=1; i<n; i++)
if(max<pArr[i])
max=pArr[i];
return max;
}
Call-By-Value
값의 복사에 의한 함수의 호출
call : 함수 호출을 뜻함
가장 일반적인 함수 호출 형태
#include <stdio.h>
int add(int a, int b);
int main()
{
int val1 = 10;
int val2 = 20;
printf("결과: ", add(val1, val2));
//call by value
return 0;
}
// 복사
int add(int a, int b)
{
// val1과 다른 변수
return a+b;
}
call-by-value에 의한 swap
swap함수 : 서로 바꾸는 기능을 하는 것
가짜 swap? 찐 swap은 val2와 val1의 진짜 값이 변경됨
직접적으로 val1, val2의 값을 바꿀 수 없고, 코드 실행 후에도 그대로이다.
#include <stdio.h>
int main()
{
int val1=10;
int val2=20;
swap(val1, val2);
printf("val1 : %d\n", val1);
printf("val2 : %d\n", val2);
return 0;
}
void swap(int a, int b)
{
int temp=a;
a= b;
b=temp;
printf("a : %d\n", a);
printf("b : %d\n", b);
}
note : previous implicit declaration of 'swap' was here swap(...
묵시적 선언 오류 : 함수 선언을 하지 않고 먼저 사용했다는 뜻.
묵시적 선언 오류 수정 후
#include <stdio.h>
void swap(int a, int b);
int main()
{
int val1=10;
int val2=20;
swap(val1, val2);
printf("val1 : %d\n", val1);
printf("val2 : %d\n", val2);
return 0;
}
void swap(int a, int b)
{
int temp=a;
a= b;
b=temp;
printf("a : %d\n", a);
printf("b : %d\n", b);
}
call-by-reference
참조(참조를 가능케하는 주소값)를 인자로 전달하는 형태의 함수 호출
main함수 내 지역변수에 접근할 수 있었다.
#include <stdio.h>
int main()
{
int val = 10;
adder(&val);
// val의 주소 전달
printf("val: %d", val);
// 11 출력
return 0;
}
void adder(int* pVal)
{
// val의 주소값이 전달됨 (Ox10 등..)
// 포인터변수 pVal이 가리키는 변수의 값 1 증가
(*pVal)++;
}
call-by-reference에 의한 swap
#include <stdio.h>
int main()
{
int val1 = 10;
int val2 = 20;
printf("before val1: %d\n", val1);
printf("before val2: %d\n", val2);
swap(&val1, &val2); //주소전달
printf("After val1: %d\n", val1);
printf("After val2: %d\n", val2);
return 0;
}
void swap(int* a, int* b)
{
int temp = *a;
*a=*b;
*b=temp;
}
call-by-value는 값을 전달 / call-by-reference는 주소 값을 전달하는 함수 호출이다.
call-by-value는 값을 복사해 전달해서 다른 메모리공간을 접근, 값을 변경할 수 없고
call-by-reference는 주소값을 전달해서 주소값에 해당하는 메모리공간에 접근 가능하다.
scanf 함수 호출 시 &를 붙이는 이유
scanf는 메인함수의 val에 접근하여 값을 대입하는 것
접근하려면 주소값을 알아야 한다. (call-by-reference 방식)
#include <stdio.h>
int main()
{
int val;
scanf("%d", &val);
#include <stdio.h>
int main()
{
char str[100];
printf("문자열 입력 : ");
scanf("%s", str);
// 배열 이름이 주소값이기 때문에 &를 붙이지 않는다
포인터와 const 키워드
const는 선언 첫부분, 포인터 이름 앞에, 두 군데 모두 붙일 수 있다.
1. 포인터가 가리키는 변수의 상수화
int a = 10;
const int* p = &a;
// 포인터가 가리키는 대상(a)을 상수화
// 포인터를 이용한 변경이 불가하다
// p의 관점에서 a가 상수화
// *p=30; //error
a=30; //ok
2. 포인터 상수화
int a = 10;
int b = 20;
int* const p = &a;
// 포인터가 갖고있는 값 자체를 상수화
// 한번 가리킨 포인터의 대상 변경 x 주소값 변경 ㄴ
// 변수는 변경 가능
// p=&b //error
*p=30; //ok
3. 둘 다
const int* const p = &a;
const 키워드를 사용하는 이유
컴파일 시 잘못된 연산에 대한 에러메시지
프로그램을 안정적으로 구성
#include <stdio.h>
float PI = 3.14;
int main()
{
float rad;
PI = 3.07; //실수..바뀌면 안되는 대상
// 그러나 컴파일에러도 없다
// 경고메세지까지 구현했어야 함
scanf("%f", &rad);
printf("원의 넓이는 %f\n ", rad*rad*PI);
return 0;
}
아래처럼 컴파일에러가 발생될 수 있게 하여 안정적으로 코드를 짜도록 한다.
#include <stdio.h>
const float PI = 3.14;
int main()
{
float rad;
PI = 3.07; // 컴파일에러 발생
scanf("%f", &rad);
printf("원의 넓이는 %f\n ", rad*rad*PI);
return 0;
}
'C, C++ > 열혈 C 프로그래밍' 카테고리의 다른 글
열혈 C 17장 포인터의 포인터 (0) | 2021.09.07 |
---|---|
열혈 C 16장 다차원 배열 (0) | 2021.09.06 |
열혈 C 13장 포인터와 배열 함께 이해하기 (0) | 2021.09.02 |
열혈 C 12장 포인터 (0) | 2021.09.02 |
열혈 C 11장 1차원배열 (0) | 2021.09.01 |