프로그래밍개론 1 5 장포인터와배열
이장의내용 포인터와주소 포인터와함수인수 포인터와배열 주소연산 문자포인터와함수 포인터배열 - 포인터에대한포인터 다차원배열 문자포인터배열의초기화 포인터배열과다차원배열 명령행인수 함수에대한포인터 2
포인터와주소 포인터 (pointer) 란? 메모리상의주소값을나타내는데이터주소표현방법은컴퓨터에따라다름 Visual Studio 6의 C 언어에서는 32 비트 (4 바이트 ) 정수를이용하여주소를표현하는것으로간주할수있음 변수가나타내는것은무엇인가? 주소? 값? x = y; x 72를 0012FF78번지에저장하라. 0012FF7C번지에들어있는값을 0012FF78 번지에저장하라. = 기호의오른쪽변수의경우저장된값이사용되는반면, 왼쪽변수의경우주소가사용된다. 300 주소 : 0012FF78 y 72 주소 : 0012FF7C 3
포인터와주소 포인터관련단항연산자 & (reference operator): 변수의주소, 즉, 포인터를취한다. p = &c; /* p 에는 c 의주소가저장된다. */ * (de-reference operator): 포인터값앞에오며, 포인터값이가리키는주소에들어있는데이터를취한다. & 의반대역할을한다. c = 3; p = &c; x = *p; /* x에는 3이저장된다. */ 포인터의출력 ( 포맷코드 %p 를사용함 ) printf( %p\n, &x); /* 8 자리 16 진수로출력됨 */ 4
포인터와주소 포인터변수의선언 type *variable; variable 이갖는주소위치에저장되어있는데이터의타입이 type 임을의미한다. 각포인터는특정타입의값을가리킨다. int x = 1, y = 2, z[10]; int *ip; /* ip는정수에대한포인터 */ ip = &x; /* ip 는 x 를가리킨다 */ y = *ip; /* y는 1이된다 */ *ip = 0; /* x의값은0이된다 */ ip = &z[0]; /* ip 는 z[0] 을가리킨다 */ 5
포인터와주소 문장 해설 double *dp; dp는 double 타입의값에대한포인터 double atof(char *); atof() 는문자에대한포인터를인수로취한다 int *ip, *iq; ip와 iq는 int 타입의값에대한포인터 ip = &x; ip는 x를가리킨다 *ip = *ip + 10; x의값이10 증가한다 y = *ip + 1; y는 x+1이된다 *ip += 1; x의값이1 증가한다 ++*ip; x의값이1 증가한다 (*ip)++; x 의값이 1 증가한다 ( 괄호가없으면의미가달라진다 ) iq = ip; iq 도 x 를가리킨다 6
포인터와함수인수 C의값에의한인수전달방식으로인해호출함수에정의된변수를직접적으로변경할수없다. 그러나포인터를사용하여간접적으로호출함수측의변수를변경할수있다. void swap(int x, int y) int temp; temp = x; x = y; y = temp; main() int a = 3, b = 5; x a y 5 3 b 3 5 void swap(int *px, int *py) int temp; temp = *px; *px = *py; *py = temp; main() int a = 3, b = 5; x a &a y &b b 5 3 swap(a, b); printf("a = %d, b = %d", a, b); swap(&a, &b); printf("a = %d, b = %d", a, b); 7
포인터와함수인수 키보드에서정수읽는함수 getint() 만들기 부호를포함하는정수표현이자유형식으로입력된다. 숫자에해당하는문자열을정수로변환하여돌려주고, 또파일끝여부를알려주어야한다. 두가지값의반환이필요하므로리턴값외에값을돌려줄방법이필요하다. ( 포인터타입의매개변수로읽은값리턴 ) #include <stdio.h> #define SIZE 100 main() int i, n, array[size], getint(int *); for (n = 0; n < SIZE && getint(&array[n])!= EOF; n++) ; for (i = 0; i < n; i++) printf("%d%c", array[i], i == n-1? '\n' : ' '); 8
포인터와함수인수 /* p. 61 의 atoi() */ #include <ctype.h> int atoi(char s[]) int i, n, sign; for (i = 0; isspace(s[i]); i++) ; sign = (s[i] == '-')? -1 : 1; if (s[i] == + s[i] == - ) i++; for (n = 0; isdigit(s[i]); ++i); n = n * 10 + (s[i] '0'); 0); return sign*n; #include <stdio.h> #include <ctype.h> int getch(void); int ungetch(int); int getint(int *pn) int c, sign; while (isspace(c = getch())) /* 공백지나가기 */ ; if (!isdigit(c) && c!= EOF && c!= '+' && c!= '-') ungetch(c); /* 숫자아님 */ return 0; sign = (c == '-')? -1 : 1; if (c == '+' c == '-') c = getch(); for (*pn = 0; isdigit(c); c = getch()) *pn = 10 * *pn + (c - '0');') *pn *= sign; if (c!= EOF) ungetch(c); return c; 9
포인터와배열 포인터와배열은아주밀접한관계에있다 a[0] a[1] a[2] a[3] a[4] int a[5], x; int *pa; pa = &a[0]; /* pa는 a[0] 의주소를갖는다 */ x = *pa; /* x = a[0] 에해당된다 */ pa pa+1 pa+2 pa+3 pa+4 포인터와배열의차이 포인터변수가갖는주소값은변경될 이예에서 *(pa + i) 는 a[i] 와수있다. ( 올바른표현 ) 동등한의미로사용될수있다. pa = a pa++ 동등한표현들 pa = a pa = &a[0] a[i] *(a + i) &a[i] a + i pa[i] *(pa + i) 배열이름은배열이시작되는주소를나타내지만, 배열이름이나타내는주소값은변경될수없다.( 틀린표현 ) a = pa a++ sizeof pa는 4인반면sizeof a는 20 이다. (int 의크기는 4 바이트로가정 ) 10
포인터와배열 배열이름을함수의인수로사용할경우, 함수에전달되는것은배열자체가아니라배열의시작주소, 즉, 포인터이다. 즉, 배열에들어있는값들이함수로전달되는것이아니라, 그배열시작주소만을전달한다. 따라서 strlen(s) 와 strlen(&s[0]) 이라는두표현은동등하다. (strlen() 은스트링의길이를알려주는 C 표준함수이다.) 함수 strlen() 의헤딩은아래의두가지표현으로표시될수있으며, 이들은동등한표현이다. int strlen(char s[]); int strlen(char *s); 전역변수나지역변수의정의에서라면 char *s 와 char s[] 는분명서로다른표현이다. 11
포인터와배열 /* 2 장의 strlen() 정의 */ /* 새로운 strlen() 정의 */ int strlen(char s[]) int strlen(char *s) int i; int i; for (i = 0; s[i]!= '\0'; ++i) ; return i; for (i = 0; *s!= '\0'; s++) i++; return i; #include <stdio.h> main() char array[] = "abcd"; char *ptr = array + 1; printf("%d %d %d\n", strlen("hello, world"), strlen(array), strlen(ptr)); 12
포인터연산 포인터와관련하여다음과같은연산이허용된다. 아래에서 p, q는동일한타입의포인터, i는정수라고하자. 포인터 + 정수 : p + i는 p가가리키는원소보다 /* 또다른 strlen() i개만큼뒤에위치하는원소의주소를나타낸다. 정의 */ 포인터 - 정수 : p - i는 p가가리키는원소보다 i개만큼앞에위치하는원소의주소를나타낸다. int strlen(char *s) 포인터 - 포인터 : p - q는 p가가리키는원소와 q가가리키는원소사이에있는원소의개수를 char *p = s; 나타낸다. 보다정확히표현하자면, p = q + i이면 p - q는 i가된다. while (*p!= '\0') 관계연산자에의한두포인터값의비교 : p < q p++; return p - s; 포인터의배정 : p = q 이외에포인터에정수를곱하거나나눈다든지, 또는포인터들끼리의덧셈등은그의미가정의되지않는다. 13
문자포인터와함수 char *pmessage = "now is the time"; 여기서의 "now is the time" 은스트링상수이며, 상수들을저장하는특별한메모리영역에저장된다. 그주소가포인터변수 pmessage 에저장된다. char amessage[] = "now is the time"; 여기서의 "now is the time" 은스트링상수가아니라, 초기화표현 'n', 'o', 'w', ' ',..., 'm', 'e', '\0' 의약식표현이다. 변수저장영역에크기가 16 바이트인배열 amessage 가만들어진다. pmessage: amessage: now is the time\0 now is the time\0 14
문자포인터와함수 ( 스트링복사 ) void strcopy(char *s, char *t) int i; i = 0; while ((s[i] = t[i])!= '\0') i++; /* C 표준함수 */ char *strcpy(char *s, char *t) char *p = s; while (*s++ = *t++) ; return p; void strcopy(char *s, char *t) while ((*s++ = *t++)!= '\0') ; void strcopy(char *s, char *t) while ((*s = *t)!= '\0') s++; t++; while (*s++ = *t++) ; void strcopy(char *s, char *t) 15
문자포인터와함수 ( 스트링비교 ) /* C 표준함수 배열버전 */ /* C 표준함수 포인터버전 */ /* return 음수 if s < t, 0 if s == t 양수 if s > t */ int strcmp(char *s, char *t) int i; for (i = 0; s[i] == t[i]; i++) if (s[i] == '\0') return 0; return s[i] [] t[i]; /* return 음수 if s < t, 0 if s == t 양수 if s > t */ int strcmp(char *s, char *t) for (; *s == *t; s++, t++) if (*s == '\0') return 0; return *s *t; 16
포인터배열 - 포인터에대한포인터 char *a[100]; a 는원소 100 개로이루어진배열 배열 a 의각원소는 char * 타입, 즉, 문자포인터 예제 : 스트링정렬프로그램 defghi 스트링의비교에는 strcmp() 함수사용 모의코드 read all the lines of input sort them using Quick Sort print them in order jklmnopqrst abc defghi jklmnopqrst abc 17
포인터배열 - 포인터에대한포인터 #include <stdio.h> #define MAXLINES 5000 /* 최대행수 */ int readlines(char *lines[], int nlines); void writelines(char *lines[], int nlines); void qsort(char *lines[], int left, int right); main() char *lines[maxlines]; int nlines; if ((nlines = readlines(lines, MAXLINES)) > 0) qsort(lines, 0, nlines-1); writelines(lines, nlines); return 0; else printf("error: no lines or too many lines\n"); return 1; 18
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <string.h> 포인터배열 - 포인터에대한포인터 #define MAXLEN 1000 /* 행최대길이 */ int readlines(char *lines[], int maxlines) int nlines; char line[maxlen]; nlines = 0; while (fgets(line, MAXLEN, stdin)!= NULL) if (nlines >= maxlines) return -1; else lines[nlines++] = strdup(line); td return nlines; void writelines(char *lines[], int nlines) while (nlines-- > 0) fputs(*lines++, stdout); void swap(char *v[], int i, int j) char *temp; temp = v[i]; v[i] = v[j]; v[j] = temp; void qsort(char *v[], int left, int right) int i, last; if (left >= right) return; swap(v, left, (left + right) / 2); last = left; for (i = left + 1; i <= right; i++) if (strcmp(v[i], v[left]) < 0) swap(v, ++last, i); swap(v, left, last); qsort(v, left, last-1); qsort(v, last+1, right); 19
다차원배열 (multi-dimensional array) < 출력결과 > #include <stdio.h> August 15th of 2008 is 228th day of the year main() 08/15/2008 int day_of_year(int year, int month, int day); void month_day(int year, int yearday, int *pmonth, int *pday); int mm, dd; dd = day_of_year(2008, 8, 15); printf("august 15th of 2008 is %d%s day of the year\n", dd, (dd%10 == 1)? "st" : (dd%10 == 2)? "nd" : (dd%10 == 3)? "rd" : "th"); month_day(2008, dd, &mm, &dd); printf("%02d/%02d/%04d\n", mm, dd, 2008); 20
static char daytab[2][13] = 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, /* 비윤년 */ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 /* 윤년 */ ; /* 연 / 월 / 일로부터연중날짜를계산하여리턴한다 */ int day_of_year(int year(int year, int month, int day) int i, leap; leap = year%4 == 0 && year%100!= 0 year%400 == 0; for (i = 1; i < month; i++) day += daytab[leap][i]; return day; /* 연도및연중날짜로부터월 / 일계산 */ void month_day(int year, int yearday, int *pmonth pmonth, int *pday) int i, leap; leap = year%4 == 0 && year%100!= 0 year%400 == 0; for (i = 1; yearday > daytab[leap][i]; i++) yearday -= daytab[leap][i]; *pmonth = i; *pday = yearday; 21
다차원배열 1 차원배열원소의위치 ( 즉, 주소 ) 계산 int a[5]; /* a의주소를 10000번지, w = sizeof(int) 는 4로가정 */ a[0] a[1] a[2] a[3] a[4] 10000 10004 10008 10012 10016 a[i] 의주소 : a 의주소 + w * i 22
다차원 20000 b[0][0] 20004 b[0][1] 20008 b[0][2] 20012 b[0][3] 20016 b[0][4] 배열 20020 b[1][0] 20024 b[1][1] 20028 b[1][2] 20032 b[1][3] 20036 b[1][4] 2 차원배열 20040 b[2][0] 20044 b[2][1] 20048 b[2][2] 20052 b[2][3] 20056 b[2][4] int b[10][5];............... 20180 20184 20188 20192 20196 b[9][0] b[9][1] b[9][2] b[9][3] b[9][4] b[0][j] 의주소 : b의주소 + w * j b1 b[1][j] 의주소 : b 의주소 + 20 + w * j b[2][j] 의주소 : b의주소 + 40 + w * j b[i][j] 의주소 : b의주소 + 20 * i + w * j b의주소 + w * 5 * i + w * j b의주소 + w * (5 * i + j) 23
다차원배열 2 차원배열원소의주소계산 int c[m][n]; /* 원소크기 : w */ c[i][j] 의주소 : c의주소 + w * (N * i + j) 3 차원배열원소의주소계산 int d[l][m][n]; /* 원소크기 : w */ d[i][j][k] 의주소 : d 의주소 + w * (M * N * i + N * j + k) 다차원배열원소의주소계산에서첫번째차원의크기를제외한나머지차원의크기들이필요하다. 함수매개변수로다차원배열이사용될경우에첫번째차원을제외한나머지차원의크기가생략되어서는안된다. f(int daytab[][13])... 24
문자포인터배열의초기화 /* 월이름을리턴한다 */ char *month_name(int name(int mm) static char *name[] = "Illegal l month", "January", "February", "March", "April", "May", "June", "July", "August", "September", " "October", " "November", " "December" " ; return (mm < 1 mm > 12)? name[0] : name[mm]; 25
포인터와다차원배열 int a[10][20]; int *b[10]; a 는 int 타입의 2 차원배열이며, b 는 (int *) 타입의 1 차원배열 a, b 모두에대해 int 원소를나타내는 a[3][4], b[3][4] 와같은표현을사용할수있다. a 는곧바로정수들을저장하기위해사용될수있지만, b 의경우각원소에대해별도의배열지정절차가필요하다. b[0] = (int *) malloc(4*100); b[1] = (int *) malloc(4*20);... b 가나타내는 2 차원정수배열은각행의크기가다를수있다. 26
포인터배열과다차원배열 char *name[] = "Illegal month", "January", "February", "March"; char aname[][15] = "Illegal month", "January", "February", "March"; name: Illegal month\0 January\0 February\0 March\0 aname: Illegal month\0 January\0 February\0 March\0h 0 15 30 45 27
명령행인수 명령행의예 명령행에나타나는인수들을받기위한장치가 main() 함수의매개변수이다. main(int argc, char *argv[])... COPY\0 argv: INF.LOG\0 argc 의값은 3 TEST.DAT\0 28
명령행인수 예제 : echo hello, world #include <stdio.h> < 출력결과 > main(int argc, char *argv[]) int i; hello, world for (i = 1; i < argc; i++) printf("%s%s", argv[i], (i < argc - 1)? " " : "\n"); #include <stdio.h> main(int argc, char *argv[]) while (--argc > 0) printf("%s%s", *++argv, (argc > 1)? " " : "\n"); 29
함수에대한포인터 함수에대한포인터도다른포인터들처럼정의하고, 변수나배열에저장하거나함수인수로사용할수있다. int x; int *p; x 는 int, p 는 int 에대한포인터 int f(char *); int (*pf)(char *); int *g(char *); f 는매개변수로 (char *) 을취하고 int 를리턴하는함수 pf 는매개변수로 (char *) 을취하고 int 를리턴하는함수에대한포인터 g 는매개변수로 (char *) 을취하고 (int *) 를리턴하는함수 30
함수에대한포인터 예제 : 일반화된퀵정렬함수 void * 임의의타입에대한포인터 이타입을갖는변수는아무타입의포인터값이나저장할수있다. 단, 이타입의변수에대해 * 연산을적용할수없다. 즉, 그주소에들어있는데이터를꺼낼수없다. int x = 0x41424344; /* ABCD */ void *p = &x; putchar(*p); /* ERROR */ printf("%x\n", *(int *)p); /* 41424344 */ printf("%c\n", *(char *) p); /* D */ #include <string.h> void swap(void *v[], int i, int j) void *temp; temp = v[i]; v[i] [] = v[j]; [] v[j] = temp; void qsort(void *v[], int left, int right, int (*comp)(void *, void *)) int i, last; if (left >= right) return; swap(v, left, (left + right) / 2); last = left; for (i = left + 1; i <= right; i++) if ((*comp)(v[i], v[left]) < 0) swap(v, ++last, i); swap(v, left, last); qsort(v, left, last-1); qsort(v, last+1, right); 31
#include <stdio.h> #include <string.h> 함수에대한포인터 #include <stdlib.h> int numcmp(char *s1, char *s2) #define MAXLINES 5000 /* 최대행수 */ int readlines(char *lines[], int nlines); double v1, v2; void writelines(char *lines[], int nlines); 예제 : 일반화된퀵정렬함수 void qsort(void *lines[], int left, int right, v1 = atof(s1); int (*comp)(void *, void *)); v2 = atof(s2); int numcmp(char *, char *); if (v1 < v2) return -1; main(int argc, char *argv[]) else if (v1 > v2) return 1; char *lines[maxlines]; else int nlines, numeric = 0; return 0; if (argc > 1 && strcmp(argv[1], "-n") == 0) numeric = 1; if ((nlines = readlines(lines, MAXLINES)) > 0) qsort((void **) lines, 0, nlines-1, (int (*)(void *, void *)) (numeric == 1? numcmp : strcmp)); writelines(lines, nlines); return 0; else... 32
11 주차실습 연습문제 5-2 & 5-5 Exercise 5-2. Write getfloat, the floating-point analog of getint. What type does getfloat return as its function value? Hint) Compare the function atoi() in p. 61 and the function atof() in p. 71. 33
11 주차실습 Exercise 5-5. 5 Write versions of the library functions strncpy, strncat, and strncmp, which operate on at most the first n characters of their argument strings. For example, strncpy(s,t,n) copies at most n characters of t to s. Full descriptions are in Appendix B. char *strncpy(s,ct,n) copy at most n characters of string ct to s; return s. Pad with '\0''s if ct has fewer than n characters. char *strncat(s,ct,n) concatenate at most n characters of string ct to string s, terminate s with '\0'; return s. int strncmp(cs,ct,n) compare at most n characters of string cs to string ct; return <0 if cs<ct, 0 if cs==ct ct, or >0 if cs>ct. 34
11 주차실습 숙제 : 연습문제 5-4 Exercise 5-4. Write the function strend(s,t), which returns 1 if the string t occurs at the end of the string s, and zero otherwise. 35
12 주차실습 연습문제 5-10 & 5-14 Exercise 5-10. Write the program expr, which evaluates a reverse Polish expression from the command line, where each operator or operand is a separate argument. For example, expr234+* evaluates 2 * (3+4). 36
12 주차실습 Exercise 5-14. Modify the sort program to handle a -r flag, which indicates sorting in reverse (decreasing) order. Be sure that r works with -n. 37