C++知識點12——構造函數初步

構造函數就是定義了類的對象的初始化方式的函數,在初始化類的對象時,會被自動調用

構造函數無返回值,可以被重載(有多個構造函數,可以有多種初始化的方式,參考C++知識點4——vector與string簡述

class A
{
public:
	A(int a) {cout<<"a constructor"<<endl;}
	A(){cout<<__func__<<endl;}
	~A() {}
};

int main(int argc, char const *argv[])
{
	A a;
	A b(10);
	return 0;
}

 

注意:

注意區分下面兩行代碼

A a;
A a();

第一個是執行默認初始化,而第二個是聲明瞭一個函數,如果將main函數中默認初始化改爲第二種形式,程序不會打印構造函數中的log

int main(int argc, char const *argv[])
{
	A a();//僅僅是聲明瞭一個函數,內存中並沒有a對象
	A b(10);
	return 0;
}

上述代碼沒有生成a對象,所以,更不能用A a()來調用其他成員

 

構造函數不能是const的成員函數,因爲在構造函數中有可能會改變對象的成員,如果又將構造函數設爲const,那麼對象將無法被初始化

class A
{
public:
	A() const {}
	~A() {}
};

 

如果類中沒有自己定義構造函數,那麼編譯器會自動生成一個構造函數(A() {}),並且類的對象在在初始化時,執行默認初始化(用默認構造函數初始化對象)

但是,最好不要使用編譯器提供的默認構造函數

class A
{
public:
	int &b;
};

int main(int argc, char const *argv[])
{
	A a;
	return 0;
}

上述代碼類中定義了一個引用,但是使用默認的構造函數並沒有初始化引用,錯過了引用初始化的唯一機會另外,導致程序報錯,如果是類中定義了一個指針,也會造成未初始化的指針

 

如果想讓錯誤消失,一種方法就是不定義引用,二是自己定義構造函數,下面代碼展示了第二種辦法

class A
{
public:
	A():a(10), b(a){cout<<__func__<<endl;}

	int &b;
	int a;
};

int main(int argc, char const *argv[])
{
    A a;
	cout<<a.b<<endl;
	return 0;
}

冒號後面的就是初始化列表,用逗號隔開

 

構造函數的初始化列表與初始化順序

儘可能使用初始化列表初始化變量,因爲這是初始化成員變量的唯一機會,不要在構造函數中對成員變量進行賦值,因爲有些變量無法賦值

class A
{
public:
	A();
    const int a;
};

A::A()
{
	a=10;
	cout<<__func__<<endl;
}

int main(int argc, char const *argv[])
{
	A a;
	return 0;
}

上述代碼在編譯時提示沒有初始化const int,因爲該成員唯一的初始化機會就是在構造函數的初始化列表中,當在構造函數中對a進行“初始化”時,其實是賦值操作,但是const變量無法重新賦值

正確做法就是將a在初始化列表中初始化

 

構造函數初始化成員變量的順序是按照成員定義的順序來初始化的

class A
{
public:
	A();
	~A() {}
    const int a;
    int b;
    int c;
};

A::A():a(10),b(5),c(1)
{
	cout<<__func__<<endl;
}

int main(int argc, char const *argv[])
{
	A a;
	cout<<a.a<<a.b<<a.c<<endl;
	return 0;
}

上述代碼中成員變量的初始化順序是a,b,c(因爲a最前,b次之,c最後),所以,在b初始化時,c中的值不確定,所以不能用c初始化b,將構造函數改成如下形式

A::A():a(10),b(c),c(1)
{
	cout<<__func__<<endl;
}

可見,b的值是一個無效值,因爲用c的無效值初始化

所以,在構造函數進行成員函數初始化時,最好不要讓成員變量彼此初始化,如果必須這樣,那麼請注意成員函數的初始化順序

 

explicit關鍵與構造函數

explicit關鍵字用來修飾構造函數,加上該關鍵字的構造函數不能執行隱式轉換,具體例子見如下代碼

class A
{
public:
	/*explicit*/A(int a):mem(a) {cout<<"a constructor"<<endl;}
	A():mem(0){cout<<__func__<<endl;}
	~A() {}
	A add(const A &one, const A & two);
	int mem;
};

A A::add(const A &one, const A & two)
{
	A tmp;
	tmp.mem=one.mem+two.mem;
	return tmp;
}

int main(int argc, char const *argv[])
{
	A one,two;
	one.mem=10;
	two.mem=20;
	A res1=one.add(one, two);
	cout<<res1.mem<<endl;
	A res2=one.add(10, 20);
	cout<<res2.mem<<endl;
	return 0;
}

構造函數A(int a) 沒有用explicit修飾,所以當執行第25行代碼時,將10和20從int型數據隱式轉化爲兩個類A的臨時對象,並輸出兩次log,然後將這兩個臨時對象傳入add函數

但是如果將構造函數A(int a)前添加explicit,那麼上述代碼就會報錯,因爲加上explicit關鍵字後,會防止上述過程

調用add時,提示函數匹配錯誤

 

此外,加上explicit修飾構造函數後,初始化對象只能用直接初始化,不能拷貝初始化

class A
{
public:
	explicit A(int a):mem(a) {cout<<"a constructor"<<endl;}
	A():mem(0){cout<<__func__<<endl;}
	~A() {}
	int mem;
};

int main(int argc, char const *argv[])
{
	A one(10);
	A two=20;
	return 0;
}

編譯上述代碼時,會提示轉化錯誤

解決辦法:

1.將explicit關鍵字去掉。2.將13行註釋掉。3.使用static_cast轉換,4。顯式構造一個對象

解決辦法3的代碼

int main(int argc, char const *argv[])
{
	A one(10);
	A two=static_cast<A>(20);
	return 0;
}

 

解決辦法4的代碼

class A
{
public:
	explicit A(int a):mem(a) {cout<<"a constructor"<<mem<<endl;}
	A():mem(0){cout<<__func__<<endl;}
	~A() {}
	A add(const A &one, const A & two);
	int mem;
};

A A::add(const A &one, const A & two)
{
	cout<<__func__<<endl;
	A tmp;
	tmp.mem=one.mem+two.mem;
	return tmp;
}

int main(int argc, char const *argv[])
{
	A res2=res2.add(A(10), A(20));//創建兩個A的對象,調用順序從右向左
	cout<<res2.mem<<endl;
	return 0;
}

上述的輸出結果爲編譯器優化的結果,關於編譯器優化的具體內容見博客https://blog.csdn.net/davidhopper/article/details/90696200,寫的很好

 

將上述代碼關閉編譯器優化的命令如下

 g++ -g -fno-elide-constructors -Wall constructor.cpp

輸出結果如下

其中,調用了兩次拷貝構造函數,第一次是函數add返回時調用的,第二次是res2初始化的時候調用的,因爲在拷貝構造函數中沒有進行賦值操作,所以打印結果是mem的初始值0

 

如果在拷貝構造函數中添加賦值操作

mem=t.mem;

打印結果如下

上過過程的討論見https://bbs.csdn.net/topics/396829997

 

參考:

《C++ Primer》

https://blog.csdn.net/davidhopper/article/details/90696200

 

歡迎大家評論交流,作者水平有限,如有錯誤,歡迎指出

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章