본문으로 건너뛰기

프로그래밍 기법

핵심 요약​

  • 배열 (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)을 이용해서도 구현할 수 있다.
재귀는 코드를 간결하게 만들 수 있는 장점이 있지만, 함수 호출에 따른 오버헤드가 발생할 수 있다.