반응형

14-1 Call-by-reference & Call-by-value

문제1

변수 num에 저장된 값의 제곱을 계산하는 함수를 정의하고, 이르 호출하는 main 함수를 작성해보자. 단, 여기서는 다음 두 가지 형태로 함수를 정의

 

 - Call-by-value기반의 SquareByValue 함수

 - Call-by-reference기반의 SquareByReference 함수

 

SquareByValue 함수는 인자로 전달된 값의 제곱을 반환해야 하며, SquareByreference함수는 정수가 저장되어 있는 변수의 주소 값을 인자로 받아서 해당 변수에 저장된 값의 제곱을 그 변수에 다시 저장해야 한다.

#include <stdio.h>

int SquareByValue(int num);//Call-by-value기반의 SquareByValue 함수
int SquareByReference(int *Rnum);// Call-by-reference기반의 SquareByReference 함수
int main()
{  
    int Vnum = 2;
    int Rnum = 3;
   
    printf("SquareByValue : %d\n", SquareByValue(Vnum));
    SquareByReference(&Rnum);
    printf("SquareByReference : %d", Rnum);
    return 0;
}

int SquareByValue(int num) {
    num = num * num;

    return num; //매개변수로 받은 값을 곱해서 리턴값으로
}
int SquareByReference(int *Rnum)
{
    *Rnum = (*Rnum) * (*Rnum);
    //매개변수로 주소를 받아 그 위치의 값 변경
}

문제2

세 변수에 저장된 값을 서로 뒤바꾸는 함수를 정의해보자. 예를 들어서 함수의 이름이 Swap3라하면, 다음의 형태로 함수가 호출되어야 한다.

 

 Swap3(&num1, &num2, &num3);

 

그리고 함수 호출의 결과로 num1에 저장된 값은 num2에, num2에 저장된 값은 num3에, 그리고 num3에 저장된 값은 num1에 자장되어야 한다.

#include <stdio.h>

void Swap(int *num1, int* num2, int *num3);
int main()
{  
    int num1 = 10, num2 = 20, num3 = 30;
   
    Swap(&num1, &num2, &num3);
    printf("N1 : %d\nN2 : %d\nN3 : %d", num1, num2, num3);
    return 0;
}

void Swap(int* num1, int* num2, int* num3){
    int temp = *num1;

    *num1 = *num3;
    *num3 = *num2;
    *num2 = temp;
}

 

반응형
반응형

14-1 함수의 인자로 배열 전달하기

함수호출 시 전달되는 인자의 값은 매개변수에 복사되어 들어간다.

int SimpleFunc(int num){....}
int main(void)
{
	int age=17;
    SimpleFunc(age);//age에 저장된 값이 매개변수 num에 복사된다.
}

소스에서 실제로 전달되는 것은 age가 아니라 age에 저장된 값이 복사 되어서 num으로 들어간다. 

int SimpleFunc(int num)
{
   num += 1;
}

복사되어 들어간 값은 SimpleFunc에서 1을 더한다고 해도 변수 age의 값은 증가가 일어나지 않는다.

그럼 이어서 함수호출 시 인자로 배열을 통재로 전달할려면 어떻게 해야 되겠는가?

배열을 통째로 넘겨받으려면 매개변수로 배열을 선언할 수 있어야 한다. 하지만 이것은 허용되지 않는다.

배열을 함수의 인자로 전달하려면

#include <stdio.h>

int SimpleFunc(int* num)
{
    for (int i = 0; i < 3; i++)
        printf("%d ", num[i]);
}
int main()
{
    int arr[3] = { 1,2,3 };
    SimpleFunc(arr);

    return 0;
}

매개변수를 포인터 변수로 선언되어야 한다. SimpleFunc(int *num) {. . . . . }

이를 대신해서 SimpleFunc(int num[]) {. . . . . } 같이 선언 하는것도 가능하다. 

int num[]int *num은 함수의 매개변수에서는 완전히 동일한 선언이다. int num[]이 좀 더 배열을 인자로 전달하는 느낌을 강하게 주기 때문에 int num[] 형태로 더 많이 사용한다.

int num[], int *num은 매개변수에서만 동일한 선언기 때문에 

int *ptr=arr; << int ptr[]=arr;로 대체 할수는 없다.

함수 호출은 매개변수에 주소 값을 전달해야 한다. SimpleFunc(arr);

14-2 Call-by-value vs Call-by-reference

Call-by-value와 Call-by-reference는 함수의 호출 방식을 의미한다.

Call-by-value는 값을 전달하는 형태의 함수 호출이다.

int SimpleFunc(int num){....}
int main(void)
{
	int age=17;
    SimpleFunc(age);//age에 저장된 값이 매개변수 num에 복사된다.
}

위에서 선언한 이런 형식의 함수 호출 방식이 Call-by-value형식이다. 주소 값을 복사해서 함수에 전달하기 때문에 

age변수의 값을 직접적으로 바꾸지는 못한다.

Call-by-reference는 주소 값을 전달하는 형태의 함수 호출이다.

#include <stdio.h>

int SimpleFunc(int* num)
{
    for (int i = 0; i < 3; i++)
         num[i] += 1;
}
int main()
{
    int arr[3] = { 1,2,3 };
    SimpleFunc(arr);
    for (int i = 0; i < 3; i++)
        printf("%d ", arr[i]);
    return 0;
}

 이 소스의 출력결과는 2 3 4 일 것이다. Call-by-reference 호출은 주소 값을 전달받아 주소값에 직접 접근 할 수 있기 때문에 arr 배열에 직접 접근해서 arr 변수의 값을 바꾸는게 가능하다.

14-3 포인터 대상의 const 선언

const 선언은 변수를 변경하지 못하는 상수화하는 목적으로 사용한다. 이러한 const 선언을 포인터 변수에서 사용했을때에 대해 알아보자.

int num= 10;
const int* ptr=&num;
*ptr=20; // 에러
num=20; // 성공

const를 포인터 변수 자료형 앞에 붙이면 포인터 변수가 가리키고 있는 주소의 값을 바꾸는 것을 허용하지 않는다.

int num= 10;
int num2= 20;
int* const ptr=&num;
ptr=&num2; //에러
*ptr=20; // 성공

const가 포인터 변수 자료형 뒤에 있으면 포인터 변수가 가리키는 주소를 바꾸는 것을 허용하지 않는다.

const int* const ptr=&num;

const를 앞 뒤로 다 붙여서 사용할 수도 있다.

 

const가 갖는 의미가 무엇일까?

const가 없으면 컴파일할때 오류가 생기지않고 컴파일 할 것이다. 그렇다면 const 변수를 사용하는 이유는 무엇일까?

int main(void)
{
	const double Pi= 3.141592;
   	 doble rad;
   	 PI = 3.07; // 컴파일 시 오류상황
   	 scanf("%lf", &rad);
   	 printf("circle area %f", rad*rad*PI);
    
   	 return 0;
}

PI를 3.07로 계산한다면 제대로 된 원의 넓이를 계산할 수 없을 것이다. const를 사용하지 않았다면 위와 같은 소스에서 PI를 값이 이상해졌다는 문제점을 파악하지 못할수도 있다. 이 소스는 간단하기 때문에 PI가 다른 수로 변했다는걸 쉽게 확인 할 수 있지만 만약 소스가 100줄만 넘어가도 PI를 다른 값으로 초기화 했다는걸 찾지 못할 것이다. 게다가 최악의 상황은 3.07로 원의 넓이를 계산했다는것 조차 인지하지 못할수도 있다는 것이다. 

그렇기 때문에 고정되는 값에 const를 사용하는 습관을 들여 컴파일에서 잡아주지 못하는 오류를 잡아줄수있다.

 

-윤성우 열혈C프로그래밍을 참고해여 작성한 글입니다.

반응형
반응형

문제 1

길이가 5인 int형 배열 arr을 선언하고 이를 1, 2, 3, 4, 5로 초기화한 다음, 이 배열의 첫 번째 요소를 가리키는 포인터 변수 ptr을 선언한다. 그 다음 포인터 변수 ptr에 저장된 값을 증가시키는 형태의 연산을 기반으로 배열요소에 접근하면서 모든 배열요소의 값을 2씩 증가시키고, 정상적으로 증가가 이뤄졌는지 확인하는 예제

#include <stdio.h>

int main()
{
	int arr[5] = { 1,2,3,4,5 };//길이가 5인 int형 배열 arr을 선언하고 이를 1, 2, 3, 4, 5로 초기화
	int* ptr = arr;//배열의 첫 번째 요소를 가리키는 포인터 변수 ptr을 선언
	for (int i = 0; i < 5; i++)
		*(ptr++) += 2;
    //포인터 변수 ptr에 저장된 값을 증가시키는 형태의 연산을 기반으로 배열요소에 접근하면서 모든 배열요소의 값을 2씩 증가
	for (int i = 0; i < 5; i++)
		printf("arr[%d]: %d   ", i, arr[i]);
	
	return 0;
}

문제2

문제 1에서는 포인터 변수 ptr에 저장된 값을 변경시켜가면서 배열요소에 접근하라고 하였다. 그런데 이번에는 포인터 변수 ptr에 저장된 값을 변경시키지 않고, ptr을 대상으로 덧셈연산을 하여, 그 결과로 반환되는 주소 값을 통해서 모든 배열요소에 접근하여 값을 2씩 증가시키는 예제

#include <stdio.h>

int main()
{
	int arr[5] = { 1,2,3,4,5 };//길이가 5인 int형 배열 arr을 선언하고 이를 1, 2, 3, 4, 5로 초기화
	int* ptr = arr;//배열의 첫 번째 요소를 가리키는 포인터 변수 ptr을 선언
	for (int i = 0; i < 5; i++)
		*(ptr + i) += 2;
    //포인터 변수 ptr에 저장된 값을 증가시키는 형태의 연산을 기반으로 배열요소에 접근하면서 모든 배열요소의 값을 2씩 증가
	for (int i = 0; i < 5; i++)
		printf("arr[%d]: %d   ", i, arr[i]);
	
	return 0;
}

문제3

길이가 5인 int형 배열 arr을 선언하고 이를 1~5로 초기화하고 이 배열의 마지막 요소를 가리키는 포인터 변수 ptr을 선언. 그 다음 ptr에 저장된 값을 감소하는 형태의 연산을 기반으로 모든 배열요소에 접근해서 전부 더하고 출력

#include <stdio.h>

int main()
{
	int arr[5] = { 1,2,3,4,5 };
		int* ptr = &arr[4];//배열의 마지막 요소를 가리키는 포인터 변수 ptr을 선언.
		int sum=0;

		for (int i = 0; i < 5; i++)
			sum += *ptr - i;
            //그 다음 ptr에 저장된 값을 감소하는 형태의 연산을 기반으로 모든 배열요소에 접근해서 전부 더하고 출력
		printf("%d", sum);
	return 0;
}

배열 안의 값 arr[4]=*(ptr+4)  ptr이 arr[0]일때

배열의 주소 &arr[4]=ptr+4 

문제4

길이가 6인 int형 배열 arr을 선언 1~6으로 초기화, 배열의 저장된 값은 순서가 6~1이 되도록 변경하는 예제 작성.

단, 배열의 앞과 뒤를 가리키는 포인터 변수 두개를 선언해서 이를 활용하여 저장된 값의 순서 바꾸야 한다.

#include <stdio.h>

int main()
{
	int arr[6] = { 6,5,4,3,2,1 };
	int* ptr1 = arr;
	int* ptr2 = &arr[5];
	int temp = 0;

	for (int i = 0; i < 3; i++)
	{
		temp = *(ptr1 + i);//ptr+0의 값을 저장
		*(ptr1 + i) = *(ptr2-i);//ptr+0을 ptr2-0의 값으로 변경
		*(ptr2 - i) = temp;//ptr2-0의 값을 temp의 값으로 변경
	}
	for (int i = 0; i < 6; i++)		
	{
		printf("%d ", arr[i]);
	}	
	return 0;
}
반응형
반응형

13-1 포인터와 배열의 관계

배열의 이름도 포인터이다. 

int arr[3];라는 배열을 선언 했을때 arr 그 자체는 arr[0]의 주소값을 가리킨다.

이게 무슨말인지 확인해보자

#include <stdio.h>

int main()
{
	int arr[3] = {11,22,33};
	printf("arr : %p \n", arr);
	printf("arr[0] : %p \n", &arr[0]);	
    printf("arr[1] : %p \n", &arr[1]);
	printf("arr[2] : %p ", &arr[2]);
    
	return 0;
}

 

위 소스코드 출력결고

arr과 arr[0]의 주소 출력이 같은걸 확인할 수 있다.  배열의 이름은 배열의 시작 주소의 값을 의미한다는 걸 확인했다.

또 이 소스로 확인 할 수 있는 부분은 int형 배열의 크기는 4바이트기 때문에 배열에서 다음 주소 값의 차가 4바이트임을 보여준다.

 

배열이름이 포인터라면 포인터와 배열의 차이점은 무엇일까?

배열은 안에 주소 값을 변경 할 수 없지만 arr= 0070F6D2; 로 변경불가

포인터는 주소 값을 변경 할 수 있다. ptr = 0070F6D2; 변경가능

배열은 그 값을 바꿀 수 없는 '상수 형태의 포인터'이다.

 

 

 

13-2 포인터 연산

int num[]={10, 20, 30};
int* ptr=num;

printf("num : %d ", *ptr+1);
int num[]={10, 20, 30};
int* ptr=num;

printf("num : %d ", *(ptr+1));

두 가지 소스의 출력결과가 예상되는가? 

첫 번째 소스는 *ptr이 num배열을 가리키고 있기 때문에 num[0] 값에 +1한 11이 출력 될 것이다.

두 번째 소스는 포인터 변수 ptr에 +1을 하고 그 값을 출력하기 때문에 20이 출력 될 것이다.

포인터 변수에 1을 더한다면 그 포인터 변수가 가지고있는 자료형의 크기만큼 증가 시킨다.

double형 포인터에 n을 더했다면 ptr+n = n * sizeof(double)의 크기만큼 증가 할 것이다. 

 

 중요한 결론은 arr[n] == *(arr+n) 

int arr[]={10, 20};

printf("%d %d\n", *(arr+0), arr[0]); //10 10
printf("%d %d", *(arr+1), arr[1]); //20 20 

똑같은 값이 출력되는 걸 확인할 수 있다. 이는 이후에 다차원 배열에서도 성립하는 유용한 식이니, 반드시 이해하고 기억해야 한다.

 

13-3 상수 형태의 문자열을 가리키는 포인터

char str[]="My String";
char* str2= "Your String";

문자열 배열에 str[0~10] "My String\n"를 저장한다.

"Your String\n"은 자동 메모리 공간에 할당하고 str2에 'Y'의 주소값을 저장한다.

#include <stdio.h>

int main()
{
	char str[] = "My String";
	char* str2 = "Your String";
	printf("%s %s \n", str, str2);

	str2 = "Our String";// 가리키는 대상 변경
	printf("%s %s \n", str, str2);

	str[0] = 'X'; //문자열 변경 성공!
	//str2[0] = 'X'; //문자열 변경 실패!
	printf("%s %s \n", str, str2);
	
	return 0;
}

str2 포인터 문자열에 다른 문자열을 넣으면 str2가 가리키는 문자열 주소를 변경한다. 소스에서는 'Y'주소를 가리키다가 'O'소스를 가리키는 포인터로 변경되었다. 그래서 2번째 줄에는 Our String이 출력된다. 이런 문자열을 '상수 형태의 문자열'이라 한다.

 

str[]은 3번째 출력에서 확인해보면 배열을 대상으로 값이 변경가능하다. str[0]='X'로 바뀠기 때문에 Xy String이 출력된다. 이런 문자열을 '변수 형태의 문자열'이라 한다.

 

13-4 포인터 변수로 이뤄진 배열: 포인터 배열

포인터 변수도 변수라 배열로 선언할 수 있다. 문자열 저장하는 포인터 배열을 만들 수 있다. 

char * strArr[3]; 길이가 3인 char형 포인터 배열을 만들면 어떤 길이의 문자열이든 3가지를 저장 할 수 있다.

#include <stdio.h>

int main()
{
	char *str[3] = { "asdasdasdasdas", "asdasda", "asdweds" };
	printf("%s \n", str[0]);
	printf("%s \n", str[1]);
	printf("%s \n", str[2]);
	
	return 0;
}

결과

char *str[3]에는 asdad....은 메모리에 저장되고 1~3번째 'a'의 주소가 저장된다.

실제 저장된 값은 ex) char * str[3]={0x1004, 0x1048, 0x2012}; //반환된 주소 값은 임의로 결정하였다.

첫 번째 주소를 불러서 \0까지 char를 읽는다.

 

-윤성우 열혈C프로그래밍을 참고해여 작성한 글입니다.

반응형
반응형

Point라는 변수는 주소값을 저장하는 변수이다.

여기서 주소값은 정수로 저장되기 때문에 int형 변수에 주소값을 저장 할 수도 있다.

그렇다면 왜 C에서는 Point라는 변수에 따로 주소값을 저장하는걸까?

메모리에 저장된 주소값

위 사진과 같이 변수 num의 주소값은 정수 0x12ff76이다.

정수이기 때문에 int변수에도 저장과 출력이 가능하지만 메모리 주소에 접근하지는 못한다.

우리가 주소값을 얻는 이유가 뭘까?

현실에서 생각해보면 부산광역시 남구 감만동이라는 주소를 알고 거기에 찾아가기 위해 우리는 주소를 집마다 부여한다.

컴퓨터에서도 메모리의 주소를 알고 주소에 접근(찾아가기)위해 Point 변수를 사용한다.

 

point변수의 크기는 64bit 시스템에서는 8byte이고 32bit 시스템에서는 4byte이다.

포인터변수 앞에서 자료형을 정해주는 이유는 8byte포인터가

int num=7;
int* pnum;
pnum = &num;

정수형 변수 num을 가리킨다면 포인터 변수 pnum은 int형 포인터 변수로 4바이트 메모리 공간에 부호 있는 정수의 형태로 데이터를 읽고 쓴다.

4byte까지 읽는다

그렇다면 만약 double 변수형에 int*(정수 포인터)로 연산하면 어떻게 될까?

	double num = 7.3;
	int* pnum = &num;
	*pnum = 4;
	printf("%d", *pnum);

형이 불이치 하지만 컴파일은 된다.

pnum이 가리키는 것은 double형 변수인데, pnum이 int형 변수이므로 int형 데이터처럼 해석된다.

 

다시 말하면

int형 포인터 변수는 4바이트 메모리 공간까지 읽고

double형 포인터변수는 8바이트 메모리 공간까지 읽는다.

포인터 변수마다 각각의 고유 자료형 이름을 가지는 이유는 변수가 시작되는 주소 값에서 얼마만큼의 크기를 읽어드릴지 위한 단위의 지정​하기 위함이다.

 

그래서 포인터의 형과 변수의 자료형을 맞춰줘야한다.

 

포인터 사용 방법

#include <stdio.h>

int main()
{
	int num = 100, num2 =100;
	int* ptr; //포인터 변수 선언
	ptr = &num; //ptr을 num주소값으로 초기화한다.
	*ptr += 30; //포인터가 가리키는 주소값에 변수의 값을 바꾼다.
	ptr = &num2;//ptr은 num2를 가리킨다.
	*ptr -= 30;//num2 -= 30과 동일

	printf("%d / %d", num, num2);
	
	return 0;
}

type없이 *ptr만 하면 메모리의 주소에 직접방문 할 수 있다.

직접방문해서 *ptr이 가리키는 값에 연산을 할 수도 있고, *ptr이 가리키는 값을 직접 출력할 수도 있다.

 

&는 변수의 주소 값을 반환한다.

 

-윤성우 열혈C프로그래밍을 참고해여 작성한 글입니다.

반응형
반응형

11-1 

문제1

정수 5개 최댓값, 최솟값, 총합 출력하는 예제

#include <stdio.h> 


int main(void) { 

	int A[5];
	int sum=0;
	for (int i = 0; i < 5; i++)
		scanf_s("%d", &A[i]);

	int max = A[0];
	int	min = A[0];

	for (int i = 0; i < 5; i++)
	{
		if (A[i] > max)
			max = A[i];
		if (A[i] < min)
			min = A[i];

		sum += A[i];
	}
	
	printf("최대 : %d 최소 : %d 합 : %d", max, min, sum);

	return 0;

}


문제2

%s 사용하지 않고 Good time문자 출력

#include <stdio.h> 


int main(void) {

	char A[] = { 'G', 'o', 'o', 'd', ' ', 'T', 'i','m','e' };

	for (int i = 0; i < sizeof(A); i++)
	{
		printf("%c", A[i]);
	}
	return 0;

}

11-2

문제 1 

영단어 하나를 입력받아 길이와 영단어를 출력

#include <stdio.h> 


int main(void) {

	char A[6];
	int i = 0;
	scanf_s("%s", A, 6);

	while (A[i] != NULL)
		i++;

	printf("%d\n", i);
	printf("%s", A);

	return 0;

}


문제 2

영단어 하나를 입력받아 거꾸로 뒤집어서 출력

#include <stdio.h> 


int main(void) {

	char A[256];
	char temp;
	int cnt=0;
	char B[256];

	scanf_s("%s", A, 256);

	while (A[cnt] != NULL)
		cnt++;
	int tempcnt = cnt;
	for (int i = 0; i < cnt; i++)
	{
		
		temp = A[tempcnt-1];
		B[i] = temp;
		tempcnt--;
		
	}
	B[cnt] = A[cnt];

	printf("%s", B);

	return 0;

}


 

문제3

입력된 영단어 중 아스키코드로 가장 큰 값을 찾아 출력

#include <stdio.h> 


int main(void) {

	char A[256];
	int num=0;
	char max;
	scanf_s("%s", A, 256);
	while (A[num]!=NULL)
	{
		if (A[num] < A[num + 1])
			max = A[num + 1];
		num++;
	}
	printf("%c", max);


	return 0;

}


반응형

+ Recent posts