데브코스 웹풀스택 과정/TIL

1107 프로그래밍 기초 문법 - 함수 포인터, 동적 메모리 할당, 사용자 정의 자료형, 객체지향언어 특징

thinktank911 2025. 11. 7. 10:11

함수 포인터

함수명 앞에 *만 붙여주면 함수 포인터가 선언된다.

자료형(함수 포인터 이름)(인자 목록)
  • 함수 포인터도 포인터이므로 주소값 저장

함수포인터 사용 이유

  • 메모리 크기 및 위치가 결정되는 시점은 컴파일 타임 또는 런타임 시점
  • 컴파일 타임 시점 결정은 정적 바인딩, 런타임 시점 결정은 동적 바인딩
    • 컴파일 타임 : C언어 ➡️ int a = 10
    • 런타임 시점 : 자바스크립트 ➡️ var a = 10;

동적바인딩 예

  • vs code의 extension들은 플러그인 방식으로 동작한다.
  • 새 기능 추가하면 매번 다시 컴파일 해야 하는 불편함이 있으나 플러그인 방식 사용 시 그럴 필요 없다.
  • 함수 포인터 사용은 프로그램의 유연한 확장성 제공
  • 플러그인 : 규격맞 맞춰놓고 끼웠다 넣었다 할 수 있는 기능

구조체

  • 하나 이상의 서로 다른 종류의 변수들을 묶어서 새로운 데이터 타입을 정의하는 것

구조체를 사용하는 이유

  • 연관된 변수를 하나로 묶어 관리함으로써 데이터 관리에 유용
  • 데이터 양(변수의 개수)이 많아지면 구조체가 유리함

구조체 정의

  • 구조체 기본형태(학생 정보)
    struct student
    {
      char name[10];
      int age;
      int height;
    }
  • struct 키워드는 구조체라는 데이터타입을 의미
  • student는 내가 만든 구조체 이름
  • name, age, height는 구조체 멤버

구조체 멤버에 접근하기

  • 구조체 변수를 통해 구조체 멤버의 값을 참조해야 한다.
    [구조체 변수명].[구조체 멤버]
    ex) st1.name, st1.age...
  • 멤버 접근 시 .[점] 사용을 직접 접근이라고 한다.
#include <stdio.h>
#include <string.h>

struct student {
   char name[10];
   int age;
   int height;
}st1;

int main() {
    strcpy(st1.name, "홍길동");
    st1.age = 20;
    st1.height = 170;

    printf("이름 : %s\n, 나이 : %d\n, 키 : %d\n", st1.name, st1.age, st1.height);
    return 0;
}

공용체

  • 공용체도 사용자 정의 자료형이다.
  • 구조체와 차이점은 메모리 공간을 공유한다는 점이다.
  • 타입스크립트에서도 사용됨

열거형

  • enumeration 약자로 enum이라고 읽음
  • 데이터들을 열거한 집합. 연속적 데이터를 정의할 때
  • 컴파일러는 열거형 멤버들을 정수형 상수로 취급한다.
    enum Week
    {
      sun = 0
      mon
      tue
      wed
      thu
    }
  • 열거형 멤버들 각 요일 나타냄
  • 첫번째 멤버 sun을 0으로 설정하면 다음 멤버 mon부터 1씩 증가
#include <stdio.h>
#include <string.h>

enum Week
{
    sun = 1,
    mon,
    tue,
    wed,
    thu,
    fri,
    sat
};

int main() {
    int day;


    printf("요일을 선택하세요 : ");
    scanf("%d", &day);

    switch(day){
        case sun:
            printf("일요일엔 짜파게티\n");
            break;
        case mon:
            printf("월요일\n");
            break;
        case tue:
            printf("화요일엔 짜파게티\n");
            break;
        case wed:
            printf("수요일엔 짜파게티\n");
            break;
        case thu:
            printf("목요일엔 짜파게티\n");
            break;
        case fri:
            printf("금요일엔 짜파게티\n");
            break;
        case sat:
            printf("토요일엔 짜파게티\n");
            break;
        default:
            printf("잘못입력하셨습니다.\n");
            break;
    }

    return 0;
}

메모리구조

메모리 영역

  • 스택 영역 : 우리가 지금껏 사용한 지역변수 및 매개변수 등은 모두 스택 메모리 사용
  • 매개변수 및 지역변수가 스택메모리에 어떤 구조로 저장되는지 보자
  • 힙영역(큐 메모리) : 힙은 컴퓨터 메모리의 일부가 할당되었다가 회수되는 일들의 반복문
    • 힙은 컴파일 시가 아닌 실행 시 사용자로부터 할당 메모리를 입력 받음
      ➡️ 동적 메모리 할당
  • 데이터영역 : 전역변수와 static 변수가 저장되는 메모리 영역, 이 메모리는 프로그램 종료시 소멸

동적 메모리 할당

동적으로 메모리 할당하는 이유

  • 일반 변수 선언 : 메모리 할당은 컴파일 타임에 이루어진다. int a; ➡️ 정적 할당
  • 전교생 10명인 학교의 학생수 배열로 선언한다면? int student[10]
  • 학생수가 늘어나게 되면? int student[100]
    ➡️ 이는 미봉책에 불과. 학생수가 어떻게 변할지 모른다.
  • 결국 학생 수는 유동적이므로 고정하지 말고 실행 시 결정하자
scanf("%d", &num);
int student[num];
  • 문제점
    • scanf는 런타임에 실행
    • int student[num]은 컴파일타임에 실행
    • 런타임에 입력받은 변수를 컴파일 타임에 대입하는 형태는 시점 논리에 맞지 않는다.
  • 해결방법
    • 동적 메모리 할당 기법 필요

동적 메모리 할당 및 해제

  • 동적 메모리 할당 함수의 원형
    void* malloc(sizeof(int)*size);
    • 전달인자 size는 바이트 단위로 입력
    • 메모리 할당이 되면 메모리의 주소값을 리턴
    • 메모리 부족 시 NULL 포인터를 리턴
    • void* 의미 : 타입이 지정되지 않는 포인터.
      (메모리 크기만큼 할당해줄테니, 메모리는 원하는 형태로 정해서 사용하라)
#include <stdio.h>

int main() {
    int num;
    int *student;

    printf("학생 수 입력 :");
    scanf("%d", &num);

    // 스택 = 힙 메모리
    student = (int*)malloc(sizeof(int) * num);

    if(student == NULL){
        printf("메모리가 할당되지 않았습니다.\n");
        return 0;
    }

    printf("메모리 크기 : %d\n", sizeof(int)*num);
    free(student);

    return 0;
}
  • 메모리 구조
    // 캡처
  • 메모리 할당 후 반드시 메모리 해제해줘야 한다. (메모리누수 방지)
  • 앞으로 객체 기반에서 new 연산자를 이용한 객체 생성의 메모리 구조는 모두 위 형태를 기반한다.

객체 지향 프로그래밍 방식

객체지향이란?

  • 객체 : 영문으로 Object, 사물을 나타내는 추상적인 개념
  • 지향 : 영문으로 Oriented, '~를 향한다'는 의미

구조적 프로그랭과 객체지향 프로그래밍

  • 구조적 프로그래밍 방식 - 순차적, 하향식, 폭포수 방식
    • 기능적인 기본 단위는 함수이다.
  • 객체지향 프로그래밍 방식
    • 기능 단위는 객체이다.
    • 대표적인 예가 이벤트 기반의 모든 윈도우 프로그램이다.
      ex> 메모장

추상화

  • 추상 : 대상에서 특징만을 뽑아낸 것
  • 우리 일상의 사물들은 관념적이고 추상적인 것들이 많다.
  • 사물의 공통점을 묶는 속성이 추상화
  • 객체지향의 철학 = 플라톤의 이데아?

캡슐화

  • 은닉하다, 숨긴다의 의미
  • 외부에서 내부를 볼 수 없게 한다는 의미
  • 마냥 숨기기만 한다면 데이터는 무용지물
  • 외부로부터 데이터를 조작할 인터페이스가 필요
  • 하나의 캡슐단위가 클래스 = 사용자 정의 타입, 구조체와 유사
    • 클래스 = 데이터 + 메소드
    • 은닉된 데이터 : 프로퍼티 필드 속성 멤버변수
    • 멤버함수 : 메소드, 인터페이스

클래스란

  • 사용자 정의 데이터 타입
  • 데이터와 메소드를 사용자인 내가 새로 정의한 데이터타입이기 때문에
    클래스를 추상적인 데이터타입이라고 함.
  • 클래스의 본질은 데이터타입이라는 점이다.
  • 구조체와 비슷하다.
  • 멤버 변수(데이터)와 멤버 함수(메소드)로 구성된다.

클래스의 구성

  • 사물의 특성을 정리하여 필드와 메소드로 표현하는 과정이 추상화
  • 추상화된 결과를 하나의 클래스에 포함시키고 스스로 보호하는 것이 캡슐화

클래스의 선언 형식

  • 클래스 선언 시 class 키워드를 쓰고, 그 뒤에 클래스 이름을 붙인다.
  • 클래스 요소로는 생성자, 멤버 변수, 메소드 등으로 구성
class 클래스 이름
{
    접근 지정자 클래스 이름(){...}
    접근 지정자 클래스 이름(){...}
    접근 지정자 데이터형 멤버 변수(필드);
    접근 지정자 데이터형 메소드();
}
  • 접근 지정자
    • public : 누구나 접근 가능
    • private : 내 클래스 내부에서만 접근 가능, 외부 접근 불가
    • protected : 상속 관계에 있을 때 상속 받은 자식 클래스에서 접근 가능.

객체 선언

  • 클래스의 본질은 데이터타입이다.
  • 데이터타입을 통해 변수를 선언할 수 있다. 변수 선언한다.
  • 클래스를 통해 선언한 변수를 객체라고 한다.

객체 생성

Dog a = new Dog();


➡️ a는 주소값 가지는 포인터

using System;

class Dog{
    private int eyes, nose, mouse, ears;
    public void bark(){Console.WriteLine("멍멍");}
}
class HelloWorld {
  static void Main() {
      Dog a = new Dog();
      a.bark();
  }
}

 

생성자의 개념

  • 모든 변수는 선언이 되면 값을 초기화해야 함 ➡️ 초기화함수
  • 객체도 본질적으로 변수이므로 선언되면 초기화해야 한다.
  • 객체 생성 시 초기화 전용 메소드를 제공하는데 바로 생성자(constructor)이다.
  • 객체 생성 시 자동으로 호출되는 메소드
// 생성자 : 초기화 전용 함수
public Dog(){
    eyes = 0;
    nose = 0;
    mouse = 0;
    ears = 0;
}

Dog a = new Dog();
using System;

class Dog{
    protected int eyes, nose, mouse, ears;
    public void bark(){Console.WriteLine("멍멍");}
    public Dog(){
        eyes = 0;
        nose = 0;
        mouse = 0;
        ears = 0;
    }
}

class Pudle : Dog{
  public Pudle() {
     base.eyes = 2;
     Console..WriteLine("푸들 눈 : {0}", eyes);
  }
}

상속성

부모 ⬅️ 자식

  • 이미 완성된 클래스를 다른 클래스에 상속할 수 있다.
  • 부모 클래스로부터 상속받을 때 클래스 이름 끝에 콜론(:)
    자바의 경우 extends 붙인 후 부모 클래스 이름 적음
  • 프로젝트 관점에서 상속 개념
    • A프로젝트(종료됨)
    • B프로젝트(신규과제)
    • A프로젝트 기능이 B프로젝트의 기능과 유사함
    • B프로젝트는 A프로젝트를 상속 받고, 추가 기능만 구현하면 됨

다형성

  • 함수 이름이 같더라도 전달인자의 타입이나 개수에 따라 구분된다.
  • 객체지향에서는 대표적으로 오버로딩과 오버라이딩 기법이 있다.

오버로딩

  • 과적하다, 적재하다 라는 의미
  • 겉모습은 똑같지만 내용이 다른 경우
  • 이름이 같은 함수일지라도 전달인자 타입이나 개수가 다른 경우
  • 스타크래프트 오버로드 유닛의 예
  • 코드로 풀어보면 같은 이름의 함수에 같은 개수의 전달인자가 서로 다른 타입으로 존재
using System;

public class Zerg{
    public void Overload(int zerggling){
        Console.WriteLine("저글링 {0}마리", zerggling);
    }

    public void Overload(int zerggling, int hydra){
        Console.WriteLine("저글링 {0}마리 + 히드라 {1}마리", zerggling, hydra);
    }

    public void Overload(int zerggling, int hydra, int lurker){
        Console.WriteLine("저글링 {0}마리 + 히드라 {1}마리 + 럴커 {1}마리", 
        zerggling, hydra, lurker);
    }

    public void Overload(char zerggling){
        Console.WriteLine("저글링 {0}등급", zerggling);
    }
}
class HelloWorld {
  static void Main() {
      Zerg zerg = new Zerg();
      zerg.Overload(10);
      zerg.Overload(10, 20);
      zerg.Overload(10, 20, 30);
      zerg.Overload('A');
  }
}

 

오버라이딩

  • 위로 올라탄다, 엎어친다는 의미
  • 기존의 것을 덮어버린다는 개념
  • 상속의 개념이 기반이 되어야 한다.
using System;

class Dog{
    protected int eyes, nose, mouse, ears;
    virtual public void bark(){Console.WriteLine("멍멍");}
    public Dog(){
        eyes = 0;
        nose = 0;
        mouse = 0;
        ears = 0;
    }
}

class Pudle : Dog{
  public Pudle() {
     base.eyes = 2;
     Console.WriteLine("푸들 눈 : {0}", eyes);
  }

  public override void bark(){Console.WriteLine("왈왈");}
}

class HelloWorld {
    static void Main(){
        Dog dog = new Dog();
        dog.bark();
        Pudle pd = new Pudle();
        pd.bark();

        dog = new Pudle();
        dog.bark();
    }
}

인터페이스

  • 메소드의 목록만을 가지고 있는 명세(Specification), 사용자 정의 타입이다.
  • 메소드의 목록만 선언하고 구현은 하지 않는다.
  • 인터페이스의 선언 형태
접근지정자 interface 이름 : 기반인터페이스
{

}
  • 인터페이스를 상속받는 클래스 형태
접근지정자 class 자식클래스이름 : 인터페이스
{

}

 

인터페이스 사용하는 이유

  • 인터페이스는 본체가 정의되지 않는 추상메소드만 갖는다.
  • 목적은 기존의 기능을 추가/수정하는 개념보다는 동일한 개념의 기능을 새롭게 구현하는 기능
  • 공동작업 시 표준을 정하는 역할

추상클래스를 상속하는 경우

  • 일반적으로 클래스를 상속하는 이유는 기능의 확장이 목적

인터페이스를 상속하는 경우

  • 상속관계의 개념이 아니라 여러가지 기능의 나열
  • 인터페이스에서 기능을 명세하고, 자식클래스에서 상속한다.
  • 인터페이스는 상속이 아니라 구현의 의미로 implements 사용
using System;

public interface IUnit{
    void Attack();
    void Move();

}

public class Zergling : IUnit{
    public void Attack(){
        Console.WriteLine("저글링 : 공격한다.");
    }

    public void Move(){
        Console.WriteLine("저글링 : 이동한다.");
    }
}

public class Dragoon : IUnit{
    public void Attack(){
        Console.WriteLine("드라군 : 공격한다.");
    }

    public void Move(){
        Console.WriteLine("드라군 : 이동한다.");
    }
}

class HelloWorld {
    static void Main(){
        Zergling zerg = new Zergling();
        zerg.Attack();
        zerg.Move();

        Dragoon dragoon = new Dragoon();
        dragoon.Attack();
        dragoon.Move();
    }
}

 

메모리 관리

  • 플랫폼 기반의 객체 지향 언어는 가비지 컬렉터가 메모리를 자동관리한다.
  • 백그라운드에서 더이상 사용되지 않는 메모리를 찾아 회수한다.

람다를 통한 화살표 함수의 이해

익명 메소드

  • 메소드를 미리 정의하지 않고 사용할 때 정의한다.
  • 익명 메소드를 사용하면 코드가 간결해진다.
  • 익명 메소드는 별도의 메소드를 만들지 않으므로 코딩 오버헤드를 줄일 수 있다.
  • 내용 자체가 목잡하면 안된다.
  • 익명 메소드는 람다식에서 사용됨다.

람다식이란

  • 기존 익명 메소드를 더욱 간결하게 만든다.
using System;
class HelloWorld {
    static int Add(int a){
        return a + 1;
    }
    delegate int CalcDele(int x);

  static void Main() {
    Console.WriteLine(Add(3));

      CalcDele d = delegate (int x){
          return x + 1;
      };

      Console.WriteLine(d(3));
  }
}

 

람다식표현

(인수)=> 표현식 또는 명령문

delegete 키워드를 사라지며 화살표로 대체한다.

CalcDele d = x => x + 1;