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”;

 


+ Recent posts