一、構造函數
1、構造函數定義
構造函數函數名與類名相同,無返回值類型(void也不可以),在創建對象時自動執行。
#include <iostream>
using namespace std;
class Person{
private:
char *m_name;
int m_age;
public:
//聲明構造函數
Person(char *name, int age);
//聲明普通成員函數
void show();
};
//定義構造函數
Person::Person(){
m_name="小孔";
m_age =20;
}
Person::Person(char *name, int age){
m_name = name;
m_age = age;
}
//定義普通成員函數
void Person::show(){
cout<<m_name<<"\t"<<m_age<<"\t"<<endl;
}
int main(){
//創建對象時向構造函數傳參,調用帶參構造
Person p1("小劉", 15);
p1.show();
//創建對象時向構造函數傳參
Person *p2 = new Person("小王", 16);
p2 -> show();
//調用無參構造
Person p3;
p3.show();
return 0;
}
構造函數應爲共有屬性,否則在創建對象時就無法調用該構造函數。
構造函數的作用是給類中的成員賦值,調用該構造函數,只需要在創建對象的同時傳參賦值,就像上例創建的對象p1,p2.只不過在棧上創建對象時,實參位於對象名後面,在堆上創建對象時,實參位於類名後面。
2、函數重載
構造函數是可以重載的,就像上例中的構造函數重載,一個該有形參,一個沒有形參。
構造函數的調用是強制性的,**一旦在類中定義了構造函數,那麼創建對象時就一定要調用,不調用是錯誤的。**如果有多個重載的構造函數,那麼創建對象時提供的實參必須和其中的一個構造函數匹配,這也就意味着,創建對象時只有一個構造函數會被調用。
在調用帶形參的函數時,需要在創建對象時,傳遞實參;而在調用沒有參數的構造時,可以直接創建對象,並且不用加(),這就表示調用的是不帶參數的構造。
3、默認構造函數
在c++類中會存在着一個默認構造函數,如果程序員在類的成員中沒有編寫構造函數,那麼系統會自動生成空的構造函數體,儘管這個函數不執行任何操作。
一個類必須有構造函數,要麼用戶自己定義,要麼編譯器自動生成。
如果用戶自己定義了構造函數,那麼編譯器便不再自動生成構造函數。
4、構造函數初始化列表
#include <iostream>
using namespace std;
class Person{
private:
char *m_name;
int m_age;
public:
Person(char *name, int age);
void show();
};
//採用初始化列表
Person::Person(char *name, int age): m_name(name), m_age(age) {
}
void Person::show(){
cout<<m_name<<m_age<<endl;
}
int main(){
Person p1("小王", 15);
p1.show();
Person *p2 = new Person("小美",16);
p2 -> show();
return 0;
}
初始化列表的作用是對類內的成員變量進行初始化,寫法如上例,是在函數首部與函數體之間添加了一個冒號:
,隨後用類內成員(形參)
的寫法,表示將形參值傳遞給該類內成員。初始化列表可以用於全部成員變量,也可以只用於部分成員變量,用於部分成員變量時,只需要在:
後,寫出所要初始化的變量即可。
成員變量的初始化順序與初始化列表中列出的變量的順序無關,它只與成員變量在類中聲明的順序有關。
初始化列表還有一個作用是給const成員變量賦值,初始化 const 成員變量的唯一方法就是使用初始化列表。
二、拷貝構造
拷貝構造是構造函數的延伸,拷貝構造 是一種特殊的構造函數 ,用自身這種類型來構造自身
用戶未定義拷貝構造,系統默認提供一個隱式的拷貝構造,它會將已存在於對象中的數據成員逐個的拷貝到新創建的對象中(淺拷貝)
拷貝構造:類名 (const 類名& 引用名)
;
如果類中存在動態申請內存,就必須要重寫拷貝構造,來做深拷貝。不然兩個類中的指針會指向同一個地址,在釋放的時候會造成錯誤。
淺拷貝:如果要拷貝的對象內存在動態申請的內存,那麼淺拷貝之後,新對象的指針指向要拷貝的對象的內存地址。
深拷貝:如果要拷貝的對象內存在動態申請的內存,那麼調用深拷貝構造函數,爲新對象申請內存,並保存數據。
class Student
{
private:
char* m_name;
public:
Student(const char* name);
Student(const Student& stu);
void show();
~Student();
};
Student::Student(const char *name)
{
cout << "帶參構造" << endl;
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}
Student::Student(const Student& stu)
{
cout << "拷貝構造" << endl;
this->m_name = new char[strlen(stu.m_name) + 1];
strcpy(this->m_name, stu.m_name);
}
void Student::show()
{
cout << m_name << endl;
}
Student::~Student()
{
cout << "析構" << endl;
if (m_name!=NULL)
{
delete[]m_name;
m_name = NULL;
}
}
int main()
{
Student stu1("小王");
Student stu2(stu1);
stu1.show();
stu2.show();
system("pause");
return 0;
}
在存在動態內存申請的時候,需要調用拷貝函數,否則執行析構函數,釋放內存時,前一個指針釋放內存後,後一個指針沒有內存可以釋放,會引發異常。
三、 析構函數
創建對象時系統會自動調用構造函數進行初始化工作,同樣,銷燬對象時系統也會自動調用一個函數來進行清理工作,這個函數就是析構函數。
#include<iostream>
using namespace std;
class student
{
private:
int m_a;
int m_b;
public:
student(int a,int b);
~student();
};
student::student(int a, int b): m_a(a), m_b(b){}
student::~student()
{
cout << m_a << "\t" << m_b << endl;
}
int main()
{
student stu(1,2);
system("pause");
return 0;
}
上例可見析構函數的形式:~類名(){}
析構函數沒有參數,不能被重載,一個類只能有一個析構函數。如果用戶沒有定義,編譯器會自動生成一個默認的析構函數。
析構函數的調用條件:
在所有函數之外創建的對象是全局對象,它和全局變量類似,位於內存分區中的全局數據區,程序在整體結束執行時會調用這些對象的析構函數。
在棧區系統開闢的內存,系統會自動釋放並調用析構函數。
在堆區手動開闢的內存,手動delete釋放時,纔會調用析構函數