[C] C언어 스터디 5주차

Review

  • Quiz
// 1. 크기 5의 int형 배열 arr를 선언하기

// 2. for문을 돌려 arr에 1부터 5까지 숫자를 순차적으로 입력받기

// 3. for문을 돌려 arr의 모든 요소들을 출력하기


/*
  코드 수행 예시)
  
  입력 : 1,2,3,4,5
  
  arr[0] = 1
  arr[1] = 2
  arr[2] = 3
  arr[3] = 4
  arr[4] = 5
  
*/


  • 배열

    • 동일한 타입의 변수들의 집합
      • int, char, double 섞인게 아니라
    • 자료형 배열이름 [배열 크기] 로 선언
    int arr[8] ; // 선언
    double values[3] = {99.8, 87.5, 100.0}; // 선언과 동시에 초기화
    

    image-20210725203641139


    • 인덱스로 접근!
    • 접근 범위는 0부터 크기-1 까지 (범위를 벗어나면 ‘읽기 전용’)
    int nums[3] = {10, 20, 30};
      
    printf("%d", nums[0]); // 10
    printf("%d", nums[1]); // 20
    printf("%d", nums[2]); // 30
    


image-20210719135738710

  • 위 그림은 byte 크기를 기준으로 조금 더 구체적으로 그린 그림.
  • nums[3] 읽는것 자체는 가능. 할당되지 않은 메모리라 ‘쓰레기값 출력’
    • (IDE 발전하면서 변함, 원래는 읽는것도 에러)
  • 하지만 값을 대입하면 Run time Error 발생!



  • ‘주소’ 개념 매우 중요! 배열의 이름은 배열의 첫 번째 주소이다.
    • int arr[10];
    • arr == &arr == &arr[0];



  • 이차원 배열

    • 행과 열 구조, 대괄호 두 개 필요
    • x축 하나만 있다가, x축 y축 두 개로 늘어난 것! 1차원 -> 2차원!!
    • 자료형 배열이름 [배열 행크기] [배열 열크기]
    int arr[2][4]; // 선언
    char str[4][10] = {"hello", "test", "string", "hi"}; // 선언과 동시에 초기화
    
    • 배열의 사용 일차원과 똑같다!
    • 위의 경우 접근 가능한 범위는 // 범위를 벗어나면 out of range Error!
    arr2[0][0] 부터 ~ arr2[1][3] 까지
    str[0][0] 부터 ~ str[3][9] 까지 
    



  • 메모리적인 관점

    • 0x0039… 는 컴퓨터 메모리 어딘가의 ‘주소’
    int value = 10;
      
    &value; // 0x0055.. 처럼 메모리 어딘가에 4byte 만큼 할당 된 것.
    value	// 그 주소에 해당하는 공간에 10이라는 값이 들어있는 것.
    


memoryAccessAboutArray


  • 함수에 배열을 넘기는 방법

    • 배열을 함수에 넘길 수 있다.
    • 매개변수로 받는 선언부
    int function1(int a); // int형 변수를 매개변수로
    int function2(char ch); // char형 변수를 매개변수로
      
    int sumOfNumbers(int[]); // int형 일차원 배열을 매개변수로
    int sumOfNumbers2(int [5][]); // int형 이차원 배열을 매개변수로
    
    • 함수에 인자로 넘길 때
    int arr[5] = ... ;
    int arr2[5][5] = ... ;
      
    sumOfNumbers(arr);	 // 배열의 '이름'을 넘겨준다.
    sumOfNumbers2(arr2); // 배열의 이름은 배열의 첫번째 주소이다!!
      
    // 다르게 표현하면?
    sumOfNumbers(__); // &arr 도 되고,  &arr[0]도 되고
    sumOfNumbers2(__); // &arr2도 되고, &arr2[0]도 되고, &arr2[0][0]도 되고
    



——————————————————–


포인터

포인터의 이해


포인터란?

  • 포인터 == 주소
  • ‘변수 A의 포인터’ 라는 문장 == ‘변수 A의 주소’
  • 따라서 포인터 변수란, 주소를 담는 변수
    • int형 변수, int형 자료를 담는 변수
    • char형 - char을 담는..
int num = 10;
int* ptr = # // & : 앰퍼샌드. 주소.

image-20210719152824895

  • 10이라는 을 담고있는 int형 변수 num
    • int 형 데이터를 자료로 담는 변수 num
  • 주소 0x1154를 담고있는 int형 포인터 변수 ptr
    • int* 형 데이터를 자료로 담는 변수 ptr
    • int형 변수의 주소를 자료로 담는 변수 ptr


image-20210725204905894

  • 포인터 변수 ptr이 num을 가리킨다 라고 표현하기도 함.
    • num 은 10
    • ptr은 &num, 즉 0x1154
    • *ptr은 num, 즉 10
      • 역참조 연산자


image-20210719153617502



image-20210719155018683

  • num에는 10이라는 값이 들어있음.
  • int* ptr 에는 num의 주소인 0x1154 가 들어있음.
  • 즉, ptr은 num을 가리키고 있다. == ptr은 변수 num의 주소를 담고 있다.
    • ptr은 변수 num이 메모리의 어디에 할당 되어있는지 알고 있는 것.
    • 특정 메모리에 접근할 수 있는 것


포인터 변수의 크기

  • 모든 포인터 변수의 크기는 4byte 이다? -> 정확한 표현이 아님.
  • 포인터 변수의 크기는, 주소를 나타내는 크기에 따라 다르다.
  • 즉, 컴파일 되는 컴파일러의 bit 방식. 32bit / 64bit냐? 에 따라서 달라진다.
  • 우리는 32bit 형식으로 코딩하다 보니까 4byte.


포인터 자료형

  • int*
  • char*
  • double*
  • 똑같은 ‘주소’인데 왜 자료형이 다를까?
    • 해당 주소에 접근해서 가져올 때 그 값이 자료형마다 다르니까…



포인터 사용

int *ptr = NULL; // 처음에 초기화를 안해줄 땐, NULL로 초기화 하는게 일반적
int num = 10;
ptr = #		// 포인터 변수 ptr에 num의 주소를 대입

int *ptr2 = ptr;

ptr2 -> num;
ptr  -> num;
  • 메모리를 다루는 것이기 때문에, 조심해야함!! 그래서 안쓸땐 NULL로 초기화


포인터 응용1

// 1. 값에 의한 호출 (Call by value)

void changeValue(int n);

void main() {
	int num = 10;
	
	printf("%d", num); // 10
	changeValue(num);	
	printf("%d", num); // 10
}

void changeValue(int n) {
	n = 20;
}
  • 결과는?


// 2. 주소에 의한 호출 (Call by reference)

void changeValue(int* n);

void main() {
	int num = 10;
	
	printf("%d", num); // 10
	changeValue(&num);	// num = 20 과 결과가 같아진다
	printf("%d", num); // 20
}

void changeValue(int* n) {
    *n = 20;
}
  • 결과는?


주소를 넘겨줄거냐? 값을 넘겨줄거냐?

★★★★ **Call by reference? Call by value? **★★★★

  • 주소를 넘겨주면, 해당 메모리의 주소에 직접 접근 할 수 있는 것!!
  • 다시 복습. 배열의 이름은 배열의 첫 번째 주소
  • 다시 말하면, 배열의 이름은 배열의 포인터


포인터 응용2

// 주소 출력하여 비교하기

void print_reference(int n);

void main() {
	int num = 100;
	int* ptr = #

	print_reference(num);
	print_reference(ptr);
	
	printf("num 16진수 : %p\t\t\tnum의 주소 : %p\n", num, &num);
	printf("num 10진수 : %d\t\t\tnum의 주소 : %d\n", num, &num);

	return 0;
}
void print_reference(int n) {
	printf("넘겨받은 값 16진수 : %p\t\t매개변수 n의 주소 : %p\n", n, &n);
	printf("넘겨받은 값 10진수 : %d\t\t매개변수 n의 주소 :  %d\n\n", n, &n);
}


응용 예시 swap

#include <stdio.h>

void swap(int*, int*);

void main() {
    int a = 10;
    int b = 20;
    int* ptr;
    
	printf("a:%d, b:%d\n",a,b);    
    swap(&a, &b);
	printf("a:%d, b:%d\n",a,b);
}

void swap(int* n1, int* n2) {
    int temp;
	// n1 == &a
    // *n1 == a == 10
        
    temp = *n1;
    *n1 = *n2;
    *n2 = temp;
}


배열포인터

#include <stdio.h>

void main() {
	int arr[3] = { 10,20,30 };
	int* ptr = arr;
	int anyVal;

	printf("arr\t: %p\n", arr);
	printf("&arr\t: %p\n\n", &arr);

	printf("arr[0]\t: %d\n", arr[0]);
	printf("arr[1]\t: %d\n", arr[1]);
	printf("arr[2]\t: %d\n\n", arr[2]);
	
	printf("&arr[0]\t: %p\n", &arr[0]);
	printf("&arr[1]\t: %p\n", &arr[1]);
	printf("&arr[2]\t: %p\n\n", &arr[2]);
	
	printf("ptr\t: %p\n", ptr);
	printf("&ptr\t: %p\n", &ptr);
	printf("&anyVal\t: %p\n\n", &anyVal);

	printf("*ptr\t: %d\n", *ptr);
	printf("*ptr+1\t: %d\n", *ptr+1);
	printf("*ptr+2\t: %d\n\n", *ptr+2);
	
	printf("*ptr\t: %d\n", *ptr);
	printf("*(ptr+1): %d\n", *(ptr+1));
	printf("*(ptr+2): %d\n\n", *(ptr+2));

	printf("ptr[0]\t: %d\n", ptr[0]);
	printf("ptr[1]\t: %d\n", ptr[1]);
	printf("ptr[2]\t: %d\n\n", ptr[2]);

	printf("&ptr[0]\t: %p\n", &ptr[0]);
	printf("&ptr[1]\t: %p\n", &ptr[1]);
	printf("&ptr[2]\t: %p\n\n\n", &ptr[2]);


    for (int i = 0; i < 3; i++) {
		printf("&arr[%d] : %p\tarr[%d] : %d\n", i, &arr[i], i, arr[i]);
	}

	for (int i = 0; i < 3; i++) {
		printf("&ptr[%d] : %p\tptr[%d] : %d\n", i, &ptr[i], i, ptr[i]);
	}
}


int arr[3] = {1, 2, 3};
int* ptr = arr;
  • 위 코드를 선언 했을 때 할당된 메모리 상의 변화 및 각 변수, 인덱스, 주소가 의미하는 값
    • arr (배열의 이름)
    • &arr
    • arr[0]
    • arr[1]
    • arr[2]
    • &arr[0]
    • &arr[1]
    • &arr[2]
    • ptr
    • &ptr
    • *ptr
    • ptr[0], ptr[1], ptr[2]


image-20210726181718511


자주 나는 에러

  • NullPointerException

    • Null 비어있다. 0 NULL 가리키는게 아무것도 없는 포인터 변수를 참조할 때.
    int *p = NULL
    printf ("%d", *p);
    
  • IndexOutOfRange (Bound) // 배열에서

    • 인덱스를 벗어났을 때



포인터의 다른 자료형들

  • 포인터 배열

    • char 형 배열 / int 형 배열 / double 형 배열
    • char*형 배열 / int*형 배열
    • int *p[3]
    • 배열의 요소로 ‘포인터 값’, 즉… 주소를 값으로 가지는 배열
    int* arr[5];
      
    int*
    int*
    int*
    int*
    int*
    


  • 배열 포인터

    • int (*p)[5]
    • 특정 크기 형식의 배열을 가리키는 포인터
    • char형 **, **int형 **, **struct ***, … **int [5] 형 포인터!
    • 참고
      
    
  • 함수 포인터

    • 반환타입 (*함수이름)(매개변수)
    • 참고
      
    
  • 구조체 포인터


연습문제


  • 호출한 함수에서 선언한 지역변수의 주소를 반환해 출력해보는 코드
#include <stdio.h>

int* add(int x, int y);

int main() {
	int a = 30, b = 50;
	int* sum;

	sum = add(a, b);

    printf("sum=%d\n", *sum); // 원칙적으로는 오류가 나야 한다!
							  // 함수 호출 이후 없어져버리는 주소이기 때문
	return 0;
}

int* add(int x, int y) {
	int result = x + y;
	return (&result);
}


  • 배열을 함수에 넘겨, 포인터 접근 방식으로 순차적으로 출력해보는 코드
#include <stdio.h>

void printArray(int*, int);

void main() {
	int nums[5] = {1,2,3,4,5};

	printArray(nums, sizeof(nums)/sizeof(nums[0])); // (&nums) 또는 (&nums[0])
}

void printArray(int* arr, int size) {
    
    // 인덱스 접근 방식
    for (int i=0; i<size; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    // 주소 접근 방식
    for (int i=0; i<size; i++) {
        printf("*arr+%d = %d\n", i, *arr+i);
    }
}
  • 포인터의 주소 출력, 배열의 주소출력, 인덱스별로 추소 증가시키며 출력해보기


과제 문제들…





© 2020.07. by Sanggoe

Powered by Sanggoe