本篇文章主要是培养正规的、大气的编程习惯,文章的构思来源来自中国台湾著名专家侯捷 – c++面向对象程序设计课程。
每个人的编程水平都不一样,所以本篇文章对于一部分人来说,可能并没有什么用;文章主要想给大家传达一种设计习惯。
这里明确将类的设计分成基于对象和面向对象两种,逐步深入。
基于对象设计的类:也即是简单的类,没有类间关系的类,这里又被分为,不含指针成员的类和含指针成员的类。
面向对象设计的类:也即是复杂的类,含有类间关系的类,涉及继承、复合、委托三大关系等。
这里按照课件设计一个复数的类complex;
当你着手设计复数类的时候,你可能要有如下的思考
除了功能上的考虑之外,你是否仔细考虑过以下问题
(1).h文件的防卫式声明
(2)数据成员尽量私有
(3)构造函数默认参数,尽量使用初值列
(4)函数名称的尽量格式统一与易读
(5)传递参数:能传引用不传值,不改参数数据就加const
(6)返回值传递:能传引用不传值,函数尽量建议inline,不改数据成员函数后面就加const
下面一步一步的设计类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
这里按照课件设计一个字符串的类string;
当你着手设计string类的时候,你可能要有如下的思考
除了功能上的考虑和之前讲过的设计原则之外,你是否仔细考虑过以下问题
(1)拷贝构造
(2)拷贝赋值
(3)析构函数
下面一步一步的设计类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;//用下面一张图片解释原因
}
这里完全按照侯捷老师的课程术语说明。
复合典型例子
委托典型例子
继承典型例子
在继承关系中,广泛应用虚函数,这里将类的成员函数分为三种;非虚函数、虚函数、纯虚函数,有以下原则:
非虚函数( void fun(); ) :不建议子类覆盖它
虚函数( virtual void fun(); ):希望子类可以覆盖它
纯虚函数( virtual void fun() = 0; ) :子类一定要覆盖它
往往以上讲述的三种类的关系,多组合使用,多出现于设计模式之中,这里举例简单说明;关于更多的设计模式自行探索。
组合模式:
从上图我们可以看出,通过类的委托关系和继承关系设计出了这个组合模式。
主要功能:将对象组合成树形结构以表示“部分-整体”的层次结构,这可以使得用户在使用单个对象和组合对象时有一致性。
这里只是说明根据类的组合关系,我们可以设计出解决特定问题的模式,解释设计模式并不是我们这里的目的。这里根据老师的课件,这里也只是一部分。
没有一个编程的思想,或者是一种解决问题的方法论,即使学了再多的语法,都发挥不了它们真正的作用。
纵然侯捷老师讲的那么好,若不坚持练习,也许仍然无用。
勿在浮沙筑高台,与大家共勉!!!
如果需要侯捷老师课件还有代码的话,可以在公众号:小白学移动机器人,发送:侯捷基础
以往链接,点击访问。
上一篇:图解SLAM
下一篇:更新中
系列文章:从零搭建ROS机器人
如果你感觉,我的文章比较适合你,关注我,点个赞,给你不一样的惊喜。