構造函數就是定義了類的對象的初始化方式的函數,在初始化類的對象時,會被自動調用
構造函數無返回值,可以被重載(有多個構造函數,可以有多種初始化的方式,參考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
歡迎大家評論交流,作者水平有限,如有錯誤,歡迎指出