다음 단계

프로그래밍 및 C++ 소개

온라인 가이드는 보다 심화된 개념으로 이어서 제공됩니다. 파트 3을 읽어 보세요. 이 모듈에서는 포인터 사용과 객체 시작에 중점을 둡니다.

예시 2로 알아보기

이 모듈에서는 분해, 포인터 이해, 객체 및 클래스 시작을 통해 더 많은 연습을 하는 데 중점을 둡니다. 다음 예를 살펴보세요. 요청을 받으면 직접 프로그램을 작성하거나 실험을 수행합니다. 좋은 프로그래머가 되기 위한 핵심은 연습, 연습, 연습이라는 사실은 아무리 강조해도 지나치지 않습니다.

예 1: 추가적인 분해 연습

간단한 게임의 다음과 같은 출력을 생각해 보세요.

Welcome to Artillery.
You are in the middle of a war and being charged by thousands of enemies.
You have one cannon, which you can shoot at any angle.
You only have 10 cannonballs for this target..
Let's begin...

The enemy is 507 feet away!!!
What angle? 25<
You over shot by 445
What angle? 15
You over shot by 114
What angle? 10
You under shot by 82
What angle? 12
You under shot by 2
What angle? 12.01
You hit him!!!
It took you 4 shots.
You have killed 1 enemy.
I see another one, are you ready? (Y/N) n

You killed 1 of the enemy.

첫 번째는 프로그램 실행당 한 번 표시되는 소개 텍스트입니다. 라운드마다 적의 거리를 정의하려면 랜덤 숫자 생성기가 필요합니다. 플레이어로부터 각도 입력을 가져오는 메커니즘이 필요합니다. 적을 공격할 때까지 반복되므로 루프 구조인 것이 분명합니다. 거리와 각도를 계산하는 함수도 필요합니다. 마지막으로, 적을 공격하는 데 걸린 샷과 프로그램이 실행되는 동안 적중한 적 수를 추적해야 합니다. 다음은 기본 프로그램에 대한 개요입니다.

StartUp(); // This displays the introductory script.
killed = 0;
do {
  killed = Fire(); // Fire() contains the main loop of each round.
  cout << "I see another one, care to shoot again? (Y/N) " << endl;
  cin >> done;
} while (done != 'n');
cout << "You killed " << killed << " of the enemy." << endl;

화재 절차는 게임 플레이를 처리합니다. 이 함수에서는 랜덤 숫자 생성기를 호출하여 적의 거리를 가져온 다음 루프를 설정하여 플레이어의 입력을 가져오고 적을 공격했는지 계산합니다. 루프의 방어 조건은 적에게 얼마나 근접했는지를 보여주는 조건입니다.

In case you are a little rusty on physics, here are the calculations:

Velocity = 200.0; // initial velocity of 200 ft/sec Gravity = 32.2; // gravity for distance calculation // in_angle is the angle the player has entered, converted to radians. time_in_air = (2.0 * Velocity * sin(in_angle)) / Gravity; distance = round((Velocity * cos(in_angle)) * time_in_air);

cos() 및 sin() 호출 때문에 math.h를 포함해야 합니다. 이 프로그램을 작성해 보세요. 문제 분해와 기본 C++ 검토에 매우 유용합니다. 함수마다 작업을 한 번만 실행해야 합니다. 이 프로그램은 지금까지 작성한 가장 정교한 프로그램이므로 시간이 조금 걸릴 수 있습니다.해결 방법은 여기를 참고해 주세요. 

예 2: 포인터 연습

포인터를 사용할 때는 다음 네 가지 사항을 기억해야 합니다.
  1. 포인터는 메모리 주소를 보유하는 변수입니다. 프로그램이 실행됨에 따라 모든 변수는 메모리에 저장되며 각 변수는 고유한 주소나 위치에 저장됩니다. 포인터는 데이터 값이 아닌 메모리 주소를 포함하는 특수한 유형의 변수입니다. 일반 변수가 사용되면 데이터가 수정되는 것처럼 포인터 변수가 조작되면 포인터에 저장된 주소 값이 수정됩니다. 다음 예를 참고하세요.
    int *intptr; // Declare a pointer that holds the address
                 // of a memory location that can store an integer.
                 // Note the use of * to indicate this is a pointer variable.
    
    intptr = new int; // Allocate memory for the integer.
    *intptr = 5; // Store 5 in the memory address stored in intptr.
          
  2. 일반적으로 포인터는 저장된 위치('포인트')를 '가리키고' 있습니다. 따라서 위의 예에서는 intptr이 Pointee 5를 가리킵니다.

    'new' 연산자를 사용하여 정수 포인터에 메모리를 할당합니다. 이 작업은 포인트에 액세스하기 전에 해야 합니다.

    int *ptr; // Declare integer pointer.
    ptr = new int; // Allocate some memory for the integer.
    *ptr = 5; // Dereference to initialize the pointee.
    *ptr = *ptr + 1; // We are dereferencing ptr in order
                     // to add one to the value stored
                     // at the ptr address.
          

    * 연산자는 C에서 역참조에 사용됩니다. C/C++ 프로그래머가 포인터를 사용할 때 범하는 가장 일반적인 오류 중 하나는 포인트 초기화를 잊어버리는 것입니다. 알 수 없는 데이터가 포함된 메모리 내 위치에 액세스하므로 런타임 비정상 종료가 발생하기도 합니다. 이 데이터를 수정하려고 하면 미묘한 메모리 손상이 발생하여 추적하기 어려운 버그가 발생할 수 있습니다. 

  3. 두 포인터 사이에 포인터 할당은 두 포인터가 동일한 포인트를 가리키게 합니다. 따라서 대입 y = x;는 y가 x와 같은 점을 가리키도록 합니다. 포인터 할당은 포인터에 닿지 않습니다. 단순히 다른 포인터와 동일한 위치를 갖도록 하나의 포인터를 변경합니다. 포인터 할당 후 두 포인터는 포인터를 '공유'합니다. 
  4. void main() {
     int* x; // Allocate the pointers x and y
     int* y; // (but not the pointees).
    
     x = new int; // Allocate an int pointee and set x to point to it.
    
     *x = 42; // Dereference x and store 42 in its pointee
    
     *y = 13; // CRASH -- y does not have a pointee yet
    
     y = x; // Pointer assignment sets y to point to x's pointee
    
     *y = 13; // Dereference y to store 13 in its (shared) pointee
    }
      

이 코드의 트레이스는 다음과 같습니다.

1. 두 포인터 x 및 y를 할당합니다. 포인터를 할당해도 포인터가 할당되지 않습니다.
2. 포인티를 할당하고 x가 포인티를 가리키도록 설정합니다.
3. x를 역참조하여 포인터에 42를 저장합니다. 이는 역참조 연산의 기본적인 예입니다. x에서 시작한 후 화살표를 따라 포인터에 액세스합니다.
4. y를 역참조하여 포인터에 13을 저장해 봅니다. 이는 y 에 포인트가 할당되지 않았기 때문에 비정상 종료됩니다.
5. y가 x의 포인트를 가리키도록 y = x;를 할당합니다. 이제 x와 y는 같은 포인트를 가리키며 '공유'하고 있습니다.
6. y를 역참조하여 포인터에 13을 저장해 봅니다. 이번에는 이전 과제에서 포인트를 주었기 때문에 작동합니다.

보시다시피 사진은 포인터 사용을 이해하는 데 매우 유용합니다. 또 다른 예를 보겠습니다.

int my_int = 46; // Declare a normal integer variable.
                 // Set it to equal 46.

// Declare a pointer and make it point to the variable my_int
// by using the address-of operator.
int *my_pointer = &my_int;

cout << my_int << endl; // Displays 46.

*my_pointer = 107; // Derefence and modify the variable.

cout << my_int << endl; // Displays 107.
cout << *my_pointer << endl; // Also 107.

이 예에서는 'new' 연산자를 사용하여 메모리를 할당한 적이 없습니다. 우리는 일반 정수 변수를 선언하고 포인터를 통해 조작했습니다.

이 예에서는 힙 메모리를 할당 해제하는 delete 연산자를 사용하는 방법과 더 복잡한 구조를 할당하는 방법을 설명합니다. 메모리 구성 (힙 및 런타임 스택)은 다른 강의에서 다룹니다. 지금은 힙을 실행 중인 프로그램에서 사용할 수 있는 메모리의 무료 저장소라고 생각하세요.

int *ptr1; // Declare a pointer to int.
ptr1 = new int; // Reserve storage and point to it.

float *ptr2 = new float; // Do it all in one statement.

delete ptr1; // Free the storage.
delete ptr2;

이 마지막 예에서는 함수에 대한 참조로 값을 전달하기 위해 포인터를 사용하는 방법을 보여줍니다. 이는 함수 내에서 변수 값을 수정하는 방법입니다.

// Passing parameters by reference.
#include <iostream>
using namespace std;

void Duplicate(int& a, int& b, int& c) {
  a *= 2;
  b *= 2;
  c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(x, y, z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x="<< x << ", y="<< y << ", z="<< z;
  return 0;
}

중복 함수 정의에서 인수에서 &를 제외했다면 변수를 '값별로' 전달합니다. 즉, 변수의 값이 복사됩니다. 함수에서 변수를 변경하면 사본이 수정됩니다. 원본 변수는 수정하지 않습니다.

변수가 참조를 통해 전달되면 변수의 사본이 전달되지 않고 함수의 주소가 함수에 전달됩니다. 로컬 변수를 수정하면 실제로 전달된 원래 변수가 수정됩니다. 

C 프로그래머라면 새로운 시도가 될 것입니다. Duplicate()Duplicate(int *x)로 선언하여 C에서도 같은 작업을 할 수 있습니다. 이때 x는 정수를 가리키는 포인터인 다음 인수 &x(주소: x)로 Duplicate()를 호출하고 Duplicate() 내에서 x의 역참조를 사용합니다(아래 참조). 그러나 C++에서는 참조로 값을 함수에 전달하는 더 간단한 방법을 제공하지만, 기존의 'C' 방법이 여전히 작동합니다.

void Duplicate(int *a, int *b, int *c) {
  *a *= 2;
  *b *= 2;
  *c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(&x, &y, &z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}

C++ 참조의 경우 변수의 주소를 전달할 필요가 없으며 호출된 함수 내의 변수를 역참조할 필요도 없습니다.

다음 프로그램은 무엇을 출력하나요? 기억을 그림으로 그려 보세요.

void DoIt(int &foo, int goo);

int main() {
  int *foo, *goo;
  foo = new int;
  *foo = 1;
  goo = new int;
  *goo = 3;
  *foo = *goo + 3;
  foo = goo;
  *goo = 5;
  *foo = *goo + *foo;
  DoIt(*foo, *goo);
  cout << (*foo) << endl;
}

void DoIt(int &foo, int goo) {
  foo = goo + 3;
  goo = foo + 4;
  foo = goo + 3;
  goo = foo;
} 

프로그램을 실행하여 정답을 확인합니다.

예 3: 참조로 값 전달하기

차량 속도와 양을 입력으로 사용하는 가속() 함수를 작성합니다. 이 함수는 속도에 양을 더하여 차량의 속도를 높입니다. speed 매개변수는 참조로 전달하고 값은 값으로 전달해야 합니다. 해결 방법은 여기를 참고하세요.

예 4: 클래스 및 객체

다음 클래스를 고려하세요.

// time.cpp, Maggie Johnson
// Description: A simple time class.

#include <iostream>
using namespace std;

class Time {
 private:
  int hours_;
  int minutes_;
  int seconds_;
 public:
  void set(int h, int m, int s) {hours_ = h; minutes_ = m; seconds_ = s; return;}
  void increment();
  void display();
};

void Time::increment() {
  seconds_++;
  minutes_ += seconds_/60;
  hours_ += minutes_/60;
  seconds_ %= 60;
  minutes_ %= 60;
  hours_ %= 24;
  return;
}

void Time::display() {
  cout << (hours_ % 12 ? hours_ % 12:12) << ':'
       << (minutes_ < 10 ? "0" :"") << minutes_ << ':'
       << (seconds_ < 10 ? "0" :"") << seconds_
       << (hours_ < 12 ? " AM" : " PM") << endl;
}

int main() {
  Time timer;
  timer.set(23,59,58);
  for (int i = 0; i < 5; i++) {
    timer.increment();
    timer.display();
    cout << endl;
  }
}

클래스 멤버 변수에는 후행 밑줄이 있습니다. 이는 로컬 변수와 클래스 변수를 구분하기 위함입니다.

이 클래스에 감소 메서드를 추가합니다. 해결 방법은 여기를 참고하세요.

과학의 경이로움: 컴퓨터 공학

연습

이 과정의 첫 번째 모듈에서와 마찬가지로 운동과 프로젝트에 대한 솔루션은 제공하지 않습니다.

좋은 프로그램을 잊지 마세요

한 함수가 하나의 작업만 수행하는 함수로 논리적으로 분해됩니다.

... 프로그램에서 할 일을 간략하게 설명하는 기본 프로그램이 있습니다.

... 설명하는 함수, 상수, 변수 이름이 있습니다.

... 상수를 사용하여 프로그램의 '매직' 숫자를 방지합니다.

사용자 인터페이스가 친절합니다.

워밍업 연습

  • 실습 1

    정수 36은 특이한 속성을 가지고 있습니다. 즉, 완벽한 제곱이며 1에서 8까지의 정수의 합이기도 합니다. 그 다음 숫자는 1225(352)와 1에서 49까지의 정수의 합입니다. 다음 정수인 완전제곱과 1...n 계열의 합을 구합니다. 다음 숫자는 32767보다 클 수 있습니다. 알고 있는 라이브러리 함수나 수학 공식을 사용하여 프로그램을 더 빠르게 실행할 수 있습니다. for 루프를 사용하여 숫자가 완벽한 제곱인지 계열의 합인지 판단하도록 이 프로그램을 작성할 수도 있습니다. (참고: 컴퓨터 및 프로그램에 따라 이 숫자를 찾는 데 시간이 꽤 걸릴 수 있습니다.)

  • 실습 2

    대학 서점이 내년 사업을 예상하는 데 도움이 필요합니다. 경험에 비추어 볼 때 도서가 과정에 필수인지 선택사항인지, 그리고 이 도서가 이전에 수업에서 사용된 적이 있는지에 따라 판매가 크게 좌우됩니다. 새로운 필수 교과서는 등록 예정자의 90% 에게 판매되지만, 이전에 수업에 사용된 적이 있다면 구매할 의향이 있는 사람은 65% 에 불과합니다. 마찬가지로 예비 등록자의 40% 가 선택형 교과서를 새로 구매하지만, 이전에 수업에서 사용한 적이 있다면 20% 만 구매할 것입니다. 여기서 '중고'는 중고 도서를 의미하지 않습니다.

  • 사용자가 센티널에 들어갈 때까지 일련의 도서를 입력으로 받는 프로그램을 작성합니다. 도서별로 다음 정보를 요청합니다. 도서의 코드, 도서의 단일 복사 비용, 현재 보유 중인 도서 수, 예비 수업 등록, 도서가 필수/선택사항인지, 이전에 새 상품/중고품인지를 나타내는 데이터입니다. 출력으로 적절한 형식의 화면에 모든 입력 정보와 함께 주문해야 하는 도서 수 (도서가 있다면 새 도서만 주문됨), 각 주문의 총비용과 함께 표시합니다.

    그런 다음 모든 입력이 완료되면 모든 도서 주문의 총비용과 매장이 정가의 80% 를 지불할 경우 예상되는 수익을 표시합니다. 아직 프로그램으로 들어오는 대량의 데이터를 처리하는 방법에 관해 이야기하지 않았으므로 (계속 지켜봐 주세요) 한 번에 한 권의 도서를 처리하고 해당 도서의 출력 화면을 표시합니다. 그런 다음 사용자가 모든 데이터 입력을 완료하면 프로그램은 총 값과 수익 값을 출력해야 합니다.

    코드 작성을 시작하기 전에 이 프로그램의 설계에 대해 생각해보세요. 함수 집합으로 분해하고 문제 해결의 개요처럼 읽는 main() 함수를 만듭니다. 각 함수가 하나의 작업을 실행하는지 확인합니다.

    샘플 출력은 다음과 같습니다.

    Please enter the book code: 1221
     single copy price: 69.95
     number on hand: 30
     prospective enrollment: 150
     1 for reqd/0 for optional: 1
     1 for new/0 for used: 0
    ***************************************************
    Book: 1221
    Price: $69.95
    Inventory: 30
    Enrollment: 150
    
    This book is required and used.
    ***************************************************
    Need to order: 67
    Total Cost: $4686.65
    ***************************************************
    
    Enter 1 to do another book, 0 to stop. 0
    ***************************************************
    Total for all orders: $4686.65
    Profit: $937.33
    ***************************************************

데이터베이스 프로젝트

이 프로젝트에서는 간단한 데이터베이스 애플리케이션을 구현하는 모든 기능을 갖춘 C++ 프로그램을 만듭니다.

이 프로그램을 통해 작곡가 데이터베이스와 작곡가 관련 정보를 관리할 수 있습니다. 프로그램의 기능은 다음과 같습니다.

  • 새 작곡가를 추가하는 기능
  • 작곡가 순위를 지정하는 기능 (작곡가의 음악에 대한 선호도 표시)
  • 데이터베이스의 모든 작곡가를 볼 수 있습니다.
  • 모든 작곡가를 순위별로 볼 수 있음

"소프트웨어 설계를 구성하는 방법에는 두 가지가 있습니다. 한 가지 방법은 명백하게 결함이 없도록 매우 간단하게 만드는 것이고 다른 하나는 그렇게 복잡하게 하여 명백한 결함이 없도록 하는 것입니다. 첫 번째 방법이 훨씬 더 어렵습니다." - C.A.R. Hoare

많은 사람이 '절차적' 접근 방식을 사용하여 설계하고 코딩하는 방법을 배웠습니다. 가장 기본적인 질문은 "프로그램이 무엇을 해야 하는가?"입니다. 문제에 대한 솔루션을 태스크로 분해하며, 각 태스크는 문제의 일부를 해결합니다. 이러한 작업은 main()에서 또는 다른 함수에서 순차적으로 호출되는 프로그램의 함수에 매핑됩니다. 이 단계별 접근 방식은 해결해야 하는 일부 문제에 이상적입니다. 그러나 대부분의 경우 프로그램이 작업 또는 이벤트의 선형 시퀀스가 아닙니다.

객체 지향 (OO) 접근 방식에서는 '나는 어떤 실제 객체를 모델링하는가?'라는 질문으로 시작합니다. 위에서 설명한 대로 프로그램을 태스크로 나누지 않고 실제 객체의 모델로 나눕니다. 이러한 물리적 객체는 일련의 속성으로 정의된 상태와 이러한 객체가 실행할 수 있는 동작 또는 작업으로 구성됩니다. 이러한 작업은 객체의 상태를 변경하거나 다른 객체의 작업을 호출할 수 있습니다. 기본 전제는 객체가 스스로 작업을 수행하는 방법을 '알고' 있다는 것입니다. 

OO 설계에서는 실제 객체를 클래스와 객체, 속성 및 동작 측면에서 정의합니다. 일반적으로 OO 프로그램에는 많은 객체가 있습니다. 그러나 이러한 객체 대부분은 기본적으로 동일합니다. 다음 사항을 고려하세요.

클래스는 실제로 존재할 수 있는 객체의 일반적인 속성과 동작 집합입니다. 위 그림에는 Apple 클래스가 있습니다. 모든 사과에는 종류에 관계없이 색과 맛 속성이 있습니다. Apple에서 속성을 표시하는 동작도 정의했습니다.

이 다이어그램에서는 Apple 클래스 객체 두 개를 정의했습니다. 각 객체는 클래스와 동일한 속성과 작업을 갖지만 객체는 특정 유형의 사과에 관한 속성을 정의합니다. 또한 표시 작업은 특정 객체의 속성을 표시합니다(예: "녹색" 및 "사워".

OO 디자인은 클래스 집합, 이러한 클래스와 관련된 데이터, 클래스가 실행할 수 있는 작업 집합으로 구성됩니다. 또한 다양한 클래스가 상호작용하는 방식도 식별해야 합니다. 이 상호작용은 다른 클래스의 객체 작업을 호출하는 클래스의 객체에 의해 실행될 수 있습니다. 예를 들어 각 Apple 객체의 Display() 메서드를 호출하여 Apple 객체 배열의 색상과 맛을 출력하는 AppleOutputer 클래스를 사용할 수 있습니다.

다음은 OO 설계에서 실행하는 단계입니다.

  1. 클래스를 식별하고, 각 클래스의 객체가 데이터로 저장하는 내용과 객체가 할 수 있는 작업을 일반적으로 정의합니다.
  2. 각 클래스의 데이터 요소 정의
  3. 각 클래스의 작업과 다른 관련 클래스의 작업을 사용하여 한 클래스의 일부 작업을 구현하는 방법을 정의합니다.

대규모 시스템의 경우 이러한 단계는 다양한 세부 수준에서 반복적으로 발생합니다.

작성기 데이터베이스 시스템에는 개별 작성기에 저장하려는 모든 데이터를 캡슐화하는 Composer 클래스가 필요합니다. 이 클래스의 객체는 자신을 승격하거나 강등 (순위를 변경)하고 속성을 표시할 수 있습니다.

Composer 객체 컬렉션도 필요합니다. 이를 위해 개별 레코드를 관리하는 Database 클래스를 정의합니다. 이 클래스의 객체는 Composer 객체를 추가하거나 검색하고 Composer 객체의 표시 작업을 호출하여 개별 객체를 표시할 수 있습니다.

마지막으로 데이터베이스에서 양방향 작업을 제공하기 위한 일종의 사용자 인터페이스가 필요합니다. 이는 자리표시자 클래스입니다. 즉, 사용자 인터페이스가 어떻게 표시될지 아직은 모르지만 필요합니다. 그래픽이나 텍스트 기반일 수도 있습니다. 지금은 나중에 채울 수 있는 자리표시자를 정의합니다.

Composer 데이터베이스 애플리케이션의 클래스를 식별했으므로 다음 단계는 클래스의 속성과 작업을 정의하는 것입니다. 더 복잡한 애플리케이션에서는 연필과 종이 또는 UML 또는 CRC 카드 또는 OOD를 사용하여 클래스 계층 구조와 객체가 상호작용하는 방식을 매핑합니다.

Composer 데이터베이스에서 각 Composer에 저장할 관련 데이터가 포함된 Composer 클래스를 정의합니다. 순위를 조작하고 데이터를 표시하는 메서드도 포함되어 있습니다.

Database 클래스에는 Composer 객체를 보유하기 위한 일종의 구조가 필요합니다. 새 Composer 객체를 구조에 추가하고 특정 Composer 객체를 검색할 수 있어야 합니다. 또한 모든 객체를 항목 순서 또는 순위별로 표시하려고 합니다.

사용자 인터페이스 클래스는 데이터베이스 클래스에서 작업을 호출하는 핸들러로 메뉴 기반 인터페이스를 구현합니다. 

컴포저 애플리케이션에서처럼 클래스를 쉽게 이해할 수 있고 속성과 작업이 명확하면 클래스를 설계하기가 비교적 쉽습니다. 하지만 클래스가 어떻게 관련되고 상호작용하는지 떠오르는 질문이 있다면 먼저 클래스를 그려보고 코드 작성을 시작하기 전에 자세히 살펴보는 것이 좋습니다.

디자인을 명확하게 파악하고 평가한 다음 (나중에 자세히 설명) 각 클래스의 인터페이스를 정의합니다. 이 시점에서는 구현 세부정보에 신경 쓰지 않고 속성과 작업이 무엇인지, 클래스의 상태와 작업 중 다른 클래스에서 사용할 수 있는 부분은 무엇인지만 알아봅니다.

C++에서는 일반적으로 각 클래스의 헤더 파일을 정의하여 이를 수행합니다. Composer 클래스에는 작성기에 저장하려는 모든 데이터의 비공개 데이터 멤버가 있습니다. 접근자('get' 메서드)와 뮤테이터('set' 메서드)는 물론 클래스의 기본 작업이 필요합니다.

// composer.h, Maggie Johnson
// Description: The class for a Composer record.
// The default ranking is 10 which is the lowest possible.
// Notice we use const in C++ instead of #define.
const int kDefaultRanking = 10;

class Composer {
 public:
  // Constructor
  Composer();
  // Here is the destructor which has the same name as the class
  // and is preceded by ~. It is called when an object is destroyed
  // either by deletion, or when the object is on the stack and
  // the method ends.
  ~Composer();

  // Accessors and Mutators
  void set_first_name(string in_first_name);
  string first_name();
  void set_last_name(string in_last_name);
  string last_name();
  void set_composer_yob(int in_composer_yob);
  int composer_yob();
  void set_composer_genre(string in_composer_genre);
  string composer_genre();
  void set_ranking(int in_ranking);
  int ranking();
  void set_fact(string in_fact);
  string fact();

  // Methods
  // This method increases a composer's rank by increment.
  void Promote(int increment);
  // This method decreases a composer's rank by decrement.
  void Demote(int decrement);
  // This method displays all the attributes of a composer.
  void Display();

 private:
  string first_name_;
  string last_name_;
  int composer_yob_; // year of birth
  string composer_genre_; // baroque, classical, romantic, etc.
  string fact_;
  int ranking_;
};

Database 클래스도 간단합니다.

// database.h, Maggie Johnson
// Description: Class for a database of Composer records.
#include  <iostream>
#include "Composer.h"

// Our database holds 100 composers, and no more.
const int kMaxComposers = 100;

class Database {
 public:
  Database();
  ~Database();

  // Add a new composer using operations in the Composer class.
  // For convenience, we return a reference (pointer) to the new record.
  Composer& AddComposer(string in_first_name, string in_last_name,
                        string in_genre, int in_yob, string in_fact);
  // Search for a composer based on last name. Return a reference to the
  // found record.
  Composer& GetComposer(string in_last_name);
  // Display all composers in the database.
  void DisplayAll();
  // Sort database records by rank and then display all.
  void DisplayByRank();

 private:
  // Store the individual records in an array.
  Composer composers_[kMaxComposers];
  // Track the next slot in the array to place a new record.
  int next_slot_;
};

작곡가별 데이터를 별도의 클래스에 어떻게 캡슐화했는지 확인하세요. Composer 레코드를 나타내는 구조체 또는 클래스를 Database 클래스에 배치하고 그 위치에서 직접 액세스할 수도 있습니다. 하지만 이는 '객체화 중'에 해당합니다. 즉, 가능한 한 많이 객체를 모델링하지 않습니다.

Composer 클래스와 데이터베이스 클래스의 구현 작업을 시작하면 별도의 Composer 클래스를 사용하는 것이 훨씬 더 깔끔한 것을 확인할 수 있습니다. 특히 Composer 객체에서 별도의 원자적 연산을 사용하면 데이터베이스 클래스에서 Display() 메서드 구현이 크게 간소화됩니다.

물론 모든 것을 클래스로 만들거나 필요한 것보다 많은 클래스를 보유하는 '과도한 객체화'와 같은 것도 있습니다. 적절한 균형을 찾는 데 연습이 필요하고 프로그래머마다 의견이 다릅니다. 

과도하거나 과소 객체화되고 있는지는 클래스를 신중하게 다이어그램으로 나타내면 해결할 수 있는 경우가 많습니다. 앞서 언급했듯이 코딩을 시작하기 전에 클래스 설계를 정하는 것이 중요하며 이는 접근 방식을 분석하는 데 도움이 될 수 있습니다. 이 목적으로 사용되는 일반적인 표기법은 UML (Unified Modeling Language)입니다. 이제 컴포저 및 데이터베이스 객체에 대한 클래스가 정의되었으므로 사용자가 데이터베이스와 상호작용할 수 있는 인터페이스가 필요합니다. 간단한 메뉴를 통해 해결할 수 있습니다.

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

사용자 인터페이스를 클래스로 구현하거나 절차적 프로그램으로 구현할 수 있습니다. C++ 프로그램의 모든 항목이 클래스일 필요는 없습니다. 실제로 이 메뉴 프로그램에서와 같이 처리가 순차적으로 또는 작업 지향적인 경우, 절차상으로 구현해도 좋습니다. '자리표시자'로 남도록 구현하는 것이 중요합니다. 즉, 특정 시점에 그래픽 사용자 인터페이스를 만들려면 시스템에서 아무것도 변경하지 않고 사용자 인터페이스만 변경해야 합니다.

애플리케이션을 완료하는 데 마지막으로 필요한 것은 클래스를 테스트하는 프로그램입니다. Composer 클래스의 경우 입력을 받아 컴포저 객체를 채운 후 표시하여 클래스가 제대로 작동하는지 확인하는 main() 프로그램이 필요합니다. Composer 클래스의 모든 메서드도 호출하려고 합니다.

// test_composer.cpp, Maggie Johnson
//
// This program tests the Composer class.

#include <iostream>
#include "Composer.h"
using namespace std;

int main()
{
  cout << endl << "Testing the Composer class." << endl << endl;

  Composer composer;

  composer.set_first_name("Ludwig van");
  composer.set_last_name("Beethoven");
  composer.set_composer_yob(1770);
  composer.set_composer_genre("Romantic");
  composer.set_fact("Beethoven was completely deaf during the latter part of "
    "his life - he never heard a performance of his 9th symphony.");
  composer.Promote(2);
  composer.Demote(1);
  composer.Display();
}

Database 클래스에도 비슷한 테스트 프로그램이 필요합니다.

// test_database.cpp, Maggie Johnson
//
// Description: Test driver for a database of Composer records.
#include <iostream>
#include "Database.h"
using namespace std;

int main() {
  Database myDB;

  // Remember that AddComposer returns a reference to the new record.
  Composer& comp1 = myDB.AddComposer("Ludwig van", "Beethoven", "Romantic", 1770,
    "Beethoven was completely deaf during the latter part of his life - he never "
    "heard a performance of his 9th symphony.");
  comp1.Promote(7);

  Composer& comp2 = myDB.AddComposer("Johann Sebastian", "Bach", "Baroque", 1685,
    "Bach had 20 children, several of whom became famous musicians as well.");
  comp2.Promote(5);

  Composer& comp3 = myDB.AddComposer("Wolfgang Amadeus", "Mozart", "Classical", 1756,
    "Mozart feared for his life during his last year - there is some evidence "
    "that he was poisoned.");
  comp3.Promote(2);

  cout << endl << "all Composers: " << endl << endl;
  myDB.DisplayAll();
}

이러한 간단한 테스트 프로그램은 좋은 첫 단계이지만 프로그램이 올바르게 작동하는지 확인하려면 출력을 수동으로 검사해야 합니다. 시스템이 커지면 결과를 수동으로 검사하는 것이 실용적이지 않을 수 있습니다. 이후 강의에서는 단위 테스트 형태의 자체 검사 테스트 프로그램을 소개합니다.

이제 애플리케이션의 설계가 완료되었습니다. 다음 단계는 클래스 및 사용자 인터페이스의 .cpp 파일을 구현하는 것입니다.먼저 .h를 복사하여 붙여넣고 위의 테스트 드라이버 코드를 파일에 붙여넣은 후 컴파일합니다.테스트 드라이버를 사용하여 클래스를 테스트합니다. 그런 다음, 다음 인터페이스를 구현합니다.

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

Database 클래스에서 정의한 메서드를 사용하여 사용자 인터페이스를 구현합니다. 메서드에 오류가 없도록 하세요. 예를 들어 순위는 항상 1~10 범위여야 합니다. Database 클래스의 데이터 구조를 변경할 계획이 아니라면 누구도 101명의 작곡가를 추가하지 못하게 합니다.

모든 코드는 편의를 위해 여기에 반복되는 코딩 규칙을 따라야 합니다.

  • Google에서 작성하는 모든 프로그램은 작성자 이름, 연락처 정보, 간단한 설명, 사용법 (해당하는 경우)을 제공하는 헤더 주석으로 시작합니다. 모든 함수/메서드는 작업 및 사용에 관한 주석으로 시작합니다.
  • 예를 들어 처리가 까다롭거나 명확하지 않거나 흥미롭거나 중요한 경우 코드 자체를 문서화하지 않을 때는 전체 문장을 사용하여 설명 주석을 추가합니다.
  • 설명이 포함된 이름을 항상 사용하세요. 변수는 my_variable에서와 같이 _로 구분된 소문자 단어입니다. 함수/메서드 이름은 MyExcitingFunction()에서와 같이 대문자를 사용하여 단어를 표시합니다. 상수는 'k'로 시작하고 kDaysInWeek와 같이 대문자를 사용하여 단어를 표시합니다.
  • 들여쓰기는 2의 배수입니다. 첫 번째 수준은 공백 2개입니다. 더 들여쓰기가 필요한 경우 공백 4개, 공백 6개 등을 사용합니다.

현실 세계에 오신 것을 환영합니다.

이 모듈에서는 대부분의 소프트웨어 엔지니어링 조직에서 사용되는 매우 중요한 두 가지 도구를 소개합니다. 첫 번째는 빌드 도구이고 두 번째는 구성 관리 시스템입니다. 두 도구 모두 많은 엔지니어가 하나의 대규모 시스템에서 작업하는 산업 소프트웨어 엔지니어링에서 필수적입니다. 이러한 도구는 코드베이스의 변경사항을 조정하고 제어하는 데 도움이 되며 여러 프로그램 및 헤더 파일에서 시스템을 컴파일하고 연결하는 효율적인 방법을 제공합니다.

Makefile

프로그램을 빌드하는 프로세스는 일반적으로 필요한 파일을 올바른 순서로 컴파일하고 링크하는 빌드 도구를 사용하여 관리합니다. C++ 파일에는 종속 항목이 있는 경우가 많습니다. 예를 들어 한 프로그램에서 호출되는 함수가 다른 프로그램에 있는 경우입니다. 또는 여러 다른 .cpp 파일에 헤더 파일이 필요할 수 있습니다. 빌드 도구는 이러한 종속 항목에서 올바른 컴파일 순서를 파악합니다. 또한 마지막 빌드 이후 변경된 파일만 컴파일합니다. 따라서 수백 또는 수천 개의 파일로 구성된 시스템에서 많은 시간을 절약할 수 있습니다.

make라는 오픈소스 빌드 도구가 흔히 사용됩니다. 자세한 내용은 이 도움말을 참조하세요. Composer 데이터베이스 애플리케이션의 종속 항목 그래프를 만들 수 있는지 확인한 후 makefile로 변환합니다.해결 방법은 여기를 참고하세요.

구성 관리 시스템

산업용 소프트웨어 엔지니어링에 사용되는 두 번째 도구는 구성 관리(CM)입니다. 변경사항을 관리하는 데 사용됩니다. 밥과 수잔이 테크 작가이며 둘 다 기술 매뉴얼 업데이트를 위해 노력 중이라고 가정해 보겠습니다. 회의 중에 관리자는 업데이트할 동일 문서의 섹션을 각 사용자에게 할당합니다.

기술 설명서는 Bob과 Susan 모두 액세스할 수 있는 컴퓨터에 저장됩니다. CM 도구나 프로세스가 마련되어 있지 않으면 여러 문제가 발생할 수 있습니다. 한 가지 가능한 시나리오는 Bob과 Susan이 동시에 매뉴얼 작업을 할 수 없도록 문서를 저장하는 컴퓨터를 설정하는 것입니다. 이로 인해 속도가 상당히 느려질 수 있습니다.

저장소 컴퓨터에서 Bob과 Susan이 동시에 문서를 열 수 있도록 허용하지 않으면 더 위험한 상황이 발생합니다. 발생할 수 있는 상황은 다음과 같습니다.

  1. 철수씨가 자신의 컴퓨터에서 문서를 열고 자신의 섹션에서 작업합니다.
  2. 수잔은 컴퓨터에서 문서를 열고 자신의 섹션에서 작업합니다.
  3. 철수씨가 변경사항을 완료하고 저장 컴퓨터에 문서를 저장합니다.
  4. 수잔이 변경사항을 완료하고 저장 컴퓨터에 문서를 저장합니다.

이 그림은 기술 설명서의 단일 사본에 컨트롤이 없는 경우 발생할 수 있는 문제를 보여줍니다. 수잔이 변경사항을 저장하면 철수씨가 변경한 내용을 덮어씁니다.

CM 시스템에서 제어할 수 있는 상황 유형입니다. CM 시스템을 사용하여 Bob과 Susan은 모두 각자의 기술 매뉴얼 사본을 '확인'하고 작업합니다. 철수씨가 변경사항을 다시 확인하면 시스템은 수잔이 자신의 사본이 체크아웃된 것을 알게 됩니다. Susan이 사본을 확인하면 시스템은 Bob과 Susan이 모두 변경한 내용을 분석하고 두 변경사항 조합을 병합하는 새 버전을 만듭니다.

CM 시스템에는 위에서 설명한 동시 변경사항을 관리하는 것 외에도 여러 기능이 있습니다. 많은 시스템에서는 문서가 처음 만들어진 시점부터 모든 버전의 문서에 대한 보관 파일을 저장합니다. 기술 매뉴얼의 경우 사용자가 이전 버전의 매뉴얼을 가지고 기술 작성자에게 질문을 할 때 매우 유용할 수 있습니다. CM 시스템을 사용하면 기술 작성자가 이전 버전에 액세스하여 사용자에게 표시되는 내용을 확인할 수 있습니다.

CM 시스템은 특히 소프트웨어의 변경사항을 제어하는 데 유용합니다. 이러한 시스템을 소프트웨어 구성 관리 (SCM) 시스템이라고 합니다. 대규모 소프트웨어 엔지니어링 조직에서 개별 소스 코드 파일의 수가 매우 많고 이를 변경해야 하는 엔지니어 수도 많다는 점을 고려하면 SCM 시스템이 매우 중요하다는 것을 알 수 있습니다.

소프트웨어 구성 관리

SCM 시스템은 파일의 최종 사본이 중앙 저장소에 보관된다는 단순한 개념을 기반으로 합니다. 사용자는 저장소에서 파일 사본을 체크아웃하고 사본을 작업한 다음 완료되면 다시 체크인합니다. SCM 시스템은 단일 마스터 세트를 기준으로 여러 사용자의 버전을 관리하고 추적합니다. 

모든 SCM 시스템은 다음과 같은 필수 기능을 제공합니다.

  • 동시 실행 관리
  • 버전 관리
  • 동기화

각 기능에 대해 자세히 살펴보겠습니다.

동시 실행 관리

동시 실행은 두 명 이상이 동시에 파일을 수정하는 것을 의미합니다. 대규모 저장소의 경우 사용자가 이러한 작업을 수행할 수 있도록 해야 하지만 이로 인해 몇 가지 문제가 발생할 수 있습니다.

엔지니어링 분야의 간단한 예를 살펴보겠습니다. 엔지니어가 소스 코드의 중앙 저장소에서 동일한 파일을 동시에 수정할 수 있도록 한다고 가정해 보겠습니다. Client1과 Client2가 동시에 파일을 변경해야 합니다.

  1. Client1이 bar.cpp를 엽니다.
  2. Client2가 bar.cpp를 엽니다.
  3. Client1이 파일을 변경하고 저장합니다.
  4. Client2가 파일을 변경하고 Client1의 변경사항을 덮어써 저장합니다.

당연히 이러한 일이 발생하지 않도록 해야 합니다. 두 엔지니어가 마스터 세트에서 직접 작업하지 않고 별도의 복사본에서 작업하도록 하여 상황을 통제하더라도 (아래 그림과 같이) 사본은 어떻게 조정되어야 합니다. 대부분의 SCM 시스템은 여러 엔지니어가 파일을 체크아웃 ('동기화' 또는 '업데이트')하고 필요에 따라 변경할 수 있도록 하여 이 문제를 해결합니다. 그런 다음 SCM 시스템은 파일이 저장소에 다시 체크인('제출' 또는 '커밋')될 때 변경사항을 병합하는 알고리즘을 실행합니다.

이러한 알고리즘은 간단할 수도 있고 (엔지니어에게 충돌하는 변경사항을 해결하도록 요청) 그다지 간단하지 않을 수도 있습니다 (충돌하는 변경사항을 지능적으로 병합하는 방법을 결정하고 시스템이 실제로 중단되는 경우 엔지니어에게만 물어보기). 

버전 관리

버전 관리란 파일 버전을 추적하여 파일의 이전 버전을 다시 만들거나 이전 버전으로 롤백할 수 있게 하는 것을 의미합니다. 이는 저장소에 체크인할 때 모든 파일의 보관 파일 사본을 만들거나 파일의 모든 변경사항을 저장하여 실행됩니다. 언제든지 보관 파일을 사용하거나 정보를 변경하여 이전 버전을 만들 수 있습니다. 버전 관리 시스템은 변경사항을 체크인한 사용자, 체크인한 시점, 변경사항에 관한 로그 보고서도 만들 수 있습니다.

동기화

일부 SCM 시스템에서는 개별 파일이 저장소에서 체크인 및 체크아웃됩니다. 보다 강력한 시스템을 사용하면 한 번에 두 개 이상의 파일을 체크아웃할 수 있습니다. 엔지니어는 저장소 (또는 그 일부)의 자체 전체 사본을 확인하고 필요에 따라 파일 작업을 합니다. 그런 다음 주기적으로 변경사항을 마스터 저장소에 다시 커밋하고 개인 사본을 업데이트하여 다른 사용자가 변경한 내용을 최신 상태로 유지합니다. 이 프로세스를 동기화 또는 업데이트라고 합니다.

서브버전

Subversion (SVN)은 오픈소스 버전 제어 시스템입니다. 위에서 설명한 모든 기능이 포함되어 있습니다.

충돌이 발생하는 경우 SVN은 간단한 방법을 채택합니다. 충돌은 두 명 이상의 엔지니어가 코드베이스의 동일한 영역에 서로 다른 변경사항을 적용한 후 둘 다 변경사항을 제출하는 경우를 말합니다. SVN은 엔지니어에게 충돌이 있음을 알릴 때만 엔지니어가 해결해야 합니다.

이 과정 전반에서는 구성 관리에 익숙해지도록 SVN을 사용할 것입니다. 이러한 시스템은 업계에서 매우 일반적입니다.

첫 번째 단계는 시스템에 SVN을 설치하는 것입니다. 안내를 보려면 여기를 클릭하세요. 사용 중인 운영체제를 찾아 적절한 바이너리를 다운로드합니다.

일부 SVN 용어

  • 버전: 파일 또는 파일 세트의 변경사항입니다. 버전은 지속적으로 변경되는 프로젝트의 하나의 '스냅샷'입니다.
  • 저장소: SVN에서 프로젝트의 전체 업데이트 기록을 저장하는 마스터 사본입니다. 프로젝트마다 저장소가 하나씩 있습니다.
  • 작업 카피: 엔지니어가 프로젝트를 변경하는 사본입니다. 특정 프로젝트에 대해 개별 엔지니어가 소유한 여러 작업 사본이 있을 수 있습니다.
  • 결제: 저장소에서 작업용 사본을 요청합니다. 작업 중인 사본은 체크아웃될 당시의 프로젝트 상태와 같습니다.
  • 커밋: 작업 사본에서 중앙 저장소로 변경사항을 전송합니다. 체크인 또는 제출이라고도 합니다.
  • 업데이트: 다른 사용자의 변경사항을 저장소에서 작업 사본으로 가져오거나 작업 사본에 커밋되지 않은 변경사항이 있는지 여부를 표시합니다. 이는 위에서 설명한 동기화와 동일합니다. 따라서 업데이트/동기화는 저장소 사본과 함께 작업 중인 사본을 최신 상태로 만듭니다.
  • 충돌: 두 엔지니어가 파일의 동일한 영역에 변경사항을 커밋하려고 하는 상황입니다. SVN은 충돌을 나타내지만 엔지니어가 이를 해결해야 합니다.
  • 로그 메시지: 버전을 커밋할 때 첨부하는 주석으로, 변경사항을 설명합니다. 로그는 프로젝트 진행 상황에 대한 요약을 제공합니다.

SVN을 설치했으니 이제 몇 가지 기본 명령어를 실행해 보겠습니다. 가장 먼저 할 일은 지정된 디렉터리에 저장소를 설정하는 것입니다. 명령어는 다음과 같습니다.

$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/project -m "Initial import"
Adding         mytree/foo.c
Adding         mytree/bar.c
Adding         mytree/subdir
Adding         mytree/subdir/foobar.h

Committed revision 1.

import 명령어는 mytree 디렉터리 콘텐츠를 저장소의 디렉터리 프로젝트에 복사합니다. list 명령어로 저장소의 디렉터리를 확인할 수 있습니다.

$ svn list file:///usr/local/svn/newrepos/project
bar.c
foo.c
subdir/

가져오기로는 작동하는 사본이 생성되지 않습니다. 이렇게 하려면 svn checkout 명령어를 사용해야 합니다. 이렇게 하면 디렉터리 트리의 작업 사본이 만들어집니다. 이제 그렇게 해 보겠습니다.

$ svn checkout file:///usr/local/svn/newrepos/project
A    foo.c
A    bar.c
A    subdir
A    subdir/foobar.h
…
Checked out revision 215.

작업 사본이 있으므로 이제 파일과 디렉터리를 변경할 수 있습니다. 작업 사본은 다른 파일 및 디렉터리 컬렉션과 같습니다. 새 항목을 추가하거나 수정하고 이동하고 이동할 수 있으며 전체 작업 사본을 삭제할 수도 있습니다. 작업 사본에서 파일을 복사하고 이동하는 경우 운영체제 명령어 대신 svn copysvn Move를 사용하는 것이 중요합니다. 새 파일을 추가하려면 svn add를 사용하고 파일을 삭제하려면 svn delete를 사용합니다. 수정하려면 편집기로 파일을 열고 편집하기만 하면 됩니다.

Subversion과 함께 자주 사용되는 몇 가지 표준 디렉터리 이름이 있습니다. '트렁크' 디렉터리는 프로젝트의 기본 개발 라인을 보유합니다. 'branches' 디렉터리에는 작업 중인 모든 브랜치 버전이 포함됩니다.

$ svn list file:///usr/local/svn/repos
/trunk
/branches

작업 사본에 필요한 모든 변경을 적용했고 이를 저장소와 동기화하려 한다고 가정해 보겠습니다. 다른 많은 엔지니어가 저장소의 이 영역에서 작업하는 경우 작업 복사본을 최신 상태로 유지하는 것이 중요합니다. svn status 명령어를 사용하여 변경한 내용을 볼 수 있습니다.

A       subdir/new.h      # file is scheduled for addition
D       subdir/old.c        # file is scheduled for deletion
M       bar.c                  # the content in bar.c has local modifications

상태 명령어에는 이 출력을 제어하는 플래그가 많이 있습니다. 수정된 파일의 구체적인 변경사항을 보려면 svn diff를 사용하세요.

$ svn diff bar.c
Index: bar.c
===================================================================
--- bar.c	(revision 5)
+++ bar.c	(working copy)
## -1,18 +1,19 ##
+#include
+#include

 int main(void) {
-  int temp_var;
+ int new_var;
...

마지막으로 저장소에서 작업 사본을 업데이트하려면 svn update 명령어를 사용합니다.

$ svn update
U  foo.c
U  bar.c
G  subdir/foobar.h
C  subdir/new.h
Updated to revision 2.

이 경우 충돌이 발생할 수 있습니다. 위의 출력에서 'U'는 이러한 파일의 저장소 버전에 변경사항이 없고 업데이트가 완료되었음을 나타냅니다. 'G'는 병합이 발생했음을 의미합니다. 저장소 버전이 변경되었지만 사용자의 변경사항과 충돌하지 않았습니다. 'C'는 충돌을 나타냅니다. 즉, 저장소의 변경사항이 개발자의 변경사항과 중복되므로 이제 변경사항 중에서 선택해야 합니다.

충돌이 있는 모든 파일의 경우 Subversion은 작업 사본에 세 개의 파일을 넣습니다.

  • file.mine: 작업 복사본을 업데이트하기 전에 작업 복사본에 있던 파일입니다.
  • file.rOLDREV: 변경하기 전에 저장소에서 체크아웃한 파일입니다.
  • file.rNEWREV: 이 파일은 저장소의 현재 버전입니다.

다음 세 가지 중 한 가지 방법으로 충돌을 해결할 수 있습니다.

  • 파일을 살펴보고 수동으로 병합을 수행합니다.
  • SVN에서 만든 임시 파일 중 하나를 작업 사본 버전 위에 복사합니다.
  • svn 되돌리기를 실행하면 모든 변경사항이 삭제됩니다.

충돌을 해결한 후에는 svn resolve를 실행하여 SVN에 알립니다. 이렇게 하면 임시 파일 3개가 삭제되고 SVN은 더 이상 충돌 상태의 파일을 보지 않습니다.

마지막으로 할 일은 최종 버전을 저장소에 커밋하는 것입니다. svn commit 명령어를 사용하면 됩니다. 변경사항을 커밋할 때는 변경사항을 설명하는 로그 메시지를 제공해야 합니다. 이 로그 메시지는 생성된 버전에 연결됩니다.

svn commit -m "Update files to include new headers."  

SVN에 대해 알아보고 SVN이 대규모 소프트웨어 엔지니어링 프로젝트를 어떻게 지원할 수 있는지에 대해 알아야 할 사항은 아직도 많이 있습니다. 웹에는 다양한 리소스가 있습니다. Google에서 'Subversion'을 검색하기만 하면 됩니다.

연습 삼아 Composer 데이터베이스 시스템용 저장소를 만들고 모든 파일을 가져옵니다. 그런 다음 작업 사본을 확인하고 위에서 설명한 명령어를 수행합니다.

참조

온라인 서브버전 도서

SVN에 대한 위키백과 문서

Subversion 웹사이트

애플리케이션: 해부학 연구

텍사스 대학교 오스틴 캠퍼스의 eSkeletons 확인