집중 단기 시작 세미나 ('자료구조와 알고리즘')
2014년 2월 10일~ 2월 14일 진행
강형민 외부 강사님(checkdisk@ioacademy.co.kr)
강의 시간 : 10시 ~ 17시
사용할 언어 : C언어
개발환경 : MS Visual Studio 2010
1일차 : 고급 C 언어
2~3일차 : 자료구조(리스트, 트리, 스택, 큐, ...)
4~5일차 : 알고리즘(압축, 암호화, 패턴 매칭, ...)
마지막 날에 테스트 볼 예정. 시험 난이도는 수업시간에 배운 내용 정도
1.
프로젝트명 : 20140210
추가 옵션 : 빈 프로젝트 체크
2. 솔루션 탐색기의 소스 파일 폴더에서 새 항목을 추가한다.
3. c++파일을 선택하고 파일명은 1.c로 준다.
( 확장명이 c언어가 없다. 하지만 c언어로 만들 수 있다. 확장자를 통해서 c++/c를구분한다.
따라서 c언어로 개발을 할 경우 *.cpp를 선택 한 후 파일 이름을 쓴 후확장명을 *.c로 해주어야한다. )
4. 소스코드 작성 후 디버깅하지 않고 시작(Ctrl + F5)을 한다.
5.
1.c 파일의 속성에서 '빌드에서 제외' - '예' 로 바꿔준다.
6. 빌드에서 제외 됬음이 나타난 것을 확인한다.
decay 사용하는 이유는 성능상의 오버헤드를 줄이기 위해서이다.
int arr[3];의 경우, arr이 전체 타입인 int를 따른다. 하지만
배열을 선언할 때에는 배열의 심봉를 항상 타입과 길이 사이에 와야 한다.(중요중요!)
#include <stdio.h> // arr와 &arr의 의미 void main() { int arr[3]; arr; // 배열의 시작주소, 타입은 첫 번째 원소 &arr; // 배열의 시작주소, 타입은 배열 전체 printf(" arr = 0x%p\n", arr); printf("&arr = 0x%p\n", &arr); }
#include <stdio.h> // arr와 &arr의 의미 void main() { int arr[3]; int *p1 = arr; // 1. 일중 포인터 : 다른 지역에 있는 메모리에 접근하기 위해서 사용 int [3] *p2 = &arr /*하지만 (중요중요!)때문에 int *p2[3] = &arr;을 해야하는데 이 경우 오류이다. 첫번째 심볼을 찾고 뒤에 있는 기호를 본다. 여기서 심볼은 p2. 뒤에 있는 [이다. 따라서 배열! 이게 포인터변수인 것을 알리려면 int (*p2)[3]2.배열 포인터으로 해 줘야 한다. 이때 p1은 일중포인터 p2는 배열 포인터라고 한다.*/ printf(" arr = 0x%p\n", arr); printf("&arr = 0x%p\n", &arr); }
파일명 : 4.c
#include <stdio.h> void main() { int arr[2][3]; // 원소의 갯수는 몇 개?
int arr[2][3]; 이것은 사실 2차원 배열의 원소가 아니라 1차원 배열이다.
2차원 배열은 2차원이 아니라 1차원 배열의 확장이다.
따라서 위의 것을 다시 쓰면 int[3] int[2];라 쓸수 있고, 이것은 int 타입의 길이가 3개인 1차원 배열을 원소로 하는 1차원 배열이다.
#include <stdio.h> void foo(int arr[][3]) { } void main() { int arr[2][3]; // int arr[][3] = arr; // int arr[][3] = arr; //error, 함수의 매개변수에 특화된 문법 }
11111111111111
#include <stdio.h> #include <stdlib.h> void foo(int (*arr)[][3]) { } void main() { int arr[2][3]; // int arr[][3] = arr; // int arr[][3] = arr; //error, 함수의 매개변수에 특화된 문법
int (*pArr)[3] = arr }
테스트1! 힙에 행은 2개이고 열은 3개인 int타입의 동적 2차원 배열을 선언해보세요!
#include <stdio.h> #include <stdlib.h> void main() { int(*p)[3] = malloc(sizeof(int)*2*3); // 왜 malloc에서 빨간 줄이 나타난걸까?
지금 우리의 컴파일러는 cpp컴파일러이기 때문에
for(int i=0;i<2;i++) { for(int j=0;j<3;j++) p[i][j] = 3; } for(int i=0; i<2; i++) { for(int j=0; j<3; j++) printf("%d\n", p[i][j]); } }
여기서 컴파일 에러가 없고, 분명 실행도 잘 되는데 malloc 부분에서 빨간 줄이 나 있을 것이다. 이것은 현재 우리의 컴파일러가 cpp컴파일이기 때문에 그런 것이지, 소스 코드 상의 문제는 없는 것이다.
파일명 : 5.c
2차원 배열이 왜 2차원이 아닌가?
여기서 배열의 주소는 왜 0부터 시작할까? .........
포인터와 포인터의 사칙연산은 되지 않지만, 제한적인 연산은 가능하다.
#include <stdio.h> // 포인터의 연산은 사칙 연산은 불가능하나 // 제한적인 연산은 가능하다. void main() { int *p, *q; p + q; /*과연 실제로 안될까?된다. 이것을 값으로 볼지 주소로 볼지를 선택하는건 사용자의 선택인데, 이 경우 컴파일러가 막은 것이다. 보안상의 위험성을 방지하고자 OS가 죽여버린다. 주소와 주소가 더해지면은 의미없는 주소가 나올 확률이 높다. 이것을 역참조해서 찾아가면 허가없는 메모리 참조가 되지 않을까요? 그러므로 컴파일 에러를 내는 것이 좋다. 그래서 컴파일러가 막는 것이다.*/ p - q; //포인터 - 포인터 = 정수 p * q; //곱셈은 덧셈의 연속이기 때문에 되지 않는다. p / q; }
p + q의 경우, 실제로는 되는 연산이다.
하지만 이것을 값으로 볼지 주소로 볼지를 선택하는건 사용자의 선택인데, 이 경우 컴파일러가 막은 것이다.
보안상의 위험성을 방지하고자 OS가 죽여버린다.
주소와 주소가 더해지면은 의미없는 주소가 나올 확률이 높다.
이것을 역참조해서 찾아가면 허가없는 메모리 참조가 되지 않을까?
그러므로 컴파일 에러를 내는 것이 좋다. 그래서 컴파일러가 막는 것이다.
외웁시다!
//포인터 + 정수 = 포인터
// 포인터 - 정수 = 포인터
// 포인터 - 포인터 = 정수
테스트2! 다음 배열에서 첨자 연산을 사용하지 않고 배열의 중간값을 구해보세요.
void main() { int arr[5] = {1,2,3,4,5}; int *center =??????????; //&arr[2];//&arr[2]; printf("%d\n", *center); //3 }
배열 첨자 연산
void main(){ int arr[2][3] = {{1,2,3},{4,5,6}}; printf("%d\n", arr[1][2]); printf("%d\n", *(arr+1)[2]); //printf(" arr = 0x%p\n", arr); //getchar(); //printf("arr+1 = 0x%p\n", arr+1);/ } /*이와같이 비정상적인 연산 값이 나온다. 별표보다 첨자가 우선이다. 그러므로 행 단위부터 접근하기 위해서 별표를 먼저 해석시키기 위해 (*(arr+1))을 해야한다.*/
// 결론 : 2차원 배열은 이중 포인터가 아니다. void main() { int arr[2][3] = {1,2,3,4,5,6}; printf("%d\n", **arr); // 1이 나온다. // 이것은 arr[0][0] = *(*(arr + 0) + 0); 와 같다. // 결국 2차원 배열은 2중 포인터처럼 보인다. 0이 전부 상쇄되고 있다. // 이것은 어느순간 죽게 되어 있다. 이것은 그저 1차원 포인터의 확장일 뿐.. }
파일명 : 8.c
//이중 포인터의 개념 #include <stdio.h> int div(int a, int b) { return a/b; } void main() { int a = 4, b = 2; printf("%d\n", div(a,b)); }
return -1;의 리턴 결과 값이 숫자 -1일지 에러 코드 값 -1일지 헷갈리므로 좋은 코드는 아니다.그래서 C++나 Java에서는 예외처리로 해준다.
void main() { char name[1024]; printf("input name: "); scanf("%s", name); printf("your name: %s\n", name); }
좋은 코드가 아니다. 왜냐하면 사용자로부터 이름을 5번 입력받는다면 이 코드가 5번이 있어야된다.. 이렇게 반복되는 코드는 함수로 빼는게 좋다. (밑에 get_name() 부분)
정리!!!
일중 포인터를 쓰는 이유는 다른 지역의 일반 변수의 심볼에 접근하기 위하여 사용!
이중 포인터를 쓰는 이유는 다른 지역의 포인터 변수의 심볼에 접근하기 위하여 사용!
파일명 : 9.c
이러한 포인터 배열은 쓰는 이유는 모든 데이터를 힙에다가 저장하기 위해서
//3개의 이름을 저장하는 프로그램을 구현해보자! void mian() { char names[3][1024]; int i; for(i=0;i<3;i++) { printf("input name: "); scanf("%s", names[i]); } for(i=0;i<3;i++) { printf("name[%d]: %s", i, names); } } /*이 프로그램은 메모리 낭비가 심하다*/
asdf1
삼중 포인터
//삼중포인터 #include <stdio.h> void get_name(char** *p) { } void main() { char **names; get_name(&names); }
숙제!! 이중 포인터를 삼중포인터로 바꾸기
파일명 : a.c
함수의 선언 : 함수의 심볼은 반드시 리턴 타입과 매개 변수 사이에 위치해야한다.
리턴타입 함수명([파라미터, ...]);
void(* hoo())(int) : 심볼은 hoo인데 앞에는 포인터 뒤에는 함수호출연산자. 뒤가 함수호출이니까 hoo는 함수호출연산자
void(* zoo( void(*fp)(int)))(int) : 심볼은 zoo인데 리턴하는 것은 포인터인데 포인터의 타입은 int이다. 함수포인터를 리턴하고 있다..
근데 누가 이렇게 길고 복잡하게 쓸까? 아니..안쓴다. 그래서 alias(별명)을 붙여서 쓴다.
'알고리즘 > 자료구조/알고리즘' 카테고리의 다른 글
[집중단기 마지막날] 자료구조 - 재귀호출, 정렬 (0) | 2014.02.14 |
---|---|
[집중단기 4일차] 자료구조 (0) | 2014.02.13 |
[집중단기 3일차] 자료구조 - Stack, 비트연산, Dynamic Array (0) | 2014.02.12 |
[집중단기 2일차] 자료구조 - 포인터 (0) | 2014.02.11 |