안녕하세요 파일입니다.
포인터 3편을 시작하겠습니다.
포인터는 기본적으로 배울것도 많고, 어렵고, 힘이 듭니다 ㅠ
저도 작성하는 내내 고역이네요..
그래도 다행인건 포인터 편은 이번이 마지막입니다! 힘내보자구요
(아 참고로 썸네일은 포인터라고 치면 나오는 개를 사용했습니다. 아무 의미 없고 어그로 용이니 무시해주세요 ㅋㅋ)
#include <stdio.h>
int main(){
int a = 0;
scanf("%d", &a);
printf("%d", a);
return 0;
}
지금까지 C언어로 입력을 받으면서 printf로 출력할땐 안그랬는데 scanf로 입력받을때는 항상 &a처럼 앞에 &(앰퍼샌드) 기호를 붙였었습니다. 주소로 값을 넘겨야만 했습니다.
이건 나중에 설명하겠다고만 했지 이유도 몰랐고 그냥 외워서 쓰는 수준이였죠.
이걸 왜 이렇게 쓰는지 여기서 드디어 알게됩니다.
주소로 값을 넘긴다는 것에서 힌트를 얻고 감이 오시는 분도 계실겁니다.
Call By Value / Call By Reference
우리가 지금까지 설명하진 않았지만 함수를 항상 호출하는 방식이
Call By Value(값에 의한 호출) 이였습니다.
함수에 인자값을 넘길때 항상 값을 넘겼었죠.
#include <stdio.h>
int func(int i);
int main(){
int a = 1;
int result = func(a);
printf("%d %d", a, result);
return 0;
}
int func(int i){
i++;
return i;
}
해당 예제는 이미 함수편에서 함수란 것을 배워서 익숙하실건데요.
a라는 int형 변수에는 1이라는 값이 담겨있고
func(a)로 호출을 하면 func의 매개변수인 int형 변수 i에 a의 값이 그대로 복사되게 됩니다.
a의 변수명이 b이던, c이던 똑같은 i이던 변수명과는 관계없이
1이라는 값 자체만을 복사해서 함수 내부의 매개변수 i에 들어가게 됩니다.
그리고 func함수 내부에서 매개변수 i를 1증감시키는 연산이 이루어지고 return으로 i값을 반환하면
result라는 변수에 2라는 값이 담기게 되는것입니다.
func라는 함수를 호출(Call)할때 값(Value)을 넘겼습니다.
이것이 바로 Call By Value, 값에 의한 호출입니다.
#include <stdio.h>
void func(int i);
int main(){
int i = 1;
printf("%d\n", i);
func(i);
printf("%d", i);
return 0;
}
void func(int i){
i = i+1; //i에 1을 더해서 저장한다.
}
1
1
--------------------------------
Process exited after 0.02375 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
해당 예제는 매개변수 값을 1더해서 저장하는게 다인 소스코드 입니다.
보시면 main()함수에서 i라는 값이 있고 func라는 함수에서 매개변수인 i가 있습니다.
두개의 변수는 이름은 겹치지만 실제로는 다른 변수입니다.
지역적으로 다르게 관리되기 때문입니다.
지역변수에 대한 설명 기억나시나요? 지역변수는 기본적으로 중괄호 내부에 묶여 있으며 중괄호를 탈출하면 메모리가 알아서 소멸되어 버립니다. 중괄호 내부에서만 효력을 발휘하는게 바로 지역변수였습니다.
함수에서 인자값을 받는 매개변수 또한 지역변수이구요.
그래서 main함수에서 만든 i라는 변수에 1이 저장되어 있는 상태에서 func(i)로 func함수를 호출하면 매개변수 i의 값 자체 만을 복사하게 됩니다. 그러면 1을 복사받아서 i에 1이 더해져서 저장되고 함수가 종료되면 i는 지역변수기 때문에 메모리가 소멸되어 사라져버리게 됩니다.
값만 복사하고 다른 지역에서 1만 더해지고 사라져버린 것이지요.
당연히 main함수의 i에는 아무런 영향도 끼치지 못합니다.
func를 호출한다고 해서 main 함수의 i에 1이 더해지는게 아니란 말이죠.
출력결과가 1,1로 나옵니다.
Call By Reference
#include <stdio.h>
void func(int * i);
int main(){
int a = 1;
printf("%d\n", a);
func(&a);
printf("%d", a);
return 0;
}
void func(int * i){
*i = *i + 1; //i라는 포인터 변수가 가리키는 주소에 대한 값에 1을 누적시켜서 대입한다.
}
해당 예제를 살펴봅시다.
보시면 이전과는 다르게 함수를 호출할때 a가 아닌 a의 주소 &a를 함수에 인자값으로 넘기고 있습니다.
a의 주소를 넘겼기 때문에 매개변수가 주소값을 받기 위해서는 i는 포인터 변수로 선언되야 하며 i는 a의 주소를 넘겨 받습니다.
포인터 변수 i가 a의 주소를 가리키고 있는 셈이 되기 때문에 i는 a에 직접접근하여 값을 변경할 수 있습니다.
*i = *i + 1;
해당 연산을 시행하게 되면, =(대입연산자)의 결합 방향은 오른쪽에서 왼쪽, *i + 1을 먼저 계산하게 됩니다.
*i는 i가 가리키고 있는 주소에 대한 값 - 즉 a의 값 1이 되며 그 값에 1을 더한 값 2로 결정되게 됩니다.
이후 2를 *i에 대입합니다. *i는 방금도 말했듯이 가리키고 있는 주소에 대한 값(a의 값)이 되며 a라는 값 1에 2를 덮어 씌우는 셈이 되니
a의 값이 2로 변경되게 됩니다.
쉽게 말해서 i라는 포인터 변수가 a의 주소를 직접 가리키고 있으니 그곳의 값을 * 연산자를 이용해서 바꿀 수 있게 된 겁니다.
func라는 함수를 호출(Call)할때 주소를 넘겼습니다.
이것이 바로 Call By Reference, 주소에 의한 호출입니다. (일반적으로 직역할땐 참조에 의한 호출이라고 합니다.)
주소에 의한 호출 에서 가장 중요한 사실은 값에 의한 호출과 다르게 복사본이 아닌 원본 그 자체를 넘긴다는 것입니다.
값에 의한 호출로는 복사본을 넘기기 때문에 func라는 함수에서 main함수 내부에 있는 변수에 접근할 수 없었지만
주소에 의한 호출은 원본(주소)를 넘기기 때문에 그 주소를 찾아가서 * 연산자를 통해 값을 바꿔낼 수가 있는 것입니다.
변수값의 복사본이 아니라 변수에 대한 주소를 넘기기 때문에 함수 내부에서도 외부 함수에 있는 값을 자유자제로 바꿀 수 있다는게 주소에 의한 호출의 특징입니다.
물론 주소에 의한 호출의 경우에는 조금 주의가 필요합니다.
왜냐하면 원본을 넘기기 때문에 원본을 수정하면 당연히 원본이 회손됩니다.
값에 의한 호출은 복사본을 넘기기 때문에 이런 문제가 없었지만요.
Call By Reference의 장점
주소에 의한 호출로 함수를 호출하면 어떤 장점이 있을까요?
주소에 의한 호출의 장점을 이용한것이 바로 지금까지 배웠던 배열과 포인터의 연결인데요.
배열을 매개변수로 받아보는 예제를 한번 살펴보겠습니다.
#include <stdio.h>
void print_array(int arr[]){
for (int i = 0; i < 5; i++){
printf("%d\n", arr[i]);
}
}
int main(){
int array[] = {1,2,3,4,5};
print_array(array);
return 0;
}
1
2
3
4
5
--------------------------------
Process exited after 0.0237 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
print_array()라는 함수는 int형 배열의 매개 변수로 받고 출력하는 함수입니다.
실행해보면 array 배열 안에 들어있는 값이 정상적으로 출력이 되는걸 알 수 있습니다.
그런데, 한가지 의문이 생깁니다.
과연 배열을 인자값으로 넘겨서 매개변수로 받을때는 값에 의한 호출(복사) 인가? 아니면 주소에 의한 호출(원본) 인가?
의문이 생깁니다.
배열을 인자값으로 넘기면 그대로 복사할까요, 아니면 원본을 넘길까요?
확인해보는건 간단합니다. print_array() 에서 매개변수인 arr[] 의 크기를 출력해 보면 알겁니다.
만약에 값에 의한 호출로 배열 통째로가 복사되서 넘어왔다면 4byte x 5 = 20byte 가 넘어왔을꺼고 포인터 변수면 주소가 저장되어 있어서 4byte 나 8byte가 출력되겠죠.
#include <stdio.h>
void print_array(int arr[]){
printf("size of arr : %d", sizeof(arr));
}
int main(){
int array[] = {1,2,3,4,5};
print_array(array);
return 0;
}
size of arr : 8
--------------------------------
Process exited after 0.02483 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
sizeof() 에 의한 20byte가 아닌 8byte 가 출력됩니다.
제 컴퓨터가 64bit 컴퓨터니깐 배열을 넘길땐 복사가 아니라 원본 주소를 넘기고 있나보네요.
한마디로 arr이라는 매개변수는 포인터 변수입니다.
근데 이렇게 질문 하시는 분들도 있으실껍니다.
??? 포인터 변수면 int * arr 형태로 선언을 해야 하는거 아닌가요? 저건 int arr[] 인데요.
이런 의문이 드는것도 당연합니다.
결론부터 말하자면 int arr[] 와 int * arr은 동일한 표현입니다.
만약에 컴퓨터가 매개변수 int arr[] 이라고 선언하면 컴파일러가 알아서 int * arr로 변환해서 읽어들이고 진행합니다.
그니깐 겉보기만 배열 형태 처럼 보이고 그냥 포인터라 이말입니다.
#include <stdio.h>
void print_array(int arr[]){
arr++; //오류 발생하지 않음
}
int main(){
int array[] = {1,2,3,4,5};
print_array(array);
return 0;
}
그런 증거는 위 예제에서 볼 수 있습니다.
arr++; 같은건 실제로 배열에서는 불가능한 연산입니다. 그런데 저걸 컴파일 해보면 오류가 발생하지 않습니다.
arr이 포인터라서 arr에 1을 더하는 연산, int 형 포인터 이므로 4바이트가 증가되어서 array[2] 의 주소를 가리키고 있는 상태라고 보시면 됩니다.
포인터 1편에서 배열 이름은 배열의 시작 주소(첫번째 요소 주소) 이며, 인덱스 a[0], a[1] 형태로 접근하듯
포인터로 배열의 시작주소를 받아와서 가리키면 포인터 p[0], p[1] 의 형태로 그 배열과 동일하게 탐색이 가능하단걸 배우셨죠?
처음에 배울땐 이게 도대체 뭐가 유용한지 어디에 사용되는지 알 수가 없었습니다.
그 개념이 바로 여기서 활용되는 겁니다. 배열을 매개변수로 받을땐 기본적으로 주소에 의한 호출이 진행되어서 원본 주소를 넘기게 됩니다.
매개변수인 int arr[] 은 실제로 int * arr 입니다, 즉 포인터 변수입니다. 포인터로 배열의 주소를 받으면 그 배열과 똑같이 사용이 가능하기 때문에 함수 매개변수를 배열로써 사용이 가능한겁니다.
int arr[] 과 같은 표현은 컴파일러에서 int * arr로 변환하여 읽어들이기 때문에 실제로 동일한 표현이지만, 왜 이렇게 표현이 2가지나 있냐고 하면 코드의 가독성 측면때문이라고 생각됩니다.
포인터 변수로 배열의 시작주소를 받으면 사실상 그 배열과 같아지니깐, 매개변수를 표현할때 꼭 int * arr 말고 arr[] 로 표현하는게 어떻게 보면 더 읽기 편할 수도 있습니다.
하지만 표현만 그렇게 되있을뿐 본질적으로 포인터라는 것을 잊으시면 안됩니다.
아래는 그와 관련된 실수입니다.
#include <stdio.h>
void print_array(int arr[]){
int length = sizeof(arr) / sizeof(int);
for (int i = 0; i < length; i++){
printf("%d\n", arr[i]);
}
}
int main(){
int array[] = {1,2,3,4,5};
print_array(array);
return 0;
}
1
2
--------------------------------
Process exited after 0.03088 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
sizeof(arr) / sizeof(int) 로 배열의 요소 개수 계산을 해서 출력하는 함수를 만들었는데요.
1,2,3,4,5가 다 출력이 되지 않고 1,2만 출력이 됩니다.
이런 문제가 발생한 이유는 현재 arr은 포인터 변수라 배열을 저장하고 있는게 아니라 array의 시작주소가 저장되어 있는 8byte 크기를 가진 변수입니다.
그걸 int형 크기인 4로 나눴으니 배열요소가 2개로 계산되버린 것이죠.
arr[]이 배열이 통째로 복사되어서 20byte가 저장되어 있다고 생각하고 발생한 오류입니다.
배열을 매개변수로 넘길땐 주소로 넘깁니다.
#include <stdio.h>
void print_array(int * ptr, int length){
//배열의 이름은 포인터로 시작주소를 가리키고 있으며 a[0], a[1] 과 같은 형태로 접근이 가능하다.
//현재 ptr은 배열의 시작주소를 받아와서 가리키고 있기 때문에 p[0], [1]과 같은 형태로
//그 배열과 동일하게 접근이 가능하다.
for (int i = 0; i < length; i++){
printf("%d\n", ptr[i]);
}
}
int main(){
int array[] = {1,2,3,4,5};
int length = sizeof(array) / sizeof(int);
print_array(array, length);
return 0;
}
1
2
3
4
5
--------------------------------
Process exited after 0.09466 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
그래서 배열 출력 함수를 구현하려면 위처럼 매개변수를 2개로 넘겨야 합니다. (포인터 1편에서 나지막히 설명을 드렸던 부분이기도 합니다.)
그리고 애초에 매개변수를 arr[]로 선언하면 컴파일러가 포인터로 변환을 하면서 일을 더 해야하기도 하고
모호한 표현같아서 필자의 경우엔 그냥 아예 처음부터 포인터 변수로 선언을 합니다.
Scanf에서 주소로 값을 넘기는 이유
#include <stdio.h>
int main(){
int a;
scanf("%d", &a);
printf("%d", a);
return 0;
}
scanf로 값을 읽어올땐 그 변수의 주소를 넘겼습니다.
즉 주소에 의한 호출이죠.
이렇게 넘기는 이유는 지금까지 주소에 의한 호출을 제대로 이해하셨으면 감이 잡히셨을건데요.
scanf() 함수가 호출되면 값을 읽어오는 루틴이 진행되고 이제 매개변수로 들어온 변수 a에 값을 저장해야 하는데요.
scanf()함수 내부에서 main()함수 a에 직접 접근할려면 값에 의한 호출로는 할 수 없습니다. 그냥 값이 복사만 해서 들어오기 때문이죠. 그러나 주소에 의한 호출로 a의 주소를 넘기게 되면 포인터 연산을 통해서
직접 a의 주소를 타고 들어가서 값을 변경해낼 수 있습니다.
그렇기 때문에 scanf는 주소를 넘긴것이였죠. printf와 scanf는 거의 C언어 가장 초창기에 배우는 함수지만 주소를 넘기는 이유는 포인터까지 배워야 알 수 있습니다.
물론 scanf를 사용하실때마다 이 부분을 항상 생각하면서 복잡하게 사용하실 필요는 없습니다.
그냥 이유는 한번만 알아두세요. 이유를 알고 쓰는 것과 모르고 쓰는건 천지 차이니깐요.
Call By Reference의 장점
그럼 또 이런 의문이 생길 수 있습니다.
배열은 왜 호출할때 주소로 넘기나요? 주소에 의한 호출의 장점은 무엇이죠?
극단적인 예시를 들어서, 1억개의 요소를 가진 int형 배열이 있다고 쳐봅시다.
int arr[10^8] = {0,};
이것을 매개변수로 넘길때 값에 의한 호출로 복사해서 넘기면 어떻게 될까요?
저 요소 요소 하나하나를 복사하는데 시간이 많이 들것입니다.
시간만 많이 드는게 아니죠. 프로그램은 램 위에서 돌아가는데 복사하면서 똑같은게 2개가 생기는 과정에서 메모리도 많이 먹게 됩니다.
함수의 매개변수(지역변수)들이 늘어날수록 이들은 하나하나씩 스택 영역에 쌓이고 많은 시간과 메모리 공간을 필요로 합니다.
배열의 경우는 기본적으로 여러 요소들의 모임인데 매개변수로 메모리를 낭비하지 않고, 시간도 줄이면서 보내는 가장 좋은 방법은 주소를 넘기는 것입니다.
배열의 요소가 10개던 100개던 1억개던 상관없이 그 시작주소를 넘기면 데이터의 크기와 관계없이 메모리는 낭비하지 않고, 시간은 최적으로 가져갈 수 있습니다.
C언어는 주소로 인자값을 넘기는 방법(주소에 의한 호출)을 채택해서 메모리, 시간 낭비를 모두 해결할 수 있었습니다.
주소를 반환하는 함수
지금까지 배운 값에 의한 호출, 주소에 의한 호출은 함수를 호출할때 얘기였고,
이제 함수의 반환값(return 값)에 대해서도 이야기 해봅시다.
지금까진 함수를 반환할때 값만 반환했는데 이번에 주소를 반환하는것도 한번 해봅시다.
#include <stdio.h>
char * input(){
char strings[1000];
scanf("%s", strings);
return strings;
}
int main(){
char * ptr = input();
printf("%s", ptr);
return 0;
}
해당 예제는 input()이라는 함수에서 scanf로 값을 받아서 지역변수 strings에 저장하고 그 주소를 return 하는 함수입니다. 배열의 이름은 시작주소였던거 기억나시죠?
scanf() 는 입력받을 공간의 주소를 받게 되는데 배열의 이름은 그 자체로 시작 주소이므로 scanf("%s", strings) 라고만 쓰면 됩니다. scanf("%s", &strings); 라고 쓰면 안되는거죠.
(파이썬을 해보신분들은 알겠지만 파이썬의 input() 함수를 한번 C언어에서 구현해보았습니다. - 파이썬 모르시면 PASS)
그런데 이 예제는 한가지 문제가 있습니다. input() 내부의 지역변수 strings의 경우는 지역변수기 때문에 함수가 종료되면 메모리 공간이 소멸해버립니다. 경고로 뜨고 컴파일도 되지만 값이 정상적으로 출력될 수도, 아예 출력이 안될 수도 있습니다.
저의 경우엔 Dev C++로 컴파일은 되는데 출력결과가 아예 안뜨네요.
이런걸 해결하려면 예전에 배웠던 정적 변수(Static Variable)을 사용해주시면 됩니다.
정적 변수는 함수가 종료된 이후에도 메모리 공간이 소멸되지 않고 남아있습니다.
#include <stdio.h>
char * input(){
static char strings[1000];
scanf("%s", strings);
return strings;
}
int main(){
char * ptr = input();
printf("%s", ptr);
return 0;
}
Hello
Hello
--------------------------------
Process exited after 0.7684 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
값이 정상적으로 잘 출력되네요.
간단히 static이라고 붙이면 정적변수로 바뀌어서 함수가 종료되어도 메모리 공간이 소멸되지 않고 남아있게 되므로, 포인터로 가리켜서 출력이 가능합니다.
main() 함수에 인자값 받기
지금까지 공부하면서 프로그램 처음 시작시 호출되는 main()함수는 인자값이 없었습니다. 매개변수와 인자는 엄밀히 들어가면 조금 다르지만 여기선 매개변수가 없었다고 이해하십시오.
#include <stdio.h>
int main(int argc, char* argv[]){
printf("문자열의 수 : %d\n", argc);
for(int i = 0; i < argc; i++)
{
printf("%d번째 문자열 : %s\n", i, argv[i]);
}
return 0;
}
다음과 같이 작성하시고 컴파일을 해주세요. 그리고 나온 exe를 아래와 같이 실행해보세요.
보통 cmd로 exe의 경로로 실행을 하면 프로그램만 그냥 실행됩니다만, 뒤에 공백으로 구분을 해서 a,b,c,d 이렇게 제공을 해주니 main( ) 함수의 인자값으로 받아내어 제대로 출력이 되었습니다.
역할을 정리해보면
int argc : 문자열의 수를 저장
char* argv[] : 들어온 문자열을 배열형태로 저장
(각 요소가 char*인 포인터로 구현된 문자열 배열)
입니다. (변수명은 크게 중요하지 않습니다.)
확인해보니 0번째엔 기본적으로 실행파일의 주소가 할당되어 있네요.
저희가 제공한것은 1번째부터 시작합니다.
파일명.exe (인자) (인자) (인자) ....
cmd창으로 위와 같은 형태로 실행을 하면 main 함수에 인자값을 제공하여 실행할 수 있습니다.
프로그램을 cmd창으로 실행하는 형태로 만들었을때 인자값에 따라 프로그램을 다르게 동작하게 할 수 있습니다.
void형 포인터
지금까진 자료형이 있는 포인터 int * ptr, char * ptr 과 같은것을 다루었지만 자료형이 없는 포인터도 있습니다.
바로 void 형 포인터입니다.
지금까지 char 변수에 대해 포인터로 접근하려면 char * 포인터로 선언해서 주소를 연결해줘야 했습니다. 왜냐면 포인터에 아무 자료형도 붙이지 않으면 데이터를 접근할때 몇 byte씩 읽어내야 할지 알 수 없기 때문이죠.
포인터 앞에 자료형은 데이터 접근 기준을 마련하는것 이였습니다.
char * 로 선언된 포인터의 경우 앞의 char을 보고 1byte씩 읽어내야 겠다, 컴퓨터가 알 수 있는것이죠.
근데 void 형 포인터는 왜 쓰는걸까요.
#include <stdio.h>
int main(){
void * ptr = NULL;
int a = 10;
ptr = &a;
char c = 20;
ptr = &c;
return 0;
}
일단 void형 포인터는 모든 자료형의 주소를 저장할 수 있는 자유로운 포인터 입니다.
자료형이 없기 때문이죠.
물론 int형 포인터 변수로 char 변수의 주소를 지칭할 수는 있는데 컴파일러에서 경고가 뜹니다. (오류는 아닙니다.)
#include <stdio.h>
int main(){
void * ptr = NULL;
int a = 10;
ptr = &a;
printf("%d", *ptr); //오류 발생
char c = 20;
ptr = &c;
return 0;
}
하지만 접근 기준이 없어서 값에 접근하는게 안됩니다.
그럼 void형 포인터는 가리키기만 하고 값은 접근할 수 없는 잉여 포인터냐? 하면 그건 또 아닙니다.
방법이 있습니다.
#include <stdio.h>
int main(){
void * ptr = NULL;
int a = 10;
ptr = &a;
printf("%d", *(int*)ptr); // ptr의 주소를 int형 주소로 강제 변환
return 0;
}
10
--------------------------------
Process exited after 0.0246 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
이렇게 이전에 배웠던 강제 형변환을 이용하여 (int*)를 붙여서 ptr의 주소를 int형 주소로 강제로 변환시켜 버리고 앞에 다시 *를 붙여주면 값 접근이 가능해집니다.
void 형 포인터를 잘 이용하려면 강제 형변환이 중요합니다.
여기까지 따라오시느라 수고 많으셨습니다. 포인터는 물론 뒤에서도 사용되나 우선 포인터에 대해 집중적으로 배우는건 이번 3편이 마지막입니다 !
저도 쓰느라 정말 고난이였네요 ㅠㅠ 앞에서 강의 3개쓰는거보다 이거 1편쓰는게 더 오래 걸렸습니다.
제대로 이해를 하셨다면 C언어에 대한 이해가 상당히 깊어지셨을건데요, 포인터라는 내용 자체가 Java, C#과 같은 고생산성 언어에선 최대한 없애는 방향으로 가고 있지만
C라는 언어가 세계적으로 가장 인지도 있고, 많이 사용되는 언어인만큼 C언어에서 포인터를 배우는게 매우 매우 중요하다고 생각됩니다.
물론 글쓰는 필자 본인도 포인터를 완벽하게 활용한다기엔 아직 한~참 멀었지만요 ^^;;
아무튼 다음편부턴 드디어 새로운 내용 구조체에 대해 알아보겠습니다.
'프로그래밍 강좌 > C' 카테고리의 다른 글
[C언어 강좌] #14-2 구조체(Structure Type) (0) | 2021.12.28 |
---|---|
[C언어 강좌] #14-1 구조체(Structure Type) (0) | 2021.12.23 |
[C언어 강좌] #13-2 포인터(Pointer) (0) | 2021.11.07 |
[C언어 강좌] #13-1 포인터(Pointer) (0) | 2021.06.28 |
[C언어 강좌] #12-2 [Array] 다차원 배열 (8) | 2020.05.17 |