2.1 類的基礎知識
2.2 構造函數
2.3 拷貝構造函數
2.4 析構函數
2.5 C++能自動產生成員函數
2.6 友元
2.7 類成員的補充
2.8 簡單內存對象模型
2.1 類的基礎知識
面向對象基本概念
類和對象
類是一組具有相同屬性和行為的對象的抽象。類和對象的關系是抽象和具體的關系。
封裝、繼承和多態
- 用類進行封裝
- 繼承:保持已有類的特性而構造新類的過程。
- 多態:使編譯器能夠在運行時決定是使用基類中定義的函數還是派生類中定義的函數。
抽象和封裝
在 C++ 中,用類來定義自己的抽象數據類型
通過定義類型來對應所要解決的問題中的各種概念,可以使我們更容易編寫、調試和修改程序。
class Clock{
public:
void SetTime(int SetHour, int SetMinute);
void ShowTime();
private:
int m_Hour; int m_Minute
};
類是有著共同特征與行為、而狀態各不相同的物體的總稱。
對象是類的實現,是類的實例。類的產生基礎是封裝。
封裝的目的是增強安全性和簡化編程,使用者不必了解具體的實現細節,而只需要通過外部接口,以特定的訪問權限使用其中的成員即可。
C++支持數據隱藏機制,public,protected, private成為保護標識符,標識后邊成員的可訪問性。
成員函數
可以僅在類中說明原型,而在類外給出函數體實現,也可以直接在類中給出函數體,形成成員函數的隱含內聯。
允許成員函數為重載函數和帶默認參數值的函數。
對成員數據的使用不再遵循“先聲明后使用”的原則。
凡被調用的成員函數一定要有函數實現。
:: ---- 作用域操作符,類就是定義了一個新的作用域。
void Clock::SetTime(int SetHour, int SetMinute)
{
m_Hour = SetHour;
m_Minute = SetMinute;
}
內聯顯示聲明:
inline int Point::GetX() {
return X;
}
創建對象
對象就是類的實體,就是把抽象出的數據具體化。類就是一個封裝了數據和函數的類型。
添加頭文件#include "clock.h"
Clock myclock; //創建對象,就像是創建一個基本類型
myclock.SetTime(12, 30); //對象名.成員
一般來說類的定義與類中成員函數的定義并不放在同一個文件中。類的聲明放在頭文件中,類中函數的定義(類實現及類使用)放在.cpp文件中
類的深入
類:1、把數據封裝(使用private),維護方便。2、使程序更加模塊化,邏輯更清晰。
類用戶的代碼中永遠不會出現private封裝起來的內容。
Class默認為私有,struct默認為公有
類作用域
類定義部分 {}
類外成員函數形參表部分
類外成員函數函數體部分
成員函數的返回類型不一定在類作用域中
類內定義typedef int HOUR; 類外使用HOUR Clock::GetHour(){ }編譯錯誤。
- 作用域是對象或成員能被使用的前提,表現了“可見性”。是語法規定好的,程序員只能遵照執行。
2.訪問權限是類的對象訪問成員的被許可性,也是類成員的可用性。表現了“封裝性”。是在作用域的前提下,由程序員定義的,是人的意志的體現
類內訪問 和 類外訪問
類作用域內的訪問為“類內訪問”,類作用域外的訪問為“類外訪問”
類內訪問:
void Clock::SetTime(int newHour, int newMinute){
m_Hour = newHour;
m_Minute = newMinute;
}
拷貝構造函數:
Clock(const Clock& c1): m_Hour(c1.m_Hour),m_Minute(c1.m_Minute) {
cout<<"拷貝構造函數調用"<<endl;
}
X(p.X)是當前對象的函數訪問別的對象的私有數據,這不是破壞了封裝性嗎?
不。這是發生在成員函數中,叫“類內訪問”:成員函數和私有數據都在同一個類中。而不屬于“對象名.成員名”的表示,那是“類外訪問”。
隱含的this指針
void Clock::SetTime( int SetHour, int SetMinute)
{
this->m_Hour = SetHour;
this->m_Minute = SetMinute;
}
this指針是什么,在面向對象編程中,針對結構體,會有相關的成員函數,而成員函數的第一個參數通常就是本類對象的指針,既然這是一個固定的方法,C++索性就把它給封裝在后邊。本類對象的所有代碼是公共的。
每一個成員函數(static member function除外)都有一個隱含的參數,為 classType* this,會自動加載進去。
SetTime( Clock* this, int SetHour, int SetMinute)
2.2 構造函數
定義及作用
構造函數是特殊的成員函數,通常創建類類型的新對象,都要執行構造函數。
構造函數的工作是保證每個對象的數據成員具有合適的初始值
構造函數的作用是在對象被創建時使用特定的值構造對象,或者說將對象初始化為一個特定的狀態。
特殊在哪兒?1.不能由對象調用,自動調用!2.沒有返回值。3.有初始化列表
初始化:是指對象(變量)誕生的那一刻即得到原始值的過程。是動詞。
若對象聲明然后再賦值,會經歷狀態由不確定到確定的過程。
調用時機
當然是在任何構造對象的時候:
a) 無論這個對象有名無名.
b) 無論這個對象被創建在什么區域.
c) 無論這個對象是否為臨時對象.
Clock clock;
Clock *pClock = new Clock;
如果程序中未聲明,則系統自動產生出一個默認形式的構造函數,它無參,函數體為空;
允許為內聯函數、重載函數、帶默認形參值的函數
是可以使用初始化列表的兩函數之一(特權(另一個拷貝構造函數));
不可以是常函數,亦不可是虛函數;
天然具有類型轉換功能,除非用explicit關閉。
語法規則及特殊性體現
類名::類名(形參總表):成員或對象1(參數1),成員或對象2(參數2)
{
構造函數的函數體
}
Clock::Clock(int h, int m):m_Hour(h), m_Minute(m)
{
}
構造函數名與類名相同 //一個類可以有多個構造函數
不可有任何返回值
可以有初始化列表(只有構造函數有初始化列表)
初始化列表
Clock::Clock(int h, int m) 這里是初始化的時機
{
//如果放棄了初始化的時機,那么就要在這里進行數據成員的賦值
m_Hour = h;
m_Minute = m;
//m_serial_number = “CASIO-EDIFICE-2009001”;
}
注意,無論是否寫初始化列表,在進入函數體之前,都有一個初始化的機會。
這個機會留給你初始化特殊的成員變量,如引用,const成員
例如: const string m_serial_number;
倔強變量需要初始化列表
const變量
引用
IO流對象 //你可以暫且先記住它
所以,如果類內包含這三類成員,一定要使用初始化列表對他們進行初始化工作。
建議使用初始化列表給成員初始化,因為效率比賦值高
默認構造函數
A a; //類名 對象名; 不帶任何初值
那么編譯器為你調用的構造函數就是默認構造函數。
構造對象的方式
有名對象:A a 或A a(參數)
無名對象(堆): new A 或 new A(參數)
注:A a;千萬不可寫成 A a();
規范:初始化列表一定要按照成員變量的聲明次序書寫,以免引發隱晦的錯誤。
A() { j = 10; i = j/2;} // A():j(10), i(j/2) {}
2.3 拷貝構造函數
使用一個已經存在的對象去初始化同類的一個新對象時調用拷貝構造函數
拷貝構造函數是一種特殊的構造函數,其形參為本類的對象引用。
規范,建議使用const引用
語法規則
class 類名
{
public:
類名(形參);//構造函數
類名(const 類名 &參數名);//拷貝構造函數原型
};
類名::類名(const 類名 &參數名) //拷貝函數的實現
{
函數體
}
Clock(const Clock& c1): m_Hour(c1.m_Hour),m_Minute(c1.m_Minute) {
cout<<"拷貝構造函數調用"<<endl;
}
調用方式
當用類的一個對象去初始化該類的另一個對象時系統自動調用拷貝構造函數實現拷貝賦值。
void main(void)
{
Clock c1(1,2);
Clock c2(c1); //拷貝構造函數被調用
}
若函數的形參為類對象,調用函數時,實參賦值給形參,系統自動調用拷貝構造函數
void fun1(Clock c)
{
cout<<c.GetHour()<<endl;
}
int main()
{
Clock c1(1,2);
fun1(c1);
return 0;
}
當函數的返回值是類對象時,系統自動調用拷貝構造函數。
Clock fun2(){
Clock c(1,2);
//調用拷貝構造函數
return c;
}
void main()
{
Clock c1;
c1 = fun2(); //臨時對象
}
2.4 析構函數
定義:(與構造函數相對立)
用來完成對象被刪除前的一些清理工作。
析構函數是在對象的生存期即將結束的時刻被自動調用的
語法規則:
類名::~類名()
{
函數體
}
注:
函數名為 ~類名
函數沒有參數
函數體負責資源清理工作(內存)
為什么需要析構函數?
處理僵死進程。
Array(int size):m_Size(size) { m_pArray = new int[m_Size]; }
~Array(){
delete [] m_pArray;
}
析構函數的調用順序與構造函數的調用順序嚴格相反
發生了2次析構函數的調用,用new分配空間,需要在delete的時候才會調用析構函數
int main()
{
Test a;
{
Test *pTest = new Test;
Test b;
delete pTest;
pTest = NULL;
}
return 0;
}
MyString類函數練習
錯誤調試:注意賦值與等于符號、注意動態申請的內存delete
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
public:
MyString(char *pstr="");
MyString(const MyString& str);
~MyString();
public:
void ShowString(); //顯示字符串
void Add(const MyString& str); //與另外一個相加
void Copy(const MyString& str); //從另外一個拷貝
int GetLength() const;
private:
char *m_buf;
};
MyString::MyString( char *pstr)
{
if(pstr)
{
m_buf = new char[strlen(pstr) +1];
strcpy(m_buf, pstr);
}
else
{
m_buf = new char[1];
*m_buf = '\0';
}
}
MyString::MyString( const MyString& str)
{
m_buf = new char[ strlen(str.m_buf) +1 ];
strcpy( m_buf, str.m_buf);
}
MyString::~MyString( )
{
delete[] m_buf;
}
void MyString::ShowString( )
{
if(*m_buf!= '\0')
{
cout<< m_buf <<endl;
}
else
{
cout << "This is a NULL string" << endl;
}
}
void MyString::Add(const MyString& str)
{
if( *str.m_buf == '\0' )
return;
int nLength = GetLength();
char *pTmp = new char[nLength];
strcpy(pTmp, m_buf );
delete [] m_buf;
m_buf = new char[ (nLength-1) + str.GetLength() ];
strcpy(m_buf, pTmp);
strcat( m_buf, str.m_buf);
delete[] pTmp;
}
void MyString::Copy(const MyString& str)
{
delete [] m_buf;
m_buf = new char[ strlen(str.m_buf) +1 ];
strcpy( m_buf, str.m_buf);
}
int MyString::GetLength() const
{
int i = (int)strlen(m_buf)+1;
return i;
}
int main()
{
MyString str("hi ");
MyString str2("You are welcome!");
str.ShowString();
str2.ShowString();
str.Add(str2);
str.ShowString();
return 0;
}
2.5 C++能自動產生成員函數
C++能自動產生的成員函數(空類)
構造函數 類名{}
拷貝構造函數 類名(類名& ){}
賦值函數 類名& operator =(類名& );
析構函數 ~類名(){}
空類在不同的編譯器下占幾個字節。
class Empty{};
const Empty e1; // 缺省構造函數
Empty e2(e1); // 拷貝構造函數
e2 = e1; // 賦值運算符重載函數
最后調用析構函數。
生成的析構函數一般是非虛的。除非是從虛析構函數的基類繼承而來
2.6 友元
友元機制:允許一個類將對其非公有成員的訪問權授予指定的函數或類。
友元的聲明以關鍵字 friend開始。它只能出現在類定義的內部
注意:
- 友元聲明可以出現在類中的任何地方,但通常將友元聲明成組地放在類定義的開始或結尾。
- 友元不是授予友元關系的那個類的成員,所以它們不受聲明出現部分的訪問控制影響。
友元分類
方法一:聲明整個類為友元
friend class RepairMan;
方法二:聲明類中的某個成員函數為友元
friend bool RepairMan::RepairClock(Clock& clock);
方法三:聲明一個非成員函數為友元。(其他類的非成員函數,自身)
friend bool RepairClock(Clock& clock);
特性
友元函數不是類的成員,因此不受類的作用域管轄。
可以訪問對象的私有成員,但必須由對象名來引導。(對象為媒介,通過友元機制)
性質
友元關系是單向的。
B類是A類的友元,則B類的函數可以訪問A類的私有和保護數據,但A類的成員函數卻不能訪問B類的私有、保護數據。(孫悟空鉆進鐵扇公主肚里。)
友元關系不能傳遞 朋友的朋友不見得是朋友!
友元關系不能繼承
如果B類是A類的友元, B類的子類不會自動成為A類的友元類。(借來的東西不是遺產。)
2.7 類成員的補充
const 數據成員
const 成員函數
static 數據成員
static 成員函數
const數據成員 (對象誕生之后其const數據成員不能改變)
const string m_serial_number;
const成員函數
const修飾this指針
this不再是:Type* const this;而是:const Type* const this;
const需要在聲明和定義中寫出
const成員函數內部無法修改數據成員
const函數內部無法調用non-const成員函數
const 對象 不可以訪問 非const 成員函數
例子解析:
1、如果沒有const成員函數,使用const對象調用成員函數會出錯
不能將“this”指針從“const Clock”轉換為“Clock &”轉換丟失限定符
2、常對象只能調用常成員函數;在常函數和非常函數并存時,非常對象只能調用非常函數,在只有常函數時,非常對象可以調用常函數。
static數據成員
non-static 數據成員是對象屬性的成員。
static 數據成員是類屬性的成員。
內存:堆、棧、data、code
數據成員變量: int a 堆
主函數main()、靜態函數、成員函數code
靜態數據成員 data
Int main(int argc,char **avgr){} 參數放在棧中
Class A{};
A *p; *p 棧
類屬性
靜態數據成員
用關鍵字static聲明;
該類的所有對象維護著同一份拷貝;
不能在構造函數中而應在類外單獨進行初始化。必須在程序運行之前完成,并且用(::) 來指明所從屬的類名。
聲明: //.h class 內
static 類型 成員名;
定義: //.cpp 內(無static)
類型 類名::靜態成員名 = 初值; or
類型 類名::靜態成員名(參數表);
靜態成員函數
類外的代碼可以使用類名和作用域操作符(::)來調用靜態成員函數;
靜態成員函數只能訪問屬于該類的靜態數據成員或靜態成員函數,不能使用非靜態成員。
沒有this指針,所以不能為常函數。
函數聲明有static修飾,如果類外實現,不加static修飾
使用:
a) 對象名.XXX //指針->XXX
b) 類名::XXX //建議采用這種方式
2.8 簡單內存對象模型
組合
class A{}; class B{A a;};
初始化:
類名::類名(形參總表) :對象1(參數),對象2(參數)
{
構造函數的其他功能
}
給類作對象布局的安排:讓類的對象中含有一個別的類對象,在本對象的適當位置處,調用那個類的構造函數,來產生內嵌對象作為本類的數據成員。
聚合(指針或引用,關系性緊密)
Class A{}; class B{A *a;};
例子:
class Point
{
private:
float x, y; //點的坐標
public:
Point(float h, float v); //構造函數
float GetX(void){return x;} //取X坐標取坐標
float GetY(void){return y;} //Yvoid Draw(void); //在(x,y)處畫點
};
Point::Point(float h, float v){//構造函數
x = h;
y = v;
}
class Line
{
private:
Point p1,p2; //線段的兩個端點
public:
Line(Point a,Point b):p1(a), p2(b)
{ //構造函數
}
void Draw(void); //畫出線段
};
void main()
{
Point point1(1,1);
Point point2(1,2);
Line line1(point1, point2);
}