728x90
SMALL

정보처리산업기사(정처산기) 실기 기출문제 모음


1. 운영체제 운용 기법을 <보기>에서 고르시오

① 하나의 주기억장치에 여러 개의 프로그램을 동시에 처리하는 방식
② 자료나 정보 단위들이 발생할 때마다 즉각적으로 처리하는 것이 아니라 일정량 또는 일정 기간 동안 모아 두었다가 한꺼번에 처리하는 방식
③ CPU의 전체 사용 시간을 작은 작업 시간량으로 나누어서 그 시간량 동안만 번갈아가면서 CPU를 할당하여 각 작업을 처리하는 방식
④ 컴퓨터 시스템에 여러 개의 프로세스(CPU)를 사용하여 처리하는 방식
<보기>
ㄱ. 일괄 처리시스템
ㄴ. 다중 프로그래밍 시스템
ㄷ. 시분할 시스템
ㄹ. 다중 처리 시스템
더보기

① ㄴ

② ㄱ

③ ㄷ

④ ㄹ


2. 개체와 관계를 아래 E-R모델에서 찾아 쓰시오

더보기

1. 개체 : 고객, 책, 출판사

2. 관계 : 구매, 공급


3. 내부에서 사설 IP 주소를 사용하고 외부 네트워크로 나가는 주소는 공인 IP 주소를 사용하도록 하는 IP 주소 변환 방식으로 사설 IP 주소를 공인 IP 주소로 바꿔주는데 사용하는 통신망의 주소 변환 기술


4. 다음 코드에서 (가)에 들어갈 것을 쓰시오.

#include<stdio.h>
#define LEN 10
void swap(int arr[], int i, int j) {
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}
int partition(int arr[], int left, int right) {
	int pivot = arr[(left + right) / (가)];
	while(left <= right) {
		while(arr[left] < pivot)
			left++;
		while(arr[right] > pivot) 
			right--;
		if(left <= right) {
			swap(arr, left, right);
			left++;
			right--;
		}
	}
	return left;
}
void sort(int arr[], int left, int right) {
	if(left >= right)
		return;
	int center = partition(arr, left, right);
	sort(arr, left, center-1);
	sort(arr, center, right);
}
void quick_sort(int arr[]) {
	sort(arr, 0, LEN-1);
}
int main(void) {
	int data[LEN] = {5, 8, 3, 12, 9, 25, 15, 21, 1, 19};
	quick_sort(data);
	for(int i=0;i<LEN;i++){
		printf("%d\n", data[i]);
	}
}
더보기

2

 

퀵 정렬 문제


5. 다음 코드에서 (가)에 들어갈 것을 쓰시오.

#include<stdio.h>
int SumNTo1(int n) {
	if(n <= 1)
		return 1;
	else
		return n + (가);
}
int main() {
	int result = SumNTo1(100);
	printf("%d", result);
}
더보기

 SumNTo1(n-1)


6. 다음 설명에 해당하는 것이 뭔지 쓰시오.

1. 트랜잭션이정상적으로 수행하여 변경된 내용을 실제 물맂거 디스크에 저장할 때 사용하는 연산
2. 트랜잭션의 실행이 비정상적임을 알리는 연산자로 트랜잭션이 수행한 결과를 원래의 상태로 원상 복귀 시키는 연산
더보기

1. commit

2. rollback


7. 관계 대수 연산자를 <보기>에서 고르시오.

1. 한 릴레이션에서 조건에 만족하는 튜플들의 부분 집합을 구한다
2. 한 릴레이션의 속성들의 부분집합을 구한다
3. 두 릴레이션 모두에 속한 튜플들로 이루어진 릴레이션을 생성한다
4. 두 릴레이션 중 하나의 릴레이션에는 속하지만 다른 하나의 릴레이션에는 속하지 않는 튜플들로 이루어진 릴레이션을 생성한다.
<보기>
ㄱ. select
ㄴ. project
ㄷ. intersection
ㄹ. difference
더보기

1. ㄱ

2. ㄴ

3. ㄷ

4. ㄹ


8. 다음 설명에 해당하는 용어를 쓰시오

IP 프로토콜 위에서 연결형 서비스를 지원하는 프로토콜로 데이터를 세그먼트라는 블록 단위로 분할해 전송하며 전이중 방식의 양방향 가상 회선을 제공하기 때문에 신뢰성 있는 데이터 전송을 보장하며 흐름 제어 기능과 혼잡 제어 기능을 지원하여 네트워크에서의 데이터 전달 통제가 가능한 프로토콜

9. 다음은 Ipv4 헤더이다. Header Length와 Destination Ip Address 크기를 구하시오

더보기

Header Length : 4

Destination Ip address : 32


10. SRT 방식으로 스케줄링 할 경우, 입력된 작업이 다음과 같을 때 평균 반환시간과 평균 대기시간을 구하시오

더보기

평균 반환 시간 : 6.75

평균 대기 시간 : 3.25

 

 

SRT(Shortest Remaining Time)
SJF방식을 선점 스케줄링 방식으로 변경한 기법
최단 잔여시간을 우선으로 하는 스케줄링이다
진행 중인 프로세스가 있어도, 최단 잔여시간인 프로세스를 위해 sleep 시키고 짧은 프로세스를 먼저 할당한다.


11. 단위 테스트가 끝난 모듈을 통합하는 과정에서 발생할 수 있는 오류를 찾는 테스트가 통합 테스트(integration test)이다. 이때 단위 테스트가 끝난 모듈을 한꺼번에 결합하여 수행하는 방식을 뭐라고 하는지 쓰시오.

(원래 문제는 보기 있는 문제)

더보기

빅뱅(bigbang) 테스트


12. 다음 코드의 출력결과를 쓰시오

public class Main {
	public static void main(String[] args) {   // true, false
		int x = 1;
		System.out.println(!(x>0));
		System.out.println((x!=0) || (x>0));
		System.out.println(x << 2);
		System.out.println(x & 2);
		System.out.println(x %= 3);
	}
}
더보기

false
true
4
0
1


13. IP 주소가 191.168.25.0이고 서브넷 마스크가 255.255.252.0일 때, 네트워크 주소와 브로드캐스트 주소를 쓰시오

더보기

1. 네트워크 주소 : 191.168.24.0

2. 브로드캐스트 주소 : 191.168.27.255


14. 데이터 링크 계층 기능 중 옳은 것을 모두고르시오

<보기>
ㄱ. 주소 지정
ㄴ. 순서 제어
ㄷ. 흐름 제어
ㄹ. 오류 처리
ㅁ. 동기화
더보기

ㄴ,ㄷ, ㄹ, ㅁ


15. 다음은 교착상태 필요충분조건중 하나이다. 설명에 해당하는 것을 쓰시오.

1. 프로세스들이 필요로 하는 자웡네 대해 배타적인 통제권을 요구한다
2. 각 프로세스는 순환적으로 다음 프로세스가 요구하는 자원을 가지고 있다.
더보기

1. 상호 배제

2. 순환 대기


16. 다음 코드에서 (가)에 들어갈 것을 쓰시오.

#include<stdio.h>
int recursive(int n) {
	if(n <= 1) 
		return 1;
	else 
		return n*n+recursive((가));
}
int main() {
	int i;
	scanf("%d", &i);
	printf("%d ", recursive(i));
}

17. 다음은 데이터 베이스 질의 처리기에 대한 설명이다. <보기>에서 고르시오

1. 데이터 조작어(삽입, 삭제, 수정, 검색) 요청을 분석하여 런타임 데이터베이스 처리기가 이해할 수 있도로 해석한다
2. 데이터베이스에 접근하는 과정에서 사용자의 접근 권한이 유효한지 검사하고 데이터베이스 무결성을 유지하기 위한 제약조건 위반 여부를 확인한다. 회복이나 병행 수행과 관련된 작업도 한다.
<보기>
ㄱ. DDL 컴파일러
ㄴ. DML 프리컴파일러
ㄷ. DML 컴파일러
ㄹ. 런타임 데이터베이스 처리기
ㅁ. 트랜잭션 관리자
더보기

1. ㄷ

2. ㅁ


18. 문장 커버리지 수행 순서를 쓰시오

#include<stdio.h>
int main() {
	int i = 1;
	while(i<=1) {
		if(i%2==1)
			printf("%d", i);
		i++;
	}
	return 0;
}

사진 출처 : 기사퍼스트 유튜브 채널

<테스트 케이스>
1->(     ) - > (      ) ->(     ) - > (      ) ->(     ) - > (      )
더보기

1234526


19. 다음은 Company 테이블을 생성하는 SQL문이다. company 테이블의 TITLE 속성에는 Intern, Staff, Manager, Director, President 값만 허용할 수 있도록 <SQL문> (가) 와 (나)에 적합한 단어를 쓰시오

CREATE TABLE COMPANY(
           ID INT NOT NULL,
           NAME VARCHAR(20) NOT NULL,
           AGE INT NOT NULL,
           TITLE VARCHAR(20) (가) (TITLE(나) ('Intern','Staff','Manager','Director','President'))
       );
더보기

(가) CHECK

(나) IN


20. 다음 (가)에 무엇이 들어갈지 쓰시오

wallet=['credit','id']
card=True
if('cash'in wallet):
	print('택시')
(가)(card):	
	print('버스')
else:
	print('도보')
지갑안에 신용카드(credit)와 신분증(id)이 있고 카드(card)를 가지고 있다.
대중교통을 이용하는데
지갑 안에 현급(cash)이 있다면 택시를 이용하고
현금이 없는 경우 카드가 있는지 확인하고
카드가 있다면 버스를 이용한다
만약 현금과 카드 둘다 없다면 도보를 이용한다
더보기

elif

 


정보처리산업기사(정처산기) 실기 기출문제 모음

728x90
LIST
728x90
SMALL

★연습문제1

1. 상품관리(EC_Product) 테이블에서 단가(Unit_price)가 평균 단가보다 높은 상품에 대하여 [상품코드, 상품명, 단가]를 출력하시오.

더보기
SELECT PRODUCT_CODE, PRODUCT_NAME, UNIT_PRICE
FROM   EC_Product
WHERE  UNIT_PRICE > (SELECT AVG(UNIT_PRICE)
                     FROM   EC_Product);

2. 주문처리(EC_Order) 테이블로부터 입금액이 제일 많은 [주문자ID, 상품코드, 주문수량, 결제방법, 입금액]을 출력하시오.

더보기
SELECT ORDER_ID, PRODUCT_CODE, ORDER_QTY,CSEL,CMONEY
  FROM EC_Order
 WHERE CMONEY = (SELECT MAX(CMONEY)
                   FROM EC_Order);

3. 주문처리(EC_Order) 테이블에서 결제종류별 입금한 금액이 제일 많은 행에 대하여 [결제종류, 주문자ID, 입금액, 입금일자]를 입금액순으로 출력하시오.

더보기
SELECT CSEL "결제종류",ORDER_ID, CMONEY "입급액", CDATE "입급일자"
FROM   EC_Order
WHERE  CMONEY IN (SELECT MAX(CMONEY)
                   FROM EC_Order
                   GROUP BY CSEL);

4. 주문처리(EC_Order)테이블에서 한 번 이상 거래한 회원의 [회원 ID, 회원명, 주민등록번호, 전화번호]를 회원명순으로 출력하시오. 

더보기
SELECT USERID, NAME,REGIST_NO,TELEPHONE
FROM   EC_Member M
WHERE  EXISTS (SELECT *
                   FROM   EC_Order O
                   WHERE  M.USERID= O.ORDER_ID)
ORDER BY 2;

5. 회원관리(EC_Member) 테이블과 주문처리(EC_Order) 테이블을 이용하여 한 번도 거래하지 않은 회원의 [회원ID, 회원명, 주민등록번호, 전화번호]를 회원명순으로 출력하시오.

더보기
SELECT USERID, NAME,REGIST_NO,TELEPHONE
FROM   EC_Member M
WHERE  NOT EXISTS (SELECT *
                   FROM   EC_Order O
                   WHERE  M.USERID= O.ORDER_ID)
ORDER BY 2;

6. 장바구니(EC_Basket) 테이블의 [주민번호, 주문자 ID, 상품코드, 주문수량, 주문일자]를 출력하여 확인한 모든 행을 주문처리(EC_Order) 테이블에 저장하고, 장바구니(EC_Basket) 테이블의 모든 행을 영구히 삭제하시오.

더보기
SELECT ORDER_NO, ORDER_ID, PRODUCT_CODE, ORDER_QTY, ORDER_DATE
FROM  EC_BASKET;

INSERT INTO EC_Order(ORDER_NO,ORDER_ID,PRODUCT_CODE,ORDER_QTY,CDATE)
SELECT*
FROM  EC_BASKET;

SELECT ORDER_NO, ORDER_ID, PRODUCT_CODE,ORDER_QTY, CMONEY
FROM   EC_Order
WHERE  CMONEY IS NULL;

DELETE FROM EC_Basket;

commit;

7. 주문처리(EC_Order) 테이블의 결제금액(cmoney)이 널인 행에 대하여 주문 수량(Order_Qty)과 상품관리(EC_Product) 테이블의 단가(Unit_Price)를 이용하여 주문처리(EC_Order) 테이블의 주문금액(주문수량 x 단가)을 계산하여 저장하고, 구분(Gubun) 칼럼에 '미결'로 수정하시오. (서브 쿼리를 사용한 UPDATE문)

더보기
UPDATE EC_Order O
SET    CMONEY =ORDER_QTY*(SELECT UNIT_PRICE
                          FROM   EC_Product P
                          WHERE  O.PRODUCT_CODE=P.PRODUCT_CODE)
       ,Gubun='미결'
WHERE  CMONEY IS NULL;

select ORDER_NO, PRODUCT_CODE, ORDER_QTY, CMONEY, GUBUN
from   EC_Order
WHERE  Gubun='미결';

commit;

8. 상품관리 테이블(EC_Product)에서 단가가 높은 상위 상품 TOP-5에 대하여 [순위, 상품코드, 상품명, 단가, 생산처]를 출력하시오. (인라인 뷰)

더보기
SELECT ROWNUM "순위", a.*
FROM (SELECT Product_CODE, Product_Name, UNIT_PRICE,COMPANY
      FROM   EC_Product
      ORDER  BY 3 DESC)a
WHERE ROWNUM<=5;

9. 상품관리 테이블(EC_Product)에서 단가가 높은 상위 상품 11위부터 15위까지 [num, 상품코드, 상품명, 단가, 생산처]를 출력하시오. (인라인 뷰)

더보기
SELECT*
FROM (SELECT ROWNUM num, a.*
      FROM (SELECT Product_CODE, Product_Name, UNIT_PRICE,COMPANY
            FROM   EC_Product
            ORDER  BY 3 DESC)a
            )
WHERE num BETWEEN 11 AND 15;

10. 주문처리(EC_Order) 테이블과 동일한 Empty_Order 빈 테이블을 생성하고, Empty_Order 테이블의 구조를 확인하시오.

더보기
CREATE TABLE Empty_Order
AS
       SELECT *
       FROM   EC_Order
       WHERE  1=2;
       
DESC Empty_Order

★연습문제2

1. 주문처리(EC_Order) 테이블로부터 'jupark'가 주문한 상품과 동일한 상품의 구매지수를 출력하시오. (스칼라 서브 쿼리 이용)

더보기
SELECT Order_ID, Product_Code, (SELECT COUNT(*)
                                FROM EC_Order A
                                WHERE A.Product_code=B.Product_code
                                ) "동일상품_구매자수"
FROM   EC_Order B                                
WHERE  Order_ID = 'jupark';

2. 주문처리(EC_Order) 테이블에서 구분(Gubun) 칼럼이 '배달' 또는 '결제' 행에 대하여 2018년 1월 1일부터 2018년 7월 30일까지 주문자ID별 결젲 합계 금액을 회원관리(EC_Member) 테이블의 구매실적(BuyCash)칼럼에 합하여 저장하시오.

더보기
UPDATE EC_Member M
SET    BuyCash = BuyCash +
                 (SELECT SUM(CMoney)
                  FROM EC_Order O
                  WHERE O.Order_ID= M.UserID
                  AND CDate BETWEEN '2018/01/01' AND '2018/07/30'
                  AND Gubun IN ('배달', '결제'));

SELECT UserID, Name, TO_CHAR(BuyCash, 'L99,999,999')
FROM   EC_Member
WHERE  Buycash IS NOT NULL;

3. 주문처리(EC_Order) 테이블과 상품관리(EC_Product) 테이블, 회원관리(EC_Member) 테이블을 이용하여 결제합계금액(Cmoney)이 가장 많은 금액중 상위 5명에 대한 (회원명, 결제합계금액)을 출력하시오. (인라인 뷰 이용)

더보기
SELECT ROWNUM "순위", a.*
FROM  (SELECT Name "회원명", SUM(CMoney) "결제합계금액"
       FROM EC_Order O INNER JOIN EC_Member M ON O.Order_ID= M.UserID
       GROUP BY Name
       ORDER BY 2 DESC ) a
WHERE  ROWNUM <= 5;

4. 주문처리(EC_Order) 테이블과 상품관리(EC_Product) 테이블을 이용하여 최근에 주문 결제한 상위자 5명에 대하여 (순위, 상품코드, 상품명, 주문수량, 결제방법, 결제금액, 결제일자)을 출력하시오. (인라인 뷰 이용)

더보기
SELECT ROWNUM "순위", a.*
FROM  (SELECT Product_Code, Product_Name, Order_QTY, Csel, CMoney, CDate
       FROM EC_Order INNER JOIN EC_Product USING (Product_Code)
       WHERE CMoney IS NOT NULL AND CDate IS NOT NULL
       ORDER BY CDate DESC ) a
WHERE  ROWNUM <= 5;

5. 과목임시(T_Course) 테이블에서 추가수강료(Course_fees)의 평균값을 계산하여 출력하고, 추가 수강료가 평균 수강료보다 높은 과목을 출력하시오.

더보기
SELECT AVG(Course_Fees) FROM T_Course;

SELECT *
FROM Course
WHERE Course_Fees >= (SELECT AVG(Course_Fees) FROM Course)
ORDER BY 1;

6. 과목임시(T_Course) 테이블에서 과목코드가 'L1'로 시작하는 과목중에서 추가 수강료가 가장 낮은 금액보다 많은 과목을 추가수강료 역순으로 출력하시오.

더보기
SELECT *
FROM  T_Course
WHERE Course_ID LIKE 'L1%'
AND   Course_fees > ANY (SELECT Course_fees
                       FROM T_Course)
ORDER BY Course_Fees Desc;

7. 수강임시(T_SG_Scores) 테이블에서 성적취득일자가 2018년 6월에 과목코드 별 최고점을 받은 과목의 [과목코드, 학번, 성적, 성적취득일자]를 과목코드 순으로 출력하시오.

더보기
SELECT Course_ID, Student_ID, Score, Score_Assigned
FROM   T_SG_Scores
WHERE (Course_ID, Score) IN (SELECT Course_ID, MAX(Score)
                             FROM T_SG_Scores
                             WHERE TO_CHAR(Score_Assigned, 'YY/MM') = '18/06'
                             GROUP BY Course_ID)
ORDER BY Course_ID;

8. 과목임시(T_Course) 테이블과 수강임시(T_SG_Scores) 테이블을 이용하여 한 명 시아 수강한 과목을 [과목코드, 과목명, 학점수, 담당교수번호, 추가수강료]를 과목코드순으로 출력하시오.

더보기
SELECT *
FROM T_Course C
WHERE EXISTS (SELECT *
              FROM T_SG_Scores SG
              WHERE SG.Course_ID = C. Course_ID);

9. 과목임시(T_Course) 테이블과 수강임시(T_SG_Scores) 테이블을 이용하여 한번도 수강하지 않은 과목을 [과목코드, 과목명, 학점수, 담당교수번호, 추가수강료]를 과목코드순으로 출력하시오.

더보기
SELECT *
FROM   T_Course C
WHERE NOT EXISTS(SELECT *
                 FROM T_SG_Scores SG
                 WHERE SG.Course_ID = C.Course_ID)
ORDER BY 1;

10. 수강(SG_Scores) 테이블을 이용하여 2018학년도에 성적을 취득한 행들을 저장하는 2018학년도 성적(SG_Score_2018) 테이블을 동일한 구조로 생성하고, 저장된 행들을 학번, 과목코드순으로 출력하시오.

더보기
CREATE TABLE SG_Score_2018
AS
   SELECT *
   FROM   SG_Scores
   WHERE  TO_CHAR(Score_Assigned, 'YY') = '18'
     AND  Score IS NOT NULL;

SELECT Student_ID, Course_ID, Score, Grade, Score_Assigned
FROM   SG_Score_2018
ORDER  BY 1, 2;

11. 과목(Course) 테이블에서 추가 수강료가 널인 행을 수강임시(T_Course) 테이블로 복사하시오.

더보기
INSERT INTO T_Course
SELECT *
FROM Course
WHERE Course_Fees IS NULL;

12. 수강(SG_Scores) 테이블과 학생(Student) 테이블을 이용하여 '컴공' 학과의 평균 성적 상위자 3명을 출력하되, 취득과목수가 최소한 3과목 이상인 학생의 (순위, 학과코드 ,학번, 서ㅕㅇ명, 취득과목수, 평균점수)를 출력하시오. 단 평균은 소숫점 2자리까지 반올림하여 출력함.

더보기
SELECT ROWNUM "순위", a.*
FROM (SELECT Dept_ID, student_ID, Name, COUNT(Course_ID), ROUND(AVG(Score),2) "평균"
      FROM SG_Scores INNER JOIN Student USING (Student_ID)
      GROUP BY dept_ID, Student_ID, Name
      HAVING COUNT(Course_ID) > 3
      ORDER BY 5 DESC )a
WHERE ROWNUM <= 3;

13. 수강(SG_Scores) 테이블로부터 과목별 등급 인원수를 피벗 테이블을 이용하여 출력하시오. 등급은 'A','A ','B+','B ','C+','C ',,'D+','D ','F '로 구분함

더보기
SELECT *
FROM (SELECT Title, Grade
      FROM SG_Scores INNER JOIN Course USING (Course_ID) )
PIVOT (
       COUNT(*)
       FOR grade IN ('A+', 'A', 'B+', 'B', 'C+', 'C', 'D+', 'D', 'F ')
       )
ORDER BY 1;

 

728x90
LIST
728x90
SMALL

*아래 연습문제는 연습문제를 위한 견본데이터베이스가 있어야 실행 가능

 

★연습문제1

1. 주문처리(EC_Order) 테이블과 상품관리(EC_Product) 테이블을 이용하여, 'jupark' 회원이 결제한 [주문자 ID, 상품코드, 상품명, 주문 수량, 주문 금액]을 주문자 ID순으로 출력하시오. (등가조인과 내부조인 방법)

①등가조인

더보기
SELECT ORDER_ID,P.PRODUCT_CODE, PRODUCT_NAME,ORDER_QTY,CMONEY "주문금액"
FROM   EC_Order P, EC_Product S
WHERE  P.PRODUCT_CODE=S.PRODUCT_CODE
  AND  ORDER_ID='jupark'
ORDER  BY 1;

②내부조인(INNER JOIN~USING 또는 INNER JOIN~ON 키워드)

더보기
SELECT ORDER_ID,PRODUCT_CODE, PRODUCT_NAME,ORDER_QTY,CMONEY "주문금액"
FROM   EC_Order INNER JOIN EC_Product USING(PRODUCT_CODE)
WHERE  ORDER_ID='jupark'
ORDER  BY 1;

2. 주문처리(EC_Order) 테이블과 회원관리(EC_Member) 테이블, 상품관리(EC_Product) 테이블을 이용하여 결제한 [회원명, 전화번호, 주소, 배달상품, 배달수량]을 회원명순으로 출력하시오. (등가조인과 내부조인 방법)

①등가조인

더보기
COLUMN 배달상품 FORMAT A14
SELECT NAME, TELEPHONE, ADDRESS, PRODUCT_NAME "배달상품", ORDER_QTY "수량"
FROM   EC_Order P, EC_Member S, EC_Product Y
WHERE  P.ORDER_ID=S.USERID
  AND  P.PRODUCT_CODE=Y.PRODUCT_CODE
  AND  GUBUN ='결제'
ORDER  BY 1;

②내부조인(INNER JOIN~USING 또는 INNER JOIN~ON 키워드)

 

더보기
SELECT NAME, TELEPHONE, ADDRESS, PRODUCT_NAME "배달상품", ORDER_QTY "수량"
FROM   EC_Order P INNER JOIN EC_Member S
                    ON(P.ORDER_ID=S.USERID)
                  INNER JOIN EC_Product Y
                    ON(P.PRODUCT_CODE=Y.PRODUCT_CODE)
WHERE  GUBUN ='결제'
ORDER  BY 1;

3. 상품관리(EC_Product) 테이블과 주문처리(EC_Order) 테이블을 이용하여, 모든 상품을 출력하되 주문 상품의 주문금액(주문 수량 x 단가)을 계산하여 [상품명, 주문 수량, 단가, 주문자 ID, 주문금액]을 주문자ID순으로 2가지 방법에 의하여 출력하시오.

① WHERE절을 이용한 좌 외부조인

더보기
SELECT PRODUCT_NAME "주문상품",ORDER_QTY "수량",
       TO_CHAR(UNIT_PRICE,'L99,999,999') "단가",
       ORDER_ID, 
       TO_CHAR(ORDER_QTY*UNIT_PRICE,'L99,999,999') "주문금액"
FROM   EC_PRODUCT P, EC_ORDER C
WHERE  P.Product_CODE=C.PRODUCT_CODE(+)
ORDER  BY 4;

② LEFT OUTER JOIN 키워드를 이용한 방법

 

더보기
SELECT PRODUCT_NAME "주문상품",ORDER_QTY "수량",
       TO_CHAR(UNIT_PRICE,'L99,999,999') "단가",
       ORDER_ID, 
       TO_CHAR(ORDER_QTY*UNIT_PRICE,'L99,999,999') "주문금액"
FROM   EC_Product LEFT OUTER JOIN EC_Order USING(Product_CODE)
ORDER  BY 4;

4. 상품관리(EC_Product) 테이블과 주문처리(EC_Order) 테이블을 이용하여 한 번도 판매되지 않은 상품의 목록 [상품코드, 상품명, 단가, 재고수량]을 상품코드순으로 출력하시오. (외부조인 응용)

더보기
SELECT PRODUCT_CODE,PRODUCT_NAME,UNIT_PRICE,LEFT_QTY
FROM   EC_Product
WHERE  PRODUCT_CODE NOT IN (SELECT PRODUCT_CODE
                            FROM   EC_Order)
ORDER BY 1;

5. 회원관리(EC_Member) 테이블과 주문처리(EC_Order) 테이블을 참고하여 한 번 도 거래하지 않은 회원의 [회원ID, 회원명, 가입일자]을 출력하시오.

더보기
SELECT USERID, NAME, TIMESTAMP
FROM EC_Member
WHERE  USERID NOT IN (SELECT ORDER_ID
                            FROM   EC_Order)
ORDER BY 1;

★연습문제2

1. 회원(EC_Member) 테이블과 주문처리(EC_Order) 테이블을 이용하여 한번 이상 거래한 회원의 [회원명, 주민등록번호, 상품명, 주문수량, 주문금액]을 회원명순으로 출력하시오. (내부조인)

더보기
SELECT Name, Regist_No, O.Product_Code, Order_Qty, Cmoney
FROM EC_Member M INNER JOIN EC_Order O ON (M.UserID = O.Order_ID) 
ORDER BY 1;

2. 회원관리(EC_Member) 테이블과 주문처리(EC_Order) 테이블을 이용하여 한번도 주문하지 않은 회원의 [회원명, 주민등록번호, 전화번호]를 출력하시오.

더보기
SELECT Name, Regist_No, Telephone
FROM EC_Member M LEFT OUTER JOIN EC_Order O ON (M.UserID = O.Order_ID) 
WHERE O.Order_ID is NULL
ORDER BY 1;

3. 주문처리(EC_Order) 테이블, 회원관리(EC_Member) 테이블, 상품관리(EC_Product) 테이블을 이용하여 거주지가 '서울'인 회원 중에서 한번 이상 주문한 회원의 [회원명, 주민번호, 상품명, 주문수량, 단가, 거주지]를 회원명순으로 출력하시오. (내부 조인)

더보기
SELECT Name, Regist_No, Product_Name, Order_Qty, Unit_Price, SUBSTR(Address, 1,2) "거주지"
FROM EC_Member M INNER JOIN EC_Order O ON (M.UserID=O.Order_ID)
                 INNER JOIN EC_Product P ON (O.Product_Code =
P.Product_Code)
WHERE Address LIKE '서울%'
ORDER BY 1;

4. 수강임시(T_SG_Scores) 테이블, 과목임시(T_Course) 테이블, 학생(Student) 테이블을 이용하여 'C1801' 학번의 수강신청 과목을 출력하시오. (등가조인)

더보기
SELECT Dept_ID, Year, TS.Student_ID, Name, TS. Course_ID, Title, C_Number
FROM   T_SG_Scores TS, Student S, T_Course TC
WHERE  TS.Student_ID = S.Student_ID
  AND  TS.Course_ID = TC. Course_ID
  AND  TS. Student_ID = 'C1801'
ORDER  BY 5;

5. 수강임시(T_SG_Scores) 테이블, 과목임시(T_Course) 테이블, 학생(Student) 테이블을 이용하여 'C1801' 학번의 성적을 내부조인(inner join) 방식으로 출력하시오.

더보기
SELECT Dept_ID, Year, Student_ID, Name, Course_ID, Title, C_Number, Grade
FROM T_SG_Scores INNER JOIN Student USING (Student_ID)
                 INNER JOIN T_Course USING (Course_ID)
WHERE Student_ID = 'C1801'
ORDER BY 5;

6. 수강임시(T_SG_Scores) 테이블과 과목임시(T_Course) 테이블을 이용하여 'C1801' 학번의 등급에 대한 평점, 과목별 평점을 출력하시오. 단, 평점은 등급이 'A+'이면 4.5, 'A '이면 4.0, 'B+'이면 3.5, 'B '이면 3.0, 'C+'이면 2.5, 'C '이면 2.0, 'D+'이면 1.5, 'D '이면 1.0, 'F '이면 0.0이며, 과목별 평점은 "등급에 대한 평점 x 학점수"로 계산한다.

더보기
SELECT Student_ID, Course_ID, Title, C_Number "학점", Grade,
       CASE Grade WHEN 'A+' THEN 4.5 WHEN 'A' THEN 4.0
                  WHEN 'B+' THEN 3.5 WHEN 'B' THEN 3.0
                  WHEN 'C+' THEN 2.5 WHEN 'C' THEN 2.0
                  WHEN 'D+' THEN 1.5 WHEN 'D' THEN 1.0
                            ELSE 0.0 
        END "등급평점",
        CASE Grade WHEN 'A+' THEN 4.5 WHEN 'A' THEN 4.0
                   WHEN 'B+'  THEN 3.5 WHEN 'B' THEN 3.0
                   WHEN 'C+' THEN 2.5 WHEN 'C' THEN 2.0
                   WHEN 'D+' THEN 1.5 WHEN 'D' THEN 1.0 
                            ELSE 0.0
        END * C_Number "과목평점"
FROM    T_SG_Scores INNER JOIN Student USING (Student_ID)
                    INNER JOIN T_Course USING (Course_ID)
WHERE   Student_ID = 'C1802'
ORDER   BY Course_ID;

7. 수강임시(T_SG_Scores) 테이블에서 학번별 취득 과목수, 취득 점수의 합계와 평균을 계산하여, [학과코드, 학년, 학번, 성명, 과목수, 총점, 평균]을 총점 내림차순으로 출력하시오. 단, 평균은 소숫점 반올림하여 둘째자리까지 출력한다.

더보기
SELECT Dept_ID, Year, Student_ID, Name, Count (1) "과목수", SUM(Score) "총점",
       TO_CHAR(ROUND (AVG(Score), 2), '999.99') "평균"
FROM   T_SG_Scores SG INNER JOIN Student S USING (Student_ID)
WHERE  Score IS NOT NULL
GROUP  BY Dept_ID, Year, Student_ID, Name
Order  BY 3 DESC;

8. 학과(Department) 테이블과 교수(Professor) 테이블을 이용하여 직책명(Duty)에 대한 계층적 구조("총장->학과장->교수"순)의 학과별 직위 명단을 출력하시오.

더보기
SELECT Dept_Name "소속", decode (Duty,'총장',''
                            ,'학과장','   L______'
                            , NULL, '           L______') || Duty ||
     '  '|| Name || '  ' || Position "직책 및 성명"
FROM Professor P INNER JOIN Department D ON (P.Dept_ID = D.Dept_ID)
START WITH mgr is NULL
CONNECT BY prior Professor_id = Mgr ;

9. 수강(SG_Scores) 테이블과 과목(Course) 테이블을 이용하여 한 명이상 수강한 과목의 [과목코드, 과목명, 학점수]를 과목코드순으로 출력하시오. (INTERSECT)

더보기
SELECT Course_ID, Title, C_Number
FROM   Course
INTERSECT
SELECT Course_ID, Title, C_Number
FROM   SG_Scores INNER JOIN Course USING (Course_ID)
ORDER BY 1;

10. 수강(SG_Scores) 테이블에서 2018학년도 수강 신청한 학생(학번이 'C18'로 시작)의 과목과 2017학년도 수강 신청한 학생(학번이 'C17'로 시작)의 과목중에서 2016학년도 학생(학번이 'C16'이 시작)이 수강하지 않은 과목을 출력하시오.

더보기
SELECT Course_ID, Title, C_Number
FROM SG_Scores INNER JOIN Course USING (Course_ID)
WHERE Student_ID LIKE 'C18%'
UNION
SELECT Course_ID, Title, C_Number
FROM SG_Scores INNER JOIN Course USING (Course_ID)
WHERE Student_ID LIKE 'C17%'
MINUS
SELECT Course_ID, Title, C_Number
FROM SG_Scores INNER JOIN Course USING (Course_ID)
WHERE Student_ID LIKE 'C16%'
ORDER BY 1;

 

728x90
LIST
728x90
SMALL

목차
1. props 다시 정리

2. props 기본값 설정 : defaultProps

3. 태그 사이의 내용을 보여주는 children

4. 비구조화 할당 문법을 통한 props 내부 값 추출

5. proTypes를 통한 props 검증

6. state

7. this.setState에 객체 대신 함수 인자 전달-+

8. 함수컴포넌트에서 useState로 state 사용


1. props

앞에서도 말했지만 props는 properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소이다.

 

아래와 같은 구조일 경우 App Component가 부모 컴포넌트이고 MyComponent Component가 자식 컴포넌트이다.

props는 부모 컴포넌트에서 설정할 수 있다.

//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent name="홍길동" />
    </div>
  );
}

export default App;
//MyComponent 컴포넌트
const MyComponent=(props)=>{

    return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;

}

export default MyComponent;

위와 같이 부모 컴포넌트에 name 속성을 홍길동이라고 줬더니 자식 컴포넌트에 props.name 자리에 홍길동이 잘 나타나게 된다.

이런식으로 name이든 다른 속성이든 props를 다르게 주면 다른 element(다른 맛 붕어빵)이 만들어진다.


2. props 기본값 설정 : defaultProps

//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent />
    </div>
  );
}

export default App;

부모컴포넌트에서 속성값을 안주게 되면 아래와 같이 렌더링되는데

이렇게 props값을 안받았을 경우에 default(기본값)으로 어떤 이름이 나오게 설정할 수 있다.

그게 바로 defaultProps이다

//MyComponent 컴포넌트

const MyComponent=(props)=>{

    return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;

}

MyComponent.defaultProps={
    name: '이름없음'
}

export default MyComponent;


3. 태그 사이의 내용을 보여주는 children

리액트 컴포넌트를 사용할 때 컴포넌트 태그 사이의 내용을 보여주는 props를 children이라고 한다.

//MyComponent 컴포넌트

const MyComponent=(props)=>{

    return <div>안녕하세요, 제 이름은 {props.children}입니다.</div>;

}

export default MyComponent;
//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent >애기</MyComponent>    
    </div>
  );
}

export default App;


4. 비구조화 할당 문법을 통한 props 내부 값 추출

현재 MyComponent에서 props 값을 조회할 때마다 props.name, props.children과 같이 props라는 키워드를 앞에 붙여 주고 있는데 이러한 작업을 편하게 하기위해 비구조화 할당 문법을 사용할 수 있다.

 

▶구조 분해 할당

배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다.

 

그 중에서 객체 구조 분해할당의 형태는 다음과 같다

var o = {p: 42, q: true};
var {p, q} = o;

위와 같이 선언해줌으로써 p와 q를 개별변수로 사용할 수 있게 된다.

p만 호출해도 42라는 값을 가져올 수 있다.

 

리액트에서도 마찬가지이다.

const{name, children}=props라고 해줌으로써

props.name, props.children이라고 명시를 하지 않고 그냥 name, children이라고만 해줘도 해당 값에 접근이 가능해진 것이다.

//MyComponent 컴포넌트

const MyComponent=(props)=>{
    const{name, children}=props;
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

export default MyComponent;

또는

//MyComponent 컴포넌트

const MyComponent=({name, children})=>{
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

export default MyComponent;
//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent name="길동">애기</MyComponent>    
    </div>
  );
}

export default App;

5. proTypes를 통한 props 검증

컴포넌트의 필수 props를 지정하거나 props의 타입(type)을 지정할 때는 proTypes를 사용한다.

proTypes를 사용하려면 코드 상단에 import 구문을 사용하여 불러와야 한다.

import ProTypes from 'prop-types';

 

아래와 같이 name 속성을 proTypes로 string으로 지정해놓으면 문자열이 아닌 다른 자료형일 경우 Console창에 에러메시지가 뜬다.

//MyComponent 컴포넌트
import ProTypes from 'prop-types';

const MyComponent=(props)=>{
    const{name, children}=props;
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

MyComponent.proTypes={
    name:ProTypes.string
}

export default MyComponent;

 

▶isRequired를 사용하여 필수 proTypes 설정

필수로 입력돼야 하는 proType에 isRequired를 붙여준다.

//MyComponent 컴포넌트
import ProTypes from 'prop-types';

const MyComponent=(props)=>{
    const{name, children}=props;
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

MyComponent.proTypes={
    name:ProTypes.string.isrequired
}

export default MyComponent;

 

▶클래스형 컴포넌트에서 props 사용

클래스형 컴포넌트에서 props를 사용할 때 render 함수에서 this.props를 조회하면 되며 defaultProps와 propTypes는 똑같은 방식으로 설정가능하다.

//MyComponent 컴포넌트
import {Component} from 'react';
import ProTypes from 'prop-types';

class MyComponent extends Component{
    render(){
    const {name, children}=this.props;

     return(<div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>)};

}

MyComponent.proTypes={
    name:ProTypes.string.isrequired
}

export default MyComponent;

6. state

state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다.

props는 부모 컴포넌트가 설정하는 값이며 컴포넌트 자신을 해당 props를 읽기 전용으로만 사용할 수 있고 props를 변경하려면 부모 컴포넌트에서 바꿔줘야하지만 state는 내부에서 바뀌게 만들 수 있는 것이다.

 

바뀔수 있는 state 초기값을 설정해주는 형태는 아래와 같다.

컴포넌트의 state는 객체 형식이어야 한다.

 

초기값을 설정해주는 방법은 constructor에 설정해주거나 constructor을 사용하지 않고 바로 state를 설정해주는 방법 2가지가 있다.

 this.state = {
      number: 0,
    };

 

클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해 주어야 하며 

이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해준다.

 

아래에 나오는 onClick이라는 이벤트는 클릭하면 onClick 다음에 오는 함수를 실행하라는 의미이며 이 함수를 넣어줄 때는 화살표 함수 문법을 사용하여 넣어줘야 한다.

 

함수 내부에서 this.setState라는 함수를 사용하는데 이 함수가 state 값을 바꿀 수 있게 해 준다.

 

//생성자에서 state 초기값 설정

import { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
    };
  }
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

 

//생성자 없이 state 초기값 설정

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

state값에는 여러 값이 올 수 있다.

    state = {
      number: 0,
      fixNumber:0
    };

7. this.setState에 객체 대신 함수 인자 전달

아래 예제코드를 보면 onClick을 했을 대 setState가 2번 호출되도록 만들었다. 즉 +2씩 증가되도록 만들려고 한 것이다.

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
            this.setState({ number: this.state.number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

그런데 실제 결과는 +1씩 증가되었다.

위와 같이 작성하면 this.setState를 2번 사용하는 것임에도 불구하고 1씩 더해지는데 this.setState를 사용한다고 해서 stae 값이 바로 바뀌는 것이 아니기 때문이다.

이런 문제점을 해결하기 위해서는 this.setState를 사용할 때 객체 대신 함수를 인자로 넣어주면 된다.

위 코드를 아래코드처럼 바꿔주면 된다.

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
            this.setState(prevState=>{return {number:prevState.number+1}});
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

함수형태로 표현하는 방식은 2개가 있다.

prevState는 기존 상태를 의미하며 기존 상태의 number에 1을 더해주는 것이다.

 

이렇게 함수를 사용하면 즉시 반영이 돼서 위에 있는 코드는 +2씩 잘 증가되는 것을 볼 수 있다.

//1번째 방식
this.setState(prevState=>{return {number:prevState.number+1}}

//2번째 방식
this.setState(prevState=>({number:prevState.number+1}));

2번째 방식은 return을 쓰지 않고 바로 객체를 반환하는 것인데 이 방식을 쓰기 위해서는

function A(){}이런 형태일때 A()뒤에 나오는 중괄호(코드블럭)을 생략하고 써주며

객체를 반환하는 형태이므로 ( { } ) 객체를 나타내는 중괄호를 괄호로 감싸는 형태가 된다.

 

setState 작업이 끝난 후 또 다른 작업을 하고싶을 대는 setState의 두 번째 파라미터로 콜백함수를 등록하면 된다.

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 },()=>{console.log("안녕")});
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;


8. 함수컴포넌트에서 useState로 state 사용

과거에는 함수컴포넌트에서 state를 사용할 수 없었지만 지금은 useState라는 함수를 사용해 함수 컴포넌트에서도 state를 사용할 수 있다.

 

userState를 알아보기 전에 배열 비구조화 할당에 대해 먼저 알아본다.

 

▶배열 비구조화 할당

구조 분해 할당은 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다.

아래와 같이 red, yellow, green이라는 개별 변수로 배열 값을 접근할 수 있는 것이다.

▶useState사용

아래의 형태는 state 초기값을 ''으로 넣어준 것이다.

배열 첫번째 원소인 message는 현재 상태를 나타내고

배열 두번째 원소인 setMessage는 상태를 바꿔주는 함수를 나타내며 세터함수라고 부른다.

    const [message,setMessage]=useState('');
import {useState} from 'react';

const Say=()=>{
    const [message,setMessage]=useState(''); //state값을 ''으로 초기화
    const onClickEnter=()=>setMessage('안녕하세요!'); //state 값을 안녕하세요로 변경
    const onClickLeave=()=>setMessage('안녕히 가세요!');
    return (<div>
        <button onClick={onClickEnter}>입장</button> {/*클릭하면 onClickEnter함수 호출*/}
        <button onClick={onClickLeave}>퇴장</button>
        <h1>{message}</h1>
    </div>);
}

export default Say;

▶useState여러번 사용

 

useState는 한 컴포넌트에 여러번 사용해도 상관없다.

다만 style을 usestate로 지정할 때는 배열의 첫번째 원소인 현재 상태를 CSS에 사용되는 키워드로 사용해야 하며 원래 CSS에서 font-size였던 것이 리액트 에서는 fontSize라고 표현해줘야한다. (카멜 표기+ -기호 제거)

import {useState} from 'react';

const Say=()=>{
    const [message,setMessage]=useState('');
    const onClickEnter=()=>setMessage('안녕하세요!');
    const onClickLeave=()=>setMessage('안녕히 가세요!');

    const [color,setColor]=useState('black');
    const [fontSize,setfontsize]=useState('');
    return (<div>
        <button onClick={onClickEnter}>입장</button>
        <button onClick={onClickLeave}>퇴장</button>
        <h1 style={{color,fontSize}}>{message}</h1>
        <button style={{color:'red'}} onClick={()=>setColor('red')}>빨간색</button>
        <button style={{color:'green'}} onClick={()=>setColor('green')}>초록색</button>
        <button style={{color:'blue'}} onClick={()=>setColor('blue')}>파란색</button>
        
        <button onClick={()=>setfontsize('50px')}>폰트증가</button>
        <button onClick={()=>setfontsize('20px')}>폰트감소</button>

    </div>);
}

export default Say;

 

props를 사용한다고 해서 값이 무조건 고정적이지는 않다. 부모 컴포넌트의 staste를 자식 컴포넌트의 props로 전달하고, 자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메서드를 호출하며 props도 유동적으로 사용할 수 있다.


출처

리액트를 다루는 기술- 김민준

 

728x90
LIST
728x90
SMALL

목차

1. 화면을 그린 후 지우고 다시 출력하면 깜빡이는 문제점

2. 게임 프로그래밍에서의 버퍼 개념

3. 페이지 전환을 통해 깜빡이는 문제 해결하기


1. 화면을 그린 후 지우고 다시 출력하면 깜빡이는 문제점

C언어게임은 콘솔창이므로 콘솔창에서는 고속으로 화면을 지우고 그리게 하는 부분이 없어 지우고 다시 그리는 과정에서 깜빡임이 발생하게 된다.

 

아래 구조는 간단하게 구조체를 하나 만들고 방향키로 캐릭터가 움직이도록 구현하였다.

#include <stdio.h>
#include<Windows.h>
void gotoxy(int x, int y)   //gotoxy api함수

{
	COORD pos = { x,y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

typedef struct _PLAYER
{
	int x, y; 
}PLAYER;

PLAYER player;

void init() {
	player.x = 20;
	player.y = 10;
}
void update() {
}
void render()
{
	system("cls");
	gotoxy(player.x, player.y);
	printf("♀");
}

void release() {
}

int main(void) {
	int nKey;
    
	init();

	while (1) {
		if (_kbhit()) { //키보드가 눌렸는지 체크 
			nKey = _getch(); //눌린값 대입
			if (nKey == 'q') //q를 눌렀다면 break
				break;
			else if (nKey == 224 || nKey == 0) {
				nKey = _getch();
			switch (nKey) {
			case 75: //왼쪽 방향키를 눌렀다면 
				player.x--; 
				break;
			case 77:  //오른쪽 방향키를 눌렀다면
				player.x++; 
				break;
			
			case 72:  //위쪽 방향키를 눌렀다면
				player.y--; 
				break;
			
			case 80:  //아래쪽 방향키를 눌렀다면
				player.y++; 
				break;
		}
			}
		}
		update();
		render();
	}
	release();
	return 0;
}

이동하기 전 위치의 문자를 보이지 않게 하기 위해 system("cls")로 지우고 다시 새로운 위치에 문자를 출력시키기 때문에

어쩔수없이 화면이 지워졌다가 다시 그려짐으로 인해서 화면이 깜빡이게 된다.

 

그렇다고 system("cls")를 해주지 않으면 다음과 같이 잔상이 남게 된다

 

 

이런 화면깜빡임은 보는 사람에게 너무 피로감을 느끼게 한다.  이런 문제를 해결하는 방법을 알아보자.


2. 게임 프로그래밍에서의 버퍼 개념

먼저 해결하는 방법을 알기 위해서는 게임 프로그래밍에서 쓰는 버퍼 개념을 알아야한다.

 

1) 전위 버퍼(primary buffer)

전위 버퍼를 전위면이라고도 하는데 화면과 일대일 대응되는 메모리르 ㄹ말하며 그래픽 카드의 메인 메모리의 일부분이다.

현재 모니터의 해상도를 변경하면 전위 버퍼에 해당되는 메모리도 변경된다.

 

2) 후위 버퍼(back buffer)

전위 버퍼와 동일한 특성을 가진 메모리를 말한다.

전위 버퍼는 모니터와 일대일 대응되므로 컴퓨터와 동작함과 동시에 그래픽 카드에 생성이 되지만 후위 법퍼는 따로 생성이 된다.

 

후위 버퍼는 전위 버퍼와 특성이 같으므로 전위 버퍼와 연결하여 사용하며 주로 시스템 메모리 보다는 그래픽 카드 메모리에 생성하여 빠른 화면 전환을 하기 위해 사용된다.

 

3) 이중 버퍼링(double buffering)

이중 버퍼링은 두 개의 버퍼를 이용하여 화면을 전환하는 방법을 말한다.

사용되는 두 개의 버퍼는 전위 버퍼와 후위 버퍼를 말하며 후위 버퍼의 내용을 전위 버퍼에 복사하여 출력하는 것을 말한다.

 

4) 페이지 전환(Page flipping)

이중 버퍼링에도 문제가 있어 오늘날 모든 게임에서 사용되는 방식이 바로 페이지 전환 방식이다.

실제로 전위 버퍼와 후위 버퍼간의 메모리 복사가 아닌 화면과 일대일 대응하는 메모리의 시작주소를 바꾸는 방식이다.

 

즉, 전위 버퍼를 쓰고 있다가 메모리의 시작 주소를 후위 버퍼로 바꿔서 후위 버퍼를 보여줬다가 다시 시작 주소를 바꿔서 전위 버퍼를 보여줬다가 번갈아가며 출력하게 하는 것이다.


3. 페이지 전환을 통해 깜빡이는 문제 해결하기

위에 봤던 간단한 예제를 버퍼 예제로 바꿔보자.

 

일단 기본 구조는 다음과 같다.

잘 모르겠다면 아래 링크를 잠깐 갔다 오자.

C언어로 게임만들기 1탄 - 게임의 기본구조 :: 잡코딩 정보 블로그 (tistory.com)

 

일단 코드를 복붙하고 따라해보자.

총 3개의 파일이 필요하다

1) main.c

#include <stdio.h>
#include<Windows.h>
#include "Screen.h"

typedef struct _PLAYER
{
	int x, y; //출력기준좌표
}PLAYER;

PLAYER player;

void init() {
	player.x = 20;
	player.y = 10;
}
void update() {
}
void render()
{
	   ScreenClear(); //대기하고 있는 화면 버퍼를 지운다.
	  ScreenPrint(player.x, player.y, "♀"); //(player.x, player.y) 좌표에 문자를 출력한다.
		ScreenFlipping(); //활성화된 화면 버퍼와 비활성화된 화면 버퍼의 상태를 바꾼다
}

void release() {
}

int main(void) {
	int nKey, nRemain;

	ScreenInit();//버퍼를 2개 생성한다.
	init();

	while (1) {
		if (_kbhit()) { //키보드가 눌렸는지 체크 
			nKey = _getch(); //눌린값 대입
			if (nKey == 'q') //q를 눌렀다면 break
				break;
			else if (nKey == 224 || nKey == 0) {
				nKey = _getch();
			switch (nKey) {
			case 75: //왼쪽 방향키를 눌렀다면 
				player.x--; 
				break;
			case 77:  //오른쪽 방향키를 눌렀다면
				player.x++; 
				break;
			
			case 72:  //위쪽 방향키를 눌렀다면
				player.y--; 
				break;
			
			case 80:  //아래쪽 방향키를 눌렀다면
				player.y++; 
				break;
		}
			}
		}
		update();
		render();
	}


	release();
	ScreenRelease(); //화면 버퍼 초기화 함수에서 생성한 두 개의 화면 버퍼를 모두 해제한다.
	return 0;
}

 

2) Screen.c (작명은 아무렇게나 해도 상관없다)

#include <stdio.h>
#include<Windows.h>

static int g_nScreenIndex;
static HANDLE g_hScreen[2];

void ScreenInit()
{
    CONSOLE_CURSOR_INFO cci;

    //화면 버퍼 2개를 만든다.
    g_hScreen[0] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
        0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
    g_hScreen[1] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
        0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);

    //커서 숨기기
    cci.dwSize = 1;
    cci.bVisible = FALSE;
    SetConsoleCursorInfo(g_hScreen[0], &cci);
    SetConsoleCursorInfo(g_hScreen[1], &cci);
}

void ScreenFlipping()
{
    SetConsoleActiveScreenBuffer(g_hScreen[g_nScreenIndex]);
    g_nScreenIndex = !g_nScreenIndex;
}

void ScreenClear()
{
    COORD Coor = { 0,0 };
    DWORD dw;
    FillConsoleOutputCharacter(g_hScreen[g_nScreenIndex], ' ', 80 * 25, Coor, &dw);
}

void ScreenRelease()
{
    CloseHandle(g_hScreen[0]);
    CloseHandle(g_hScreen[1]);
}

void ScreenPrint(int x, int y, char* string)
{
    DWORD dw;
    COORD CursorPosition = { x, y };
    SetConsoleCursorPosition(g_hScreen[g_nScreenIndex], CursorPosition);
    WriteFile(g_hScreen[g_nScreenIndex], string, strlen(string), &dw, NULL);

}

 

3) Screen.h (작명은 아무렇게나 해도 된다)

void ScreenInit();
void ScreenFlipping();
void ScreenClear();
void ScreenRelease();
void ScreenPrint(int x, int y, char* string);
void SetColor(unsigned short color);

 

위에 깜빡이는 예제와 거의 똑같은데 다른 것은 윈도우 API함수가 들어간 것이다.

파일이 준비돼었으니 차근차근 보도록하자.

 

Screen.c 코드는 이해할필요없다. 그냥 가져다 쓰면 된다.

 

<1> ScreenInit()로 전위 버퍼와 백 버퍼로 사용할 화면 버퍼를 두 개 생성한다.

 

<2> ScreenClear()로 하나의 화면 버퍼가 활성화되어 출력되고 있는 동안에 다음 장면을 위한 화면 버퍼는 지운다.

 

 

<3>  ScreenPrint()로 대기 화면 버퍼의 x, y 좌표에 문자열을 출력해준다.

전위버퍼/후위버퍼

 

<4> ScreenFlipping()로 활성화된 화면 버퍼와 비활성화된 화면 버퍼의 상태를 바꾼다.

전위버퍼/후위버퍼

 

아주 빠른 속도로 일어나서 그냥 이동하는 것 처럼 보이지만 이런식으로 빠르게 바꿔치기 돼서 화면에 보이게 된다.

이렇게 코드를 짜면 아래와 같이 깜빡이지 않는 편안함을 느낄 수 있다


참고문헌

C를 이용한 게임프로그래밍-이태성

 

 

 

728x90
LIST
728x90
SMALL

목차

1. 게임의 기본구조


1. 게임의 기본구조

게임의 기본 구조는 크게 초기화, 데이터 갱신, 화면 출력, 해제로 나뉜다.

모든 게임이 이 구조로 돼있지는 않지만 대부분의 게임 구조가 이 구조로 되어있다.

사진 출저 : 아래 참조

1) 초기화

실제로 게임이 실행되기 이전에 게임에 필요한 기본 데이터를 읽고 각종 변수를 초기화한다.

초기화의 내용에는 캐릭터 초기화, 사운드 초기화, 패턴 도는 AI 초기화, 메모리 할당 등이 있다.

 

2) 데이터 갱신

말그대로 데이터가 갱신되는 것이다.

입력장치(주로 키보드나 마우스)에 의해 데이터가 갱신되거나

충돌등의 다양한 방식으로 데이터가 스스로 갱신된다.

 

3) 화면 출력

말그대로 출력하는 것이다.

 

4) 해제

동적으로 메모리를  해제하는 경우가 대부분이지만 초기화에서 생성한 객체를 해제하는 역할도 한다.

객체는 사운드 엔진, 물리 엔진, 그래픽 엔진등을 말한다.

 

 

다음은 게임 기본 구조에 사용할 함수들이며 주로 게임 엔진에 사용되는 함수명이다.

 

사진 출저 : 아래 참조

게임 프로그래밍을 하기 전에 이와 같은 구조를 만드는 것은 분업을 해야하기 때문이다.

사진 출처 : 아래참조

기본구조를 코드로 구현하면 다음과 같다.

 

4개의 함수가 각각 한번씩 실행되고 종료된다. 

#include <stdio.h>

void init() {

}

void update() {

}

void render() {

}

void release() {

}

int main(void) {
	init(); 
	update();
	render();
	release();
	return 0;
}

 

하지만 게임에서 데이터 갱신과 화면 출력은 계속돼야하므로 무한 반복하는 형태로 바꿔준다.

#include <stdio.h>

void init() {

}

void update() {

}

void render() {

}

void release() {

}

int main(void) {
	init();  //초기화
    
	while(1){
	update(); //데이터 갱신
	render(); //화면출력
	}
    
	release(); //해제
	return 0;
}

출처

1. C를 이용한 게임프로그래밍-이태성

2. 사진출처 :  3장. 게임의 기본 구조 (1/2) - C 게임 프로그래밍 - 나우캠퍼스 (daum.net)

 

728x90
LIST
728x90
SMALL

*이 게시글은 Do it! 클론 코딩 영화 평점 웹서비스를 보고 정리한 글입니다.


목차

1. HTML구조 만들어주고 장르 추가하기

2. CSS로 스타일링하기

3. 영화앱 여러 기능 추가하기-react-router-dom 설치하고 프로젝트 폴더 정리하기

4.  영화앱 여러 기능 추가하기-라우터 만들기

5. 영화앱 여러 기능 추가하기-내비게이션 만들어보기

6. 영화앱 여러 기능 추가하기-영화 상세 정보 기능 만들어보기

7. 영화앱 여러 기능 추가하기-리다이렉트 기능 만들어 보기

8. 영화 앱 깃허브에 배포하기


1. HTML 구조 만들어주고 장르 추가하기

그 다음 기본적인 스타일링을 해보도록 하자.

스타일링의 핵심은 CSS지만 CSS를 적용할 HTML도 필요하므로 HTML구조를 넣어준다.

 

HTML에서 class속성을 사용하려면 'class'를 그대로 쓰지만 리액트에서는 class 속성을 사용하기 위해서는 'class'가 아니라 'className'을 써야한다.

리액트는 JSX를 HTML로 변환하면서 className을 class로 다시 바꿔준다.

 

App.js

import React from 'react';
import axios from 'axios';
import Movie from './Movie';


class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  getMovies = async () => {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({ movies, isLoading: false });
  };
  componentDidMount() {
    this.getMovies();
  }
  render() {
    const { isLoading, movies } = this.state;
    return (
      <section className="container">
        {isLoading ? (
          <div className="loader">
            <span className="loader__text">Loading...</span>
          </div>
        ) : (
          <div className="movies">
            {movies.map((movie) => {
              return (
                <Movie
                  key={movie.id}
                  id={movie.id}
                  year={movie.year}
                  title={movie.title}
                  summary={movie.summary}
                  poster={movie.medium_cover_image}
                />
              );
            })}
          </div>
        )}
      </section>
    );
  }
}

export default App;

 

Movie.js

코드를 완성해보니 id props가 필요없어서 지운다.

import React from 'react';
import PropTypes from 'prop-types';

function Movie({ title, year, summary, poster }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {

  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
};

export default Movie;

 

여기까지 만들고보니 영화에서 나름 중요한 장르 데이터가 안들어가있다.

추가해준다.

 

arrayOf(PropTypes.string)은 문자열을 원소로 하는 배열을 의미한다.

import React from 'react';
import PropTypes from 'prop-types';


function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

아래 App 컴포넌트에는

genres={movie.genres}를 추가한다.

import React from 'react';
import axios from 'axios';
import Movie from './Movie';


class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  getMovies = async () => {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({ movies, isLoading: false });
  };
  componentDidMount() {
    this.getMovies();
  }
  render() {
    const { isLoading, movies } = this.state;
    return (
      <section className="container">
        {isLoading ? (
          <div className="loader">
            <span classNAme="loader__text">Loading...</span>
          </div>
        ) : (
          <div className="movies">
            {movies.map((movie) => {
              return (
                <Movie
                  key={movie.id}
                  year={movie.year}
                  title={movie.title}
                  summary={movie.summary}
                  poster={movie.medium_cover_image}
                  genres={movie.genres}
                />
              );
            })}
          </div>
        )}
      </section>
    );
  }
}

export default App;

그리고 Movie 컴포넌트에 장르가 출력될 수 있도록 코드를 수정한다

<ul className="movie__genres">
            {genres.map((genre)=>{
                return <li className="movie__genre">{genre}</li>;

            })}
        </ul>
import React from 'react';
import PropTypes from 'prop-types';


function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <ul className="movie__genres">
            {genres.map((genre)=>{
                return <li className="movie__genre">{genre}</li>;

            })}
        </ul>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

 

위와 같은 코드를 짜면 오류가 발생된다.

앞에서 잠깐 얘기했지만 컴포넌트를 여러개 출력할 때는 유일한 값을 이용하여 key props를 추가해야한다.

그런데 API에서 movies는 id라는 식별가능한 유일한 키가 있는데 genres에는 그런게 없다.

이런 경우에는 어떻게 해야할까?

map함수의 두번째 인자로 index를 주면된다.

자세한 사항은 map함수에 대해 찾아보기 바란다.

 

<ul className="movie__genres">
            {genres.map((genre,index)=>{
                return <li key={index}className="movie__genre">{genre}</li>;

            })}
        </ul>
import React from 'react';
import PropTypes from 'prop-types';


function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <ul className="movie__genres">
            {genres.map((genre,index)=>{
                return <li key={index} className="movie__genre">{genre}</li>;

            })}
        </ul>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

2. CSS로 스타일링하기

우선 Movies.css와 App.css를 만든다.

그 후 Movie.js와 App.js에 import 시킨다.

 

그 다음 아래 코드를 그냥 복사한다.

css수업이 아니므로 자세한 사항은 생략한다.

 

App.css

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
    'Open Sans', 'Helvetica Neue', sans-serif;
  background-color: #eff3f7;
  height: 100%;
}

 

Movie.css

.movies .movie {
    background-color: white;
    margin-bottom: 70px;
    font-weight: 300;
    padding: 20px;
    border-radius: 5px;
    color: #adaeb9;
    box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3),
      0 -6px 16px -6px rgba(0, 0, 0, 0.025);
  }
  
  .movies .movie a {
    display: grid;
    grid-template-columns: minmax(150px, 1fr) 2fr;
    grid-gap: 20px;
    text-decoration: none;
    color: inherit;
  }
  
  .movie img {
    position: relative;
    top: -50px;
    max-width: 150px;
    width: 100%;
    margin-right: 30px;
    box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3),
      0 -12px 36px -8px rgba(0, 0, 0, 0.025);
  }
  
  .movie .movie__title,
  .movie .movie__year {
    margin: 0;
    font-weight: 300;
  }
  
  .movie .movie__title {
    margin-bottom: 5px;
    font-size: 24px;
    color: #2c2c2c;
  }
  
  .movie .movie__genres {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    margin: 5px 0px;
  }
  
  .movie__genres li,
  .movie .movie__year {
    margin-right: 10px;
    font-size: 14px;
  }

 

영화 요약을 180자로 제한하기 위해 아래 코드를 수정한다.

<p className="movie__summary">{summary.slice(0,180)}</p>
import React from 'react';
import PropTypes from 'prop-types';
import './Movie.css';

function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <ul className="movie__genres">
            {genres.map((genre,index)=>{
                return <li key={index} className="movie__genre">{genre}</li>;

            })}
        </ul>
        <p className="movie__summary">{summary.slice(0,180)}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

 

뜨는 제목을 바꾸기 위해 index에서 Movie App으로 바꿔준다.


3. 영화앱 여러 기능 추가하기-react-router-dom 설치하고 프로젝트 폴더 정리하기

◆네비게이션 기능

Home과 About을 만들어서 각가을 클릭하면 해당 화면으로 이동시키는 메뉴이다.

이렇게 화면을 이동시키기 위해서는 장치가 필요한데 그것을 라우터라고 하며

라우터는 react-router-dom 패키지를 이용하면 쉽게 도입 가능하다.

 

cmd나 터미널에 명령어 입력

npm install react-router-dom

 

 

우선 src폴더 아래에 components폴더랑 routes폴더를 만든다.

그 다음 components에 Movie.css, Movie.js 파일 이동

routes폴더에는 About.js, Home.js를 만들어준다.

그런다음 App.js 코드를 Home.js에 복사하고 이름변경에 맞게 수정해준다.

 

Home.js

import React from 'react';
import axios from 'axios';
import Movie from '../components/Movie';
import './Home.css';

class Home extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  getMovies = async () => {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({ movies, isLoading: false });
  };
  componentDidMount() {
    this.getMovies();
  }
  render() {
    const { isLoading, movies } = this.state;
    return (
      <section className="container">
        {isLoading ? (
          <div className="loader">
            <span className="loader__text">Loading...</span>
          </div>
        ) : (
          <div className="movies">
            {movies.map((movie) => {
              return (
                <Movie
                  key={movie.id}
                  year={movie.year}
                  title={movie.title}
                  summary={movie.summary}
                  poster={movie.medium_cover_image}
                  genres={movie.genres}
                />
              );
            })}
          </div>
        )}
      </section>
    );
  }
}

export default Home;

 

Home.css

.container {
  height: 100%;
  display: flex;
  justify-content: center;
}

.loader {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: 300;
}
.movies {
  display: grid;
  grid-template-columns: repeat(2, minmax(400px, 1fr));
  grid-gap: 100px;
  padding: 50px;
  width: 80%;
  padding-top: 70px;
}

@media screen and (max-width: 1090px) {
  .movies {
    grid-template-columns: 1fr;
    width: 100%;
  }
}

 

App.js도 수정

import React from 'react';
import Home from './routes/Home';
import './App.css';


function App(){
  return <Home/>;
}

export default App;

4.  영화앱 여러 기능 추가하기-라우터 만들기

라우터는 사용자가 입력한 URL을 통해 특정 컴포넌트를 불러준다.

예를 들어 사용자가 localhost:3000/about이라고 입력하면 About 컴포넌트를 불러주는 것이다.

react-router-dom은 여러 종류의 라우터를 제공하는데 우리는 HashRouter과 Route 컴포넌트를 사용할 것이다.

 

App.js 코드

 

<Route path="/about" element={<About/>}/>

이렇게 작성해주면 localhost:3000/#/about 주소일 경우

About 컴포넌트 화면을 보여준다.

home도 마찬가지

 

 

import React from 'react';
import './App.css';
import {HashRouter,Route, Routes} from 'react-router-dom';
import About from './routes/About';
import Home from './routes/Home';


function App(){
  return(
    <HashRouter>
      <Routes>
      <Route path="/" element={<Home/>}/>
      <Route path="/about" element={<About/>}/>
      </Routes>
    </HashRouter>
  )
}

export default App;

 

About컴포넌트에도 내용이 있어야하므로 간단하게 만들어준다.

 

About.js

import React from 'react';
import './About.css';

function About() {
  return (
    <div className="about__container">
      <span>
        “Freedom is the freedom to say that two plus two make four. If that is granted, all else
        follows.”
      </span>
      <span>- George Orwell, 1984</span>
    </div>
  );
}

export default About;

About.css

.about__container {
  box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3),
    0 -6px 16px -6px rgba(0, 0, 0, 0.025);
  padding: 20px;
  border-radius: 5px;
  background-color: white;
  margin: 0 auto;
  margin-top: 100px;
  width: 100%;
  max-width: 400px;
  font-weight: 300;
}

.about__container span:first-child {
  font-size: 20px;
}
.about__container span:last-child {
  display: block;
  margin-top: 10px;
}


5. 영화앱 여러 기능 추가하기-내비게이션 만들어보기

라우터가 준비되었으므로 내비게이션을 통해 다른 화면으로 이동하면 된다.

 

먼저 components폴더에 Navigation.js를 만든다.

그리고 Navigation.css 파일도 만든다.

App 컴포넌트에 Navigation 컴포넌트를 import 시킨다

 

페이지 이동시키는 거니까 html a태그 쓰면 되겠지라고 생각하겠지만

a태그는 링크를 누를 때마다 리액트가 죽고 새 페이지가 열리는 문제점이 있어서

react-router-dom의 Link 컴포넌트를 사용한다.

 

Navigation.js

import React from 'react';
import { Link } from 'react-router-dom';
import './Navigation.css';

function Navigation() {
  return (
    <div className="nav">
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
    </div>
  );
}

export default Navigation;

 

App.js

import React from 'react';
import './App.css';
import {HashRouter,Route, Routes} from 'react-router-dom';
import About from './routes/About';
import Home from './routes/Home';
import Navigation from './components/Navigation';

function App(){
  return(
    <HashRouter>
      <Navigation/>
      <Routes>
      <Route path="/" element={<Home/>}/>
      <Route path="/about" element={<About/>}/>
      </Routes>
    </HashRouter>
  )
}

export default App;

 

Navigation.css

.nav {
    z-index: 1;
    position: fixed;
    top: 50px;
    left: 10px;
    display: flex;
    flex-direction: column;
    background-color: white;
    padding: 10px 20px;
    box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3),
      0 -6px 16px -6px rgba(0, 0, 0, 0.025);
    border-radius: 5px;
  }
  
  @media screen and (max-width: 1090px) {
    .nav {
      left: initial;
      top: initial;
      bottom: 0px;
      width: 100%;
    }
  }
  
  .nav a {
    text-decoration: none;
    color: #0008fc;
    text-transform: uppercase;
    font-size: 12px;
    text-align: center;
    font-weight: 600;
  }
  
  .nav a:not(:last-child) {
    margin-bottom: 20px;
  }

 


6. 영화앱 여러 기능 추가하기-영화 상세 정보 기능 만들어보기

Home에서 볼 수 있는 영화 정보는 아주 일부분인데 영화 카드를 누르면 상세 정보를 보여주는 기능을 만들 것이다.

 

이 기능을 만들기 위해서는 route props를 알아야한다

route props는 라우팅 대상이 되는 커포넌트에 넘겨주는 기본 props를 말하는데

우리가 직접 넘겨주지 않아도 기본으로 넘어가는 route props라는 것이 있고 이것을 이용해야 영화 데이터를 상세 정보 컴포넌트에 전달할 수 있다.

 

우선 Link를 import 하고

카드를 누르면 상세 정보를 보여줘야하므로 영화 div 태그안에 Link태그로 감싼다.

그리고 Link에 클릭시 state값을 넘겨준다.

import React from "react";
import PropTypes from "prop-types";
import "./Movie.css";
import { Link } from "react-router-dom";  //여기 

function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <Link //여기
        to={"/movie-detail"}
        state={{ year, title, summary, poster, genres }}
      >
        <img src={poster} alt={title} title={title} />
        <div className="movie__data">
          <h3 className="movie__title">{title}</h3>
          <h5 className="movie__year">{year}</h5>
          <ul className="movie__genres">
            {genres.map((genre, index) => {
              return (
                <li key={index} className="movie__genre">
                  {genre}
                </li>
              );
            })}
          </ul>
          <p className="movie__summary">{summary.slice(0, 180)}</p>
        </div>
      </Link> //여기
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

 

카드를 누르면 detail화면에 넘어가게 만들었으니 이제 detail 컴포넌트를 만들자

 

detail 컴포넌트를 우선 간단하게 만들어주고

import React from 'react';
import {useLocation} from "react-router-dom";

function Detail(props){
    const location=useLocation();
    console.log(location);
    return <span>hello</span>;
    }

export default Detail;

 

App.js에 Detail import 추가하고

route path 추가해준다.

import React from 'react';
import './App.css';
import {HashRouter,Route, Routes} from 'react-router-dom';
import About from './routes/About';
import Home from './routes/Home';
import Navigation from './components/Navigation';
import Detail from './routes/Detail'; //여기

function App(){
  return(
    <HashRouter>
      <Navigation/>
      <Routes>
      <Route path="/"  element={<Home/>}/>
      <Route path="/about" element={<About/>}/>
      <Route path="/movie-detail" element={<Detail/>}/> //여기
      </Routes>
    </HashRouter>
  )
}

export default App;

state 값이 잘 들어가져있는 것을 볼 수 있다


7. 영화앱 여러 기능 추가하기-리다이렉트 기능 만들어 보기

영화 카드를 누르면 state가 잘 들어있는데 URL로 직접 /movie-detail 쳐서 들어가면 state값이 null은 것을 확인할 수 있다

Detail 컴포넌트로 영화 데이터가 넘어가지 못했기 때문에 이런 경우 사용자를 강제로 Home으로 돌려보내야한다.

이런 기능을 리다이렉트 기능이라고 부른다.

 

Detail.js 파일을 아래와 같이 바꾼다

import React from 'react';
import {useLocation, useNavigate} from "react-router-dom";
import { useEffect } from 'react';

function Detail(props){
    const location=useLocation();
    const navigate = useNavigate();
    useEffect(() => {
        
        if(location.state===null){
            navigate('/'); //home으로 되돌아 가는 기능
        }
});
   
    if(location.state){
        return <span>{location.state.title}</span> //제목 출력
    }
    return null;
    }

export default Detail;

 

영화앱을 완성했다. 이제 배포해보자.


8. 영화 앱 깃허브에 배포하기

아래 링크보고 따라하면 된다

https://velog.io/@nemo/github-page-deploy-%EB%B0%B0%ED%8F%AC

 


참고문헌

Do it! 클론 코딩 영화 평점 웹서비스-니꼴라스

[React] Link를 통해 State 전달하기 ( Router v6 ) (velog.io)

https://blog.woolta.com/categories/1/posts/211

https://velog.io/@boyfromthewell/React-useLocation-%EC%82%AC%EC%9A%A9%EC%8B%9C-state-%EA%B0%92%EC%9D%B4-null%EB%A1%9C-%EB%93%A4%EC%96%B4%EC%98%A4%EB%8A%94-%EB%AC%B8%EC%A0%9C

 

728x90
LIST
728x90
SMALL

*이 게시글은 Do it! 클론 코딩 영화 평점 웹서비스를 보고 정리한 글입니다.


목차

1. 로딩 구현

2. 영화 API 가져오기

3. 영화 데이터 화면에 그리기

4. 화면 스타일링 하기


1. 로딩 구현

import React from "react";

class App extends React.Component {
  state = {
    isLoading: true,
  };

  render() {
    const { isLoading } = this.state;  //구조분해할당
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>; //삼항연산자
  }
}

export default App;

구조분해할당을 모르면 이해 자체가 안될 것이다

구조 분해 할당 - JavaScript | MDN (mozilla.org)

 

위 설명을 잘 참고하길 바란다.

 

state 내부의 isLoading을 개별 변수로 사용하기 위해 구조분해할당 기법을 사용하였다. 

구조분해할당한 덕분에 isLoading을 변수로 쓸 수 있게 되었다.

 

삼항연산자는

isLoading이 True면 Loading이 출력되고 False면 We are ready가 출력된다.

 

영화를 담아야하니 state에 movies라는 배열도 추가한다

import React from "react";

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

2. 영화 API 가져오기

우선 render()함수가 실행되면 호출되는 생명주기 함수가 바로 componentDidMount()함수이다.

이 함수를 이용해서 렌더링된 후 바로 화면에 나와야할 영화데이터를 넣을 것이다.

 

원래는 자바스크립트의 fetch() 함수를 알아야하지만 초보자가 사용하기는 난이도가 있어서 Axios라는 도구를 사용한다.

npm install axios

API Documentation - YTS YIFY

영화 데이터를 위 사이트에서 가져올 것이다

 

아래와 같이 App.js 파일을 수정한다.

화면에는 Loading...만 나오지만 오류가 나오지 않으므로 axios는 잘 작동하는 것이다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  componentDidMount(){
    axios.get('https://yts-proxy.now.sh/list_movies.json');
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

브라우저에 F12를 눌러서 나오는 관리자모드에 네트워크에 들어가면

list_movies.json이라는 것이 보이는데 이게 axios가 API를 호출하고 있기 때문에 생긴 것이다

 

axios는 네트워크를 사용하기 때문에 느리게 작동한다.

그래서 axios.get()이 반환한 영화 데이터를 잡으려면 자바스크립트에서 axios.get()을 포함하고 있는 함수의 실행이 끝날 때 까지 시간이 걸릴 수 있다고 알려줘야한다.

 

getMovies라는 함수를 만들고 그 함수 안에서 axios.get()이 실행되도록 만든다.

그리고 axios.get()이 반환한 결과를 movies에 저장한다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=()=>{
    const movies =axios.get('https://yts-proxy.now.sh/list_movies.json');
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

이렇게 하면 componentDidMount()함수가 실행되면 this.getMovies()가 실행될 것이다.

이때 자바스크립트에게 getMovies()함수는 시간이 좀 필요하다고 말해야만

axios.get()이 반환한 데이터를 제대로 잡을 수 있다.

그것을 하기 위해서 async, await라는 키워드가 필요하다.

 

자바스크립트에게 getMovies()함수는 시간이 필요하다고 알리기위해 async를 쓰고

axios.get()의 실행을 기다려 달라고 말해주기 위해 await가 필요하다

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const movies =await axios.get('https://yts-proxy.now.sh/list_movies.json');
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

중간정리

리액트 앱이 실행되면 최초로 render()함수가 실행되고 최초의 state에는 isLoading, movies가 있다.

isLoading은 true이고 movies는 빈배열이다

 

이어서 App 컴포넌트가 마운트되면 componentDidMount() 함수가 실행되면서 getMovies() 함수가 실행된다.

여기서 getMovies()함수에 시간이 많이 걸리는 axios.get()이 포함돼있어 async와 awawit를 붙여준다.


3. 영화 데이터 화면에 그리기

 

console.log(movies)라고 해보자.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const movies =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    console.log(movies);
    
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

콘솔에 보면 우리가 영화 사이트를 만들기 위해 필요한 정보들이 들어있다.

data->data->movies 순서대로 개갸체에 접근하면 원하는 데이터를 추출할 수 있을 것이다

 

console.log(movies.data.data.movies)라고 해보자.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const movies =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    console.log(movies.data.data.movies);
    
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

원하는대로 원하는 영화 데이터만 추출할 수 있게 된 것이다

movies.data.data.movies로 객체에 접근하는 것은 아름답지않아서 아래와같이 구조분해할당으로 코드를 수정한다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    console.log(movies);
    
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

똑같은 결과인 것을 볼 수 있다.

 

데이터 추출을 했으니 이제 이 영화 데이터를 state에 저장하면 될 것이다.

console.log를 지우고 this.setState({movies:movies});를 입력한다.

 

setState는 State 값을 변경해준다.

빈 배열이던 movies state에 movies(영화 데이터)를 넣어준다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    this.setState({movies:movies});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

그런데 자바스크립트 ES6에서는 객체의 키와 대입할 변수의 이름이 같다면 코드를 축약할 수 있다.

this.setState({movies:movies});를

this.setState({movies})로 쓸 수 있는 것이다

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    this.setState({movies});
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

영화데이터를 받았으니 Loading화면이 아니라 We are ready가 출력돼야할 것이다.

따라서 setState에 isLoading을 false로 바꾸는 내용을 추가한다.

 this.setState({movies, isLoading: false});

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    this.setState({movies, isLoading: false});
    
  }



  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

영화데이터를 받았고 영화데이터를 받은 후 isLoading, Movies state값을 변경해줬지만 화면에는 영화 데이터가 아닌 We are ready만 뜬다.

우리는 We are ready가 아니라 영화 정보가 렌더링돼야 하므로 movies state를 그리기 위해 Movies 컴포넌트를 만든다.

 

Movie.js라는 파일을 만든다.

Movie 컴포넌트는 state가 필요하지 않으므로 함수형 컴포넌트로 만든다.

 

아래와 같이 뼈대를 만든다.

import React from 'react';
import PropTypes from 'prop-types';

function Movie(){
 return <h1></h1>;
}


Movie.propTypes = {};

export default Movie;

 

위 링크에서 필요한 데이터가 뭔지 일단 확인한다.

아래와 같이 안뜨면 크롬브라우저 스토어에서 JSON 뷰어를 설치하기 바란다.

필요한 데이터를 확인하고

개발자가 실수하는 것을 방지하기 위해 우선 propTypes를 작성한다.

주의할 것은 poster에는 image의 주소가 들어간다

API에서는 medium_cover_image라고 했지만 이해하기 쉽도록 poster라고 지정했다.

import React from 'react';
import PropTypes from 'prop-types';


function Movie() {
  return (
    <div>
    
    </div>
  );
}

Movie.propTypes = {
  id : PropTypes.number.isRequired,
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
};

export default Movie;

그냥 영화를 보여주면 재미없으므로 랭킹 순서대로 보여주기 위해 axios.get()에 주소를 수정한다.

await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

이제 평점 내림차순으로 영화 데이터를 가져올 수 있게 되었으므로 App 컴포넌트에서 Movie 컴포넌트로 id, title, year, summary ,poster props를 넘겨주면 된다. 

 

아래와 같이 구조 분해 할당으로 props를 받는다.

import React from 'react';
import PropTypes from 'prop-types';


function Movie({ id,title, year, summary, poster }) {
  return (
    <div >
      <img src={poster} alt={title} title={title} />
      <div >
        <h3 >{title}</h3>
        <h5 >{year}</h5>
        <p >{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  id : PropTypes.number.isRequired,
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
};

export default Movie;

 

그 후 App.js(App 컴포넌트)로 가서 영화 데이터에 props를 전달할 수 있도록 수정한다.

 

제일 먼저 Movie component import(가져오기)를 한다.

import Movie from'./Movie';

 

그다음 We are ready를 지우고 movies.map()으로 변경한다.

movie라는 변수에 movies 배열의 요소 하나하나를 다 저장하고 콘솔화면에 그 변수를 출력하는 코드로 수정한다.

그 후 Movie 컴포넌트를 리턴한다.

Movie 컴포넌트를 리턴하긴 했지만 props를 전달하지 않아 아무것도 출력되지 않는다.

return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        console.log(movie);
        return <Movie />;
    }
import React from "react";
import axios from 'axios';
import Movie from'./Movie';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        console.log(movie);
        return <Movie />;
    }
    )}</div>;
  }
}

export default App;

 

Movie 컴포넌트에 props를 전달한다.

   return <Movie 
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}

        
        />;
import React from "react";
import axios from 'axios';
import Movie from'./Movie';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        console.log(movie);
        return <Movie 
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}

        
        />;
    }
    )}</div>;
  }
}

export default App;

 아래와 같이 화면에 잘 뜨는 것을 볼 수 있다.

 

마지막으로

 

App 컴포넌트에 

console.log(movie)는 필요없으니 지우고 key 값을 추가한다.

 

아래와 같이 컴포넌트를 여러개 출력할 때는 유일한 값을 이용하여 key props를 추가해야한다.

   return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        return <Movie 
        key={movie.id} //이 부분
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}
        />;
    }
    )}</div>;

 

import React from "react";
import axios from 'axios';
import Movie from'./Movie';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        return <Movie 
        key={movie.id}
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}
        />;
    }
    )}</div>;
  }
}

export default App;

출처

Do it! 클론 코딩 영화 평점 웹서비스-니꼴라스

 

728x90
LIST

+ Recent posts