C기초 플러스 chap.10 배열과 포인터
드디어 악랄한 포인터 녀석까지 도달했다! 이고비만 넘기면 우리는 C언어를 구사 할 수 있는 바바리안이 된다!!
걱정하지마라! 걱정하면 너는 바바리안이 아니다!! 간다!!
베ㅔㅐㅇ해ㅔ라라아아아아아아아아!!!
몰라도 일단 스스메에엥!!! 신조 사사게로!! 부숴라아아아!!
배열
배열(Array)이란?
배열(array)은 같은 타입의 변수들로 이루어진 유한 집합입니다.
배열을 구성하는 각각의 값을 배열 요소(element)라고 하며, 배열에서의 위치를 가리키는 숫자는 인덱스(index)라고 합니다.
C언어에서 인덱스는 0부터 시작하며, 0을 포함한 양의 정수만을 가질 수 있습니다.
- 하나의 변수 이름에 순서에 의한 번호를 부여한 자료들의 리스트
- 동일한 자료형을 갖는 자료들의 리스트
- 선언 형태에 따라 1차원, 2차원, 3차원 배열 등으로 구분함
1차원 배열
1차원 배열이란 배열의 첨자가 하나만 있는 것을 말합니다.
- 배열의 이름은 배열의 첫 번째 요소와 같은 주소를 가리킴.(arr = arr[0])
형식 : 자료형 배열명[개수]
사용 예 : int a[10];
의미 : 배열명은 a이고 배열의 크기는 10인 이 배열에는 정수값이 기억된다.
배열의 초기화
배열을 선언했으면 변수와 마찬가지로 마련된 기억공간을 초기화 해주어야 합니다.
배열의 초기화 방법에는 먼저 배열을 선언한 후 초기값을 할당하는 방법과 배열의 선언과 동시에 초기값을 할당하는 방법이 있습니다.
- 배열 선언 이후 초기값 할당
- 배열 선언시 초기값 할당
- 외부로부터 초기값 할당(키보드 입력)
/* day_mon1.c -- 각 달의 날짜 수를 출력한다. */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31}; //(월별 날짜수 입력)
int index;
for (index = 0; index < MONTHS; index++) //조건검사 (index초기화, 조건검사, index증가)
printf("%2d월: 날짜수 %2d\n", index +1, days[index]); //(intdex+1월:, 날짜수:days의 index값)
return 0;
}
//윤달이 적용이 않아 4년에 한번 날짜수가 틀림.
//defint은 미리 상수선언하는 형태이기 때문에 배열의 크기를 고정한다는 뜻이다.
//const는 저장된 배열, 즉 days의 값을 상수화 한다.
//윤달이 적용이 않아 4년에 한번 날짜수가 틀림.
//defint은 미리 상수선언하는 형태이기 때문에 배열의 크기를 고정한다는 뜻이다.
//const는 저장된 배열, 즉 days의 값을 상수화 한다.
//출력 결과
// 1월: 날짜수 31
// 2월: 날짜수 28
// 3월: 날짜수 31
// 4월: 날짜수 30
// 5월: 날짜수 31
// 6월: 날짜수 30
// 7월: 날짜수 31
// 8월: 날짜수 31
// 9월: 날짜수 30
//10월: 날짜수 31
//11월: 날짜수 30
//12월: 날짜수 31
초기화시키지 않은 배열
/* no_data.c -- 초기화시키지 않은 배열 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
int no_data[SIZE]; /* 초기화시지 않은 배열. no_data의 배열 크기는 16바이트다. */
int i; // no_data의 사이즈는 4자리임.
printf("%2s%14s\n",
"i", "no_data[i]");
for (i = 0; i < SIZE; i++) //i를 0으로 초기화, size보다 i가 작을 때, i를 1씩 증가.
printf("%2d%14d\n", i, no_data[i]); //i의 값 출력, no_data의 인덱스 값을 출력
return 0;
}
// 현재 코드는 배열을 초기화 시키지 않았기 때문에, 어떤 값이 나오게 될지 알수 없다.
// 출력결과
// i no_data[i]
// 0 -660563143
// 1 32765
// 2 100
// 3 0
일부분만 초기화된 배열
/* some_data.c -- 일부분만 초기화된 배열 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
int some_data[SIZE] = {1492, 1066}; //배열 총 4개의 자리에서 두개만 초기화 시켜본다.
int i;
printf("%2s%14s\n",
"i", "some_data[i]");
for (i = 0; i < SIZE; i++) //i를 0으로 초기화, size보다 i가 작을 때, i를 1씩 증가.
printf("%2d%14d\n", i, some_data[i]); //i의 값 출력, no_data의 인덱스 값을 출력
return 0;
}
// 그럼 나머지 두개의 자리도 초기화가 되는 것을 확인할 수 있다
// 결국, 하나만 초기화를 시켜도 나머지 배열은 같이 초기화가 된다는 것이다.
// 출력 결과
// i some_data[i]
// 0 1492
// 1 1066
// 2 0
// 3 0
원소개수 카운트
/* day_mon2.c -- 컴파일러가 원소 개수를 카운트한다. */
#include <stdio.h>
int main(void)
{
const int days[] = {31,28,31,30,31,30,31,31,30,31};
int index;
for (index = 0; index < sizeof days / sizeof days[0]; index++) //size of days는 배열의 크기 즉 40바이트이다. size of days[0]의 값은 int형이므로 4바이트다.
printf("%2d월 날짜수 %2d\n", index +1,
days[index]);
return 0;
}
//출력 결과
// 1월 날짜수 31
// 2월 날짜수 28
// 3월 날짜수 31
// 4월 날짜수 30
// 5월 날짜수 31
// 6월 날짜수 30
// 7월 날짜수 31
// 8월 날짜수 31
// 9월 날짜수 30
//10월 날짜수 31
지정초기화 사용
// designate.c -- 지정 초기화를 사용한다.
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = {31,28, [4] = 31,30,31, [1] = 29}; //배열의 자리를 바꾼 것.
int i;
for (i = 0; i < MONTHS; i++)
printf("%2d %d\n", i + 1, days[i]);
return 0;
}
//출력 결과
// 1 31
// 2 29
// 3 0
// 4 0
// 5 31
// 6 30
// 7 31
// 8 0
// 9 0
//10 0
//11 0
//12 0
배열의 범위
// bounds.c -- 배열의 범위를 벗어난다.
#include <stdio.h>
#define SIZE 4
int main(void)
{
int value1 = 44;
int arr[SIZE]; //얘의 크기는 16바이트
int value2 = 88;
int i;
printf("value1 = %d, value2 = %d\n", value1, value2); //
for (i = -1; i <= SIZE; i++)
arr[i] = 2 * i + 1;
for (i = -1; i < 7; i++)
printf("%2d %d\n", i , arr[i]);
printf("value1 = %d, value2 = %d\n", value1, value2);
printf("arr[-1]의 주소: %p\n", &arr[-1]); //파이썬과 다르게 c언어는 인덱스 -1이 인덱스의 끝자리가 아니다.
printf("arr[4]: %p\n", &arr[4]); //존재하지 않는 자리다.
printf("value1: %p\n", &value1);
printf("value2: %p\n", &value2);
return 0;
}
// 사용하는 컴파일러 및 사용하는 컴퓨터 마다 출력되는 방식이 다름.
// 결론은 인덱스 범위에 해당하지 않는 주소를 불러오면 아예 다른 값이 나오거나 프로그램이 멈춤
// 출력 결과
// -1 -1
// 0 1
// 1 3
// 2 5
// 3 7
// 4 9
// 5 0
// 6 -1057872384
// value1 = 44, value2 = 88
// arr[-1]의 주소: 0x7ffda5123cec
// arr[4]: 0x7ffda5123d00
// value1: 0x7ffda5123ce4
// value2: 0x7ffda5123ce8
다차원 배열
다차원 배열이란 2차원 이상의 배열을 의미하며, 배열 요소로 또 다른 배열을 가지는 배열을 의미합니다.
즉, 2차원 배열은 배열 요소로 1차원 배열을 가지는 배열이며, 3차원 배열은 배열 요소로 3차원 배열을 가지는 배열입니다.
- 배열의 이름은 배열의 첫 번째 요소와 같은 주소를 가리킴
- a[0] = a[0][0], a[1] = a[1][0], a[2] = a[2][0]
/* rain.c -- 강우량 데이터로부터
몇년에 걸쳐 계산한 연평균 강우량,
각 달의 월평균 강우량을 구한다.*/
#include <stdio.h>
#define MONTHS 12 // 1년의 달 수
#define YEARS 5 // 데이터를 수집한 햇수
int main(void)
{
// 2010년에서 2014년까지의 강우량 데이터의 초기화
const float rain[YEARS][MONTHS] =
{
{4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,6.6},
{8.5,8.2,1.2,1.6,2.4,0.0,5.2,0.9,0.3,0.9,1.4,7.3},
{9.1,8.5,6.7,4.3,2.1,0.8,0.2,0.2,1.1,2.3,6.1,8.4},
{7.2,9.9,8.4,3.3,1.2,0.8,0.4,0.0,0.6,1.7,4.3,6.2},
{7.6,5.6,3.8,2.8,3.8,0.2,0.0,0.0,0.0,1.3,2.6,5.2}
};
int year, month;
float subtot, total;
printf(" 년도 강우량 (inches)\n");
for (year = 0, total = 0; year < YEARS; year++)
{ // 각 해에 대해 12달치 강우량을 더한다.
for (month = 0, subtot = 0; month < MONTHS; month++)
subtot += rain[year][month];
printf("%5d %15.1f\n", 2010 + year, subtot);
total += subtot; //여러 해에 걸친 총 강우량을 구한다.
}
printf("\n연평균 강우량은 %.1f 인치 입니다..\n\n",
total/YEARS);
printf("월평균 강우량은 다음과 같습니다.:\n\n");
printf(" Jan Feb Mar Apr May Jun Jul Aug Sep Oct ");
printf(" Nov Dec\n");
for (month = 0; month < MONTHS; month++)
{ // 각 달에 대해 5년치 강우량을 더한다.
for (year = 0, subtot =0; year < YEARS; year++)
subtot += rain[year][month];
printf("%4.1f ", subtot/YEARS);
}
printf("\n");
return 0;
}
포인터
주소값의 이해
- 데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미합니다.
- C언어에서의 이러한 주소값을 1Byte 크기의 메모리 공간으로 나누어 표현합니다.
- 예를 들어, int형 데이터는 4Byte 크기를 가지지만, int형 데이터의 주소값은 시작 주소 1Byte만을 가리킵니다.
포인터(pointer)란?
C언어에서 포인터(pointer)란 메모리의 주소값을 저장하는 변수이며, 포인터 변수라고 부릅니다.
- 변수 : 특정 데이터 값을 가지고 있음
- 포인터 : 특정 데이터가 저장된 기억장소의 주소(번지)값을 가지고 있음
- 포인터는 기억공간을 변수명으로 접근하지 않고 주소로 접근하기 위해 사용
포인터 변수의 선언
포인터 변수의 선언은 변수명 앞에 *를 붙인다는 것만 제외하고 일반 변수의 선언과 같습니다.
형식 : 자료형 *포인터변수명;
사용 예 : int *p;
기능 : 변수 p는 포인터 변수로서 정수형의 자료를 갖는 변수의 주소를 갖는다.
int *p
- p : 포인터 변수로서 정수형 자료가 수록되어 있는 주소를 갖고 있음
- *p : 해당 주소에 수록되어 있는 정수형 자료를 갖고 있음
포인터 변수의 참조
어떤 포인터 변수가 가리키는 내용을 읽어 내거나 값을 대입하는 것을 "포인터 변수를 참조(reference)한다" 라고 합니다. 이때 포인터 변수의 참조에는 포인터 연산자 &, *가 사용됩니다.
- &(주소연산자) : 모든 변수에 대한 주소값을 구하는 연산자
- *(내용연산자) : 포인터 변수의 자료(내용)를 구하는 연산자
- 포인터변수=값 : 해당 포인터 변수에 해당 값이 저장된 기억공간에 해당하는 주소를 가리킴
- 포인터변수=&변수명 : 해당 변수가 저장된 기억공간의 주소를 가리킴, *포인터변수로 해당 기억공간의 값 사용
일반 변수와 포인터 변수의 비교
|
일반 변수
|
포인터 변수
|
||
선언
|
int a;
|
정수형 변수 a 선언
|
int *a;
|
포인터 변수 a를 정수형으로 선언
|
값 할당
|
a = 100;
|
변수 a에 100 할당
|
*a = 100;
|
a 주소에 100 할당
|
주소 참조
|
&a
|
변수 a의 주소
|
a
|
a 자체가 주소
|
주소 연산
|
연산 불가능
|
|
a--;
|
포인터를 1 감소
|
포인터의 덧셈
// pnt_add.c -- 포인터와 덧셈
#include <stdio.h>
#define SIZE 4
int main(void)
{
short dates [SIZE]; //dates의 크기는 8바이트
short * pti;
short index;
double bills[SIZE]; //bills의 크기는 64바이트
double * ptf;
pti = dates; // 배열의 주소를 포인터에 대입한다.
ptf = bills;
printf("%23s %15s\n", "short", "double"); //그냥 문자 출력하는거임.
for (index = 0; index < SIZE; index ++) //인덱스 초기화; 인덱스가 사이즈보다 작을때, 인덱스 1씩증가
printf("pointers + %d: %10p %10p\n", //포인터 문자 + 인덱스 정수형:,
index, pti + index, ptf + index);
return 0;
}
//포인터의 덧셈은 바이트의 크기만큼 주소가 더해진다
//short형은 2바이트이기 때문에 정수 1을 더하면 주소값이 2씩 늘어난다.
//double형은 8바이트이기 때문에 정수 1을 더하면 주소값이 8씩 늘어난다.
//출력 결과
//pointers + 0: 0x7ffda2c9d8a8 0x7ffda2c9d8b0
//pointers + 1: 0x7ffda2c9d8aa 0x7ffda2c9d8b8
//pointers + 2: 0x7ffda2c9d8ac 0x7ffda2c9d8c0
//pointers + 3: 0x7ffda2c9d8ae 0x7ffda2c9d8c8
포인터 표기의 사용
/* day_mon3.c -- 포인터 표기를 사용한다. */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
int index;
for (index = 0; index < MONTHS; index++)
printf("%2d월: 날짜수 %2d\n", index +1,
*(days + index)); // days[index]와 같다.
return 0;
}
//출력 코드
// 1월: 날짜수 31
// 2월: 날짜수 28
// 3월: 날짜수 31
// 4월: 날짜수 30
// 5월: 날짜수 31
// 6월: 날짜수 30
// 7월: 날짜수 31
// 8월: 날짜수 31
// 9월: 날짜수 30
// 10월: 날짜수 31
// 11월: 날짜수 30
// 12월: 날짜수 31
배열원소들의 합 구하기
// sum_arr1.c -- 배열의 원소들의 합을 구한다.
// %zd가 동작하지 않는다면 %u 또는 %lu를 사용하라.
#include <stdio.h>
#define SIZE 10
int sum(int ar[], int n);
int main(void)
{
int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20}; //멤버의 사이즈
long answer; //엔썰 long데이터형 선언
answer = sum(marbles, SIZE); //함수 sum의 전달인자는 marbles,size answer에 대입
printf("구슬의 전체 개수는 %ld개입니다.\n", answer); //answerd의 값
printf("marbles의 크기는 %zd 바이트 입니다.\n", //mables의 사이즈 크기
sizeof marbles);
return 0;
}
//함수의 정의
int sum(int ar[], int n) // 배열의 크기를 함수
{
int i; //변수 i에 int 데이터형 선언
int total = 0; // total int 데이터형 선언과 동시에 초기화.
for( i = 0; i < n; i++) //i초기화; i가 n보다 작을 때, i증가
total += ar[i]; //total= total + ar[i]의 값!
printf("ar의 크기는 %zd 바이트 입니다.\n", sizeof ar); //ar 전체의 크기를 구함.
return total; //total의 값 리턴.
}
//출력 결과
//ar의 크기는 8 바이트 입니다.
//구슬의 전체 개수는 190개입니다.
//marbles의 크기는 40 바이트 입니다.
배열원소들의 합 구하기
/* sum_arr2.c -- 배열의 원소의 합을 구한다. */
#include <stdio.h>
#define SIZE 10
int sump(int * start, int * end);
int main(void)
{
int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
long answer;
answer = sump(marbles, marbles + SIZE); //배열에 아무 값도 더해지지 않으면, 인덱스 0부터 시작한다., 인덱스 0의 marbles상수 10을 더한다.
printf("구슬의 전체 개수는 %ld개입니다. \n", answer) ;
return 0;
}
/*포인터 계산을 사용한다. */
int sump(int * start, int * end)
{
int total = 0;
while (start < end) // start부터 end미만일때 끝난다.
{
total += *start; // *total에 값을 더한다.포인터의 크기만큼 증가 시킨다. int형이기 때문에 4바이트의 주소값이 증가한다.
start++; // *포인터를 증가시켜 다음 원소를 가리킨다.
}
return total;
}
//출력 결과
//구슬의 전체 개수는 190개입니다.
포인터 연산에서의 우선순위
/* order.c -- 포인터 연산에서의 우선순위 */
#include <stdio.h>
int data[2] = {100, 200};
int moredata[2] = {300, 400};
int main(void)
{
int * p1, * p2, * p3; //p1,p2,p3의 포인트
p1 = p2 = data; //data를 p2에 대입하고, p2를 p1에 대입한다.
p3 = moredata; // moredata를 p3에 대입한다.
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n", //*p1, *p2, *p3의 배열 값을 출력
*p1 , *p2 , *p3);
printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d\n", //*p1++,*++p2,(*p3)++의 배열값을 출력
*p1++ , *++p2 , (*p3)++);
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n",//*p1, *p2, *p3의 값을 출력
*p1 , *p2 , *p3);
return 0;
}
//출력 결과값을 보면, 두번째 라인에서, p1, p2,는 그냥 연산자를 사용했고, p3는 (*p3)에 연산자를 적용한 값을 나타낸다.
//p1,p2는 다음 배열의 값을 나타냈지만, p3는 다음 배열이 아닌, p3의 값 자체를 증가 시켰다는 것을 알 수있다!!
//출력 결과
// *p1 = 100, *p2 = 100, *p3 = 300
//*p1++ = 100, *++p2 = 200, (*p3)++ = 300
// *p1 = 200, *p2 = 200, *p3 = 301