C++ 構造函數、析構函數、拷貝構造、賦值運算符


之所以要把它們放在一起,是因為在使用C/C++類語言的時候,很容易混淆這幾個概念(對Java來說完全沒有這樣的問題,表示Javaor完全沒有壓力)。

先建立一個測試類(包含.h和.cpp)

//~ Person.h

#ifndef PERSON_H_
#define PERSON_H_
#include <iostream>
class Person {
private:
    static int counter;
public:
    Person() {
        counter++;
        std::cout << "構造函數" << std::endl;
        std::cout << "counter:" << counter << std::endl;
    }
    Person(const Person& pr) {
        counter++;
        std::cout << "拷貝構造函數" << std::endl;
        std::cout << "counter:" << counter << std::endl;
    }
    Person& operator=(const Person& pr) {
        std::cout << "賦值運算函數" << std::endl;
        return *this;
    }
    virtual ~Person() {
        counter--;
        std::cout << "析構函數" << std::endl;
        std::cout << "counter:" << counter << std::endl;
    }
};

#endif /* PERSON_H_ */

//~ Person.cpp
#include "Person.h"

int Person::counter = 0;

通常重載賦值運算符容易遺忘,但是它真的很重要。所以推薦,如果你記得重載拷貝構造就一定要對賦值運算符做對應處理。

下面看看它們在什么情況下發生作用,然后再做簡要說明。

1.作為自動變量聲明

#include "Person.h"
int main() {
    Person p1;
}

運行結果:

構造函數
counter:1
析構函數
counter:0

說明:作為自動變量聲明的時候,p1變量保存在堆棧中,main函數結束的時候會自動調用析構函數。

2.作為自由變量聲明

#include "Person.h"
int main() {
    Person * p1 = new Person();
}

運行結果:

構造函數
counter:1

說明:自由變量聲明,p1變量保存在heap(堆)中,內存不會被自動釋放。因此必須在使用完成以后手工調用“delete p1”,切記。

3.使用另一個對象構造

#include "Person.h"
int main() {
    Person p1;
    Person p2 = p1;
}

運行結果:

構造函數
counter:1
拷貝構造函數
counter:2
析構函數
counter:1
析構函數
counter:0

說明:p1對象使用構造函數,p2對象使用拷貝構造。由於它們都是自動變量,因此在函數結束時會自動調用析構函數。

4.賦值運算

#include "Person.h"
int main() {
    Person p1;
    Person p2;
    p2 = p1;
}

運行結果:

構造函數
counter:1
構造函數
counter:2
賦值運算函數
析構函數
counter:1
析構函數
counter:0

說明:我特意把“賦值運算函數”加色。目的是讓讀者清楚賦值運算和拷貝構造本質上是不同的,因為他們調用不同的函數。雖然在大多數情況下,結果是相同的。

5.指針賦值

#include "Person.h"
int main() {
    Person* p1 = new Person;
    Person* p2 = p1;
p2 = p1;
delete p1; }

運行結果:

構造函數
counter:1
析構函數
counter:0

說明:這里發生的事情其實就是在Java中發生的事情。除了第一條語句使用了構造函數,第2和第3條語句都僅僅是做指針的賦值。

6.函數調用一

#include "Person.h"

Person func(Person p); // 調用對象原型

int main() {
    Person p; // 構造
    func(p);
} // 析構#2 和 析構p

Person func(Person p) { // 拷貝構造#1
    return p; // 拷貝構造#2
} // 析構#1

運行結果:

構造函數
counter:1
拷貝構造函數
counter:2
拷貝構造函數
counter:3
析構函數
counter:2
析構函數
counter:1
析構函數
counter:0

說明:函數需要調用對象原型,並且也返回一個對象原型。之所以叫返回 一個 對象原型是因為它真的是返回了另一個對象。

7.函數調用二

#include "Person.h"

Person& func(Person & p); // 調用對象引用

int main() {
    Person p;
    func(p);
}

Person& func(Person & p) {
    return p;
}

運行結果:

構造函數
counter:1
析構函數
counter:0

說明:調用引用不會構造新的對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM