들어가며
해당 포스팅은 포인터 기초부터 시작해 심화내용에 대한 부분을 정리한 포스팅이다.
시스템 프로그래밍 기초 시험이 하루채 남지 않은 시점에서 포인터를 공부하면서 작성하는 포스팅이니,
이 점 유의해서 해당 블로그를 참조하길 바란다.
포인터
포인터는 메모리 상의 주소 값을 의미한다.
메모리 상에서 저장되어진 "Data"에 대해서 포인터 값 (메모리 상 주솟값) 으로 접근하여
직접적으로 "Data"를 참조한다.
# 타입별 할당 시 메모리에 지정받는 값 참고
Int 타입 : 4 Byte
Shot 타입 : 2 Byte
Long 타입 : 4 Byte
Char 타입 : 1 Byte
Float 타입 : 4 Byte
Double 타입 : 8 Byte
# 포인터 기초연산 *기호*
(&) : 선언한 Data 변수에 대한 메모리 상의 주솟값을 나타낸다.
int a;
// int 타입으로 저장한 a의 주솟값
printf(&a);
(*) : 메모리 상의 주솟값에 대해서 저장된 Data를 나타낸다.
// 함수의 인자로 int 형 타입의 주솟값을 받는다.
void input(int* input_d){
scanf("%d", input_d);
// 주솟값에 대한 Data를 참조후 printf();
printf("%d", *input_d);
}
int main(){
// int 타입 변수 선언
int a;
// input 함수에 a의 주솟값을 삽입 (&) : 주소값 참조 연산자
input(&a);
printf("%d", a);
}
배열 - 포인터
배열은 선언 시 지정한 타입의 연속으로 이어진 Data 삽입 자료형이다.
# 배열 - 포인터로써의 의미
- "배열 이름"은 포인터의 의미를 지닌다.
변수와 달리, 배열은 이름을 적으면 배열의 "첫번째 요소의 주솟값"을 의미한다.
int array[3];
// 배열 이름은 배열의 첫번째 요소의 주솟값
array == &array[0]
- 배열의 특정 요소는 배열에 저장된 Data를 나타낸다.
배열의 특정 요소를 지정하게 되면, 그때는 배열 선언 시 지정한 "타입형의 Data"를 의미한다.
정리해서, 주솟값이 아닌, 배열에 저장한 "Data" 값을 의미한다.
배열의 특정 요소 주솟값을 참조하기 위해선 (&) 주솟값 참조 연산자를 사용해 참조할 수 있다.
array + 1 == &array[1]
- (&) 와 (*)를 이용한 연산
(&) 연산자 참고
포인터 연산자의 순서에 따라서 다른 의미를 수행하는 코드가 된다.
따라서, 포인터 연산자에 대한 의미를 이해하고 코드를 구성해야된다.
// int 타입의 배열 선언
int arr[5] = {0};
// & 연산 "순서에 따른" 연산결과
// 배열 주소 참조 후 1증가
(&arr[0])++ == arr[1]
// 배열 요소 값 참조 후 1증가
&(arr[0]++) == &(arr[0] = 0+1)
(*) 연산자 참고
함수의 인자로 주솟값으로 넘겨받아 활용할 때도 포인터 연산자는 특히 유의해서 사용해야한다.
아래 코드를 참고하자.
void test_point(int arr[]){
// (*) 연산자의 연산순서에 따른 연산결과
// 주솟값에 대한 Data 참조 후 1증가
(*arr)++ == arr[0] + 1
// 주솟값에 대해 증감연산을 진행 후 Data 참조
*(arr++) == arr[1]
}
int main(){
int arr[5] = {0};
test_point(arr);
}
# 2차원 배열 - 포인터로써의 의미
예제 코드만을 나열해두고, 텍스트로 기록하는 건 포인터에 대해서
추가적인 포인터에 대한 이해가 되었을 때 정리하도록 하겠다.
- 2차원 배열 이름은 배열 첫번째 요소의 주솟값을 의미한다.
// 2차원 배열 선언
int array_2[2][3];
// 2차원 배열이름 == 첫번째 요소의 주솟값
array_2 == &array_2[0][0]
- 2차원 배열에서도 주솟값 연산을 진행할 수 있다.
주솟값에 연산을 진행하면, 지정한 주솟값의 Data 타입크기만큼 주소를 변경 후
다음 주소에 할당된 Data의 주소를 가르킨다.
// 주솟값 연산
array_2 + 1 == &array_2[0][1]
우선 포인터에 대한 개념은 이정도로만 정리하겠다.
가장 기초적인 포인터의 정의부터 포인터 연산자에 대해서 알아보았고, 새롭게 배우고 깨닫는 포인터 배움이 있을 때마다 해당 포스팅을 리팩토링하겠다.
해당 글 뒤로 적힌 글들은 포인터의 의의를 기록해둘 것이다.
단순히 개념 나열이 아닌, 포인터를 직접사용해보면서 느낀점과 깨달은 점을 기록해두면서 포인터의 의의를 나만의 언어로 풀어서 기록했다.
아래의 나열된 내용에는 나의 프로그래밍 가치관이 들어가 있으므로, 아래의 내용부턴 흘려듣기식으로 블로그를 감상하는 걸 권장한다. - 인생에는 정답이 없고, 왕도가 없다 ㅎㅎ
함수 포인터
함수 포인터는 변수와 같이 함수 호출 시 메모리 상에 할당되는 고유한 주솟값이다.
메모리 상에서 "고유하게" 주솟값이 할당되며 함수 포인터 주솟값을 직접 참조하여 함수를 사용한다.
- typedef를 이용해 함수 포인터 재정의 하기
// return 값 void & 매개변수 void 형태의 함수 포인터 타입 "정의"
typedef void (*fun_t)(void);
새로운 자료형 fun_t를 선언한다.
예시로 규정한 fun_t는 반환값과 매개변수가 없는 함수를 가리킬 수 있는 함수 포인터 타입이다.
정리해서, typedef를 이용해 함수 포인터 타입을 지정하며
지정된 함수 포인터 타입을 이용해 (typedef 수행 시 정의했던 함수 포인터 타입으로) 함수를 선언한다.
위에서 처리한 동작을 토대로 아래와 같이 함수를 선언할 수 있다.
// func_t 함수 포인터 타입으로 새로운 함수를 선언
func_t func;
# 예제 활용 - 함수 포인터
// 함수 포인터 타입을 정의할 때 (포인터 주솟값을 나타내는 이유?)
// 메모리 상에 정의할 함수 포인터 틀을 만듬. - 메모리를 할당할 수 있는 코드 틀을 구성
typedef void (* func_t) (void);
int main(){
// 함수 포인터 타입으로 배열길이 3인 배열 생성
func_t func[3];
func[0] = Hello;
func[1] = GoodNight;
func[2] = WhatsUp;
}
void Hello(){
printf("Hello? \n");
}
void GoodNight(){
printf("Good Night");
}
void WhatsUp(){
printf("What's up");
}
typedef를 이용해서 함수 포인터 타입으로 타입을 정의한다.
정의한 타입으로 새로운 메소드들을 간편하게 생성하고 이를 이용해서 함수를 직접적으로 참조한다.
typedef 의의
typedef를 이용해 타입의 형식을 지정해두면서, 동일한 타입 형태의 변수 & 함수들을 간편하게 main 함수에서 정의할 수 있도록 해준다.
C언어는 함수와 변수의 차이점이 존재하지 않고, 동일하게 값을 삽입하고 받아서 사용할 수 있다.
return 값으로 넣어진 "함수"는 변수에 저장될 수 있고, 그 변수에서 호출을 수행하면, 함수가 호출이 된다.
# typedef를 이용해 함수를 마치 변수처럼 정의하고 사용한다.
#include <stdio.h>
#define MENU "[0] Quit [1] Hello [2] GoodNight [3] WhatsUp - "
void Hello(void);
void GoodNight(void);
void WhatsUp(void);
// typedef를 이용해 함수 포인터 타입을 정의합니다.
typedef void (*print_t)(void);
// func 함수의 프로토타입을 작성합니다. || 함수 선언만 해둠.
print_t func(int idx);
int main(void) {
// 함수로부터 반환 받은 함수 포인터를 저장할 변수를 선언합니다.
print_t fn;
// 무한 반복문을 돌면서 아래 동작 수행
while (1) {
// 1. 메뉴를 출력합니다.
printf(MENU);
// 2. 사용자로부터 메뉴를 입력받습니다.
int menu;
scanf("%d", &menu);
// 3. 0을 입력한 경우 종료합니다.
if (menu == 0)
break;
else if (menu < 0 || menu > 3)
continue;
// 4. 입력값을 func()에 전달하고 함수 포인터를 돌려받아 변수에 저장합니다.
fn = func(menu);
// 5. 함수 포인터 변수를 이용하여 함수를 호출합니다.
// 함수 포인터로 함수를 호출한다.
fn();
// func(menu)();
// 함수를 메모리 주솟값으로 접근한다.
}
return 0;
}
void Hello(void) { printf("Hello?\n"); }
void GoodNight(void) { printf("Good night!\n"); }
void WhatsUp(void) { printf("What's up?\n"); }
// typedef로써 함수의 타입을 지정해줌.
// C언어에선, 주소로 모두 동작이 이루어진다.
/*
함수도 변수처럼 동작한다.
메모리의 주솟값을 가지고 있으며, 이를 토대로 접근한다.
*/
// func() 함수는 return 타입이 "함수"
print_t func(int idx) {
print_t rtn_fn;
if (idx == 1)
rtn_fn = Hello;
else if (idx == 2)
rtn_fn = GoodNight;
else if (idx == 3)
rtn_fn = WhatsUp;
return rtn_fn;
}
# 함수 매개변수에 특정 함수를 넣어서 처리한다.
함수의 매개변수로 함수를 삽입하여 함수를 생성하는 전략은
프로그램 실행 시 할당되는 메모리 상에서의 포인터 주솟값을 직접 "참조"한다.
그러면서, 직접 "참조"하는 전략을 토대로 코드의 재활용률을 높힌다.
#include <stdio.h>
#include <stdlib.h>
#define MENU "[0] 종료, [1] 생성, [2] 두배, [3] 출력 - "
// 함수 포인터를 위한 func_t 타입을 정의합니다.
void fill_random(int *arr, int size);
void double_up(int *arr, int size);
void print(int *arr, int size);
typedef void(*func_t)(int*, int);
// 입력에 따라 함수를 호출할 proxy_function을 선언합니다.
void proxy_function(func_t func, int *arr, int size);
// 이 함수는 func_t 타입, int* 타입, int 타입을 인자로 받습니다.
int main(void) {
// 정수를 10개 저장할 수 있는 배열을 선언합니다.
int arr[10];
int size = 10;
func_t func[3] = {fill_random, double_up, print};
// 무한 반복문에서 아래 동작을 처리합니다.
// replit 단축키 Shirt + 지정한 영역 + {}
while(1){
// 사용자에게 메뉴를 출력하고 입력을 받습니다.
printf(MENU);
int menu;
scanf("%d", &menu);
// 입력에 따라 3개 함수 중 하나를 호출합니다.
// 함수를 호출하는 과정에서 proxy_function을 사용해야 합니다.
if(menu == 0) break;
else if (menu < 0 || menu > 3) continue;
proxy_function(func[menu - 1], arr, size);
}
return 0;
}
// 각각의 함수를 정의합니다.
/*
함수의 매개변수는 배열 포인터의 주솟값을 받는다.
*/
void fill_random(int* arr, int size){
for(int i =0; i < size; i ++){
// rand() 함수는 0 ~ 1 사이의 숫자
// rand() * 100 은 0 ~ 100 사이의 숫자
arr[i] = (int)(rand() * 100 % 100);
}
}
void double_up(int* arr, int size){
for(int i = 0; i < size; i ++){
arr[i] *= 2;
}
}
void print(int *arr, int size){
for(int i = 0; i < size; i ++){
printf("%3d ", arr[i]);
}
printf("\n");
}
void proxy_function(func_t func, int *arr, int size){
func(arr, size);
}
정리해서, typedef를 이용해 포인터 주솟값에 대한 "틀"을 만들어두고,
정의한 typedef를 토대로 메모리 상에 "고유한" 포인터 주솟값을 할당한다. 그리고 이를 main에서 활용하여 사용한다.
문자열
구조체
# 구조체의 기본 선언
// 구조체 선언
struct elec{
char name[20];
int price;
};
# typedef를 이용한 구조체 타입으로 선언해둠.
typedef struct elec {
char name[20]
int price;
} Elec;
파일 입출력
# fopen(), fread(), fprintf() 함수를 토대로 {가전 제품 매출관리 프로그램 제작 - 파일 입출력}
#include <stdio.h>
#define QUERY_AMOUNT "각 상품의 수량을 순서대로 입력하세요: "
#define MAX 4
// 가전제품 정보 구조체 정의
// 구조체 타입을 Elec으로 지정
typedef struct elec {
char name[20]; // char 타입에 크기에 따라서, name에 지정되는 요소가 결정된다.
int price;
} Elec;
int main(void) {
// 구조체 생성
Elec electronics[4];
// Call by reference [파일 주소를 가리키는 포인터 변수]
FILE *fp = fopen("price.txt", "rb"); // FILE 타입의 파일저장변수는 포인터 변수로 생성
/*
파일 읽기 방법 1.
*/
// electronics[0] 부터 시작해서, 구조체 Elec 타입의 electronics 배열의 전체 크기를 재서 배열 타입의 하나 요소만큼 파일 잘라서 파일을 읽음.
fread(electronics, sizeof(electronics), 1, fp); //fread("파일의 읽은 값을 저장하는 곳", "파일의 Data를 어떻게 읽을지에 대해 정의", "1", "파일 [포인터] 주솟값")
/*
파일 읽기 방법 2.
*/
// 구조체 정의에 따라서 값들이 주소값으로 잘라져서 지정됨.
for (int i = 0; i < MAX; i++) {
// fread(&electronics[i], sizeof(electronics[i]), 1, fp);
printf("%s \t %d\n", electronics[i].name, electronics[i].price);
}
// price.txt 파일 파일 정보 읽기 작업 완료 후 파일 닫기
fclose(fp);
int input[MAX]; // 사용자 가전제품 구매 변수
// 사용자 (구조체에서 정의한) 가전제품 구매갯수 의사입력
for (int i = 0; i < MAX; i++) {
scanf("%d", &input[i]);
}
/*
파일 쓰기 작업시작 - sales.txt 파일
*/
// 읽기 모드로 작업할 파일 불러오기 - 포인터 변수로 파일 불러오기
FILE *fout = fopen("sales.txt", "wt");
// 파일 열기 예외처리
if (fout == NULL) {
fprintf(stderr, "output file open error\n");
return 1;
}
// sales 파일에 구매내역 추가 & 합계 추가
int sum = 0;
// sales 파일에 가전제품 구매내역 추가
for (int i = 0; i < MAX; i++) {
sum = electronics[i].price * input[i] + sum;
// fprintf() : 한줄씩 sales.txt 파일에 데이터 입력
fprintf( fout, "%s\t%d\t%d\t%d\n", electronics[i].name, electronics[i].price, input[i], electronics[i].price * input[i]);
}
// sales 파일에 합계내역 추가
fprintf(fout, "합계: %d\n", sum);
// sales.txt 파일 쓰기 작업완료 후 파일 닫기
fclose(fout);
/*
파일 읽기 & 쓰기 작업은 메모리 상에서 FILE 포인터 주솟값이
할당되면, 반드시 작업이 종료되고 close() 작업을 처리해야한다.
*/
return 0;
}
{내가 직접 사용하면서 느꼈던} 포인터 의의
# 메모리상의 주솟값을 직접 참조하면서 코드의 재활용률을 높힌다.
포인터는 메모리 상에 저장된 "Data"에 대해서 직접적인 메모리 주소를 참조한다.
그렇기에, 함수를 구성할 때, 포인터로써 인자를 받으면, return 값을 받지않아도
main() 함수에서 선언한 변수의 "Data"를 변경할 수 있다.
이러한 점을 보고,
기존에 함수 인자를 작성했던 사항이었던 main() 함수의 "Data"를 복제 후 함수 인자로 넘겨줘서
함수에서 연산 후 return 값으로 다시 복제해서 main()에서 값을 받아서 사용했었던 방식을 다시 살펴볼 수 있었다.
포인터를 사용하지 않고 함수를 구현하면, 함수에서 연산된 결과를 main()에 전달하기 위해선 "반드시" return 값이 필요했다.
하지만, 포인터를 함수에 사용하는 순간부터 함수의 return 값을 받지 않고, 직접 참조된 함수 인자를 사용해
함수 내부 코드에서 main()에서 선언한 "Data"에 대한 값을 변경할 수 있었다.
정리해서, 포인터를 활용하지 않고 함수를 구성해서 활용하면, 함수 내부에서 연산된 값을 main 함수에서 받아서 사용하려면 "반드시" return 값을 main에 넘겨주고 main 함수에서는 이를 받아서 프로그램을 구현해야 했다.
하지만, 포인터로써 함수의 인자로 넘겨주게 되면, 함수의 return 값을 main 함수에 넘겨주지 않고,
직접 참조된 메모리 상의 주솟값을 이용해 함수 내부에서 "Data" 값을 변경할 수 있게 되었다.
이로써, 불필요하게 함수 내부에서 return 값을 지정하여 main에 넘겨주는 코드를 작성하지 않고,
main에 선언된 "Data"의 주솟값을 함수 내부에서 "직접 참조"함으로써 "Data"의 값을 변경시킬 수 있다는 혁신적인 포인터의
의의를 깨닫고, 포인터를 이용하면 코드의 재활용률을 높히면서 코드를 작성할 수 있겠다는 생각을 하게 되었다.
"직접 참조"함으로써 메모리 상에 할당된 데이터의 값을 변화시킨다.
# 객체 (Object)는 결국엔, 포인터로써 파생된 개념이다.
이 부분은 참 재미있는 사항이다 ㅎㅎ
현재 난 프로그램 설계 방법론 강의를 수강하면서 객체 지향 프로그래밍 언어인 "JAVA"에 대해서 학습을 하고 있었다.
Java에서는 "객체" 라는 정의를 이용해서 프로그램을 설계하고 개발하게 된다.
그러면서, 절차지향 프로그래밍 언어인 "C언어"와 코드를 설계하고 개발하는 방식이 아예 다르다고 생각했다.
객체 지향 프로그래밍 언어는 클래스를 "객체"로써 생성하고, 이를 활용해서 프로그램을 개발하게 된다.
클래스 파일을 자체를 "객체"로 정의해두고 클래스 상에서 다른 클래스를 "객체"로 생성해서 프로세스가 동작할 때 생성한 고유한 "객체"를 이용해서 프로그램을 개발했다.
C언어는 절차 지향 프로그래밍 언어로 "절차" 즉, 순서에 민감하여 프로그램 개발 시 "알고리즘"이 중시되는 언어라고 생각하고 또 생각했다.
그래서,,ㅋㅋㅜㅜ C언어로 개발하는 행위는 내겐 백준문제와 같은 어려운 알고리즘을 풀기위한 언어로써만 인식했다..
하지만, 이번 포인터 개념을 배운 뒤로부터 C언어에 대한 새로운 시야가 생겼고, C언어에 대해서 흥미도 생기기 시작했다.
포인터는 C언어에서 "Data"를 생성하면, 메모리 상에서 고유한 "포인터 주솟값"이 지정된다. 이렇게 지정된 포인터 주솟값을 활용해
포인터 주소의 "직접 참조"를 통해 메모리 상에 저장된 "Data"의 값을 받아오고 변경한다는 사실을 깨닫자 마자
어라..? 결국에 포인터가 객체의 개념을 내포하고 있었네? 라는 사실임을 알게되었다 ㅋㅋ..
결국엔, "객체"라는 것도 생성되어질 때 메모리 상에 고유한 형태로 저장되어지고, Java 클래스 파일내부에서 다른 클래스 "객체"를 생성하고 활용할 때는 메모리 상에 할당된 "객체"의 포인터 주솟값을 "직접 참조"해서 활용되어진다.
이러한 깨달음을 토대로, C언어에 대한 두려움, 무서움이 사라지기 시작했다.
그리고, C언어에 대한 새로운 시야와 관점을 찾게되었고, C언어의 본질이 궁금해지기 시작했다 ㅋㅋ
원래는, C언어로 알고리즘 문제를 풀면서 어렵고 이해가 잘 안되어 C언어는 내 길이 아니구나~ 라는 생각을 했지만, 이번 계기로
C언어에 대한 흥미가 생겼고, C언어에 대해서 더 공부해보고 싶은 생각이 들었다.
왜냐, C언어가 임베디드 시스템 프로그래밍에서 메인으로 사용되고, 하드웨어 프로그래밍에서 C언어 기반으로 코드를 작성하는 것들이 많고, 임베디드 프로그래밍도 직접해보면서, 시야를 넓혀보고 싶기 때문이다.
(현재는 소프트웨어 프로그래밍에만 중점적으로 했기에, 내가 진출할 수 있는 분야는 오직 소프트웨어 개발에만 한정되어 있다..
그래서, 임베디드 프로그래밍에 대해서도 배워보면서 궁극적인 목표인 창업을 하여 사람들의 삶을 이롭게 해줄 분야를 확대할 수 있다고 생각하기에 임베디드 프로그래밍을 배워보고 싶다.)
아마 임베디드 프로그래밍도 포인터를 활용해서 개발을 진행하지 않을까? 라는 추측을 가지며 C언어랑 더 친해질 생각이 있다.
마치며
해당 포스팅은 여기서 끝나지 않는다. 앞으로 C언어의 포인터에 대해서 꾸준히 공부해보면서, 새롭게 배운 지식들 그리고 개념들을 정리할 것이고, C언어 포인터에 대해서 배우면서 느꼈던 점과 깨달은 점을 나만의 언어로 표현하여 기록해둘 것이다.
비록,, 학기 중에 진행되었어야 할 포인터 공부를 (스타트업톤, 또래튜터링, UMC 개발..ㅎㅎ) 학기가 끝나가는 시점부터 공부를 하게되었지만 ㅎㅎ... 처음 배운다는 초심을 유지하면서 C언어 포인터에 대해서 얕은 부분부터 깊은 부분까지 학습할 각오가 있다.
마치면서, 요즘의 나에 대해서 기록해두면서 이 글을 마치도록 하겠다.
오늘은 이산수학 시험 전날이다. 이산수학 공부를 하나도 하지 않았다. 근데, 시프기 C언어 공부하는 이유가 뭘까.
내가 하고 싶기 때문에 하고 있다. 이산수학을 공부하면, 단순 암기의 연속이라 현재로써 필요성을 느끼지 못해서 나는 C언어에 대해서 열정적으로 공부했다.
이번 경험은 20살 인생 중 새로운 "도전"이다. 왜냐, 나는 항상 사회가 세운 "시험"이라는 것에만 열중해서 공부하고 또 공부했다.
최근들어 "나"에 대해서 정의하기 시작하면서 나는 나만의 길을 걸어가는 중이다. 내가 진짜 원하고, 내가 하고 싶은 것들만 골라서 하고 있다. 그러면서 생기는 리스크인 "학점"을 못받는 점이 있지만, 그래도 나는 하고싶은 것만 골라서 하고 있다.
현재는 아직 시험을 치루지 않은 시점이기에, 생각을 간단하게 정리하겠다.
시험이 끝난 뒤 내게 주어진 "학점" 결과를 보면서 이에 대해서 나의 생각을 정리하는 포스팅을 작성하도록 하겠다.
지금은 새로운 "도전"을 하는 중이기에, 도전이 완료되고, 나의 생각과 정리해서 글을 작성하도록 하겠다.
정리해서, 현재의 나는 내가 가는 길이 "전혀" 틀린 길이라고 생각하지도 않고, 내가 가는 길이 "완전" 맞는 길이라고도 생각이 들지 않는다.
단순히 새로운 경험을 쌓기위해 새로운 "도전"을 하는 것이고, 새로운 "도전"을 함으로써 내게 부족했던 점과 잘하는 점을 깨닫고 있다.
한마디로, "나"를 정의하기 위한 리소스들이 이번 2학기 때 말로 표현할 수 없을만큼 많이 나왔다 ㅎㅎ
이 점에 대해서 혼자서 곱씹으며, 지난 2학기를 회상하며 "나"를 또 다시 정의하는 시간을 가질 계획이다.
요즘의 생각을 정리하면, 새로운 "도전" , 새로운 "경험"을 할 수록 "나"에 대해 정의내릴 수 있는 새로운 리소스들이 찾아지게 된다는 사실이 생각이 가득하다.
그리고, 이러한 새로운 리소스들은 한계없이 무긍무진하게 연속적으로 무한하게 만들어진다는 것을 요즘들어서 많이 생각하고 있다.
마치며에 대한 부분은 현재 글을 쓰면서 느끼고 있는 감정 & 생각들을 의식의 흐름대로 정리해서 쓰는 파트이기에,
이 점을 감안해서 흘려듣기를 권장한다.
'CS 대학강의' 카테고리의 다른 글
[CS 1-2 | 프로그램 설계 방법론] Github 충돌해결 전략 - 모두의 루미큐브 (0) | 2022.12.14 |
---|---|
[CS 1-2 | 프로그램 설계 방법론] Github에서 충돌났을 때 해결법 (0) | 2022.12.09 |
[CS 1-2 | 오픈소스 SW 기초] Docker 이미지 & 컨테이너 (0) | 2022.12.08 |
[CS 1-2 | 오픈소스 SW 기초] 실수로 main 브랜치를 건드렸을 때 대처법 (0) | 2022.12.07 |
[CS 1-2 | 오픈소스 SW 기초] JAVA 프로젝트를 위한 개발 리소스 정리 (0) | 2022.12.06 |