class
class 키워드를 이용해 클래스를 생성할 수 있다
class는 struct를 대체하기 때문에
앞으로 모든 struct는 class로 만들게 된다
이 클래스의 내부에는 꼭 public: 이라는 키워드가 있어야 한다
class MyClass{
public:
...
};
인라인 조건절
인라인 조건절을 이용해 생성되는 값을 지정할 수 있다
<조건절> ? 값1 : 값2
위와 같은 인라인 조건절은 조건절이 true라면 값 1 이 생성되고
조건절이 false 라면 값 2 가 생성되게 된다
정적인 클래스 선언
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <string> // strcpy 함수 사용을 위해 class Student { public: // public 이라고 하는 키워드가 존재해야만 이 키워드 밑에 있는 내용들은 바깥에서 접근할 수 있다 int student_no; char name[20]; int gender; // 0 = 남자, 1 = 여자 // 이런식으로 클래스 안에 멤버변수를 선언, 클래스 안에 들어가있는 모든 것들은 사실상 멤버 (변수도 멤버, 함수도 멤버) // 멤버 변수를 멤버 함수 내부에서 접근했는데 멤버 변수는 멤버 함수에서 멤버변수의 브라켓 {}에 의해서 지정되는 유효범위 만큼 접근할 수 있다. void printStudentInfo() { // 이 함수는 단순히 클래스의 멤버 변수들을 쭈르륵 출력하는 역할만 하게 됨 printf("학생번호 : %d\n", student_no); printf("학생 이름 : %s\n", name); printf("성별 : %s\n", gender == 0 ? "남자" : "여자"); // gender가 0이면 남자고 0이 아니면 여자가 출력 } }; int main(){ Student s; // 클래스를 활용할때는 구조체를 활용한 것 처럼 하면 됨 s.student_no = 1234; strcpy(s.name, "김말똥"); s.gender = 0; s.printStudentInfo(); // 멤버 함수를 호출해주면 됨 Student s2; s2.student_no = 3456; strcpy(s2.name, "김순이"); s2.gender = 1; s2.printStudentInfo(); // 클래스의 변수를 하나를 선언 할 때마다 클래스 변수와 함께 안에 있는 멤버 변수들이 한꺼번에 만들어진다, 클래스이 변수 안에는 다 독립적으로 멤버 변수가 존재 마치 하나의 변수 집합체 (지금 까지 한 건 정적인 클래스 선언) // 포인터 타입 만들기 Student* ps = new Student(); ps->student_no = 2345; strcpy(ps->name, "김순이"); ps->gender = 1; ps->printStudentInfo(); // 이렇게 동적으로 new 키워드를 활용해서 동적으로 할당하게 되면 함수에 저장되는 것이 아니라 실제 값이 Heap이라는 다른 메모리 영역에 저장됨 그래서 함수의 라이프사이클과 관계없이 함수가 실행되든 죽든 계속 남아있다 delete ps; // 그래서 꼭 delete를 해줘야한다 return 0; } |
클래스 인스턴간 차이 (정적과 동적의 차이)
: 변수의 값이 직접적으로 어디에 저장되느냐
main 함수 안에 Student s; 처럼 함수 내부에 저장되면 함수가 끝나면 사라지게 된다
new 키워드를 활용해서 동적으로 할당하게 되면 함수에 저장 되는 것이 아니라 실제 값이 heap이라는
다른 메모리 영역에 저장이 된다. 그러면 계속 남아있게 됨 delete 꼭 해주깅
new 키워드를 이용해 클래스를 만들게 되면 클래스의 실제 값이 Heap 영역에 저장되는 것이 가장 큰 차이이다.
Heap 영역에 저장되는 클래스의 인스턴스 값은 함수의 실행주기(라이프사이클)에 관계없이 유지된다.
정적인 형태와 동적인 형태의 차이는 가장 크게는 이 변수의 값이 직접적으로 어디에 저장되고 있느냐에 달림
정적인 형태(사실 정적인 형태는 아니고 Stack Allocated 지만)의 클래스와 동적인 클래스는 생명주기간의 차이가 있다.
이는 객체지향 프로그래밍을 할 때 매우 중요한 부분
객체지향 개발을 하다 보면 함수의 생명주기에 관계 없는 객체 인스턴스를 만들 일이 매우 많다.
따라서 포인터 형태, 즉 동적할당된 클래스 인스턴스를 정말 많이 다루게 된다.
delete
동적클래스를 할당해 줬을 경우에는 마지막에 다 쓰고 나서 함수의 메모리를 해제 해 주지 않으면
메모리를 지속적으로 잡아 먹고 있는 요인
따라서 new 키워드를 이용해 클래스 인스턴스를 만들었을 경우
delete 키워드를 이용해 클래스 인스턴스의 실제 값을 Heap 메모리에서 삭제해줘야 한다.
배열의 경우에는 delete[] 키워드를 활용
이 개념은 malloc( ... )과 free( ... ) 두 함수의 활용의 경우와 동일
생성자 (Constructor)
클래스는 생성자(constructor)를 통해 초기 행동을 지정해줄 수 있다.
이를 생성자라고 한다.
생성자를 만들게 되는 이유 : 꼭 들어가게 되는 것들이 있는데 매번 만들고 하나씩 집어 넣는 과정이 불편
그런 것들을 한 번에 하기 위해 초기화 과정을 도와주는 것이 생성자
생성자는 클래스명(파라미터들...)로 선언할 수 있다.
생성자는 리턴타입을 갖지 않는다.
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <string> class Student { public: int student_no; char name[20]; int gender; // 0 = 남자, 1 = 여자 Student(int pStdNo, const char* n,int g ) { // 생성자!! student_no = pStdNo; strcpy(name, n); gender = g; } // 이런식으로 생성자를 만들어 버리게 되면 선언만으로는 클래스를 활용할 수 없다 Student s; 불가능 // Student s = Student (1234, "김말똥", 0); 이런식으로 하기 -> 생성을 하면서 파라미터를 통해 초기화 // 그 이유는 생성자를 활용해야만 클래스를 생성할 수 있기 때문 void printStudentInfo() { printf("학생번호 : %d\n", student_no); printf("학생 이름 : %s\n", name); printf("성별 : %s\n", gender == 0 ? "남자" : "여자"); } }; int main(){ // 클래스를 활용할때는 구조체를 활용한 것 처럼 하면 됨 Student s = Student(1234, "김말똥", 0); // 생성자를 만들게 되면 선언 만으로 클래스를 활용할 수 없다 (Student s; 불가능) ()안에 Student 함수를 호출 하듯이 이 파라미터들을 집어 넣어 주면 됨 생성을 하면서 바로 Student(int pStdNo, const char* n,int g) 파라미터를 이용해서 값들을 초기화 s.printStudentInfo(); // 멤버 함수를 호출해주면 됨 // 이렇게 만들게 되면 함수 내부에 실제 값 s 가 저장됨 그래서 함수가 끝나면 사라진다 // 포인터 타입 만들기 Student* ps = new Student(3456, "김순이", 1); ps->printStudentInfo(); delete ps; return 0; } |
this 키워드
: 현재 인스턴스 라는 뜻
this : 클래스의 변수를 의미 (각각의 클래스 변수의 멤버변수)
멤버 변수의 이름과 파라미터의 이름이 같은 경우 사용
this -> 멤버변수 이름 = 파라미터 이름
클래스 멤버 함수는 this 라는 키워드를 통해
자기자신의 인스턴스(포인터 타입)를 가져올 수 있다
클래스 멤버 함수를 호출하기 위해서는
호출한 주체인 어떤 클래스 인스턴스(instance)가 존재하게 되는데
그 호출 주체를 this 라고 한다.
인스턴스(Instance)
클래스는 정의만으로 존재할 수 없다. 그냥 초안일 뿐이다.
우리가 여지껏 '클래스의 변수'라고 말해왔던 것을 클래스 인스턴스라고 생각하면 편하다.
클래스를 선언하고 우리가 변수의 형태로 만들어서 활용했다.
이 변수 형태로 만들어서 사용할 때 만든 이 '변수'를 인스턴스라고 한다.
지금까지는 이해하기 쉽게 클래스 변수, 구조체 변수라는 용어를 사용했지만,
이제부터는 정확한 명칭인 클래스 인스턴스라는 용어를 사용하게 될 것
Student s = Student(1234, "김말똥", 0); // stack(함수) 영역에 존재하는 인스턴스
: s가 Student(1234, "김말똥", 0); 의 인스턴스가 된다, 정적인 인스턴스
정적 변수들은 함수가 끝나면 그 즉시 소멸이 된다.
Student *ps = new Student(1234, "김말똥", 0); // heap 영역에 존재하는 클래스 인스턴스
: 함수의 life cycle과 관계없는 것이 동적 할당
함수의 life cycle과 관계 없이 메모리에 상주해 있는다.
함수가 끝나도 heap 영역에 존재하기 때문에 소멸자 절대 호출되지 않는다. delete로 수동으로 소멸자 호출
소멸자 (Destructor)
클래스는 소멸자 (destructor)를 통해 메모리에서 해제될 때의 행동을 지정해줄 수 있다.
이를 소멸자라고 한다.
소멸자는 ~클래스명() 으로 선언할 수 있으며
소멸자도 리턴타입이 존재하지 않는다 + 파라미터를 받지 않는다
소멸자는 언제 호출 되냐면 이 클래스 인스턴스가 파괴될때 즉, 메모리에서 해제될때 호출이 됨
소멸자 호출의 필요성
모든 동적 메모리를 수동으로 관리해줘야 하는 C++의 특성상
클래스 멤버 변수에 동적 메모리 할당된 개체가 있다고 할 경우
소멸자에서 처리하지 않으면 하나하나 클래스 외부에서 delete 전에 메모리를 해제해야 한다.
이는 굉장히 불편함과 불합리함을 초래하게 되는데
이를 해결할 수 있는 것이 소멸자이다.
C++는 특성상 소멸자를 통해 멤버 변수에 할당되어 있는 동적 배열이나
멤버 동적 인스턴스를 적당히 제거해줄 필요가 있다.
우리가 이것을 클래스 외부에서 수동으로 하나하나 해 줄 수 있겠지만
이건 번거롭고 좋은 방법이 아님
이 때문에 C++은 이런 일을 할 수 있는 '소멸자'라는 옵션을 제공
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <string> class Student { public: int student_no; char name[20]; int gender; // 0 = 남자, 1 = 여자 Student(int student_no, const char* n,int g ) { this->student_no = student_no; // 파라미터 이름과 멤버변수의 이름을 같게 한 경우 // this-> operator를 이용하여 변수명을 입력하게 되면 이 클래스의 클래스 변수를 의미, 멤버에 접근할 수 있다. strcpy(name, n); gender = g; } ~Student() { printf("%s의 소멸자 호출", name); } void printStudentInfo() { printf("학생번호 : %d\n", student_no); printf("학생 이름 : %s\n", name); printf("성별 : %s\n", gender == 0 ? "남자" : "여자"); } }; int main(){ Student s = Student(1234, "김말똥", 0); s.printStudentInfo(); Student* ps = new Student(3456, "김순이", 1); // Student* 뒤 ps는 사실 클래스 변수가 아니라 클래스 인스턴스 라고 한다. // ps는 heap 영역에 존재하는 클래스 인스턴스, s는 함수의 영역, 즉 stack 영역에 존재하는 인스턴스 ps->printStudentInfo(); delete ps; return 0; } |
strlen ( ... )
cstring 헤더파일에 포함되어 있는 함수
파라미터로 문자열을 입력받아 문자열의 길이를 반환한다 (널문자는 제외)
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <string> class Student { public: int student_no; char* name; // 동적인 형태의 길이를 갖고 있는 이름을 멤버 변수로 선언한 경우 int gender; // 0 = 남자, 1 = 여자 Student(int student_no, const char* n,int g ) { this->student_no = student_no; name = new char[strlen(n) + 1]; // 메모리 초기화, +1인 이유는 마지막에 널문자가 들어가기 때문 // strlen[n]+1 은 문자열의 길이를 나타냄 (strlen(n)은 널문자를 제외한 길이) // 이렇게 하면 이름이라는 문자열 포인터에 동적배열이 할당이 되고 문자열 길이에 딱 맞는 문자열 배열이 만들어짐 strcpy(name, n); gender = g; } ~Student() { printf("%s의 소멸자 호출\n", name); delete[] name; // 소멸자에서 보통 해제하게 된다. } void printStudentInfo() { printf("학생번호 : %d\n", student_no); printf("학생 이름 : %s\n", name); printf("성별 : %s\n", gender == 0 ? "남자" : "여자"); } }; int main(){ //Student s = Student(1234, "김말똥", 0); //s.printStudentInfo(); Student* ps = new Student(3456, "김순이", 1); ps->printStudentInfo(); delete ps; return 0; } |
연습 프로그래밍
name과 rank에 관한 내용을 동적 배열로 할당
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstdlib> #include <ctime> #include <cstring> #include <cmath> int number_counter = 1; // 사원 번호는 알아서 관리를 하라고 해서 일단 변수 하나 두기 class Employee { public: int no; char name[20]; int gender; char rank[20]; // 생성자, 포인터를 받아왔으니까 넣어준다, 생성자로 Employee 초기화 Employee(char*name, int gender, char* rank){ strcpy(this->name, name); // 이름이 곂치기 때문에 this 키워드 활용 this->gender = gender; strcpy(this->rank, rank); this->no = number_counter; // 사원 번호는 알아서 관리하니 변수 하나 만들어 자동으로 +1 해주기 number_counter++; // number_counter은 프로그램이 진행되면서 지속적으로 1씩 늘어나면서 사원마다 유일한 사원 번호가 순차적으로 부여 } void printInfo() { // 종업원의 정보를 출력하는 함수 printf("사원 번호 : %d\n", no); printf("사원명 : %s\n", name); printf("성별 : %s\n", gender == 1 ? "남성" : "여성"); printf("직급 : %s\n", rank); } }; int getInt(const char* prompt); // 사용자로 부터 입력을 받기 위한 getInt 함수 // prompt에 입력을 받기 위해 사용하는 string을 받아 getInt를 사용하여 한꺼번에 출력 // 함수 실행 후 메모리 해제를 꼭 해줘야 한다. char* getString(const char* prompt); |
#include "c++ practice.hpp" int main(){ Employee* employee[100]; // 사원을 관리하는 자료구조 이 employee가 몇가지 채워져있는지는 알 수 없다. int count = 0; // employee 숫자를 저장하고 있을 카운트 변수, 인스턴스가 몇가지 채워져있는지 추가적인 정보 기록 // 1. 사원 보기 // 2. 사원 추가 // 3. 사원 삭제 // 4. 프로그램 종료 while(true){ printf("명령을 입력하세요\n"); printf("1. 사원 보기\n"); printf("2. 사원 추가\n"); printf("3. 사원 삭제\n"); printf("4. 프로그램 종료\n"); fseek(stdin, 0, SEEK_END); int input; scanf("%d", &input); if (input == 1){ // 사원 보기 for (int i = 0; i < count; i++){ employee[i]-> printInfo(); // 종업원의 정보 출력 // 인스턴스가 몇가지 채워져 있는지를 알기 위한 추가적인 정보를 기록해주는 int형 변수 } } else if (input == 2) { // 사원 추가 char* name = getString("사원명 입력해주세요:"); int gender = getInt("성별을 입력해 주세요 (1남성, 2여성):"); char* rank = getString("직급을 입력해주세요:"); Employee* e = new Employee(name, gender, rank); // 위 내용을 가지고 Employee 라는 클래스를 초기화 할 수 있다 (헤더파일에서 생성자 활용) // e라는 직원을 자료구조에다가 추가해줘야함 delete[] name; delete[] rank; // 메모리 누수를 막기 위해 두가지 동적으로 할당된 이 변수들을 모두 다 삭제를 해줘야함 이름, 랭크는 지속적으로 메모리에 남아있게 된다. employee[count] = e; // 자료구조에 등록 count++; // 등록이 된 이후에 count 1증가 } else if (input == 3) { // 사원 삭제 // 전체 employee를 loop를 돌면서 사원번호를 확인하고 일치하는 사원번호가 있으면 삭제 int number = getInt("사원번호를 입력해주세요 "); int deleteIndex = -1; for (int i = 0; i<count; i++){ if (number == employee[i]->no) { // number와 employee의 i번째의 사원 넘버와 같다면 삭제 delete employee[i]; deleteIndex = i; // 배열 번호를 지웠다고 해서 배열이 당겨져 오는게 아님, 뒤쪽에 있는 배열 값들을 앞쪽으로 끌어오는 동작하기 break; // 루프를 탈출하여 deleteIndex가 -1이 아닌 다른 어떤 숫자가 된다 2번째 배열을 지웠다고 하면 배열 index값인 2가 deleteIndex에 할당된다. } } if (deleteIndex >= 0) { // 삭제가 성공했는지 성공하지 않았는지를 판단(-1이 아니라면 삭제되었다고 판단) for (int i = deleteIndex; i < count - 1; i++){ employee[i] = employee[i + 1]; // 삭제된 배열 부터 마지막 배열까지 돌면서 삭제된 i 의 값을 i+1의 값으로 할당 해주기 그 뒤에도 다 i를 i+1로 만들기 2번이 삭제 되었다고 치면 2번이 없어지니 3번을 2번으로 해주기 } } count --; // 하나가 지워졌으니 count를 하나 감소 시켜 주기 printf("삭제가 완료되었습니다."); } else if (input == 4) { // 프로그램 종료 printf("프로그램을 종료합니다.\n"); break; } else { // 올바르지 않은 입력 printf("올바르지 않은 입력입니다.\n"); } } return 0; } int getInt(const char* prompt){ int input; printf("%s", prompt); fseek(stdin, 0, SEEK_END); scanf("%d", &input); return input; } char* getString(const char* prompt){ char* input = new char[100]; // char input[100];이 아니라 char* input = new char[100];로 동적할당 // 동적할당을 해주게 되면 이 input이라는 변수가 이 함수영역에서 만들어진 게 아니라 heap영역에 존재, 그것의 주소값 input만 리턴 해 주는 것 // 그래서 함수가 끝나도 이 문자열은 사라지지 않는 형태 printf("%s", prompt); fseek(stdin, 0, SEEK_END); scanf("%99[^\n]s", input); // 99개의 문자를 받아서 string으로 input에다가 저장을 하게 됨 return input; // return input;을 하게 되면 이 배열은 getString이라는 함수가 끝나면 모두 다 파괴가 됨 왜냐하면 이 문자열은 지금 함수 영역에 만들어져 있음, 이 배열은 이 함수가 끝나면 다 사라지게 됨 return은 배열을 리턴하는 것이 아니라 input 주소값만 리턴해서 오작동 가능성이 있다. 그래서 동적 메모리 할당을 하게 된다 char* input = new char[100]; 사용 } |
과제형 연습 프로그래밍
만들어진 사원관리 시스템에서
이름, 직급에 대한 멤버변수를 동적 배열로, 최초 입력된 값으로 할당 받고
소멸자를 이용해 필요한 시점에 메모리 해제가 될 수 있도록 작성하세요
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstdlib> #include <ctime> #include <cstring> #include <cmath> int number_counter = 1; // 사원 번호는 알아서 관리를 하라고 해서 일단 변수 하나 두기 class Employee { public: int no; char *name; int gender; char *rank; // 생성자, 포인터를 받아왔으니까 넣어준다 Employee(char*name, int gender, char* rank){ this -> name = new char[strlen(name) + 1]; this -> rank = new char[strlen(rank) + 1]; // 널문자가 들어가기 때문에 +1 strcpy(this->name, name); // 이름이 곂치기 때문에 this 키워드 활용 this->gender = gender; strcpy(this->rank, rank); this->no = number_counter; number_counter++; // number_counter은 프로그램이 진행되면서 지속적으로 1씩 늘어나면서 사원마다 유일한 사원 번호가 순차적으로 부여 } ~Employee() { delete[] name; delete [] rank; printf("%d의 사원번호를 가진 사원이 삭제되었습니다.\n", no); } // 소멸자는 따로 호출할 필요 없이 delete employee[i]; 이런식으로 이 객체의 instance를 삭제 할 때 자동으로 호출 void printInfo() { printf("사원 번호 : %d\n", no); printf("사원명 : %s\n", name); printf("성별 : %s\n", gender == 1? "남성" : "여성"); printf("직급 : %s\n", rank); } }; int getInt(const char* prompt); // 함수 실행 후 메모리 해제를 꼭 해줘야 한다. char* getString(const char* prompt); |
cpp는 위 예제코드랑 동일, 헤더파일만 다르게 만들어주면 된다.
'C++' 카테고리의 다른 글
c++ 6 (열거형 - 가독성을 위한 타입) (0) | 2022.01.23 |
---|---|
c++ 5 (가상함수 - Java, C# 등 다른 언어의 개념 까지 ) (0) | 2022.01.16 |
c++ 4 (오버라이드 / 오버로드) (0) | 2022.01.13 |
c++ 3 (클래스 상속 / 접근지정자) (0) | 2021.09.24 |
c++ 1 (클래스 / 객체지향 - 게임 개발에 매우 중요한 개념) (0) | 2021.08.30 |