0%

【SE】C++常见问题

一些C++面试常见问题

原理类

C++虚函数的原理(讲一下C++里面的虚函数)

参考:虚函数 2 之虚函数的定义_aaqian1的博客

定义

  • 虚函数:在基类中被关键字 virtual 说明,并在派生类重新定义的成员函数。

  • 作用:允许在派生类 中 重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

    此虚函数就可 在一个或多个派生类中被重新定义

    虚函数声明只能出现在类定义中的函数原型声明中,不能在成员函数实现的时候。

    在派生类中重新定义时,其函数原型,包括函数类型、函数名、参数个数、参数类型的顺序,都必与基类中的原型完全相同。

🔴 使用 对象名和点运算符 的方式 调用虚函数是在编译时进行的,是静态联编,没有利用虚函数的特性。

🔴 只有通过 基类指针访问虚函数 时 才能获得 运行时的多态性

  • 运行期多态发生的三个条件:继承关系、虚函数覆盖、父类指针或引用指向子类对象

✅ 虚函数 与 函数重载 的关系

  • 在一个派生类中重新定义基类的虚函数是函数重载的另一种形式,但它不同于一般的函数重载。
  • 当普通的函数重载时,其函数的 参数 或 参数类型 有所不同,函数的 返回类型 也可以不同。
  • 但是,当重载一个虚函数时,即在派生类中重新定义虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同。①如果仅仅返回类型不同,其余均相同,系统会给出错误信息;②若仅仅函数名相同,而参数的个数,类型或顺序不同,系统将它作为普通的函数重载,虚函数的特性将丢失

作用

虚函数是运行时多态的一种实现方式,基类中的成员函数被定义为虚函数,派生类对相应的虚函数进行了重写,使用的时候,可以使用基类指针指向派生类对象,通过这个基类指针能够访问到派生类中重写的函数,这样,通过基类的指针就可以使属于不同派生类的不同对象产生不同的行为,从而实现运行时的多态。程序能够完成同样的消息被不同的类型的对象接收时导致不同的行为,能够用更加一般化的操作方式操作部分具体的对象。

虚函数的实现原理

重点理解!!!!!!

参考:

C/C++杂记:虚函数的实现的基本原理 - malecrab - 博客园 (cnblogs.com)

C++虚函数表剖析_haozlee的博客-CSDN博客

c语言编译的过程中,const是怎么保证静态的?

参考:

C语言const是如何保证变量不被修改的?_Xilaii的博客-CSDN博客

C++的重载需要注意什么问题?

C++是怎么实现多态和继承?

指针是构造类型还是基础类型,为什么?

介绍类

从高级语言到机器语言要经历那几个过程

c++内存泄漏,for循环如何加快

什么是STL,有哪些部分组成及主要应用?

STL:全称Standard Template Library,是C++的标准模板库。

组成部分:容器、迭代器和算法。

  • 容器:顺序容器,关联容器;
  • 迭代器:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器;
  • 算法:不可变序列算法、可变序列算法、排序和搜索算法、数值算法。

核心作用

使开发者能够有效地利用已有的成果,将经典的、优秀的算法标准化、模块化,从而提高软件的生产效率。

举例说明const和static

C语言的局部变量(栈)和全局变量存储位置(堆)

内存的分配方式有几种?

  • 静态存储区域分配。内存在程序编译时就已分配好,这块内存在程序的整个运行期间都存在。如全局变量。
  • 上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

C++的类中的成员变量 如何初始化(顺序)的?

① 基类的 静态或全局变量

②派生类的 静态或全局变量

③基类的 成员变量

④派生类的 成员变量

为什么说”继承是C++面向对象的一个主要特征之一”?

继承是软件重用的一种形式,继承机制允许程序员在保持原有类特征的基础上,进行更具体、更详细的类的定义,以原有的类为基础产生新的类,提高了代码的重用性和可扩充性。通过继承可以充分利用别人做过一些类似的研究,和已有的一些分析、解决方案。进一步提高程序的抽象程度,同时虚函数的多态性也是在继承功能的基础之上展开的。

什么是友元,有什么利弊?

:友元可以是一个函数也可以是一个类,提供了不同类的成员函数之间、类的成员函数与普通函数之间的数据共享,通过友元关系,普通函数或者另一个类中的成员函数可以访问当前类中的私有成员和保护成员,能够减少通过函数接口调用的开支,提高程序运行效率,实现信息共享。

弊:友元关系会破坏类的封装性和隐藏性,使其内部的数据成员暴露出来,加大维护的难度。

1.先回到第一个问题,当我们需要提高效率或者为了方便,我们会使用它。因为普通函数无法访问私有的成员,除非全部声明为共有的。但是有了友元这个机制,我们就可以访问到对象的私有成员和数据了。

2.好处上面已经说了,是为了方便和快速。但是它明显的带来了一定的坏处,那就是破坏了类的封装性。(我校招的时候,某金融企业面试官问我继承的缺点,实际上我想,继承本身是为了构建父子关系,但是它从一定程度上讲,确实是破坏了子类的封装性,因为一个类被封装了之后就表面其内容对其他类是不可见的,但是继承却可以通过不同的方式看到父类的一些信息。当然,这只是我个人体会的)。

面向对象的三大特征?实现方式?

封装:将对象中不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。

继承:当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法了,只需要通过extend语句继承父类即可,这样,子类就会自动拥有父类定义的属性和方法,达到代码复用的效果。Java 是单继承,一个类只能继承一个父类。

多态:指同一个行为具有多种不同的表现形式,有三种方式来实现多态:子类继承父类,子类重写父类的方法,父类型的引用指向子类型的对象。主要的应用有:方法的重载和重写。好处是使代码具有可替换性、扩展性、降低耦合、接口性、灵活性、简化性。在调用的时候只需将子类对象传递给基类对象,编译器将会根据具体类的对象调用相应对象的方法,从而简便了编程

多态:指同样的消息被不同类型的对象接收时导致不同的行为。从实现的角度可以分为编译时多态和运行时多态。前者是在编译过程中确定同名操作的具体操作对象,主要是通过所声明的类型匹配相应操作,例如函数的重载,后者则是在程序运行过程中才动态地确定操作所针对的具体对象,例如虚函数,其主要是通过虚表实现。

⭕什么是ADT,比较“数据抽象”和“信息隐藏”?

ADT:Abstract Data Type,抽象数据类型,是具有类似行为的 特定类别的数据结构的数据模型,或者具有类似语义的一种或多种 程序设计语言的数据类型它通常是对数据的某种抽象,定义了数据的取值范围及其结构形式,以及对数据操作的集合

数据抽象:指定义数据成员和函数成员的能力,从实际问题中抽象出所关心的共同特征,忽略非本质的细节,把这些特性用各种概念精确地加以描述组成某种模型。以数据为中心,把数据及在数据上的操作作为一个整体来进行描述。

信息隐藏:通过封装实现,将类外部的接口放在类的公有部分,数据放在类的私有部分,防止用户在类外直接访问数据,并且屏蔽了类的具体实现细节。

哪些运算符必须重载,哪些不能重载?

当运算符的操作数中有自定义类型的时候,需要将相关运算符重载。

不能重载:类属关系运算符“.”、成员指针运算符“.*”、作用域分辨符“::”和三目运算符“?:”

必须重载为成员函数的运算符:赋值运算符“=”、取下标运算符“[]”、成员访问运算符“->”,函数运算符“()”。

运算符重载是什么,它如何增强C++的扩展性?

定义

运算符重载就是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。运算符重载使C++中的同样的运算符能够支持对多种类型的操作,甚至是自定义类型的的操作,扩展了C++运算符的含义。同时,运算符重载是在编译过程中完成,属于编译时多态。

重载规则

C++标准规定,不能重载的运算符有:类属关系运算符“.”、成员指针运算符“.*”、作用域分辨符“::”和三目运算符“?:”。必须重载为成员函数的运算符有:赋值运算符“=”、取下标运算符“[]”、成员访问运算符“->”和函数运算符“()”。

面向对象的程序”接口与实现方法分离”的优点?

接口定义并标准化了客户使用该类功能的使用方法,而这些功能的具体实现对于客户而言是不可见的,达到了信息隐藏,使得客户不会写出依赖于类的实现细节的客户代码。同时,程序维护更加方便,只要类的接口不变,类的实现的改变不会影响客户。

对比类

Java 🆚 C++

  • Java在编译时会先转换为字节码文件,然后经解释器解释得到机器码;C++在编译的过程中链接得到机器码;

    java是解释性语言,java程序在运行时类加载器从类路经中加载相关的类,然后java虚拟机读取该类文件的字节,执行相应操作.而C++编译的 时候将程序编译成本地机器码.一般来说java程序执行速度要比C++慢10-30倍.即使采用just-in-time compiling (读取类文件字节后,编译成本地机器码)技术,速度也要比C++慢好多.

  • Java是纯面向对象的语言,C++既有面向对象又有面向过程的部分;

  • Java的类不支持多继承,C++支持;

  • C++运行速度比Java快,Java具有比C++更好的跨平台性

  • Java没有指针,C++有。

  • Java 支持自动垃圾回收;C++ 需要手动回收

面向对象 🆚 面向过程

  • 面向对象:将客观事物看作具有属性和行为的对象,通过抽象找出同一类对象的共同属性和行为,封装成类。通过继承与多态可以很方便地实现代码重用,容易维护、扩展,缩短软件开发周期,并使软件风格统一。但消耗资源,性能低。

    数据和方法 组织为一个整体,更能够贴近事物的自然运行模式。

  • 面向过程:分析出实现需求所需的步骤,通过函数一步一步实现这些步骤,接着依次调用。易性能好,但不易维护、复用和扩展。

    将解决问题的重点放在如何实现过程的细节方面,把数据和对数据进行操作的函数分离,以数据结构为核心,围绕着功能的实现或操作流程来设计程序,安全性较低、扩展升级麻烦,当问题的规模变大时,编程将很复杂。

指针 🆚 引用

(1)指针是实体,引用是别名,没有空间。本质上的区别就是指针是一个新的变量,只是这个变量存放着另一个变量的地址,而引用是变量本身

(2)引用定义时必须初始化,指针不用。

(3)指针可以改变它指向的对象,而引用不可以。

(4)引用不能为空,不能有NULL引用,引用必须与一块合法的存储单元关联。指针可以。

(5)Sizeof(引用)计算的是它引用的对象的大小,而sizeof(指针)计算的是指针本身的大小。

(6)给引用赋值修改的是该引用与对象所关联的值,而不是与引用关联的对象。

(7)如果返回的是动态分配的内存或对象,必须使用指针,使用引用会产生内存泄漏。

(8)对引用的操作即是对变量本身的操作。

(9)指针和引用的自增(++)运算意义不一样。

(10)指针可以有多级,但是引用只有一级(int**p是合法的,int&&q是不合法的

版权声明:本文为CSDN博主「victimsss」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42094283/article/details/108035873

malloc 🆚 new

参考:

细说new与malloc的10点区别 - melonstreet - 博客园 (cnblogs.com)

malloc在哪分配内存,以及怎么释放内存,malloc具体怎么分配的

最大的区别:new在申请空间的时候会调用构造函数,malloc不会调用
申请失败返回:new在申请空间失败后返回的是错误码bad_alloc,malloc在申请空间失败后会返回NULL
属性上:new/delete是C++关键字需要编译器支持,maollc是库函数,需要添加头文件
参数:new在申请内存分配时不需要指定内存块大小,编译器会更具类型计算出大小,malloc需要显示的指定所需内存的大小
成功返回类型:new操作符申请内存成功时,返回的是对象类型的指针,类型严格与对象匹配,无需进行类型转换,因此new是类型安全性操作符。malloc申请内存成功则返回void,需要强制类型转换为我们所需的类型
自定义类型:new会先调operator new函数,申请足够的内存(底层也是malloc实现),然后调用类的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数来释放内存(底层是通过free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构函数
*重载
:C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回地址。malloc不允许重载。

C++ 和 java 的面向对象多态性的区别

C++Release版本🆚 Debug版本的底层代码区别

全局变量 🆚 局部变量

  • 生命周期不同:

    • 全局变量 随主程序创建和创建,随主程序销毁而销毁
    • 局部变量 在局部函数内部,甚至局部循环体等内部存在,退出就不存在。
  • 使用方式不同:通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用;分配在栈区。

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

const 🆚 #define

  • const 常量有数据类型,而宏常量没有数据类型。
  • 编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应) 。

  • 有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试

重载 🆚 重写

  • 重载:是一个类中多态性的一种表现。同一个类中的方法,方法名相同,参数列表不同。
  • 重写:是父类与子类之间多态性的一种表现。子类继承父类,重写父类的方法,要求方法名、参数列表相同、返回类型都相同。子类的 访问权限 >= 父类,抛出异常 <= 父类。

函数模板🆚函数重载

区别

  1. 函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码;函数重载会生成目标代码;
  2. 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,不能像普通函数那样只将声明放在头文件中;函数重载的时候可以将声明和定义分别放在头文件和源文件中;
  3. 函数指针只能指向该模板的实例,不能指向模板本身;函数指针可以指向重载的函数;

联系

从函数模板产生的相关函数都是同名的,编译器用重载的方法调用相应的函数,函数模板本身也可以用多种方式重载。

函数模板🆚模板函数

区别

函数模板重点在于模板,其处理的数据类型是参数化的类型,函数模板本身在编译时不会生成任何目标代码。模板函数重点在于函数,编译器以函数模板为样板,生成了一个函数,这个函数就是模板函数,这个实例化的过程会生成目标代码。

联系

函数模板是数据类型参数化的函数定义,是一个函数族,代表了操作算法相同的一类函数,而模板函数则只是这个函数族中的一个具体函数。

虚函数 🆚 纯虚函数

两者在声明的时候形式不同,如下所示:

1
2
virtual void fun();    // 虚函数
virtual void fun() = 0;// 纯虚函数

同时,纯虚函数根本没有函数体,但是基类中仍然允许对虚函数给出实现,而即使给出实现,也必须由派生类覆盖,否则无法实例化;对比之下,虚函数是有函数体的,只是函数体可以为空。

两者的作用有所不同。带有纯虚函数的类是抽象类,其主要作用是通过它为一个类族建立一个公共的接口,使他们能够更有效地发挥多态特性,抽象类声明了一个类族派生类的共同接口,而接口的完整实现,即纯虚函数的函数体,需要派生类自己定义;虚函数的主要作用是实现运行时多态,通过基类指针或引用与派生类对象建立联系,就可以使同样的消息对于不同派生类的不同对象产生不同的行为。

数据类型 🆚 抽象数据类型

数据类型

数据类型是一组性质相同的具有一定范围的值集合以及定义在这个值集合上的一组操作。数据类型既有内部数据类型,如int、char、float、bool等,也有自定义外部数据类型,如枚举类型、结构类型、联合类型、类类型等。

ADT

ADT即Abstract Data Type(抽象数据类型),是基于已有类型组合而组成的复合数据类型,类就是抽象数据类型的一种描述形式。

类作用域🆚文件作用域

类作用域

类作用域定义在类内,在类作用域之内,如果没有声明同名的标识符,可以直接访问;在类作用域之外,相应的数据成员和函数成员就被隐藏起来了,普通的数据和函数成员只能由ptr->m或x.m这样的表达式访问,类的静态成员可以使用X::m的方式访问。(其中X为类名,x为类X的对象,m为类X的数据成员)

文件作用域

在任何函数外声明的标识符的作用域为文件作用域,可以从声明标识符的位置开始,一直到文件末尾处的任何函数中访问。

函数原型作用域

在函数原型声明时形式参数的作用范围就是函数原型作用域。

局部作用域

  1. 函数形参列表中的形参的作用域,从形参列表中的声明处开始,到整个函数体结束之处为止;
  2. 函数体内声明的变量,其作用域从声明处开始,一直到声明所在块结束的大括号为止。

指针和数组的关系和区别;浅拷贝和深拷贝等