Android C++系列:C++最佳实践1虚函数

简介: C++多态的核心技术基础就是虚函数,虚函数允许我们使用同样的基类指针调用同一个方法的不同实现版本。我们Android使用Java开发过程中,方法重写技术自动实现了多态,C++角度可能更繁琐一些,本文从Java程序员思维角度来阐述C++虚函数及开发过程一些准则。

image.png


1. 背景


C++多态的核心技术基础就是虚函数,虚函数允许我们使用同样的基类指针调用同一个方法的不同实现版本。我们Android使用Java开发过程中,方法重写技术自动实现了多态,C++角度可能更繁琐一些,本文从Java程序员思维角度来阐述C++虚函数及开发过程一些准则。


2. 什么是虚函数


在Java中我们实现继承结构的两个类:


class Base{
  public void action(){
    System.out.pritln("in Base");
  }
}
class Sub extend Base{
  public void action(){
    System.out.println("in Sub");
  }
}
public static void main(String[] args){
  Base b = new Base();
  b.action();
  b = new Sub();
  b.action();
}


Java直接覆盖后就会自动的调用到实际类的对应方法,但是C++中不行。


class Base{
public:
  void action();
}
void Base::action(){
    std::cout>>("in Base");
}
class Sub:public Base{
public:
  void action();
}
void Sub::action(){
    std::cout>>("in Sub");
  }
static void main(){
  Base *b = new Base();
  b->action();
  b = new Sub();
  b->action();
}


打印的结果是:


in Base
in Base


就算是b指向了Sub对象,但是打印的还是Base的方法,没有自动绑定,写惯了Java后马上慌了,怎么破。这里就需要用到我们今天的主人公”虚函数“了,在方法声明之前加上virtual这个函数就变成虚函数。我们在Base的action方法声明前加上virtual打印结果就变成了:


in Base
in Sub


当前仅当对通过指针或引用掉用虚函数时,才会在运行时解析该调用,也只有这种情况下对象的动态类型才有可能与静态类型不同。


3. 虚函数注意点


  1. 基类通过在其成员函数的声明语句之前加上关键字virtual使得该函数执行动态绑定。如果基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数。派生类可以在它覆盖的函数前使用virtual关键字,但是不是强制的。
  2. 如果我们想将某个类作基类,则该类必须已经定义而非仅仅声明。因为派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类当然要知道它们是什么。一个类不能派生它本身。
  3. 如果在基类中含有一个或多个虚函数,我们可以使用dynamic_cast请求一个类型装换,这个转换的安全检查在运行时执行。同样,如果我们已知某个基类向派生类的转换是安全的,我们可以使用static_cast来强制覆盖掉编译器的检查工作。
  4. 如果我们不使用某个函数,则无须为该函数提供定义,但是我们必须为每一个虚函数都提供定义,而不管它是否被用到,因为连编译器也无法确定到底会使用哪个虚函数。
  5. 派生类中虚函数的返回类型必须与基类函数匹配。例外的情况是,当类的虚函数返回类型是类本身的指针或引用时,不需要遵循这个规则。如果Impl由Base派生,则基类的虚函数可以返回Base*,而派生类的对应函数可以返回Impl*,这样的返回类型要求从Impl到Base的转换是可访问的。
  6. 我们可以把某个函数指定为final,如果已经把函数指定为final,则之后任何尝试覆盖该函数的操作都将引发错误。
  7. 如果虚函数使用默认实参,则积累和派生类中定义的默认实参最好一致。
  8. 如果我们不想对虚函数的调用执行动态绑定,可以使用作用域运算法强迫执行虚函数的某个特定版本。通常情况,只有成员函数(或友元)中的代码才需要使用作用域运算法来回避虚函数的机制。在Java中子类调用父类被重写的方法直接super.xxx()即可,在C++中要强制使用某个版本的虚函数,必须使用该版本对应的类加::作用域,如Base::action()。我们Java中如果想调用父类的父类的被覆盖的方法就有点困难了,这一点C++反而更有优势了。
  9. 如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。


4. 总结


作为Android方向C++系列文章,不仅介绍C++相关的知识,Android主要开发语言是Java,文章中尽量会对比Java和C++实现的一些不同,以及一些优劣对比。本文介绍了C++多态的基础虚函数,对比了和Java版本实现多态的差异。

目录
相关文章
|
1月前
|
移动开发 监控 Android开发
Android & iOS 使用 ARMS 用户体验监控(RUM)的最佳实践
本文主要介绍了 ARMS 用户体验监控的基本功能特性,并介绍了在几种常见场景下的最佳实践。
|
27天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6
|
1月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
23 0
C++ 多线程之线程管理函数
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
154 1
|
1月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
30 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
编译器 C++
【C++核心】函数的应用和提高详解
这篇文章详细讲解了C++函数的定义、调用、值传递、常见样式、声明、分文件编写以及函数提高的内容,包括函数默认参数、占位参数、重载等高级用法。
22 3
|
2月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
129 1