对象的组织

const 对象

类对象也可以声明为 const 对象,一般来说,能作用于 const 对象的成员函数除了构造函数和析构函数,就只有 const 成员函数了。因为 const 对象只能被创建、撤销和只读访问,写操作是不允许的。

const 对象与 const 成员函数的规则:

  1. 当类中有 const 成员函数和非 const 成员函数重载时,const 对象会调用 const 成员函数,非 const 对象会调用非 const 成员函数。
  2. 当类中只有一个 const 成员函数时,无论 const 对象还是非 const 对象都可以调用这个版本。
  3. 当类中只有一个非 const 成员函数时,const 对象就不能调用非 const 版本。

 

思考1:一个类中可以有参数形式“完全相同”的两个成员函数( const 版本与非 const 版本),既然没有报错重定义,那么它们必然是构成了重载,为什么它们能构成重载呢?

参数( this 指针 )的类型是不同的。

 

思考2:const 成员函数中不允许修改数据成员,const 数据成员初始化后不允许修改,其效果是否相同?

不可修改的范围不同

  • const 成员函数保护整个对象,防止成员变量在函数调用期间被修改。
  • const 数据成员则是特定成员变量一旦初始化后保持不变,不依赖于 const 成员函数。

修改权限不同

  • const 成员函数不允许修改任何非 mutable 成员变量,但不要求成员变量必须为 const
  • const 数据成员在对象生命周期内完全不可修改,不管在 const 成员函数还是非 const 成员函数中。

用途不同

  • const 成员函数适用于需要承诺“只读”访问的函数,使代码更具安全性。
  • const 数据成员适用于逻辑上恒定不变的数据,比如对象创建后就不会改变的配置或标识符。

指向对象的指针

对象占据一定的内存空间,和普通变量一致, C++ 程序中采用如下形式声明指向对象的指针:

1
类名 * 指针名 [=初始化表达式];

初始化表达式是可选的,既可以通过取地址(&对象名)给指针初始化,也可以通过申请动态内存给指针初始化,或者干脆不初始化(比如置为 nullptr ),在程序中再对该指针赋值。指针中存储的是对象所占内存空间的首地址。针对上述定义,则下列形式都是合法的:

1
2
3
4
Point pt(1, 2);
Point * p1 = nullptr;
Point * p2 = &pt;
Point * p3 = new Point(3, 4);

对象数组

对象数组和标准类型数组的使用方法并没有什么不同,也有声明、初始化和使用等步骤。

  • 对象数组的声明
1
Point pts[2];

这种格式会自动调用默认构造函数或所有参数都是缺省值的构造函数。

  • 对象数组的初始化(可以在声明时进行初始化)
1
2
3
4
5
6
7
8
9
Point pts[2] = {Point(1, 2), Point(3, 4)};
Point pts[] = {Point(1, 2), Point(3, 4)};
Point pts[5] = {Point(1, 2), Point(3, 4)};
//或者
Point pts[2] = {{1, 2}, {3, 4}};
pts->print(); // 第一个对象(1,2)
(pts + 1)->print(); // 第二个对象 (3,4)
Point pts[] = {{1, 2}, {3, 4}};
Point pts[5] = {{1, 2}, {3, 4}};

这里最后一点需要提及,我们的对象数组是 5 个大小,但是只有前 2 个是有参,对于后 3 个位置,默认就是调用无参构造函数。

堆对象

和把一个简单变量创建在动态存储区一样,可以用 new 和 delete 表达式为对象分配动态存储区,在拷贝构造函数一节中已经介绍了为类内的指针成员分配动态内存的相关范例,这里主要讨论如何为对象和对象数组动态分配内存。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
void test()
{
Point * pt1 = new Point(11, 12);
pt1->print();
delete pt1;
pt1 = nullptr;

Point * pt2 = new Point[5]();//注意
pt2->print();
(pt2 + 1)->print();
delete [] pt2;
pt2 = nullptr;
}