干货福利,第一new、delete、malloc、free之间的关系

干货福利,第一时间发货!

大家好,我叫唐唐。今天给大家分享一些经典的C++面试题。

1.new、delete、malloc、free的关系

malloc 和 free 是 C/C++ 语言的标准库函数,new/delete 是 C++ 运算符。

new 调用构造函数,delete 调用对象的析构函数,free 只是释放内存。

它们都可以用来分配动态内存和空闲内存。但是对于非内部数据类型的对象,仅靠malloc/free是无法满足动态对象的要求的。对象在创建时会自动执行构造函数,而析构函数会在对象死亡之前自动执行。由于 malloc/free 是库函数而不是操作符,它不在编译器的控制范围内,执行构造函数和析构函数的任务不能强加给 malloc/free。因此,C++语言需要一个能完成动态内存分配和初始化工作的操作符new,和一个能完成清理和释放内存工作的操作符delete。注意:new/delete 不是库函数。

2.delete 和 delete[] 的区别

delete 只会调用一次析构函数,而 delete[] 会调用每个成员函数的析构函数。

在更有效的 C++ 中有更详细的解释:“当在数组上使用 delete 运算符时,它会为每个数组元素调用析构函数,然后调用 operator delete 以释放内存。” delete 与 new 配对,delete [] 与 new [] 兼容

在-4-报错。

这意味着:对于内置的简单数据类型,delete 和 delete[] 函数是相同的。对于自定义复杂数据类型,delete 和 delete[] 不能互换使用。delete[] 删除一个数组,delete 删除一个指针。简而言之,用new分配的内存用delete删除;用 new[] 分配的内存用 delete[] 删除。delete[] 将调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你使用不带括号的delete,delete会认为它指的是单个对象,否则,它会认为它指的是一个数组。

3.C++有哪些特性(面向对象的特性)

封装、继承和多态

4.子类析构的时候应该调用父类的析构函数吗?

析构函数调用的顺序是先派生类的析构函数,然后是基类的析构函数,也就是说,当基类的析构函数被调用时,派生类的信息已经被彻底销毁。定义对象时,先调用基类构造函数,再调用派生类构造函数;析构时则相反:先调用派生类的析构函数,再调用基类的析构函数。

5.多态性、虚函数和纯虚函数简介。

多态性:不同的对象在收到相同的消息时会采取不同的动作。C++的多态性体现在两个方面:运行和编译:程序运行时的多态性体现为继承和虚函数;程序在编译时,多态性体现在函数和运算符的重载上;

虚函数:基类中以关键字 virtual 为前缀的成员函数。它提供了一个接口接口。允许在派生类中重新定义基类虚函数。

纯虚函数的作用:在基类中为其派生类保留函数的名称,以便派生类可以根据需要进行定义。作为接口存在的纯虚函数不具备函数的功能,一般不能直接调用。

从基类继承的纯虚函数在派生类中仍然是虚函数。如果一个类中至少有一个纯虚函数,则该类称为抽象类。

抽象类不仅包括纯虚函数,还包括虚函数。抽象类是必须用作可以派生其他类的基类,并且不能用于直接创建对象实例的类。使用指向抽象类的指针仍然支持运行时多态性。

笔记:

将函数定义为虚函数并不意味着该函数没有实现。它被定义为一个虚函数,允许使用基类指针调用子类的函数。将函数定义为纯虚函数意味着该函数没有实现。

6.求以下函数的返回值(微软)

假设 x = 9999。答案:8

思路:将x转换为二进制,看它包含的1个数

7.什么是“参考”?声明和使用“引用”时应注意哪些问题?

引用是目标变量的“别名”,对应用程序的操作与对变量的直接操作完全相同。声明引用时,请记住对其进行初始化。声明引用后,相当于目标变量名有两个名字,即目标的原名和引用名。引用名称不能用作其他变量名称的别名。声明一个引用并不意味着一个变量是新定义的。它仅表示引用名称是目标变量名称的别名。它本身不是数据类型,因为引用本身不占用存储单元,系统也不为引用分配存储单元。. 无法创建对数组的引用。

8.使用“引用”作为函数参数有什么特点?

(1)给函数传递引用的效果和传递指针的效果是一样的。此时调用函数的形参作为原调用中实参变量或对象的别名函数,所以在被调用函数中调用函数中对形参变量的操作就是对对应目标对象(在调用函数中)的操作。

(2)使用引用传递函数参数并不会在内存中生成实参的副本,而是直接对实参进行操作;而使用通用变量传递函数参数,当发生函数调用时,需要给form 参数分配存储单元,形参变量是实参变量的一个拷贝;如果传递了对象,也会调用拷贝构造函数。因此,当参数传递的数据较大时,效率并且占用空间好。

(3)虽然使用指针作为函数参数也可以达到和使用引用一样的效果,但是在被调用的函数中,还必须为形参分配一个存储单元,并且”*pointer”的形式变量名”需要重复使用另一方面,在调用函数的调用处,必须将变量的地址作为参数。引用更容易使用,更清晰。

9.什么时候需要使用“常量引用”?

如果要使用引用来提高程序的效率,同时又要保护传递给函数的数据在函数中不被更改,则应该使用常量引用。常量引用声明方式:const类型标识符&引用名=目标变量名;

示例 1

示例 2

那么下面的表达式将是非法的:

原因是 foo() 和“hello world”字符串都产生了一个临时对象,而在 C++ 中,这些临时对象的类型是 const。所以上面的表达式是试图将 const 类型的对象转换为非 const 类型,这是非法的。引用参数在可以定义为 const 的情况下尽量定义为 const。

10.使用“引用”作为函数返回类型有哪些格式、好处和规则?

格式:类型标识符&函数名(参数列表和类型描述){ //函数体}

好处:不会在内存中制作返回值的副本;(注意:正因为如此,不建议返回对局部变量的引用。因为随着局部变量生命周期的结束,相应的引用也会失败,导致运行时错误!

防范措施:

(1)不能返回对局部变量的引用。这个可以参考Effective C++[1]第31条。主要原因是函数返回后局部变量会被销毁,所以返回的引用变成” nothing”指的是对“的引用,程序将进入未知状态。

(2) 不能在函数内部返回对new分配的内存的引用。这可以参考Effective C++[1]的Item 31。虽然没有被动销毁局部变量,但是对于这种情况(返回new inside函数)的引用分配内存),并面临其他尴尬的情况。例如,函数返回的引用只是作为临时变量出现,没有被赋值为实际变量,那么引用指向的空间(由new分配)就无法释放。,导致内存泄漏。

(3)可以返回一个类成员的引用,但最好是const。这个原理可以参考Effective C++[1]的第30条。主要原因是当对象的属性与业务规则相关时(业务规则) 关联时,其赋值往往与其他一些属性或对象的状态有关,所以需要将赋值操作封装在业务规则中,如果其他对象可以获取非const引用(或指针) ) 到属性,那么简单地分配这个属性将违反业务规则的完整性。

(4) 流运算符重载声明为“引用”的返回值:

流操作符,这两个操作符通常期望连续使用,例如:cout

赋值运算符 =。这个算子和流算子一样,可以连续使用,例如:x = j = 10;or (x=10)=100; 赋值运算符的返回值必须是左值,这样才能通过延续赋值。所以引用成为该运算符唯一的返回值选择。

(5)在其他运算符中,绝对不能返回引用:+-*/四个运算符。它们不能返回引用,Effective C++[1]的Item23详细讨论了这个问题。主要原因是这些四个运算符没有副作用,所以必须构造一个对象作为返回值,可选的解决方案包括:返回一个对象、返回一个局部变量的引用、返回一个新分配的对象的引用、返回一个静态的Object引用. 根据上面提到的三个引用作为返回值的规则,2、3的两个解都被拒绝了。静态对象的引用是因为((a+b) == (c+d))会总是为真并导致错误。所以剩下的唯一选择就是返回一个对象。

11.结构体和联合体有什么区别?

(1)。结构体和联合体都是由多个不同数据类型的成员组成的,但是在任何时候,联合体中只存储一个被选中的成员(所有成员共享一个地址空间),而结构体的所有成员存在(不同的成员有不同的存储地址)。

(2)。对于联合体不同成员的赋值,会重写其他成员,原成员的值不存在,对结构不同成员的赋值互不影响。

12.尝试写程序结果:

13.重载和覆盖有什么区别?

根据定义:

重载:表示允许多个同名函数,并且这些函数的参数表不同(可能参数个数不同,参数类型可能不同,或两者兼而有之)。

Overriding:指子类重新定义超类的虚函数的方法。

在实施原则上:

重载:编译器根据函数的不同参数列表修改同名函数的名称,然后这些同名函数变成不同的函数(至少对于编译器而言)。例如,有两个同名函数:function func(p:integer):integer; 和函数 func(p:string):integer;。那么编译器修改的函数名可能是这样的:int_func,str_func。对这两个函数的调用已经在编译器之间确定并且是静态的。也就是说,它们的地址是在编译时绑定的(早期绑定)。

重写:当子类重新定义父类的虚函数时,父类指针根据分配给它的不同子类指针动态调用属于子类的函数。在编译期间无法确定这样的函数调用。(不能给出被调用子类的虚函数地址)。因此,此类函数地址是在运行时绑定的(后期绑定)。

14.哪些情况可以只使用初始化列表而不能赋值?

当类包含 const 和引用成员变量时;基类的构造函数需要初始化表。

15. C++ 类型安全吗?

不。可以在两个不同类型的指针之间进行转换(使用重新解释转换)。C# 是类型安全的。

16.main函数执行前会执行什么代码?

全局对象的构造函数在主函数之前执行。

17.描述内存是如何分配的以及它们有何不同?

图片[1]-干货福利,第一new、delete、malloc、free之间的关系-老王博客

1)从静态存储区分配。内存是在程序编译时分配的,这个内存存在于程序的整个运行时。比如全局变量、静态变量。

2)在堆栈上创建。函数执行时,可以在栈上创建函数中局部变量的存储单元,这些存储单元会在函数执行结束时自动释放。堆栈内存分配操作内置于处理器的指令集中。

3)从堆中分配,也称为动态内存分配。程序运行时,使用malloc或new申请任意数量的内存,由程序员负责何时使用free或delete释放内存。动态内存的寿命是由程序员决定的,使用起来非常灵活,但也存在最多的问题。

18.分别写出bool、int、float、指针类型变量a和“零”的比较语句。

1 bool : if(!a) 或 if(a) 2 int : if(a == 0) 3 float : const EXPRESSION EXP = 0.000001 4 if (a < EXP && a >-EXP ) 5 指针:if(a != NULL) 或 if(a == NULL)

19.请告诉我 const 与#define 相比有什么优势?

const的作用:定义常量、修改函数参数、修改函数返回值。const 修改的一切都受到强制保护,可以防止意外的变化,提高程序的健壮性。

1)const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者执行类型安全检查。后者只进行字符替换,没有类型安全检查,字符替换时可能出现意外错误。

2)一些集成调试工具可以调试const常量,但不能调试宏常量。

20.简述数组和指针的区别?

数组可以在静态存储(如全局数组)中创建,也可以在堆栈上创建。指针可以随时指向任何类型的内存块。

(1)修改内容差异

(2) 可以使用运算符sizeof来计算数组的容量(字节数)。sizeof(p),p是指针,获取一个指针变量的字节数,不是指向的内存容量to by p. C++/C语言无法知道指针所指向的内存量,除非在分配内存的时候被记住。注意当数组作为函数参数传递时,数组会自动退化为指针同类型。

21.引用和指针有什么区别?

引用必须初始化,指针不需要。

引用初始化后不能更改,指针可以更改它指向的对象。

没有对空值的引用,但有指向空值的指针。

22.基类的析构函数不是虚函数,会带来什么问题?

不使用派生类的析构函数,会造成资源泄漏。

23.全局变量和局部变量有什么区别?它是如何实现的?操作系统和编译器如何知道它?

生命周期不同:

全局变量随主程序创建和创建,主程序销毁时销毁;局部变量存在于局部函数内部,甚至局部循环体等如果没有覆盖虚函数,不存在exit;

使用不同:

声明全局变量后,程序的所有部分都可以使用;局部变量只能在本地使用;它们被分配在堆栈区域中。

内存分配位置不同:

全局变量分配在全局数据部分,并在程序开始运行时加载。局部变量在堆栈上分配。

24.编写完整版的strcpy函数:

如果你写一个标准的strcpy函数,总分10分,这里有几个不同分数的答案:

2分钟

4分

7分

10分

25.为什么标准头文件有如下结构?

编译头文件中的宏

作用是防止重复引用。

作为面向对象的语言,C++ 支持函数重载,而过程语言 C 不支持。C++编译后符号库中的函数名称与C语言不同。例如,假设一个函数的原型是:

该函数由 C 编译器编译,符号库中的名称为 _foo,而 C++ 编译器将生成类似 _foo_int_int 的名称。像_foo_int_int这样的名字包含了函数名、函数参数的个数和类型,C++就是利用这种机制来实现函数重载的。

为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern“C”来解决名称匹配问题。在函数声明前加上 extern “C” 后,编译器将遵循 C 语言的方法。该函数被编译为_foo,以便可以从C语言调用C++函数。

26、每种情况下班级的人数是多少?

空类的大小为 1。在 C++ 中,空类占用一个字节,以便可以区分对象的实例。具体来说,空类也可以实例化,每个实例在内存中都有唯一的地址。因此,编译器会隐式地给空类添加一个字节,这样空类实例化后,就会有唯一的内存地址。当空白类作为基类时,类的大小优化为0,子类的大小就是子类本身的大小。这称为空白基类优化。

空类的实例大小就是类的大小,所以 sizeof(a)=1 字节。如果 a 是指针,sizeof(a) 就是指针的大小,即 4 个字节。

因为有虚函数的类对象中有一个虚函数表指针__vptr,所以它的大小是4字节

静态成员存储在静态存储区,不占用类的大小,普通函数也不占用类的大小

静态成员a不占用类的大小,所以类的大小就是b变量的大小,也就是4个字节。

27、影响类对象大小的因素有哪些?

类的非静态成员变量的大小,静态成员不占用类空间,成员函数不占用类空间;

内存对齐除了分​​配的空间大小,类中的数据也需要进行内存对齐;

如果使用虚函数,则将vptr指针插入到类对象中,加上指针的大小;

当类是某个类的派生类时,派生类继承的基类部分的数据成员也会存在派生类的空间中,派生类也会被扩展。

15、当这个指针调用成员变量时,堆栈会发生什么?

在类的非静态成员函数中访问类的非静态成员时,编译器会自动将对象的地址作为隐式参数传递给函数,也就是this指针。

即使不写this指针,编译器在链接的时候也会加上this,每个成员的访问都是通过this。

例如,当你创建一个类的多个对象时,当你调用该类的一个成员函数时,你并不知道调用的是哪个对象。这时候可以通过查看this指针来查看调用的是哪个对象。这个指针首先被压入堆栈,然后成员函数的参数从右到左被压入堆栈,最后函数返回地址被压入堆栈。

28. 编写String类的构造函数、析构函数和赋值函数。已知类 String 的原型是:

29.请尽量说一下static和const关键字的作用是什么?

static关键字至少有以下五个功能:

(1)静态变量在函数体中的作用域就是函数体。与auto变量不同的是,变量的内存只分配一次,所以下次调用时它的值仍然是最后一个值;

(2)模块内的静态全局变量可以被模块内使用的函数访问,但不能被模块外的其他函数访问;

(3)模块中的静态函数只能被该模块中的其他函数调用,并且该函数的使用范围仅限于声明它的模块;

(4)类中的静态成员变量属于整个类,类的所有对象只有一份;

(5)类中的静态成员函数属于整个类,这个函数不接收this指针,所以只能访问类的静态成员变量。

const 关键字至少有以下五个功能:

(1)为了防止变量被改变,可以使用const关键字。在定义const变量时,通常需要初始化它,因为以后没有机会改变它;

(2)对于指针,指针本身可以指定为const,指针指向的数据也可以指定为const,也可以两者都指定为const;

(3)在函数声明中,const可以修改形参,表示是入参,在函数内部不能改变其值;

(4)对于类的成员函数,如果指定为const类型,则表示是常量函数,不能修改类的成员变量;

(5)对于类的成员函数,有时返回值必须指定为const类型如果没有覆盖虚函数,这样它的返回值就不是“左值”了。例如:

30.请写一个C函数,如果处理器是Big_endian,则返回0;如果是 Little_endian,则返回 1。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论