C++은 객체지향 프로그래밍언어이다. 그 첫걸음으로 class에 대해 배워보겠다.
class없는 성적 관리 프로그램 코드이다.
#include <iostream>
#include <string>
using namespace std;
//3 과목의 평균을 구하는 함수
double getAvg(int kor, int eng, int math)
{
return (kor + eng + math ) / 3.0f;
}
//두 개의 수중 최대값을 반환하는 함수
int maxNum(int num1, int num2)
{
if(num1 >= num2) return num1;
else return num2;
}
//3과목중 가장 높은 점수를 반환하는 함수
int getMax(int kor, int eng, int math)
{
return maxNum( maxNum(kor, eng), math);
}
int main()
{
int kor[3];
int eng[3];
int math[3];
//점수 입력
for(int i = 0 ; i < 3; i++)
{
cin >> kor[i] >> eng[i] >> math[i];
}
//각 학생의 평균 점수와 과목 최대 점수를 출력
for(int i = 0 ; i < 3; i++)
{
cout << getAvg(kor[i], eng[i], math[i]) << endl;
cout << getMax(kor, eng, math) << endl;
}
return 0;
}
이 코드에는 어떤 문제가 있을까?
1. 과목의 개수가 늘어나면 함수의 호출 방식이 변경된다. 즉 기존의 코드는 동작x
2. 성적 관리를 하는 데이터가 모두 노출되어 있다. 사용자의 다른 코드와 혼용되어서 문제를 발생시킬 수 있음.
자동차를 생각해보자. 브레이크를 밟으면 차가 멈추고, 페달을 밟으면 가속이 된다는 걸 우리는 알고 있다. 우리는 운전 할 때 이것만 알면 크게 무리가 없다. 브레이크의 세부 스펙을 알필요는 없다.
class도 동일하다. 우리는 동작을 공개하고 세부 데이터는 공개하지 않는다. 그래야 변화에 유연하게 대처가 가능하기 때문이다.
class는 크게 동작과 데이터로 구성된다. 위 성적 관리 프로그램을 기준으로 동작과 데이터를 나눠보면 아래와 같다.
1 동작
•가장 큰 점수 반환/ 과목의 평균 계산
2 데이터
•각 과목의 점수
코드로 한번 학생 클래스를 정의 해보자.
class Student
{
//동작 정의(이를 멤버함수라고 한다.)
double getAvg();
int getMaxNum();
//데이터 정의(이를 멤버변수라고 한다.)
int kor[3];
int eng[3];
int math[3];
};
#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>
using namespace std;
class Student
{
//동작 정의(이를 멤버함수라고 한다)
double getAvg()
{
return (kor + eng + math ) / 3.0;
}
int getMax()
{
return max(max(kor, eng), math);
}
//데이터 정의(이를 멤버변수라고 한다.)
int kor;
int eng;
int math;
};
#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>
using namespace std;
class Student
{
//동작 정의(이를 멤버함수라고 한다)
double getAvg();
int getMaxScore();
//데이터 정의(이를 멤버변수라고 한다.)
int kor;
int eng;
int math;
};
Student::getAvg()
{
return (kor + eng + math) / 3.0;
}
Student::getMax()
{
return max(max(kor, eng), math);
}
class는 사용자에게 접근을 허용여부 제어가 가능하다. 접근 제어는 크게 public,private가 있다.
아무것도 작성하지 않으면 접근 제어는 private가 된다.
private는 외부에서 접근 시 에러가 발생한다.
#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>
using namespace std;
class Student
{
//동작 정의(이를 멤버함수라고 한다)
double getAvg();
int getMaxScore();
//데이터 정의(이를 멤버변수라고 한다.)
int kor;
int eng;
int math;
};
double Student::getAvg()
{
return (kor + eng + math) / 3.0;
}
int Student::getMaxScore()
{
return max(max(kor, eng), math);
}
int main()
{
Student s;
s.getAvg();
return 0;
}

private멤버에 액세스할 수 없다고 뜬다. 당연하다.
#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>
using namespace std;
class Student
{
public:
//동작 정의(이를 멤버함수라고 한다)
double getAvg();
int getMaxScore();
private:
//데이터 정의(이를 멤버변수라고 한다.)
int kor;
int eng;
int math;
};
double Student::getAvg()
{
return (kor + eng + math) / 3.0;
}
int Student::getMaxScore()
{
return max(max(kor, eng), math);
}
int main()
{
Student s;
s.getAvg();
return 0;
}

오류가 나지않는다. 코드에서 오류가 발생하지 않는 이유는 Student 클래스의 getAvg()와 getMaxScore() 메서드가 클래스 내부에서 정의된 멤버 함수이기 때문이다. C++에서 private 접근 지정자는 같은 클래스 내부에서만 접근할 수 있도록 제한한다. 하지만 멤버 함수는 클래스 내부에 정의되므로 private 멤버 변수에 접근할 수 있다.
그렇다면 private에 있는 변수를 제어해야 할 때는 어떤 방법을 사용할까?
이 때 사용 하는게 getter와 setter이다.
멤버변수를 바꿀 때 setter를, 값을 가져올 때 getter를 사용한다.
#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>
using namespace std;
class Student
{
public:
//동작 정의(이를 멤버함수라고 한다)
double getAvg();
int getMaxScore();
void setMathScore(int math)
{
this->math = math;
}
void setEngScore(int eng)
{
this->eng = eng;
}
void setKorScore(int kor)
{
this->kor = kor;
}
int getMathScore() { return math; }
int getEngScore() { return eng; }
int getKorScore() { return kor; }
private:
//데이터 정의(이를 멤버변수라고 한다.)
int kor;
int eng;
int math;
};
double Student::getAvg()
{
return (kor + eng + math) / 3.0;
}
int Student::getMaxScore()
{
return max(max(kor, eng), math);
}
int main()
{
Student s;
//점수 입력(변수로 해도 되지만 간단히
s.setEngScore(32);
s.setKorScore(52);
s.setMathScore(74);
//평균 최대점수 출력
cout << s.getAvg() << endl;
cout << s.getMaxScore() << endl;
return 0;
}

생성자는 객체가 생성될 때 한번 호출 된다.
보통 필요한 멤버 변수를 초기화 하거나 객체가 동작할 준비를 하기 위해 사용한다. 생성자는 반환형이 없고, class 이름과 같은 함수 형태이다.
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 기본 생성자
Person() {
name = "Unknown";
age = 0;
}
void display() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main() {
Person p; // 기본 생성자 호출
p.display();
return 0;
}

#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 매개변수가 있는 생성자
Person(string n, int a) {
name = n;
age = a;
}
void display() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main() {
Person p("Alice", 25); // 매개변수가 있는 생성자 호출
p.display();
return 0;
}

#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 기본 매개변수가 있는 생성자
Person(string n = "DefaultName", int a = 18) {
name = n;
age = a;
}
void display() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main() {
Person p1; // 기본값 사용
Person p2("Bob", 30); // 값을 지정
p1.display();
p2.display();
return 0;
}

#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 매개변수가 있는 생성자
Person(string n, int a) {
name = n;
age = a;
}
};
int main() {
Person p("Tom"); // 에러: 생성자에 필요한 매개변수 부족
// 컴파일 에러: "no matching function for call to 'Person::Person(const char [4])'"
// 매개변수 두 개를 요구하는 생성자에 하나의 매개변수만 전달하여 매칭되지 않음
p.display();
return 0;
}

Person의 매개변수는 string타입의 n과 int타입 a 두개인데 main()함수에서 Person p("Tom");즉 string타입의 n만 매개변수 전달로 오류가난다. 또한 Person클래스에 display메서드도 정의되어 있지않기때문에 오류가난다.
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 생성자를 선언만 하고 정의하지 않음
Person(string n, int a);
};
int main() {
Person p("Alice", 25); // 선언된 생성자의 정의가 없으므로 컴파일 에러 발생
cout << "Name: " << p.name << ", Age: " << p.age << endl;
return 0;
}

#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
void temp() {}
// 생성자를 선언만 하고 정의하지 않음
Person(string n, int a) {}
};
int main() {
Person p("a", 30);
Person p2;
p.temp();
p2.temp();
return 0;
}

이제 Student에 생성자를 적용해보자.
어차피 3과목의 점수는 무조건 있어야 한다.
하지만. 경우에 따라서는 3과목 점수를 모두 입력하고 싶지 않을 때가 있다. 생성자는 객체가 생성되는 순간 한번 호출됨.
#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>
using namespace std;
class Student
{
public:
//값이 주어지지 않을경우 기본값을 할당하는 생성자
Student(int math = 32, int eng = 17, int kor = 52)
{
this->math = math;
this->eng = eng;
this->kor = kor;
}
double getAvg();
int getMaxScore();
//동작 정의(이를 멤버함수라고 합니다)
void setMathScore(int math)
{
math = math;
//this.math = math;와 동일
}
void setEngScore(int eng)
{
eng = eng;
//this.eng = eng;와 동일
}
void setKorScore(int math)
{
math = math;
//this.math = math;와 동일
}
int getMathScore() { return math; }
int getEngScore() { return eng; }
int getKorScore() { return kor; }
private:
//데이터 정의(이를 멤버변수라고 합니다.)
int kor;
int eng;
int math;
};
double Student::getAvg()
{
return (kor + eng + math) / 3.0;
}
int Student::getMaxScore()
{
return max(max(kor, eng), math);
}
int main()
{
Student s;
//아래와 같이 사용할 수도 있음
//Student s(1);
//Student s(1, 2);
//Student s(32, 52, 74);
//평균 최대점수 출력
cout << s.getAvg() << endl;
cout << s.getMaxScore() << endl;
return 0;
}

지금까지 Student class를 구현해봤다 지금까지 모든 class를 하나의 파일에 구현했는데 말씀드렸다시피 내부구현은 사용자가 알필요 없기 때문에 include해서 사용할 수 있는 방식으로 구현해서 제공 한다.
글도 마찬가지다. 소설을 쓰는데 한문장으로 200페이지를 쓰는 경우는 없다. 코드도 한 파일에만 작성하지 않고 파일을 나눠서 작성한다.
헤더 파일과 소스 파일로 크게 나뉠 수 있으며 보통 헤더 파일에 선언을 하고, 소스 파일엔 구현을 한다. 이전에 구현했던 Student클래스를 기준으로 알아보겠다. 최종적으로 파일의 구조는 아래 처럼 될 것이다.
#ifndef STUDENT_H_
#define STUDENT_H_
class Student
{
public:
//값이 주어지지 않을경우 기본값을 할당하는 생성자
Student(int math = 32, int eng = 17, int kor = 52)
{
this->math = math;
this->eng = eng;
this->kor = kor;
}
double getAvg();
int getMaxScore();
//동작 정의(이를 멤버함수라고 합니다)
void setMathScore(int math)
{
this->math = math;
}
void setEngScore(int eng)
{
this->eng = eng;
}
void setKorScore(int math)
{
this->math = math;
}
int getMathScore() { return math; }
int getEngScore() { return eng; }
int getKorScore() { return kor; }
private:
//데이터 정의(이를 멤버변수라고 합니다.)
int kor;
int eng;
int math;
};
#endif
class를 헤더 파일에 정의할 때 가장 중요한 것은 class가 중복 선언되지 않게 하는 것 이다. 내가 만든 헤더 파일을 여러 파일에서 사용 하다 보면 class가 여러번 정의 될 수 있다. 이를 방지 하기 위해서 #ifndef 구문을 활용 한다.
#include "Student.h"
#include <algorithm> // max 함수
using namespace std;
double Student::getAvg()
{
return (kor + eng + math) / 3.0;
}
int Student::getMaxScore()
{
return max(max(kor, eng), math);
}
소스 파일에서 정의한 멤버함수중 아직 구현되지 않은 부분을 구현하면 된다. 이 때 당연히 구현한 헤더파일을 include 해줘야 한다.
1. 정의된 class가 포함된 Student.h를 include한다.
본인이 만든 헤더 파일을 double quote로(큰 따옴표) C++에서 제공하는 헤더 파일은 <>로 포함한다.
2. 멤버 함수를 정의할때는 클래스 이름::멤버함수와 같이 하고 정의 하면 된다.
이제 class의 정의와 구현이 끝났으므로 실제 main함수에서 사용해보자.
정의된 class가 포함된 Student.h를 include한다.
#include <iostream>
#include "Student.h"
using namespace std;
int main()
{
Student s;
Student s2(1);
Student s3(1,2);
Student s4(32,52,74);
//평균 최대점수 출력
cout << s.getAvg() << endl;
cout << s.getMaxScore() << endl;
return 0;
}

지금까지 배운 내용으로 두 문제정도 풀어볼려고한다.
C++로 간단한 배터리 관리 클래스를 만들어보자.
• 기능
-배터리 잔량(0~100)을 관리한다.
-배터리 사용 및 충전을 할 수 있어야 한다.
-초기 배터리는 0이다.
• 구현할 메서드
-배터리 잔량을 반환하는 메서드
-배터리를 사용하는 메서드(한번 호출될때마다 배터리 잔량 5씩 감소)
-배터리를 충전하는 메서드(한번 호출될때마다 배터리 잔량은 7씩 충전)
• 초기 상태
배터리 잔량의 기본값은 100이다. 해당 값은 생성자에서 설정할 수 있어야 한다.
아래는 완성된 모습
[배터리 관리 클래스 시나리오]
1. 프로그램 시작 시, 초기 배터리 잔량은 100%로 설정됩니다.
2. 배터리를 두 번 사용하여 각 사용 시 5%씩 잔량이 감소합니다.
- 첫 번째 사용 후 잔량: 95%
- 두 번째 사용 후 잔량: 90%
- 배터리를 한 번 충전하여 7% 잔량이 증가합니다.
- 충전 후 잔량: 97%
- 다시 배터리를 한 번 사용하여 잔량이 5% 감소합니다.
- 사용 후 잔량: 92%
- 각 단계에서 배터리 잔량의 변화가 출력됩니다.
위 시나리오대로 실행했을 때 출력 값은 아래와 같습니다.
Initial charge: 100%
Battery used. Current charge: 95%
Battery used. Current charge: 90%
Battery charged. Current charge: 97%
Battery used. Current charge: 92
#ifndef BATTERY_H_
#define BATTERY_H_
class Battery
{
private:
int charge; // 배터리 잔량
public:
// 초기화 리스트를 사용한 생성자: 기본값은 100으로 설정
Battery(int initialCharge = 100): charge(initialCharge) {
if (charge < 0) {
charge = 0;
}
else if (charge > 100) {
charge = 100;
}
}
// 배터리 사용
void useBattery();
// 배터리 충전
void chargeBettery();
//배터리 잔량 반환
int getCharge() const{
return charge;
}
};
#endif
#include "Battery.h"
#include <iostream>
using namespace std;
void Battery::useBattery() {
if (charge >= 5) { // 배터리가 5퍼이상이면 5를뺌
charge -= 5;
}
else { // 5미만이면 0
charge = 0;
}
cout << "Battery used. Current charge: " << charge << "%" << endl; // 문구 출력
}
void Battery::chargeBettery() {
if (charge <= 93) { // 배터리가 93퍼 이하면 7을 더함
charge += 7;
}
else {
charge = 100; // 배터리가 93퍼 미만이면 100%
}
cout << "Battery charged. Current charge: " << charge << "%" << endl; // 문구 출력
}
#include <iostream>
#include "Battery.h"
using namespace std;
int main()
{
Battery battery;
cout << "Initial charge: " << battery.getCharge() << endl;
battery.useBattery();
battery.useBattery();
battery.chargeBettery();
battery.useBattery();
return 0;
}
배터리 헤더파일을 확인하면 생성자를 좀 다르게 선언한다 이게 무엇이냐 하면 초기화 리스트를 사용한 생성자이다.
초기화리스트 초기화 리스트에 관한 내용이다.

C++로 간단하게 두 분수의 곱셈을 하는 클래스를 만들어 보자.
• - Fraction 클래스를 만들어 분수를 표현 하세요
- 분자는 numerator, 분모는 denominator로 관리 합니다.
- 두 분수의 곱의 결과는 기약분수로 출력 합니다.
• 구현할 메서드
- 기본 생성자를 만드세요(numerator = 0 / denominator = 1 )
- 분자의 분모의 수를 입력받는 생성자를 구현하세요
- 배터리 잔량을 반환하는 메서드
- 현재 분수를 출력하는 함수 display를 를 구현하세요
- 기약분수 형태로 만드는 함수 simplify를 구현하세요 이 때 최대공약수를 구하는 함수가 필요할 수 있는데 아래를 참조하세요
아래는 완성된 모습이다.
입력 시나리오:
- 입력은 사용자로부터 직접 받지 않으며, 프로그램에 하드코딩된 값으로 실행됩니다.
- Fraction f1(1, 2): 첫 번째 분수는 1/2로 설정됩니다.
- Fraction f2(3, 4): 두 번째 분수는 3/4로 설정됩니다.
출력:
곱한 결과: 3/8
#ifndef FRACTION_H_
#define FRACTION_H_
// Fraction클래스 정의
class Fraction
{
private:
int numerator; // 분자
int denominator; // 분모
int gcd(int a, int b); // gcd 함수는 Fraction 클래스 내부에서만 사용되므로 private으로 설정
public:
Fraction() : numerator(0), denominator(1) {} // 기본 생성자, 분자를0, 분모를1로 초기화.(0/1은 0을 나타냄)
Fraction(int num, int denom); // 매개변수가 있는 생성자 사용자로부터 분자와 분모를 입력받아 초기화한다.
void display(); // 현재 분수를 보여주는 함수
void simplify(); // 기약분수로 변환하는 함수
Fraction multiply(const Fraction& other) const; // 두 분수를 곱하는 함수
};
#endif
#include "Fraction.h"
#include <iostream>
using namespace std;
Fraction::Fraction(int num, int denom)
: numerator(num), denominator(denom) {
if (denom == 0) {
throw std::invalid_argument("Denominator cannot be zero.");
}
}
int Fraction::gcd(int a, int b) { //최대 공약수를 계산하는 유클리드 호제법 구현
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a; // b가0이 되었을때 최대공약수
}
void Fraction::simplify(){
int gcd = Fraction::gcd(numerator, denominator); // 분자와 분모의 최대공약수 계산
numerator /= gcd; // 분자를 최대공약수로 나눔
denominator /= gcd; // 분모를 최대공약수로 나눔
}
void Fraction::display() {
cout << numerator << "/" << denominator << endl;
}
Fraction Fraction::multiply(const Fraction& other) const{
// 새로운 분자
int newNumerator = numerator * other.numerator;
// 새로운 분모
int newDenominator = denominator * other.denominator;
// 새로운 분수
Fraction newFraction = Fraction(newNumerator, newDenominator);
// 새로운 분수를 기약분수로 만듦
newFraction.simplify();
return newFraction;
}
#include <iostream>
#include "Fraction.h"
using namespace std;
int main()
{
// 두 분수 생성
Fraction f1(1, 2); // 첫 번째 분수: 1/2
Fraction f2(3, 4); // 두 번째 분수: 3/4
// 두 분수를 곱하기
Fraction product = f1.multiply(f2); // f1 * f2
// 결과 출력
cout << "곱한 결과: ";
product.display(); // 결과 분수 출력
cout << endl;
return 0;
}

오늘은 클래스에대해 공부해보았다. 헤더파일 소스파일 메인소스파일 이렇게 구현하는게 습관이 안되어있다보니 조금 애를 먹은 것 같다. 그냥 메인소스파일에다가 전부 다 구현하던 습관이있어서.. 하지만 좋은 개발자가 되기위해서는 이렇게 나누어서 구현하는게 좋으니 계속 연습해서 익숙해지는게 목표이다.