static 클래스 멤버
static 클래스 멤버는 그 클래스의 모든 객체가 공유한다.
객체 생성과 관계없이 존재하고 접근할 수 있습니다.
static 멤버, 사용하는 이유
- 객체 간 데이터 공유: 모든 객체가 공통으로 사용해야 하는 데이터를 저장하고 관리하는 데 유용합니다. 예를 들어, 프로그램에서 생성된 객체의 총 개수를 세거나, 모든 객체가 접근해야 하는 설정 정보를 저장할 때 사용할 수 있습니다.
- 객체 생성 없이 접근 가능: static 멤버는 객체를 생성하지 않고도 클래스 이름을 통해 직접 접근할 수 있습니다. 이는 객체 생성 없이도 특정 기능을 수행해야 할 때 유용합니다.
- 메모리 효율성: static 멤버 변수는 모든 객체가 공유하기 때문에 각 객체마다 별도의 메모리를 할당하지 않아도 됩니다. 이는 메모리 사용량을 줄이는 데 도움이 됩니다.
#include<iostream>
class Counter {
public:
static int count; // static 멤버 변수 선언
Counter() {
count++;
}
};
int Counter::count = 0; // static 멤버 변수 정의
int main() {
Counter c1, c2, c3;
std::cout << "생성된 객체 수: " << Counter::count << std::endl; // static 멤버 변수 접근
return 0;
}
- Counter 클래스는 count라는 static 멤버 변수를 가지며, 생성자에서 count 값을 1씩 증가시킵니다.
- main 함수에서 Counter 객체를 3개 생성합니다.
- Counter::count를 통해 static 멤버 변수에 접근하여 생성된 객체 수를 출력합니다.
static 멤버, 주의할 점
- 초기화: static 멤버 변수는 클래스 외부에서 반드시 초기화해야 합니다.
- 접근 범위: static 멤버는 접근 지정자(public, private, protected)에 따라 접근 범위가 제한될 수 있습니다.
- 상속: static 멤버는 상속되지만, 파생 클래스에서 재정의할 수 없습니다.
** static 데이터 멤버는 클래스 선언 안에서 선언되지만, 메서드 파일에서 초기화된다. 그 static 클래스 멤버가 어느 클래스에 속하는지 나타내기 위해서 초기화할 때 사용 범위 결정 연산자(::)를 사용한다. 그러나 static 멤버가 정수형이나 열거형의 const이면 클래스 선언자체에서 초기화할 수 있다.
동적 메모리 와 클래스
사용할 메모리의 크기를 컴파일할 때 결정하지 않고 프로그램을 실행할 때 상황에 따라 융퉁성 있게 결정하는 것이다.
다시말해, 메모리 사용을 기억 공간들의 확정된 규칙에 따르지 않고 프로그램의 요구에 맞출 수 있다.
메모리를 동적으로 제어하기 위해서, new와 delete 연산자를 사용한다.
▶▶▶ new와 delete 연산자
new 연산자
** 클래스 안에서 접근제어로 생성자가 만들어지고, 클래스 객체생성 시 new로 메모리 할당한다.
** new연산 시 힙메모리에 접근하여 저장하기 위해서는 *(애스터리스크)를 사용하여 포인터 변수에 담아 저장한다.
[기능]
● 힙 메모리에 동적으로 메모리를 할당합니다.
● 객체의 경우 생성자를 자동으로 호출합니다.
[사용법]
int형 변수의 할당 int * ptr1 = new int;
double형 변수의 할당 double * ptr2 = new double;
길이가 3인 int형 배열의 할당 int * arr1 = new int[3];
길이가 7인 double형 배열의 할당 double * arr2 = new double[7];
[특징]
● 할당 실패 시 std::bad_alloc 예외를 던집니다.
● 초기화도 동시에 수행할 수 있습니다: int* p = new int(5);
delete 연산자
** 클래스 안에서 접근제어로 파괴자가 만들어지고, 클래스 객체사용 후 delete로 메모리 할당을 해제한다.
[기능]
● new로 할당된 메모리를 해제합니다.
● 객체의 경우 소멸자를 자동으로 호출합니다.
[사용법]
앞서 할당한 int형 변수의 소멸 delete ptr1;
앞서 할당한 double형 변수의 소멸 delete ptr2;
앞서 할당한 int형 배열의 소멸 delete []arr1;
앞서 할당한 double형 배열의 소멸 delete []arr2;
**생성자에서 new []형식을 사용했다면 파괴자에서도 delete []형식을 사용해야한다.
[특징]
● 배열을 해제할 때는 반드시 delete[] 형식을 사용해야 합니다.
● null 포인터에 대해 안전하게 사용할 수 있습니다.
▶▶▶ 생성자와 소멸자
- 생성자: 객체가 생성될 때 자동으로 호출되는 특별한 멤버 함수입니다. 객체의 멤버 변수를 초기화하거나 필요한 자원을 할당하는 등의 작업을 수행합니다.
- 소멸자: 객체가 소멸될 때 자동으로 호출되는 특별한 멤버 함수입니다. 객체가 사용하던 자원을 해제하거나 정리하는 등의 작업을 수행합니다.
디폴트 생성자:
- 매개변수가 없으며, 명시적으로 초기화하지 않고 객체를 생성할 때 사용한다.
- 사용자가 어떠한 생성자도 제공하지 않으면, 사용자를 대신하여 컴파일러가 디폴트 생성자를 정의한다.
- 그렇지 않을 경우에는 사용자 자신의 디폴트 생성자를 제공해야 한다. 사용자가 제공하는 생성자는 어떠한 매개변수도 사용하지 않거나, 모든 매개변수에 디폴트 값을 사용해야 한다.
Klunk :: Klunk() {} // 암시적 디폴트 생성자
Klunk lunk; //디폴트 생성자를 호출한다
Klunk :: Klunk() // 명시적 디폴트 생성자
{
klunk_ct = 0;
}
class Dog {
public:
Dog(string name) { // 생성자
cout << name << " 강아지가 태어났어요!" << endl;
this->name = name;
collar = new string("빨간색 목줄"); // new로 메모리 할당
}
~Dog() { // 소멸자
cout << name << " 강아지가 무지개 다리를 건넜어요." << endl;
delete collar; // delete로 메모리 해제
}
private:
string name;
string* collar;
};
int main() {
Dog* myDog = new Dog("멍멍이"); // 새 강아지 객체 생성
// ... 강아지와 놀기 ...
delete myDog; // 강아지 객체 삭제
return 0;
}
- 'new Dog("멍멍이")'는 새 강아지 객체를 만들고 생성자를 호출합니다.
- 생성자에서는 강아지 이름을 설정하고 목줄을 만듭니다 (메모리 할당).
- delete myDog를 하면 소멸자가 호출됩니다.
- 소멸자에서는 목줄을 제거합니다 (메모리 해제).
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
// 생성자
Person(const std::string& n, int a) : name(n), age(a) {
std::cout << "Person 객체 생성: " << name << ", " << age << "세\n";
}
// 파괴자
~Person() {
std::cout << "Person 객체 소멸: " << name << ", " << age << "세\n";
}
};
int main() {
// new를 사용하여 Person 객체 동적 할당
Person* personPtr = new Person("홍길동", 20);
// personPtr을 통해 객체의 멤버 변수 접근 및 수정
std::cout << personPtr->name << "의 나이는 " << personPtr->age << "세입니다.\n";
personPtr->age = 21;
std::cout << personPtr->name << "의 나이는 " << personPtr->age << "세입니다.\n";
// delete를 사용하여 Person 객체 메모리 해제
delete personPtr;
return 0;
}
** 실행 순서 : main( )시작 -> 객체생성 (new 동적할당) -> 멤버변수 할당 -> 객체소멸 (delete 메모리해제) -> main( )종료
복사 생성자
▶▶▶ 얕은 복사 와 깊은 복사
[얕은 복사]
원본 객체와 복사된 객체는 같은 메모리 공간을 공유하기 때문에, 한 객체의 값을 변경하면 다른 객체에도 영향을 미칩니다.
얕은 복사의 장점과 단점
- 장점:
- 복사 속도가 빠릅니다. (단순히 값만 복사하기 때문)
- 메모리 사용량이 적습니다. (메모리 공간을 공유하기 때문)
- 단점:
- 원본 객체와 복사된 객체가 메모리 공간을 공유하므로, 한 객체의 변경이 다른 객체에 영향을 준다.
- 동적 할당된 메모리를 가리키는 포인터 멤버 변수가 있을 경우, 메모리 해제 시 문제가 발생할 수 있다.
예를들어,
얕은 복사는 객체의 멤버 변수들이 모두 기본 자료형(int, float, double 등)이거나, 객체 간에 데이터 공유가 필요한 경우에 사용할 수 있습니다. 하지만 동적으로 할당된 메모리를 가리키는 포인터 멤버 변수가 있을 경우에는 얕은 복사를 사용하면 안 된다.
#include <iostream>
#include <string>
class MyClass {
public:
int* data;
MyClass(int value) {
data = new int(value);
}
// 복사 생성자 (기본 얕은 복사 수행)
MyClass(const MyClass& other) {
data = other.data; // 포인터만 복사, 메모리 공간은 공유
}
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1(10);
MyClass obj2 = obj1; // 얕은 복사 발생
*obj1.data = 20; // obj1의 data 값 변경
std::cout << "obj1.data: " << *obj1.data << std::endl; // 20 출력
std::cout << "obj2.data: " << *obj2.data << std::endl; // 20 출력 (obj1의 변경에 영향 받음)
return 0;
}
[깊은 복사]
깊은 복사에서도 원본 객체가 가리키는 메모리 공간을 복제하여 새로운 메모리 공간을 할당하기 때문에, 원본 객체와 복사된 객체는 서로 독립적입니다.
깊은 복사의 장점과 단점
- 장점:
- 원본 객체와 복사된 객체가 서로 독립적이므로, 한 객체의 변경이 다른 객체에 영향을 미치지 않습니다.
- 동적 할당된 메모리를 가리키는 포인터 멤버 변수가 있어도 안전하게 복사할 수 있습니다.
- 단점:
- 복사 속도가 느립니다. (메모리 공간을 복제해야 하기 때문)
- 메모리 사용량이 많습니다. (새로운 메모리 공간을 할당해야 하기 때문)
예를들어,
깊은 복사는 객체 간에 독립성을 유지해야 하거나, 동적으로 할당된 메모리를 가리키는 포인터 멤버 변수가 있을 때 사용해야 합니다.
#include <iostream>
#include <string>
class MyClass {
public:
int* data;
MyClass(int value) {
data = new int(value);
}
// 복사 생성자 (깊은 복사 수행)
MyClass(const MyClass& other) {
data = new int(*other.data); // 새로운 메모리 공간 할당 후 값 복사
}
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1(10);
MyClass obj2 = obj1; // 깊은 복사 발생
*obj1.data = 20; // obj1의 data 값 변경
std::cout << "obj1.data: " << *obj1.data << std::endl; // 20 출력
std::cout << "obj2.data: " << *obj2.data << std::endl; // 10 출력 (obj1의 변경에 영향 받지 않음)
return 0;
}
복사 생성자
[복사 생성자 공식]
MyClass(const MyClass& other) : data(other.data) {}
// 클래스 객체에 대한 const는 참조를 매개변수로 사용한다.
// 복사 생성자 (기본 얕은 복사 수행)
MyClass(const MyClass& other) {
data = other.data; // 포인터만 복사, 메모리 공간은 공유
}
MyClass obj2 = obj1; // 얕은 복사 발생
*obj1.data = 20; // obj1의 data 값 변경
// 복사 생성자 (깊은 복사 수행)
MyClass(const MyClass& other) {
data = new int(*other.data); // 새로운 메모리 공간 할당 후 값 복사
}
MyClass obj2 = obj1; // 깊은 복사 발생
*obj1.data = 20; // obj1의 data 값 변경
=> 객체를 복제하여 동일한 값을 가진 새로운 객체를 생성할 때 사용된다.
=> 함수의 매개변수나 반환 값으로 객체를 전달할 때, 복사 생성자를 통해 객체를 복사하여 전달할 수 있습니다.
이를 통해 원본 객체의 값이 변경 되는 것을 방지하고 안전하게 데이터를 전달할 수 있습니다.
비교멤버
비교 멤버 함수는 객체의 특정 속성을 기준으로 객체들을 비교하고, 그 결과를 반환하여 객체 정렬, 검색 등 다양한 작업에 활용될 수 있습니다.
비교 멤버 함수는 객체의 특정 속성을 기준으로 두 객체를 비교하고, 그 결과를 true 또는 false로 반환하는 특별한 멤버 함수입니다.
- 객체 정렬: 객체들을 특정 기준으로 오름차순 또는 내림차순으로 정렬할 수 있습니다. 예를 들어, 학생들을 성적순으로 정렬하거나, 상품들을 가격순으로 정렬할 수 있죠.
- 객체 검색: 특정 조건을 만족하는 객체를 빠르게 찾을 수 있습니다. 예를 들어, 특정 이름을 가진 학생을 찾거나, 특정 가격 범위에 속하는 상품들을 검색할 수 있습니다.
- 중복 제거: 객체들을 비교하여 중복된 객체를 제거할 수 있습니다. 예를 들어, 주소록에서 중복된 연락처를 제거하거나, 데이터베이스에서 중복된 레코드를 삭제할 수 있습니다.
class Student {
public:
int score;
bool operator<(const Student& other) const { // 비교 멤버 함수 정의
return score < other.score;
}
};
// Student 클래스에 operator< 라는 비교함수를 정의하여 이 함수는 현재 객체의 score가 다른 객체의
// score보다 작은지 비교하고, 그 결과를 true 또는 false로 반환합니다.
Student student1 { 90 };
Student student2 { 85 };
if (student1 < student2) {
std::cout << "student1의 점수가 더 낮습니다." << std::endl;
} else {
std::cout << "student2의 점수가 더 낮습니다." << std::endl;
}
// 객체를 student1 , student2를 생성하고 초기값 설정
// student1 < student2는 student1.operator<(student2)를 호출하는 것과 같습니다.
▶ 문자열 비교 멤버함수
bool operator<(const String & st1, const String & st2)
{
if(std::strcmp(st1.str,st2.str) < 0)
{
return true;
}
else
{
return false;
}
}
///////////////////////////////////////////////////
bool operator<(const String & st1, const String st2)
{
return (std::strcmp(st1.str,st2.str) < 0);
}
// 내장된 < 연산자가 이미 bool형의 값을 리턴하고 있기 때문에 간결하게 가능하다
///////////////////////////////////////////////////
bool operator>(const String & st1, const String st2)
{
return st2 < st1;
}
bool operator==(const String & st1, const String st2)
{
return (std::strcmp(st1.str,st2.str) == 0);
}
1. > 연산자를 < 연산자로 표현하고 있다. 그것은 인라인 함수로 만들기 좋은 예이다.
2. 비교함수들을 프렌드로 만들면, String 객체와 보통의 C스타일의 문자열 비교를 할 수 있다.
'LAB > C++' 카테고리의 다른 글
C++ 기초플러스(15.3 예외처리) / C++로 Mariadb 연결 및 DB Table생성 (2) | 2024.09.28 |
---|---|
C++ 기초플러스 (13. 클래스의 상속) (1) | 2024.09.27 |
C++ 기초플러스 (08. 함수의 활용 - 10. 객체와 클래스) (0) | 2024.09.25 |
C++ 기초플러스 (05. 루프와 관계 표현식 - 07. 함수 - C++의 프로그래밍 모듈) (0) | 2024.09.24 |
C++ 기초플러스 (02. C++ 시작하기 - 04. 복합데이터형) (0) | 2024.09.23 |