C++:類與物件(最終)
前兩篇關於類與物件的部落格,都是類與物件中不可或缺的物件,這篇就是在前兩篇的基礎上,再對類與物件進行補充。
一.簡識深淺拷貝
當我們進行拷貝造作函式,或者賦值運算子過載的時候,我們不給出這兩個函式,編譯器就會預設自動生成,預設對類進行位拷貝(按照基本型別進行值的拷貝)。
那麼編譯器給的到底有沒有問題呢?
看程式碼:
class Slist { public: Slist() { } Slist(int size,int cap) { int* arr = (int*)malloc(sizeof(int)*cap); _arr = arr; _size = size; _cap = cap; } void PopSlsit(Slist& p,int data) { p._arr[p._size] = data; p._size++; } void print() { for (int i = 0; i < _size; i++) { cout << _arr[i] << endl; } } ~Slist() { if (_arr) { free(_arr); _arr = NULL; } } private: int* _arr; int _size; int _cap; }; int main() { Slist s1(0, 10); Slist s2; s1.PopSlsit(s1, 1); s1.PopSlsit(s1, 2); s1.PopSlsit(s1, 3); s2 = s1; s2.print(); system("pause"); return 0; }
上邊的程式碼能正確的打印出 1,2,3,但是最後就會觸發一個斷點,為什麼會觸發一個斷點?
通過除錯,我們看到當s1釋放之後,s2的_arr也被釋放,所以就是對同一個地址進行了兩次釋放,所以就會出錯。
在拷貝的時候只拷貝地址,而並未複製資源,我們就成之為淺拷貝;
如果一個類擁有資源,當這個類的物件發生複製過程的時候,資源也進行相應複製,這個 過程就可以叫做深拷貝。
如圖:
二.類的const成員
1.const修飾普通變數
在C++中,const修飾的變數已經為一個常量,具有巨集的屬性,即在編譯期間,編譯器會將const所修飾的常量進行替換。
int main()
{
const int a = 10;
a = 30;//錯誤 error C3892: “a”: 不能給常量賦值
system("pause");
return 0;
}
2.const修飾類成員
(1)const修飾類成員變數時,該成員變數必須在建構函式的初始化列表中初始化
(2) const修飾類成員函式,實際修飾該成員函式隱含的this指標,該成員函式中不能對類的任何成員進行修改
看下邊程式碼:
class Date { public: void print() { _year = 2018; _month = 11; _day = 1; cout << _year << " " << _month << " " << _day << " " << endl; } void print()const//相當於 void print (const Date* this) { _year = 2018;//出錯 _month = 11;//出錯 _day = 1;//出錯 cout << _year << " " << _month << " " << _day << " " << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.print(); const Date d2; d2.print(); system("pause"); return 0; }
思考題:
1. const物件可以呼叫非const成員函式和const成員函式嗎?
2. 非const物件可以呼叫非const成員函式和const成員函式嗎?
3. const成員函式內可以呼叫其它的const成員函式和非const成員函式嗎?
4. 非const成員函式內可以呼叫其它的const成員函式和非const成員函式嗎?
class Date
{
public:
void print()
{
_year = 2018;
_month = 11;
_day = 1;
cout << _year << " " << _month << " " << _day << " " << endl;
}
void print1()const
{
cout << _year << " " << _month << " " << _day << " " << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
const Date d1;
//d1.print();//不能通過編譯,error C2662: “void Date::print(void)”: 不能將“this”指標從“const Date”轉換為“Date &”
d1.print1();
Date d2;
d2.print();
d2.print1();
system("pause");
return 0;
}
class Date
{
public:
void print()
{
cout << _year << " " << _month << " " << _day << " " << endl;
}
void print1()const
{
cout << _year << " " << _month << " " << _day << " " << endl;
}
void test()
{
print();
print1();
}
void test()const
{
//print();//error C2662: “void Date::print(void)”: 不能將“this”指標從“const Date”轉換為“Date &”
print1();
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.test();
const Date d2;
d2.test();
system("pause");
return 0;
}
通過上述的兩個程式碼:
const物件只能呼叫const修飾的成員函式;
非const物件可以呼叫const修飾的成員函式,也可以呼叫非const修飾的成員函式。
在const成員函式內,只能呼叫const修飾的成員函式
在非const成員函式內,既可以呼叫const修飾的成員函式,也可以呼叫非const修飾的成員函式。
三.再談建構函式
前邊說過的建構函式,由於能給成員多次賦值,所以只能稱之為賦初值,而不是初始化(初始化只能有一次)。
1.初始化列表:在建構函式後邊加一個冒號,後邊在跟用逗號隔開的所有資料成員列表,並在每一個“成員變數”後面跟一個放在括號中的初始值或者表示式。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
特性:
(1)每個成員在初始化列表中只能出現依次,也就是初始化只進行依次。
(2)初始化列表只能用於初始化資料成員,資料成員在類中的定義順序就是初始化列表中的初始化順序,否則就會出現混亂;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
const int _year;
int _month;
int _day;
};
class Date1
{
public:
Date1(int year, int day, int month)
:_year(year)
, _month(month)
, _day(day)
{}
private:
const int _year;
int _month;
int _day;
};
int main()
{
Date d(2018,11,2);
Date1 d1(2018, 11, 2);
system("pause");
return 0;
}
(3)儘量避免用成員初始化成員,成員的初始化順序最好和成員的定義順序一致;
(4)如果類中有以下三個成員,一定要在初始化列表中進行初始化:
。引用成員變數
。const成員變數
。類型別的成員
2.建構函式的作用
(1)構造物件
(2)給物件中成員變數一個初始值
(3)型別轉化
對於單參建構函式,可以接受的引數轉化成類型別的物件
class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2018);
int a = 2019; //把整形變數給日期型別物件賦值
d = a; //實際編譯器做一個隱式的型別轉化,用2019構造一個無名函式,最後賦值給d
system("pause");
return 0;
}
用explicit修飾建構函式,可以抑制這種建構函式的隱式轉化。
class Date
{
public:
explicit Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2018);
int a = 20;
d = a; //錯誤error C2679: 二進位制“=”: 沒有找到接受“int”型別的右運算元的運算子(或沒有可接受的轉換)
system("pause");
return 0;
}
3.預設建構函式
如果一個類未顯示定義建構函式,編譯器就會合成一個預設的建構函式;如果類顯示定義了,編譯器就不再合成。
看程式碼:
class Date
{
public:
/*Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}*/
void setdate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.setdate(2018, 11, 1);
system("pause");
return 0;
}
由於d在建立的時候,是沒有任何意義的,完全可以不用合成,合成就需要呼叫,呼叫這個建構函式又沒有實際的意義,所以編譯器就不會自動合成。
d物件建立時,並沒有相關的彙編程式碼。
所以在呼叫建構函式的時候,編譯器會根據自己需求來選擇合適建構函式。如果類中沒有顯示定義建構函式,如果需要編譯器會自己預設合成一個建構函式,但這個建構函式一定沒有引數。
如下面程式碼:
class Time
{
public:
Time(int hour=11, int minute=59, int second=59)
:_hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void setdate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d;
d.setdate(2018, 11, 1);
system("pause");
return 0;
}
因為在Date類建立當中有一個Time類,編譯器在呼叫Date類的建構函式完成對Date類物件的構造, Date類為顯示,所以編譯器必須合成:因為Date類中中存在Time類,對Date類構造的時候必須對類中的Time類進行構造,所以編譯器必須合成。
四.友元類
友元分成友元函式和友元類
1.友元函式
友元函式可以訪問一個類的私有成員,它是定義在類外部的普通函式,在需要時在類中進行宣告,在函式面前加friend關鍵字。
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date()
{
}
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
int main()
{
Date d(2018, 11, 1);
cout << d << endl;
system("pause");
return 0;
}
特性:
(1)友元函式可以訪問類中的私有成員,但不是類的成員;
(2)友元函式可以在類中任何一個地方進行宣告;
(3)友元函式不能有const修飾
(4)一個函式可以是多個類的友元函式
2.友元類
class Time
{
friend class Date;//友元類的宣告
public:
Time(int hour=0, int minute=0, int second=0)
:_hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
void TimeOfDay(int hour,int minute,int second)//直接訪問私有成員
{
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d(2018, 11, 1);
d.TimeOfDay(11, 59, 59);
system("pause");
return 0;
}
特性:
(1)優點:友元類可以提高程式碼的效率;
在一個類中訪問另一個類的私有成員,原來需要通過呼叫函式的方法,要花費大量的時間可空間。
(2)缺點:友元類破壞了類的封裝性和隱藏性
類中私有的成員本來就是不能被別的類或者函式訪問,友元的加入就是的這一種特性失去。
(3)友元類是單向的;
在A類中,用friend加B類進行宣告,只能是B類中訪問A的私有成員,而A類不能訪問B類的私有成員。
(4)友元類沒有傳遞性。
在友元的加持下,A類可以訪問B類的私有成員,B類可以訪問C類的私有成員,但不代表A類就可以訪問C類的私有成員。
五.static成員
先來一個問題:如何知道一個類建立了多少個物件?以下兩種方式是否可行,為什麼?
(1) 在類中新增一個普通的成員變數進行計數
(2) 使用一個全域性變數來計數
1.宣告為static的類成員稱為類的靜態成員,用static修飾的成員變數,稱之為靜態成員變數;用static修飾的成員 函式,稱之為靜態成員函式。靜態的成員變數一定要在類外進行初始化。
class A
{
public:
A()
{
_count++;
}
A(const A& d)
{
_count++;
}
~A()
{
--_count;
}
static int GetAcount()
{
return _count;
}
private:
static int _count;
};
void test()
{
cout << A::GetAcount() << endl; //0
A a1, a2, a3;
A a4(a3);
cout << A::GetAcount() << endl; //4
}
int A::_count = 0;//在類的外部進行初始化
int main()
{
test();
system("pause");
return 0;
}
特性:
(1)靜態成員變必須在類的外部定義,定義是不加static關鍵字。
(2)靜態成員函式沒有this指標,不能訪問任何非靜態的成員。
(3)靜態成員會被所有的類共享,不屬於某個具體的例項。
(4)靜態成員和普通的成員一樣。
問題:
1. 靜態成員函式可以呼叫非靜態成員函式嗎?
class A
{
public:
A()
{
_count++;
}
A(const A& d)
{
_count++;
}
~A()
{
--_count;
}
static int GetAcount()
{
return _count;//錯誤error C2597: 對非靜態成員“A::_count”的非法引用
}
int GetACount()
{
return _count;
}
private:
int _count;
};
void test()
{
cout << A::GetAcount() << endl;
A a1, a2, a3;
A a4(a3);
cout << A::GetAcount() << endl;
}
int main()
{
test();
system("pause");
return 0;
}
答:靜態成員函式是不能呼叫非靜態成員函式的。
2. 非靜態成員函式可以呼叫類的靜態成員函式嗎?
class A
{
public:
A()
{
_count++;
}
A(const A& d)
{
_count++;
}
~A()
{
--_count;
}
static int GetAcount()
{
return _count;
}
int GetACount()
{
return _count;
}
private:
static int _count;
};
void test()
{
cout << A::GetAcount() << endl;
A a1, a2, a3;
A a4(a3);
cout << A::GetAcount() << endl;
//cout << A::GetACount() << endl;//錯誤error C2352: “A::GetACount”: 非靜態成員函式的非法呼叫
}
int A::_count = 0;
int main()
{
test();
system("pause");
return 0;
}
答:非靜態成員函式不能呼叫類的靜態成員函式。
問題:在不用乘除法,for,while,if,else,switch,case,等關鍵字以及條件判斷語句,完成1+2+3+4+......+n。
我們可以先定義一個類,接著在建立n個這樣的型別,然後通過在建構函式函式中完成累加的工作。
class Test
{
public:
Test()//建構函式
{
++n;
sum += n;
}
static int getsum()
{
return sum;
}
private:
static int sum;
static int n;
};
int Test::sum = 0; //類外初始化靜態變數
int Test::n = 0; //類外初始化靜態變數
int main()
{
Test*ptr = new Test[10];//設 n=10
cout << Test::getsum() << endl;
delete[]ptr;
ptr = NULL;
system("pause");
return 0;
}