코치님 comment
자원의 추상화
가상메모리 : 컴퓨터가 모두에게 자신을 혼자만 이용하는 것처럼 착각하게 함
가짜 여러 개 만들어서 짜잔! 이게 컴퓨터야! 한 다음에 진짜 자원을 가짜들에 배분
파이썬과 C 언어의 특성을 비교하면서, 특히 C 언어의 주요 개념들을 자세히 설명해 드리겠습니다. 여기서는 선언과 정의, 전방 선언, static
과 extern
키워드, enum
과 union
, 컴파일과 링크 과정, 포인터, 동적 메모리 할당, 배열, 함수 호출 방식, 가변 인자, 전처리기 명령어, 리스트 반복(iteration), 비트 연산자 등을 다루겠습니다.
선언 : 그냥 이런 거 있어요~
정의 : 이런 식으로 쓸게요~ 메모리 주세요~
헤더 파일 : 다른 파일에 있는 선언 가져다 쓸거에요~
소스 파일 : 다른 파일에 있는 정의 가져다 쓸거에요~
선언(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;
}
전방 선언 : 나중에 나오는 함수 땡겨와야 할 필요성 있을 때 사용
전방 선언은 함수나 변수가 이후에 정의될 것이라는 것을 컴파일러에 알리는 것입니다. 주로 순환 참조 문제를 해결할 때 사용됩니다.
void funcB(); // 전방 선언
void funcA() {
funcB(); // 나중에 정의될 함수 호출
}
void funcB() {
// 함수 정의
}
static
과 extern
의 개념static : 다른 파일에서 못 쓰게 만듦
extern : 다른 파일에 있는거 가져옴
static
: 변수나 함수의 가시성을 제한합니다. 변수는 해당 소스 파일 내에서만 접근 가능하고, 함수는 파일 내에서만 호출 가능합니다.
static int x = 10; // 이 파일 내에서만 접근 가능
static void func() { // 이 파일 내에서만 호출 가능
// 함수 구현
}
extern
: 다른 파일에 정의된 변수나 함수의 선언을 명시합니다.
// file1.c
int x = 10; // 정의
// file2.c
extern int x; // 선언
enum
과 union
개념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;
컴파일: 소스 코드를 목적 코드(오브젝트 파일)로 변환합니다.
gcc -c mysource.c -o mysource.o
링크: 여러 목적 파일을 결합하여 실행 파일을 만듭니다.
gcc mysource.o -o myprogram
단계 요약:
#include
, #define
등 전처리 지시문 처리.void : 함수로 선언하면 리턴값 없는거
void*로 선언하면 아무 메모리 주소 값 저장 가능.
void**는 메모리 주소가 들어있는 메모리 주소
왼쪽에 붙어있는 : 역참조 (오른쪽 하나 지워주는 걸로 생각해도 됨)
포인터는 다른 변수의 메모리 주소를 저장하는 변수입니다.
void** ptr = 0x00000000FFFF8392; // ptr의 메모리 주소가 들어있는 메모리 주소
char* name = (char*) *ptr;
printf("%s", name);
void** ptr2 = 0x00000000FFFF3821;
int64_t* number = (int64_t*) *ptr2;
printf("%p", *number);
void** ptr = 0x00000000FFFF8392;
ptr
은 void*
타입의 포인터를 가리키는 포인터입니다. 여기서 0x00000000FFFF8392
는 임의의 메모리 주소를 가리키는 예시 값입니다.ptr
은 포인터의 포인터이며, 0x00000000FFFF8392
주소에 저장된 값을 가리킵니다. 이 주소에 실제로 어떤 값이 저장되어 있는지는 알 수 없지만, 다음 줄에서 이 값을 사용합니다.char* name = (char*) *ptr;
ptr
이 가리키는 주소의 값을 역참조(dereference)하여 char*
타입으로 변환합니다.ptr
이 가리키는 주소에 저장된 값을 char*
로 캐스팅하여 name
에 저장합니다. name
은 문자열의 시작 주소를 가리키게 됩니다.printf("%s", name);
name
이 가리키는 문자열을 출력합니다.name
이 가리키는 주소에 저장된 문자열을 출력합니다. 이때, name
은 이전 줄에서 ptr
이 가리키는 주소에 저장된 값을 char*
로 캐스팅한 것입니다.void** ptr2 = 0x00000000FFFF3821;
ptr2
는 void*
타입의 포인터를 가리키는 포인터입니다. 0x00000000FFFF3821
은 또 다른 임의의 메모리 주소를 가리키는 예시 값입니다.ptr2
는 포인터의 포인터이며, 0x00000000FFFF3821
주소에 저장된 값을 가리킵니다.int64_t* number = (int64_t*) *ptr2;
ptr2
가 가리키는 주소의 값을 역참조하여 int64_t*
타입으로 변환합니다.ptr2
가 가리키는 주소에 저장된 값을 int64_t*
로 캐스팅하여 number
에 저장합니다. number
는 int64_t
타입의 값을 가리키게 됩니다.printf("%p", *number);
number
가 가리키는 값을 포인터 형식으로 출력합니다.number
가 가리키는 주소의 값을 포인터 형식으로 출력합니다. 이때 number
는 ptr2
가 가리키는 주소에 저장된 값을 int64_t*
로 캐스팅한 것입니다.ptr
는 임의의 메모리 주소 0x00000000FFFF8392
를 가리키는 포인터의 포인터입니다.ptr
가 가리키는 주소를 역참조하여 해당 주소에 저장된 값을 char*
타입으로 변환한 후 name
에 저장합니다.name
이 가리키는 문자열을 출력합니다.ptr2
는 또 다른 임의의 메모리 주소 0x00000000FFFF3821
를 가리키는 포인터의 포인터입니다.ptr2
가 가리키는 주소를 역참조하여 해당 주소에 저장된 값을 int64_t*
타입으로 변환한 후 number
에 저장합니다.number
가 가리키는 값을 포인터 형식으로 출력합니다.void*
를 char*
와 int64_t*
로 캐스팅하여 사용합니다.*ptr
와 *ptr2
는 각각 ptr
과 ptr2
가 가리키는 주소의 값을 의미합니다.malloc
, free
)malloc 왜 씀? : 필요한 만큼만 메모리 가져다 쓸 수 있음
calloc : malloc은 메모리 할당하고 쓰레기 값 들어있는데 calloc은 메모리 할당 후에 0으로 채워줌
realloc : 할당했던 메모리 다시 재할당하기
운영체제가 메모리 할당해준다
메모리 있으면 일단 할당해준다 (가상 메모리 만들어서라도 메모리 할당해준다)
한 번 할당되면 다른 거 때문에 재할당되지 않는다. 그래서 풀어줘야 됨.
malloc
: 동적 메모리를 할당합니다.
int* ptr = (int*) malloc(sizeof(int) * 10); // 정수형 배열 할당
free
: 동적 할당된 메모리를 해제합니다.
free(ptr); // 메모리 해제
배열은 동일한 타입의 여러 변수를 하나의 이름으로 묶은 것입니다. 배열 이름은 포인터와 유사하게 작동합니다.
int arr[10]; // 정수형 배열
int* ptr = arr; // 배열 이름은 포인터로 동작
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 출력
}
...으로 들어온 가변 인자를 사용하려면 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
#define
, #include
, #typedef
등)#define
을 사용하여 상수나 매크로를 정의하는 것과 변수를 선언하는 것은 몇 가지 중요한 차이점이 있습니다. 여기서는 이 차이점을 상세히 설명하겠습니다.
#define
과 변수 선언의 차이#define
#define
은 전처리기 지시어로, 컴파일러가 소스 코드를 컴파일하기 전에 수행하는 전처리 단계에서 처리됩니다. #define
을 사용하면 소스 코드에서 특정 문자열을 다른 문자열로 대체할 수 있습니다. 이는 실제 변수나 함수가 아니라 단순히 텍스트 대체를 의미합니다.
예시:
#define PI 3.14
이 코드는 컴파일 전에 모든 PI
를 3.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;
비트 연산자는 비트 단위로 연산을 수행합니다.
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 (오른쪽 시프트)
리스트 반복(C++ STL의 개념)은 컨테이너(예: vector
, list
, map
등)에 저장된 요소들을 순회(traverse)하는 방법입니다. C++ 표준 템플릿 라이브러리(STL)는 다양한 반복자(iterator) 타입과 루프 구문을 제공하여 이러한 작업을 쉽게 수행할 수 있도록 합니다.
STL에서 리스트를 반복하는 방법은 여러 가지가 있지만, 가장 일반적인 방법은 반복자(iterator)를 사용하는 것입니다. 반복자는 포인터와 유사한 개념으로, 컨테이너의 요소를 순차적으로 접근할 수 있게 해줍니다.
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()
는 마지막 요소 다음을 가리키는 반복자를 반환합니다.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()
를 사용하여 리스트의 시작과 끝을 가리키는 반복자를 얻습니다.이러한 반복자와 루프 구문을 사용하면 C++ STL의 컨테이너를 효율적으로 순회하고 조작할 수 있습니다.
https://webnautes.tistory.com/1854
launch.json 만들 때 선택 버튼이 안 떠서 다른 블로그 찾음.
이걸로 했더니 잘 됨.
# 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번이었음