美文网首页
c++类设计

c++类设计

作者: 某昆 | 来源:发表于2020-06-30 16:56 被阅读0次

    c++和 java有很多的小点不一样,今天来总结下c++在类设计方面的知识点,并且比较下和java的异同

    • 构造函数
    • 复制构造函数
    • 友元函数
    • 虚函数
    • 转换函数
    • 传对象、引用、指针
    • const

    构造函数

    一般子类构造函数的成员初始化列表中,如果没有显式调用基类的构造函数,则编译器会使用基类默认的构造函数来构造子类对象的基类部分

    如上图,如果不显式调用基类的构造函数,则编译器会自动给加上基类的默认构造函数,和 java 不一样的是,c++ 不是用 super 关键字来调用基类的构造函数或者方法。c++ 使用 命名空间 的形式来调用基类的方法。如下图:

    复制构造函数

    c++java 第二个重大的区别即在于此。虽然 java也有深拷贝和浅拷贝的概念,但java发生拷贝只在特定的时刻,即调用 clone 方法,而 c++ 则发生的太触不及防了,以下几种情况均会调用复制构造函数:

    • 将新对象初始化为一个同类对象
    • 按值传递对象传递给函数
    • 函数按值返回对象
    • 编译器生成临时对象

    一般复制构造函数是这么写的:

    什么时候需要写复制构造函数呢?

    使用new初始化的成员指针或者类可能包含需要修改的静态变量时,需要自己定义复制构造函数。如果不自定义,编译器就执行默认的复制构造函数,即将新对象的每个成员都初始化为原始对象相应成员的值。

    问题就出在这,如果有指针成员变量,复制仅把指针指向原始对象所指的内存块,当原始对象被回收时,相应的内存块也被回收,但新对象的指针还存在,它指向一块被回收的内存,程序会报错。

    java 类似,需要自定义复制构造函数,实现深拷贝,如上图所示代码一样,重新为自己的指针分配内存区域,而不是简单赋值

    值得一提的是,使用赋值运算符也会出现这种深拷贝浅拷贝的问题,需要显式定义赋值运算符函数。

    友元函数

    友元函数是什么呢,就是在函数声明中添加 friend 关键字,友元函数不是成员函数,但是可以像成员函数一样,访问类中的成员变量。

    友元函数一般用在什么场景呢?当类的成员函数无法实现相关功能时,只能使用友元函数上场了。比如要重写一些运算符函数时。例如,现在要把 BadString 类的 << 运算符重写下,以便直接打印对象。

     friend std::istream& operator>>(std::istream& is, BadString& s); 
    

    运算符函数的调用格式其实和其它方法一样,如果此函数不是友元函数,则调用格式是

    BadString.operator>>(cout, badstring&)
    BadString>>(cout, badstring&)//上一行的简写
    

    但我们知道,真实的格式不是这样的,而是

    cout >> badstring
    

    调用对象是 cout,它是一个istream对象,而不是BadString,所以 >> 运算符函数它不可能是BadString的成员函数,但它还要访问BadString的成员变量,怎么办呢?只能用友元函数了,这也正符合友元函数的特点,不是成员函数,却可以访问类的成员变量。

    为什么 c++ 中有友元函数呢,而 java 没有。因为java 中没有这种场景,java 所有类都有个共同的基类 object,什么对象不能表示呢,如果不知道类名,则用object替代。试想下,在 java中,打印类对象,只要类重写 toString 方法即可。因为 System.out.println参数传的是 object

    虚函数

    虚函数,在函数声明之前添加关键字 virtual。虚函数在基类中需要被声明,子类中也需要申明。

    虚函数的关键作用是多态,基类声明一个虚函数,子类也声明此虚函数,并且基类和子类都实现此虚函数。如果有指针或者引用调用此虚函数,则会根据指针或者引用的实际对象来调用真正的函数。

    c++中,如何根据类声明找到类的定义,有两种方法:

    • 静态联编
    • 动态联编

    顾名思义,静态联编发生在编译期间,编译器根据函数的参数,返回值,名称,一一对应查找相应函数,以应对各种情况,比如函数重载

    而虚函数又让这个过程更复杂了,要实现多态,只能在程序运行期间来确定指针或者引用的真实类型了,程序运行期间选择执行正确的虚函数,这就叫做动态联编

    c++中,构造函数、析构函数、友元函数是不会被子类继承的。构造函数不存在虚函数这个概念,因为基类也需要被初始化。而友元函数不是成员函数,所以不存在被继承一说。

    析构函数一般要被声明为虚函数。因为子类中可能会额外添加一些指针成员变量,如果析构函数不是虚函数,那么子类被回收时,只会执行基类的析构函数,子类中的额外指针无法被回收,会有内存泄漏产生。所以,基类的析构函数一定是虚函数

    当执行完子类的析构函数,然后会继续执行基类的析构函数

    转换函数

    如果存在一个单形参的构造函数,则赋值时可以采用一些千奇百怪的方式

    单形参的构造函数,如果不用关键字 explicit 给限制下,就可以直接给对象赋值,赋值的类型即是单形参构造函数里的形参类型,例如上图中,Stone类可以使用double类作单参数调用构造函数,所以就可以直接给 Stone类赋值。其实背后的流程还是先调用单形参构造函数,生成一个临时对象后,再通过调用=运算符函数,把临时对象赋值给Stone对象

    可以通过重写转换函数,例如上例中的 operator doubleoperator int,即可把对象直接赋值给相应类型,Stone重写了 doubleint转换函数,所以可以直接赋值给doubleint

    传对象、引用、指针

    c++中的传值和java的传值类似,都是在函数中构造一个临时对象,并且将临时对象赋值为实参。

    所以如果是传对象的话,c++涉及到了复制拷贝函数,效率较低,而且可能达不到想要的效果,本想修改实参的相关数据,结果发现实参未有变化,一般不推荐传实参。

    函数返回值,某些时候必须要返回对象,而不是指针或引用,比如常见的+运算符函数,因为要返回一个全新的对象。

    如果是传引用的话,事实上这非常常见,对于类对象,c++甚至推荐使用传引用,而不是传指针。在返回引用的时候有坑,需要特别小心,返回引用,不能返回函数中临时对象的引用,因为函数一执行完,临时变量就被回收了,它的引用自然也会被回收,这会引起异常

    传指针,其实传引用或者指针都能有效避免发生复制构造函数,它的效率是较高的,也不会发生传值引起的问题。但是有个小点要注意下,如果是初始化一个指针,形参就必须是指针的指针了,这和传值陷阱一样,仔细想想即可知道。多多注意观察,也可以发现这个问题,例如在 ffmpeg 中,初始化指针一定是传指针的指针

    const

    const用在函数中有三个位置:

    • 形参,表示形参不可修改
    • 返回值,表示返回一个不可修改的对象
    • 函数后,表示调用此函数的对象不会被此函数修改

    一般来说,如果不会修改对象,那么可以使用const,尤其是形参,因为函数的形参类型可能不会一定完全匹配,把形参设定为const,编译器可以为形参设置自动转型,自动匹配

    相关文章

      网友评论

          本文标题:c++类设计

          本文链接:https://www.haomeiwen.com/subject/xemtqktx.html