一、类中嵌套结构或类声明
在类中嵌套结构或类声明,可以使其作用域为整个类。可以使用嵌套的结构或类来声明类成员,也可以将它作为类方法中的类型名称,但只能在类中使用。可以查看如下示例代码:
typedef Customer Item;
class Queue
{
private:
struct Node
{
Item item;
struct Node* next;
};
enum { Q_SIZE = 10 };
Node* front;
Node* rear;
int items;
const int qsize;
Queue(const Queue &q)
: qsize(0)
{
}
Queue& operator=(const Queue& q)
{
return *this;
}
public:
Queue(int qs = Q_SIZE);
~Queue();
bool isempty() const;
bool isfull() const;
int queuecount() const;
bool enqueue(const Item& item);
bool dequeue(Item& item);
};
嵌套结构或类: 在类声明中声明的结构、类或枚举被称为是嵌套在类中,其作用域为整个类。这种声明不会创建数据对象,而只是指定了可以在类中使用的类型。
结构、类或枚举在类私有部分声明: 则只能在这个类使用被声明的类型。
结构、类或枚举在类公有部分声明: 则可以从类的外部通过作用域解析运算符使用被声明的类型。例如,上述示例代码的结构体Node假如是在公有部分声明,则可以在类的外面声明Queue::Node类型的变量。
二、成员初始化列表
在第一节中的示例代码中有qsize常量,假如编写了如下的示例代码:
Queue::Queue(int qs)
{
front = rear = NULL;
items = 0;
qsize = qs; // 不被允许的操作
}
2.1上述示例代码无法正常运行原因:
上述示例代码无法正常运行,原因在于qsize是常量,所以可以对它进行初始化,但是不能对它进行赋值。具体说明如下:
从概念上说,调用构造函数时,对象将在构造函数括号中的代码执行前被创建。因此,调用Queue(int qs)构造函数将导致程序首先给第一节示例代码中的4个成员变量分配内存。然后,程序流程再执行到构造函数括号中,使用常规的赋值方式将值存储到内存中。因此,对于const数据成员,必须在执行到构造函数体之前,即创建对象时进行初始化。
2.2 上述示例代码无法正常运行解决方式
针对本章节示例代码无法正常运行情况,C++提供了一种特殊的语法来解决,他被叫做成员初始化列表。
成员初始化列表: 由逗号分隔的初始化列表组成。它位于参数列表的右括号之后、函数体大括号之前。
举例: 如果数据成员的名称为data,并需要将它初始化为val,则初始化器为data(val)。
根据成员初始化列表初始化使用,可以将本章节错误示例代码更改为如下代码:
Queue::Queue(int qs) : qsize(qs)
{
front = rear = NULL;
items = 0;
}
2.3 成员初始化列表语法
如果Classy是一个类,而mem1、mem2、mem3都是这个类的数据成员,则类构造函数可以使用如下的语法来初始化数据成员:
Classy::Classy(int n, int m)
: mem1(n)
, mem2(0)
, mem3(n * m + 2)
{
// ...
}
上述代码将mem1初始化为n,将mem2初始化为0,将mem3初始化为n * m + 2,从概念上说,这些初始化工作都是在对象创建时完成的,此时还未执行构造函数大括号中的任何代码。
使用成员初始化列表请注意一下几点:
(1)成员初始化列表只能应用于构造函数
(2)必须使用这种格式来初始化非静态const数据成员
(3)必须用这种格式来初始化引用数据成员,这是因为引用与const数据类似,只能在被创建时进行初始化。
(4)这种这种方式并不限于初始化常量,亦可以初始化普通数据成员,但是对于简单数据成员使用成员初始化列表和在函数体中使用赋值没有什么区别。
(5)对于本身就是类对象的成员来说,使用成员初始化列表的效率更高
(6)数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与初始化器中的排列顺序无关。
2.4 类内初始化
C++11 允许以更直观的方式进行初始化,示例代码如下:
class Classy
{
public:
int mem1 = 10;
const int mem2 = 20;
};
上述代码初始化方式与使用成员初始化列表方式等价,下述示例代码是将mem1和mem2分别初始化为10和20:
Classy::Classy() : mem1(10), mem2(20)
{
// ...
}
如果调用了使用成员初始化列表的构造函数,实际成员初始化列表将覆盖在类内初始化的值,如下示例代码,构造函数使用n来初始化mem1,但mem2仍是类内初始化的初始值20:
Classy::Classy(int n) : mem1(n)
{
// ...
}
三、如果用不到复制构造函数与重载赋值运算符该如何做
假设实际应用中暂不使用复制构造函数与重载的赋值运算符,但是在将来的某个时候,可能需要再次对对象进行复制或赋值操作。
再假如 ,长时间过后,开发者可能也会忘记是否对复制构造函数或重载的赋值运算符编写代码或编写成熟的代码。可能再使用时程序可编译和运行,但是运行结果是混乱错误的,甚至会导致程序崩溃。
针对上述两种情况,最好还是提供复制构造函数和复制运算符功能,但可屏蔽当前使用。解决方式如下:
将编写的赋值构造函数和重载的赋值运算符定义为伪私有方法,如下代码示例:
class Queue
{
private:
Queue(const Queue &) : qsize(0)
{
}
Queue& operator=(const Queue &)
{
return *this;
}
};
定义为伪私有方法主要有两个作用:
(1)这样避免了本来将自动生成的默认方法定义
(2)因为这些方法是私有的,所以不能被广泛使用。举例,如果que1和que2是Queue对象,则编译器不允许以下示例代码这样编写:
Queue snake(que1);
que2 = que1;
定义为伪私有方法的使用精髓在于:
(1)与其将来面对无法预料的运行故障,不如得到一个易于跟踪的编译错误,指出这些方法是不可访问的。
(2)定义其对象不允许被复制的类时,这种方法也很有用。另外,C++11还提供了另一种禁用方式,使用关键字delete,这个会在后续文章中再详细说明。
定义为伪私有方法的其它影响注意:
(1)当对象按值传递(或返回)时,复制构造函数将被调用。然而,如果遵循优先采用按引用传递对象的惯例,将不会有任何问题。
(2)复制构造函数还被用于创建其它的临时对象,例如,重载加法运算符。