크래프톤 정글 TIL : 0725

lazyArtisan·2024년 7월 25일
0

정글 TIL

목록 보기
25/147

📚 Journal


코치님 comment

  • 코테 챗gpt 어뷰저 때문에 문제를 왜 이렇게 풀었는지 설명할 줄 알아야 한다.
  • what? why? how?를 말할 줄 알아야 함
  • 프로그래밍 문파 : 절차지향, 객체지향, 함수형, 논리형
  • 1주차 recap : 정렬, 빅오, 시간복잡도 공간복잡도, 제일 큰 차수 남기는 이유, 선형 자료구조
  • 2주차 recap : 비선형 자료구조, dfs, bfs
  • 3주차 recap : dp (2가지 조건이 무엇이었는가?), 그리디, 각각을 써야하는 이유를 갖고 써야 한다


📝 배운 것들


🏷️ 가상메모리

자원의 추상화

  • 1개의 자원을 여러 개로 나눌 수 있는 것처럼 여기거나,
  • 여러 개의 자원을 하나인 것처럼 생각하는 과정들

가상메모리 : 컴퓨터가 모두에게 자신을 혼자만 이용하는 것처럼 착각하게 함
가짜 여러 개 만들어서 짜잔! 이게 컴퓨터야! 한 다음에 진짜 자원을 가짜들에 배분

  • (1) 컴퓨터 리소스를 '추상화'시켜서
  • (2) 하나의 물리 리소스를 여러 개의 논리 리소스처럼 기능 시키거나
  • (3) 여러 개의 물리 리소스를 하나의 논리 리소스처럼 기능하게 하는 것

🏷️ 파이썬과 다른 C언어 특성들

파이썬과 C 언어의 특성을 비교하면서, 특히 C 언어의 주요 개념들을 자세히 설명해 드리겠습니다. 여기서는 선언과 정의, 전방 선언, staticextern 키워드, enumunion, 컴파일과 링크 과정, 포인터, 동적 메모리 할당, 배열, 함수 호출 방식, 가변 인자, 전처리기 명령어, 리스트 반복(iteration), 비트 연산자 등을 다루겠습니다.

1. 선언(declaration)과 정의(definition)

선언 : 그냥 이런 거 있어요~
정의 : 이런 식으로 쓸게요~ 메모리 주세요~
헤더 파일 : 다른 파일에 있는 선언 가져다 쓸거에요~
소스 파일 : 다른 파일에 있는 정의 가져다 쓸거에요~

  • 선언(declaration): 변수나 함수의 타입과 이름을 컴파일러에 알리는 것입니다. 실제 메모리를 할당하거나 코드 내용을 제공하지 않습니다.

    extern int x; // x라는 이름의 정수형 변수가 있다고 선언
    int func(int, char); // func라는 함수가 있다고 선언
  • 정의(definition): 변수나 함수의 실제 메모리를 할당하거나 함수의 구현 코드를 제공합니다.

    int x = 10; // x 변수 정의 및 초기화
    int func(int a, char b) { // func 함수 정의 및 구현
        return a + b;
    }
  • 헤더 파일(.h): 주로 선언을 포함합니다. 여러 파일에서 동일한 선언을 공유하기 위해 사용됩니다.

    // myheader.h
    extern int x;
    int func(int, char);
  • 소스 파일(.c): 주로 정의를 포함합니다. 실제 실행 코드를 담고 있습니다.

    // mysource.c
    #include "myheader.h"
    
    int x = 10;
    
    int func(int a, char b) {
        return a + b;
    }

2. 전방 선언(forward declaration)

전방 선언 : 나중에 나오는 함수 땡겨와야 할 필요성 있을 때 사용

전방 선언은 함수나 변수가 이후에 정의될 것이라는 것을 컴파일러에 알리는 것입니다. 주로 순환 참조 문제를 해결할 때 사용됩니다.

    void funcB(); // 전방 선언

    void funcA() {
        funcB(); // 나중에 정의될 함수 호출
    }

    void funcB() {
        // 함수 정의
    }

3. staticextern의 개념

static : 다른 파일에서 못 쓰게 만듦
extern : 다른 파일에 있는거 가져옴

  • static: 변수나 함수의 가시성을 제한합니다. 변수는 해당 소스 파일 내에서만 접근 가능하고, 함수는 파일 내에서만 호출 가능합니다.

    static int x = 10; // 이 파일 내에서만 접근 가능
    static void func() { // 이 파일 내에서만 호출 가능
        // 함수 구현
    }
  • extern: 다른 파일에 정의된 변수나 함수의 선언을 명시합니다.

    // file1.c
    int x = 10; // 정의
    
    // file2.c
    extern int x; // 선언

4. enumunion 개념

enum : 내가 쓸 비슷한 데이터들 묶어놓기

struct (구조체) : 멤버 변수 각각마다 메모리를 할당해줌

union : 멤버 변수 중 딱 하나만 공간 차지 가능

union student {
	int age;
    double grade;
}
  • enum: 열거형. 관련된 상수들을 그룹으로 묶는 데 사용됩니다.

    enum Color { RED, GREEN, BLUE };
    enum Color myColor = RED;
  • union: 공용체. 하나의 메모리 공간을 여러 타입으로 재사용할 수 있도록 합니다.

    union Data {
        int i;
        float f;
        char str[20];
    };
    union Data data;
    data.i = 10;

5. 컴파일과 링크를 통해 실행 파일이 만들어지는 과정

  • 컴파일: 소스 코드를 목적 코드(오브젝트 파일)로 변환합니다.

    gcc -c mysource.c -o mysource.o
  • 링크: 여러 목적 파일을 결합하여 실행 파일을 만듭니다.

    gcc mysource.o -o myprogram
  • 단계 요약:

    1. 전처리: #include, #define 등 전처리 지시문 처리.
    2. 컴파일: 소스 코드를 어셈블리 코드로 변환.
    3. 어셈블: 어셈블리 코드를 목적 코드로 변환.
    4. 링크: 목적 코드와 라이브러리 등을 결합하여 실행 파일 생성.

6. 포인터

void : 함수로 선언하면 리턴값 없는거
void*로 선언하면 아무 메모리 주소 값 저장 가능.
void**는 메모리 주소가 들어있는 메모리 주소

왼쪽에 붙어있는 : 역참조 (오른쪽 하나 지워주는 걸로 생각해도 됨)

int64_t

  • 정의: int64_t는 C99 표준과 이후의 C 표준, 그리고 C++11 표준과 이후의 C++ 표준에서 제공하는 고정된 너비의 정수형 타입입니다. <stdint.h> 헤더 파일에 정의되어 있습니다.
  • 크기: 항상 64비트 크기를 가지며, 플랫폼에 관계없이 동일한 크기를 보장합니다.
  • 표현 범위: -9,223,372,036,854,775,808에서 9,223,372,036,854,775,807까지의 값을 가집니다.
  • 이식성: 고정된 너비와 표준화된 타입이기 때문에, 다양한 플랫폼에서 일관된 동작을 보장합니다.

포인터는 다른 변수의 메모리 주소를 저장하는 변수입니다.

void** ptr = 0x00000000FFFF8392; // ptr의 메모리 주소가 들어있는 메모리 주소
char* name = (char*) *ptr;
printf("%s", name);
void** ptr2 = 0x00000000FFFF3821;
int64_t* number = (int64_t*) *ptr2;
printf("%p", *number);

1. void** ptr = 0x00000000FFFF8392;

  • 설명: ptrvoid* 타입의 포인터를 가리키는 포인터입니다. 여기서 0x00000000FFFF8392는 임의의 메모리 주소를 가리키는 예시 값입니다.
  • 의미: ptr은 포인터의 포인터이며, 0x00000000FFFF8392 주소에 저장된 값을 가리킵니다. 이 주소에 실제로 어떤 값이 저장되어 있는지는 알 수 없지만, 다음 줄에서 이 값을 사용합니다.

2. char* name = (char*) *ptr;

  • 설명: ptr이 가리키는 주소의 값을 역참조(dereference)하여 char* 타입으로 변환합니다.
  • 의미: ptr이 가리키는 주소에 저장된 값을 char*로 캐스팅하여 name에 저장합니다. name은 문자열의 시작 주소를 가리키게 됩니다.

3. printf("%s", name);

  • 설명: name이 가리키는 문자열을 출력합니다.
  • 의미: name이 가리키는 주소에 저장된 문자열을 출력합니다. 이때, name은 이전 줄에서 ptr이 가리키는 주소에 저장된 값을 char*로 캐스팅한 것입니다.

4. void** ptr2 = 0x00000000FFFF3821;

  • 설명: ptr2void* 타입의 포인터를 가리키는 포인터입니다. 0x00000000FFFF3821은 또 다른 임의의 메모리 주소를 가리키는 예시 값입니다.
  • 의미: ptr2는 포인터의 포인터이며, 0x00000000FFFF3821 주소에 저장된 값을 가리킵니다.

5. int64_t* number = (int64_t*) *ptr2;

  • 설명: ptr2가 가리키는 주소의 값을 역참조하여 int64_t* 타입으로 변환합니다.
  • 의미: ptr2가 가리키는 주소에 저장된 값을 int64_t*로 캐스팅하여 number에 저장합니다. numberint64_t 타입의 값을 가리키게 됩니다.

6. printf("%p", *number);

  • 설명: number가 가리키는 값을 포인터 형식으로 출력합니다.
  • 의미: number가 가리키는 주소의 값을 포인터 형식으로 출력합니다. 이때 numberptr2가 가리키는 주소에 저장된 값을 int64_t*로 캐스팅한 것입니다.

전체 흐름 설명

  1. ptr는 임의의 메모리 주소 0x00000000FFFF8392를 가리키는 포인터의 포인터입니다.
  2. ptr가 가리키는 주소를 역참조하여 해당 주소에 저장된 값을 char* 타입으로 변환한 후 name에 저장합니다.
  3. name이 가리키는 문자열을 출력합니다.
  4. ptr2는 또 다른 임의의 메모리 주소 0x00000000FFFF3821를 가리키는 포인터의 포인터입니다.
  5. ptr2가 가리키는 주소를 역참조하여 해당 주소에 저장된 값을 int64_t* 타입으로 변환한 후 number에 저장합니다.
  6. number가 가리키는 값을 포인터 형식으로 출력합니다.

포인터와 캐스팅

  • 포인터의 포인터(`void`)**: 포인터를 가리키는 포인터입니다. 주로 여러 단계의 간접 참조가 필요할 때 사용합니다.
  • 캐스팅: 포인터의 타입을 변경하여 다른 타입으로 사용합니다. 여기서는 void*char*int64_t*로 캐스팅하여 사용합니다.
  • 역참조(dereference): 포인터가 가리키는 주소의 실제 값을 접근합니다. *ptr*ptr2는 각각 ptrptr2가 가리키는 주소의 값을 의미합니다.

7. 동적 메모리 할당 (malloc, free)

malloc 왜 씀? : 필요한 만큼만 메모리 가져다 쓸 수 있음
calloc : malloc은 메모리 할당하고 쓰레기 값 들어있는데 calloc은 메모리 할당 후에 0으로 채워줌
realloc : 할당했던 메모리 다시 재할당하기

  • 운영체제가 메모리 할당해준다

  • 메모리 있으면 일단 할당해준다 (가상 메모리 만들어서라도 메모리 할당해준다)

  • 한 번 할당되면 다른 거 때문에 재할당되지 않는다. 그래서 풀어줘야 됨.

  • malloc: 동적 메모리를 할당합니다.

    int* ptr = (int*) malloc(sizeof(int) * 10); // 정수형 배열 할당
  • free: 동적 할당된 메모리를 해제합니다.

    free(ptr); // 메모리 해제

8. 배열의 개념

배열은 동일한 타입의 여러 변수를 하나의 이름으로 묶은 것입니다. 배열 이름은 포인터와 유사하게 작동합니다.

    int arr[10]; // 정수형 배열
    int* ptr = arr; // 배열 이름은 포인터로 동작

9. 함수 호출 방식: Call By Value, Call By Reference

  • Call By Value: 인수의 값을 복사하여 함수에 전달합니다.

    void func(int x) {
        x = 10;
    }
    
    int main() {
        int a = 5;
        func(a);
        printf("%d", a); // 5 출력
    }
  • Call By Reference: 인수의 주소를 전달하여 함수 내에서 원래 변수를 수정할 수 있습니다.

    void func(int* x) {
        *x = 10;
    }
    
    int main() {
        int a = 5;
        func(&a);
        printf("%d", a); // 10 출력
    }

10. 가변 인자 (va_start, va_end)

...으로 들어온 가변 인자를 사용하려면 stdarg.h 헤더 파일에 정의된 매크로를 이용해야 합니다. 따라서 #include로 stdarg.h 헤더 파일을 포함해줍니다. stdarg.h에 정의된 가변 인자 처리 매크로는 다음과 같습니다.

출처

va_list: 가변 인자 목록. 가변 인자의 메모리 주소를 저장하는 포인터입니다.
va_start: 가변 인자를 가져올 수 있도록 포인터를 설정합니다.
va_arg: 가변 인자 포인터에서 특정 자료형 크기만큼 값을 가져옵니다.
va_end: 가변 인자 처리가 끝났을 때 포인터를 NULL로 초기화합니다.

가변 인자 함수는 인자의 수가 정해지지 않은 함수입니다.

#include <stdio.h>
#include <stdarg.h>    // va_list, va_start, va_arg, va_end가 정의된 헤더 파일

void printNumbers(int args, ...)    // 가변 인자의 개수를 받음, ...로 가변 인자 설정
{
    va_list ap;    // 가변 인자 목록 포인터

    va_start(ap, args);    // 가변 인자 목록 포인터 설정
    for (int i = 0; i < args; i++)    // 가변 인자 개수만큼 반복
    {
        int num = va_arg(ap, int);    // int 크기만큼 가변 인자 목록 포인터에서 값을 가져옴
                                      // ap를 int 크기만큼 순방향으로 이동
        printf("%d ", num);           // 가변 인자 값 출력
    }
    va_end(ap);    // 가변 인자 목록 포인터를 NULL로 초기화

    printf("\n");    // 줄바꿈
}

int main()
{
    printNumbers(1, 10);                // 인수 개수 1개
    printNumbers(2, 10, 20);            // 인수 개수 2개
    printNumbers(3, 10, 20, 30);        // 인수 개수 3개
    printNumbers(4, 10, 20, 30, 40);    // 인수 개수 4개

    return 0;
}
10
10 20
10 20 30
10 20 30 40

11. 전처리 명령어 (#define, #include, #typedef 등)

#define을 사용하여 상수나 매크로를 정의하는 것과 변수를 선언하는 것은 몇 가지 중요한 차이점이 있습니다. 여기서는 이 차이점을 상세히 설명하겠습니다.

1. #define과 변수 선언의 차이

#define

#define은 전처리기 지시어로, 컴파일러가 소스 코드를 컴파일하기 전에 수행하는 전처리 단계에서 처리됩니다. #define을 사용하면 소스 코드에서 특정 문자열을 다른 문자열로 대체할 수 있습니다. 이는 실제 변수나 함수가 아니라 단순히 텍스트 대체를 의미합니다.

  • 예시:

    #define PI 3.14

    이 코드는 컴파일 전에 모든 PI3.14로 대체합니다. 실제로 컴파일러가 이 코드를 처리할 때는 PI 대신 3.14가 들어가게 됩니다.

  • 매크로의 장점:

    • 상수 정의: 상수를 정의할 때 사용합니다.
    • 간단한 코드 대체: 복잡한 표현식을 간단한 이름으로 대체할 수 있습니다.
    • 조건부 컴파일: 특정 조건에서만 컴파일되도록 코드를 조절할 수 있습니다.
  • 예시:

    #define SQUARE(x) ((x) * (x))

    이 코드는 SQUARE(5)((5) * (5))로 대체합니다.

변수 선언

변수 선언은 프로그램의 실행 중에 메모리에 실제로 저장되는 데이터를 정의합니다. 변수는 특정 타입을 가지며, 해당 타입의 크기만큼 메모리를 할당받습니다.

  • 예시:

    const float PI = 3.14;

    이 코드는 PI라는 이름의 float형 상수를 선언하고 초기화합니다. 여기서 PI는 실제 메모리 공간을 차지하며, 프로그램 실행 중에는 변경할 수 없습니다.

  • 변수 선언의 장점:

    • 타입 안정성: 변수는 타입을 가지므로, 컴파일러가 타입 검사를 통해 오류를 방지할 수 있습니다.
    • 디버깅: 디버깅 시 변수의 값을 추적할 수 있습니다.
    • 범위 제한: 변수를 특정 범위 내에서만 사용할 수 있도록 제한할 수 있습니다.
  • #define: 상수나 매크로를 정의합니다.

    #define PI 3.14
  • #include: 다른 파일의 내용을 포함시킵니다.

    #include <stdio.h>
  • #typedef: 새로운 타입 이름을 정의합니다.

    typedef unsigned long ulong;

12. 비트 연산자

비트 연산자는 비트 단위로 연산을 수행합니다.

    int a = 5; // 0101


    int b = 9; // 1001
    int c = a & b; // 0001 (AND 연산)
    int d = a | b; // 1101 (OR 연산)
    int e = a ^ b; // 1100 (XOR 연산)
    int f = ~a; // 1010 (NOT 연산)
    int g = a << 1; // 1010 (왼쪽 시프트)
    int h = b >> 1; // 0100 (오른쪽 시프트)

13. C++ STL의 리스트 반복

리스트 반복(C++ STL의 개념)은 컨테이너(예: vector, list, map 등)에 저장된 요소들을 순회(traverse)하는 방법입니다. C++ 표준 템플릿 라이브러리(STL)는 다양한 반복자(iterator) 타입과 루프 구문을 제공하여 이러한 작업을 쉽게 수행할 수 있도록 합니다.

STL에서 리스트를 반복하는 방법은 여러 가지가 있지만, 가장 일반적인 방법은 반복자(iterator)를 사용하는 것입니다. 반복자는 포인터와 유사한 개념으로, 컨테이너의 요소를 순차적으로 접근할 수 있게 해줍니다.

1. vector를 사용한 반복

vector는 동적 배열을 구현한 컨테이너로, 반복자를 사용하여 요소를 순회할 수 있습니다.

  • 예시:

    #include <iostream>
    #include <vector>
    
    int main() {
        std::vector<int> vec = {1, 2, 3, 4, 5};
    
        // 반복자를 사용한 반복
        for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
            std::cout << *it << " ";
        }
        std::cout << std::endl;
    
        // 범위 기반 for 루프 (C++11 이상)
        for (int value : vec) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    
        return 0;
    }
    • 첫 번째 루프: std::vector<int>::iterator를 사용하여 vec의 요소를 순회합니다. vec.begin()은 첫 번째 요소를 가리키는 반복자를 반환하고, vec.end()는 마지막 요소 다음을 가리키는 반복자를 반환합니다.
    • 두 번째 루프: C++11 이상에서는 범위 기반 for 루프를 사용할 수 있습니다. 이는 컨테이너의 모든 요소를 간편하게 순회할 수 있게 해줍니다.

2. list를 사용한 반복

list는 이중 연결 리스트를 구현한 컨테이너로, 요소의 삽입과 삭제가 빈번한 경우 유용합니다.

  • 예시:

    #include <iostream>
    #include <list>
    
    int main() {
        std::list<int> lst = {1, 2, 3, 4, 5};
    
        // 반복자를 사용한 반복
        for (std::list<int>::iterator it = lst.begin(); it != lst.end(); ++it) {
            std::cout << *it << " ";
        }
        std::cout << std::endl;
    
        // 범위 기반 for 루프 (C++11 이상)
        for (int value : lst) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    
        return 0;
    }
    • 첫 번째 루프: std::list<int>::iterator를 사용하여 lst의 요소를 순회합니다. lst.begin()lst.end()를 사용하여 리스트의 시작과 끝을 가리키는 반복자를 얻습니다.
    • 두 번째 루프: 범위 기반 for 루프를 사용하여 리스트의 요소를 순회합니다.

요약

  • 반복자(iterator): 포인터와 유사한 개념으로, 컨테이너의 요소를 순차적으로 접근할 수 있게 해줍니다.
    • 벡터(vector): 동적 배열을 구현한 컨테이너. 빠른 임의 접근(random access)이 가능합니다.
    • 리스트(list): 이중 연결 리스트를 구현한 컨테이너. 빠른 삽입과 삭제가 가능합니다.
  • 범위 기반 for 루프: C++11 이상에서 사용할 수 있으며, 컨테이너의 모든 요소를 간편하게 순회할 수 있습니다.

이러한 반복자와 루프 구문을 사용하면 C++ STL의 컨테이너를 효율적으로 순회하고 조작할 수 있습니다.

🏷️ ubuntu (로컬) 개발 환경 설치 기록

vscode 컴파일, 디버깅 환경 세팅

https://webnautes.tistory.com/1854

launch.json 만들 때 선택 버튼이 안 떠서 다른 블로그 찾음.
이걸로 했더니 잘 됨.



⚔️ 백준


📌 2576 계단 오르기

# 1계단이나 2계단 오를 수 있음
# 3번 연속 1계단 오르면 안됨
# 마지막 도착 계단을 반드시 밟아야 함

# 배열에 현재 계단까지 얻은 최고 점수와 
# 1계단을 이전에 0번 뛰었는지 1번 뛰었는지 2번 뛰었는지 저장

# 각 계단에서 1계단 뛰거나 2계단 뛰기

import sys
input = sys.stdin.readline

N = int(input())
Stairs = [0]
# 각각의 계단에 대해 이전에 뛴 1계단 횟수마다 최고 점수 저장
Record = []
for i in range(N+1):
    Record.append([0,0])
for _ in range(N):
    Stairs.append(int(input()))

Record[1][0] = Stairs[1]
# 모든 계단에서 2갈래씩 앞으로
for i in range(N):
    if i+1 < len(Stairs):
        if Record[i+1][1] < Record[i][0]+Stairs[i+1]:
            Record[i+1][1] = Record[i][0]+Stairs[i+1]

    if i+2 < len(Stairs):
        for j in range(2):
            if Record[i+2][0] < Record[i][j]+Stairs[i+2]:
                Record[i+2][0] = Record[i][j]+Stairs[i+2]

print(max(Record[-1]))         

# 억까 당한점
# 똑같은거 곱하기로 했더니 하나 바꿨더니 전부 바꿔짐
# 1단 점프 2번까지 되는줄 알았는데 1번이었음

0개의 댓글