1절 배열
l 프로그램의 효율성을 위해서는 처리할 자료에 따라 적합한 자료구조를 찾는 것이 중요.
l 자료구조 : 응용 프로그램에서 처리할 자료를 조직화하고 저장하는 방법
l 배열 : 자료형이 같은 여러 개의 연속된 기억공간에 같은 이름(배열명)으로 저장
자료형이 같은 것만 저장 가능
배열의 크기에 맞게 주기억장치의 연속된 기억장소에 저장됨
배열의 원소 / 첨자(인덱스)
1. 1차원 배열
n 일렬로 연속된 구조로 자료 저장
n 특정 원소 한개를 명시하기 위해 원소가 배열에 저장된 순서에 따라 1개씩 증가하는 첨자 1개 사용
1) 1차원 배열의 선언과 배열 원소
(1) 1차원 배열의 선언
o 자료형 배열명 [배열 원소의 수];
o 배열 원소 수와 상관 없이 배열에 저장되는 모든 값의 자료형이 같아야 함
o 배열 원소 수 = 배열의 크기 -> 정수형 상수만 가능
(2) 1차원 배열의 초기화
o 자료형 배열명[배열 원소 수] = {초깃값 목록};
o Int b[5] = {1,2,3,4,5}; : 배열 원소에 {} 순서대로 저장됨
o Int c[10] = {0}; : {}안에 초기값 개수가 배열원소 수보다 적으면 나머지 원소는 0으로 초기화 됨
o Int d[ ]={1,2,3,4,5}; : 배열 원소 수 명시하지 않으면 {} 안 개수가 배열 원소 수로 결정 됨
o 초깃값 명시하지 않으면서 [ ]안 원소 수 생략하면 오류 발생
o { }안 초기값 배열이 배열 원소 수보다 많으면 오류 발생
(3) 1차원 배열의 원소 참조
o 배열명[첨자]
- 첨자 : 0 ~ (배열의 원소 수 -1) 범위의 정수
- 배열에 저장된 첫 번째 원소 인덱스 : 0
- 마지막 원소 인덱스 : (배열 원소 수-1)
- c[2]++ : 배열 c의 세 번째 원소에 저장된 값을 1 증가
2. 1차원 배열의 입출력
u 배열 원소 단위로 입출력하거나 저장
u 반복문 이용
u Scanf( )를 사용해 입력값 저장할때 &배열명[첨자]로 하나씩 입력
1) 2차원 배열의 선언과 배열 원소
(1) 2차원 배열의 선언
o 자료형 배열명[행 개수][열 개수];
(2) 2차원 배열의 초기화
o 자료형 배열명[행수][열수] = {{1행 초기값 목록},{2행 초기값 목록}, … };
o 행의 개수는 생략할 수 있어도, 열의 개수는 생략할 수 없음
- 열의 개수로 행수를 간주함
(3) 2차원 배열 원소의 참조
o 행 첨자, 열 첨자 필요
o 배열명 [행 첨자][열 첨자]
2) 2차원 배열의 입출력
u 입출력과 대입문에서 배열 원소 단위로 처리
u 이중으로 중첩된 반복문으로 표현
3) 2차원 배열의 입력 : 행 단위 입력과 열 단위 입력
u 한 행씩 차례로 입력 or 한 열씩 차례로 입력
u Scanf( )를 사용해 입력값 저장할때 &배열명[행첨자][열첨자]로 주소 명시
(1) 행 단위 입력
o 배열 원소 입력받을 때 행 첨자 같고 열 첨자만 변함
o 열 첨자 안쪽 for문 제어변수 , 행 첨자 바깥쪽 for문 제어변수
(2) 열 단위 입력
o 배열 원소 입력받을 때 열 첨자 같고 행 첨자만 변함
o 행 첨자 안쪽 for문 제어변수 , 열 첨자 바깥쪽 for문 제어변수
3. Char형 배열을 이용한 문자열 처리
n 문자열 처리 방법 : (1) char형 배열 (2) char형 포인터. string 자료형을 지원하지 않음
1) char형 배열을 이용한 문자열 처리
u 문자열 상수 : " "로 묶어 놓은 연속된 문자들
o 끝에 자동으로 null(\0)문자 포함됨
o " " 안의 문자 수 + 1 BYTE
u char형 배열로 배열 안에 한 글자씩 넣어야 함
(1) 문자열 저장할 때 배열 선언
o 문자열의 길이 : 널문자 이 전까지의 문자 개수
o 배열 크기 지정 : 배열 원소 개수 + 1 (널문자 포함)
(2) scanf() / printf() 를 이용한 문자열의 입출력
o char형 1차원 배열에 저장된 문자열 출력하거나 배열로 문자열 입력 받을 때 배열명만 명시
o 배열명 자체가 배열의 시작 주소!
o 기억장소 구하기 위해 배열명 앞에 & 사용할 필요 없음
- 배열의 한 원소에 접근할 시 & 붙여야 함
- Scanf(%s, array); O / printf(%s, array);
- Scanf("%c, &array[2]);
(3) 문자열 전용 입력 함수 : gets()
o 공백을 포함하는 문자열 입력받음
o 문자열만 받으므로 변환명세(%s)필요 없음
o gets(char형 1차원 배열명);
(4) 문자열 전용 출력 함수 : puts()
o 문자열을 출력한 후 자동 개행
o puts(char형 1차원 배열명);
(5) 문자열에 포함된 문자의 처리
o 배열의 특정 원소의 값을 직접 입력받을 때 원소의 해당 주소를 구하기 위한 주소 연산자 사용
o scanf(“%c, &array[2]);
o printf(%c, array[3[);
(6) 문자열의 끝을 의미하는 널문자의 중요성
o 배열에 널문자 포함하지 않으면 문제는 발생하지 않지만 결과 예측 X
o 문자열 단위로 출력할 때 문자열이 저장되어 있는 첫 기억장소부터 차례대로 출력하고 널문자를 만날 때 까지 출력
o 문자열 처리 시 문자열 끝에 널문자 반드시 포함하도록 처리해야 함
2) char형 2차원 배열을 이용한 여러 개의 문자열 처리
u 배열명 뒤 행 첨자만 명시 : 해당 행에 저장된 문자열 의미 -> 해당 행의 시작 주소에 해당함
u char형 2차원 배열에 저장된 문자열의 입출력
o scanf(“%s”, 배열명[행 첨자]); : (행 첨자 + 1)째 행의 문자열 입력
o printf(“%s”, 배열명[행 첨자]); : (행 첨자 + 1)째 행의 문자열 출력
o gets(배열명[행 첨자]); : 엔터키 이 전까지의 문자열을 (행 첨자 + 1) 째 행에 입력
o puts(배열명[행 첨자]); : (행 첨자 + 1) 째 행의 문자열 출력 후 개행
u char형 1차원 배열명 : 문자열의 시작 주소
u char형 2차원 배열명 : 전체 배열의 시작 주소
u 2차원 배열명[행 첨자] : (행 첨자 + 1)째 행의 시작 주소. 열에 대한 첨자 생략으로 같은 행 문자열을 의미
u 배열 원소를 입력 받을 때 scanf(%c, &array[2][4]); 처럼 & 붙여야 됨
4. 3차원 배열
n 배열 선언 : 면, 행, 열 개수
5. 배열 원소를 함수로 전달하기
n 배열 원소 함수로 전달 시 값에 의한 호출 / 주소에 의한 호출 둘 다 사용 가능
n 배열 전체를 함수로 전달 시 주소에 의한 호출만 사용 가능
n 함수 호출 : 함수명 (배열명[첨자])
n 함수 정의 : 반환자료형 함수명 (자료형) { }
2절 포인터
l 포인터 변수 : 데이터가 저장된 주기억장치의 주소만 저장
1. 포인터
1) 주기억장치의 주소
u 주기억장치 : CPU가 실행할 명령어 코드와 처리할 데이터를 저장하기 위한 기억장치
u 프로그램의 코드와 데이터가 주기억장치에 저장된 후 CPU가 프로그램의 코드를 실행하여 데이터 처리
u 메모리 : 바이트 단위로 나뉨. 각 바이트에는 주소 지정됨
o 주소 : 변수가 위치하는 곳
o & : 변수의 주소를 가져오는 연산자
u %p : 주소값 나타낼 때 사용하는 변환명세. 16진수로 표현
u 변수의 메모리 할당 : 임의의 위치에 자료형만큼의 byte가 부여됨
u 배열의 메모리 할당 : 임의의 위치에 연속해서 자료형만큼 byte가 부여됨
o 배열 이름 : 전체 배열의 주소(배열이름 자체가 배열의 주소를 나타냄) & 배열의 첫 번째 주소
o &a[0] = a (&a X)
2) 포인터의 개념과 필요성
(1) 참조 불가능한 변수 간접적으로 참조 가능
o 호출된 함수에서는 자신을 호출한 함수의 지역 변수를 직접 참조할 수 없음
o 포인터로 간접적으로 참조함
(2) 프로그램의 성능 개선, 기억공간 효율적으로 사용
o 크기가 큰 배열이나 구조체를 함수에 인수로 전달할 때 값 복사를 하게 되면 메모리 낭비가 큼
o 배열이나 구조체의 시작 주소만 인수로 전달하고 함수에서는 포인터를 이용해 배열과 구조체를 참조하면 메모리 절약 가능
o 동적 할당과 포인터는 트리나 연결리스트 같은 자료구조 구현에 유용
- 동적 할당 : 프로그램 실행하면서 필요한 만큼의 기억 공간을 할당 받아 사용하고 나중에 필요 없어진 것 해제하는 것
3) 포인터를 사용하기 위한 세 가지 과정
(1) 포인터 변수도 일반 변수처럼 선언해야 사용 가능
(2) 포인터 변수에는 가리키고 싶은 기억장소의 주소를 대입
(3) * 연산자 : 포인터 변수에 저장된 주소를 이용해 다른 기억장소를 참조할 때 사용(*p : 포인터 변수 p가 가리키는 곳에 있는 것)
2. 포인터의 사용
n 사용하기 전에 선언하고, 선언 후 변수에 가리킬 곳 주소 대입, 간접 연산자 이용해 참조
1) 포인터 변수 선언
u 자료형 *포인터 변수명; / 자료형 *포인터 변수명, *포인터 변수명 ;
u * : 간접 참조 연산자 / 선언 할 때는 단순히 포인터 변수임을 표시
u 자료형 : 포인터 변수가 가리키는 기억장소에 저장될 자료의 형
o 포인터 변수가 차지하는 기억장소 크기 : 일반적으로 4BYTE (32Bit)
o 포인터 변수 크기는 컴파일러에 따라 다름
char *p; |
- char형 포인터 변수 p 선언 - p가 가리키는 곳에 저장될 값의 자료형이 char형
|
2) 주소 연산자 &와 주소 대입
u 포인터 변수에는 가리킬 변수가 위치한 기억장소의 주소를 저장 -> 변수의 주소값(번지)을 저장
u &변수명 : &로 변수의 실제 주기억장치 번지를 구함
u 포인터 변수명 = &변수명; 포인터 변수에 변수의 주소값(번지)을 대입
u 주소 연산자 &
o 주소 구하는 연산자. 주기억장치에서의 번지수를 구함
o 일반 변수 , 포인터 변수 등에 사용 가능
o 상수에는 사용할 수 없음
3) 간접 연산자 *
u * : 간접 참조 연산자 / 역참조 연산자
o 피연산자인 주소 값을 이용해 주소로 찾아가 참조
(1) 일반 변수의 직접 참조
o 변수명을 사용하여 변수를 직접 참조
o var = 100;
printf(%d, var);
(2) 포인터 변수의 직접 참조
o 포인터 변수에 저장된 주소를 읽어오거나 포인터 변수에 새로운 주소를 저장할 수 있음
o 포인터가 가리키는 곳의 내용을 참조할 수 없음
o int var = 100;
int *ptr = &var; // ptr에 var의 주소를 대입함. ptr에는 var의 주소 번지가 저장됨
printf(%d, ptr); // ptr에 들어있는 var의 주소값을 출력함. ptr을 직접 참조한 것
(3) 포인터 변수의 간접 참조
o *를 사용 (~가 가리키는 곳!)
o 포인트 변수에 저장된 주소에 해당하는 기억장소를 참조
4) 포인터 변수와 일반 변수 비교
(1) 변수의 두 가지 의미 : 저장된 값 / 기억장소
(2) 포인터 변수와 일반 변수의 차이 요약
double grade = 4.0; |
è double형 변수 선언 |
double *ptr = &grade; |
è double형 변수를 가리킬 포인터 변수 선언 &grade=100이라고 가정. |
|
|
printf(%lf, grade); |
è 4.000000 출력 |
printf(%lf, *ptr); |
è 4.000000 출력. ptr이 가리키는 곳의 값을 가져옴 |
|
|
printf(%lf, grade +1 ); |
è 5.000000 출력. 변수에 저장된 값과 1을 더함 |
printf(%u, ptr+1 ); |
è 108 출력. double형 포인터 다음 번지를 출력한 것. ptr(100) + 8(double형 크기) |
|
|
*ptr += 1; |
è ptr이 가리키는 곳의 값을 1 증가시킴 |
printf(%lf, *ptr) |
è 5.000000 출력 |
o 포인터 변수 p가 있을 때
p – 1 : p 기준 하나 앞 메모리 주소 / p + 1 : p기준 하나 뒤 메모리 주소
하나 앞 뒤의 기준은 p가 가리키는 변수의 자료형에 따라 달라짐
5) 간접 연산자, 가감 연산자, 증감 연산자의 우선 순위
u ++, -- > 간접 연산자(*) > +, -
(1) *p + 1 : *p와 1을 더함. à p가 가리키는 곳의 값과 숫자 1을 더함
(2) *(p + 1) : (p + 1)을 먼저 수행한 후 * 적용 à p가 가리키는 곳 하나 뒤의 장소를 간접 참조
(3) *p1++ : p++ 수행 후 * 적용 à *(p + 1) 과 같이 p 다음 장소 참조
(4) (*p)-- : *를 먼저 적용하여 p가 가리키는 곳의 값이 1 감소
3. 포인터와 배열
n 포인터에 대한 가감 연산 à 배열 참조할 때 사용
n 배열명 : 배열의 시작 주소 ! a = &a[0]
n 배열의 원소를 참조하는 방법
-> 배열명(배열의 시작 주소)에 대한 덧셈 연산 : 배열명과 첨자 사용
-> 간접 연산자 이용 : *(array + 1)
1) 배열명은 배열의 시작 주소
u 배열명 : 주기억장치 주소인 상수 값. 배열 시작위치 알리는 포인터 상수
u 배열명 array = 배열 시작 주소 = 첫 원소의 시작 주소 (&array[0])
주소 값 그대로 라는 것!
*array는 주소값 100이 가리키는 곳에 있는 내용물 이란 거겠지
u 배열명은 포인터 상수이기에 포인터에 대한 가감 연산 가능
*(array + i) : *(array는 포인터 상수 + 자료형의 크기 * i) == array[i]
array[0] == *(array +0) array는 주소값. 주소값 + 0 이므로 변화한 거 없음
array[1] == *(array +1) array[2] == *(array +2)
2) 포인터 변수를 이용한 배열 원소 참조
u 포인터 변수를 마치 배열명처럼 사용할 수 있음
o ‘배열명[첨자]’가 내부적으로는 ‘*(배열명+첨자)’로 처리 됨
∵ 배열명이 배열의 시작 위치인 포인터 상수이므로! *(array + 1)!!
o 반대로! 특정 포인터 변수가 배열 시작 위치를 가리키는 상태라면 ‘포인터 변수명[첨자]’도 가능
o ‘포인터 변수명[첨자]’는 내부적으로 ‘*(포인터 변수명+첨자)’로 처리될 것
포인터 변수명 = 배열명; |
è ptr = array; |
배열명[첨자] = *(배열명 + 첨자) |
è array[1] = *(array + 1) |
포인터 변수명[첨자] = *(포인터 변수명 + 첨자) |
è ptr[2] = *(ptr + 2) |
array[0] = *(array + 0) = *(ptr + 0) = ptr[0]
array[i] = *(array + i) = *(ptr + i) = ptr[i]
4. 포인터와 함수
n 포인터를 사용하지 않는 코드보다 가독성 떨어지고 처리과정도 복잡하고 실행 시간 오류도 많이 발생할 수 있음. 그래도 장점이 있다!
1) 포인터를 이용한 주소에 의한 호출
u 주소에 의한 호출 방식
o 함수 호출 시 인수의 주소를 전달하고 호출된 함수는 전달 된 주소를 포인터 매개변수에 저장
o 포인터 매개변수가 인수를 가리키게 되어 호출된 함수에서는 포인터 매개변수의 간접참조 이용해 인수 참조
u 호출 : 함수명(&인수명); 정의 : 반환형 함수명(자료형 * 포인터 변수명)
포인터 변수가 인수를 가리키게 되고, 포인터 변수명의 자료형은 전달받는 인수 자료형과 동일
u 값에 의한 호출: 인수와 매개변수가 서로 다른 기억장소 차지 -> 이름이 같아도 서로 다른 변수
u 주소에 의한 호출: 매개변수가 인수를 가리키는 포인터 변수
-> 간접 참조로 인수 기억장소 참조 가능
-> 다른 장소에 선언 된 변수를 다른 블록 안에서 참조해 변경 가능하다는 것!
2) 배열을 함수의 매개변수로 사용하는 경우
u 함수를 호출하면서 배열을 통째로 전달하는 것은 불가능
u 포인터를 이용해 함수 간에 배열을 전달하는 방식으로 전달
정의된 함수 내에서 ‘포인터 변수[i]’ 또는 *(포인터 변수명 + i)로 원소 참조
u int ages[50];
convert(ages);
void convert(int *years){ *years , *(years + 1) 등으로 배열 ages 참조 가능}
5. 포인터와 문자열
n 문자열 처리 방법! char형 배열 / char형 포인터
1) char형 배열과 char형 포인터를 이용한 문자열 저장
u C언어에서는 문자열형 포인터 사용 불가능!(문자열형 자료형이 없지..일단)
à char형 포인터로 선언
u char str1[10] = “language”;
배열의 크기에 해당하는 기억장소에 초기값으로 지정한 문자열 상수가 저장되고 바로 뒤에 문자열 끝을 나타내는 널문자가 저장됨
u char *str2=“language”;
(초기값인 문자열 상수의 길이 + 1)개의 문자를 저장할 수 있는 공간에 문자열과 문자열 끝을 나타내는 널문자가 저장되며 이 문자열이 저장된 기억장소의 시작 주소가 str2에 저장됨
(1) char형 배열을 이용한 문자열 처리
o char형 배열은 문자열 끝을 의미하는 널문자가 포함되도록 배열 원소 수를 저장할 최대 문자 수 보다 1 많게 선언해야 함
o str1은 배열의 시작 주소인 포인터 상수
-> str1 =” aaa”;처럼 대입문을 이용해 내용 수정 불가능
o 문자열 내용을 새로운 문자열로 변경하고 싶다면 #include <string.h> 하고 strcpy() 함수 사용. strcpy(str1, “aaa”);
(2) char형 포인터를 이용한 문자열 처리
o char형 포인터는 문자열 복사, 수정 등을 처리할 때 문자열의 끝에 언제나 널문자 포함되게 해야 함
o 선언하면서 초기화 한다면 언제나 문자열 끝에 자동으로 널문자 포함됨 -> 배열처럼 원소 수에 신경 쓸 필요 없음
o 문자열 입력, 둘째 문자 입력, 문자열 복사 처리할 때 str2에 동적으로 할당된 기억장소가 있을 때만 가능
o str2는 포인터 변수이므로 str2=”aaa”;처럼 대입문으로 내용 수정 가능
2) 문자열과 관련된 대표적인 함수의 사용
예) char s1[10] = “start”; char s2[10] = “end”;
'[시험] 독학사 2020 전공기초' 카테고리의 다른 글
[독학사][C프로그래밍] 08 파일처리함수 (0) | 2020.04.28 |
---|---|
[독학사][C프로그래밍] 07 구조체와 공용체 (0) | 2020.04.28 |
[독학사][C프로그래밍] 05 함수와 기억클래스 (0) | 2020.04.28 |
[독학사][C프로그래밍] 04 제어문 (0) | 2020.04.28 |
[독학사][C프로그래밍] 03 입 ∙ 출력함수와 연산자 (0) | 2020.04.28 |