美文网首页
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++ — 类 & 对象超详解

    C++ 类 & 对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ ...

  • C++零基础教程之类和对象初识

    C++ 类和对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核...

  • C++面向对象

    C++类和对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心...

  • C++ 类 & 对象

    原文地址:C++ 类 & 对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是...

  • 读《C++沉思录》有感

    关于类的设计:代理类 《C++沉思录》的原话是这样的 我们怎样才能设计一个C++容器,使它有能力包含类型不同而彼此...

  • NDK开发—C++面向对象编程(四)

    类 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,用户定...

  • CPP基础:面向对象编程

    面向对象编程 类 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核...

  • 2022-08-01# 面向对象编程

    类 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,用户定...

  • c++类设计

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

  • cpp面向对象

    面向对象编程 [TOC] 类 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 ...

网友评论

      本文标题:c++类设计

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