C 이론(19)

Hi Beck·2023년 6월 14일

TIL-Language(C & C++)

목록 보기
24/34

포인터 강의 복습

1.(* pc)++; 주의하기

(* pc )++; == 포인터변수 pc 안에 있는 데이터에 1을 더한다

  • pc++; == 포인터변수 pc의 주소값을 증가시키고 가리키는 애를 가져오는것

++가 우선순위가 되니깐 항상 대상에 따라 괄호를 쳐줘서 괄호가 우선순위가 되도록하자

2.포인터변수를 꼭 초기화해주자 -> NULL로
int p;처럼 초기화를 안해주면 포인터변수 자체엔 주소값이 아닌 쓰레기값이 들어있는데 이때 p = 100;처럼 값을 넣어주면 어딘지도 모르는 주소에다가 값을 넣어주는 것과 똑같음.그래서 NULL로 포인터의 유효성 여부 판단을 하기 쉽도록 초기화 해줘야한다.

3.포인터 변수에서의 ++
포인터변수에 대하여 ++ 하는 것은 주소값을 더해주는 것과 같다.
이때 주소값은 '자기 자료형에 대한 주소값','자료형의 바이트 크기'와 같다.
-> '1'이 더해지는게 아니다!

ex)
pc++ -> 주소값에 +1byte(char형이니깐)
pi++ -> 주소값에 +4byte
pd++ -> 주소값에 +8byte

주소값을 더해준다는 의미는?
++한다는거는 주소를 더해서 범위로 늘려가지고 있는다는 의미가아니라
'새로운 다른 주소를 가리킨다'라는 의미다.

새로운 다른 주소로 가리킨다는건 뭔가 새로운 값을 덮어씌울때 사용되는것이며, 대표적인 예로 반복문에서 많이 쓰인다.

4.포인터 변수를 쓸 땐 항상 NULL 체크를 하고 쓰자(프로그래머들이 많이하는 실수)

예시)

if( p1 != NULL )
printf("p1이 NULL이 아님\n");

else

이런식으로 검사하기 아니면 초기화를 해주던가

5.간접 참조 연산자와 증감 연산자
대입식과 괄호의 유무에 따라 달라진다.
식을 두그룹으로 묶어주면 좀 쉬움(우선순위 : p > = > ++)

포인터 형식지정자 : %p(값이 아닌 주소를 표현한다)

v = * p++
-> p가 가리키는 값을 v에 대입한 후에 p의 주소값을 증가한다

v = (* p)++
-> p가 가리키는 값을 v에 대입한 후에 가리키는 값을 증가한다

v = * ++p
-> p의 주소값을 증가시킨 후 그 주소가 가리키는 값을 v에 대입한다.

v = ++ * p
-> p가 가리키는 값을 가져온 후에 그 값을 증가하여 v에 대입한다.

예시)

int i = 10;
int * pi = &i;

printf("i = %d, pi = %p\n", i, pi);
(* pi)++;
printf("i = %d, pi = %p\n", i, pi);
printf("i = %d, pi = %p\n", i, pi);

  • pi++;
    printf("i = %d, pi = %p\n", i, pi);

여기서 내가 실수한게 있는데 * pi++;는 바로 밑에 있는 프린트에프문에 끼치는 영향은 증가된 주소값 뿐이다.

  • pi++;는 pi주소값을 증가시키고 그게 가리키는 값을 가져오는건데 로 가져왔다고해서 코드에 남아있는게아니라 어딘가 변수에 저장하거나 해야 남아있고 다음줄가면 또 새로운 코드임. 밑에 printf에서 변수를 pi로 하지않는 이상은 가리키는 애가 출력되진않는다.
    변수로 pi를 썼으니 증가된 주소값만 출력되는것이다.

-> 코드의 유효성과 구분을 똑바로 하자.코드 한줄을 하나의 결과값으로 생각하지말자
출력되는 변수와 대응되는 요소인지를 확인하자

  1. 포인터변수 대신에 배열이름을 넣어서 활용할 수 있다.
    이유: 배열의 이름이 포인트 변수랑 똑같은 역할을 해서

예시)
int a[] = { 10 20 30 40 50 }

printf("a = %u\n", a); //배열의이름이 첫주소값이니 그대로 출력

printf("a + 1 = %u\n", a + 1); //배열의 첫주소에 + 4바이트한 주소를 출력

printf(" a = %d\n", a);
//포인터변수 대신 배열이름을 넣어 첫주소가 가리키는 애의 데이터를 출력

printf(" (a+1) = %d\n", (a+1));
//배열의 첫주소값에 4바이트 더한 주소값을 가리키고 즉 배열의 두번째 원소 주소값에 있는 값을 가져와서 출력

return 0;

결과
a=1245008
a+1=1245012

  • a = 10
  • (a+1) = 20

7.반대로 포인터를 배열처럼 사용할 수도 있다.
(포인터도 배열과 똑같은 문법으로 쓰일 수 있다.)

메모리에 접근하고 읽어오거나 변경하는 법은 2가지 이름, 주소값인데 주소값으로 하는법이 이전에는 배열이름가지고 하는거엿다면 새로운 방법으로 포인트 변수가 추가된 것이다.
포인트 변수도 곧 주소값을 가리키는 애니깐 가능한 것!

예시
int a[] = { 10, 20, 30, 40, 50 };
int * p;

p = a;
printf("a[0]=%d a[1]=%d a[2]=%d \n", a[0], a[1], a[2]);
printf("p[0]=%d p[1]=%d p[2] %d \n\n", p[0], p[1], p[2]);

p[0] = 60;
p[1] = 70;
p[2] = 80;

printf("a[0]=%d a[1]=%d a[2]=%d \n", a[0], a[1], a[2]);
printf("p[0]=%d p[1]=%d p[2]=%d \n", p[0], p[1], p[2]);

8.배열 인덱스 표기법과 포인터의 차이

반복문을 이용한 예시

ㄱ. 배열 인덱스 표기법
for(i=0;i<n;i++)
sum += a[i];

ㄴ. 포인터 사용시
for(i=0;i<n;i++)
sum += * p++;

위랑 똑같은게 포인터 사용시 포인터변수가 가리키는 애의 값을 누적시켜 합치고 매번 P의 주소를 바이트만큼 추가시키는데 이 말은 곧 배열의 원소마다 접근하는것과 같음.

포인터가 인덱스 표기법보다 빠르다(원소의 주소를 계산할 필요가 없어서)

9.포인터와 함수의 관계(-> 포인터를 함수에 넣는 방법)

예시로 변수 2개의 값을 바꾸는 작업을 해주는 함수인 swap이라는게 있는데 이 함수를 이용하여 포인터가 함수에서 어떻게 쓰이는지 보겠다.

ㄱ.일반적인 경우
보면 함수정의문에서 매개변수로 정수형 변수를 2개 받아 바꾼다.그리고 메인 함수에서 swap를 호출할 때 정수형 변수 a와 b를 인자로 받았음
-> 호출하면 값이 바뀔 것 같지만 바뀌지않는다.이유는 이름으로 메모리에 접근하는것은 지역변수에 영향을 받기때문에 함수정의문을 벗어나면 사라지기때문이다.

#include <stdio.h>
void swap(int, int);

int main(void)
{
int a = 100, b = 200;
printf("main() a=%d b=%d\n",a, b);

swap(a, b);

printf("main() a=%d b=%d\n",a, b);
return 0;

}

void swap(int x, int y)
{
int tmp;

printf("swap() x=%d y=%d\n",x, y);

tmp = x;
x = y;
y = tmp;

printf("swap() x=%d y=%d\n",x, y);
}

ㄴ. 포인터를 사용한 경우

포인터를 함수에 적용할 땐, 함수정의문의 매개변수에 포인트변수를 넣어주면된다.
매개변수에 포인트변수가 들어있으니 호출문 인자에 포인트변수가 원하는 주소값(배열이름이나 엔드연산자 이용)이 들어가면된다.
코드를 보면 이전에 x 대신 int * px라는 포인터변수를 넣어줌 그러고 출력하면 가리키는 a의 값인 100이 나올것이다.(y도 마찬가지) 그리고 이전처럼 값을 2개 바꾸는 로직을 거치면 메모리에 직접 접근했기때문에 함수정의문을 벗어나도 안바뀌고 호출문에서 제대로 바꿔서 출력되는것을 확인할 수 있다.

#include <stdio.h>
void swap(int, int);

int main(void)
{
int a = 100, b = 200;
printf("main() a=%d b=%d\n",a, b);

swap(&a, &b);

printf("main() a=%d b=%d\n",a, b);
return 0
}

void swap(int px, int py)
{
int tmp;
printf("swap() px=%d py=%d\n", px, py)

tmp = * px;

  • px = * py;
  • py = tmp; printf("swap() px=%d py=%d\n", px, py);
    }
  1. 2개 이상의 결과를 반환

포인터변수의 장점 중 하나가 2개 이상의 결과를 반환할 때 쓰일 수 있다는것이다.
(로직 신경쓰지말고 여기선 용도만 보기)

예시)
#include <stdio.h> // 기울기와 y절편을계산
int get line parameter get_line_parameter(int x1 , int y1 , int x2 , int y2 , float slope , float yintercept)

{
if( x1 == x2 )
return -1;
else {

  • slope = (float)(y2 - y1)/(float)(x2 - x1);
  • yintercept = y1 - ( slope) x1;
    return 0;
    }
    }

int main(void)
{
float s,y;
if( get_line_parameter(3, 3, 6, 6, &s, &y) == -1 )
printf("에러\n");
else
printf("기울기는 %f, y절편은 %f\n", s, y);

return 0;
}

이렇게 함수정의문을 보면 포인터변수2개가 매개변수로 들어간 코드를 볼 수 있는데, main에서 어디서 받아왔는지를 보면, main에선 s와 y를 초기화 없이 주소값만 넘긴걸 볼 수 있다.

이런 경우는 바로 "함수 내부에서 이 인자를 받고 해당 포인터 변수에 대해서 로직을 거쳐서 반환값을 2개 완성시켜줘" 이건데 이게 뭔말이냐 사실상 로직에 필요한 인자는
3,3,6,6이다. 인자는 &s랑 &y자나.얘네둘은 쓰기위해 보낸게 아니라 반환값을 2개로 받기위해 쓰인 요소들이다. 반환값인 return은 값 하나 밖에 못받는다.(함수정의문 보면 int형 하나임)이렇게 주소값 인자를 2개 쓰면 반환값을 2개 받을 수 있다.

좀 더 쉽게 말하자면
main에선 s와 y가 한게 아무것도 없고 함수 내에서 s와 y에 대한 주소값을 가져와서 slope와 yintercept값을 변경시켜주고(포인터 변수니깐 가능) 그걸 찍어준 것이다.

  1. 배열이 함수 인수인 경우

매개변수로 배열을 쓸 때 배열 대신 포인터 변수로도 쓸 수 있다.
인자가 배열이름 즉 주소값이기 때문이다.

예시)

#include <stdio.h>
void sub(int b[], int n);

int main(void)
{
int a[3] = { 1,2,3 };
printf("%d %d %d\n", a[0], a[1], a[2]);
sub(a, 3);
printf("%d %d %d\n", a[0], a[1], a[2]);
return 0;
}

void sub(int b[], int n)
{
b[0] = 4;
b[1] = 5;
b[2] = 6;
}

int b[]를 int * b[]로 쓸 수 있다는 소리다.

  1. 포인터 변수의 장점은 결국 함수에다가 주소값 넘겨서 MAIN에 있는걸 변경할 수 있다는 것! = 함수 내부에서 외부의 변수의 값을 바꾸고 싶을때 사용
profile
Emotional realizer

0개의 댓글