您现在的位置是:网站首页 > 语言基础 > C++类设计原则

简介本文介绍C++类设计过程中的几个原则,感兴趣的朋友可以参考一下。

面向对象和与面向过程的比较

  • 对象使数据和成员函数之间的结合更加紧密,更加有意义;
  • 对象更便于查找错误,因为操作都只局限于它们的对象;
  • 对象可以对其他对象隐藏某些操作细节,从而使得这些操作不会受到其他对象的影响。

析构函数设置为virtual且私有化

虚构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数。如果析构函数不是虚函数,而程序执行时又要通过基类的指针去销毁派生类的动态对象,那么用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;
}

面向对象,编码规范,C++类设计原则

自定义拷贝构造函数和赋值构造函数

场景:动态多维数组......

拷贝构造函数是一种特殊的构造函数,也叫复制构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。

注意:函数名称必须和类名称一致;虽然编译器自行会定义一个,但如果类中有动态内存分配,必须有一个自定义拷贝构造函数,以实现深层拷贝。

// 例子:一个表(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 and Setters

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设计原则

static 方法不属于特定对象,没有this指针。

注意:不能将static声明为const,这也是多余的,因为静态方法没有类的实例,因此不会改变类内部的值。

Override

子类的重写基类(以接口类居多)的虚函数时,建议在函数后面加入override,让编译器自动检查函数是否与基类函数完全匹配。

原因:在函数后面加入override,让编译器自动检查函数是否与基类函数完全匹配,从而正确实现方法重写。不加的话:如果发现函数返回值或者参数类型不同,则是在派生类中创建了新的方法,而不是重写。

更多为你推荐