내용 정리

 

※ 순차 자료구조
구현할 자료들을 논리적인 순서대로 메모리에 연속하여 저장하는 구현 방식
구현 방식 → 배열

 

 

※ 순차 자료구조 vs 연결 자료구조
순차 자료구조 - 메모리에 연속하여 저장하여, 논리적인 순서와 물리적인 순서가 일치 (배열)
연결 자료구조 - 메모리에 저장된 위치에 상관없이, 링크에 의해 논리적인 순서를 표현 (포인터)

 

 

※ 선형 리스트 vs 연결 리스트
선형 리스트 - 메모리에 저장하는 구현 방식에 따라 순차 방식으로 구현
연결 리스트 - 메모리에 저장하는 구현 방식에 따라 연결 방식으로 구현

 순차 방식으로 구현한 리스트 = 선형 리스트
      연결 방식으로 구현한 리스트 = 연결 리스트

순차 방식의 리스트는
논리적 순서와 물리적 순서가 일치하기 때문에
원하는 정보가 어떤 인덱스에 있는지 알 수 있다.
 간단하게 배열로 구현 가능
    구현 용이, but 메모리 공간 낭비, 삽입 삭제에서 원소들의 물리적 이동 연산에 대한 오버헤드 발생

 

  1월 2월 3월 4월
판매 량 1024 1050 2000 850

 

위와 같은 데이터가 있고, 배열로 데이터를 저장한다면
판매량만 저장하여도 원소들의 위치를 알 수 있다. (3월 판매량은 index 2에 저장되어있다.)

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include
int main()
{
    int a[4= { 102410502000850 };
    int search;
 
    printf("몇 월의 판매량을 알고 싶은가?? ");
    scanf("%d"&search);
 
    printf("%d월의 판매량은 %d입니다.", search, a[search-1]);
 
    return 0;
}
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter

 

이렇게 말이다. 인덱스 하나로 원하는 정보를 얻을 수 있는 것이다.

 

 

시작 위치의 주소가 a이고

원소 1개의 크기가 k이면

 

n번째 원소의 위치는 = a+(n-1)k이다.

 

 

 

원소 삭제, 삽입은

데이터의 논리적 순서와 물리적 순서가 일치하게 유지하기 위해서

매번 원소의 위치를 조정해야 한다.

 

 

예를 들어서

2015년 데이터가 인덱스 0에

2017년 데이터가 인덱스 1에

2018년 데이터가 인덱스 2에 있다면

 

2016년 데이터를 인덱스 1에 저장해야 하기 때문에

2017, 2018 데이터를 하나씩 뒤로 밀어야 한다.

 

책에서는 몇 개의 데이터를 옮겨야 하는지 공식으로 정리하였는데

굳이 공식을 알 필요 없이 상황에 맞게 생각하면 금방 알 수 있다.

 

1차원 배열뿐만 아니라

2차원, 3차원 배열도 순차방식으로 데이터를 다룰 수 있다.

 

행 우선 순서 방법으로 메모리에 저장된다.

 

 

 

연습문제 풀이

 

이번 챕터는 연습문제가 부실한 것 같다.

 

책에서는 다항식의 덧셈을 구조체를 이용하여

다항식의 차수와 각 항의 계수를 저장할 배열을 이용하였다.

 

나는 간단하게 배열을 사용하여 풀이해보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//다항식의 덧셈
#include <stdio.h>
#include <stdlib.h>
 
void add(int* a, int* b, int* c)
{
    int i;
    for (i = 0; i < 5; i++)
    {
        c[i] = a[i] + b[i];
    }
}
 
void print(int* arr)
{
    int i;
 
    printf("C(x)= ");
    for (i = 4; i >= 0; i--)
    {
        printf("%dx^%d + ",arr[i],i);
    }
    printf("\b\b");
}
 
int main()
{
    //배열에 다항식의 역순으로 저장 (연산 편하게 하기 위해서)
    int a[5= { 05340 }; //배열 크기 맞춰줌 맨뒤에 0 삽입
    int b[5= { 12013 };
    int c[5]; //a+b저장할 다항식 배열
 
    add(a, b, c);
 
    print(c);
 
    return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter

 

다항식의 덧셈 실행결과

 

Chapter2 에는 여러 가지 기법이 있지만

 

어렵다고 생각되는, 이중 포인터, 재귀호출 이 두 가지를 정리해보겠다.

 

 

 


 

포인터

 

내용 정리

포인터에 관련된 내용은 C프로그래밍에서 배웠으니, 이중 포인터만 정리하고 넘어간다.

 

 

 

※ 이중 포인터
포인터의 포인터 즉, 포인터를 가리키고 있는 포인터.

 

char **ptr의 모습

 

 


 

연습 문제

바로 이중포인터 연습문제 풀이를 통해서, 쉽게 이해 하자.

 

 

예제 2-11 포인터 배열과 포인터의 포인터 사용하기.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
 
int main()
{
    char *ptrarr[2];
    char **dptr;
    int i;
 
    ptrarr[0= "Seoul";
    ptrarr[1= "Mapo";
 
    //이중 포인터 변수에 포인터변수의 주소를 할당(배열명은 포인터와 유사)
    dptr = ptrarr;  
    
    //포인터 배열 이용한 출력
    printf("\n ptrarr[0]의 주소 (&ptrarr[0]) = %u"&ptrarr[0]);
    printf("\n ptrarr[0]의 값 (ptrarr[0]) = %u", ptrarr[0]);
    printf("\n ptrarr[0]의 참조값 (*ptrarr[0]) = %c"*ptrarr[0]);
    printf("\n ptrarr[0]의 참조 문자열 (*ptrarr) = %s \n"*ptrarr);
 
    printf("\n ptrarr[1]의 주소 (&ptrarr[1]) = %u"&ptrarr[1]);
    printf("\n ptrarr[1]의 값 (ptrarr[1]) = %u", ptrarr[1]);
    printf("\n ptrarr[1]의 참조값 (*ptrarr[1]) = %c"*ptrarr[1]);
    printf("\n ptrarr[1]의 참조 문자열 (*(ptrarr+1)) = %s \n"*(ptrarr+1));
 
    //이중 포인터 이용한 출력
    printf("\n dptr의 주소 (&dptr) = %u"&dptr);
    printf("\n dptr의 값 (dptr) = %u", dptr);
    printf("\n dptr의 1차 참조값 (*dptr) = %u"*dptr);
    printf("\n dptr의 2차 참조값 (**dptr) = %c"**dptr);
    printf("\n dptr의 2차 참조 문자열 (*dptr) = %s \n"*dptr);
 
    printf("\n ptrarr의 주소 (&ptrarr) = %u"&ptrarr);
    printf("\n ptrarr의 값 (ptrarr) = %u", ptrarr);
    printf("\n ptrarr의 1차 참조값 (*ptrarr) = %u"*ptrarr);
    printf("\n ptrarr의 2차 참조값 (**ptrarr) = %c"**ptrarr);
    printf("\n ptrarr의 2차 참조 문자열 (*ptrarr) = %s \n\n"*ptrarr);
 
    return 0;
}

cs

 

실행 결과

 

 

 

 

line 10 까지 실행되면, 메모리 구조는 아래와 같다.

 

 

 

 

line 13 까지 실행되면, dptr도 ptrarr이 가리키고 있는 포인터 배열의 주소를 가리킨다.

 

파란색 박스는, 실제 메모리 구조는 아니지만, 이해하기 쉽게 하기 위해 그림.

 

 

 

배열명 = 포인터처럼

포인터 배열도 이중 포인터처럼 사용 가능하다.

 

코드 실행 결과에서 확인 가능하다.

 

ptrarr의 값 = dptr의 값 = ptrarr[0]의 시작 주소
*ptrarr = *dptr = ptrarr의 값 = Seoul문자열의 시작 주소

 

포인터는 복잡한 설명보다는, 메모리 구조를 직접 그려보며 이해 하는것이 좋다.

 

 

 


 

 

 

재귀호출

 

내용 정리

 

 

※ 재귀호출
함수가 자기 자신을 호출하여 순환하여 수행되는 것

 

재귀호출을 사용하면 프로그램을 간단하게 작성할 수 있는 이점이 있다.

같은 유형의 하위 작업이 필요할 때 사용한다.

 

 

한 번에 해결할 수 있는 가장 작은 단위의 하위 작업을 베이스케이스라고 한다.

 

 

베이스케이스를 잘 정의하고, 재귀호출이 베이스케이스를 향하게 코딩해야 한다.

 

 

재귀호출 함수를 가장 잘 보여주는 팩토리얼 문제를 확인해보자.

 

 

예제 2-14 재귀호출을 이용해 팩토리얼 값 구하기

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
 
unsigned int fact(int);
 
int main()
{
    int n, result;
 
    printf("정수를 입력하세요: ");
    scanf("%d"&n);
    result = fact(n);
    printf("\n\n %d의 팩토리얼 값은 %d입니다.", n, result);
 
    return 0;
}
 
unsigned int fact(int n)
{
    int value;
    if (n <= 1)
    {
        printf("\n fact(1)함수 호출!");
        printf("\n fact(1)함수 반환!");
        return 1;
    }
    else
    {
        printf("\n fact(%d)함수 호출!",n);
        value = (n * fact(n-1));
        printf("\n fact(%d) 값 %d 반환!", n,value);
        return value;
    }
}
cs

 

 

재귀함수인 fact 함수에서 

 

베이스케이스는 n <= 1 인 경우이다.

 

나머지의 경우는 베이스케이스를 향해 함수를 반복 실행한다.

 

 

 

실행 결과

 

 

실행결과를 참고하면 재귀호출의 기본적인 원리를 알 수 있다.

 

 

재귀함수는 함수 실행 중 내부 함수 실행

 

내부 함수 실행 중 또 내부 함수 실행

 

이때, 베이스 케이스를 만나면 

 

마지막에 실행됐던 함수부터 리턴이 되는 방식이다.

 

그림으로 표시하면 아래와 같은 순서로 코드가 흘러간다.

 

 

 

다른 대표적인 예로 하노이탑 문제가 있는데, 생략하겠다.

 

 

 

 

 

내용 정리

 

드디어 C 프로그래밍의 애증의 포인터이다.

 

내 생각에 아직도 C언어가 널리 쓰이는 이유는 

 

포인터 덕분인것 같다 ㅎㅎㅎ

 

아직도 어렵다... 꼭 정복하겠다.

 

 

 

※ 포인터 개념 정리: 포인터 정리 및 포인터와 배열명의 공통점 및 차이점

 

 

포인터를 사용하는 이유
- 참조 불가능한 변수를 간접적으로 참조
- 프로그램의 성능을 개선하고 기억 공간을 효율적으로 사용할 수 있다.
- 동적 할당이 가능하다. (트리, 연결리스트에서 자세히)

포인터 사용 순서
1. 포인터 변수 선언
2. 포인터 변수에 다른 변수의 주소 대입
3. 간접 참조 연산자(*) 사용하여 포인터 변수 이용

출처https://ko.wikipedia.org/wiki/%ED%8F%AC%EC%9D%B8%ED%84%B0_(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D)

 

위의 그림에서 *pa는
배열 a의 시작 주소를 가리키는 포인터 변수이다.

앞에서 배열명인 a는 배열 a의 시작 주소라고 했다.

결국 *pa = a이다.

*pa가 int형인 이유는 int형 변수의 주소를 저장하기 때문이다.

예제를 보자

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main()
{
    int *pa;
    int a[5= { 1020304050 };
    int i;
 
    pa = a; //또는 pa = &a[0];
 
    printf("pa의 값(a의 시작주소 들어있음):%u\n", pa);
    printf("배열 a의 주소:%u\n\n"&a);
 
    printf("간접참조 연산자 이용해서 배열의 값 얻기\n\n");
 
    printf("배열 첫번째 원소 얻기\n");
    printf("*pa = %d\n"*pa);
    printf("a[0] = %d\n", a[0]);
    printf("*pa = a[0]\n\n");
 
    printf("배열 두번째 원소 얻기\n");
    printf("*(pa+1) = %d\n"*(pa+1));
    printf("a[1] = %d\n", a[1]);
    printf("*(pa+1) = a[1]\n\n");
 
    printf("배열 세번째 원소 얻기\n");
    printf("*(pa+2) = %d\n"*(pa + 2));
    printf("a[2] = %d\n", a[2]);
    printf("*(pa+2) = a[2]\n\n");
 
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

pa에 배열 a의 시작 주소를 넣어주어 
pa가 간접적으로 배열을 참조할 수 있다는 것을 보여준다.


아래 결과를 보면 이해할 수 있다.

 

 

포인터는 그림으로 이해하면 훨씬 이해하기 쉽다.

 

 

 

실제 메모리 내부의 상황을 그림으로 나타내었다.

더 간략하게 표현하면 이런 모습

 

 

 

 

 

 

포인터 변수 pa는 배열 a가 시작하는 주소를 기억장치에 저장하고 있고
int형 배열인 a는 원소마다 저장된 값을 가지고 있다.


아래 그림은 위의 결괏값이랑 다른 주소를 나타낸다.
다시 실행했기 때문에 다른 것이므로 이는 무시하고

 

 

pa+1이 a[1]의 주소를 가진다고 보면 된다.
pa가 int 형이기 때문에
만약 pa에 위의 그림과 같이 7797696이 저장되어 있다면
pa+1은 int형 크기인 4byte가 더해져
7797000이 된다.

그래서 *(pa+1)은 a[1]과 같게 되는 것이다.

=======================================
위에서 정리한 내용을 보면
포인터 = 배열명
이라고 하는 공식(?)이 저명하다고 생각된다.

하지만 엄밀히 말하면 다르다!

 

 

다르다

 

 

다르다.

 

 

 

※ 값에 의한 호출, 주소에 의한 호출

 

값에 의한 호출과 주소에 의한 호출은 함수 부분에서 잠깐 정리했었는데

 

포인터의 개념을 배웠으니 더 확실하게 정리해보겠다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
 
void swap_value(int x, int y)
{
    int temp;
    temp = x;
    x = y;
    y = temp;
    printf("swap_value함수 내부의 값: x = %d, y = %d\n\n", x, y);
}
 
void swap_address(int *x, int *y)
{
    int temp;
    temp = *x;
    *= *y;
    *= temp;
    printf("swap_address함수 내부의 값: *x = %d, *y = %d\n\n"*x, *y);
}
 
int main()
{
    int x = 100, y = 200;
 
    printf("main에서 함수 사용 전 값: x = %d, y = %d\n\n", x, y);
 
    swap_value(x, y);
    printf("main애서 swap_value함수 사용 후: x = %d, y = %d\n\n", x, y);
    
    swap_address(&x, &y);
    printf("main애서 swap_address함수 사용 후: x = %d, y = %d\n\n", x, y);
    
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

 

결론은
값에 의한 인자 전달은 함수에서는 값이 바뀌지만
main으로 돌아오면 그대로

주소에 의한 인자 전달은 함수에서 값 변경되어서
main으로 돌아와도 유지

이제 이유를 차근차근 살펴볼 차례

 

1. swap value함수 호출한 후 메모리

 

2. swap value함수 내부 실행

 

이때 swap value함수는 변경한 값을 돌려주지 못한다. 
void함수이고, return을 할 수 있는 int 함수로 변경한다고 해도
return 값은 1개이므로 변경된 2개의 값을
main으로 전달할 수 없다.

 

 

3. swap_value 함수 종료 후 기억 장소 리턴, main에 아무런 영향 주지 못하고 사라짐

 

4. swap_address함수 호출한 후 메모리

 

5. swap_address함수 내부 실행

 

 

6.  swap_address함수 종료 후 기억장소 리턴, 사라짐

 

 

7. 메모리의 마지막 상태

 

 

 

※ 포인터와 문자열

 

C에서는 문자열 변수가 없다.
배열 or 포인터로 문자열을 표현한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
 
int main()
{
    char str1[10= "language";
    char *str2 = "language";
 
    printf("배열 문자열 변경\n");
    printf("%s->", str1);
    strcpy(str1, "program");
    printf("%s\n\n", str1);
 
    printf("포인터 문자열 변경\n");
    printf("%s->", str2);
    str2 = "Cprogramming";
    printf("%s\n\n", str2);
 
    return 0;
}

 

 

str1은 배열의 시작 주소인 포인터 상수
주소변경이 불가능하므로
따라서 str1 = "문자열" 이 불가능하다.

str2는 문자열을 가리키는 포인터 변수
원래 "language"가 저장되어 있는 공간을 가리켰는데
str2="문자열"로 다른 문자열이 저장되어있는 공간을 가리키게 할 수 있다.

str1과 같은 배열의 문자열은
문자열 함수(strcpy)만을 이용해 전체 문자열을 변경하거나, 원소 하나하나 만을 변경할 수 있다.

 

 

기억공간의 차이


배열 문자열은 미리 개발자가 배열의 크기를 정해야 한다. 
만약 배열의 크기가 20인데
"abc"라는 문자열을 저장한다면
'\0'문자를 포함하여 총 4개의 원소만 사용하고, 나머지 16개의 공간은 낭비가 된다.

포인터 문자열은 "abc"를 대입하면 '\0'을 포함하여
문자의 길이에 맞게 동적으로 할당된다.

동적 할당은 나중에 자세히

예제 하나 더
포인터, 배열 문자열 이용하여 문자열 대문자, 소문자화

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <ctype.h>  //toupper, tolower위해서
#include <string.h> 
 
int main()
{
    char s[100], *p;
    int i;
 
    printf("문자열을 입력하시오 : ");
    gets(s);
 
    p = s;
 
    //char형 포인터 이용해서 문자열 대문자로 변경
    while (*!= '\0')
    {
        *= toupper(*p);
        p++;
    }
    printf("대문자로 변환한 결과 : %s\n", s);
 
 
    for (i = 0; i < strlen(s); i++)
    {
        s[i] = tolower(s[i]);
    }
    printf("소문자로 변환한 결과 : %s\n", s);
 
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

쉬우니까 설명은 생략

 


※ char형 포인터 배열, 포인터와 다차원 배열

 

포인터와 1차원 배열은 전에 포스팅했다.

그럼 2차원 배열, 3차원 배열은 어떻게 될까

똑같은 원리이다.

A[3]에서 A은
배열 A의 시작 주소라고 했다.

A는 주소 100을 나타내는 포인터 상수라 했다.

 

 

실제 메모리의 구조는 아니지만 이해하기 쉽게 그리면

 

A는 배열 A를 포인팅 하는 포인터 상수이다.
 
그럼 2차원 배열

 

B[2][3]은 어떻게 될까?
 
B는 배열 B의 시작주소일 것이다.

실제 메모리의 구조는 아니지만 이것도 이해하기 쉽게 그리면
 

 

B는 전체 배열을 포인팅 하며
B[0]은 B[0][0], B[0][1], B[0][2] => B[0]으로 시작하는 원소들을 포인팅한다. (B[0][0] 주소(100) 포인팅)
B[1]은 B[1][0], B[1][1], B[1][2] => B[1]으로 시작하는 원소들을 포인팅한다. (B[1][0] 주소(112) 포인팅)

예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//프로그램 10-15
#include <stdio.h>
#include <string.h>
#define SIZE 5
 
int main()
{
    char silver[SIZE][10= { "나태희""유빈""나원빈""문건영""소지법" };
    char temp[10];
    int pass, i;
 
    printf("** 은메달 리스트 : ");
    for (i = 0; i < SIZE; i++)
    {
        printf("%s, ", silver[i]);
    }
    printf("\b\b**\n\n");
 
    //버블정렬 사용하여 정렬
    for (pass = 1; pass < SIZE; pass++)
    {
        for (i = 0; i < SIZE-pass; i++)
        {
            if (strcmp(silver[i], silver[i + 1]) > 0)
            {
                strcpy(temp, silver[i]);
                strcpy(silver[i],silver[i+1]);
                strcpy(silver[i + 1], temp);
            }
        }
    }
 
    printf("** 정렬한 리스트 : ");
    for (i = 0; i < SIZE; i++)
    {
        printf("%s, ", silver[i]);
    }
    printf("\b\b**\n\n");
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

2차원 배열에 문자열을 저장하여
2차원 배열의 SIZE원소는 문자열 1개를 포인팅 한다.


그림 그리기 귀찮다.

 

* 저작권에 문제가 된다면 수정하겠습니다.


연습 문제

 

17, 20번을 풀어보겠다.

 

문제17: sales 배열에 대리점 10곳의 월 판매량이 저장되어 있다. 이 배열에서 사용자가 입력한 n에 대해 상위 n개의 판매량을 출력하는 프로그램을 작성하시오.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#define N 10
 
void top_n(int *sale)
{
    int i,j;
    int temp;
 
    //내림차순 정렬
    for (i = 1; i < N; i++)
    {
        for (j = 0; j < N - i; j++)
        {
            if (sale[j] < sale[j + 1])
            {
                temp = sale[j];
                sale[j] = sale[j + 1];
                sale[j + 1= temp;
            }
        }
    }
}
 
int main()
{
    int sales[N] = { 203105302200289175130120267312 };
    int n; //상위 n개의 판매량 출력
    int i;
 
    printf("상위 몇개의 판매량 출력: ");
    scanf("%d"&n);
 
    top_n(sales);
 
    printf("%d개의 판매량 출력:", n);
    for (i = 0; i < n; i++)
    {
        printf("%4d", sales[i]);
    }
    printf("\n");
 
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

문제의 조건이 까다롭고, 뭔 말인지 모르겠다.
그냥 함수에서 내림차순 sorting 해서
입력받은 값까지 출력하면 되는 것 아닌가?

 

어려운 문제인지 알고 풀었는데 아니다..

 

문제20: 1~45 범위의 난수 6개로 구성된 로또 번호를 생성하는 프로그램을 작성하시오. 난수 6개는 no배열에 저장하되 6개의 난수가 서로 달라야 하며 화면에 출력될 때는 오름차순으로 정렬하여 표시한다. 사용자에게 생성한 로또 번호를 보여준 후에는 새로운 로또 번호의 생성 여부를 물어 사용자가 [ESC] 키를 누르면 더 이상 생성하지 않고 실행을 끝낸다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 6
 
void random_no(int *arr)
{
    int i,j;
    int flag=0// 중복된 번호인지 확인
 
    srand(time(NULL)); //시드값 설정
 
    for (i = 0; i < N; i++)
    {    
        do
        {
            flag = 0//flag 0으로 초기화
            arr[i] = (rand() % 45+ 1//번호 할당
 
            for (j = 0; j < i; j++)
            {
                if (arr[i] == arr[j])  //이전 번호와 중복이면
                {
                    flag = 1//flag 1로 설정
                    break;  //조건문 탈출, 위의 for문 탈출
                }
            }
        } while (flag == 1); //flag가 1인동안 계속 반복
    }    
}
 
void sort(int *arr)
{
    int i, j;
    int temp;
 
    for (i = 0; i < N - 1; i++)
    {
        for (j = 0; j < N - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j + 1= temp;
            }
        }
    }
}
 
void print_lotto(int *arr)
{
    int i;
 
    printf("생성된 로또 번호:");
    for (i = 0; i < N; i++)
    {
        printf("%3d", arr[i]);
    }
    printf("\n");
}
 
int main()
{
    int no[N]; //로또 번호 배열
 
    do{
        random_no(no);
        print_lotto(no);
        sort(no);
        print_lotto(no);
        printf("그만하려면 [ESC]키를, 새 로또 번호를 보려면 다른 키를 누르세요.\n");
 
    } while (getch() != 0X1B);
 
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

왜 처음에 이상한 값 뜨는 거지?

random_no 함수가 조금 생각해야 하는 부분

flag변수를 두어
배열에 같은 값이 있으면
flag를 1로 설정하여 while문을 계속 반복하면서

새로운 값을 부여하고
다시 확인하기 반복

로또 당첨되고 싶다. 일단 사지도 않는데 무슨 ㅋㅋ

 

내용 정리

 

함수는 크게 라이브러리 함수와

 

사용자 지정 함수로 나뉜다

 

라이브러리 함수는 6장의 링크를 참조하여 사용할 때 찾아보면 익숙해질 것이다.

 

 

이번 chapter에서는 사용자 지정 함수에 대해 공부해보자

 

 

 

간단한 예제를 통해서 사용자 지정 함수가 어떠한 것인지

 

함수는 어떻게 생겼는지 보자

 

 

 

사용자 지정 함수

 

예제로 간단한 1차원 배열 문제를 확인하고 넘어가려 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//함수
 
//두 수중 큰 값 구하기
#include <stdio.h>
 
int find_larger(int first, int second)
{
    int larger;
    
    if (first > second)
        larger = first;
    else
        larger = second;
 
    return larger;
}
 
int main()
{
    int n1, n2, max;
 
    printf("첫 번째:");
    scanf("%d"&n1);
 
    printf("두 번째:");
    scanf("%d"&n2);
 
    max = find_larger(n1, n2);
    printf("%d, %d중큰 수는:%d\n",n1,n2,max);
 
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

 

main함수를 실행하다 도중에

find_larger(n1,n2)를 만나

n1, n2를 인자로 사용하여 find_larger을 호출한다.

 

find_larger함수가 실행되며, n1값은 first로 n2값은 second로 전달되어 저장된다.

 

이때n1 == first는 아니다!

전달하는 시점에 값을 전달해주는 것뿐이다.

 

find_larger은 int형 변수인 larger을 반환해주면서 함수가 끝나고

다시 함수를 호출했던 부분인 main함수로 돌아와서 계속 main함수를 진행한다.

 

 

 

※ 함수의 인수 전달 (값에 의한 전달, 주소에 의한 전달)

 

 

값에 의한 호출로는
함수를 호출할 때 전달하는 인자와
호출한 함수의 변수(전달받은 인자가 저장된 값)는 다른 변수이기 때문에

호출한 함수 내부에서 값을 변경하더라도
main함수에서는 그대로임...

예를 들어

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
 
void sort(int first, int second)
{
    int temp;
 
    if (first > second)
    {
        temp = first;
        first = second;
        second = temp;
    }
    printf("\nsort함수의 정렬 결과 first: %d, second: %d\n\n", first, second);
    printf("first의 기억장소 주소: %u\n"&first);
    printf("second의 기억장소 주소: %u\n\n"&second);
}
 
int main()
{
    int n1 = 5000, n2 = 4000;
    
    printf("함수 호출 전 n1: %d, n2: %d\n", n1, n2);
    sort(n1, n2);
    printf("함수 호출 후 n1: %d, n2: %d\n\n", n1, n2);
 
    printf("n1의 기억장소 주소: %u\n"&n1);
    printf("n2의 기억장소 주소: %u\n"&n2);
    
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 
값에 의한 호출 방식으로 함수의 인자를 전달하면
main함수의 변수 값을 다른 함수에서 변경 할 수 없다.

결과를 확인하면 sort함수에서는 숫자가 오름차순으로 정렬이 되었으나
sort함수를 호출 하고 나서 main함수의 숫자는 정렬이 안된것을 확인 할 수 있다.

이유는 변수가 전혀 다른 변수이기 때문이다.
전혀 다른 변수라는것은 변수가 저장 되어있는 기억장소의 주소를 확인하면 알 수 있다.
n1와 first의 변수의 주소가 다르다.

 

따라서 다른 함수에서 호출 한 함수의 변수를 변경하고 싶으면
변수의 주소를 함수에 전달하여야 한다!

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
 
void sort(int *first, int *second)
{
    int temp;
 
    if (*first > *second)
    {
        temp = *first;
        *first = *second;
        *second = temp;
    }
    printf("\nsort함수의 정렬 결과 first: %d, second: %d\n\n"*first, *second);
    printf("first의 기억장소 주소: %u\n", first);  //&(*first)도 같은 표현
    printf("second의 기억장소 주소: %u\n\n", second);
}
 
int main()
{
    int n1 = 5000, n2 = 4000;
 
    printf("함수 호출 전 n1: %d, n2: %d\n", n1, n2);
    sort(&n1, &n2);
    printf("함수 호출 후 n1: %d, n2: %d\n\n", n1, n2);
 
    printf("n1의 기억장소 주소: %u\n"&n1);
    printf("n2의 기억장소 주소: %u\n"&n2);
 
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

main에서 sort(&n1,&n2)로 변수의 주소 전달
sort함수는 main주소를 전달받았으므로
간접 참조 연산자*를 이용해 주소가 아닌 값을 받아올 수 있다.


결과를 보면 main함수의 변수들이 정렬된 것을 볼 수 있다.
주소를 확인하면 main함수의 변수와 sort함수의 변수가 같은 것을 확인할 수 있다.
주소를 전달받았기 때문이다.

 

추후 포인터를 공부한다면 좀 더 쉽게 이해할 수 있을 것이다.

 

 

 

※ 배열을 인자로 함수에 전달하기

 

 

배열명도 포인터이므로 포인터처럼 사용하여 인자로 전달하면 된다.
결론이지만
이해해보자

배열의 원소 하나를 함수의 인자로 전달하는 방식으로 모든 원소를 함수에 넣는다.
이 방법 번거롭고 많은 함수 호출이 일어난다.

일단 예제

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//함수
//배열의 원소 하나하나를 인자로 전달
 
#include <stdio.h>
#define N 10
 
int find_larger(int first, int second);
 
int main()
{
    int freeze[N] = { 150-20-3050-5-120-510-12 };
    int i, max;
 
    max = freeze[0];
 
    for (i = 1; i < N; i++)
    {
        max = find_larger(max, freeze[i]);
    }
 
    printf("어는 점 목록:");
    for (i = 0; i < N; i++)
    {
        printf("%d ", freeze[i]);
    }
    printf("\n가장 높은 어는 점:%d\n", max);
 
    return 0;
}
 
int find_larger(int first, int second)
{
    if (first > second)
        return first;
    else
        return second;    
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

max = find_larger(max, freeze[i]);
함수 호출할 때 freeze배열의 원소 하나하나 find_larger함수에 넣어서 최댓값을 찾는다.

처음에 max = find_larger[0] 로 지정해 놓았기 때문에
함수에는 1번 원소부터 넣어서

find_larger(max,freeze[1]) => 0~1중 큰 값이 리턴
find_larger(max,freeze[2]) => 0~2중 큰 값이 리턴
find_larger(max,freeze[3]) => 0~3중 큰 값이 리턴
find_larger(max,freeze[4]) => 0~4중 큰 값이 리턴
...
find_larger(max,freeze[9]) => 0~9중 큰 값이 리턴

총 9번 함수를 호출하여 큰 값을 구한다.

 

 

원소 하나하나 인자로 전달하는 방법은 상당히 비효율적이다.
=> 배열 전체를 인자로 전달한다.

포인터를 배우지 않았지만, 배열명 = 포인터라고 알아두자

배열명인 freeze를 인자로 전달하면 배열 전체를 전달할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//함수
//배열의 전체를 인자로 전달
#include <stdio.h>
#define N 10
 
int find_larger(int arr[]);
 
int main()
{
    int freeze[N] = { 150-20-3050-5-120-510-12 };
    int max,i;
 
    max = find_larger(freeze);
 
    printf("어는 점 목록:");
    for (i = 0; i < N; i++)
    {
        printf("%d ", freeze[i]);
    }
 
    printf("\n가장 높은 어는 점:%d\n", max);
 
    return 0;
}
 
int find_larger(int arr[])
{
    int larger;
    int i;
 
    larger = arr[0];
    for (i = 1; i < N-1; i++)
    {
        if (larger < arr[i])
            larger = arr[i];
    }
    return larger;    
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

max = find_larger(freeze);
freeze배열 전체를 인자로 전달하였다.

 

 

배열 이름인 freeze는 변수를 저장하고 있는 변수가 아니라
배열의 시작 부분을 가리키고 있는 주소와 같다.
위 그림을 보면 이해가 가능하다.

freeze를 인자로 전달하여 freeze [0~9]까지 접근이 가능하게 된다.

 

 

-정리-
원소 하나하나를 전달하는 방법에 비해 훨씬 효율적인 방법
배열명을 인자로 전달하면 된다.
배열명은 배열의 첫 시작 원소의 주소이다.

 

 

※ 난수 구하기

 

난수구하기 은근히 자주 사용하는데 이참에 정리해보자
JAVA,C++,C#,C,PYTHON 배울 때 모두 사용한 거 같다
좀 자세히 정리해봐야지

C언어의 난수 생성함수
rand() : 0~32767 범위 안의 임의의 값 반환
헤더파일 stdlib.h(standard library 맞나?) 필요

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int i, random;
    for (i = 1; i <= 5; i++)
    {
        random = rand();
        printf("%d번째 난수:%5d\n", i, random);
    }
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

계속 같은 결과 나온다. 41,18467,6334,26500,19169 계속 이 순서...
난수 맞니??

 

 

씨드 설정 함수인 srand를 설정하여 난수가 순서를 변경한다.
=> 씨드가 같으면 생성되는 난수의 순서가 동일하다.

default값은 1로
위에서 씨드를 설정하지는 않았지만 srand(1)로 설정이 되어있는 상태인 것이다.

하지만 씨드값을 프로그램을 실행할 때 매번 다르게 할 수 없다.
time함수를 이용하여 현재 시간으로 씨드값을
설정하면 매번 다르게 자동적으로 씨드값을 설정할 수 있다.

srand(time(NULL))  time 함수 사용하기 위해 time.h 헤더파일 필요

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main()
{
    int i, random;
 
    srand(time(NULL));
 
    for (i = 1; i <= 5; i++)
    {
        random = rand();
        printf("%d번째 난수:%5d\n", i, random);
    }
    return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

 

매번 다르다!! 이 정도는 돼야 난수라고 인정하지

 


스케일링
난수를 원하는 범위로 설정하는 방법
간단하다

rand() : 0~32767 이라고 했다.

rand()%6 을 하면 0부터 5까지 나오겠지
그럼 (rand()%6)+1 을 하면 1부터 6까지의 범위가 나온다. (주사위)

5~10은
(rand%6)+5

일반화
min~max는
(rand%(max-min+1)) + min

간단한 게임 만들기
컴퓨터가 숨긴 수 맞추기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//컴퓨터가 숨긴 1~100 중의 정수를 맞히는 게임
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main()
{
    int begin = 1end = 100;
    int count = 0;
    int computer, user;
 
    srand(time(NULL)); //씨드 설정
 
    computer = (rand() % (end - begin + 1)) + begin//컴퓨터가 숨기는 수 생성
    
    do
    {
        printf("%d~%d중의 값입니다. 얼마일까요?"beginend);
        scanf("%d"&user);
 
        count++;
 
        if (user < computer)
            begin = user + 1;
        else
            end = user - 1;
    } while (user != computer);
 
    printf("\n%d를 %d번만에 맞췄습니다.\n", computer, count);
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

 

computer = (rand() % (end - begin + 1)) + begin;
이것만 이해하면 끝

 

 

 

 

 

 


연습 문제

 

8, 9번을 풀어보겠다.

 

문제8: 한 달 동안의 음성 통화 시간이 voice분이고 문자 전송 건수가 text건일 때 휴대폰 사용료 계산 방법을

사용해 휴대폰 사용료 charge를 구하는 프로그램을 작성하시오

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
 
int voice(int time)
{
    int charge; //음성통화 요금
    
    if (time > 100//100분 초과
        charge = (100 * 100+ ((time - 100* 80);
    else
        charge = 100 * time;
 
    return charge;
}
 
int message(int num)
{
    int charge; //문자 요금
 
    if (num > 20//문자 20건 초과
        charge = (num - 20* 20;
    else
        charge = 0;
 
    return charge;
}
 
int main()
{
    int voice_time; //음성 통화 시간
    int message_num; //문자 전송 건수
    int voice_charge; //음성 통화 요금
    int message_charge; //문자 전송 요금
    int sum_charge; //총 요금
    int surtex; //부가세
 
    printf("음성 통화 시간은(분)? ");
    scanf("%d"&voice_time);
    printf("문자 전송 건수는? ");
    scanf("%d"&message_num);
 
    //함수 호출
    voice_charge = voice(voice_time);
    message_charge = message(message_num);
 
    sum_charge = voice_charge + message_charge + 10000;
    surtex = sum_charge*0.1;
 
    //출력
    printf("휴대폰 사용 요금 청구서\n");
    printf("=========================================\n");
    printf("음성 통화 시간 %d\n",voice_time);
    printf("문자 전송 건수 %d건\n", message_num);
    printf("-----------------------------------------\n");
    printf("기본요금\t\t\t%6d원\n"10000);
    printf("음성 통화료 %d분\t\t%6d원\n",voice_time,voice_charge);
    printf("문자 전송료 초과 %d건(20건 무료)%6d원\n", message_num-20, message_charge);
    printf("-----------------------------------------\n");
    printf("합계\t\t\t\t%6d원\n", sum_charge);
    printf("부가세(10%%)\t\t\t%6d원\n", surtex);
    printf("=========================================\n");
    printf("이번 달 요금\t\t\t%6d원\n", sum_charge+ surtex);
    
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

그냥 텍스트 줄 맞추는데 힘들었다.

 

 

 

문제9: 학생 N명의 퀴즈 점수 10,8,7,9,6,10,9,8,7이 저장된 배열 class에 대해 사용자의 점수가 입력되면 순위를 출력하는 프로그램을 작성하시오. 입력된 점수 my_score의 순위를 구하는 함수 rank를 정의해 사용하시오.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#define N 9
 
int rank(myscore, otherscore)
{
    if (myscore < otherscore) //더 높은 점수있으면 등수 1개 낮추기
        return 1;
    else                       //내가 더 높으면 등수 그대로
        return 0;
}
 
int main()
{
    int score;
    int my_rank = 1;
    int i;
    int class[N] = { 10879610987 };
 
    printf("사용자의 점수를 입력하세요: ");
    scanf("%d"&score);
 
    for (i = 0; i < N; i++//배열 원소 하나씩 함수에 전달
    {
        my_rank += rank(score, class[i]); //리턴 받은 수를 누적시키기
    }
    for (i = 0; i < N; i++)
    {
        printf("%3d"class[i]);
    }
    printf("과 비교\n");
    printf("내 등수 = %d등\n", my_rank);
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

포인터로 배열 전체를 함수에 인자로 전달하면 더 쉬울 것을 하나하나 전달하니까
변수 생각하는데 더 머리가 아프다.
아직 포인터 진도 안나갔으니 하나하나 전달하여 해결

 

이번 chapter는 정리해야 할 내용이 많아서 분리해서 정리하려 한다.

 

추가적으로 연습문제도 풀어보겠다.

 

 

내용 정리

이번에는 WHERE절이 무엇인지

 

WHERE 절에서 사용하는 AND와 OR 연산자

 

그리고 논리 연산자 외의 연산자들에 대해서 공부한다.

 

 

 

WHERE절

 

SELECT문으로 데이터를 조회할 때

특정 조건을 기준으로 원하는 행을 출력하는 데 사용

 

 

예를 들자면

 

앞 chapter에서 배운 것과 같이 테이블을 전체적으로 조회하는 것이 아니라

 

특정 조건에 맞는 테이블 내의 행만 조회하는 것이다.

 

부서 번호가 30인 사원, 보너스가 300만 원 이상인 사원 등

 

다양한 조건을 사용해서 원하는 데이터를 조회할 수 있게 하는 방법을 알아본다.

 

 

기본 문법

SELECT [조회할 열1 이름], ..., [조회할 열N 이름]

FROM [조회할 테이블 이름]; 

WHERE [조회할 행을 선별하기 위한 조건식];

 

아래 표는 저번 chapter과 같이 계속 사용할 테이블의 전체 데이터이다.

 

 

이 테이블을 각종 조건을 이용하여

 

입맛에 맞게 조회해보자

 

 

EMP 테이블의 부서 번호가 30인 사원의 이름과 급여 부서 번호 조회

 

1
2
3
SELECT ENAME, SAL, DEPTNO 
FROM EMP
WHERE DEPTNO = 30;

 

 

WHERE절에는 이용할 수 있는 다양한 연산자가 있는데

 

다양한 연산자들의 사용 방법에 대해 알아보겠다.

 

 

 

AND, OR 연산자

 

WHERE절에서 여러 개의 조건식을 지정하게 해주는 논리 연산자

 

비 전공자는 모르겠지만 나는 전공 자기에 논리 연산자의

 

자세한 설명은 PASS

 

 

EMP 테이블의 부서 번호가 30이고 직업이 'SALESMAN'인 사원의 정보

 

1
2
3
4
SELECT *
FROM EMP
WHERE DEPTNO = 30
    AND JOB = 'SALESMAN';

 

 

AND 연산자를 통해 여러 개의 조건식을 사용할 수 있다.

 

 

여기서 주의할 점은 

 

SQL은 대 소문자를 구별하지 않지만

 

JOB이라는 문자열 데이터는 테이블에 대문자로 들어있기 때문에

 

대문자로 검색해야 일치하는 행을 조회할 수 있다.

 

 

OR 연산자도 마찬가지로 여러 개의 조건식을 사용할 수 있도록 해준다.

 

 

 

산술 연산자, 비교 연산자

 

간단하게 말하면 사칙연산이다.

 

산술 연산자

 

간단하게 말하면 사칙연산이다.

+, -, *, /

 

비교 연산자

 

대소 비교 연산자(부등호)와 등가 비교 연산자가 있다.

 

 

대소 비교 연산자

연산자 사용법 설명
> A > B A 값이 B 값을 초과할 경우 참
>= A >= B A 값이 B 값 이상일 경우 참
< A < B A 값이 B 값 미만일 경우 참
<= A <= B A 값이 B 값 이하일 경우 참

 

 

등가 비교 연산자

연산자 사용법 설명
= A = B A 값이 B 값과 같은 경우 참
!= A != B A 값이 B 값과 다를 경우 참
<> A <> B
^= A ^= B

 

 

EMP 테이블의 1년 급여(보너스 제외)가 36000 이상인 사원의 정보

 

1
2
3
SELECT *
FROM EMP
WHERE SAL * 12 >=  36000;

 

 

SAL * 12 와 같이 산술 연산자을 이용 할 수 있다.

 

또한 36000 이상인 사원을 조회하는 과정에서

 

비교 연산자인 부등호를 이용하였다.

 

 

 

논리 부정 연산자

 

부정문에 쓰이는 영어는 NOT

 

논리 부정 연산자는 NOT이다.

 

 

NOT은 '아닌 것'이라고 생각하면 된다.

 

 

EMP 테이블의 1년 급여(보너스 제외)가 36000이 아닌 사원의 정보

 

1
2
3
SELECT *
FROM EMP
WHERE NOT SAL * 12 =  36000;

 

WHERE SAL*12 != 36000과 같기 때문에

 

NOT은 혼자 잘 사용되지 않는다.

 

 

IN, BETWEEN, IS NULL과 같은 연산자에 복합적으로 사용된다.

 

또한 AND, OR과 같이 여러 조건식이 묶여있는 상황에서 

 

정반대의 결과를 얻을 때 사용한다.

 

 

 

IN 연산자

 

IN 연산자는 특정 열에 해당하는 조건을 여러 개 지정하게 한다.

 

AND나 OR 연산자를 여러 번 사용하지 않게 하는 장점이 있다.

 

 

EMP 테이블의 JOB가 MANAGER OR SALESMAN OR CLERK인 사원의 정보

 

1
2
3
4
5
SELECT *
FROM EMP
WHERE JOB = 'MANAGER'
    OR JOB = 'SALESMAN'
    OR JOB = 'CLERK';
 
1
2
3
SELECT *
FROM EMP
WHERE JOB IN ('MANAGER','SALESMAN','CLERK');

 

두 코드는 같은 결과를 보여준다.

 

추후 중첩 쿼리문을 사용할 때도 유용하게 사용하는 연산자이다.

 

 

 

BETWEEN A AND B 연산자

연산자 이름처럼 A와 B 사이에 존재하는 값을 조회하기 위해 사용하는 연산자이다.

 

EMP 테이블의 SAL이 2000 이상 3000 미만인 사원의 정보

 

1
2
3
4
SELECT *
FROM EMP
WHERE SAL >= 2000
    AND SAL <= 3000;

 

1
2
3
SELECT *
FROM EMP
WHERE SAL BETWEEN 2000 AND 3000;

 

 

 역시 같은 결과를 보여준다.

 

훨씬 간단하게 사이의 값에 존재하는 데이터를 확인할 수 있다.

 

 

 

LIKE 연산자와 와일드카드

 

 LIKE 연산자는 일부 문자열이 포함된 데이터를 조회할 때 사용한다.

 

이때 와일드카드를 사용한다.

 

 

와일드카드는 _와 %가 있다.

 

_는 한 개의 문자 데이터를 의미하며

 

% 는 길이와 상관없는 모든 문자 데이터를 의미한다.

 

 

EMP 테이블의 사원 이름의 두 번째 글자가 L인 사원의 정보

 

 

1
2
3
SELECT *
FROM EMP
WHERE ENAME LIKE '_L%';

 

 

LIKE '_L%'의 의미는

 

두 번째 문자가 반드시 L이고

 

L 앞에는 반드시 한 문자가 와야 한다

 

L 뒤에는 몇 개의 문자가 와도 상관없다.

 

 

 

EMP 테이블의 사원 이름에 AM이 포함되어 있지 않은 사원의 정보

 

 

1
2
3
SELECT *
FROM EMP
WHERE ENAME NOT LIKE '%AM%';

 

이름에 AM이 포함돼있는 사원이 아닌 사원의 정보를 보여준다.

 

이때 AM은 붙어있어야 한다.

 

 

IS NULL 연산자와 집합 연산자는 다음 글에서 정리해보겠다.

내용 정리

 

SELECT문은 데이터베이스에 보관되어 있는 데이터를 사용자의 입맛에 맞게 꺼내오기 위해서 사용한다.

 

각종 조건문(WHERE, ORDER BY, FROM 등)을 사용하여

 

조회하고 싶은 데이터를 보기 쉽게 조회가 가능하다.

 

 

 

먼저 실습용 테이블을 살펴보면

 

DESC 명령을 사용하여 테이블의 구성 정보를 확인할 수 있다.

 

1
DESC EMP;           -- EMP는 사원 테이블

 

 

테이블을 구성하는 컬럼(열) 명과 컬럼에 대한 정보들이 표출된다.

 

EMPNO(사원 번호)가 기본키(PK)이며 4자리 숫자로 구성된다.

 

여기서 VARCHAR2형식의 데이터는 가변형 문자열이며

 

문자열의 데이터가 10byte보다 작다면 데이터의 공간도 작다는 것을 말한다.

 

*저장 공간을 무조건 10byte할당하는 자료형은 고정형 저장 방식이다.

 

 

 

SQL의 기본 뼈대, SELECT절과 FROM절

 

SELECT문은 데이터베이스에 보관되어 있는 데이터를 조회하는 데 사용한다.

 

FROM 절은 조회할 데이터가 저장된 테이블을 명시

 

 

기본 문법

SELECT [조회할 열1 이름], ..., [조회할 열N 이름]

FROM [조회할 테이블 이름]; 

 

전체 열을 조회하려면 *를 사용하면 된다.

 

또한 마지막에 ;(세미콜론)을 써주어 SQL문이 끝났다는 걸 명시해주어야 한다.

 

1
SELECT * FROM EMP;

 

이 명령어의 뜻은

 

EMP 테이블의 모든 데이터의 모든 열 정보를 출력한다는 뜻이다.

총 14개의 데이터가 EMP 테이블에 저장되어 있다.

 

 

그렇다면 테이블의 모든 열이 아닌

 

EMPNO, ENAME, DEPTNO의 정보만 출력하고 싶다면?

 

 

1
SELECT EMPNO, ENAME, DEPTNO FROM EMP;

 

위의 SQL문과 같이 SELECT절에 데이터를 가져오고 싶은 열의 이름을 입력하면 된다.

 

 

1
SELECT DEPTNO FROM EMP;

 

어떠한 부서 번호들이 EMP 테이블에 저장되어 있는지 확인하기 위해서

 

위와같은 SQL문을 사용하면

 

아래와 같은 정보를 얻을 수 있다.

 

10, 20, 30 총 3개의 부서 번호가 있는데 

 

불필요하게 중복된 부서 번호가 여러 번 출력된다.

 

이때 DISTINCT 명령어를 통해 중복을 제거할 수 있다.

 

 

1
SELECT DISTINCT DEPTNO FROM EMP;

 

 

 

검색할 칼럼이 2개 일 때 DISTINCT 명령어를 사용하면

 

2개의 칼럼 모두 중복이 되어야 제거가 된다.

 

아래의 명령어의 경우 직급이 같아도 부서 번호가 다르면 출력이 된다.

 

1
SELECT DISTINCT JOB, DEPTNO FROM EMP;

 

 

 

DISTINCT와 반대는 ALL로 중복을 제거하지 않고

 

모든 열을 출력 한다.

 

하지만 ALL은 DEFAULT 값으로 생략하면 자동 적용이 된다.

 

1
SELECT ALL JOB, DEPTNO FROM EMP;
1
SELECT JOB, DEPTNO FROM EMP;

 

위와 아래의 결과는 같다는 뜻이다.

 

 

별칭 설정하기

 

SQL문에서는 최종 출력되는 열 이름을 사용자가 지정할 수 있다.

 

1
SELECT ENAME, SAL, SAL*12+COMM, COMM FROM EMP;

 

위와 같은 명령을 수행하면 다음과 같이 테이블이 조회된다.

 

SAL*12+COMM(추가 수당)은 1년의 총급여이다.

 

위와 같이 사용자가 계산식을 만들어 새로운 열로 확인할 수 있다.

(실제 테이블에는 위와 같은 열은 존재하지 않는다.)

 

SMITH, JONES 등 의 경우는 COMM의 값이 NULL 값이기 때문에 

 

1년의 총급여가 출력되지 않았다.

 

 

 

하지만

 

위와 같은 테이블은 계산식이 길어지면 어떠한 의미인지 알 수 없고

 

계산식도 그대로 노출되기 때문에 별칭을 지정한다.

 

별칭은 AS(alias)라는 명령어를 통해 지정한다.

 

1
SELECT ENAME, SAL, SAL*12+COMM AS "연간 총 급여", COMM FROM EMP;

 

 

별칭을 지정하면 사용자가 지정한 이름으로 열 이름이 나타난다.

 

 

데이터 정렬

 

데이터를 조회할 때 오름차순 또는 내림차순으로 데이터를 정렬할 수 있다.

 

ORDER BY 명령을 통해 데이터를 정렬 할 수 있으며

 

오름차순 - ASC(ascending)  기본 설정 (default)

내림차순 - DESC(descending)

 

의 명령어를 사용한다.

 

 

오름차순

ASC는 생략 가능

 

1
SELECT * FROM EMP ORDER BY SAL (ASC);

 

 

내림차순

 

1
SELECT * FROM EMP ORDER BY SAL DESC;

 

 

* 꼭 필요한 경우가 아니면 사용 권장하지 않는다.

 

정렬은 컴퓨터의 자원을 많이 사용하기 때문.

 

3장은 설치 부분이라 내용을 정리할 내용은 없고 설치 과정에서 있었던 문제점과

 

문제점 해결 과정에서 알게 된 부분 몇 가지를 정리하려고 한다.

 

 

 

 

설치 완료 이후 SCOTT 계정을 활성화하는 과정에서 문제가 있었다.

 

CMD창에서 SYSTEM계정이 계속 로그인 되지 않았다.

 

 

 

 

C:\>sqlplus "/as sysdba" 라고 입력을 하면

 

 

DBA(관리자)계정으로 로그인이 된다.

 

이후 SYSTEM계정의 비밀번호를 다시 재설정해주었다.

 

 

SQL문이 관리자 계정으로 활성화된 뒤.

 

C:\>ALTER USER SYSTEM(계정) IDENTIFIED BY ORACLE(암호) 라고 입력하면

 

계정의 비밀번호를 변경할 수 있다.

 

 

이후 SCOTT계정을 활성화하여 오류를 해결하였다.

 

 

 

 

알게 된 내용은 

 

오라클의 계정 체계에 대해 알 수 있었다.

 

SYS는 DBMS의 최고 관리자이다. DB 생성이 가능하다.

 

SYSTEM은 DB생성이 불가능하다는 것을 제외하고 SYS와 같다.

 

이 이외에 사용자는 접근 또는 접근 수정등의 권한을 가진 사용자들이 있다.

 

내용 정리

 

2장에서는 관계형 데이터베이스를 구성하고 있는 요소의 정의와 용어들을 설명하고

 

오라클 데이터 베이스에 대해서 간략하게 설명한다.

 

 

 

관계형 데이터베이스의 구성요소

 

 

테이블: 표 형태의 데이터 저장 공간

 

: 하나의 개체를 구성하는 여러 값을 가로로 늘어뜨린 형태

 

: 저장하려는 데이터를 대표하는 이름과 공통 특성을 정의

 

 

열 중에서 특별한 의미를 지닌 하나의 열 또는 여러 열의 조합을 키(Key)라고 한다.

 

 

 

 

기본키, 보조키, 외래키에 대해 정리해보자

 

 

기본키는 한 테이블 내에서 중복되지 않는 값만 가질 수 있는 키이다.

 

위의 테이블에서 사원 번호는 사원을 유일하게 구별할 수 있기 때문에

 

중복이 되지 않는다. 이를 기본키라고 한다.

 

복합키는 기본키가 한 컬럼이 아니라 여러 컬럼의 조합으로 이루어진 것을 말한다.

 

 

 

기본키의 속성

1. 테이블에 저장된 행을 식별할 수 있는 유일한 값이어야 한다.

2. 중복이 없어야 한다.

3. NULL 값을 가질 수 없다.

 

1번의 속성이 전제가 되기 때문에 자연스럽게 2, 3번은 따라온다.

 

 

보조키후보키에 속해 있는 키이며, 후보키 중에서 기본키로 지정되지 않은 열이다.

 

말이 어려운데, 후보키는 테이블에 저장된 행을 식별할 수 있는 키의 모든 집합을 나타내고

 

쉽게 말하면 보조키는 테이블에 저장된 행을 식별할 수 있는 기본키 이외의 키를 말한다.

간단하게 밴 다이어그램을 그리자면 이렇다.

 

 

 

만약 위와 같은 테이블에서

 

같은 직급 내에서는 동명이인이 없다는 가정이 있다면

 

(사원 이름+사원 직급)이라는 열을 통해서 유일하게 행을 식별할 수 있다.

 

이를 보조키라고 한다.

 

 

 

외래키는 특정 테이블의 어떠한 열의 값이 어떠한 다른 테이블의 기본키로 지정되어있어

 

서로 연결이 되는 키를 말한다.

 

 

위 테이블처럼 데이터의 중복을 방지하기 위해서 테이블을 따로 관리하며

 

테이블을 따로 관리하기 때문에 외래키라는 개념이 생겨난 것 같다.

 

 

 

※책에서는 실무에서 외래키를 잘 사용하지 않는다고 하는데,

이 부분은 공감 간다. 우리 회사도 사용을 지양하고 있다.

 

 

 

오라클 데이터베이스

 

자료형

 

 

오라클은 처음 접해본 입장에서, 어떠한 자료형을 많이 사용하는지 모르겠다.

 

MySql과 비슷하겠지...

 

 

VARCHAR2(길이): 4000byte만큼의 가변 길이 문자열 데이터 저장

NUMBER(전체 자리, 소수점 이하 자리): 38자릿수 +-의 숫자를 저장

DATE: 날짜형식 저장

CHAR(길이): 4000byte만큼의 고정 길이 문자열 데이터 저장

 

등등 있는데, 이 자료형을 가장 많이 사용

 

 

 

객체 

 

 

오라클 데이터베이스 내에서 데이터를 저장하고 관리하기 위한

 

논리 구조를 가진 구성요소

 

테이블, 인덱스, 뷰, 시퀀스 등등...

 

아직은 잘 모르겠다. 나중에 정리.

 

 

 

PL/SQL

 

데이터를 관리하기 위해 복잡한 기능 필요할 때

 

기존 SQL만으로는 한계가 있다.

 

PL/SQL을 사용하면 변수, 조건문, 반복문 등 프로그래밍 언어에서

 

제공하는 요소를 사용하여 데이터를 관리할 수 있다.

 

+ Recent posts