如何正规的、大气的设计一个类?

如何正规的、大气的设计一个类?

1、前言

本篇文章主要是培养正规的、大气的编程习惯,文章的构思来源来自中国台湾著名专家侯捷 – c++面向对象程序设计课程。

每个人的编程水平都不一样,所以本篇文章对于一部分人来说,可能并没有什么用;文章主要想给大家传达一种设计习惯。

2、类(class)设计的分类

这里明确将类的设计分成基于对象和面向对象两种,逐步深入。

基于对象设计的类:也即是简单的类,没有类间关系的类,这里又被分为,不含指针成员的类和含指针成员的类。

面向对象设计的类:也即是复杂的类,含有类间关系的类,涉及继承、复合、委托三大关系等。

3、基于对象类的设计

3.1、类不含指针成员

3.1.1、 说明

这里按照课件设计一个复数的类complex;

当你着手设计复数类的时候,你可能要有如下的思考

除了功能上的考虑之外,你是否仔细考虑过以下问题

(1).h文件的防卫式声明

(2)数据成员尽量私有

(3)构造函数默认参数,尽量使用初值列

(4)函数名称的尽量格式统一与易读

(5)传递参数:能传引用不传值,不改参数数据就加const

(6)返回值传递:能传引用不传值,函数尽量建议inline,不改数据成员函数后面就加const

3.1.2、实践

下面一步一步的设计类complex,依次拓展,局部代码,仅做示意。

.h文件的防卫式声明

#ifndef __MYCOMPLEX__ //避免该.h文件重复引入,自定义,防止重复
#define __MYCOMPLEX__
...
#endif

数据成员尽量私有

#ifndef __MYCOMPLEX__ 
#define __MYCOMPLEX__
class complex
{
     
private:
	double re,im;    //数据成员设计规则,能设计成私有,就设计成私有的原则    
};
#endif

构造参数的设计

#ifndef __MYCOMPLEX__ 
#define __MYCOMPLEX__
class complex
{
     
    //构造函数,初始化了数据成员,能使用初值列(:re(r),im(i))就是用初值列的原则,其余的初始化部分可以放到{}内
    complex(double r=0, double i=0):re(r),im(i) {
     }
private:
	double re,im;    
};
#endif

操作符重载函数的设计 operator +=

#ifndef __MYCOMPLEX__ 
#define __MYCOMPLEX__
class complex
{
     
    complex(double r=0, double i=0):re(r),im(i) {
     }
    //函数名 operator +=,重载操作符+=
    //考虑传递参数传值/传引用:能使用引用传递就尽量使用的原则(complex&),毕竟只有4个字节,不大于4个字节类型的能考虑不用
    //考虑传递参数是否改变引用的数据:不改变就一定使用const的原则(const complex&)
    //考虑返回值传递:一般来说A+=B,B加到A上,返回void也可以,但是A+=B+=C;呢?从右到左运算,B+=C之后要返回运算之后B的值
    //所以这里才有了返回参数,并且传递的是引用,再一次是能传引用就不传值。(complex&)
    //考虑inline:写上inline也只是建议编译器inline,具体还要看编辑器操作,非常小的成员函数也会自动inline,所以inline随意
    //考虑函数后面+const:这里明显改变了this的数据成员,所以这里不加const
    complex& operator += (const complex&);//每一个类的成员函数都会有一个隐藏的this指针
private:
	double re,im;    
};
#endif

其他成员函数设计 real()和imag()

#ifndef __MYCOMPLEX__ 
#define __MYCOMPLEX__
class complex
{
     
    complex(double r=0, double i=0):re(r),im(i) {
     }
    complex& operator += (const complex&);
    //当每设计一个函数接口的时候,都要考虑函数名、传递参数、返回值传递、函数inline和常量函数const
    //比如这里:函数直接在类内实现,默认inline,不更改数据成员,函数后面加const
    //这里举个例子:如果定义一个const complex A(1,2);若你的real()不加const;则调用A.real();编译器会报错
	double real() const {
     return re;}
	double imag() const {
     return im;}
private:
	double re,im;    
};
#endif

其他功能函数设计 operator +

#ifndef __MYCOMPLEX__ 
#define __MYCOMPLEX__
class complex
{
     
    complex(double r=0, double i=0):re(r),im(i) {
     }
    complex& operator += (const complex&);
	double real() const {
     return re;}
	double imag() const {
     return im;}
private:
	double re,im;    
};
//这里重载了操作符+;这里并不是一个类内的成员函数,而设计成了一个全局成员函数,大概有两个原因:无需引入this且含有多种变种
//关于函数名、传递参数、返回值传递、函数inline和const函数与上面的设计原则一致
//这里引出临时对象complex (x.real() + y.imag(),x.imag() + y.imag()),所以返回值也就只能传值complex而不是引用啦
//return 返回的是个副本,若接收还为引用,临时对象已被销毁,那就找不到这个引用了
inline complex
operator + (const complex& x,const complex& y){
     
    return complex (x.real() + y.imag(),x.imag() + y.imag());
}
inline complex
operator + (const complex& x,const double y){
     }
inline complex
operator + (const double x,const complex& y){
     }

#endif

3.2、类含指针成员

3.2.1、说明

这里按照课件设计一个字符串的类string;

当你着手设计string类的时候,你可能要有如下的思考

除了功能上的考虑和之前讲过的设计原则之外,你是否仔细考虑过以下问题

(1)拷贝构造

(2)拷贝赋值

(3)析构函数

3.2.2、实践

下面一步一步的设计类string,依次拓展,之前说过类似的就不再说了,仅做示意。

字符串大小管理的方式,这里采用指针动态管理

#ifndef __MYSTRING__ //防卫式声明
#define __MYSTRING__
class String
{
     
public:                                 
   String(const char* cstr=0);//默认参数,不改变传入的字符串,所以是(const char* cstr)                     
private:
   char* m_data;              //通过这种方式动态的管理字符串的大小
};

inline//函数尽量建议inline
String::String(const char* cstr)
{
     
   if (cstr) {
     //判断传入字符串是否为空
      m_data = new char[strlen(cstr)+1];//根据传入参数为m_data动态申请足够的内存空间
      strcpy(m_data, cstr);//将传入的字符串拷贝到m_data的内存上
   }
   else {
        
      m_data = new char[1];//就算传入参数为空,也为m_data申请一个存储'\0'的空间
      *m_data = '\0';
   }
}
#endif

拷贝构造

#ifndef __MYSTRING__ 
#define __MYSTRING__
class String
{
     
public:                                 
   String(const char* cstr=0);  
   //拷贝构造,用一个字符串对象初始化新的字符串对象,不改变传入参数的内容,故加const,并且是遵循能传引用不用传值的原则       
   String(const String& str); 
private:
   char* m_data;              
};
#endif

拷贝赋值

#ifndef __MYSTRING__ 
#define __MYSTRING__
class String
{
     
public:                                 
   String(const char* cstr=0);  
   //拷贝赋值,用B字符串对象给A字符串对象赋值,如A=B,不改变传入参数的内容,故加const,并且是遵循能传引用不用传值的原则 
   //对于返回值传递的考虑,若仅是A=B,返回void即可,若是A=B=C呢?这里B和A都会充当this的成分,所以返回值为String&
   String& operator=(const String& str);   
private:
   char* m_data;              
};
//拷贝赋值函数
inline
String& String::operator=(const String& str)//接口设计与上文一致
{
     
   if (this == &str)//这里一定要检测是否自我赋值
      return *this;
   delete[] m_data;//删除原本的空间
   m_data = new char[ strlen(str.m_data) + 1 ];//新分配一个足够大的空间
   strcpy(m_data, str.m_data);//将数据从参数拷贝到m_data
   return *this;//传递者(*this)不需要知道接收者(String&)是否以引用形式接收,能用引用接收就用
   //当传递者是一个局部对象,返回值决不能使用引用接收
}
#endif

析构函数

inline
String::~String()
{
     
   delete[] m_data;//用下面一张图片解释原因
}

如何正规的、大气的设计一个类?_第1张图片

4、面向对象类的设计

这里完全按照侯捷老师的课程术语说明。

4.1、复合

复合典型例子

如何正规的、大气的设计一个类?_第2张图片

4.2、委托

委托典型例子

如何正规的、大气的设计一个类?_第3张图片

4.3、继承

继承典型例子

如何正规的、大气的设计一个类?_第4张图片

4.4、拓展:虚函数

在继承关系中,广泛应用虚函数,这里将类的成员函数分为三种;非虚函数、虚函数、纯虚函数,有以下原则:

非虚函数( void fun(); ) :不建议子类覆盖它

虚函数( virtual void fun(); ):希望子类可以覆盖它

纯虚函数( virtual void fun() = 0; ) :子类一定要覆盖它

4.5、举例说明

往往以上讲述的三种类的关系,多组合使用,多出现于设计模式之中,这里举例简单说明;关于更多的设计模式自行探索。

组合模式:

如何正规的、大气的设计一个类?_第5张图片

从上图我们可以看出,通过类的委托关系和继承关系设计出了这个组合模式。

主要功能将对象组合成树形结构以表示“部分-整体”的层次结构,这可以使得用户在使用单个对象和组合对象时有一致性

这里只是说明根据类的组合关系,我们可以设计出解决特定问题的模式,解释设计模式并不是我们这里的目的。这里根据老师的课件,这里也只是一部分。

5、总结

没有一个编程的思想,或者是一种解决问题的方法论,即使学了再多的语法,都发挥不了它们真正的作用。

纵然侯捷老师讲的那么好,若不坚持练习,也许仍然无用。

勿在浮沙筑高台,与大家共勉!!!

如果需要侯捷老师课件还有代码的话,可以在公众号:小白学移动机器人,发送:侯捷基础

以往链接,点击访问。

上一篇:图解SLAM
下一篇:更新中
系列文章:从零搭建ROS机器人

如果你感觉,我的文章比较适合你,关注我,点个赞,给你不一样的惊喜。

稿定设计导出-20200810-205521
在这里插入图片描述

你可能感兴趣的:(C++,c++,类的设计)