본문으로 건너뛰기

프로그래밍 기법

핵심 요약

  • 배열 (Arrays): 동일한 자료형의 데이터 집합을 메모리의 연속된 공간에 저장하는 자료구조이다. 인덱스를 통해 각 요소에 빠르게 접근할 수 있으며, 1차원뿐만 아니라 다차원 형태로도 구성이 가능하다.
  • 포인터 (Pointers): 변수의 메모리 주소값을 직접 저장하고 다루는 변수이다. 메모리에 대한 직접적인 접근을 가능하게 하여 동적 메모리 할당, 배열 및 구조체의 효율적 관리 등 고급 프로그래밍 기법의 근간을 이룬다.
  • 구조체 (Structs): 서로 다른 자료형의 변수들을 하나의 논리적 단위로 묶어 새로운 자료형을 정의하는 기능이다. 배열과 달리 이질적인 데이터 그룹을 만들 수 있어, '레코드'와 같은 복잡한 데이터 형태를 표현하는 데 매우 유용하다.
  • 재귀호출 (Recursive Calls): 함수가 자기 자신을 다시 호출하는 프로그래밍 기법이다. 복잡한 문제를 동일한 구조를 가진 더 작은 문제들로 분할하여 해결하는 데 효과적이며, 특정 알고리즘을 간결하게 표현할 수 있게 한다.

배열 (Arrays): 심층 분석

배열은 동일한 자료형의 원소(element)들을 모아 메모리에 연속적으로 할당한 자료 그룹이다.
각 원소는 고유한 번호인 '인덱스(index)'를 통해 구별되며, C 언어에서 인덱스는 항상 0부터 시작한다.

1차원 배열

가장 기본적인 형태의 배열로, 데이터를 한 줄로 나열한 구조를 가진다.

  • 선언 형식: 자료형 배열이름 [배열 요소의 개수];
    • 자료형: 배열에 저장될 모든 원소의 데이터 타입을 지정한다.
    • 배열이름: 변수 이름 규칙에 따라 정의한다.
    • 배열 요소의 개수: 대괄호([]) 안에 배열의 크기를 명시한다.
  • 메모리 할당: 배열 선언 시 할당되는 총 메모리 크기는 (자료형의 메모리 크기) × (배열 요소의 개수)로 결정된다.
배열 선언 예의미메모리 할당 크기
char c[100];char형 원소 100개로 구성된 배열 c (c[0]~c[99])1바이트 × 100
int i[100];int형 원소 100개로 구성된 배열 i (i[0]~i[99])4바이트 × 100
short s[100];short형 원소 100개로 구성된 배열 s (s[0]~s[99])2바이트 × 100
long l[100];long형 원소 100개로 구성된 배열 l (l[0]~l[99])4바이트 × 100
  • 초기화: 선언과 동시에 중괄호{ } 안에 초깃값 리스트를 지정하여 초기화할 수 있다.
    • 자료형 배열이름[배열크기] = { 초깃값 리스트 };
    • 초깃값의 개수가 배열 크기보다 작으면 나머지 원소들은 0으로 자동 초기화된다.
    • 초깃값의 개수가 배열 크기보다 많으면 컴파일 오류가 발생한다.
  • 문자 배열: 문자열(String)을 저장하기 위해 char형 배열을 사용한다. 문자열은 문자들의 연속적인 나열이며, 큰따옴표(" ")로 표현된다.
    • 초기화 방법: 문자열 리터럴"String"을 사용하거나, 개별 문자를 리스트{'S', 't', 'r', 'i', 'n', 'g', '\0'}로 지정할 수 있다.

다차원 배열

2차원 이상의 배열로, 행과 열 또는 면, 행, 열의 구조를 논리적으로 표현한다.

  • 선언 형식:
    • 2차원: 자료형 배열이름 [행 개수][열 개수];
    • 3차원: 자료형 배열이름 [면 개수][행 개수][열 개수];
  • 구조:
    • 논리적 구조: 2차원은 표(table), 3차원은 입방체(cube) 형태로 인식된다.
    • 물리적 구조: 메모리에는 차원과 관계없이 모든 원소가 1차원적으로 연속되게 저장된다.
  • 초기화: 1차원 배열과 유사하게 초깃값 리스트를 사용한다. 차원별로 중첩된 중괄호를 사용하여 구조를 명확히 하거나, 모든 값을 나열하여 순서대로 초기화할 수 있다.



포인터 (Pointers): 심층 분석

포인터는 변수의 메모리 주소값을 값으로 가지는 특별한 변수이다.
포인터 변수가 특정 변수의 주소를 저장하는 것을 '해당 변수를 가리킨다(pointing)'고 표현하며,
이를 통해 메모리 영역에 직접 접근하고 제어할 수 있다.

선언 및 개념

  • 정의: "변수의 메모리 주소값" 그 자체 또는 주소값을 저장하는 "포인터 변수"를 의미한다.
  • 선언 형식: 자료형 *포인터이름;
    • * 기호는 해당 변수가 포인터임을 나타낸다.
    • 자료형은 포인터 자체가 아닌, 포인터가 가리킬

주요 연산자

포인터는 두 가지 핵심 연산자를 통해 동작한다.

연산자이름설명사용 예시
&주소 연산자변수 앞에 사용하여 해당 변수의 시작 메모리 주소를 반환한다.ptr = &i;
*참조 연산자포인터 변수 앞에 사용하여, 해당 포인터가 가리키는 메모리 주소에 저장된 값을 읽거나 쓴다.*ptr = 20; 또는
j = *ptr;

포인터 초기화 방법

포인터 변수에 유효한 주소값을 할당하는 주요 방법은 다음과 같다.

  1. 주소 연산자 & 사용: 기존 변수의 주소를 할당한다.
  2. 동적 메모리 할당: malloc 등의 함수를 통해 할당된 메모리의 시작 주소를 지정한다.
  3. 문자열 리터럴 할당: char* 포인터에 문자열의 시작 주소를 직접 지정한다.
  4. 배열 이름 사용: 배열의 이름은 그 자체로 배열의 시작 주소를 의미하므로 포인터에 할당할 수 있다.
  5. 배열 첫 번째 요소의 주소 사용: &array[0] 와 같이 배열의 첫 번째 요소의 주소를 명시적으로 지정한다.

고급 활용

  • 포인터 배열 (Pointer Array): 포인터 변수들로 구성된 배열. 자료형 *포인터배열이름[배열크기]; 형태로 선언한다. 각 배열의 원소가 포인터이므로, 여러 개의 문자열이나 동적으로 할당된 메모리 블록들을 효율적으로 관리할 수 있다.
  • 포인터의 포인터 (Pointer to Pointer): 다른 포인터 변수의 주소를 저장하는 포인터로, 이중 포인터라고도 한다. 자료형 **포인터이름; 형태로 선언한다. 포인터 배열을 함수 인자로 전달하거나, 함수 내에서 포인터 변수 자체의 값을 변경할 때 사용된다.



구조체 (Structs): 심층 분석

구조체는 하나 이상의 변수를 묶어 새로운 자료형으로 정의하는 기능이다. 배열과 달리, 서로 다른 자료형의 변수들을 멤버(항목)로 포함할 수 있다.

핵심 개념

  • 정의: "서로 다른 자료형을 그룹으로 묶을 수 있"는 사용자 정의 자료형.
  • 배열과의 차이점: 배열은 동일 자료형(homogeneous)의 집합인 반면, 구조체는 다른 자료형(heterogeneous)의 집합을 만들 수 있다.
  • 활용: 이름, 입사 연도, 연봉 등 여러 필드(field)로 구성된 레코드(record)와 같은 복잡한 데이터를 표현하는 데 매우 유용하다.

선언 및 사용

구조체는 3단계의 과정을 거쳐 사용된다.

  1. 구조체형 선언: struct 키워드를 사용하여 구조체의 이름과 내부 멤버 변수들을 정의한다.
  2. 구조체 변수 선언: 정의된 구조체형을 사용하여 변수를 선언한다.
  3. 구조체 변수 사용: 선언된 변수의 내부 멤버에 접근하여 데이터를 저장하거나 사용한다.
  • 선언 형식:
  • 사용 형식: struct 구조체형이름 구조체변수이름;

데이터 항목 참조

구조체 변수의 멤버에 접근하기 위해 두 가지 연산자가 사용된다.

연산자이름설명사용 예시
.점 연산자구조체 변수의 데이터 항목을 지정할 때 사용한다. (예: Lee[i].name)
->화살표 연산자구조체를 가리키는 포인터를 통해 데이터 항목을 지정할 때 사용한다. (예: ptr->name)

참고: 화살표 연산자 p->member(*p).member와 완전히 동일한 표현이다.

구조체 연산

  • 복사 연산: 같은 구조체형을 가진 변수들끼리는 대입 연산자(=)를 통해 모든 멤버의 내용을 한 번에 복사할 수 있다.
  • 주소 구하기 연산: 주소 연산자(&)를 사용하여 구조체 변수의 시작 주소를 얻을 수 있다.



재귀호출 (Recursive Calls): 심층 분석

재귀호출(순환호출)은 함수가 실행 도중에 자기 자신을 다시 호출하는 프로그래밍 기법이다.

핵심 개념

  • 정의: "자기 자신을 호출하여 순환 수행되는 것".
  • 원리: 해결하려는 문제를 동일한 구조를 가진 더 작은 하위 문제로 분할하고, 가장 작은 단위의 문제(베이스 케이스)부터 해결해 나가는 방식이다.
  • 베이스 케이스 (Base Case): 재귀의 연쇄적인 호출을 멈추게 하는 조건이다. 이 조건이 없으면 함수는 무한히 자신을 호출하여 스택 오버플로 오류를 발생시킨다.

작동 원리: 팩토리얼 예제

팩토리얼 함수 factorial(n)은 재귀의 대표적인 예이다.

  • 재귀적 정의: n > 0일 때, factorial(n) = n \_ factorial(n - 1)
  • 베이스 케이스: n = 0일 때, factorial(0) = 1

factorial(3)을 호출하면, 내부적으로 factorial(2), factorial(1), factorial(0)이 순차적으로 호출된다. factorial(0)이 베이스 케이스에 도달하여 1을 반환하면, 호출의 역순으로 결과값이 계산되어 최종적으로 3 * 2 * 1 * 1 = 6이 반환된다.

재귀의 종류

  • 직접 재귀 (Direct Recursion): 함수가 자기 자신을 직접 호출하는 경우 (예: factorial 함수).
  • 간접 재귀 (Indirect Recursion): 함수 X가 함수 Y를 호출하고, 다시 함수 Y가 함수 X를 호출하는 것처럼 다른 함수를 통해 간접적으로 자기 자신이 호출되는 구조.

구현

재귀 알고리즘이 적합한 경우는 문제, 함수, 또는 데이터 구조 자체가 재귀적으로 정의될 때이다.
재귀로 표현 가능한 대부분의 문제는 반복문(loop)을 이용해서도 구현할 수 있다.
재귀는 코드를 간결하게 만들 수 있는 장점이 있지만, 함수 호출에 따른 오버헤드가 발생할 수 있다.