【图解八股-C/C++②-C++基础】

作者简介和专栏内容见专栏介绍:https://www.nowcoder.com/creation/manager/columnDetail/0eL5bM

麻烦看到贴子的伙伴点点赞大家点赞订阅支持下,提前祝各位offer多多,有问题评论区见~~

图解版

C++基础

指针和引用的区别

先讲为什么,再讲底层区别,讲具体应用场景(指针传递,引用传递)

为什么要有指针和引用?

指针和引用的存在是为了方便程序员对内存的操作。指针可以用来访问数组、结构体等复杂数据类型,引用可以用来作为函数参数,避免了复制大量的数据。

底层区别

引用传递不需要创建临时变量,开销要更小

表象区别

  • 从定义上看:指针是个变量,存储指向的内存地址;引用相当于原变量的别名。也因此,指针是可以有多级的,也可以为空,初始化后也可以改变指向;而引用只能有一级,在定义的时候必须初始化,初始化后就不能变了。sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小

应用区别

从使用上来讲:

引用

  • 一般在传递函数参数的时候我们首先选择引用传递。引用传递不需要创建临时变量,开销小。
  • 对栈空间大小比较敏感(比如递归)的时候使用引用。
  • 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式

指针

  • 如果需要返回函数内局部变量的内存,需要用指针。
  • 如果数据是内置数据类型,则使用指针
  • 如果数据对象是数组,则只能使用指针

值传递、指针传递、引用传递的区别和效率

1) 值传递:值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本(替身)。

值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。如果值传递的对象是类对象或是大的结构体对象,将耗费一定的时间和空间。(传值)

2) 指针传递:指针参数传递本质上是值传递,它所传递的是一个地址值。同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。

3) 引用传递:引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。

被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。

因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。

4) 引用传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。

而对于指针传递的参数,如果改变被调函数中的指针地址,它将应用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使用指向指针的指针或者指针引用。

5) 从编译的角度来讲,程序在编译时分别将指针和引用添加到符号表上,符号表中记录的是变量名及变量所对应地址。

指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值(与实参名字不同,地址相同)。符号表生成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

6) 效率上讲,指针传递和引用传递比值传递效率高。使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作,开销小,一般主张使用引用传递,代码逻辑上更加紧凑、清晰。

程序必须从main函数开始吗?

在C++语言中,程序并不一定要从main函数开始执行。实际上,在程序运行期间,操作系统会首先加载程序的代码和数据到内存中,并从程序的入口点开始执行。在C++语言中,程序的入口点是main函数。

然而,C++标准并没有规定程序必须从main函数开始。一些特殊的应用场景,例如裸机编程、嵌入式开发等,可能需要在程序启动时执行一些特殊的初始化工作,这时可以使用一些特殊的入口点。例如,在一些嵌入式系统中,程序可以从Reset向量处开始执行,执行一些硬件初始化工作,之后跳转到main函数开始执行应用程序代码。

在使用非标准入口点时,需要注意操作系统的限制和安全性问题。例如,一些操作系统可能会禁止程序从非main函数入口点开始执行,或者会对程序的入口点进行安全检查,以防止恶意程序的执行。

new / delete 与 malloc / free

  • 总体上来讲,前者是在C的基础上对后者的封装,封装之后作为C++运算符,申请释放内存更加安全便捷。安全就比如malloc大小与类型不对应时编译不会报错,而new是类型安全的;new也可以自己计算要分配的空间大小。
  • 底层实现
  • 另外,new 和 delete在C++中是运算符,那两个是标准库函数。malloc返回void*,需要手动计算大小

delete[]如何知道调用多少次

需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

malloc如何分配内存(实现原理)

从操作系统层面上看,malloc是通过两个系统调用来实现的: brk和mmap

brk是将进程数据段(.data)的最高地址指针向高处移动,这一步可以扩大进程在运行时的堆大小

mmap是在进程的虚拟地址空间中寻找一块空闲的虚拟内存,这一步可以获得一块可以操作的堆内存。

通常,分配的内存小于128k时,使用brk调用来获得虚拟内存,大于128k时就使用mmap来获得虚拟内存。

进程先通过这两个系统调用获取或者扩大进程的虚拟内存,获得相应的虚拟地址,在访问这些虚拟地址的时候,通过缺页中断,让内核分配相应的物理内存,这样内存分配才算完成。

malloc申请的存储空间能用delete释放吗?

不能,malloc /free主要为了兼容C,new和delete 完全可以取代malloc /free的。

malloc /free的操作对象都是必须明确大小的,而且不能用在动态类上。

new 和delete会自动进行类型检查和大小,malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的。

当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。

C++中有几种类型的new

在C++中,new有三种典型的使用方法:plain new,nothrow new和placement new

(1)plain new

言下之意就是普通的new,就是我们常用的new。plain new在空间分配失败的情况下,抛出异常std::bad_alloc而不是返回NULL

(2)nothrow new

nothrow new在空间分配失败的情况下是不抛出异常,而是返回NULL

(3)placement new

这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。

使用placement new需要注意两点:

palcement new的主要用途就是反复使用一块较大的动态分配的内存来构造不同类型的对象或者他们的

数组placement new构造起来的对象数组,要显式的调用他们的析构函数来销毁(析构函数并不释放对象的内存),千万不要使用delete,这是因为placement new构造起来的对象或数组大小并不一定等于原来分配的内存大小,使用delete会造成内存泄漏或者之后释放内存时出现运行时错误

类如何实现只能静态分配和只能动态分配

1) 前者是把new、delete运算符重载为private属性。后者是把构造、析构函数设为protected属性,再用子类来动态创建

2) 建立类的对象有两种方式:

①静态建立,静态建立一个类对象,就是由编译器为对象在栈空间中分配内存;

②动态建立,A *p = new A();动态建立一个类对象,就是使用new运算符为对象在堆空间中分配内存。这个过程分为两步,第一步执行operator new()函数,在堆中搜索一块内存并进行分配;第二步调用类构造函数构造对象;

3) 只有使用new运算符,对象才会被建立在堆上,因此只要限制new运算符就可以实现类对象只能建立在栈上,可以将new运算符设为私有

如何阻止一个类被实例化?有哪些方法?

1) 将类定义为抽象基类或者将构造函数声明为private;

2) 不允许类外部创建类对象,只能在类内部创建对象

宏定义与typedef const inline

  • 宏替换发生在预编译阶段(typedef是编译的一部分,而const在编译),之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用。(函数)
  • 宏定义属于在结构中插入代码,没有返回值。(函数)
  • 宏定义参数没有类型,不进行类型检查。(函数,typedef,const)
  • 宏定义不是语句,不在最后加分号。(函数,typedef)
  • 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
  • 宏定义的数据没有分配内存空间,只是插入替换掉(const)
  • 内联函数在编译时直接将函数代码嵌入到目标代码中,省去函数调用的开销来提高执行效率,并且进行参数类型检查,具有返回值,可以实现重载。

为什么不能把所有的函数写成内联函数?

内联函数以代码复杂为代价,它以省去函数调用的开销来提高执行效率。所以一方面如果内联函数体内代码执行时间相比函数调用开销较大,则没有太大的意义;另一方面每一处内联函数的调用都要复制代码,消耗更多的内存空间,因此以下情况不宜使用内联函数:

函数体内的代码比较长,将导致内存消耗代价函数体内有循环,函数执行时间要比函数调用开销大

const和static

static

1.先来介绍它的第一条也是最重要的一条:隐藏。(static函数,static变量均可)

当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。

2.static的第二个作用是保持变量内容的持久

(static变量中的记忆功能和全局生存期)存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。

3.static的第三个作用是默认初始化为0(static变量)

其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。

4.static的第四个作用:C++中的类成员声明static

1) 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

2) 在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

3) 在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

4) 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

5) 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

类内:

6) static类对象必须要在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化;

7) 由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的,this指针是指向本对象的指针。正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问 static修饰的类成员;

8) static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this指针调用的,所以不能为virtual;虚函数的调用关系,this->vptr->ctable->virtual function

const

1) 阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

2) 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;

3) 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

4) 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量,类的常对象只能访问类的常成员函数;

5) 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。

6) const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;

7) 非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;

8) 一个没有明确声明为const的成员函数被看作是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。因此const对象只能调用const成员函数。

9) const类型变量可以通过类型转换符const_cast将const类型转换为非const类型;

10) const类型变量必须定义的时候进行初始化,因此也导致如果类的成员变量有const类型的变量,那么该变量必须在类的初始化列表中进行初始化;

全局变量和static变量的区别

这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。

static全局变量与普通的全局变量的区别是static全局变量只初始化一次,防止在其他文件单元被引用。

static函数与普通函数有什么区别?

static函数与普通的函数作用域不同,仅在本文件中。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。

对于可在当前源文件以外使用的函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。

static函数与普通函数最主要区别是static函数在内存中只有一份,普通静态函数在每个被调用中维持一份拷贝程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)

初始化时机

全局变量和静态变量在程序运行前就已经被初始化了,具体时间取决于变量定义的位置和类型。

全局变量和静态变量的定义位置分为两种情况:

  1. 定义在函数外部:此时全局变量和静态变量会在程序加载时初始化。如果没有指定初始值,则全局变量和静态变量会被自动初始化为0或NULL。
  2. 定义在函数内部:此时静态变量会在程序运行时第一次进入该函数时初始化,而全局变量在函数内部定义不合法。如果没有指定初始值,则静态变量会被自动初始化为0或NULL。

需要注意的是,全局变量和静态变量的初始化顺序是按照它们在程序中出现的顺序进行的。如果两个变量相互依赖,那么它们的初始化顺序就很重要,否则会出现未定义的行为。

全局变量和局部变量有什么区别?

生命周期不同:全局变量随主程序创建和创建,随主程序销毁而销毁;局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;

使用方式不同:通过声明后全局变量在程序的各个部分都可以用到;局部变量分配在堆栈区,只能在局部使用。

操作系统和编译器通过内存分配的位置可以区分两者,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。

静态成员与普通成员的区别是什么?

1) 生命周期

静态成员变量从类被加载开始到类被卸载,一直存在;

普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;

2) 共享方式

静态成员变量是全类共享;普通成员变量是每个对象单独享用的;

3) 定义位置

普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;

4) 初始化位置

普通成员变量在类中初始化;静态成员变量在类外初始化;

5) 默认实参

可以使用静态成员变量作为默认实参,

静态变量什么时候初始化

1) 初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存。

2) 静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存,但在C和C++中静态局部变量的初始化节点又有点不太一样。在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化,在程序运行结束,变量所处的全局内存会被全部回收。

3) 而在C++中,初始化时在执行相关代码时才会进行初始化,主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次用到时才会进行构造,并通过atexit()来管理。在程序结束,按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的

如何设计一个类计算子类的个数?

1、为类设计一个static静态变量count作为计数器;

2、类定义结束后初始化count;

3、在构造函数中对count进行+1;

4、 设计拷贝构造函数,在进行拷贝构造函数中进行count +1,操作;

5、设计复制构造函数,在进行复制函数中对count+1操作;

6、在析构函数中对count进行-1;

C++的顶层const和底层const

概念区分

顶层const:指的是const修饰的变量本身是一个常量,无法修改,指的是指针,就是 * 号的右边

底层const:指的是const修饰的变量所指向的对象是一个常量,指的是所指变量,就是 * 号的左边

int a = 10;int* const b1 = &a; //顶层const,b1本身是一个常量
const int* b2 = &a; //底层const,b2本身可变,所指的对象是常量
const int b3 = 20; //顶层const,b3是常量不可变
const int* const b4 = &a; //前一个const为底层,后一个为顶层,b4不可变
const int& b5 = a; //用于声明引用变量,都是底层const

区分作用

执行对象拷贝时有限制,常量的底层const不能赋值给非常量的底层const

使用命名的强制类型转换函数const_cast时,只能改变运算对象的底层const

final和override关键字

override

当在父类中使用了虚函数时候,你可能需要在某个子类中对这个虚函数进行重写

如果不使用override,当你手一抖,将foo()写成了f00()会怎么样呢?结果是编译器并不会报错,因为它并不知道你的目的是重写虚函数,而是把它当成了新的函数。如果这个虚函数很重要的话,那就会对整个程序不利。所以,override的作用就出来了,它指定了子类的这个虚函数是重写的父类的,如果你名字不小心打错了的话,编译器是不会编译通过的。

final

当不希望某个类被继承,或不希望某个虚函数被重写,可以在类名和虚函数后添加final关键字,添加 final关键字后被继承或重写,编译器会报错。

volatile、mutable和explicit关键字的用法

(1)volatile

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。 告诉编译器不要去假设(优化)这个变量的值,因为这个变量可能会被意想不到地改变。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。多线程中被几个任务共享的变量需要定义为volatile类型。

volatile 指针

volatile 指针和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念

注意:

  • 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
  • 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
  • C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

volatile用在如下的几个地方:

1) 中断服务程序中修改的供其它程序检测的变量需要加volatile;

2) 多任务环境下各任务间共享的标志应该加volatile;

3) 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

多线程下的volatile

有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。

(2)mutable

mutable的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const函数里面修改一些跟类状态无关的数据成员,那么这个函数就应该被mutable来修饰,并且放在函数后后面关键字位置

(3)explicit (下一个问题)

explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换,注意以下几点:

  • explicit 关键字只能用于类内部的构造函数声明上
  • explicit 关键字作用于单个参数的构造函数
  • 被explicit修饰的构造函数的类,不能发生相应的隐式类型转换

隐式转换,如何消除隐式转换?

1、C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换

2、C++面向对象的多态特性,就是通过父类的类型实现对子类的封装。通过隐式转换,你可以直接将一个子类的对象使用父类的类型进行返回。在比如,数值和布尔类型的转换,整数和浮点数的转换等。某些方面来说,隐式转换给C++程序开发者带来了不小的便捷。C++是一门强类型语言,类型的检查是非常严格的。

3、 基本数据类型 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。自定义对象子类对象可以隐式的转换为父类对象。

4、 C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。

5、如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为explicit加以制止隐式类型转换,关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。

C++ 的 四 种 强 制 转 换 reinterpret_cast/const_cast/static_cast/dynamic_cast

reinterpret_cast

reinterpret_cast<type-id> (expression)

type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以用于类型之间进行强制转换。

const_cast

const_cast<type_id> (expression)

该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。用法如下:

  • 常量指针被转化成非常量的指针,并且仍然指向原来的对象
  • 常量引用被转换成非常量的引用,并且仍然指向原来的对象
  • const_cast一般用于修改底指针。如const char *p形式

static_cast

static_cast < type-id > (expression)

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

  • 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用引用的转换
  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
  • 把空指针转换成目标类型的空指针
  • 把任何类型的表达式转换成void类型

注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

dynamic_cast

有类型检查,基类向派生类转换比较安全,但是派生类向基类转换则不太安全

dynamic_cast (expression)

该运算符把expression转换成type-id类型的对象。type-id 必须是类的指针、类的引用或者void*如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么expression 也必须是一个引用

dynamic_cast运算符可以在执行期决定真正的类型,也就是说expression必须是多态类型。如果下行转换是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果 如果下行转换不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的

在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全

static_cast比C语言中的转换强在哪里?

1) 更加安全;

2) 更直接明显,能够

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

计算机实习秋招全阶段指南 文章被收录于专栏

作者简介:2个月时间逆袭嵌入式开发,拿下理想汽车-ssp、小米汽车-sp、oppo-sp、迈瑞医疗、三星电子等八家制造业大厂offer~ 专栏内容:涵盖算法、八股、项目、简历等前期准备的详细笔记和模板、面试前中后的各种注意事项以及后期谈薪、选offer等技巧。保姆级全阶段教程帮你获得信息差,早日收到理想offer~

全部评论

相关推荐

1 9 评论
分享
牛客网
牛客企业服务