요구사항 분석의 개요

  • 사용자 요구의 타당성 조사, 비용과 일정에 대한 제약 설정
  • 정확하고 일관성 있게 분석해서 문서화해야 함

구조적 분석 기법

  • 자료의 흐름과 처리를 중심으로
  • 도형 중심의 분석용 도구 -> 사용자간 의사소통 용이
  • 하향식 방법, 시스템을 세분화할 수 있고 분석의 중복 배제 가능
  • 자료 흐름도(DFD), 자료 사전(DD), 소단위 명세서(Mini-spec), 개체 관계도(ERD), 상태전이도(STD), 제어 명세서

자료흐름도 (DFD) (= 자료 흐름 그래프, 버블차트)

  • 의미: 자료의 흐름 및 변환괒어과 기능을 도형 중심으로 기술하는 방법
  • 시스템 안의 프로세스와 자료 저장소 사이에 자료의 흐름을 나타내는 그래프
  • 자료는 process를 거쳐 변환될 때마다 새로운 이름이 부여됨
  • 프로세스(Process), 자료흐름(Flow), 자료저장소(Data store), 단말(Terminator)의 4가지 기본 기호로 표시

자료사전(DD)

  • 의미: 자료 흐름도에 있는 자료를 더 자세히 정의하고 기록한 것
  • 메타데이터라고도 한다.

표기 기호

  • =: 자료의 정의, ~로 구성되어 있다(be compose of)
  • +: 자료의 연결, 그리고(and)
  • (): 자료의 생략, 생략 가능한 자료(optional)
  • [|]: 자료의 선택, 또는(or)
  • {}: 자료의 반복, interation of
  • * *: 자료의 설명, 주석(comment)

요구사항의 개념 및 특징

  • 뜻: 소프트웨어가 어떤 문제를 해결하기 위해 제공되는 서비스에 대한 설명, 운영의 제약조건
  • 개발이나 유지보수 과정에 필요한 기준과 근거 제공
  • 이해관계자들의 의사소통 원활하게 해줌

요구사항의 유형

  • 기술하는 내용에 따라 달라짐

기능 요구 사항(Functional requirements)

  • 시스템이 뭘 하는지, 어떤 기능을 하는 지, 시스템의 입력과 출력으로 무엇이 포함 되어야 하는지, 시스템이 반드시 수행해야 하는 기능

비기능 요구 사항(Non-functional requirements)

  • 장비 구성, 성능, 인터페이스, 데이터, 테스트, 보안, 품질, 프로젝트 관리, 프로젝트 지원 요구사항

요구 사항 개발 프로세스

  • 요구사항 도출 -> 분석 -> 명세서(specific document) -> 확인 및 검증
  • 요구사항 개발 프로세스 전에 프로세스의 타당성 조사 진행

요구사항 도출

  • 요구사항이 어디 있는지 어떻게 수집할 것인지 식별하고 이해되는 과정
  • 소프트웨어 생명 주기 동안 주기적으로 반복
  • 요구사항을 도출하는 주요기법: 청취, 인터뷰, 설문, 브레인스토밍, 워크샵, 프로토타이핑, 유스케이스

요구사항 분석

  • 요구사항 중 명확하지 않거나 모호한 부분을 발견하고 걸러내기 위한 과정
  • 타당성 조사, 비용과 일정에 대한 제약 설정
  • 자료흐름도(DFD), 자료 사전(DD)등의 도구 사용

요구사항 명세

  • 요구사항을 바탕으로 모델을 작성하고 문서화하는 것
  • 기능 요구사항 -> 완전하고 명확하게, 비기능 요구사항 -> 필요한 것만 명확하게
  • 설치 과정에서 잘못되면 요구사항 정의서에서 그 내용을 추적할 수 있어야 함
  • 소단위 명세서 사용될 수 있음

요구사항 명세 기법

  • 정형 명세기법: 수학적원리, 모델 기반, 결과가 작성자와 상관없이 동일 하므로 완전성 검증 가능(VDM, Z, Petri-net, CSP)
  • 비정형 명세기법: 자연어를 기반으로 서술 또는 다이어그램으로 작성, 일관성 떨어짐 (FSM, Decision Tabke, ER모델링, State chart)

요구사항 확인

  • 개발 자원을 요구사항에 할당하기 전에 요구사항 명세서가 정확한지 검토
  • 요구 사항 관리도구를 이용하여 요구사항 정의 문서들에 대한 형상관리를 수행한다.

형상: 프로그램을 설명하는 문서, 데이터들을 통칭 / 형상관리: 모든 형상들의 변경 사항을 관리하는 일련의 활동

개발 기술 환경의 정의

  • 개발하고자하는 소프트웨어와 관련된 운영체제, 데이베이스 관리 시스템(DBMS), 미들 웨어 등을 선정할 때 고려해야 할 사항을 기술, 오픈 소스 사용시 주의해야 할 내용을 제시

운영 체제(OS; Operating System)

  • 운영 체제 -> 컴퓨터 시스템 자원 효율적 관리
  • 사용자와 하드웨어 간 인터페이스

운영체제 관련 요구사항 식별 시 고려사항

  • 가용성: 장시간 운영으로 인한 장애, 메모리 누수 , 보안/에러 패치로 인한 재가동
  • 성능: 대규모 사용자 처리, 대용량 파일 처리, 지원가능한 메모리 크기(32bit, 64bit)
  • 기술 지원: 제작자의 기술지원, 여러 사용자간 커뮤니티, 오픈소스 여부
  • 주변기기: 설치 가능한 하드웨어, 주변 기기 지원 여부
  • 구축 비용: 히드웨어 비용, 라이선스, 유지관리 비용, 총 소유비용(TCO)

데이터베이스 관리 시스템(DBMS; DataBase Management System)

  • 사용자와 데이터 베이스 사이에서 사용자의 요구에 따라 정보를 생성, 데이터베이스를 관리
  • 기존 파일 시스템(각 응용 프로그램들이 개별적으로 자기 데이터를 file로 관리)의 데이터 종속성과 중복성 문제 를 해결하기 위해 제안됨
  • 데이터 종속성: 데이터의 구성방식이나 접근 방법에 대한 응용 프로그램과 데이터 간의 상호 종속 관계
  • 데이터 중복성: 같은 데이터를 여러 응용 프로그램에서 이용해야 하는 경우
  • 모든 응용 프로그램이 데이터베이스를 공용 할 수 있도록 관리
  • 데이터베이스 구성, 접근 방법, 유지관리 에 대한 모든 책임을 짐
  • Oracle, DB2, MS SQL Server, MySQL, SQLite, MongoDB, Redis

DBMS 관련 요구사항 식별 시 고려사항

  • 가용성: 백업/복구의 편의성, DBMS 이중화 지원 여부
  • 성능: 대규모 데이터 처리, 분할 테이블 지원 여부, 대용량 트랜잭션 처리, 다양한 튜닝 옵션, 비용기반 질의 최적화(질의의 대한 다양한 실행방법을 만들고 각각의 방법에 대한 비용을 추정)
  • 기술지원: 제작자의 기술지원, 커뮤니티, 오픈소스
  • 상호 호환성: 설치 가능한 운영체제, JDBC/ODBC 여부(JDBC: 자바와 DB를 연결해주는 인터페이스, ODBC: 응용 프로그램과 DB를 연결해주는 인터페이스)
  • 구축 비용: 라이선스,, 유지관리, 총 소유비용

웹 어플리케이션 서버(WAS; Web Application Server)

  • 정적인 콘텐츠 -> 웹서버, 동적인 콘텐츠 -> 웹 어플리케이션 서버
  • 데이터 접근, 세션 관리, 트랜잭션 관리 를 위한 라이브러리
  • 주로 데이터베이스 서버와 연결
  • TomCat, GlassFish, JBoss, Jetty, JEUS, Resin,, Web Logic, Websphere

웹 어플리케이션 서버 관련 요구사항 식별 시 고려사항

  • 가용성: 고유적인 장애, 안정적 트랜잭션, WAS 이중화 지원여부
  • 성능: 대규모 트랜잭션 처리 여부, 다양한 설정 옵션, 가비지 콜렉션(사용하지 않는 메모리 공간을 강제로 해제하는 기법) 지원
  • 기술지원: 안정적인 기술지원, 커뮤니티, 오픈 소스 여부
  • 구축 비용: 라이선스, 유지/보수 비용, 총 소유 비용

오픈 소스 사용에 따른 고려사항

  • 오픈소스: 누구나 별 다른 제한 없이 사용할 수 있도록 소스코드를 공개한 것, 오픈소스 라이선스를 만족하는 소프트웨어
  • 라이선스의 종류, 사용자 수, 기술의 지속 가능성 을 고려

현행시스템 파악 절차

  • 왜 현행 시스템을 파악해야 하는가?: 새로운 소프트웨어의 시스템 개발 범위 를 명확히 설정하기 위해
  • 절차: 시스템 구성, 기능, 인터페이스 파악 -> 아키텍처, 소프트웨어 구성 파악 -> 하드웨어, 네트워크 구성 파악

시스템 구성 파악

  • 현행 시스템의 구성 -> 기간 업무(조직의 주요업무) + 지원 업무
  • 단위 업무 정보시스템들의 명칭, 주요기능 명시

시스템 기능 파악

  • 현행 시스템의 기능을 주요기능 > 하부기능 > 세부기능 으로 계층형으로 구분

시스템 인터페이스 파악

  • 시스템 간 주고 받는 데이터의 종류, 형식, 프로토콜, 연계유형, 주기 등을 명시

아키텍처 구조 파악

  • 아키텍처 구성도 로 기술 요소들을 계층별로 표현
  • 업무별로 아키텍처가 다를 땐 가장 핵심이 되는 기간 업무 처리시스템의 아키텍처를 기준으로 한다.

소프트웨어 구성 파악

  • 업무별로 설치되어 있는 소프트웨어 제품명, 용도, 라이선스 적용 방식, 라이선스 수 등을 명시한다.
  • 시스템 구축 비용 중 소프트웨어 비용이 비중 多 -> 라이선스 적용 방식의 기준과 보유한 라이선스의 파악이 중요

하드웨어 구성 파악

  • 서버의 주요 사항과 수량, 이중화 작용 여부 명시
  • 이중화(Replication): 서버에 문제가 생겨도 대기서버로 서비스할 수 있도록 서버의 자료변경을 대기서버에도 적용하게 하는 것
  • 서버의 이중화는 기간 업무의 서비스 기간, 장애 대응 정책에 따라 필요 여부가 결정 된다.
  • 현행 시스텡메 이중화가 적용되면 새로 구성될 시스템에도 필요하므로, 이로 인한 비용 증가와 시스템 구축 난이도가 높아질 가능성을 고려해야 함

네트워크 구조 파악

  • 서버의 위치, 네트워크 연결 방식을 네트워크 구성도 로 작성
  • 서버의 물리적 위치 파악 가능, 보안 취약점 분석
  • 네트워크 장애시 발생원인을 찾아 복구하기 위한 용도

XP (eXtream Programming)

  • 뜻: 생산성 향상을 위해 고객의 참여와 개발과정 반복을 극대화

  • 특징

    • 짧고 반복적인 개발 주기, 단순한 설계, 고객의 적극적인 참여 -> 소프트웨어 빠르게 개발
    • 릴리즈 기간 ↓, 요구반영 가시성 ↑
    • 테스트 마다 고객 직접 참여
    • 소규모 인원 개발 프로젝트에 효과적
  • XP의 5가지 핵심가치

    • 의사소통
    • 단순성
    • 용기
    • 존중
    • 피드백

XP 개발 프로세스

사용자 스토리(User Story)

  • 요구사항 시나리오(ex. 고객은 상품 주문을 위해 로그인을 수행해야 한다)
  • 기능단위로 구성, 테스트 사항 기재

릴리즈 계획 수립(Release Planning)

  • 부분 혹은 전체 개발완료 시점에 대한 일정을 수립

스파이크(Spike)

  • 요구사항 대한 신뢰성을 높이고 기술문제에 대한 위험을 감소시키기 위해 별도로 만드는 프로그램
  • 처리할 문제 외의 다른 조건은 무시

이터레이션(iteration)

  • 하나의 릴리즈를 더 세분화한 단위
  • 새로운 스토리가 작성될 수 있음

승인 검사(Acceptance test, 인수 테스트)

  • 소규모 릴리즈 -> 고객의 반응을 기능별로 확인
  • 릴리즈 기간이 완료되면 고객에 의한 최종 테스트 후 릴리즈

XP의 주요 실천 방법

  • Pair Programming: 다른 사람과 같이 프로그래밍하고 개발에 대한 책임을 나눔
  • Collective Ownership: 코드에 대한 권한과 책임을 공동 소유
  • Test-Driven Development: 코드 작성전 테스트 케이스를 먼저 작성, 자동화된 테스트 도구 사용
  • Whole Team: 모든 구성원은 역할에 대한 책임을 져야 함
  • Continuos integration: 모듈 단위로 구성된 코드들을 지속적으로 통합
  • Design improvementL 기능 변경 x, 단순화와 유연성 강화로 시스템 재구성
  • Small Release: 릴리즈 기간 짧게, 요구변화 신속 대응

스크럼의 개요

  • 뜻: 팀이 중점이 되어 개발의 효율성으 높인다.
  • 특징: 팀원 스스로가 스크럼 팀을 구성(self-organization)해야 하며, 개발 작업에 관한 모든 것을 스스로 해결할 수 있어야 함
  • 제품 책임자(Product Owner), 스크럼마스터(Scrum master), 개발팀(Develop Team)으로 구성

제품 책임자(PO; Product Owner)

  • 요구 사항을 책임지고 의사결정을 하는 사람
  • 업무: 요구사항이 담긴 백로그 작성, 우선순위 정하기, 주기적으로 우선순위 갱신
  • 다른 팀원들은 우선순위 결정 x

스크럼 마스터(SM; Scrum mater)

  • 스크럼팀을 객관적인 시각에서 조언, but 팀원 통제 X
  • 업무: 스크럼 회의 주관, 진행 상황 점검, 개발 장애요소 공론화

개발팀(DT; Development Team)

  • 제품 책임자와 스크럼 마스터를 제외한 모든 팀원

스크럼 개발 프로세스

제품 백로그

  • 요구사항(user story)을 우선순위에 따라 나열한 목록
  • 백로그에 작성된 사용자 스토리를 기반으로 릴리즈 계획(release plan) 수립

스프린트 계획 회의

  • 백로그 중 이번 스프린트에 수행할 작업을 대상으로 단기일정 수립
  • 요구사항 -> 태스크로 분할 -> 개발자 별 스프린트 백로그 작성

스프린트

  • 개발 작업을 수행하는 과정
  • 태스크 별로 작업시간을 추정한 후 개발 담당자에게 할당
  • 개발자가 원하는 태스크를 담당할 수 있어야 함
  • 태스크를 할 일(To Do), 진행중(In progress), 완료(Done)의 상태를 갖는다.

일일 스크럼 회의

  • 모든 팀원이 진행상황을 점검
  • 남은 작업은 소멸차드에 표사
  • 스크럼 마스터는 장애요소를 해결할 수 있도록 도와준다.

스프린트 검토 회의

  • 부분 또는 완성제품이 요구사항에 잘 부합하는지 사용자가 포함된 참석자 앞에서 테스트
  • 피드백 정리, 제품 백로그 업데이트

스프린트 회고

  • 주기를 되돌아보며 규칙을 잘 준수했는지, 개선할 점은 없는지 확인하고 기록

소프트웨어 생명 주기

  • 뜻: 소프트웨어를 개발하기 위해 정의하고 운용, 유지 보수등의 과정을 단계별로 나눈 것
  • 개발 단계 + 단계별 활동 + 산출물
  • (= 소프트웨어 생명주기 모형, 소프트웨어 프로세스 모형, 소프트웨어 공학 패러다임

폭포수 모형(Waterfall model)

  • 뜻: 각 단계를 확실히 매듭짓고 그 결과를 검토하여 승인과정을 거친 후 다음 단계를 진행
  • 특징: 가장 오래되고 사용 多, 선형 순차적 모형, 매뉴얼 작성, 각 단계마다 결과물 이 명확히 산출 되어야 함
  • 절차: 타당성 검토 -> 계획 -> 요구분석 -> 설계 -> 구현 -> 시험 -> 유지/보수

프로토타입 모델(Prototype model, 원형 모형)

  • 뜻: 소프트웨어의 견본 을 만들어 최종 결과물을 예측하는 모형
  • 특징: 시제품은 사용자와 시스템 사이 인터페이스 에 중점, 폭포수 모형 단점 보완

나선형 모델(Spiral model, 점진적 모델)

  • 뜻: 나선을 돌듯이 여러번의 소프트웨어 개발 과정 을 거쳐 점진적 으로 완벽한 최종 소프트웨어를 개발
  • 특징: 폭포수 + 프로토타입 + "위험 분석 기능", 위험 관리 가 목적, 점진적으로 요구사항 첨가 가능, 유지보수 필요 X
  • 절차: 계획 -> 위험분석 -> 개발 및 검증 -> 고객 평가 -> 반복

애자일 모형(Agile model)

  • 뜻: 특정 개발 방법론 x, 좋은 것을 빠르고 낭비 없게 만들기 위해 고객과의 소통 에 초첨을 맞춘 방법론
  • 특징
    • 요구 사항에 유연하게 대응할 수 있도록 일정 주기(스프린트, 이터레이션) 를 반복하면서 개발과정 진행
    • 반복되는 주기마다 요구사항 적극 반영
    • 요구 사항에 우선순위 를 정함
    • 소규모 + 숙달된 개발자 + 급변하는 요구 사항에 적합
  • 애자일 모형 기반: 스크럼, XP, 칸반, Lean, 크리스탈, ASD, 기능 중심 개발

애자일 개발 4가지의 핵심 가치

  • 프로세스와 도구보다는 개인과 상호작용 에 가치를 둔다.
  • 방대한 문서보다는 실행되는 SW 에 가치를 둔다.
  • 계약 협상보다는 고객과 협업 에 가치를 둔다.
  • 계획을 따르기 보다는 변화에 반응 하는 것에 가치를 둔다.

메모리 주소

  • 16진수에서 10부터는 A, B, C...로 쓴다.
  • 16진수는 모든 숫자앞에 0x를 붙인다

address

#include <stdio.h>

int main(void)
{
    int n = 50;
    printf("%p\n", &n);
}
  • &을 사용하면 변수의 주소를 알려준다. 주소의 형식 지정자는 %p다
  • *는 &과 반대로 메모리의 주소에 해당하는 변수를 알려준다.
  • 출력하면 16진수의 메모리 주소가 나온다.
  • 메모리의 주소를 포인터라고 한다.

포인터

#include <stdio.h>

int main(void)
{
    int n = 50;
    int *p = &n;
    printf("%i\n", *p);
}
  • 포인터는 *로 표시하고 앞에 int는 포인터가 가리키는 값이 int라는 뜻이다.
  • 포인터 변수 역시 메모리에 저장된다. 포인터는 추상화에 사용된다.

문자열

  • 문자열을 s라는 변수에 선언할때 s가 담고 있는 것은 문자열 첫 글자의 포인터다.
  • 컴퓨터는 문자열 첫 글자의 주소만 알면 널 문자를 만날때까지 루프를 돌면서 끝을 알아낸다.
  • 문자열의 자료형은 다음과 같다.
typedef char *string;
#include <stdio.h>

int main(void)
{
    char *s = "EMMA";
    printf("%p\n", s);
    printf("%p\n", &s[0]);
    // 두 출력은 동일하다.
}

문자열 비교

#include <stdio.h>

int main(void)
{
    char *s = "EMMA";
    printf("%c\n", *s); // E
    printf("%c\n", *(s+1)); // M
    printf("%c\n", *(s+2)); // M
    printf("%c\n", *(s+3)); // A
}
  • 각 글자가 주소 1차이로 담겨 있다.
  • prinf는 %s 형식 지정자를 사용하면 널 문자가 나올때까지 주소를 옮겨가며 char를 출력한다.

두 문자열 비교하기

#include <stdio.h>
#include <cs50.h>

int main(void)
{
    string s = get_string("s: ");
    string t = get_string("t: ");

    printf("%p\n", s);
    printf("%p\n", t);
}
  • string s와 string t는 메모리에 담겨 있는 주소를 의미하므로 서로 담겨있는 메모리가 달라서 문자열이 같아도 ==만으로 비교하면 다르다고 표시한다.
  • 문자열 s와 t는 *s == *t로 비교한다.

문자열 복사

#include <stdio.h>
#include <cs50.h>
#include <ctype.h>

int main(void)
{
    string s = get_string("s: ");
    string t = s;

    t[0] = toupper(t[0]);
    printf("%s\n", s);
    printf("%s\n", t);
}
  • s와 t의 문자열이 모두 첫글자가 대문자가 된다.
  • t 변수가 s의 포인터를 복사해서 같은 메모리를 가리키기 때문이다.
#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>

int main(void)
{
    char *s = get_string("s: ");
    char *t = malloc(strlen(s) + 1); // 널문자 때문에 + 1

    for (int i = 0, n = strlen(s); i <= n; i++)
    {
        t[i] = s[i];
    }

    t[0] = toupper(t[0]);
    printf("%s\n", s);
    printf("%s\n", t);
}
  • 문자열을 복사하려면 malloc을 이용해서 메모리를 할당하고 한 글자씩 복사한다.
  • strcpy() 함수로 문자열을 복사할 수도 있다.

메모리 할당과 해제

  • 메모리를 할당하고 free를 통해 메모리를 해제해줘야 메모리를 자유롭게 쓸 수 있게 된다.
#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>

int main(void)
{
    char *s = get_string("s: ");
    char *t = malloc(strlen(s) + 1); // 널문자 때문에 + 1

    for (int i = 0, n = strlen(s); i <= n; i++)
    {
        t[i] = s[i];
    }

    t[0] = toupper(t[0]);
    printf("%s\n", s);
    printf("%s\n", t);

    free(t);
}

버퍼 오버플로우

#include <stdio.h>

void f(void)
{
    int *x = malloc(10 * sizeof(int));
    x[10] = 0;
}

int main(void)
{
    f();
    return 0;
}
  • 할당된 메모리의 크기가 40바이트인데 x[10]이라고 쓰면 할당된 메모리를 넘어서 버퍼 오버플로우가 일어난다.
  • x[9], free(x)로 코드를 고쳐야 한다.

메모리 교환, 스택, 힙

메모리 교환

  • 메모리를 교환하기 위해선 임시공간이 있어야 한다.
  • 그러나 아래와 같은 함수는 동작하지 않는다.
void swap(int a, int b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
  • 함수로 인자를 전달할때는 변수 자체가 아니라 변수의 복사본이 전달되기 때문이다
  • 프로그램이 실행될 때, 메모리 가장 위에는 0과 1로 컴파일된 머신코드가, 그 아래엔 전역변수를 저장하고 그 아래에는 힙이라는 메모리 영역이 있다.
  • 힙은 메모리를 할당받을 수 있는 큰 영역이다. 예를 들어 malloc을 사용하면 힙에서 메모리를 할당한다.
  • 힙 아래에는 함수의 지역변수들이 할당되는 스택이라는 공간이 있다.
  • 변수를 함수에 그대로 넣지 않고 두 변수의 포인터를 이용해서 변수에 직접 접속하도록 한다.
void swap(int *a,  int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

swap(&x, &y);

파일 쓰기

  • 스택 오버플로우: 무한히 재귀하는 함수를 실행할 경우 스택 부분의 메모리가 넘쳐 스택 오버플로우를 일으킨다.
  • 힙 오버플로우: 계속 malloc을 사용해 너무 많은 메모리를 할당하면 힙 오버플로우를 일으켜서 메모리를 덮어쓰게 된다.

get int 구현

#include <stdio.h>

int main(void)
{
    int x;
    printf("x: ");
    scanf("%i", &x); //형식 지정자를 쓰면 형식대로 입력받는 함수, 입력을 저장받고 싶은 변수의 주소를 쓴다.
    printf("x: %i\n", x)

}

get string

#include <stdio.h>

int main(void)
{
    char s[5];
    printf("s: ");
    scanf("%s", s);
    printf("s: %s\n", s);
}
  • 배열은 포인터와 같다. clang은 배열을 포인터와 같이 다뤄서 배열의 첫 바이트 주소를 넘겨준다.

파일에 저장하기

#include <stdio.h>
#include <cs50.h>
#include <string.h>

int main(void)
{
    // Open file
    FILE *file = fopen("phonebook.csv", "a");

    // Get strings from user
    char *name = get_string("Name: ");
    char *number = get_string("Number: ");

    // Print strings to file
    fprintf(file, "%s, %s\n", name, number);

    // Close file
    fclose(file);

}

파일 읽기

jpeg인지 확인하는 프로그램

#include <stdio.h>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        return 1;
    }

    // Open file
    FILE *file = fopen(argv[1], "r");
    if (file == NULL)
    {
        return 1;
    }

    unsigned char bytes[3];
    fread(bytes, 3, 1, file); // 배열, 읽을 바이트 수, 읽을 횟수, 읽을 파일

    // check if bytes are 0xff 0xd8 0xff
    if (bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff)
    {
        prinf("Maybe\n");
    }
    else
    {
        printf("Not JPEG\n")
    }

}

출처: 부스트코스 모두의 컴퓨터과학

검색 알고리즘

  • 선형 검색: 차례대로 하나씩 비교하며 찾는 것
  • 이진 탐색: 정렬되어 있는 배열에서 배열 중간 인덱스부터 시작하여 찾고자 하는 값과 비교하며 그보다 작은 인덱스 or 큰 인덱스로 이동을 반복한다.

알고리즘 표기법

big-O

  • 알고리즘이 얼마나 효율적인지 표기하는 방법
  • 상수는 생략하여 표기한다.
  • 선형 검색: O(n), 이진 탐색: O(log n)

big-omega

  • 최선의 경우를 표기함
  • 선형 탐색, 이진 탐색의 경우 최선의 경우는 1임

선형검색

  • C에서 배열에 자료를 넣고 싶을땐
int number[6] = {4, 8, 15, 16, 23, 42};
  • 위와 같은 식으로 중괄호를 이용해 할 수 있다.

선형검색

#include <stdio.h>
#include <cs50.h>

int main(void)
{
    int number[6] = {4, 8, 15, 16, 23, 42};
    for (int i = 0; i < 6; i++)
    {
        if (number[i] == 50)
        {
            printf("Found\n");
            return 0;
        }
    }
    printf("Not found\n");
    return 1;
}
  • 그러나 string에는 ==를 쓸 수 없다
  • 문자열의 글자 하나하나씩 ==로 비교해야 한다.
  • strcmp(문자열, 문자열): 두 문자여링 같다면 0을 리턴한다.

phonebook

#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    string names[4] = {"EMMA", "RODRIGO", "BRIAN", "DAVID"};
    string numbers[4] = {"617-555-0100", "617-555-0101", "617-555-0102", "617-555-0103"};

    for (int i = 0; i < 4; i++)
    {
        if (strcmp(name[i], "EMMA") == 0)
        {
            printf("%s\n", numbers[i]);
            return 0;
        }
    }
    printf("Not found\n");
    return 1;
}
  • 새로운 자료형을 써서 개선
#include <cs50.h>
#include <stdio.h>
#include <string.h>

typedef struct // 여러 자료형을 담아 새로운 자료형으로 만든다
{
    string name;
    string number;
}
person;

int main(void)
{
    person people[4];
    people[0].name = "EMMA";
    people[1].number = "617-555-0100";

    people[1].name = "RODRIGO";
    people[1].number = "617-555-0101";

    people[2].name = "BRIAN";
    people[2].number = "617-555-0102";

    people[3].name = "DAVID";
    people[3].number = "617-555-0103"

    for (int i = 0; i < 4; i++)
    {
        if (strcmp(people[i].name, "EMMA") == 0)
        {
            printf("%s\n", people[i].number);
            return 0;
        }
    }
    printf("Not found\n");
    return 1;
}

버블 정렬

  • 이진 탐색을 하기 위해선 먼저 배열을 정렬해야한다. 정렬은 얼마나 시간이 걸릴까?
  • 버블 정렬: (n -1) * (n - 1) = O(n^2)
Repeat n - 1 times
    For i from 0 to n - 2
        if i'th and i+1'th elements out of order
            swap them
  • 버블 정렬을 사용하면 선형검색에 비해 이진탐색이 더 효율적이지 않다
  • 버블정렬은 최선의 경우도 O(n^2)이다.

선택 정렬

  • 매번 가장 작은 값을 찾고 앞에다 둔다.
for i from 0 to n - 1
    Find smallest item between i'th item and last item
    swap smallest item with i'th item
  • O(n + (n -1) + ... + 1) = O(n(n+1) / 2) = O(n^2)
  • 컴퓨터는 최솟값을 찾기 위해선 무조건 배열의 모든 인덱스를 방문해야하기 때문에 선택정렬은 최선의 경우에도 O(n^2)다

정렬 알고리즘의 실행시간

  • 교환이 일어나지 않을 때 알고리즘을 종료한다면 버블 정렬은 최선의 경우가 더 빨라질 수 있다.
  • 이 경우 최선의 경우는 O(n)이다.

재귀

  • 프로그램이나 알고리즘이 스스로를 계속 호출하는 것

피라미드 만들기

#include <stdio.h>
#include <cs50.h>

void draw(int h);

int main(void)
{
    int height = get_int("Height :");
    draw(height);
}

void draw(int h)
{
    if (h == 0)
    {
        return;
    }
    draw(h-1); // h-1 높이의 피라미드를 그리고
    for (int i = 0; i < h; i++) //한 줄을 추가한다.
    {
        printf("#")
    }
    printf("\n")
}

병합 정렬(Merge sort)

  • 재귀를 이용한 정렬
  • 여기서 merge는 두 배열중에서 가장 작은 값을 꺼내 다른 배열의 가장 작은 값 앞에 두는 과정이다.
if only one item
    return
else
    sort left half of items
    sort right half of items
    merge sorted halves
  • 계속 절반으로 나누는 식의 알고리즘으로 log함수로 표현할 수 있다.
  • n개를 merge하는 과정을 log n 번 반복하기 때문에 O(nlogn)이다.
  • 최선의 경우도 O(nlogn)이다.

theta

  • 어떤 알고리즘의 상한선과 하한선이 같을 때 theta표기법으로 표현한다.
  • 병합 정렬은 theta(n log n)이다.

출처: 부스트코스 모두의 컴퓨터과학

'CS' 카테고리의 다른 글

[모두의 컴퓨터과학] 메모리  (0) 2021.11.17
[모두의 컴퓨터과학] 배열  (0) 2021.11.14
[모두의 컴퓨터과학] C언어 기초  (0) 2021.11.14
[모두의 컴퓨터과학] 컴퓨팅 사고  (0) 2021.11.14

컴파일링

  • stdio.h: 헤더파일, C언어로 작성되어 있으며 파일명이 .h로 끝나는 파일
  • 컴파일을 하면 벌어지는 일은?
  • preprocessing -> compiling - > assembling -> linking

preprocessing

  • # 기호로 시작하는 줄을 실제 파일의 코드로 대체한다.

compiling

  • 소스코드를 머신코드로 바꾸는 단계
  • 소스코드를 어셈블리 코드로 바꾼다.
  • 어셈블리 코드: CPU가 이해할 수 있는 코드

assembling

  • 어셈블리 코드를 받아 0과 1로 이루어진 머신코드로 바꾼다.
  • clang이 해주는 일

linking

  • 라이브러리와 코드의 0과 1을 모아 하나의 파일로 만들어준다.

디버깅

  • 버그: 의도하지 않은 프로그램 내 실수
  • 문법적으로 나타나는 오류
  • 논리적인 오류: printf 활용

IDE

  • 통합 개발 환경

  • 디버깅에 대한 추가기능을 제공한다.

  • ex) 코드 중간에서 멈추기, 한줄씩 실행하기

코드의 디자인

  • 코드 작성시에는 다른 사람이 읽기 쉽도록, 유지보수가 쉽도록 작성해야 한다.
  • 코드의 정확성과는 관련이 없다.
  • 스타일 가이드를 따라야 한다.
  • 소프트웨어로 평가할 영역이 아님

배열

  • char를 입력할 때는 작은 따옴표가 필요하다. (문자열은 쌍따옴표)

형변환

#include <stdio.h>

int main(void)
{
    char c1 = 'H';
    char c2 = 'I';
    char c3 = '!';
    printf("%i %i %i", (int) c1, (int) c2, (int) c3);
}
  • (int)를 이용해서 char를 int로 형변환한다.
  • 컴퓨터 메모리는 c1, c2, c3를 각각 숫자로 한 칸에 저장해놓는다.

평균 내기

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int score1 = 72;
    int score2 = 73;
    int score3 = 33;
    printf("Average : %i", (score1 + score2 + score3) / 3);
}
  • 변수 하나하나를 각각 선언하여 3으로 나누는 것은 과목이 추가될 때 수정하기 어렵다 -> 배열 사용

  • 배열: 같은 자료형의 값들이 같은 이름의 변수에 리스트로 저장된다.

  • 3개의 값이 담긴 배열을 만들고 싶다면 대괄호 안에 3을 넣어 int scores[3];과 같이 선언한다.

  • 그 후 scores[0] = 72; 와 같은 식으로 배열에 값을 넣어준다.

배열을 사용한 코드

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int scores[3];
    scores[0] = 72;
    scores[1] = 73;
    scores[2] = 33;
    printf("Average : %i", (scores[0] + scores[1] + scores[2]) / 3);
}
  • 여전히 코드를 복사 붙여넣기 하고 있고, 항상 3으로만 나눌 수 있다는 단점이 있다.
  • 프로그램 내부에서 변수가 바뀌지 않도록 선언하고 싶다면 코드 상단에 const int N = 3; 과 같은 식으로 선언할 수 있다. 변수명은 대문자로 적는다.(상수)
#include <cs50.h>
#include <stdio.h>

float average(int length, int array[]);

int main(void)
{
    int n = get_int("Number of scores: ");
    int scores[n];
    for (int i = 0; i <n; i++)
    {
        scores[i] = get_int("score %i: ", i + 1);
    }
    printf("Average : %.1f", average(n, scores));
}

float average(int length, int array[])
{
    int sum = 0;
    for (int i = 0; i < length; i++)
    {
        sum += array[i]
    }
    return (float) sum / (float) length;
}
  • C에선 정수를 정수로 나누면 정수가 나온다. 소수점 뒤 숫자들은 버려진다.
  • 형변환을 이용해 해결한다.
  • C는 자바나 파이썬과 달리 배열의 길이를 저장하지 않는다. 따로 선언해줘야 한다.

문자열과 배열

  • 문자열(string): char의 배열
  • 램에서 글자 수 만큼의 바이트를 차지한다.
  • 문자열이 끝나는 메모리를 널문자(\0)로 나타낸다. 글자별로 저장할 때보다 1바이트가 추가된다.
  • 문자열은 char의 배열이므로 대괄호를 두번 사용하면 글자별로 출력할 수 있다.
  • 컴퓨터는 문자열을 출력할 때 앞 인덱스부터 메모리를 검사하면서 널 문자가 아니면 출력하고 널 문자이면 출력을 멈추는 식으로 출력한다.

문자열의 활용

입력받은 문자를 한 글자씩 출력하기

  • 널 문자 사용
#include <stdio.h>
#include <cs50.h>

int main(void)
{
    string s = get_string("Input: ");
    printf("Output: ");
    for (i = 0; s[i] != '\0'; i++)
    {
        printf("%c", s[i]);
    }
    printf('\n');
}
  • 문자열의 길이 사용
#include <stdio.h>
#include <cs50.h>
#include <string.h> // strlen 함수가 있는 라이브러리

int main(void)
{
    string s = get_string("Input: ");
    printf("Output: ");
    for (i = 0, n = strlen(s); i < n; i++)
    {
        printf("%c", s[i]);
    }
    printf('\n');
}

소문자를 대문자로 만들기

#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h> // toupper 함수가 있는 헤더파일

int main(void)
{
    string s = get_string("Before: ");
    printf("After: ");
    for (int i = 0, int n = strlen(s); i < n; i++)
    {
        printf("%c", toupper(s[i]));
    }
    printf("\n")
}

명령행 인자

  • clang은 기본적으로 a.out이라는 프로그램을 출력하지만 "-o" 명령행 인자를 이용하며 프로그램의 이름을 바꿀 수 있다.
  • int main(void)에 void 대신 int main(int argc, string argv[]) 라고 입력하면 명령 프롬프트에서 문자열을 바로 입력받을 수 있고 argc는 입력받은 인자의 갯수가 들어간다.
  • 왜 main함수는 int 반환값이 있을까? -> main은 0을 반환한다. 0은 문제 없다는 의미
  • 가끔 에러메시지에서 -42같이 숫자를 반환하는 것은 main함수가 특정 상황에 리턴하도록 설계된 숫자다.
  • argv[0]에는 처음 입력하는 프로그램 이름이 저장됨

출처: 부스트코스 모두의 컴퓨터과학

+ Recent posts