Home > C > Basics > [C언어의 기본]포인터+배열

[C언어의 기본]포인터+배열

포인터+배열

포인터과 배열에는 관계가 있다

int* ptr;
int num;
ptr = #

이 구문을 보면 ptr은 num을 가리키고 있다.

int arr[3];
//ptr => arr[0]을 가리키고 있는 것이다.

가리킨다는 점에서 두개는 전부다 같다.

*ptr *arr은 가능하다. 변수와 상수의 차이점이라는 것이 존재할뿐.

즉 배열의 이름은 (상수형태)포인터를 말하는 것이다.

int main(void)
{
	int arr[3]={0, 1, 2};
	printf("배열의 이름: %p \n", arr);
	printf("첫 번째 요소: %p \n", &arr[0]);
	printf("두 번째 요소: %p \n", &arr[1]);
	printf("세 번째 요소: %p \n", &arr[2]);
	return 0;
	// arr=&arr[i]; //이 문장을 넣으면 컴파일 에러가 나옴: 상수라서 오류
}

이 예시를 보면 arr[3]라는 배열 0,1,2를 초기화 해준 후

각 출력값을 뽑아내면 배열의이름: 00122FF50

첫번째: 00122FF50 , 두번째:0012FF54…이렇게 나오는데

이 것을 보면 배열의 이름은 첫번째 요소의 주소값과 동일하다.

arr은 상수기 때문에 대입연산이 불가능하다. 가리키는 대상을 바꿀수 없다는 것이다.

비교 포인터변수 배열의이름
이름존재 존재함 존재함
무엇을 나타내고 저장 메모리의 주소 값 메모리의 주소 값
주소값의 변경 가능하다 불가능하다.
  • 배열요소간 주소 값의 크기는 4byte로 요소가 서로 붙어있다는 것을 알 수 있다.

double arr[1]=> double *상수다.

1차원 배열의 이름의 포인터형 배열이름 대상 *연산

int arr1[5]; //arr1은 int형 포인터 실수
double arr2[7]; //arr2는 double형 포인터 상수

즉, 1차원 배열이름의 포인터형은 배열의 이름이 가리키는 대상을 기준으로 결정된다.

int main(void)
{
	int arr1[3]={1, 2, 3};
	double arr2[3]={1.1, 2.2, 3.3};

	printf("%d   %g \n", *arr1, *arr2); //int %d / double %g 
	*arr1 += 100; //4byte
	*arr2 += 120.5; //8byte
	printf("%d   %g \n", arr1[0], arr2[0]);
	return 0;
}

포인터를 통해서 arr1의 첫번째 1+100을 해서 = 101

포인터를 통해서 arr2의 첫번째 1.1 + 120.5을 해서 = 120.6

+)추가로 생각해보기

int main(void)
{
	int arr[3] = { 1,2,3 };
	arr[0] += 5;
	arr[1] += 7;
	arr[2] += 9; //포인터를 대상으로 구성한 셈
	...
}

이렇게 하면 될까?

된다. arr은 int형 포인터 변수나 배열의 모든요소를 접근한 것으로

실제로 이렇게 쓸 수 있다. 포인터 변수 ptr을 대상으로 ptr[0], ptr[1]이 가능하다.

즉, 포인터를 배열의 이름처럼 사용할 수도 있다.

int main(void)
{
	int arr[3]={15, 25, 35};
	int * ptr=&arr[0];     // int * ptr=arr; 과 동일한 문장 //배열 접근가능

	printf("%d %d \n", ptr[0], arr[0]); //ptr[0]...도 가능하다.
	printf("%d %d \n", ptr[1], arr[1]);
	printf("%d %d \n", ptr[2], arr[2]);
	printf("%d %d \n", *ptr, *arr);
	return 0;
}

이 문장을 보면 포인터 수 변수를 배열의 이름처럼 사용하는 경우는 없지만 가능은 하다..!

포인터에 대한 정확한 지식을 넣고 가자.

포인터 연산

포인터 => *연산과 []연산이 있음.

위에 포인터가 *와 []이 있듯이!

int* P; = 0x01=[1]
P=+1
->0x01+4=0x05

포인터 에서는 0x01을 가리키는 곳에서 +1을 하면 0x02가 아니라

0x05가 된다.

왜? int형이니까 +4byte라서

int arr[3];
int*ptr=&arr; //둘다 타입이 일치되서 대입이 가능함. ptr은 변수고 arr는 상수니까 대입가능.

위 int *ptr = &arr을 거치면

arr[0] => 10 ~

arr[2] => 20 ~

arr[3] => 30 ~

*ptr=10이라는 것을 넣고 이후 ptr++를 하면? 4씩 증가

*ptr=20을 넣어주면 이후 20값 저장 후 ptr++을 하면? 4씩 증가…

이 상태에서 ptr -=2;를 하면?

2를 감소시키면 sizeof(int)*2만큼 값이 감소됨. (-8)

ptr은 다시 첫번째 요소를 가리키게 됨.

*ptr+=30을 하면 값이 40이 되면서 arr[n]이 바뀜.

포인터를 대상으로 하는 증가 및 감소 연산

int main(void)
{
	int* ptr1 = ...;
	int* ptr2 = ...;
	ptr1++;
	ptr1 += 3;
	ptr2 -= 5;
	ptr2 = ptr1 + 2;
	...
}

연산의 결과를 확인한다면?

int main(void)
{
	int * ptr1=0x0010;
	double * ptr2=0x0010;

	printf("%p %p \n", ptr1+1, ptr1+2); 
	printf("%p %p \n", ptr2+1, ptr2+2);
	//적절하지는 않음

	printf("%p %p \n", ptr1, ptr2);
	ptr1++; //4증가
	ptr2++; //8증가
	printf("%p %p \n", ptr1, ptr2);
	return 0;
}

이를 위해서 이 예제를 실행시켜보자!

00000014 00000018 //+4 / +8
00000018 00000020 //+8 / +16  // 16진수라서 0x0010+16 = 26이 아닌 0x0020
00000010 00000010 //기본 값 부르기
00000014 00000018 // 증가 후 기본 값 부름

이것을 보면 n x sizeoff(type)만큼 크기 증가/감소 된다는 사실을 알 수 있다.

위에는 int형이니까 값이 1증가할 때 마다 4씩 증가한다.

아래는 double형이니까 값이 1증가할 때 마다 8씩 증가한다.

연산 특성을 이용한 배열접근

int main(void)
{
	int arr[3]={11, 22, 33};
	int * ptr=arr;     // int * ptr=&arr[0]; 과 같은 문장

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

	printf("%d ", *ptr);  ptr++; //후열이라 출력 되고
	printf("%d ", *ptr);  ptr++;
	printf("%d ", *ptr);  ptr--;
	printf("%d ", *ptr);  ptr--;
	printf("%d ", *ptr);  printf("\n");
	return 0;
}

arr[3]배열 값이 11,22,33을 포인터로 지정

각 배열 값을 순서대로 출력한다. (단 ptr++로 후열에 있으니 출력되고 값이 +1되는 것.)

11,22,33

11,22,33,22,11이 된다.

이 예제를 근거로 연산의 차이를 알 수 있다.

*(++ptr)= 20; //저장된 값 자체를 변경
*(ptr+1)=20; // 값은 변경하지 않음.
int main(void)
{
	int arr[3] = { 11,22,33 };
	int* ptr = arr;
	printf("%d %d %d \n", *ptr, *(ptr + 1), *(ptr + 2));
	...
}

arr[i]=*(arr+i)다.

여기서 ptr은 arr값과 출력 결과가 동일하다.

배열의 이름과 포인터 변수는 상수 변수차이만 있을 뿐 동일하기 때문

문제

문제1: 포인터를 이용한 배열 접근
길이가 5 int형 배열 arr을 선언하고 1,2,3,4,5로 초기화 후
배열의 첫번째 요소를 가리키는 포인터 변수 ptr선언
ptr저장된 값을 증가시키는 연산을 기반으로 배열요소접근
값을 2증가시키고 정상적 증가이루어졌는지 확인하라.
int main(void)
{
	int arr[5] = { 1,2,3,4,5 };
	int* ptr = arr;
	int i;
	printf("기본 값은 : %d 입니다 \n", ptr[0]);

	for (i = 0; i < 5; i++) //0부터 시작해서 5개까지 1증가
	{
		*ptr += 2; //true면 2누적
		ptr++; //ptr 값 상승 //다음배열로
	}
	for (i = 0; i < 5; i++) //출력 반복문
		printf("%d \n", arr[i]);

	return 0;
}

반복문을 통해서 각 배열의 순서대로 접근하면서 2을 누적시킨다.

그리고 출력반복문을 통해서 각 배열을 순차적으로 출력한다.

문제2:문제1에서 ptr 값을 변경시켜서 배열접근하라.
이번에는 값 변경 불가능 하고 
덧셈연산하여 값 2씩 증가하는 코드
int main(void)
{
	int arr[5] = { 1,2,3,4,5 };
	int* ptr = arr;
	int i;
	printf("기본 값은 : %d 입니다 \n", ptr[0]);

	for (i = 0; i < 5; i++) //0부터 시작해서 5개까지 1증가
	{
		*(ptr+i) +=2 ; // 축약 arr[i] = *(arr+i)
	}
	for (i = 0; i < 5; i++) //출력 반복문
		printf("%d \n", arr[i]);

	return 0;
}

기본적인 코드는 문제1의 코드를 따라간다.

포인터변수 ptr의 값을 변경시키지 않고 ptr자체에 덧셈연산을 해야되기에

*ptr +=2;*(ptr+i)+=2로 변경해준다.

문제3:
길이 5 int형 배열 arr 선언
이를 1,2,3,4,5 초기화
마지막 요소 가리키는 포인터변수 ptr선언
ptr 값을 감소시키는 형태 연산
모든 배열 요소 접근하여 배열에 정수 더하기
int main(void)
{
	int arr[5] = { 1,2,3,4,5 };
	int* ptr = &arr[4];
	int total = 0, i; //포인터 0 ,null 초기화

	for (i = 0; i < 5; i++)
		total += *(ptr--); //포인터의 감소형 ptr[4] [3] ..

	printf("%d", total); //역순으로 5+4+3+2+1로 더해진것
	return 0;
}

주의 할점은 마지막 요소를 가리키는 포인터변수 ptr을 선언해야되니까 직접적으로 지시해준다.

&arr[4]를 지정시켜 마지막 요소를 가리킨다.

그후 ptr 값을 감소시키는 형태의 연산을 진행하니 누적변수를 통해서 포인터 감소형을 해준다.

즉 ptr[4]..[3]..[2]. ..그후 역순으로 합친 값을 출력한다.

문제4:
길이가 6인 int형 arr을 선언
이를 1,2,3,4,5,6으로 초기화
배열 저장된 값 순서가 6,5,4,3,2,1이; 되도록 변경
배열의 앞과 뒤를 가리키는 포인터 변수 두개를 선언해서 활용
int main(void)
{
	int arr[6] = { 1,2,3,4,5,6 };
	int* sptr = &arr[0];
	int* eptr = &arr[5];
	int a, b ;

	for (a = 0; a < 3; a++) //3번 이후는 역전이라 3개까지만
		b = *sptr; //b에 1저장
		*sptr = *eptr; // sptr에 6저장
		*eptr = b;// eptr에 1저장
		sptr += 1; //1누적증가 
		eptr -= 1; //1누적감소

for (a = 0; a < 6; a++)
	printf("%d", arr[a]);
return 0;
}

아까처럼 반대로 뒤집기를 해야되고 위치 변경 후

각각 가리키는 곳이 반대인 포인터 변수 2개 선언 (Start ptr , end ptr)

시작지점을 sptr에 끝지점을 eptr에 지정해줌.

b에 sptr이 가리키는 1값 / sptr에 eptr 가리키는 값 6 / eptr에 b가 가리키는 값 1 을 넣음.

eptr과 sptr이 교환 됨.

그후 sptr에 1누적증가 / eptr에 1누적감소를 시킴.

1. 초기 = {1,2,3,4,5,6}
2. 첫 번째 반복 진행 = {6,2,3,4,5,1}
3. 두 번째 반복 진행 = {6,5,4,3,2,1}
4. 결과 = {6,5,4,3,2,1}

중요) *sptr에 *eptr을 했는데 왜 str+=1하면 1부터 증가인가?

왜냐하면 *sptr = *eptr을 통해서 값을 ‘복사’된 것…

즉 값을 따온거지 그 자체의 값을 바꾼 것이 아님.

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

포인터는 1.변수 2.배열이 있다.

[문자열 가공]
char str[20] = "abcd" 
? str="def" - 불가능
= def str이라는 상수에 저장해라 (상수라서  변환 불가)
[문자열 비가공 상수형]
char*str = "abcd"
 경우는 가능하다.
why? [] 없는데? =>메모리 준비할 필요 x
abdc 메모리 자동활당됨

위쪽 Str은 그 자체로 문자열 전체를 저장한다.

아래 Str은 메모리 상 자동으로 저장된 문자열 첫번째 문자 가르키기.

위는 계속해서 저장된 위치이다.

아래는 다른 위치로 변동이 가능하다는 점이 다르다.

char * str = "Yout Team"
str = "Our team"; //str이 가리키는 대상을 문자열 Out team 변경
//가능은 한데 되도록 작성을 지양하자.

변수형태의 문자열이라는 것이다.

이렇게 쓰면 헷갈리게 되니까 지양하는 것이다.

반대는 상수형태의 문자열

int main(void)
{
	char str1[]="My String"; //변수형태
	char * str2="Your String"; //상수형태
	printf("%s %s \n", str1, str2);

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

	str1[0]='X'; //성공
	str2[0]='X'; //실패
	printf("%s %s \n", str1, str2);
	return 0;
}

변수형태와 상수형태에 대한 가리키는 대상 변경을 시도.

성공과 실패로 나뉜다.

이는 컴파일러마다 결과값이 다르긴한데 바꾸기는 가능하다.

하지만, 특정 컴파일러를 사용해야되는 코드작성은 올바르지 못하다.

어디서든 선언할 수 있는 상수형태의 문자열

char *str = "Const String"; (자동할당)
char * str = 0x1234; //이렇게 저장된다. -> 주소값

printf("show your string"); //함수 호출과정에서 선언되는 문자열
printf("0x1234"); //호출을 하면 주소값이다.
//printf함수는 문자열 통쨰로 받는 게 아니라 주소값 전달받음

Whoareyou("Hong"); //whoareyou함수 호출
void whoareyou(char * str) {....}
//실제로 전달되는 주소는 H의 주소값 이다.

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

int arr[3]; -> int *arr[3]이다.

포인터배열의 이해

int * arr1[20]; //길이 20 int형 포인터 배열 arr1
double * arr2[30];//길이 30 doulb형 포인터 배열 arr2
int main(void)
{
	int num1=10, num2=20, num3=30;
	int* arr[3]={&num1, &num2, &num3}; //5행 선언한 변수로 주소값 초기화

	printf("%d \n", *arr[0]);
	printf("%d \n", *arr[1]);
	printf("%d \n", *arr[2]);
	return 0;
}

이 구문을 통해서 *arr[0]을 통해서

각각 값이 10,20,30이 출력된다.

문자열을 저장하는 포인터배열

char*strArr[3];길이가 3인 char형 포인터배열

int main(void)
{
	char * strArr[3]={"Simple", "String", "Array"}; 
	//char형 3개 [3]넣음 각각 Simple = 7byte / String= 7byte / Array=6byte
	//메모리 simple -> 0x.. -> [0] -> 0x...
	//char strArr={0x1004, 0x1048, 0x2102]
	//strArr[0]->simple \0 strArr[1]->String \0 strArr[2] -> Array \0

	printf("%s \n", strArr[0]);
	printf("%s \n", strArr[1]);
	printf("%s \n", strArr[2]);
	return 0;
}

char형 이기 떄문에 글자수 +1(\0)을 계산한다. 7/7/6

그러면 메모리를 보면 simple이 0x123124이 되고 이를 [0]이고 이것이 또 0x..가된다.

즉 strArr={0x1104, 0x1048, 0x2102}가 된다.

각각 값은 입력한 값에 \0가 붙은 것으로 저장된다.

포인터배열에 대한것은 여기까지이다.

각 챕터별로 하면 올바른 분량이 나오는 것같다.

다음은 포인터와 함수에 대한 이해이다.