前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++类的设计与实现规范

C++类的设计与实现规范

作者头像
恋喵大鲤鱼
发布2019-02-22 11:34:03
1.2K0
发布2019-02-22 11:34:03
举报
文章被收录于专栏:C/C++基础

规范是一种规定,遵守这种规定能够带来长远的利益,而违反这种规定却不会立即收到惩罚。程序设计的规范是人们在长期的编程实践中总结出来的,深入理解这些规范需要认真的思考和大量的实践 。不符合程序设计规范的代码也能通过编译并运行,但是从长远来看,代码存在可读性差、安全性低、不易扩展、不易维护等问题。类是面向对象程序设计最主要的元素,遵循必要的规范,设计出性能优良的类,并以适当的方式实现,是编写出高质量程序的关键。

1.规范一:将类的定义放在头文件中实现

这样可以保证通过引入头文件时,使用的是同一个类,也有利于代码维护。比如我们有如下Student类:

代码语言:javascript
复制
//a.cpp
class Student
{
	uint64_t id;
	string name;
public:
	uint64_t getID(){return id;};
	string getName(){return name;}
};

//b.cpp
//有相同的类Student定义
class Student
{
	uint64_t id;
	string name;
public:
	uint64_t getID(){return id;};
	string getName(){return name;}
};

假如根据项目的新需求,类Student需要添加年龄(age)私有数据成员,此时,如果更改了a.cpp中的Student定义而忘记更改b.cpp中的定义,则会出现类定义不一致的情况,容易导致编译错误。即使记得每个源文件都需要修改,如果几十甚至上百个源文件都定义了类Student,那么我们岂不是要重复更改很多次,这种费力不讨好的做法应该尽量避免。有没有一劳永逸的做法,其实是有的,我们将类的定义放在头文件中,在需要类的源文件包含类定义所在头文件即可,保证了类定义的一致性,并且修改效率高,代码易于维护。

2.规范二:尽量将数据成员申明为私有

数据成员表示了类对象的状态,这些状态对外界应该是不可见的。在设计一个类的时候,如果把它的数据成员访问权限设为public和protected,会带来如下影响。 (1)会使类的封装性遭到破坏。 (2)public数据成员,类的用户直接以来数据成员,一旦数据成员的定义频繁改变,类的所有客户端代码都要修改,增加了代码模块间的耦合度。

考察如下示例程序:

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class Student
{
public:
	uint64_t id;
	string name;

public:
	Student()
	{
		id = 0;
		name = "";
	};

	void print()
	{
		cout<<"id:"<< id<<" name:"<<name<<endl;
	}
	uint64_t getID() { return id; };
	string getName() { return name; }
};

int main(int argc, char* argv[])
{
	Student s;
	s.id = 1;
	s.name = "C罗";
	s.print();
}

程序输出结果:

代码语言:javascript
复制
id:1 name:C罗

Student是一个学生类,我们希望用户能够正确的使用Student来创建学生对象,但是在上面的代码中,我们发现用户给学生设置的名称为“C罗”,然而中国目前姓名是不能以字母开头的,所以这个名字是不合法的。产生这个错误的原因是Student类涉及存在缺陷,将数据成员id和name的访问权限设置为public,意味着有无数的函数可以不加限制地访问学生对象的数据成员,这样就无法保证每次对数据成员的设置是正确的。如果我们增加一个设置接口,例如成员函数int set(uint64_t id,const string& name){...},那么能够修改数据成员的接口只有一个,只要在修改接口中排除各种错误的输入,就可以保证对Student对象的正确设置。这种对数据成员的直接访问,是对类封装性的一种破坏。

另外,从代码模块间的耦合度来看,将数据成员设置为共有,意味着所有用户对类数据成员直接依赖,一旦数据成员的定义发生变化,类的所有客户端代码均需要修改,降低了代码的可维护性。

同样地,将数据成员声明为protected,也破坏了类的封装性,因为该类的所有子类均可以直接访问protected数据成员,如果该类的子类数量庞大,一旦数据成员定义发生变化,所有的派生类都需要重写。所以,应该尽量将所有的数据成员申明为私有(private)。

3.规范三:将成员函数放到类外定义

类成员函数既可以放在类体内定义,也可以放在类体外定义。如果将类成员函数定义在类体内,会有如下影响。 (1)类的成员函数定义在类的内部影响可读性。一般来说,类的定义放在头文件中,使用时被不同的源文件包含,如果类成员函数定义在类体内,将会是代码体积增大,影响阅读,不利于类的修改与维护。 (2)泄露类的实现细节,不利于保护设计者的合法权益。因为接口开放给外部使用时,需要给出原型,比如类的定义,如果将类成员函数定义放在类体内,则函数实现将被暴露。 (3)会存在潜在的风险,如果类的成员函数存在多重定义,由于类不具有外部连接特性,C++编译器不能充分检查出类定义的二义性。假设有一个类Student的定义放在两个头文件中,并且同名成员函数print()出现了二义性,考察如下程序:

代码语言:javascript
复制
/*test1.h*/
class Student
{
	string name;

public:
	Student()
	{
		name = "lvlv";
	};

	void print()
	{
		cout<<"name:"<<name<<endl;
	}
};
/*end test1.h*/

/*test1.cpp*/
#include "test1.h"

void useClass();

int main()
{
	Student s;
	s.print();
	useClass();
}
/*end test1.cpp*/

/*test2.h*/
class Student
{
	string name;

public:
	Student()
	{
		name = "jf";
	};

	void print()
	{
		cout<<"another name:"<<name<<endl;
	}
};
/*end test2.h*/

/*test2.cpp*/
#include "test2.h"

void useClass()
{
	Student s;
	s.print();
}
/*end test2.cpp*/

编译运行上面的程序,输出结果如下:

代码语言:javascript
复制
name:lvlv
name:lvlv

上面错误地将类Student成员函数print()放在类体内定义并且出现重定义,本希望编译器在编译时能够帮助开发人员发现这种错误,但是由于编译器采用分离编译模式,各个源文件中的函数在编译时互不干涉,在连接时又由于类体内定义的函数为inline函数,不具有外部连接性,导致连接时也未发现重定义错误。如果将类成员函数放在类外定义,则编译器可以发现这种重定义错误,所以在类的实现中,应该将类成员函数尽可能地放在类外定义,如果要定义内联函数,只需要在成员函数定义时显示地使用inline关键字即可。


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.4.10类的设计与实现规范.P164-P167

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015年10月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.规范一:将类的定义放在头文件中实现
  • 2.规范二:尽量将数据成员申明为私有
  • 3.规范三:将成员函数放到类外定义
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档