C++ OOP(Object-Oriented-Programming) (2)
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
C++ OOP(Object-Oriented-Programming) (2)
복사생성자
생성자 : 객체가 생성될 때 실행될 메소드임
복사생성자 : 같은 클래스에 속한 다른 객체를 이용하여 새로운 객체를 초기화 할때 사용
받는 매개변수가 자신이랑 같은 const& 면 복사생성자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 //header class Vector1{ public : Vector1(); Vector1( const Vector1 & other); private : int mX; int mY; } //cpp Vector1::Vector1() : mX( 0 ) , mY( 0 ) { } Vector1::Vector1( const Vector1 & other) : mX(other.mX); //other는 다른 인스턴스지만 , mY(other.mY); //같은 클래스형이라서 private에 접근가능함 { } Colored by Color Scripter cs
코드에 복사 생성자가 없는 경우, 컴파일러가 암시적 복사생성자 자동생성
cf) 아무런 생성자도 없으면 기본생성자 + 암시적 복사생성자 만들어줌
하나의 생성자라도 유저가 구현했다면 기본생성자는 만들어주지 않음
암시적 복사 생성자
의미와 특징
- 유저가 구현하지 않아서 컴파일러가 만든 복사 생성자를 암시적 복사생성자라 함
- 암시적 복사 생성자는 "얕은 복사"를 함
- 멤버 별 값 복사
- 멤버변수가 객체라면 그 객체의 복사 생성자도 호출됨
- 만약 heap에 동적할당 후 얕은 복사를 진행하면 문제가 발생할 우려가 있음 (아래코드)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ClassRecord::ClassRecord( const int * scores, int count) : mCount(count) { mScores = new int [mCount]; //동적할당 -> 크기 열어줌 memcpy(mScores, scores, mCount * sizeof ( int )); //복사진행 } ClassRecord::~ClassRecord(){ delete [] mScores; } ClassRecord::ClassRecord( const ClassRecord & other) //암시적 복사생성자와 같음->얕은복사 : mCount(other.mCount) , mScores(other.mScores) // 배열의 시작주소 먹음 { } //main ClassRecord cr(scores, 5 ); //생성자 호출 ClassRecord * crCopy = new ClassRecord(cr); //복사생성자 호출 -> 값만 복사 delete crCopy; //cpCopy의 소멸자 호출 Colored by Color Scripter cs
>>> 메모리 머리속에서 그려보기
-
카피한 객체를 없애는 순간 소멸자가 호출됨
-> 그에 따른 mScores 배열은 없어짐
-> 근데 원본이 여전히 mScores를 가르킴 (문제)
클래스안에서 동적으로 메모리를 할당하고 있다면?
-> 얕은 복사로 인해 위험할 가능성이 높음
-> 직접 복사 생성자를 만들어서 "깊은 복사"를 해야 함
-> 포인터 변수가 가르키는 실제 데이터까지도 복사해야 함(아래코드)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ClassRecord::ClassRecord( const int * scores, int count) : mCount(count) { mScore = new int [mCount]; //동적할당 -> 크기 열어줌 memcpy(mScores, scores, mCount * sizeof ( int )); //복사진행 } ClassRecord::~ClassRecord(){ delete [] mScores; } ClassRecord::ClassRecord( const ClassRecord & other) //깊은 복사 : mCount(other.mCount) { mScores = new int [other.mCount]; memcpy(mScores, other.scores, mCount * sizeof ( int )); } //main ClassRecord cr(scores, 5 ); //생성자 호출 ClassRecord * crCopy = new ClassRecord(cr); //복사생성자 호출 -> 값만 복사 delete crCopy; //cpCopy의 소멸자 호출 Colored by Color Scripter cs
>>> 자세히 보면, 그냥 오버로딩된 생성자와 비슷함
더보기 연습 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 > > > 아래 헤더파일가지고 깊은 복사 해보기 #pragma once namespace test { class String { public : String( const char * str); String( const String & str); ~String(); void Print(); private : char * mString; size_t mSize; }; } > > > 답 #include < iostream > #include "String.h" using namespace std ; namespace test { String::String( const char * str) : mSize(strlen(str) + 1 ) { mString = new char [mSize]; memcpy(mString, str, mSize); } String::String( const String & str) : mSize(str.mSize) { mString = new char [mSize]; memcpy(mString, str.mString, mSize); } String::~String() { delete [] mString; } void String::Print() { cout < < mString < < endl ; } } > > > 메인사용 #include "String.h" #include < iostream > using namespace std ; int main() { test::String str( "123" ); str.Print(); test::String * str2 = new test::String(str); str2 - > Print(); delete str2; str.Print(); } Colored by Color Scripter cs
연산자 오버로딩
함수오버로딩
- 매개변수 목록을 제외하고 모든 게 동일
- 반환형은 달라도 괜찮음
- 근데 매개변수가 같은데 반환형이 다르면 컴파일에러 -> 중복문제
- 매칭되는 것이 하나여야함
- 자료형이 float -> int 가능함 (표준변환)
(C언어 캐스팅): https://dojang.io/mod/page/view.php?id=493
함수 오버로딩 매칭하기
1) void Print(int score);
2) void Print(const char* name);
3) void Print(float gpa, const char* name);
4) int Print(int score); //컴파일 에러 -> 1번과 매칭됨
5) int Print(float gpa);
1) void Print(int score);
2) void Print(const char* name);
3) void Print(float gpa, const char* name);
Print(100); // 1
Print("Pope")l // 2
Print(4.0f, "Pope"); // 3
Print(77.5f); // 1 컴파일 경고가 나올 수 있음
Print("Pope", 4.0f); //컴파일에러
1) void Max(double, double)
2) void Max(int, int)
max(1, 3.14)
>>> 표준변환 1개, 정확한타입 1개 >>> 모호한 호출임 >>> 컴파일에러
>>> 그냥 형변환해서 둘 이상 들어갈 수 있으면 컴파일에러임
cf) 3.14f == float | 3.14 == double
연산자 오버로딩(operator overloading)
단항연산자 : 피연산자 1개
이항연산자 : 피연산자 2개
- C++에서는 연산자 오버로딩을 지원함 (C, Java는 없음)
- 연산자를 오버로딩 하는 방법은 두가지
1) 멤버 함수로 : (클래스 + 연산자 + 매개변수 | ex. Vector1 + Vector1 )
2) 전역 함수로 : (매개변수1 + 연산자 + 매개변수2) | ex. cout << Vector )
-> 좌항이 접근할 수 없는 int형이나 os객체일 때 사용함
- 특정 연산자들은 멤버 함수를 이용해서만 오버로딩 가능
-> =, (), [], -> 등등
1) 멤버 함수로 연산자 오버로딩
- Vector끼리의 연산을 진행하고 싶음
ex) Vector vec = Vector1 + Vector2;
- 멤버 구현쪽으로 가서 연산자 오버로딩 진행
1 2 3 4 5 6 7 Vector1 Vector1:: operator + ( const Vector1 & rhs) { Vector1 retVec; retVec.mX = mX + rhs.mX; retVec.mY = mY + rhs.mY; return retVec; } Colored by Color Scripter cs
1 2 3 4 5 6 7 8 9 10 11 12 #include < iostream > #include "Vector1.h" using namespace std ; int main() { Vector1 vec1( 1 , 1 ); Vector1 vec2( 2 , 2 ); Vector1 vec = vec1 + vec2; cout < < vec.GetX() < < ", " < < vec.GetY(); //3, 3 } Colored by Color Scripter cs
2) 전역 함수로 연산자 오버로딩
- cout << Vector1; 하면 (10, 20)처럼 만들고 싶음 (ex. toString())
- cout에 들어가서 바꾸는 것은 불가능
-> 전역 함수를 만들어 연산자 오버로딩해야 함
-> 전역 함수는 private멤버에 접근할 수 없으므로 friend 키워드 필요
friend
- 클래스 정의 안에 friend 키워드 사용가능
-> 다른 클래스나 함수가 나의 private 또는 protected 멤버에 접근할 수 있게 허용
- friend 클래스
1 2 3 4 5 6 class X{ friend class Y; private : int mPrivateInt; }; // class Y에서 mPrivateInt 접근 가능 해짐 cs
- friend 함수
1 2 3 4 5 6 7 class X{ friend class Y; friend void Foo( const X & x); private : int mPrivateInt; }; // Foo()에서 mPrivateInt 접근 가능 해짐 cs
- cout << Vector1; 를 위한 구현
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //header class Vector1{ friend std ::ostream& operator < < ( std ::ostream & os, const Vector1 & rhs); public : private : int mX; int mY; }; //cpp std ::ostream& Vector1:: operator < < ( std ::ostream & os, const Vector1 & rhs){ os < < rhs.mX < < ", " < < rhs.mY; return os } Colored by Color Scripter cs
cf) 리턴자료형이 ostream& 인 것은 출력의 체이닝을 위함
-> os << Vector1 << endl;
-> 위의 예에서 os << Vector1 이 void라면 void << endl; 은 진행할 수 없음
-> 그래서 os를 반환함
cf) 좌항의 매개변수를 const로 하지 않은 이유는 읽기 전용으로 사용하지 않아서
-> 출력의 상태를 바꿔 버릴수도 있기 때문
cf) friend함수를 작성해야 class 구현부에서 작성하는 전역함수를 main에서 사용할 수 있음
cf) 만약 하지 않으면 컴파일에러
cf) friend함수를 작성하면 외부에서 Vector1:: 로 접근해야함 (11줄 참고)
(정리)전역함수로 연산자오버로딩을 위해서는
header파일에 전역함수를 friend키워드 사용or전역함수선언 -> 하지않으면 main쪽에서 찾지를 못함
1) header에 전역함수를 class멤버말고 전역으로 선언만 -> cpp에서 정의구현
2) header에 friend키워드 사용 -> cpp에서 정의구현
3) main에 사용
연산자 오버로딩과 const
const를 쓰는 이유? (차후 포스팅)
- 멤버 변수의 값이 바뀌는 것을 방지
- 최대한 많은 곳에 const를 붙일 것
-> 일단 const를 붙여서 사용 -> 에러면 바꿔라
- 지역변수(매개변수)에도 마찬가지
const& 를 쓰는 이유?
- 멤버변수가 바뀌고(const) 불필요한 객체의 사본이 생기는 것을 방지(&)
const를 언제 안쓸까?
- 오브젝트의 상태변경이 있을 때 (스트림도 마찬가지)
연산자 오버로딩 제한사항
1. 오버로딩된 연산자는 최소한 하나의 사용자정의 형을 가져야 함
2. 오버로딩된 연산자는 피연산자 수를 동일하게 유지해야 함
3. 새로운 연산자 부호를 만들 수 없음
4. 오버로딩할 수 없는 연산자 존재(. .* :: ?:)
연산자 오버로딩을 남용하지 말 것
- 직관적인 상황에서만 사용하고 직관적이지 못하면 그냥 함수 만들어라
대입 연산자
operator=
- 복사 생성자와 거의 동일 (초기화와 대입의 차이)
-> 복사 생성자는 생성당시에만 호출함
-> (String myStr = originStr : 이것도 복사생성자로 가져감, 선언 후 myStr = originStr으로 호출함)
-> 그래서 대입 연산자는 내가 힙에 가진 메모리를 해제해 줄 필요가 있을 수도
-> 자신이 원래 들고 있던 heap영역 메모리를 해제해야 하기 때문
ex)
MyString a("chararray"); //생성자 호출
MyString b("asdf");
a = b; // 대입연산자 + a가 가지고 있던 heap영역 메모리 해제
- 복사 생성자를 구현했다면 대입 연산자도 구현해야 좋음
-> 하는일이 비슷하니까!
암시적 operator=
- 컴파일러가 자동으로 만들어 줌
- 근데 얕은 대입임
-> 깊은 복사해야 함
암시적 함수
- 암시적 기본 생성자 : 아무런 생성자가 없으면 컴파일러가 만듦
- 암시적 복사 생성자 : 아무런 복사 생성자가 없으면 컴파일러가 만듦(얕은 복사)
- 암시적 소멸자 : 아무런 소멸자가 없으면 컴파일러가 만듦
- 암시적 대입 연산자 : 아무런 소멸자가 없으면 컴파일러가 만듦(얕은 복사)
암시적 함수를 제거하는 법(전통적...)
-암시적 기본 생성자 -> private에 넣어버림, 다른 생성자 구현
-암시적 복사 생성자 -> private에 넣어버림
-암시적 소멸자 -> private에 넣어버림
-암시적 대입 연산자 -> private에 넣어버림
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 #include < iostream > #include < cstring > #include "Tmp.h" namespace test1 { String::String( const char * str) //"123" : mSize(strlen(str) + 1 ) //"123" + 1 { mString = new char [mSize]; memcpy(mString, str, mSize); } String::String( const String & other) : mSize(other.mSize) { std :: cout < < "call copy structor" < < std :: endl ; mString = new char [mSize]; memcpy(mString, other.mString, mSize); } String::~String() { delete [] mString; } void String::Print() { std :: cout < < mString < < std :: endl ; } void String:: operator = ( const String & rhs) { std :: cout < < "call operator=" < < std :: endl ; mSize = rhs.mSize; delete [] mString; mString = new char [mSize]; memcpy(mString, rhs.mString, mSize); } } Colored by Color Scripter cs
===참고===
Q) 레퍼런스에 대해서 (반환, 매개변수 등등)
Q) const에 대해서 자세히
현재까지 객체지향
- 생성자 오버로딩
- 복사생성자 오버로딩
- 대입연산자 오버로딩
- 소멸자 구현 (가상소멸자)
- 전역함수로 연산자오버로딩해보기
아래 해더파일로 놀아보기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #pragma once class Test { //friend std::ostream& operator<<(std::ostream& os, const Test& rhs); public : Test( const char * str); Test( const Test & str); virtual ~Test(); void operator = ( const Test & rhs); void Print(); private : char * mTest; size_t mSize; }; std ::ostream & operator < < ( std ::ostream & os, const Test & rhs); cs
from http://malbongcode.tistory.com/56 by ccl(S) rewrite - 2020-03-25 15:54:09
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기