简介本文介绍C++类设计过程中的几个原则,感兴趣的朋友可以参考一下。
虚构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数。如果析构函数不是虚函数,而程序执行时又要通过基类的指针去销毁派生类的动态对象,那么用delete销毁对象时, 只调用了基类的析构函数,未调用派生类的析构函数 。这样会造成销毁对象不完全。
析构函数为虚函数,当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
1、虚析构函数的作用:当基类指针指向派生类并delete时,可以调用派生类的析构函数;
2、私有析构函数的作用: 令对象只能在堆上生成,即用new方法 。原理是C++是一个静态绑定语言,在编译过程中,所有的非虚函数调用都必须分析完成(虚函数也要检查可访问性)。因此,当在栈上生成对象时,对象会自动析构,即析构函数必须可以访问。而在堆上生成对象时,析构步骤由程序员控制,不一定需要析构函数。同时,此生成对象不能直接delete删除,析构过程还需要一个专门的成员函数(代码中的destory()函数)。
例子:
#include <iostream>
using namespace std;
class A{
public:
A(){
cout<<"construct A"<<endl;
}
virtual ~A(){
cout<<"destory A"<<endl;
}
};
class B:public A{
public:
B(){
cout<<"construct B"<<endl;
}
~B(){
cout<<"destory B"<<endl;
}
};
class C{
~C(){
cout<<"destory C"<<endl;
}
public:
C(){
cout<<"construct C"<<endl;
}
void destory(){
delete this;
}
};
int main(){
A* p = new B;
delete p;
C* p1 = new C;//new为在堆上分配空间
cout<<"new分配地址 "<<p1<<endl;
//delete p1; //编译出错,不能直接delete
p1->destory();
//C t; //编译出错,构造函数为私有的
A a;
cout<<"不用new "<<&a<<endl;
return 0;
}
场景:动态多维数组......
拷贝构造函数是一种特殊的构造函数,也叫复制构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
注意:函数名称必须和类名称一致;虽然编译器自行会定义一个,但如果类中有动态内存分配,必须有一个自定义拷贝构造函数,以实现深层拷贝。
// 例子:一个表(Spreadsheet)中含有许多单元格(SpreadsheetCell.)。
#pragma once
class SpreadsheetCell;
class Spreadsheet
{
public:
Spreadsheet(int inWidth, int inHeight);
virtual ~Spreadsheet();
Spreadsheet(const Spreadsheet& src);
Spreadsheet& operator=(const Spreadsheet& rhs);
void setCellAt(int x, int y, const SpreadsheetCell& cell);
SpreadsheetCell& getCellAt(int x, int y);
private:
bool inRange(int val, int upper);
// void copyFrom(const Spreadsheet& src);
int mWidth, mHeight;
SpreadsheetCell** mCells;
};
//////////////////////////////////////////////////////////
// 构造函数与析构函数:
Spreadsheet::Spreadsheet(int inWidth, int inHeight)
: mWidth(inWidth), mHeight(inHeight)
{
mCells = new SpreadsheetCell*[mWidth];
for (int i = 0; i < mWidth; i++) {
mCells[i] = new SpreadsheetCell[mHeight];
}
}
Spreadsheet::~Spreadsheet()
{
for (int i = 0; i < mWidth; i++) {
delete[] mCells[i];
}
delete[] mCells;
mCells = nullptr;
}
//////////////////////////////////////////////////////////
// 复制构造函数和赋值运算符 (初版)
Spreadsheet::Spreadsheet(const Spreadsheet& src)
{
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new SpreadsheetCell*[mWidth];
for (int i = 0; i < mWidth; i++) {
mCells[i] = new SpreadsheetCell[mHeight];
}
for (int i = 0; i < mWidth; i++) {
for (int j = 0; j < mHeight; j++) {
mCells[i][j] = src.mCells[i][j];
}
}
}
// 注意:自赋值检测不仅是为了效率,更是为了正确性。赋值运算符首先删除左边对象的mCells,再给左边对象分配一个新的mCells。
Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
// check for self-assignment
if (this == &rhs) {
return *this;
}
// free the old memory
for (int i = 0; i < mWidth; i++) {
delete[] mCells[i];
}
delete[] mCells;
mCells = nullptr;
// copy the new memory
mWidth = rhs.mWidth;
mHeight = rhs.mHeight;
mCells = new SpreadsheetCell*[mWidth];
for (int i = 0; i < mWidth; i++) {
mCells[i] = new SpreadsheetCell[mHeight];
}
for (int i = 0; i < mWidth; i++) {
for (int j = 0; j < mHeight; j++) {
mCells[i][j] = rhs.mCells[i][j];
}
}
return *this;
}
// 这里的复制构造函数和赋值运算符十分相似,可以使用通用辅助方法实现简约。
void Spreadsheet::copyFrom(const Spreadsheet& src)
{
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new SpreadsheetCell*[mWidth];
for (int i = 0; i < mWidth; i++) {
mCells[i] = new SpreadsheetCell[mHeight];
}
for (int i = 0; i < mWidth; i++) {
for (int j = 0; j < mHeight; j++) {
mCells[i][j] = src.mCells[i][j];
}
}
}
// 复制构造函数和赋值运算符 (终版)
Spreadsheet::Spreadsheet(const Spreadsheet &src)
{
copyFrom(src);
}
Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
// check for self-assignment
if (this == &rhs) {
return *this;
}
// free the old memory
for (int i = 0; i < mWidth; i++) {
delete[] mCells[i];
}
delete[] mCells;
mCells = nullptr;
// copy the new memory
copyFrom(rhs);
return *this;
}
#include <iostream>
#include <string>
using namespace std;
class A
{
private:
int i = 5;
string str = "初始值";
public:
A(){
str = "委托构造函数";
i = 99;
}
A(int ii) :A(){
// 不能写成AA(int ii):A(),i(ii)
// 委托构造函数不能再利用初始化器初始化其他数据成员
i = ii;
}
void show(){
cout << "i=" << i << ",str=" << str << endl;
}
};
int main()
{
A a(10);
a.show();
system("pause");
return 0;
}
注意:委托构造函数必须放在构造函数初始化器中,且必须是列表中唯一的成员初始化器。 要注意避免出现构造函数的递归 。
Getters : 会改变的直接返回该类型,不应改变的返回 const T& 。
Setters : 会改变的直接参数为该类型,不应改变的参数为 const T& 。
类比:不变的成员都应设置为 const 。
#include <string>
class Employee
{
public:
Employee();
virtual ~Employee();
void promote(int raiseAmount = 1000);
void demote(int demeritAmount = 1000);
void hire(); // Hires or rehires the employee
void fire(); // Dismisses the employee
void display() const;// Outputs employee info to console
// Getters and setters
void setFirstName(const std::string& firstName);
const std::string& getFirstName() const;
void setLastName(const std::string& lastName);
const std::string& getLastName() const;
void setEmployeeNumber(int employeeNumber);
int getEmployeeNumber() const;
void setSalary(int newSalary);
int getSalary() const;
bool getIsHired() const;
private:
std::string mFirstName;
std::string mLastName;
int mEmployeeNumber;
int mSalary;
bool mHired;
};
类比:不变的成员都应设置为 const 。
对于const还有一个重要的用法,对于常对象,只能访问常函数。
Void A::func() const
Const A a;
a.func();
static 方法不属于特定对象,没有this指针。
注意:不能将static声明为const,这也是多余的,因为静态方法没有类的实例,因此不会改变类内部的值。
子类的重写基类(以接口类居多)的虚函数时,建议在函数后面加入override,让编译器自动检查函数是否与基类函数完全匹配。
原因:在函数后面加入override,让编译器自动检查函数是否与基类函数完全匹配,从而正确实现方法重写。不加的话:如果发现函数返回值或者参数类型不同,则是在派生类中创建了新的方法,而不是重写。
本文向大家介绍一个C++实战项目:C++实现雪花算法(SnowFlake)产生唯一ID,主要涉及雪花算法、算法知识等,具有一定的C++实战价值,感兴趣的朋友可以参考一下。
本文介绍一个C++代码片段:如何在C++中删除一个文件目录下的所有文件及目录,感兴趣的朋友可以参考一下。
本文介绍C++实现C++实现8种排序算法,主要包括冒泡排序、插入排序、二分插入排序、希尔排序、直接选择排序、堆排序、归并排序、快速排序,直接上代码,感兴趣的朋友可以参考一下。
本文介绍C++实现线程同步的四种方式:事件对象、互斥对象、临界区、信号量,感兴趣的朋友可以参考一下。
本文介绍C++内存泄漏的检测与定位方法,感兴趣的朋友可以参考一下。
本文向大家介绍一个C++实战项目:C++实现一个多线程安全的队列容器模板类,主要涉及C++模板类的使用、互斥体实现多线程安全、队列数据结构等知识,具有一定的C++实战价值,感兴趣的朋友可以参考一下。