Chapter 09 배열과 포인터
스택 메모리와 주소 포인터 배열과 포인터의 관계
1. 스택 스택의 이해 한쪽 끝이 막혀있는 구조 가장 먼저 들어간 것이 가장 나중에 나옴 : LIFO(Last In First Out) 용어 top(탑) : 가장 마지막에 들어간 데이터의 위치를 가리킴 push(푸쉬) : 데이터를 넣는 것 pop(팝) : 데이터를 빼는 것
1. 스택 배열로 스택 만들기 자동차 5대가 들어가지만 한쪽이 막힌 터널 만들기 초기화
1. 스택 ‘자동차 A’를 넣기 top 값 : 0 1 top 위치 : stack[0] stack[1] ‘자동차 B’와 ‘자동차 C’를 터널에 넣기 top 값 : 1 2 3 top 위치 : stack[0] stack[1] stack[2]
1. 스택 자동차 1대 빼기 top 값 : 3 2 top 위치 : stack[2] stack[1]
[기본 9-1] 스택의 구현 예 ① 01 #include <stdio.h> 02 03 int main( ) 04 { 05 char stack[5]; 06 int top=0; 07 08 stack[top] = 'A'; 09 printf(" %c 자동차가 터널에 들어감\n", stack[top]); 10 top ++; 11 12 stack[top] = 'B'; 13 printf(" %c 자동차가 터널에 들어감\n", stack[top]); 14 top ++; 15 16 stack[top] = 'C'; 17 printf(" %c 자동차가 터널에 들어감\n", stack[top]); 18 top ++; 19 20 printf("\n"); 21 ---스택과 top의 초깃값을 선언한다. ---스택에 값이 들어간 후 top값이 1만큼 증가했다. ---스택에 값이 들어간 후 top값이 1만큼 증가했다. ---스택에 값이 들어간 후 top값이 1만큼 증가했다.
[기본 9-1] 스택의 구현 예 ① 22 top --; 23 printf(" %c 자동차가 터널을 빠져나감\n", stack[top]); 24 stack[top] = ' '; 25 26 top --; 27 printf(" %c 자동차가 터널을 빠져나감\n", stack[top]); 28 stack[top] = ' '; 29 30 top --; 31 printf(" %c 자동차가 터널을 빠져나감\n", stack[top]); 32 stack[top] = ' '; 33 } ---top값을 1씩 줄이면서 스택 에서 값을 하나씩 빼낸다. ---top값을 1씩 줄이면서 스택 에서 값을 하나씩 빼낸다. ---top값을 1씩 줄이면서 스택 에서 값을 하나씩 빼낸다.
1. 스택 오류 발생 경우와 대처 top 값이 0일 때, 자동차를 빼라고 명령하는 경우 오류 발생 8~10행, 12~14행, 16~18행 : 오류 대비 소스 추가
1. 스택 오류 발생 경우와 대처 top 값이 5일 때, 자동차를 넣으라고 명령하는 경우 오류 발생 22~24행, 26~28행, 30~32행 : 오류 대비 소스 추가
[응용 9-2] 스택의 구현 예 ② 01 #include <stdio.h> 02 03 int main( ) 04 { 05 char stack[5]; 06 int top=0; 07 08 char carName = 'A'; 09 int select=9; 10 11 while (select != 3) 12 { 13 printf("<1> 자동차 넣기 <2> 자동차 빼기 <3> 끝 : "); 14 scanf_s("%d", &select); 15 ---자동차 이름을 A부터 시작한다. ---사용자가 선택할 작업을 입력할 변수다. ---사용자가 3을 선택하지 않으면 while문을 반복한다. ---사용자가 선택하는 값이다.
[응용 9-2] 스택의 구현 예 ② 16 ____①____ 17 { 18 case 1: 19 if ( ___②___ ) 20 { printf("터널이 꽉 차서 차가 못 들어감\n"); } 21 else 22 { 23 stack[top] = carName++ ; 24 printf(" %c 자동차가 터널에 들어감\n", stack[top]); 25 top ++; 26 } 27 break; 28 -----사용자가 1(넣기)을 선택하면 실행한다. ------이미 터널에 5대가 있다면 더 못 들어간다. ------빈 곳이 있을 경우 (5대 미만이 있으면) 자동차를 넣고, top을 1 증가시킨다. ------switch문을 벗어난다.
[응용 9-2] 스택의 구현 예 ② 29 case 2: 30 if ( ) 31 { printf("빠져나갈 자동차가 없음\n"); } 32 else 33 { 34 top --; 35 printf(" %c 자동차가 터널에서 빠짐\n", stack[top]); 36 stack[top] = ' '; 37 } 38 break;. 39 40 case 3: 41 printf("현재 터널에 %d대가 있음.\n", top); 42 printf("프로그램을 종료합니다.\n");. 43 break; 44 45 default : 46 printf("잘못 입력했습니다. 다시 입력하세요. \n"); 47 } 48 } 49 } ----사용자가 2(빼기)를 선택하면 실행된다. ------터널에 자동차가 한 대도 없다면 빼낼 차가 없다. ------빼낼 자동차가 있으면 (1대 이상이 있으면) top을 1 감소하고 자동차를 빼낸다. 그리고 빠져 나간 자리를 빈 칸으로 채운다. ---사용자가 3(끝)을 선택하면 현재 자동차 수를 출력하고 종료한다. ---사용자가 1, 2, 3 외의 값을 입력하면 처리된다.
[응용 9-2] 스택의 구현 예 ②
1. 스택 ① 자동차 넣기 : 19행~27행 top이 5라면 top이 5미만 ② 자동차 빼기 : 30행~38행 더 이상 자동차가 들어가지 못한다는 메시지를 출력 break 문에 의해 밖으로 나간 후 다시 while 문으로 이동 top이 5미만 스택에 현재 자동차를 넣은 후에 자동차 이름을 하나 증가시킴 들어간 자동차 이름을 출력한 후 top을 증가시킴 switch 문을 빠져나감 ② 자동차 빼기 : 30행~38행 top이 0이라면 더 이상 빼낼 자동차가 없다는 메시지를 출력 top이 0보다 크면 top 값을 감소시킴 빠져나간 자동차 이름을 출력한 후, 자동차가 빠진 곳에 공백(‘ ’)을 채움
1. 스택 ③ 끝 : 41~43행 그 외의 값 입력 시 default: 부분 수행 현재 터널에 들어있는 자동차의 대수(top) 출력 프로그램을 종료한다는 메시지 출력 switch 문을 빠져나간 후 while 문으로 이동 select가 3이므로 while 문을 종료하며 프로그램을 빠져나옴 그 외의 값 입력 시 default: 부분 수행 잘못 입력되었다는 메시지 출력
2. 메모리와 주소 메모리 구조와 주소 정수형 변수의 메모리 할당 메모리는 바이트(Byte) 단위로 나뉘며, 각 바이트에는 주소가 지정됨. 정수형 변수의 크기는 4바이트이므로 이 메모리에 정수형 변수 a를 선언하면 임의의 위치에 4바이트가 자리잡음. 변수가 위치하는 곳 : 주소(address) 변수의 주소를 알려면 변수 앞에 ‘&’를 붙임 a의 주소(&a) = 1036번지, b의 주소(&b) = 1040번지
[기본 9-3] 변수의 주소 알아내기 01 #include <stdio.h> 02 03 int main( ) 04 { 05 int a = 100; 06 int b = 200; 07 08 printf("변수 a의 주소는 %d 입니다. \n", &a); 09 printf("변수 b의 주소는 %d 입니다. \n", &b); 10 } ---a와 b의 주소를 출력한다.
2. 메모리와 주소 정수형 배열의 메모리 할당 배열의 주소 표현 배열 이름 aa = 전체 배열의 주소 = 1031번지 aa[0]의 주소(&aa[0]) = 1031번지 aa[1]의 주소(&aa[1]) = 1035번지 aa[2]의 주소(&aa[2]) = 1039번지 배열 이름 aa = 전체 배열의 주소 = 1031번지 배열 aa의 주소를 구할 때는 ‘&’를 쓰지 않고, 단순히 ‘aa’로 표현
[기본 9-4] 정수형 배열의 메모리 할당 ① 01 #include <stdio.h> 02 03 int main( ) 04 { 05 int aa[3] = {10, 20, 30}; 06 07 printf("aa[0]의 값은 %d, 주소는 %d \n", aa[0], &aa[0]); 08 printf("aa[1]의 값은 %d, 주소는 %d \n", aa[1], &aa[1]); 09 printf("aa[2]의 값은 %d, 주소는 %d \n", aa[2], &aa[2]); 10 printf("배열 이름 aa의 값(= 주소)는 %d \n", aa); 11 } ---배열의 각 자리 값과 주소를 출력한다. ---배열 이름(aa 주소)을 출력한다.
2. 메모리와 주소 배열의 이름 활용법 aa 값을 1031로 가정하고, aa+1을 계산한 결과는 무엇일까? 계산 과정 예상결과 : aa+1 1031 + 1 = 1032 (X) 실제 결과: aa+1 1031 + 4 = 1035 (O) 계산 과정 ‘+1’의 의미 : 배열 aa의 위치에서 한칸 건너뜀 한칸 : aa가 정수형 배열이므로 4byte 즉, aa+1 = &aa[1] = 1035
[응용 9-5] 정수형 배열의 메모리 할당 ② 01 #include <stdio.h> 02 03 int main( ) 04 { 05 int aa[3] = {10, 20, 30}; 06 07 printf("&aa[0]는 %d, aa+0은 %d \n", ___①__ , aa+0); 08 printf("&aa[1]는 %d, aa+1은 %d \n", &aa[1], ___②__ ); 09 printf("&aa[2]는 %d, aa+2는 %d \n", &aa[2], ___③__ ); 10 } ---&aa[0]= =aa+0 ---&aa[1]= =aa+1 ---&aa[2]= =aa+2
3. 포인터 포인터의 기본 개념 포인터란 주소를 담는 그릇(변수) 포인터 선언 : * 를 붙여줌 포인터 변수 p에는 변수의 주소가 들어감
3. 포인터 ➊ char ch : 1byte를 차지하므로, 주소 1031번지에 1byte 자리잡음 ➋ char* p : 1032~1035번지까지 4byte 자리잡음 ➌ ch = ‘A’ : 변수 ch에 ‘A’ 값 넣기 ➍ p = &ch : &ch는 1031번지 이므로 포인터 변수 p에는 1031이 들감
[기본 9-6] 일반 변수와 포인터 변수의 관계 01 #include <stdio.h> 02 03 int main( ) 04 { 05 char ch; 06 char* p; 07 08 ch = 'A'; 09 p = &ch; 10 11 printf("ch가 가지고 있는 값: ch = = > %c \n", ch); 12 printf("ch의 주소(address): &ch = = > %d \n", &ch); 13 printf("p가 가지고 있는 값 : p = = > %d \n", p); 14 printf("p가 가리키는 곳의 실제값 : *p = = > %c \n", *p); 15 } ---문자형 변수와 포인터 변수를 선언한다. ---문자 'A'를 ch에 대입하고 ch의 주소를 p에 대입한다.
3. 포인터 5행~6행 : 문자형 변수 ch, 포인터 변수 p를 선언 8행 : ch에 A를 대입 14행 : ‘*p’는 ‘p에 저장된 주소(9042587)가 가리키는 곳의 실제값’임. 따라서 9042587번지에 들어 있는 ‘A’가 출력
3. 포인터 ➊ 포인터 변수를 선언 방법 : 변수형 * - 정수형 : int*, 문자형 : char*, 실수형 : float* ➊ 포인터 변수를 선언 방법 : 변수형 * - 정수형 : int*, 문자형 : char*, 실수형 : float* ➋ char* p; 선언 시 p에는 문자형 변수의 주소값을 넣어야 함 int* p; 선언 시 p에는 정수형 변수의 주소값을 넣어야 함 통일시키지 않아도 컴파일은 가능, 논리적으로 옳지 않으므로 주의 필요
[응용 9-7] 포인터의 관계 이해하기 01 #include <stdio.h> 02 03 int main( ) 04 { 05 char ch; 06 char* p; 07 char* q; 08 09 ch = ___①___ ; 10 p = &ch ; 11 12 q = p; 13 14 ___②___ = 'Z'; 15 16 printf("ch가 가지고 있는 값: ch = = > %c \n\n", ch); 17 } ---문자형 변수 ch를 선언한다. ---문자형 포인터 변수 p와 q를 선언한다. ---ch에 문자를 대입한다. ---ch의 주솟값을 p에 대입한다. ---p의 값을 q에 대입한다. ---q가 가리키는 곳의 실제값을 변경한다.
3. 포인터 5행~7행 : 문자형 변수 ch와 문자형 포인터 변수 p와 q를 선언 9행 : ch에 ‘A’를 대입(➊) 10행 : ch의 주솟값(&ch, 1031번지)을 포인터 변수 p에 대입(➋) 12행 : p의 값을 q에 대입(➌)
3. 포인터 14행 : “q가 가리키는 곳의 실제값을 Z로 변경하라”는 의미 q가 가리키는 곳은 1031번지의 실제값이므로 ‘A’가 ‘Z’로 변경 16행 : ch를 출력하면 ‘Z’가 출력
4. 배열과 포인터의 관계 문자형 배열과 포인터 I T - C o K B k \0 문자 하나 표현 : char 문자열 표현 : s[12] char* p = s : 배열 s와 포인터 p 호환 가능 s[3] == *(p+3) I T - C o K B k \0
[기본 9-8] 문자열 배열과 포인터의 관계 ① 01 #include <stdio.h> 02 03 int main( ) 04 { 05 char s[8]= "Basic-C"; 06 char *p; 07 08 p = s; 09 10 printf("&s[3] = = > %s\n", &s[3]); 11 printf("p+3 = = > %s\n\n", p+3); 12 13 printf("s[3] = = > %c\n", s[3]); 14 printf("*(p+3) = = > %c\n", *(p+3)); 15 } ---문자형 배열을 선언하고 초깃값을 대입한다. ---문자형 포인터 변수를 선언한다. ---p에 배열 s의 주소를 대입한다. ---문자열과 포인터의 주솟값을 %s로 출력한다. ---문자와 포인터의 실제값을 %c로 출력한다.
4. 배열과 포인터의 관계 5행 : 문자열 배열 s 선언, ‘Basic-C’ 문자열로 초기화 6행 : 문자형 포인터 변수 p 선언 8행 : p에 배열 s의 주소값 s 대입 10행 : 배열 s와 포터 p 모두 1016번지를 가리킴 &s[3] : i 값을 저장하고 있는 주소값 = 1019번지 널값을 만날 때까지 계속 출력 ic-C 출력
4. 배열과 포인터의 관계 11행 : p+3 - 1016에서 3칸을 건너뜀 1019번지 1019번지에 i 값이 저장되어 있으므로 ic-C 출력 13행 : s[3] 출력 i 출력 14행 : *(p+3) - p에서 3칸 더 간 주소(1019번지)의 실제값 i 출력
[응용 9-9] 문자열 배열과 포인터의 관계 ② 01 #include <stdio.h> 02 03 int main( ) 04 { 05 char s[8]= "Basic-C"; 06 char *p; 07 int i; 08 09 p = __①___ ; 10 11 for( i=sizeof(s)-2 ; i>=0 ; i--) 12 printf("%c", *( __②___ ) ); 13 14 printf("\n"); 15 } ---포인터 변수에 배열 주소를 대입한다. ---문자형 배열의 끝부터 배열의 개수만큼 반복한다. -----포인터 변수가 가리키는 곳의 문자 하나를 출력한다.
4. 배열과 포인터의 관계 문자열 배열과 포인터의 응용 5행 : 문자열 배열 선언, ‘Basic-C’로 초기화 6행 : 포인터 변수 p 선언 9행 : 배열 s의 이름을 p에 대입 11행 : i의 초기값을 ‘배열크기-2’로 대입 i가 0보다 크거나 같은 동안 반복(6~0까지 7회 반복) 12행 : (p+i)의 실제값 출력 *(p+i) (p+6) (p+5) (p+4) (p+3) (p+2) (p+1) (p+0)
4. 배열과 포인터의 관계 포인터 학습 노하우 ➊ 포인터 변수가 무엇을 가리키는지 확인한다. ➋ 포인터 변수를 선언할 때는 변수 앞에 *만 붙이면 된다. ➌ 포인터 변수에는 꼭 주솟값을 넣어야 한다. - 변수 이름 앞에 ‘&’ 를 붙임 - 배열의 이름은 그 자체가 주소이므로 ‘&’를 붙이지 않음
4. 배열과 포인터의 관계 ➍ 포인터가 가리키는 곳의 실제값을 구하려면 *를 붙인다. - 포인터 변수 p가 변수 a가 들어있는 주소인 1016번지를 가리킨다고 가정함.
4. 배열과 포인터의 관계 - *p에는 임의의 값을 대입할 수 있지만, p에는 오직 주소만 들어간다는 점에 주의.
[예제모음 24] 포인터를 이용해 문자열을 거꾸로 출력
[예제모음 ] 포인터를 이용해 문자열을 거꾸로 출력 01 #include <stdio.h> 02 #include <string.h> 03 04 int main( ) 05 { 06 char ss[100]; 07 int count, i; 08 char *p; 09 10 printf("문자열을 입력하세요 : "); 11 scanf_s("%s", ss, 100); 12 13 count = strlen(ss); 14 15 p = ss; 16 17 printf("내용을 거꾸로 출력 = = > "); 18 for(i=0 ; i<count ; i++) 19 { 20 printf("%c", *(p+count-(i+1)) ); 21 } ---입력받을 문자 배열을 선언한다. ---문자형 포인터를 선언한다. ---문자열을 입력한다. ---입력한 문자열의 개수다. ---배열 ss의 주소를 포인터 변수 p에 대입한다. ---포인터 p에 있는 실제값을 문자열의 제일 뒤부터 출력한다.
[예제모음 ] 포인터를 이용해 문자열을 거꾸로 출력 22 printf("\n"); 23 }
[예제모음 25] 포인터를 이용한 두 값의 교환
[예제모음 ] 포인터를 이용한 두 값의 교환 01 #include <stdio.h> 02 03 int main( ) 04 { 05 int a, b, tmp; 06 int *p1, *p2; 07 08 printf("a 값 입력 : "); 09 scanf_s("%d", &a); 10 printf("b 값 입력 : "); 11 scanf_s("%d", &b); 12 13 p1 = &a; 14 p2 = &b; 15 16 tmp = *p1; 17 *p1 = *p2; 18 *p2 = tmp; 19 20 printf("바뀐 값 a는 %d, b는 %d \n", a, b); 21 } ---정수형 변수 세 개와 포인터 변수 두 개를 선언한다. ---a와 b에 값을 입력한다. ---변수 a의 주솟값을 p1에 대입한다. ---변수 b의 주솟값을 p2에 대입한다. ---➊ p1이 가리키는 곳의 실제값을 tmp에 넣는다. ---➋ p2이 가리키는 곳의 실제값을 p1이 가리키는 곳에 넣음 ---➌ tmp에 저장된 값을 p2가 가리키는 곳에 넣는다.
[예제모음 26] 포인터를 이용한 배열의 정렬
[예제모음 26] 포인터를 이용한 배열의 정렬 01 #include <stdio.h> 02 03 int main( ) 04 { 05 int s[10] = {1, 0, 3, 2, 5, 4, 7, 6, 9, 8}; 06 int tmp; 07 int i, k; 08 09 int *p; 10 11 p = s; 12 13 printf("정렬 전 배열 s = = > "); 14 for(i=0 ; i<10 ; i++) 15 { 16 printf("%d ", *(p+i)); 17 } 18 printf("\n"); 19 ---배열 s에 정숫값 10개를 초기화하고 관련 변수를 선언한다. ---포인터 변수를 선언한다. ---배열 s의 주소를 포인터 변수 p에 대입한다. ---정렬 전의 상태로 데이터 10개를 출력한다.
[예제모음 26] 포인터를 이용한 배열의 정렬 20 for (i=0 ; i<9 ; i++) 21 { 22 for(k=i+1 ; k<10 ; k++) 23 { 24 if ( *(p+i) > *(p+k) ) 25 { 26 tmp = *(p+i); 27 *p(+i) = *(p+k); 28 *p(+k) = tmp; 29 } 30 } 31 } 32 printf("정렬 후 배열 s = = > "); 33 for(i=0 ; i<10 ; i++) 34 { 35 printf("%d ", *(p+i)); 36 } 37 printf("\n"); 38 } ---9회 반복한다. 내부 for문으로 비교할 두 값 중 첫 번째 값의 자리는 s[0]~s[8]이다. ----내부 for문으로 9회, 8회, 7회, …, 1회 반복한다. 비교할 두 값 중 두 번째 값의 자리는 s[1]~s[9]다. -------p+i의 실제값이 p+k의 실제값보다 크면 두 값을 바꾼다. ---정렬 후의 상태로 데이터 10개를 출력한다.
[9장 요약] 1 스택 ➊ 한쪽 끝이 막혀 있는 데이터 구조(자료 구조)를 말하며,L IFO(Last In First Out) 구조다. ➋ 스택을 구현하려면 ‘배열’이라는 데이터 구조를 사용해야 한다. 2 메모리와 주소 ➊ 프로그램에서 사용된 변수, 배열 등은 모두 메모리(램)에 존재하며, 이 메모리의 각 자리에는 주소가 할당되어 있다. ➋ 변수의 주소를 표시하려면 변수 이름 앞에 ‘&’를 붙인다. ➌ 배열의 이름 자체는 배열의 시작 주소다. ➍ 정수형 변수는 4바이트를, 문자형 변수는 1바이트를 차지한다. 3 포인터 ➊ 포인터 변수란 주소를 저장하는 변수로, 포인터 변수를 선언할 때는* ‘’를 붙여준다. ➋ 포인터 변수에는 반드시 주소만 대입시켜야 한다. ➌ 포인터 변수의 크기는 정수, 실수, 문자형에 관계없이 무조건4 바이트다. ➍ 가변적인 문자열을 저장할 때는 배열보다는 포인터가 더 적당하다.