C++拷貝構造函數的調用時機
一、拷貝構造函數調用的時機
當以拷貝的方式初始化對象時會調用拷貝構造函數,這裏需要注意兩個關鍵點,分別是以拷貝的方式
和初始化對象
1. 初始化對象
初始化對象是指,爲對象分配內存後第一次向內存中填充數據,這個過程會調用構造函數,對象被創建後必須立即初始化。也就是說只要創建對象就會調用構造函數。
2.初始化和賦值的區別
初始化和賦值都是將數據寫入內存中,從表面看,初始化在很多時候都是以複製的方式來實現的,很容易引起混淆。在定義的同時進行復制叫做初始化,定義完成以後再賦值(不管定義的時候有沒有賦值)就叫做賦值。初始化只能由一次,賦值可以由很多次。具體可以看下面的示例。
int a = 100; //以賦值的方式初始化
a = 200; //賦值
a = 300; //賦值
int b; //默認初始化
b = 29; //賦值
b = 39; //賦值
對於基本的數據類型,我們很少會區分初始化和賦值,即使是混淆了,也不會出現什麼錯誤。但是對於類,他們的區別就非常重要了,因爲初始化時會調用構造函數(以拷貝的方式初始化時會調用拷貝構造函數),而賦值時會調用重載過的賦值運算符。
二、拷貝構造函數和普通構造函數調用的例子
#include<iostream>
#include<string>
using namespace std;
class Student {
public:
Student(string name = "", int age = 0); //普通構造函數
Student(const Student &Stu); //拷貝構造函數
Student& operator=(const Student &Stu); // 重載 = 運算符
void display();
private:
string m_name;
int m_age;
};
//普通構造函數
Student::Student(string name , int age )
{
m_name = name;
m_age = age;
}
//拷貝構造函數
Student::Student(const Student &Stu)
{
this->m_name = Stu.m_name;
this->m_age = Stu.m_age;
cout << "Copy constructor was called." << endl;
}
// 重載 = 運算符
Student& Student:: operator=(const Student &Stu)
{
this->m_name = Stu.m_name;
this->m_age = Stu.m_age;
cout << "operator=() was called." << endl;
return *this;
}
void Student::display()
{
cout << m_age << " " << m_age << endl;
}
int main()
{
Student stu1("Xiao Ming", 18); // 調用普通構造函數
Student stu2("Xiao Wang", 18); // 調用普通構造函數
Student stu3 = stu1; // 調用拷貝構造函數
stu3 = stu2; //調用operator=()
Student stu4(stu1); // 調用拷貝構造函數
Student stu5; // 調用普通構造函數
stu5 = stu2; //調用operator=()
return 0;
}
/*
輸出:
Copy constructor was called.
operator=() was called.
Copy constructor was called.
operator=() was called.
*/
三、以拷貝的方式初始化對象
1. 初始化對象時會調用構造函數,不同的初始化方式會調用不同的構造函數:
- 如果用傳遞進來的實參初始化對象,那麼會調用普通的構造函數。
- 如果用現有對象的數據來初始化對象,就會調用拷貝構造函數,這就是以拷貝的方式初始化對象。
2. 以拷貝的方式來初始化對象的幾種情況:
將其它對象作爲實參。
Student stu1("Xiao Ming", 18); // 普通初始化 Student stu4(stu1); // 以拷貝的方式進行初始化 /* 即使我們不在類中顯示定義拷貝構造函數,這種初始化方式也是有效的,編譯器會生成默認的拷貝構造函數 */
在創建對象的同時賦值。
Student stu1("Xiao Ming", 18); // 普通初始化 Student stu3 = stu1; // 以拷貝的方式進行初始化 /* 這是最常見的一種以拷貝的方式初始化對象的情況 */
函數的形參爲類類型。
如果函數的形參爲類類型(對象),那麼調用函數時要將另外一個對象作爲實參傳遞進來賦值給形參,這也是以拷貝的方式初始化形參對象,如下所示。
void func(Student s){ //TODO: } Student stu1("Xiao Ming", 18); // 普通初始化 func(stu1); //以拷貝的方式初始化 /* func() 函數有一個 Student 類型的形參 s,將實參 stu 傳遞給形參 s 就是以拷貝的方式初始化的過程。 */
函數返回值爲類類型(與編譯器有關不絕對)
當函數的返回值爲類類型時,return 語句會返回一個對象,不過爲了防止局部對象被銷燬,也爲了防止通過返回值修改原來的局部對象,編譯器並不會直接返回這個對象,而是根據這個對象先創建出一個臨時對象(匿名對象),再將這個臨時對象返回。而創建臨時對象的過程,就是以拷貝的方式進行的,會調用拷貝構造函數,如下所示。
Student func(){ Student stu1("Xiao Ming", 18); // 普通初始化 return stu1; } Student stu = func();