** 구조체 등장 이유?
: 연관 있는 데이터를 하나로 묶으면, 프로그램의 구현 및 관리가 용이
1) C++은 구조체 안에 함수와 enum 상수의 사용이 가능함
#include <iostream>
using namespace std;
struct Car
{
// 구조체 안에 enum 상수의 선언
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
// 구조체 변수의 선언
char gamerID[ID_LEN]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재속도
// 구조체 안에 함수 삽입
void ShowCarState()
{
cout << "소유자ID: " << gamerID << endl;
cout << "연료량: " << fuelGauge << "%" << endl;
cout << "현재속도: " << curSpeed << "km/s" << endl << endl;
}
void Accel()
{
if (fuelGauge <= 0)
return;
else
fuelGauge -= FUEL_STEP;
if (curSpeed + ACC_STEP >= MAX_SPD)
{
curSpeed = MAX_SPD;
return;
}
curSpeed += ACC_STEP;
}
void Break()
{
if (curSpeed < BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= BRK_STEP;
}
};
int main()
{
Car run99 = { "run99", 100, 0 };
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
cout << endl;
Car run77 = { "run77", 100, 0 };
run77.Accel();
run77.Break();
run77.ShowCarState();
return 0;
}
2) C++의 함수는 외부로 뺄 수 있음
#include <iostream>
using namespace std;
struct Car
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
char gamerID[ID_LEN];
int fuelGauge;
int curSpeed;
// 함수는 외부로 뺄 수 있다
void ShowCarState();
void Accel();
void Break();
};
void Car::ShowCarState()
{
cout << "소유자ID: " << gamerID << endl;
cout << "연료량: " << fuelGauge << "%" << endl;
cout << "현재속도: " << curSpeed << "km/s" << endl << endl;
}
void Car::Accel()
{
if (fuelGauge <= 0)
return;
else
fuelGauge -= FUEL_STEP;
if (curSpeed + ACC_STEP >= MAX_SPD)
{
curSpeed = MAX_SPD;
return;
}
curSpeed += ACC_STEP;
}
void Car::Break()
{
if (curSpeed < BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= BRK_STEP;
}
int main()
{
Car run99 = { "run99", 100, 0 };
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
return 0;
}
** 구조체 안에 함수가 정의되어 있으면, 함수를 인라인으로 처리
ㄴ 함수를 외부로 빼면 이러한 의미가 사라지므로, 키워드 inline을 이용하여 인라인 처리를 명시적으로 지시
** C++의 구조체는 클래스 일종
[차이점]
1) 키워드를 struct를 대신해서 class를 사용하면, 구조체가 아닌 클래스가 됨
2) 접근제어 지시자를 선언하지 않았을 때, 클래스는 private으로 구조체는 public으로 선언
** 접근제어 지시자(접근제어 레이블)
1) public : 어디서든 접근허용
2) protected : 상속관계에 놓여있을 때, 유도(=자식) 클래스에서의 접근 허용
3) private : 클래스 내에서만 접근 허용
** 레이블
: 접근제어 지시자의 뒤에는 세미콜론이 아닌 콜론이 붙는데,
이는 접근제어 지시자가 특정 위치 정보를 알리는 '레이블(라벨)'이기 때문
우리가 알고 있는 switch문에 사용되는 case도 레이블이기 때문에 콜론이 붙음
** 클래스를 구성하는 변수와 함수를 '멤버변수'와 '멤버함수'라고 부름
▼ Car.h : 헤더파일, 클래스의 선언을 담는다, 클래스의 선언(declaration)
#ifndef __CAR_H__
#define __CAR_H__
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
class Car
{
private:
char gameID[CAR_CONST::ID_LEN];
int fuelGauge;
int curSpeed;
public:
void InitMembers(const char* ID, int fuel);
void ShowCarState();
void Accel();
void Break();
};
#endif // !__CAR_H__
▼ Car.cpp : 소스파일, 클래스의 정의(멤버함수의 정의)를 담는다, 클래스의 정의(definition)
#include <iostream>
#include <cstring>
#include "Car.h"
using namespace std;
void Car::InitMembers(const char* ID, int fuel)
{
strcpy_s(gameID, ID);
fuelGauge = fuel;
curSpeed = 0;
}
void Car::ShowCarState()
{
cout << "소유자ID: " << gameID << endl;
cout << "연료량: " << fuelGauge << "%" << endl;
cout << "현재속도: " << curSpeed << "km/s" << endl << endl;
}
void Car::Accel()
{
if (fuelGauge <= 0)
return;
else
fuelGauge -= CAR_CONST::FUEL_STEP;
if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
{
curSpeed = CAR_CONST::MAX_SPD;
return;
}
curSpeed += CAR_CONST::ACC_STEP;
}
void Car::Break()
{
if (curSpeed < CAR_CONST::BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= CAR_CONST::BRK_STEP;
}
▼ Main.cpp
#include "Car.h"
int main(void)
{
Car run99;
run99.InitMembers("run99", 100);
run99.Accel();
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
return 0;
}
** 인라인 함수는 헤더파일에 함께 넣어야 함
ㄴ 컴파일 과정에서 함수의 호출 문이 있는 곳에 함수의 몸체 부분이 삽입되어야 하므로
** 객체지향 프로그래밍
: 현실에 존재하는 사물과 대상, 그래도 그에 따른 행동을 있는 그대로 실체화시키는 형태의 프로그래밍
** 객체는 하나 이상의 상태 정보(데이터)와 하나 이상의 행동(기능)으로 구성
** 클래스 기반의 두 가지 객체 생성 방법
1) 일반적인 변수의 선언방식
ClassName objName;
2) 동적 할당방식(힙 할당방식)
ClassName * ptrObj = new ClassName
** 객체 간의 대화 방법 - Message Passing, 메시지 전달
: 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 방법은 함수 호출을 기반
** 정보은닉(Information Hiding)
: 멤버변수를 private으로 선언하고,
해당 변수에 접근하는 함수를 별도로 정의하여, 안전한 형태로 멤버 변수의 접근을 유도
ㄴ 엑세스 함수(access function) : GetXXX, SetXXX
** const 함수
: 이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다
** 캡슐화(Encapsulation)
: 관련 있는 함수와 변수를 하나의 클래스 안에 묶는 것
** 생성자(Constructor)
: 객체 생성시 딱 한번 호출
ㄴ 특징
① 클래스의 이름과 함수의 이름이 동일
② 반환형 선언되어 있지 않으며, 실제로는 반환하지 않음
ㄴ 디폴트 생성자(Default Constructor) : 객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 함
+ 멤버 이니셜라이저(Member Initializer)를 이용한 멤버 초기화
: 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성
const 멤버변수도 이니셜라이저를 이용하면 초기화가 가능
#include <iostream>
using namespace std;
class FruitSeller
{
private:
const int APPLE_PRICE;
int numOfApples;
int myMoney;
public:
FruitSeller(int price, int num, int meney)
: APPLE_PRICE(price), numOfApples(num), myMoney(meney)
{}
int SaleApple(int money)
{
int num = money / APPLE_PRICE;
numOfApples -= num;
myMoney += money;
return num;
}
void ShowSalesResult() const
{
cout << "남은 사과" << numOfApples << endl;
cout << "판매 수익" << myMoney << endl << endl;
}
};
class FruitBuyer
{
private:
int myMoney;
int numOfApples;
public:
FruitBuyer(int meney) : myMoney(meney), numOfApples(0)
{}
void BuyApples(FruitSeller& seller, int money)
{
numOfApples += seller.SaleApple(money);
myMoney -= money;
}
void ShowBuyResult() const
{
cout << "현재 잔액" << myMoney << endl;
cout << "사과 개수" << numOfApples << endl << endl;
}
};
int main(void)
{
FruitSeller seller(1000, 20, 0);
FruitBuyer buyer(5000);
buyer.BuyApples(seller, 2000);
cout << "과일 판매자의 현황" << endl;
seller.ShowSalesResult();
cout << "과일 구매자의 현황" << endl;
buyer.ShowBuyResult();
return 0;
}
** 소멸자(Destructor)
: 객체 소멸시 반드시 호출
ㄴ 특징
① 클래스의 이름 앞에 '~'가 붙은 형태의 이름을 갖음
② 반환형이 선언되지 있지 않으며, 실제로 반환하지 않음
③ 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩도, 디폴트 값 설정도 불가능
** this 포인터
: 멤버함수 내에서는 this라는 이름의 포인터를 사용할 수 있는데,
이는 객체 자기 자신을 가리키는 용도로 사용되는 포인터
public:
void ThisFunc(int num)
{
this->num = 207;
num = 105; // 매개변수의 값을 105로 변경
}
** 복사 생성자(copy constructor)
: 한 객체의 내용을 다른 객체로 복사하여 생성된 생성자
ㄴ 복사 생성자를 정의하지 않으면,
멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입됨
#include <iostream>
using namespace std;
class SoSimple
{
private:
int num1;
int num2;
public:
SoSimple(int n1, int n2) : num1(n1), num2(n2)
{}
// 객체로 받는 생성자
// 이니셜라이저를 이용해 멤버 대 멤버의 복사를 진행
// 아래의 생성자를 가리켜 복사 생성자(copy constructor)라고 부른다
SoSimple(SoSimple& copy) : num1(copy.num1), num2(copy.num2)
{
cout << "Called SoSimple(SoSimple ©)" << endl;
}
void ShowSimpleData()
{
cout << num1 << endl;
cout << num2 << endl;
}
};
int main(void)
{
SoSimple sim1(15, 30);
cout << "생성 및 초기화 직전" << endl;
SoSimple sim2 = sim1; // SoSimple sim2(sim1); 으로 변환
// 즉, 묵시적 형변환
// 이를 방지하기 위해 키워드 explicit를 이용하면
// 더이상 대입연산자로 사용 불가능
cout << "생성 및 초기화 직후" << endl;
sim2.ShowSimpleData();
return 0;
}
https://velog.io/@sjongyuuu/C-%EB%B3%B5%EC%82%AC-%EC%83%9D%EC%84%B1%EC%9E%90Copy-Constructor
** 위의 예시와 디폴트 복사 생성자는 얕은 복사(shallow copy)
: 멤버 대 멤버를 단순히 복사함
ㄴ 멤버 변수가 힙의 메모리 공간을 참조하는 경우에 문제됨
[어떤 문제?] 하나의 문자열을 두 개의 객체가 공유하는데
만약에, 둘 중 하나의 객체를 소멸해야 하는데
그 과정에서 문자열이 소멸되는 경우가 만약에 생기게 된다면..?
혹은, 둘 중 하나의 객체의 문자열에만 내용을 바꿔야 한다면...?
#include <iostream>
using namespace std;
class Human
{
public:
int age;
char* name;
Human(int n1, const char* s) : age(n1)
{
name = new char[strlen(s) + 1];
strcpy_s(name, strlen(s) + 1, s);
}
Human(Human& copy) : age(copy.age), name(copy.name)
{ }
void ShowSimpleData()
{
cout << "나이 : " << age << ", 나이 주소: " << &age << " // 이름: " << name << ", 이름 주소: " << &name << endl;
}
};
int main(void)
{
Human sim1(15, "Shine");
Human sim2 = sim1;
////////////////////////////////////////////////////
sim1.ShowSimpleData();
sim2.ShowSimpleData();
cout << "=================" << endl;
////////////////////////////////////////////////////
const char* changeName = "Shine Muscat";
strcpy_s(sim1.name, strlen(changeName) + 1, changeName);
sim1.age = 100;
////////////////////////////////////////////////////
sim1.ShowSimpleData();
sim2.ShowSimpleData();
////////////////////////////////////////////////////
cout << endl << endl;
cout << "sim1 주소 : " << &sim1 << " // sim2 주소 : " << &sim2 << endl;
return 0;
}
** 깊은 복사(deep copy)
: 멤버변수가 참조하는 문자열까지 복사를 해서 각각의 객체가 완전히 별개의 문자열을 소유
Human(const Human& copy) : age(copy.age)
{
name = new char[strlen(copy.name) + 1];
strcpy(name, copy.name)
}
** 메모리 공간 할당과 초기화가 동시에 일어나는 상황
1) num1이라는 이름의 메모리 공간 할당과 동시에 num2에 저장된 값으로 초기화 하는 문장
int num1 = num2;
2) SimpleFunc 함수가 호출되는 순간에 매개변수 n이 할당과 동시에 변수 num에 저장되어 있는 값으로 초기화
int SimpleFunc(int n)
{
...
}
int main(void)
{
int num = 10;
SimpleFunc(num); // 호출되는 순간 매개변수 n이 할당과 동시에 초기화
...
}
3) 함수가 값을 반환하면, 별도의 메모리 공간에 할당되고, 이 공간에 반환 값이 저장(반환 값으로 초기화)
int SimpleFunc(int n)
{
...
return n; // 반환하는 순간 메모리 공간에 할당되면서 동시에 초기화
}
int main(void)
{
int num = 10;
cout << SimpleFunc(num) << endl;
...
}
** 복사 생성자가 호출되는 시점은?
1) 기존에 생성된 객체를 이용해서 새로운 객체를 초기화하는 경우
2) Call-by-value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우
3) 객체를 반환하되, 참조형으로 반환하지 않는 경우
** 임시객체가 사라지는 타이밍
1) 다음 행으로 넘어가면 바로 소멸
Temporary(100);
2) 참조자에 참조되는 임시객체는 바로 소멸하지 않음
const Temporary &ref = Temporary(100);
** const 객체
: 이 객체의 데이터 변경을 허용하지 않음
ㄴ const의 선언 유무도 함수 오버로딩의 조건에 해당
(예) void SimpleFunc() { ... }
void SimpleFunc() const { ... }
#include <iostream>
using namespace std;
class SoSimple
{
private:
int num;
public:
SoSimple(int n) : num(n)
{}
SoSimple& AddNum(int n)
{
num += n;
return *this;
}
void SimpleFunc()
{
cout << "SimpleFunc " << num << endl;
}
void SimpleFunc() const
{
cout << "const SimpleFunc " << num << endl;
}
};
void YourFunc(const SoSimple& obj)
{
obj.SimpleFunc();
}
int main(void)
{
SoSimple obj1(2);
const SoSimple obj2(7);
obj1.SimpleFunc();
obj2.SimpleFunc();
YourFunc(obj1);
YourFunc(obj2);
}
** friend
: A 클래스가 B 클래스를 대상으로 friend 선언을 하면, B 클래스는 A 클래스의 private 맴버에 직접 적용 가능
ㄴ 정보은닉의 규칙을 깨기 때문에 지나치면 아주 위험할 수 있으니 꼭 필요할 때 소극적으로 사용해야 함
ㄴ 전역함수를 대상으로 사용 가능
** static
1) 전역변수에 선언 - C 에서 배움
: 선언된 파일 내에서만 참조를 허용
2) 함수 내에서 선언 - C 에서 배움
: 한번만 초기화되고, 지역변수와 달리 함수를 빠져나가도 소멸하지 않음
3) static 멤버변수(= 클래스 변수)
: 일반적인 멤버변수와 달리 클래스당 하나씩만 생성
4) static 멤버함수
: 선언된 클래스의 모든객체가 공유
public 으로 선언이 되면, 클래스의 이름을 이용해서 호출 가능
객체의 맴버로 존재하는 것이 아님
5) const static 멤버
: 클래스 내에 선언된 const 멤버변수(상수)의 초기화는 이니셜라이저를 통해서만 가능
** 키워드 mutable
: const 함수 내에서의 값의 변경을 예외적으로 허용
** 해당 글은 윤성우의 열혈 C++ 프로그래밍 도서를 읽고 정리한 글입니다.
'IT공부 > IT서적' 카테고리의 다른 글
[윤성우 열혈 C++프로그래밍] Part4. 객체지향의 완성 - 1 (0) | 2024.09.21 |
---|---|
[윤성우 열혈 C++프로그래밍] Part3. 객체지향의 전개 (0) | 2024.09.14 |
[윤성우 열혈 C++프로그래밍] Part1. c++로의 전환 (0) | 2024.08.08 |
[윤성우 열혈 C프로그래밍] Part4. C언어의 깊은 이해 (0) | 2024.07.28 |
[윤성우 열혈 C프로그래밍] Part3. 포인터와 배열의 완성 (0) | 2024.07.24 |