C ++ (10) 클래스 및 동적 메모리 할당 재 학습


이 장에서는 클래스에 대해 new 및 delete를 사용하는 방법과 동적 메모리 사용으로 인해 발생하는 몇 가지 미묘한 문제를 처리하는 방법을 소개합니다.

1. 동적 메모리 및 클래스

C ++는 new 및 delete 연산자를 사용하여 메모리를 동적으로 제어하지만 클래스에서 이러한 연산자를 사용하면 많은 새로운 프로그래밍 문제가 발생합니다. 이 경우 소멸자가 필요합니다. 때로는 프로그램이 실행되도록 할당 연산자를 리팩토링해야합니다.

1.1 특수 멤버 기능

C ++는 다음 멤버 함수를 자동으로 제공합니다.

  • 생성자가 정의되지 않은 경우 기본 생성자입니다.
  • 정의되지 않은 경우 기본 소멸자입니다.
  • 정의되지 않은 경우 생성자를 복사합니다.
  • 정의되지 않은 경우 할당 연산자.
  • 정의되지 않은 경우 주소 연산자입니다.

C ++ 11은 두 가지 다른 특수 멤버 함수 인 이동 생성자와 이동 할당 연산자를 제공합니다. 이에 대해서는 C ++ 입문서 18 장에서 설명합니다.

(1) 기본 생성자

생성자가 제공되지 않으면 C ++는 기본 생성자를 생성하지만 생성자가 있으면 더 이상 기본 생성자를 제공하지 않습니다. 기본 생성자 양식은 다음과 같습니다.

Klunk::Klunk(){
    
     };

그런 다음 기본 생성자로 객체를 정의 할 수 있습니다.

Klunk lunk;//调用默认构造函数

매개 변수화 된 생성자는 모든 매개 변수에 기본값이있는 한 기본 생성자가 될 수도 있습니다. 이 경우 모호성을 유발하므로 매개 변수없이 생성자를 더 이상 정의 할 수 없습니다. 매개 변수가있는 기본 생성자의 형식은 다음과 같습니다.

Klunk(int n = 0){
    
    klunk_ct = n;}

(2) 복사 생성자

복사 생성자는 새로 생성 된 객체에 객체를 복사하는 데 사용되며 일반 할당 과정이 아닌 초기화 과정에서 사용됩니다. 함수 프로토 타입은 일반적으로 다음과 같습니다.

Class_name(const Class_name &);

호출시기와 기능을 알아야합니다.

(3) 복사 생성자 호출시기

새 객체를 생성하고 동일한 유형의 기존 객체로 초기화 할 때 복사 생성자가 호출 됩니다. 좌우명이 StringBad 객체라고 가정하면 다음 네 가지 경우에 복사 생성자가 호출됩니다.

StringBad ditto(motto);//calls StringBad( const StringBad &)
StringBad metoo = motto;//calls StringBad( const StringBad &)
StringBad also = StringBad(motto);//calls StringBad( const StringBad &)
StringBad * pStringBad = new StringBad(motto);//calls StringBad( const StringBad &)

중간 두 선언은 복사 생성자를 사용하여 metoo를 직접 생성하거나 복사 생성자를 사용하여 임시 객체를 생성 한 다음 특정 구현에 따라 임시 객체의 내용을 metto 및 또한 할당 할 수 있습니다. 마지막 선언 유형은 모토를 사용하여 익명 개체를 초기화하고 새 개체의 주소를 pStringBad 포인터에 할당합니다.

프로그램이 객체의 복사본을 만들 때마다 컴파일러는 복사 생성자를 사용합니다. 예를 들어, 함수가 값으로 객체를 전달하거나 객체를 반환 할 때 복사 생성자가 사용됩니다. 또 다른 예로, 3 개의 Vector 객체가 추가되면 컴파일러 (컴파일러에 따라 다름) 중간 결과를 저장하기 위해 임시 Vector 객체를 생성 할 수 있습니다.

값으로 객체를 전달하면 복사 생성자가 호출되므로 객체는 참조로 전달되어야합니다. 새 개체를 저장할 시간과 공간을 절약하십시오.

(4) 기본 복사 생성자의 기능

기본 복사 생성자는 비 정적 멤버를 하나씩 복사하고 (멤버 복사본은 단순 복사라고도 함) member의 값을 복사합니다 .

일반적인 타입 복사 값이라면 언급 할 가치가 없지만, char * str;과 유사한 포인터가 클래스에 정의되어 있다면 그 값은 주소입니다! 특히 두 개의 객체가 정의되어있는 경우 프로그램 종료시 소멸자는 두 번 호출되며, 소멸자에 의해 포인터가 삭제되면 포인터가 두 번 삭제되어 프로그램이 비정상적으로 종료 될 수 있습니다.

(5) 문제를 해결하기 위해 명시 적 복사 생성자를 정의합니다 (딥 복사).

클래스 디자인에서이 문제에 대한 해결책은 딥 카피를 수행하는 것입니다. 즉, 각 개체에는 다른 개체를 참조하는 문자열이 아니라 자체 문자열이 있습니다. 다음과 같이 StringBad의 복사 생성자를 작성할 수 있습니다.

StringBad::Stringbad( const StringBad& st)
{
    
    
	len = st.len;
	str = new char [len + 1];//新地址
	std::strcpy(str, st.str);//将对象的字符串复制给新对象,而不是指针复制!!!
}

복사 생성자를 정의해야하는 이유는 일부 클래스 멤버가 데이터 자체가 아니라 new로 초기화 된 데이터에 대한 포인터이기 때문입니다.

클래스에 new로 초기화 된 포인터 멤버가 포함되어있는 경우 포인터 대신 지정된 데이터를 복사하도록 복사 생성자를 정의해야합니다.이를 deep copy 라고 합니다. 또 다른 형태의 복사 (멤버 복사 또는 얕은 복사 )는 포인터 값을 복사하는 것입니다. 얕은 복사는 포인터 정보를 표면적으로 만 복사합니다.

(6) 할당 연산자

C ++에서는 클래스에 대한 복사 연산자를 자동으로 오버로드하여 클래스 객체를 할당 할 수 있습니다 . 프로토 타입은 다음과 같습니다.

Class_name& operator= (const Class_name &);

기존 개체를 다른 개체에 할당 할 때 오버로드 된 할당 연산자가 사용됩니다.

StringBad headlin1("Celery Stalks");
StringBad kont;
knot = headline1;//调用赋值运算符

객체를 초기화 할 때 할당 연산자가 반드시 사용되는 것은 아닙니다. 복사 생성자를 호출합니다.

StringBad metoo = knot;

이 유형의 = 할당은 다음과 같이 수행 할 수도 있습니다. 먼저 복사 생성자를 사용하여 임시 개체를 만든 다음 할당을 통해 임시 개체의 값을 새 개체에 할당합니다. 즉, 초기화는 항상 복사 생성자를 호출하고 할당 연산자는 = 연산자가 사용될 때 호출 될 수도 있습니다.

복사 생성자와 마찬가지로 할당 연산자의 암시 적 구현은 멤버 복사 작업 이지만 정적 데이터 멤버는 영향을받지 않습니다. 멤버 자체가 클래스 객체 인 경우 프로그램은이 클래스에 대해 정의 된 할당 연산자를 사용하여 멤버를 복사합니다.

할당 연산자는 얕은 복사에 속하는 복사 생성자의 문제와 동일하므로 포인터 문제를 해결하기 위해서는 할당 연산자 (딥 복사)의 정의를 제공해야합니다. 그러나 몇 가지 차이점이 있습니다.

  • 대상 객체는 이전에 할당 된 데이터를 참조 할 수 있으므로 함수는 이러한 데이터를 해제하기 위해 delete []를 사용해야합니다.
  • 함수는 객체를 자신에게 할당하지 않아야합니다. 그렇지 않으면 객체를 재 할당하기 전에 메모리 해제 작업이 객체의 내용을 삭제할 수 있습니다.
  • 이 함수는 호출 개체에 대한 참조를 반환합니다.

StringBad 클래스에 대한 할당 연산자를 작성합니다.

StringBad& StringBad::operator = (cosnt StringBad& st)
{
    
    
	if(this == &st)
		return *this;//避免赋值给自己
	delete [] str;//删除以前的值
	len = st.len;
	str = new char [len + 1];
	std::strcpy(str, st.str);//复制字符串
	return *this;
}

2. 개선 된 새로운 String 클래스

String 클래스는 표준 문자열 함수 cstring의 모든 함수를 포함해야합니다. 여기서는 String 부분의 작동 원리 만 소개합니다 (String 클래스는 예시를위한 예일 뿐이며 C ++ 표준 문자열 클래스에는 많은 내용이 있습니다).

int length() const{
    
    return len;}//和size()一样
friend bool operator<(const String &st1, const String &st2);
friend bool operator>(const String &st1, const String &st2);
friend bool operator==(const String &st1, const String &st2);
friend operator>>(istream& is, String &st);
char& operator[] (int i);
const char& operator[] (int i) const;
static int HowMany();

구체적인 구현 방법은 여기서 자세히 설명하지 않습니다. 이해가 필요한 경우 책의 12.2 장을 참조하십시오.

3. 생성자에서 new를 사용할 때주의해야 할 사항

new를 사용하여 객체의 포인터 멤버를 초기화 할 때 특별한주의가 필요하다는 것을 알고 있습니다.

  • 생성자에서 new를 사용하여 포인터 멤버를 초기화하는 경우 소멸자에서 delete를 사용해야합니다.
  • new 및 delete는 서로 호환되어야합니다. new는 삭제에 해당하고 new []는 삭제 []에 해당합니다.
  • 생성자가 여러 개인 경우 괄호를 포함하거나 포함하지 않고 동일한 방식으로 new를 사용해야합니다. 소멸자가 하나뿐이기 때문입니다.
  • 전체 복사를 통해 한 개체를 다른 개체로 초기화하려면 복사 생성자를 정의해야합니다.
  • 깊은 할당을 통해 한 개체를 다른 개체에 할당하려면 할당 연산자를 정의해야합니다.

NULL, 0 또는 nullptr :

  • 과거에는 널 포인터가 0 또는 NULL로 표시 될 수 있습니다 (많은 헤더 파일에서 NULL은 0으로 정의 된 기호 상수입니다).
  • C 프로그래머는 일반적으로 0 대신 NULL을 사용하여 이것이 포인터임을 표시합니다. 마치 0 대신 '\ 0'을 사용하여 이것이 문자임을 나타내는 널 문자를 표시하는 것과 같습니다.
  • C ++는 전통적으로 동등한 NULL 대신 단순한 0을 사용하는 것을 선호합니다.
  • C ++ 11은 키워드 nullptr을 제공합니다.

클래스 멤버가 문자열 유형 인 경우 복사 또는 할당은 멤버 유형에 의해 정의 된 복사 생성자와 할당 연산자를 사용하기 때문에 일반 변수 복사 또는 할당과 유사합니다.

4. 반환 된 개체에 대한 설명

멤버 함수 또는 일반 함수가 객체를 반환 할 때 선택할 수있는 몇 가지 반환 메서드가 있습니다. 객체에 대한 참조, 객체에 대한 const 참조 또는 const 객체를 반환합니다. 처음 두 가지는 지금까지 소개되었고 세 번째는 아직 소개되지 않았습니다.

4.1 const 객체에 대한 참조 반환

const 참조를 사용하는 일반적인 이유는 효율성 향상입니다. 같은

//第一种情况
Vector Max(const Vector & v1, const Vector& v2)
{
    
    
	if(v1.magval() > v2.magval() )
		return v1;
	else
		return v2;
}
//第二种情况
const Vector&  Max(const Vector & v1, const Vector& v2)
{
    
    
	if(v1.magval() > v2.magval() )
		return v1;
	else
		return v2;
}

위의 두 가지 방법은 괜찮지 만 참조를 반환하는 두 번째 방법이 더 효율적입니다. 객체를 반환하는 첫 번째 방법은 복사 생성자를 호출하는 것이지만 참조를 반환하는 것은 그렇지 않습니다.

그러나 참고 :

  • 첫째, 반환이 가리키는 객체는 호출 함수가 실행될 때 존재해야합니다.
  • 두 번째 : v1과 v2는 모두 const 참조로 선언되므로 일치하려면 반환 유형이 const 여야합니다.

4.2 상수가 아닌 객체에 대한 참조 반환

상수가 아닌 객체가 반환되는 두 가지 일반적인 상황 :

  • 첫째 : 할당 연산자 오버로딩. 연속 할당을 허용합니다.
  • 둘째 : cout과 함께 사용되는 << 연산자. 지속적으로 출력 할 수 있습니다.

둘 다 참조를 사용하여 복사 생성자를 호출하여 새 String 객체를 생성하지 않도록합니다.

4.3 반환 개체

반환 된 객체가 호출 된 함수의 로컬 변수 인 경우 호출 된 함수가 완료되면 로컬 객체가 소멸자를 호출하므로 참조로 반환해서는 안됩니다. 따라서 제어가 호출 함수로 반환되면 참조가 가리키는 개체가 더 이상 존재하지 않습니다.

오버로드 된 산술 연산자는 두 개체를 추가하는 등이 상황을 출력하며 임시 개체를 만들어야합니다. 이 오버 헤드는 불가피합니다.

4.4 const 객체 반환

더하기와 같은 위의 산술 연산자는 임시 개체를 생성하므로 여러 더하기 연산 (obj1 + obj2 + obj3)을 사용할 수 있습니다. 이 경우 다음 표현이 합법화됩니다.

obj1 + obj2 = obj3;//加法产生临时对象

이것이 일어날 것이 걱정된다면 반환 유형을 const로 선언하는 간단한 해결책이 있습니다. const Vector :: operator와 같은.

5. 개체에 대한 포인터 사용

5.1 신규에 대해 이야기하고 다시 삭제

소멸자는 다음과 같은 경우에 호출됩니다.

  • 개체가 동적 변수 (예 : 임시 변수) 인 경우 개체를 정의하는 프로그램 블록이 실행될 때 개체의 소멸자가 호출됩니다.
  • 개체가 정적 변수 인 경우 개체의 소멸자는 프로그램이 끝날 때 호출됩니다.
  • 객체가 new로 생성 된 경우 삭제로 객체를 명시 적으로 삭제할 때만 소멸자가 호출됩니다.

5.2 포인터 및 개체 요약

개체 포인터를 사용할 때주의해야 할 몇 가지 사항이 있습니다.

  • 객체에 대한 포인터를 선언하려면 일반적인 표기법을 사용하십시오.
    string * glamour;
  • 기존 객체를 가리 키도록 포인터를 초기화 할 수 있습니다.
    string * first = & sayings [0];
  • new를 사용하여 포인터를 초기화하면 새 객체가 생성됩니다.
    string * favorite = new string (sayings [choice]);
  • 클래스에서 new를 사용하면 해당 클래스 생성자를 호출하여 새로 생성 된 객체를 초기화합니다.
    string * gleep = new string;
    string * glop = new string ( "hello hello");
    string * favorite = new string (sayings [choice]) ;
  • -> 연산자를 사용하여 포인터를 통해 클래스 메서드에 액세스 할 수 있습니다.
    favorite-> length ();
  • 객체 포인터에 역 참조 연산자 (*)를 적용하여 객체를 가져올 수 있습니다.
    if (sayings [i] <* first) // Compare two strings
    first = & saying {i};

6. 시뮬레이션 대기열

큐는 (FIFO, 선입 선출), 큐 클래스를 생성합니다 (표준 클래스는 큐입니다). 우리는 ATM 자동 인출 시뮬레이션 대기열 프로그램을 작성하기 위해 여기에 있습니다. 주요 작업은 고객 클래스 설계, 고객과 대기열 간의 상호 작용을 시뮬레이션하는 프로그램 작성입니다.

생성자가 private로 설정된 경우 다음 메서드는 허용되지 않습니다 (또한 교과서의 18 장에 다른 메서드가 도입되어 키워드 delete를 추가 함).

Queue snick(nip);//不允许
tuck = nip;//不允许

첫째, 위의 방법은 자동으로 생성 된 기본 방법 정의를 피합니다. 둘째, 이러한 방법은 비공개이므로 널리 사용할 수 없습니다.


개요 카탈로그
이전 : (9) 클래스 사용
다음 : (11) 클래스 상속


문서 참조 : "C ++ Primer Plus Sixth Edition"

추천

출처blog.csdn.net/QLeelq/article/details/112599895