6) 배열
배열이란?
: 같은 자료형의 값을 순서대로 여러 개 저장할 수 있는 변수 묶음
int scores[5]; // 정수 5개 저장하는 배열 선언
- 위의 배열은 scores[0] ~ scores[4]까지 총 5칸 (5개 요소)
- scores[i] 로 접근
- 배열 선언 및 초기화
int a[5] = {10, 20, 30, 40, 50};
(크기 생략)
int a[] = {10, 20, 30}; // 크기 3으로 자동 설정
- 배열 전체를 출력하는 예제 (반복문 사용)
#include <stdio.h>
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
- 사용자 입력을 받아서 배열을 구성하는 예제
int a[5];
for (int i = 0; i < 5; i++) {
printf("%d번째 값 입력: ", i + 1);
scanf("%d", &a[i]);
}
** scanf("%d", &a[i]); 의 형태로 사용함.
<연습 문제>
사용자로부터 정수 5개를 입력받아 배열에 저장하고, 최댓값과 그 인덱스를 출력하라.
- 힌트: 배열 선언, for문으로 입력받기, max와 maxIndex 변수 사용
<출력 예시>
5개의 정수를 입력하세요:
10 60 30 40 20
최댓값: 60
인덱스: 1
<정답>
#include <stdio.h>
int main(void) {
int arr[5];
printf("정수 5개를 입력하시오\n");
for (int i = 0; i < 5; i++)
scanf("%d", &arr[i]);
int max = arr[0];
int maxIndex = 0;
for (int i = 1; i < 5; i++){
if(arr[i]>max){
max = arr[i];
maxIndex = i;
}
}
printf("최댓값: %d\n", max);
printf("인덱스: %d\n", maxIndex);
system("pause");
return 0;
}
<연습문제>
정수 5개를 입력받아서 배열에 저장하고, 그 배열을 함수에 전달하여 전체 합을 계산하라.
#include <stdio.h>
int sumArray(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
int main(void) {
int arr[5];
printf("정수 5개를 입력하시오\n");
for (int i = 0; i < 5; i++)
scanf("%d", &arr[i]);
int total = sumArray(arr, 5);
printf("총합: %d\n", total);
system("pause");
return 0;
}
7) 포인터 << 집중 학습
(※포인터를 복습하며 개인적으로 정리하기 위한 글이지, 교재 순서대로 정리된 입문용 글이 아닙니다.
포인터를 아예 처음 접하는 분에게는 예제의 순서가 중구난방일 수 있습니다.)
포인터란?
: 어떤 변수의 주소를 저장하는 변수
- 일반적인 변수는 값을 저장하지만, 포인터 변수는 주소를 저장한다.
int *p = &x;
*p 는 주소 p가 가리키는 곳의 값, &x는 x의 주소
- 핵심 문법
int *p; | 정수형 값을 가리키는 포인터 선언 |
p = &x; | x의 주소를 p에 저장 |
*p | p가 가리키는 주소에 있는 값을 읽거나 바꿀때 |
- 예제: 포인터의 기본 동작
#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x;
printf("x의 값: %d\n", x); // 10
printf("x의 주소: %p\n", &x); // 예: 0x7ff...
printf("p의 값(=x의 주소): %p\n", p);
printf("*p = x의 값: %d\n", *p); // 10
*p = 20; // x의 값을 바꿈!
printf("x의 값(변경 후): %d\n", x); // 20
return 0;
}
x: 10 ← 변수 자체
&p: 0x123 ← 포인터 p의 주소
p: &x ← x의 주소 저장됨
*p: x의 값 ← x가 가리키는 값을 의미
*은 값에 접근하는 것, &는 주소에 접근하는 것
- 예제: 포인터 두 개와 값 바꾸기 (헷갈리지 않도록)
#include <stdio.h>
int main(void) {
int a = 10;
int b = 20;
int *p1 = &a; // p1은 a를 가리킴
int *p2 = &b; // p2는 b를 가리킴
*p1 = 100; // a의 값을 바꿈
p1 = p2; // 이제 p1도 b를 가리킴
*p1 = 200; // b의 값을 바꿈
printf("a = %d\n", a); // ?
printf("b = %d\n", b); // ?
return 0;
}
<생각해보기>
1) *p1 = 100; 이후 a의 값은?
2) p1 = p2; 이후 p1은 무엇을 가리키나?
3) *p1 = 200; 이후 b의 값은?
<풀이>
처음에는 a = 10, b = 20
p1 값은 a의 주소, p2 값은 b의 주소임
포인터 p1을 이용해서 a값을 100으로 바꿈
p2를 p1에 대입, p2의 값 즉 b의 주소를 p1에 저장하게 됨
p1을 이용해서 b 값을 200으로 바꿈
최종적으로 a는 100, b는 200
<참고>
- 위의 예제에서는 포인터 선언과 주소 대입을 동시에 함.
int *p1 = &a;
int *p1; // 포인터 선언만 먼저
p1 = &a; // 주소 대입 따로
이렇게 해도 같음.
<연습문제>
포인터로 배열 순회하며 평균 구하기
- 정수 5개를 배열에 입력받고
- 포인터 int *p를 사용해 배열을 순회하면서
- 총합을 구하고
- 평균을 출력하라 (소수점 첫째 짜리까지)
** 반드시 포인터를 이용해 순회하라 - 배열 인덱스 arr[i]을 사용하지 않아야 함!
<정답>
#include <stdio.h>
int main(void) {
int arr[5];
int *p = arr;
int sum = 0;
printf("정수 5개를 입력하세요: ");
for (int i = 0; i < 5; i++)
scanf("%d", &arr[i]);
for (int i = 0; i < 5; i++) {
sum += *(p + i); //arr[i]와 같음, 배열을 i로 순회하며 모두 더함
}
printf("총합: %d\n", sum);
printf("평균: %.1f\n", sum / 5.0); //실수로 나눔
system("pause");
return 0;
}
- 함수에 포인터를 인자로 넘기기
: 배열이든 변수든, 원본 데이터를 함수에서 바꾸고 싶을 때 사용함
기본적으로 C에서 함수의 인자는 "값 복사", "값에 의한 전달 (Call bt Value)"
void setZero(int x) {
x = 0;
}
이건 main()에 있는 x를 바꾸는 게 아니라, 복사본만 바꿈.
원본엔 영향 없음.
하지만 포인터를 쓰면?
void setZero(int *px) {
*px = 0;
}
*px는 실제 x가 있는 주소에 접근해서 값을 변경, 즉 원본을 바꿀 수 있음.
"참조에 의한 전달 (Call by Reference)"
- 정리
값에 의한 전달 | 복사본만 다룸 |
참조에 의한 전달 | 주소를 넘겨 원본을 바꿈 |
- 예제: 함수로 변수 바꾸기
#include <stdio.h>
void setZero(int *px) {
*px = 0;
}
int main(void) {
int a = 100;
setZero(&a); // 주소를 넘김
printf("a = %d\n", a); // 출력: 0
return 0;
}
<연습문제>
정수 하나를 입력받고 함수 void doubleValue(int *p)를 만들어서 그 값을 2배로 만들기
main()에서 printf()로 변경된 값을 출력하기
<정답>
#include <stdio.h>
void doubleValue(int *p) {
*p *= 2;
}
int main(void) {
int a;
int *v = &a;
printf("정수를 하나 입력하시오: ");
scanf("%d", &a);
//함수 호출: a의 주소를 보냄
doubleValue(v);
//a의 값을 출력
printf("변경된 값: %d\n", a);
system("pause");
return 0;
}
- 포인터 연산자와 연산 규칙
포인터 연산자 종류: 사칙연산 중에서는 덧셈, 뺄셈 연산만 허용
+) 증감연산자, 주소참조연산자, 대입연산자, 비교연산자 사용 가능
유형 | 종류 | 활용 예시 (p: 포인터, v: 일반 변수) |
산술 연산자 | +, -, ++, -- | *p+1, *p-1, *p--, *++p, ++*p, --*p |
주소 참조 연산자 | &, * | int *p, p=&a |
대입 연산자 | =, +=, -= | p=&a, v += (*p)++ |
비교 연산자 | ==, != | p==NULL, p!=NULL |
포인터 타입별 증감 연산 (*p++ 또는 *p--)
: 기본적으로 포인터를 선언한 자료형의 크기만큼 더하거나 빼라는 의미, 증감값은 반드시 정수
(자료형별 크기)
char | 1바이트 |
short int | 2바이트 |
int | 4바이트 |
float | 4바이트 |
double | 8바이트 |
포인터 증감 연산자 활용 (반드시 구분해서 기억해야 함!)
: 메모리 주소값에 대한 변화 VS 포인터가 가리키는 주소에 저장된 데이터값에 대한 변화?
(주의) 나란히 있을 경우 ++의 우선순위가 *보다 높음
하지만 ++이 p의 뒤에 선언되었을 경우 *p 수행 후 p++ 수행
괄호 안에 있는 것을 먼저 수행
종류 | 설명 | 값 변동 요인 |
*p++ | *p 수행 --> p++ 수행 (주소값 1 증가) | 주소값 |
(*p)++ | *p 수행 --> ++ 수행 (데이터값 1 증가) | 데이터값 |
*++p | p++ 수행 --> *p 수행 (주소값 1 증가한 데이터값 참조) | 주소값, 데이터값 |
++*p | ++ 수행 --> *p 수행 (데이터값 1 증가 후 그 데이터값 참조) | 데이터값 |
<예제>
char 형 포인터에 증감 연산자 사용 (책 11-7)
(C언어 일취월장 교재를 참고했습니다)
#include <stdio.h>
//예제 11-7: char형 포인터에 증감 연산자 사용
int main(void) {
char c;
char *pc;
pc = &c;
int i;
int *pi;
pi = &i;
double d;
double *pd;
pd = &d;
printf("포인터 증감 연산자 사용 전 주소값\n");
printf("char형 포인터 주소값: %d\n", pc);
printf("int형 포인터 주소값: %d\n", pi);
printf("double형 포인터 주소값: %d\n", pd);
//*p++은 *p (값 - 데이터형 - 참조) 후 p++ (그만큼 주소값 증가) 이다.
*pc++; //1바이트 증가
*pi++; //4바이트 증가
*pd++; //8바이트 증가
printf("\n포인터 *p++ 연산자 수행 후 주소값\n");
printf("char형 포인터 주소값: %d\n", pc);
printf("int형 포인터 주소값: %d\n", pi);
printf("double형 포인터 주소값: %d\n", pd);
system("pause");
return 0;
}
- 문자열과 포인터
문자열: 문자 배열이면서, 끝에 '\0'이 붙은 것
char s[] = "hello";
// 실제로는: {'h', 'e', 'l', 'l', 'o', '\0'}
문자열을 가리키는 포인터
char *p = "hello";
- "hello"는 문자열 상수
- p는 그 문자열의 시작 주소를 가리킴
선언 방법에 따른 차이
char s[] = "hi"; | 문자열 복사해서 배열에 저장 | s는 내부에 복사된 데이터가 있고 수정 가능 ex) s[0] = 'H'; |
char *p = "hi"; | 문자열 상수의 주소만 가짐 | p는 상수 영역을 가리켜서 수정하면 오류 발생 가능 |
"문자열은 포인터처럼 다룬다"
(문자열 이름 = 시작 주소 = 포인터)
즉, 배열 이름이 배열 시작 주소로 자동 변환됨 --> *, +, [i] 등 포인터 문법을 사용 가능
배열의 이름은 첫 번째 배열 요소를 가리키는 포인터와 의미가 같다!
char name[] = "hi"; // == {'h', 'i', '\0'}
name == &name[0], 포인터랑 똑같이 동작!
- 문자열에 접근하기 위한 표현
name[0] | 'h' |
*name | 'h' |
name[1] | 'e' |
*(name + 1) | 'e' |
printf("%s", name); | "hello" |
printf("%s", name + 2); | "llo" |
"포인터로 선언한 문자열도 배열처럼 사용할 수 있다"
char *p = "hello";
printf("%c", p[0]); // 'h'
printf("%c", *(p + 1)); // 'e'
포인터인데 배열처럼 [] 를 사용할 수 있음
<핵심정리>
arr[i] | 배열 문법 | 배열에서 기본 |
*(arr+i) | 포인터 문법 | 배열도 OK |
p[i] | 포인터를 배열처럼 | 포인터도 OK |
*(p+i) | 포인터 정석 문법 | 포인터 전용 |
=> 이것이 배열은 포인터처럼, 포인터를 배열처럼 다룰 수 있다는 의미!
<예시>
char arr[] = "abc";
char *p = arr;
// 이 네 개는 모두 같은 문자 'b'를 출력함!
printf("%c\n", arr[1]); // 배열 문법
printf("%c\n", *(arr + 1)); // 포인터 문법
printf("%c\n", p[1]); // 포인터를 배열처럼
printf("%c\n", *(p + 1)); // 정석 포인터
<확인 문제>
아래 각 줄의 출력 결과를 예측하시오.
#include <stdio.h>
int main(void) {
char str[] = "world";
char *p = str;
printf("1. %c\n", str[3]); // ?
printf("2. %c\n", *(str + 3)); // ?
printf("3. %c\n", p[3]); // ?
printf("4. %c\n", *(p + 3)); // ?
printf("5. %s\n", p + 2); // ?
return 0;
}
<정답>
1. l
2. l
3. l
4. l
5. rld
(p는 "world"의 시작 주소 'w', p+2는 'r'부터 시작하는 주소, %s는 그 주소부터 끝까지 출력하니까 "rld"가 나옴)
<더 자세히>
주소 값
+--------+------+
| p | 0x100| ← p는 문자열 시작 주소
+--------+------+
메모리:
0x100 → 'w'
0x101 → 'o'
0x102 → 'r'
0x103 → 'l'
0x104 → 'd'
0x105 → '\0'
*p | 값 |
p | 주소 |
*p는 'w'
*p == 'w' // 첫 글자
*(p+1) == 'o' // 두 번째 글자
*(p+2) == 'r' // 세 번째 글자
p는 "world" 문자열의 시작 주소
p+1은 "o"부터 시작하는 다음 주소
printf("%s\n", p); // world
printf("%s\n", p + 1); // orld
printf("%s\n", p + 2); // rld
p+1 | "orld" 시작 주소 |
*(p+1) | 두 번째 글자 'o' |
%s | 주소에서 '\0'이 나올 때까지 문자열을 출력함 (= 그 주소부터 문자열 끝까지 출력해줌) |
- 문자열 수정
어떻게 선언했는가에 따라 다름!
배열로 선언한 경우: 수정 가능
char s[] = "hello";
s[0] = 'H'; // 가능!
포인터로 선언한 문자열: 수정 금지 (오류)
char *p = "hello";
p[0] = 'H'; // 오류 or 실행 중 에러 (Segmentation Fault)
why? "hello"는 상수 영역(읽기전용)에 저장됨.
*p는 그걸 가리키기만 하기 때문에 수정할 경우 에러 발생.
- 문자열 복사, 수정 함수 (배열에 저장된 문자열 대상으로 동작)
#include <string.h>
char s[20];
strcpy(s, "apple");
strcat(s, " pie");
<예제>
포인터에 배열 대입 후 증감연산
(책 11-9) << 오류 발견. 수정 예정입니다
#include <stdio.h>
//예제 11-9: 포인터에 배열 대입 후 증감 연산
int main(void) {
int a[] = { 100, 200, 300 };
int *pa;
pa = a; //중요!! 배열을 대입할 때는 & 를 사용하지 않음.
printf("int a[] = {100, 200, 300}일 때 포인터 증감 연산\n");
printf("\n포인터 *++pa의 증감 연산을 수행한 주소값과 데이터값\n");
printf("포인터 현재 주소값: %d\n", pa);
printf("현재 포인터 주소의 데이터값: %d\n", *a);
printf("포인터 *++pa 수행 후 데이터값: %d\n", *++pa);
printf("포인터 *++pa 수행 후 주소값: %d\n", pa);
printf("\n포인터 *--pa의 증감 연산을 수행한 주소값과 데이터값\n");
printf("포인터 현재 주소값: %d\n", pa);
printf("포인터 *--pa 수행 후 데이터값: %d\n", *--pa);
printf("포인터 *--pa 수행 후 주소값: %d\n", pa);
printf("\n포인터 ++*pa의 증감 연산을 수행한 주소값과 데이터값\n");
printf("포인터 현재 주소값: %d\n", pa);
printf("포인터 ++*pa 수행 후 데이터값: %d\n", ++*pa);
printf("포인터 ++*pa 수행 후 주소값: %d\n", pa);
system("pause");
return 0;
}
*++pa 수행 후 주소값 4바이트 증가
*--pa 수행 후 주소값 4바이트 감소
++*pa 수행 후 주소값 변동 없음 (데이터값에 대한 연산이기 때문)
<예제>
포인터와 배열을 사용하여 배열의 주소값과 포인터에 증감 연산을 수행, 해당 배열 요소의 값을 출력하기
(책 11-10)
#include <stdio.h>
#define R 5 //매크로 상수 선언
// R이라는 이름을 5로 대체하라 (유지보수 용이)
int main(void) {
int a[] = { 100, 200, 300, 400, 500 };
int cnt;
//배열 요소의 주소값 출력
for (cnt = 0; cnt < R; cnt++)
printf(" %d번째 배열 요소 a[%d] = &a[%d]\n", cnt + 1, cnt, &a[cnt]);
//주소값과 배열 요소의 값 출력
// 이때 a는 정수가 아니라 주소이므로, %u 를 사용함
// unsigned int (부호 없는 정수), 하지만 %p를 사용하는 것이 현재 표준
printf("\n배열의 현재 주소값: a는 %u\n\n", a);
printf("배열의 첫 번째 요소 값: *a는 %u\n\n", *a);
printf("증감 연산 후 주소값: a+1은 %u\n\n", a + 1);
printf("배열의 첫 번째 요소 값: *(a+1)은 %u\n\n", *(a+1));
system("pause");
return 0;
}
<예제>
포인터를 사용하여 배열 요소의 합계를 산출하기
(책 11-11)
#include <stdio.h>
#define R 5
//예제 11-11: 포인터로 배열 요소 합계 산출
int main(void) {
int a[R] = { 100, 200, 300, 400, 500 };
int* pa, cnt, hap = 0;
pa = a; //배열이므로 & 사용 X
for (cnt = 0; cnt < R; cnt++) {
hap += *pa++; //값의 자료형 참조 후 그만큼 주소값 증가(=다음 요소로 이동하며 요소 누적 합을 구함)
printf("%d번째 배열 요소: %d\n", cnt + 1, a[cnt]);
printf("포인터 주소: %d\n", pa);
}
printf("\n%d개의 배열 요소 합계: %d\n", cnt, hap);
system("pause");
return 0;
}
여기까지가 포인터 기초 입니다.
다음 편에서 포인터 활용을 다루고,
중간고사 이후에 문자와 문자열, 구조체, 동적 메모리 할당 등의 개념을 기초부터 다루겠습니다.
'프로그래밍 언어 > C' 카테고리의 다른 글
C언어 - 간단한 문법 예제 (중간 직전 review!) (0) | 2025.04.29 |
---|---|
C언어: 포인터의 활용 - 예제 (0) | 2025.04.21 |
C언어: 포인터의 활용 - 개념 (0) | 2025.04.13 |
C언어 기초 문법 빠르게 복습하기 (1) (0) | 2025.04.12 |
댓글