Post

Java to C++

Java to C++

개발 공부를 하다보면 다른 개발언어로 작성된 코드를 볼 일도 많고 특히 해외 사이트에서 discussion을 볼 일이 잦았는데 C/C++ 코드를 볼 일이 상당히 많았다. 자료구조 위주로 비교하며 자바를 C++로 어떻게 작성되는지 알아보고자 공부하였다.

데이터 유형 및 변수

C++에서 숫자를 저장하는 방법

  • 자바와 마찬가지로 int, double가 있다. 하지만 int와 같은 숫자 유형의 범위는 machine에 따라 다르다. DOS, Windows 3.x를 실행하는 PC와 같은 16비트 시스템에서는 Java의 4바이트 int와 달리 2바이트다. 이러한 머신에서는 int범위가 충분하지 않을 때 마다 long으로 전환해야한다.

C++에는 숫자를 더 효율적으로 저장할 수 있는 short 및 unsigned 유형이 있다. 추가 효율성이 중요하지 않은 한 이러한 유형을 피하는 것이 가장 좋다.

boolean유형은 C++에서는 bool로 사용한다. string유형은 string이라고 하며 Java와 매우 유사하다. 다음과 같은 차이점이 있다.

  1. C++ 문자열은 아스키코드가 아닌 유니코드로 저장된다.
  2. C++ 문자열은 수정가능하지만 자바는 수정 불가능하다.
  3. C++ 하위 문자열 연산은 substr아 합니다. s.substr(i, n)은 i위치에서 길이 n인 하위 문자열을 추출한다.
  4. 문자열은 다른 문자열과만 연결할 수 있으며, 임의의 객체와는 연결할 수 없다.
  5. 문자열을 비교하려면 관계 연산자 == != < <= > >= 를 사용한다. 마지막 4개의 연산자는 사전식으로 비교를 수행한다. 실제로 Java에서 equals와 compareTo를 사용하는것보다 편리하다.

변수와 상수

다음과 같이 지역 변수가 선언된다. int n = 5;

그러나 Java와 큰 차이가 있다. C++ 컴파일러는 모든 로컬 변수가 읽기 전에 초기화 되었는지 확인하지 않으므로 변수를 초기화하는것을 잊기 쉽다. 그러면 변수의 값은 로컬 변수가 차지하는 메모리 위치에 우연히 있던 무작위 비트 패턴이다. 이는 프로그래밍 오류의 원천이다.

Java에서와 같이 클래스는 데이터필드와 정적 변수를 가질 수 있다. 그리고 변수는 함수와 클래스 외부에서 선언될 수 있다. 이러한 전역변수는 프로그램의 모든 함수에서 엑세스할 수 있다. C++는 전연변수를 피해야한다.

C++ 에서 상수는 어디에서나 선언될 수 있다. (Java에서 상수는 클래스의 정적 데이터여야 했다). C++에서는 const키워드를 사용한다. const int 1년당일수 = 365;

클래스

C++에서 클래스의 정의는 Java와 다르다.

1
2
3
4
5
6
7
8
9
10
11
class Point {
public:
    Point();
    Point(double xval, double yval);
    void move(double dx, double dy);
    double getX() const;
    double getY() const;
private:
	double x;
    double y;
};

몇 가지 중요한 차이점

  1. C++에는 public 및 private 섹션이 있으며 키워드 public 및 private로 시작한다. Java에서는 각 개별 항목에 public 또는 private태그를 지정한다.
  2. 클래스 정의는 메서드 선언만 포함한다. 실제 구현은 별도로 나열된다.
  3. 접근자 메서드는 키워드 const로 태그된다.
  4. 클래스 끝에 세미콜론이 있다. 메서드 구현은 클래스 정의를 따른다. 메서드는 클래스 외부에서 정의되므로 각 메서드 이름 앞에 클래스 이름이 접두사로 붙는다. :: 연산자는 클래스와 메서드 이름을 구분한다. 암묵적 매개변수를 수정하지 않는 엑세서 메서드는 const로 태그가 지정된다. ```c Point::Point() { x = 0, y = 0 }

void Point::move(double dx, double dy) { x = x + dx; y = y + dy; }

double Point::getX() const { return x; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
## 객체
Java와 C++의 주요 차이점은 객체 변수의 동작이다. C++에서 객체 변수는 객체 참조가 아닌 값을 보유한다. **C++에서 객체를 생성할 때는 new 연산자를 사용하지 않는다.** 변수 이름 뒤에 생성 매개변수를 제공하기만 하면 된다.
`Point p(1, 2);`
생성매개변수를 제공하지 않으면 객체는 기본 생성자를 사용해 생성된다.
`Time now;`

이는 자바와 매우 다르다. Java에서 이 명령은 단지 초기화되지 않은 참조를 생성할 뿐이다. C++에서는 실제로 객체를 구성한다.
한 객체가 다른 객체에 할당되면 실제 값의 사본이 만들어진다. Java에서 객체 변수를 복사하면 객체에 대한 두번째 참조가 설정될 뿐이다. C++객체를 복사하는 것은 Java에서 clone을 호출하는 것과 같다. 사본을 수정해도 원본이 변경되지는 않는다.

`Point q = p;`
`q.move(1, 1);`

대부분의 경우 객체가 값처럼 동작한다는 사실은 매우 편리하다. 그러나 이러한 동작이 바람직하지 않은 상황이 꽤 있다.
1. 함수에서 객체를 수정할 때는 참조로 호출을 사용해야 한다는 점
2. 두 객체 변수는 하나의 객체에 공동으로 접근할 수 없다. C++에서 이 효과가 필요한 경우 포인터를 사용해야한다.
3. 객체 변수는 특정 유형의 값만 보관할 수 있다. 변수가 다른 하위 클래스의 객체를 보관하도록 하려면 포인터를 사용해야 한다.
4. 변수가 null 또는 실제 객체를 가리키도록 하려면 C++에서 포인터를 사용해야한다.

## 기능
Java에서 모든 함수는 클래스의 인스턴스 메서드 또는 정적 함수여야 한다. C++는 클래스의 인스턴스 메서드와 정적 함수를 지원하지만, 어떤 클래스에도 속하지 않는 함수도 허용한다. 이러한 함수를 전역 함수라고 한다.
특히, 모든 C++함수는 전역 함수 main으로 시작한다.
`
int main() {
	...
}
`
명령줄 인수를 캡처하는데 사용할 수 있는 두 번째 main이 있지만 이를 사용하려면 C 스타일 배열과 문자열에 대한 지식이 필요하며, 여기서는 다루지 않겠다.

프로그램이 성공적으로 완료되면 main의 반환값은 0이고, 그렇지 않으면 0이 아닌 정수이다.
Java에서와 마찬가지로 함수 인수는 값으로 전달된다. Java에서 함수는 그럼에도 불구하고 객체를 수정할 수 있었다. 하지만 C++객체값은 실제 객체에 대한 참조가 아니기 때문에 함수는 실제 인수의 사본을 받고 따라서 원본을 수정할 수 없다.
따라서 C++에는 두 가지 매개변수 전달 매커니즘이 있다. 값에 의한 호출(Java에서처럼)과 참조에 의한 호출이다. 매개변수가 참조에 의해 전달되면 함수는 원본을 수정할 수 있다. 참조에 의한 호출은 매개변수 유형 뒤에 `&`로 표시된다.

```c
void raiseSalary(Employee& e, double by) {
	...
}

다음은 참조에 의한 호출을 활용하는 전형적인 함수다. Java에서 이러한 함수를 작성하는것은 불가능하다.

1
2
3
4
5
void swap(int& a, int& b) {
	int tmp = a;
    a = b;
    b = tmp;
}

이 함수가 swap(x, y)로 호출되면 참조 매개변수 a, b는 인수 x와 y의 위치를 참조하며, 이러한 인수의 값을 참조하지 않는다. 따라서 함수는 실제로 이러한 변수의 내용을 스왑할 수 있다. C++에서는 함수가 매개변수를 수정해야할 때 항상 참조 호출을 사용한다.

벡터

C++ 벡터 구조는 Java에서 배열과 벡터의 가장 좋은 특징을 결합한다. C++벡터는 편리한 요소 엑세스가 가능하며 동적으로 커질 수 있다. T가 임의의 유형이면 vector<T>는 T유형의 요소의 동적 배열이다.

명령어 vector<int> a; 는 처음에 비어있는 벡터를 만든다. vector<int> a(100); 는 처음에 100개의 요소를 갖는 벡터를 만든다. push_back 메서드로 더 많은 요소를 추가할 수 있다. a.push_back(n);

a.pop_back() 호출은 a에서 마지막 요소를 제거한다. size메서드를 사용해 a의 현재 요소 수를 찾는다. 익숙한 [] 연산자를 사용해 요소에 접근한다.

1
2
for(i=0; i<a.size(); i++)
    sum = sum + a[i];

Java에서와 같이 배열 인덱스는 0과 a.size() - 1 사이여야한다. 그러나 Java와 달리 합법적인 배열 인덱스에 대한 런타임 검사가 없다. 그렇기 때문에 잘못된 인덱스에 접근하면 매우 심각한 에러가 발생할 수 있다. 벡터도 마찬가지로 C++객체와 같이 값이기 때문에 한 벡터를 다르 벡터에 할당하면 모든 요소가 복사된다.

1
vector<int> b = a; // 모든 요소가 복사됨

이를 Java와 비교해보자. Java에서 배열 변수는 배열에 대한 참조다. 변수의 사본을 만들면 같은 배열에 대한 두 번째 참조가 생성된다. 이러한 이유로 벡터를 수정하는 C++함수는 참조 매개변수를 사용해야한다!

1
2
3
void sort(vector<int>& a) {
	...
}

입력 및 출력

C++에서 표준 입력 및 출력 스트림은 cin 및 cout 객체로 표현된다. << 연산자를 사용해 출력을 작성한다.

1
cout << "Hello World!";

여러 항목도 가능

1
cout << "The answer is " << x << "\n";

입력에서 숫자나 단어를 읽으려면 >> 연산자를 사용한다.

1
2
3
4
5
6
7
8
9
// 숫자
double x;
cout << "Please enter x: ";
cin >> x;

// 문자
string name;
cout << "Please enter your name: ";
cin >> name;

getline 메서드는 입력의 전체 줄을 읽는다. Java의 readLine()과 비슷하다.

1
2
string inputLine;
getline(cin, inputLine);

입력의 끝에 도달했거나 숫자를 올바르게 읽을 수 없는 경우 stream은 실패 상태로 설정된다. fail 메서드로 이를 테스트할 수 있다.

1
2
3
int n;
cin >> n;
if (cin.fail()) cout << "Bac input";

스트림 상태가 실패하면 쉽게 재설정할 수 없다. 프로그램에서 잘못된 입력을 처리해야 하는 경우 getline을 사용한 다음 수동으로 입력을 처리해야 한다.

포인터

C++에서 객체 변수는 객체 값을 보관한다. 이는 객체 변수가 다른곳에 저장된 객체 값에 대한 참조일 뿐 Java와 다르다. C++에서 동일한 배열이 필요한 상황이 있다. C++에서 객체를 참조할 수 있는 변수를 포인터라고 한다. T가 임의의 유형이면 T*는 T유형의 객체에 대한 포인터다.

Java에서와 마찬가지로 포인터 변수는 다른 포인터 변수 혹은 new 호출로 초기화될 수 있다.

1
2
3
Employee* p = NULL;
Employee* q = new Employee("Hacker, Harry", 35000);
Employee* r = q;

다른 네번째 방법은 &연산자를 사용해 다른 객체의 주소로 초기화될 수 있다.

1
2
Employee boss("Morris, Melinda", 83000);
Employee* s = &boss;

이는 좋은 방법이 아니다. C++포인터는 new로 할당된 객체만 참조해야한다.

지금까지의 C++포인터는 Java 객체 변수와 매우 유사하다. 그러나 필수적인 구문적 차이가 있다. 포인터가 가리키는 객체에 엑세스하려면 * 연산자를 적용해야한다. p가 Employee객체에 대한 포인터인 경우 *p 는 해당 객체를 참조한다.

1
2
Employee* p = ...;
Employee boss = *p;

메서드를 실행하거나 데이터 필드에 엑세스하려면 *p를 참조해야한다.

1
(*p).setSalary(91000);

여기서 괄호는 . 연산자가 *연산자보다 우선순위가 높기 때문에 필요함. C의 설계자들은 이것이 보기 흉하다는 생각에 *.을 결합하여 -> 연산자를 제공한다.

1
p->setSalary(91000);

*p 객체에서 setSalary메서드를 호출한다. 객체에서는 .연산자를 사용하는것과 포인터에서는 ->연산자를 사용하는 것을 기억하자!

포인터를 초기화하지 않거나, NULL이거나, 더이상 존재하지 않는 객체를 참조하는 경우 * 또는 ->연산자를 적용하는 것은 error다. 안타깝게도 C++ 런타임 시스템은 이러한 오류를 확인하지 않는다. 이러한 실수를 하면 프로그램들이 죽거나 불안정하게 작동할 수 있다.

Java에서는 이러한 오류가 발생할 수 없다. 초기화되지 않은 참조는 있을 수 없다. 모든 객체는 참조가 있는 한 살아있다. 따라서 삭제된 객체에 대한 참조는 있을 수 없다. 런타임 시스템은 null참조를 확인하고 null포인터에 엑세스하면 null pointer exception을 throw한다.

C++과 Java사이에는 또 다른 중요한 차이점이 있다. Java에는 더 이상 필요하지 않은 모든 객체를 자동으로 회수하는 Garbage Collector이 있다. C++에서는 프로그래머가 메모리를 관리해야한다.

객체 변수는 범위를 벗어나면 자동으로 회수된다. 그러나 new로 생성된 객체는 delete연산자로 수동으로 회수해야한다.

1
2
3
4
5
Employee p = new Emplyee("Hacker, Harry" 38000);
	.
    .
    .
delete p; /* 더이상 이 객체가 필요하지 않음 */

객체를 삭제하는 것을 잊으면 결국 모든 메모리가 소진될 수 있다. 이를 Memory Leak이라고 한다. 더 중요한 것은, 객체를 삭제한 다음 계속 사용하면 더이상 소유하지 않는 데이터를 덮어쓸 수 있다는 것이다. 재활용 스토리지를 관리하는데 사용되는 데이터 필드를 덮어쓰면 할당 메커니즘이 오작동하여 진단 및 수정이 매우 어려운 미묘한 오류가 발생할 수 있다. 이러한 이유로 C++에서 포인터 사용을 최소화하는 것이 가장 좋다!

상속

상속의 기본 구문은 Java와 C++이 비슷하다. extends 대신 : public을 사용한다. (C++는 또한 Private 상속이라는 개념을 지원하지만 그다지 유용하진 않다.) 기본적으로 함수는 C++에서 동적으로 바인딩되지 않는다. 특정 함수에 대한 동적 바인딩을 원하면 virtual로 선언해야한다.

1
2
3
4
5
6
7
class Manager : public Employee {
	public:
    	Manager(string name, double salary, string dept);
        virtualvoid print() const;
	private:
    	string department;
};

Java에서와 같이 생성자가 슈퍼클래스의 생성자를 호출하는 특수 구문이 있다. 자바는 키워드 super를 사용한다. C++에서는 서브클래스 생성자의 본문 밖에서 슈퍼클래스 생성자를 호출해야한다. Ex)

1
2
3
4
5
Manager::Manager(string name, double salary, string dept)
: Employee(name, salary) /* 슈퍼 클래스 메서드 호출 */
{
	department = dept;
}

Java는 서브클래스 메서드가 슈퍼클래스 메서드를 호출할 때 super키워드를 사용한다. C++에서는 슈퍼클래스 이름과 ::연산자를 사용한다.

1
2
3
4
5
void Manager::print() const
{
	Employee::print(); /* 슈퍼클래스 메서드 호출*/
	cout << department << "\n";
}

C++ 객체 변수는 특정 유형의 객체를 보관한다. C++에서 다형성을 활용하려면 포인터가 필요하다. T* 포인터는 T의 모든 하위 클래스의 객체를 가리킬 수 있다.

1
2
3
4
5
6
7
vector<Employee*> staff;
	.
    .
    .
    for(i=0; i<staff.size(); i++) {
    	staff[i] -> print();
    }

References

Java to C++

This post is licensed under CC BY 4.0 by the author.