C++基礎學習筆記----第九課(建構函式)
本節主要講物件的初始化、建構函式的基本概念使用方法以及兩個特殊的建構函式,課後練習是手寫陣列類。
物件的初始化
所有的物件都需要一個確定的初始狀態。這樣我們可以使用一個initialize函式來進行初始化。物件在建立後可以呼叫這個函式進行初始化。
initialize在這裡只是一個普通的函式,必須進行呼叫,如果沒有呼叫,物件的結果將是不確定的。
這裡列印的三個值在VS2008的編譯環境下是一樣的,但是很明顯都是垃圾值。#include <stdio.h> class A { private: int a; int b; int c; public: void initialize() { a = 9; b = 8; c = 7; } int print() { printf ("a = %d\n", a); printf ("b = %d\n", b); printf ("c = %d\n", c); return 0; } }; int main() { A b; b.print(); return 0; }
initialize這個函式只是C++中的一種初始化函式,個人感覺在類的封裝以後應該沒有使用這種形式的初始化了,因為要額外的排程一次初始化函式。
建構函式
基本使用方法
建構函式的特點:①建構函式是類成員函式,比較特殊的類成員函式
②建構函式的名字與類的名字相同
③建構函式在定義的時候可以有引數,但是沒有任何返回型別的宣告
物件的記憶體分配並不是由建構函式來完成的,物件的記憶體分配是由編譯器來完成的,建構函式的作用是對物件本身進行初始化工作,也就是給使用者提供初始化類中成員變數的一種方式。
基本例程如下:
這裡建構函式初始化類的成員一共有兩種形式,分別是A b(1); A c =2;這兩種形式都是自動呼叫建構函式。還有一種是手動呼叫建構函式的形式,#include <stdio.h> class A { private: int a; int b; int c; public: A(int d) { a = b = c = d; } int print() { printf ("a = %d, b = %d, c = %d\n",a, b, c); return 0; } }; int main() { A b(1); A c = 2; b.print(); c.print(); }
A b = A(9);
成員函式的過載
類的成員函式和普通函式一樣可以進行過載,並遵守相同的過載規則。#include <stdio.h> class A { private: int a; int b; int c; public: A(int d) { a = b = c = d; } A() { a = b = c = 8; } int print() { printf ("a = %d, b = %d, c = %d\n",a, b, c); return 0; } void print(int x) { printf("x = %d", x); } }; int main() { A b = A(9); b.print(); return 0; }
拷貝建構函式和無參建構函式
無參建構函式是建構函式沒有引數,而拷貝建構函式是為了無參建構函式而生的。
例程如下:
#include <stdio.h>
class A
{
private:
int a;
int b;
int c;
public:
A()
{
printf (".....\n");
}
A (const A& i)
{
printf ("~~~~~\n");
}
void print(int x)
{
printf("x = %d", x);
}
};
int main()
{
A b1 ;
A b2 = b1;
return 0;
}
程式列印結果如下圖所示:
這裡的b1物件沒有進行初始化,這個時候無參的建構函式就預設對沒有進行初始化的物件進行初始化,而b2物件被b1賦值,相當於A b2 = 常量,這樣b2所對應的建構函式就應該是一個有參的過載建構函式,也就是A (const A& i)
當類中沒有定義任何一個建構函式,C++編譯器會為其提供無參建構函式和拷貝建構函式。當類中定義了任意的非拷貝建構函式的時候,C++編譯器不會為其提供無參建構函式。
關於拷貝建構函式的疑惑
拷貝建構函式就是對建構函式的一個複製,那麼A (const A& i)這個函式中的引數i就是新的類似類的物件,我們可以通過i直接呼叫原來的建構函式中的成員。基本例程如下: A()
{
int a;
printf (".....\n");
}
A (const A& i)
{
i.a = 2;
printf ("~~~~~\n");
}
上面的程式編譯將會報錯,因為變數a的作用域只在原函式的A()中,所以我們不能夠進行呼叫,但是我們可以對A函式中的printf進行呼叫。具體的拷貝建構函式還是不太理解,而且有很多其他的要求,例如深拷貝和淺拷貝,還有繼承類中的拷貝等等。參考其他部落格得到:
如果一個類中沒有定義任何的建構函式,那麼編譯器只有在以下三種情況,才會提供預設的建構函式:
1、如果類有虛擬成員函式或者虛擬繼承父類(即有虛擬基類)時;
2、如果類的基類有建構函式(可以是使用者定義的建構函式,或編譯器提供的預設建構函式);
3、在類中的所有非靜態的物件資料成員,它們對應的類中有建構函式(可以是使用者定義的建構函式,或編譯器提供的預設建構函式)。
#include <stdio.h>
class A
{
private:
int a;
int b;
int c;
public:
/*A()
{
//a = b = c = i;
}
A(const A& oj)
{
}*/
void print()
{
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
}
};
int main()
{
A b;
A c = b;
b.print();
c.print();
return 0;
}
課後練習
要求:建立一個數組類。
具體程式碼如下所示:
main.cpp
#include <stdio.h>
#include "a.h"
int main()
{
A a = 5;
for (int i = 0; i < a.getlength(); i++)
{
a.setdata(i,i);
printf ("%d\n",a.getdata(i));
}
A b = a;
for (int j = 0; j < b.getlength(); j++)
{
printf ("%d\n",b.getdata(j));
}
a.destroy();
b.destroy();
return 0;
}
a.cpp
#include "a.h"
A::A(int length)
{
if (length < 0) //進行安全性檢測
{
alength = 0;
}
alength = length;
/*申請大小為alength個int型別的空間*/
ap = new int[alength];
}
/*這裡不使用C++編譯器給的預設的拷貝建構函式*/
A::A(const A& aj)
{
alength = aj.alength;
//重新從堆上進行空間分配
ap = new int[alength];
/*依次進行值得拷貝*/
for(int i=0; i<alength; i++)
{
ap[i] = aj.ap[i];
}
}
int A::getlength()
{
return alength; //直接返回陣列的長度
}
void A::setdata(int index, int data)
{
ap[index] = data;
}
int A::getdata (int index)
{
return ap[index];
}
void A::destroy()
{
alength = -1; //將長度賦為-1,表示出錯
delete []ap; //對申請的堆空間進行釋放
}
a.h
#ifndef _A_H
#define _A_H
class A
{
private:
int alength;
int* ap;
public:
A(int length);
A(const A& aj);
int getlength();
void setdata(int index,int data);
int getdata (int index);
void destroy();
};
#endif
通過上面的a.cpp可以看出,這裡沒有使用預設的C++拷貝建構函式,因為預設的C++拷貝建構函式只是進行簡單的值複製,也就是將我們在建構函式中的陣列的大小和陣列的指標進行復制,經過拷貝建構函式的陣列和原來的陣列指向同一塊記憶體空間,而我們在主函式中對同一塊記憶體空間進行了兩次delete,所以程式會崩潰。解決辦法是不使C++編譯器預設的拷貝建構函式,在自己定義的拷貝建構函式中重新為第二個陣列分配記憶體空間。