构造函数与析构函数
例1
设计一个长方形CRectangle类,调用类的成员函数计算长方形的周长和面积。
#include <iostream>
class CRectangle
{
public:
CRectangle(); // 声明默认构造函数
CRectangle(int width, int height); // 声明带参构造函数
~CRectangle(); // 声明析构函数
double perimeter(); // 声明计算周长成员函数
double area(); // 声明计算面积成员函数
private:
int width; // 声明长方形的宽度成员变量
int height; // 声明长方形的高度成员变量
};
CRectangle::CRectangle() // 定义默认构造函数
{
width = 10;
height = 5;
std::cout << "Created CRectangle!" << std::endl;
}
CRectangle::CRectangle(int width, int height) // 定义带参构造函数
{
this->width = width;
this->height = height;
std::cout << "Created CRectangle!" << std::endl;
}
CRectangle::~CRectangle() // 定义析构函数
{
std::cout << "Destroyed CRectangle!" << std::endl;
}
double CRectangle::perimeter() // 定义计算周长成员函数
{
return 2 * (width + height);
}
double CRectangle::area() // 定义计算面积成员函数
{
return width * height;
}
int main()
{
CRectangle Rect1, Rect2(30, 20), *pRect = &Rect2; // 定义对象和指向对象的指针
// 使用对象输出周长和面积
std::cout << "perimeter of Rect1: " << Rect1.perimeter() << std::endl; // 使用对象输出Rect1的周长
std::cout << "area of Rect1: " << Rect1.area() << std::endl; // 使用对象输出Rect1的面积
std::cout << "perimeter of Rect2: " << Rect2.perimeter() << std::endl; // 使用对象输出Rect2的周长
std::cout << "area of Rect1: " << Rect1.area() << std::endl; // 使用对象输出Rect2的面积
// 使用对象指针输出Rect2的周长和面积
std::cout << "perimeter of Rect2: " << pRect->perimeter() << std::endl; // 使用指向对象的指针输出Rect2的周长
std::cout << "area of Rect2: " << pRect->area() << std::endl; // 使用指向对象的指针输出Rect2的面积
}
对象的动态建立与释放
C++内存空间通常4个分区
- 全局数据区(data area):全局变量、静态数据、常量
- 代码区(code area):所有类成员函数和非成员函数代码
- 栈区(stack area):为运行而分配的局部变量、函数参数、返回数据、返回地址等
- 堆区(heap area),也称自由存储区:余下的空间
动态存储分配
有些操作对象的值只有在程序运行时才能确定,编译器无法在编译时预留存储空间,只能运行时进行内存分配,这种分配方式为动态存储分配。动态存储分配在堆区进行。
当需要一个动态分配的对象,必须向系统申请堆中的存储空间,并且当声明周期结束时,要显式释放所占用的内存空间。
使用new和delete可以根据需要动态建立和撤销对象。
建立对象格式:类名 * 指针变量名 = new 类型(初始化式)
释放对象格式:delete 指向该对象的指针变量名
使用new创建的对象式无名对象,不能使用对象名访问,只能使用指针访问。
一维对象数组动态内存的分配与释放
一维对象数组内存分配格式:类名 * 指针变量名 = new 类名[下标表达式]
一维对象数组的内存释放格式:delete [] 指向该数组的指针变量名
二维对象数组动态内存的分配与释放
二维数组是一种特殊的一维数组,其元素又是一维数组
- R是二维数组的首地址,行指针;
- R[0]和R[1]是第0列元素的地址,成为列指针;
二维对象数组的内存分配格式:
类名 **指针变量名 = new 类名 * [行下标表达式];
for (int i = 0; i < 行下标表达式; i++)
{
指针标量名[i] = new 类名[列下标表达式];
}
二维对象数组的内存释放格式:
for(int i = 0; i < 行下标表达式; i++)
{
delete [] 指针标量名[i];
}
delete [] 指针变量名;
由堆区创建的对象数组,只能调用默认的构造函数,不能调用其他任何构造函数。
二维动态数组在计算机图形学中一般用于建立三维物体的顶点表和面表。
例2
动态创建CRectangle类二维对象数组,输出每个对象的周长和面积。
#include <iostream>
class CRectangle
{
public:
CRectangle(); // 声明默认构造函数
CRectangle(int width, int height); // 声明带参构造函数
~CRectangle(); // 声明析构函数
double perimeter(); // 声明计算周长成员函数
double area(); // 声明计算面积成员函数
private:
int width; // 声明长方形的宽度成员变量
int height; // 声明长方形的高度成员变量
};
CRectangle::CRectangle() // 定义默认构造函数
{
width = 10;
height = 5;
std::cout << "Created CRectangle!" << std::endl;
}
CRectangle::CRectangle(int width, int height) // 定义带参构造函数
{
this->width = width;
this->height = height;
std::cout << "Created CRectangle!" << std::endl;
}
CRectangle::~CRectangle() // 定义析构函数
{
std::cout << "Destroyed CRectangle!" << std::endl;
}
double CRectangle::perimeter() // 定义计算周长成员函数
{
return 2 * (width + height);
}
double CRectangle::area() // 定义计算面积成员函数
{
return width * height;
}
int main()
{
int RowIndex = 2, ColIndex = 3; // 设置二维数组行下标和列下标
CRectangle **pRect;
// 创建二维动态数组
pRect = new CRectangle *[RowIndex]; // 设置行
for (int i = 0; i < RowIndex; i++)
{
pRect[i] = new CRectangle[ColIndex]; // 设置列
}
for (int i = 0; i < RowIndex; i++) // 设置周长和面积
{
for (int j = 0; j < ColIndex; j++)
{
std::cout << "Perimeter of Rect[" << i << "," << j << "] is " << pRect[i][j].perimeter() << std::endl; // pRect[i][j]是对象,可以用.访问成员函数
std::cout << "Area of Rect[" << i << "," << j << "] is " << pRect[i][j].area() << std::endl;
}
}
// 释放二维动态数组
for (int k = 0; k < RowIndex; k++)
{
delete[] pRect[k];
pRect[k] = NULL;
}
delete [] pRect;
pRect = NULL;
std::cin.get();
}
第一个循环创建pRect[0], 打印:
Created CRectangle!
Created CRectangle!
Created CRectangle!
说明pRect[i] = new CRectangle[ColIndex];
会调用ColIndex次构造函数
第二个循环创建pRect[1],打印:
Created CRectangle!
Created CRectangle!
Created CRectangle!
随后,打印初始化后的周长和面积:
Perimeter of Rect[0,0] is 30
Area of Rect[0,0] is 50
Perimeter of Rect[0,1] is 30
Area of Rect[0,1] is 50
Perimeter of Rect[0,2] is 30
Area of Rect[0,2] is 50
Perimeter of Rect[1,0] is 30
Area of Rect[1,0] is 50
Perimeter of Rect[1,1] is 30
Area of Rect[1,1] is 50
Perimeter of Rect[1,2] is 30
Area of Rect[1,2] is 50
然后,将创建的动态对象数组释放掉,先释放pRect[0],调用3次析构函数,打印:
Destroyed CRectangle!
Destroyed CRectangle!
Destroyed CRectangle!
再释放pRect[1],打印三次:
Destroyed CRectangle!
Destroyed CRectangle!
Destroyed CRectangle!
最后,需要释放pRect,由于创建的对象CRectangle内存都已经释放掉了,所以不会调用再调用析构函数,但是pRect的内存仍存在,所以仍然需要显式delete。
动态链表
需要动态开辟一段内存空间来存储对象。在堆链表的结点进行访问时,通过存放在上一结点内的指向下一结点的指针,可以依次访问每个结点,构成链表关系。
例3
建立一个桶类,其中成员变量ScanLine为扫描线的位置,pNext为指向下一结点的指针。请动态建立两个桶结点构成的桶链表,并依次输出其扫描线的位置。
#include <iostream>
class CBucket // 定义桶类
{
public:
int ScanLine; // 声明扫描线位置
CBucket *pNext; // 声明指向下一桶结点的指针
};
int main()
{
CBucket *pHead = new CBucket, *pCurrentB; // 定义桶结点的头指针和桶结点的当前指针
pCurrentB = pHead; // 当前指针为桶结点的头指针,开始给第一个结点赋值
pCurrentB->ScanLine = 1; // 当前指针(第一个结点)的扫描线为1
std::cout << "The ScanLine of the first node is " << pCurrentB->ScanLine << std::endl; // 输出当前指针的扫描线位置
pCurrentB->pNext = new CBucket; //为当前指针(第一个结点)的pNext指针新建结点
pCurrentB = pCurrentB->pNext; // 将当前指针指向新建结点
pCurrentB->ScanLine = 2; // 当前指针(第二个结点)的扫描线位置为2
std::cout << "The ScanLine of the second node is " << pCurrentB->ScanLine << std::endl; // 输出当前指针的扫描线位置
pCurrentB->pNext = NULL; // 只构造两个结点,当前指针(第二个结点)的pNecxt指针置空
delete pHead; // 释放头指针所指向的内存空间
delete pCurrentB; // 释放当前指针所指向的内存空间
pHead = NULL; // 头指针置空
pCurrentB = NULL; // 当前指针置空
std::cin.get();
}
运行程序,打印:
The ScanLine of the first node is 1
The ScanLine of the second node is 2
继承与派生
继承是指在已有类的基础上增加新的内容创建一个新类。
派生类的定义:
class 派生类名:[继承方式]基类名
{
派生类新增加的成员;
};
继承方式:指派生类访问控制方式,用于控制基类中声明的成员在多大范围内能被派生类中的成员函数访问
继承方式有3种:
- 公有继承public
- 私有继承private
- 保护继承protected
基类的构造函数是不能被继承的,对继承过来的基类成员的初始化工作要由派生类的构造函数完成。在设计派生类构造函数时,不仅要考虑对派生类新增加的成员变量初始化,还应当考虑对基类成员变量的初始化。
实现方法是执行派生类的构造函数时调用基类的构造函数。
派生类构造函数的定义:
派生类构造函数名(总参数表列):基类构造函数名(参数表列)
{派生类中新增成员变量初始化语句}
例4
从已有的长方形类CRectangle继承出长方形类CCuboid,增加长度成员变量length和计算体积成员函数volumn(),试计算长方体的体积。
#include <iostream>
class CRectangle
{
public:
CRectangle(int width, int height); // 声明带参构造函数
~CRectangle(); // 声明析构函数
double perimeter(); // 声明计算周长成员函数
double area(); // 声明计算面积成员函数
protected:
int width; // 声明长方形的宽度成员变量
int height; // 声明长方形的高度成员变量
};
CRectangle::CRectangle(int width, int height) // 定义带参构造函数
{
this->width = width;
this->height = height;
std::cout << "Created CRectangle!" << std::endl;
}
CRectangle::~CRectangle() // 定义析构函数
{
std::cout << "Destroyed CRectangle!" << std::endl;
}
double CRectangle::perimeter() // 定义计算周长成员函数
{
return 2 * (width + height);
}
double CRectangle::area() // 定义计算面积成员函数
{
return width * height;
}
class CCuboid : public CRectangle // 公有继承的派生类
{
public:
CCuboid(int width, int height, int length); // 声明派生类构造函数
~CCuboid(); // 声明派生类析构函数
double volume(); // 声明派生类计算体积成员函数
private:
int length; // 声明派生类长度成员变量
};
CCuboid::CCuboid(int width, int height, int length) : CRectangle(width, height) // 定义派生类构造函数,width, height的初始化继承自基类
{
this->length = length;
std::cout << "Created CCuboid!" << std::endl;
}
CCuboid::~CCuboid() // 定义派生类析构函数
{
std::cout << "Destroyed CCuboid!" << std::endl;
}
double CCuboid::volume() // 定义派生类计算体积成员函数
{
return width * height * length;
}
int main()
{
CCuboid * pCuboid = new CCuboid(30, 20, 100);
std::cout << "The volume of Cuboid is " << pCuboid->volume() << std::endl;
delete pCuboid;
std::cin.get();
}
创建CCuboi指针pCuboid时,先调用基类的构造函数,再调用派生类的构造函数,打印:
Created CRectangle!
Created CCuboid!
然后,打印语句:
The volume of Cuboid is 60000
最后,释放pCuboid的内存,先调用派生类的析构函数,再调用基类的析构函数,打印:
Destroyed CCuboid!
Destroyed CRectangle!
参考:
孔令德, 计算机图形学基础教程(Visual C++版)