1、类定义
类成员:可以包括 数据、函数、类型别名等;
构造函数:初始化列表,在构造函数的形参列表后,由冒号起始,参数间以逗号隔开;
成员函数:成员函数必须在类内声明,定义是可选的,类内定义的成员函数默认为inline,成员函数后 添加 const 表示本函数不改变类对象的数据成员,即this指针为指向const对象的指针。
另外this永远是一个 const 指针即只能指向类对象本身,不能人为改变。
2、static类成员
2.1 static成员函数
static关键字只能出现在类定义内的成员声明处,类似于explicit。 static成员函数没有this参数所以不能声明为const
2.2 static数据成员
staic数据成员必须在类的定义体外部定义(only once),static数据成员不是通过构造函数初始化,而是应该在定义时进行初始化,原因是当类头文件包含在多个源文件中时防止变量的重复定义;
特殊的整型const static成员:整型const static成员可以在类定义体中初始化,该成员仍然必须在类外部进行定义,不过不用使用初始化式。也就是说类内即使有初始化式也只是声明和初始化,只有类外才是定义,才会产生内存空间;
static成员不是类对象的组成部分
所以static成员类型可以是所属类的类型本身,但是普通成员不可以,普通成员被限定为类类型的指针或引用、或者其他变量类型。
栗子:
class Singleton
{
public:
static Singleton* getInstance();
private:
Singleton();
//把复制构造函数和=操作符也设为私有,防止被复制
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* instance;
};
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance()
{
return instance;
}
3、 复制控制
复制控制包括:复制构造函数、析构函数、赋值操作
如果没有定义他们,编译器会自动合成一个,但是编译器合成的复制控制函数属于位复制(浅度复制),如果类包含指针类型,就需要定义自己的复制控制函数,此时应当实现值复制(深度复制)。
只有单个形参,而且该形参为本类类型的引用(常用const修饰),这样的构造函数称为复制构造函数。
1. 对象的定义形式
string null_book = "9-999-99999-9";
// copy-initialization 调用接收C风格字符串的构造函数构造一个string对象
// 然后使用复制构造函数将null_book初始化为临时对象
string null_book2 = string() ;
// copy-initialization 调用默认构造函数构造一个string对象
// 然后使用复制构造函数将 null_book2初始化为该临时对象
2.形参与返回值
当形参或返回值类型为类类型时由复制构造函数进行复制。
3.初始化容器元素
vector<string> svec(5);
编译器首先使用默认构造函数,构造一个临时string对象,然后使用复制构造函数将临时值复制到svec每个元素。
4.构造函数与数组元素
如果没有为类类型提供数组初始化式,将使用默认构造函数初始化每个元素,如果使用花括号来提供显示初始化式,使用复制构造函数复制到对应元素。
3.1 合成的复制构造函数
如果没有定义复制构造函数,编译器就会合成一个,与默认构造函数不同的是,即使提供了其他构造函数,只要没有定义复制构造函数,都会合成一个。合成的复制构造函数其行为是:逐个成员初始化。
3.2 定义自己的复制构造函数
当类中具有指针类型,应当自己定义复制构造函数,而且复制构造函数一般不指定为explict,指定了explicit不能使用等号进行隐式调用复制构造函数。
3.3 禁止复制
为了禁止复制,类必须显式声明复制构造函数为private,然而类的友元和成员仍可以进行复制;
如果想要连友元和成员也禁止,可以声明一个private复制构造函数而不对其定义,此时用户代码中的复制尝试将会标记为编译错误,友元和成员的复制尝试将在链接期导致错误。
3.4 赋值操作符
赋值时,对于一般的都是直接使用合成赋值函数。但是对于成员变量为指针、对象或复制时希望完成其他附加操作的均需自己来处理赋值行为。
3.5 析构函数
析构函数与复制构造函数或赋值操作符重要区别,即使定义了自己的析构函数,合成的析构函数仍然运行,在自定义函数之后运行。
三法则:需要析构函数,就需要复制构造函数和赋值操作符,因为需要析构函数的场景通常为成员变量为指针、对象或希望完成其他附加操作的处理行为。
- 常见三种复制是一起出现的,即需要自定义一种复制行为时,往往另外两种也需要自定义
- 需要禁止复制时,必须显式地声明其复制构造函数为 private(其友元和成员依然可以复制)
- 如果要连友元和成员的复制也禁止,就可以声明一个private的复制构造函数但不对它进行定义。(如果复制类对象会提示编译错误,如果成员和友元尝试复制就会导致链接错误)
- 在实现时其实赋值重载函数是包含复制构造函数和析构函数功能的,此时可以将复制和析构功能单独做到两个私有函数中,如演示代码中的CopyData和DeleteData
定义了复制控制行为的栗子:
class CCopyControl
{
public:
CCopyControl(int nData=0);
CCopyControl(const CCopyControl& c); //复制构造函数
CCopyControl& operator=(const CCopyControl& c); //赋值重载函数
~CCopyControl(); //析构函数
private:
int m_nData;
int *m_pData;
void CopyData(const CCopyControl& c);
void DeleteData();
};
//默认构造函数
CCopyControl::CCopyControl( int nData/*=0*/ )
{
m_nData = nData;
m_pData = new int(nData);
}
//复制构造函数
CCopyControl::CCopyControl( const CCopyControl& c )
{
CopyData(c);
}
//赋值重载函数
CCopyControl& CCopyControl::operator=( const CCopyControl& c )
{
if (this != &c)
{
DeleteData();
CopyData(c);
}
return *this;
}
//析构函数
CCopyControl::~CCopyControl()
{
DeleteData();
}
//复制数据
void CCopyControl::CopyData( const CCopyControl& c )
{
this->m_nData = c.m_nData;
this->m_pData = new int(c.m_nData); //深拷贝
}
//删除数据
void CCopyControl::DeleteData()
{
if (!m_pData)
{
delete m_pData;
m_pData = NULL;
}
}
4 、智能指针
智能指针的使用栗子(如下定义中,也可以使指针计数维护类增加封装类为友元):
// CMySmartPtr指针封装类,将普通指针封装为智能指针
template <class T>
class CMySmartPtr
{
public:
/*构造函数*/
CMySmartPtr(T* pT)
{
pCountT = new CCountT(pT);
}
CMySmartPtr()
{
pCountT = NULL;//默认指针为空
}
/*复制控制*/
~CMySmartPtr()
{
DeleteData();
}
CMySmartPtr(const CMySmartPtr& p)
{
CopyData(p);
}
CMySmartPtr& operator=(const CMySmartPtr& p)
{
if (this != &p) //注意自身对自身赋值的情况
{
DeleteData();
CopyData(p);
}
return *this;
}
/*指针*和->解引用*/
T& operator*()
{
return *(this->pCountT->pT);
}
T* operator->()
{
return this->pCountT->pT;
}
private:
//装饰类,为待管理的指针维护引用计数
class CCountT
{
public:
CCountT(T* pT)
{
this->pT = pT;
this->nCount = 1;
}
~CCountT()
{
delete pT;
}
T* pT;
int nCount;
};
//统一共享的指针,依靠引用计数来释放
private:
CCountT *pCountT;
void CopyData(const CMySmartPtr& p)
{
p.pCountT->nCount++;
this->pCountT = p.pCountT;
}
void DeleteData()
{
if (pCountT && --pCountT->nCount==0)
{
delete pCountT;
pCountT = NULL;
}
}
};