本篇通過類來討論C++中封裝的問題。
不是全面講述封裝,是摘錄一些我學習過濾後覺得值得注意的一些點。
1.訪問控制
public,private,protected
2.構造函數。
構造函數需要考慮多種情況,
(1)默認(無參或有默認參數)構造函數,(若不定義則由系統自動生成)
這個必須有生成數組的時候會用到
(2)有參構造函數
(3)拷貝構造函數(此種情況通過類對象a來初始化新建對象),有深拷貝和淺拷貝的問題。 (當類對象中有通過指針new的內存時,要執行深拷貝,否則兩個指針指向同一塊內存,會在析構時重複析構而出現錯誤)
MyString::MyString(const MyString & other)
{
int len = strlen(other._str);
this->_str = new char[len+1];
//可以加判斷是否申請內存成功
if(this->_str == NULL)
{
//return ;
}
strcpy(this->_str,other._str); //拷貝
}
3.拷貝構造可與賦值運算符重載對比,賦值運算符重載中需要考慮將對象本身賦給自己的情況,而拷貝構造不需要。
MyString & MyString::operator=(const MyString & another)
{
if(this == &another)
return *this;
else
{
delete []this->_str;
int len = strlen(another._str);
this->_str = new char[len+1];
strcpy(this->_str,another._str);
return *this;
}
}
strcpy(this->_str,another._str);
賦值運算符重載返回的是引用,這樣可以實現連=操作。
要先釋放原先的內存,重新申請相應大小並拷貝內容 。
4.加法運算符重載
計算總內存長度,釋放原有內存,申請相加長度後的內存,清零,賦值
MyString MyString::operator+(const MyString & other)
{
int len = strlen(this->_str) + strlen(other._str);
MyString str;
delete []str._str;
str._str = new char[len+1];
memset(str._str,0,len+1);
strcat(str._str,this->_str);
strcat(str._str,other._str);
return str;
}
strcat(str._str,other._str); //相加
5.const 修飾類的成員變量,表示成員常量,不能被修改,同時它只能在初始化列表中賦值
class A
{
public:
A():iValue(199){}
private:
const int iValue;
6.常成員函數
const 修飾函數的意義
承諾在本函數內部不會修改類內的數據成員,不會調用其它非 const 成員函數
類體外定義的 const 成員函數,在定義和聲明處都需要 const 修飾符 。 加在末尾
const 構成函數重載
class A
{
public:
A():x(199),y(299){}
void dis() const //const 對象調用時,優先調用
{
//input(); 不能調用 非 const 函數,因爲本函數不會修改,無法保證所調的函數也不會修改
cout<<"x "<<x<<endl;
cout<<"y "<<y<<endl;
//y =200; const 修飾函數表示承諾不對數據成員修改。
}
void dis() //此時構成重載,非 const 對象時,優先調用。
{
y = 200;
input();
cout<<"x "<<x<<endl;
cout<<"y "<<y<<endl;
}
7.靜態成員
靜態函數只能訪問靜態成員
調用
類名::函數調用
類對象.函數調用
靜態成員函數屬於類,而不屬於對象,沒有 this 指針
static const 成員
如果一個類的成員,既要實現共享,又要實現不可改變,那就用 static const 修飾。
修飾成員函數,格式並無二異,修飾數據成員。必須要類內部初始化。
class A
{
public:
static const void dis()
{
cout<<i<<endl;
}
private:
const static int i = 100;
};
8.定義指向類成員或類成員函數的指針
指向類數據成員的指針
定義
<數據類型><類名>::<指針名>
賦值&初始化
<數據類型><類名>::<指針名>[=&<類名>::<非靜態數據成員>]
指向非靜態數據成員的指針在定義時必須和類相關聯,在使用時必須和具體的對象
關聯。
在使用這類指針時,需要首先指定類的一個對象,然後,通過對象來引用指針所指向的成員。
<類對象名>.<指向非靜態數據成員的指針>
<類對象指針>-><指向非靜態數據成員的指針>
string Student::*ps = &Student::name;
cout<<s.*ps<<endl;
cout<<s2.*ps<<endl;
Student *pp = new Student("wangwu",1003);
cout<<pp->*ps<<endl;
指向類成員函數的指針
定義一個指向非靜態成員函數的指針必須在三個方面與其指向的成員函數保持一致:參數
列表要相同、返回類型要相同、所屬的類型要相同
定義
<數據類型>(<類名>::<指針名>)(<參數列表>)
賦值&初始化
<數據類型>(<類名>::<指針名>)(<參數列表>)[=&<類名>::<非靜態成員函數>]
void (Student::*pf)() = & Student::dis;
(s.*pf)();
(s2.*pf)();
(ps->*pf)()
實例:用指向類成員函數的指針,實現更加隱蔽的接口
class Widget
{
public:
Widget()
{
fptr[0] = &f;
fptr[1] = &g;
fptr[2] = &h;
fptr[3] = &i;
}
void select(int idx, int val)
{
if(idx<0 || idx>cnt) return;
(this->*fptr[idx])(val);
}
int count()
{
return cnt;
}
private:
void f(int val){cout<<"void f() "<<val<<endl;}
void g(int val){cout<<"void g() "<<val<<endl;}
void h(int val){cout<<"void h() "<<val<<endl;}
void i(int val){cout<<"void i() "<<val<<endl;}
enum{ cnt = 4};
void (Widget::*fptr[cnt])(int);
};
int main()
{
Widget w;
for(int i=0; i<w.count(); i++)
{
w.select(i,1);
}
return 0;
}
//指向類靜態成員的指針
int *p = & A::data;
cout<<*p<<endl;
void (*pfunc)() = &A::dis;
9.前向聲明
前向聲明,是一種不完全型(forward declaration)聲明,即只需提供類名(無需提供類實現)即可。正因爲是(incomplete type)功能也很有限:
(1)不能定義類的對象。
(2)可以用於定義指向這個類型的指針或引用。
(3)用於聲明(不是定義),使用該類型作爲形參類型或者函數的返回值類型
class Point; //前向聲明
class ManagerPoint
{
public:
double Distance(Point &a, Point &b);
};
class Point
{
public:
Point(double xx, double yy)
{
x = xx;
y = yy;
}
void Getxy();
friend double ManagerPoint::Distance(Point &a, Point &b);
};
聲明爲誰的友元,就可以通過誰的對象訪問誰的私有成員
這裏ManagerPoint::Distance聲明爲Point的友元,就能通過Point的對象,訪問Point的私有成員
10.友元類
友元類的所有成員函數都是另一個類的友元函數,都可以訪問另一個類中的隱藏信息(包
括私有成員和保護成員)。
當希望一個類可以存取另一個類的私有成員時,可以將該類聲明爲另一類的友元類。定義友元類的語句格式如下:
friend class 類名;
class A
{
public:
friend class B;
};
類 B 是類 A 的友元類,類 B 的所有成員函數都是類 A 的友元函數,能存取類 A 的私有成員和保護成員
11.用類型轉換構造函數進行類型轉換
explicit Point3D(Point2D &p) //注:explicit 是個僅用於聲明的關鍵字
{
this->_x = p._x;
this->_y = p._y;
this->_z = 0;
}
12.用類型轉換操作符進行轉換
class 源類{
operator 轉化目標類(void)
{
//根據需求完成從源類型到目標類型的轉換
}
}
Point2D::operator Point3D()
{
return Point3D(_x,_y,0);
}
13.函數操作符(())—仿函數(把類對象像函數名一樣使用)
實現一個operator(),這個類就有了類似函數的行爲
class 類名
{
返值類型 operator()(參數類型)
函數體
}
#include <iostream>
#include <vector>
using namespace std;
class Pow
{
public:
int operator()(int i)
{
return i*i;
}
double operator ()(double d)
{
return d*d;
}
};
int main()
{
Pow pow;
int i = pow(4); //pow.opreator()(4);
double d = pow(5.5);
cout<<i<<endl;
cout<<d<<endl;
return 0;
}
14.代理類,
通過向客戶提供只知道類的 public 接口的代理類,就可以使客戶能夠使用類的服務,而無法訪問類的實現細節。
#include <iostream>
using namespace std;
class Implementation
{
public:
Implementation(int v):value(v)
{}
void setValue(int v)
{
value = v;
}
int getValue() const
{
return value;
}
private:
int value;
};
class Delegate
{
public:
Delegate(int v):pi(new Implementation(v))
{}
void setValue(int v)
{
pi->setValue(v);
}
int getValue() const
{
return pi->getValue();
}
private:
Implementation * pi;
};
int main()
{
Delegate d(5);
d.setValue(100);
cout<<d.getValue()<<endl;
return 0;
}
特別關注此處pi對象的申請內存及初始化!
Delegate(int v):pi(new Implementation(v))