C++面试

it2023-02-02  57

转载自:转载链接 C++面试一般围绕以下三部分: 1,基础wend 2,虚函数、虚函数表、纯虚函数、抽象类、析构函数、拷贝构造函数 3,操作数重载、STL、智能指针

C++内存分区:堆、栈、自由存储区、全局/静态存储区和常量存储区 -------------------------------------------分割线-------------------------------------------------------------------

问答

Q1:你知道虚函数吗? 实现多态所必须,父类类型的指针指向子类的实例,执行的时候会执行子类中定义的函数。 Q2:析构函数可以是虚函数吗? 如果有子类的话,析构函数必须是虚函数。否则析构子类类型的指针时,析构函数可能不会被调用到。 Q3:多态的实现? 简言之是编译器根据虚函数表找到恰当的虚函数。对于一个父类的对象指针类型变量,如果给它赋父类 对象的指针,那么它就调用父类函数中的函数,如果给他赋子类对象的函数,它就调用子类中的函数。函数执行之前通过查虚函数来查找调用的函数。 Q4:虚函数表是针对类还是针对对象的? 虚函数表是针对类的,一个类的所有对象的虚函数表都一样。 Q5:纯虚函数和虚函数有什么区别? 纯虚函数就是定义了一个虚函数但没有实现,原型后面加“=0”; 包含纯虚函数的类都是抽象类,不能生成实例; 在虚函数表中 Q6:构造函数可以是虚函数吗? 构造函数不能是虚函数,每个对象的虚函数表指针是在构造函数中初始化的,因为构造函数没执行完,所以虚函数表指针还没初始化好,构造函数的虚函数不起作用,所以构造函数不能是虚函数。 Q7:构造函数可以调用虚函数吗? 调用虚函数不起作用,调用虚函数同调用一般的成员函数一样。 Q8:析构函数中,可以调用虚函数吗? 可以,调用会跟普通函数一样,析构函数中调用虚函数不起作用,调用虚函数同调用一般成员函数一样。析构函数的顺序是先派生类后基类,有可能内容已经被析构没了,所以虚函数不起作用。 Q9:构造初始化的执行顺序?析构函数的执行顺序? 构造时:父类的构造函数--->子类的构造函数 析构时:子类的析构函数-->父类的析构函数 QA11:除了栈以外,堆、只读数据区、全局变量地址增长方向都是从低到高。 Q12:struct和class的区别? 二者大体相似,但是访问权限不同。struct默认的访问权限是public,class默认是private。 Q13:C++的内存管理? 堆、栈、自由存储区、全局/静态存储区、常量存储区 自由存储区存储malloc申请的内存。 扩充: C++中,内存分为5个区:堆、栈、自由存储区、全局/静态存储区和常量存储区。 栈:是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。 堆:是由new分配的内存块,由程序员释放(编译器不管),一般一个new与一个delete对应,一个new[]与一个delete[]对应。如果程序员没有释放掉,资源将由操作系统在程序结束后自动回收。 自由存储区:是由malloc等分配的内存块,和堆十分相似,用free来释放。 全局/静态存储区:全局变量和静态变量被分配到同一块内存中(在C语言中,全局变量又分为初始化的和未初始化的,C++中没有这一区分)。 常量存储区:这是一块特殊存储区,里边存放常量,不允许修改。 (注意:堆和自由存储区其实不过是同一块区域,new底层实现代码中调用了malloc,new可以看成是malloc智能化的高级版本) Q14:C++是不是类型安全的? 不是。两个不同类型的指针之间可以强制转换(用reinterpret cast) Q15:一个类没有定义属性和函数,sizeof大小是多少?编辑器为什么要这么做? 一个空类对象的大小是1B。这是被编译器安插进去的一个字节,这样就使得这个空类的实例得以在内存中配置独一无二的地址。 Q16:函数内存空间放在哪里? 放在代码段里。 Q17:局部变量可否与全局变量重名? 可以,类型也可以不同。 Q18:引用和指针有什么区别? 引用必须初始化; 引用不能为空; 引用在赋值后不能修改; 指针通过某个指针变量指向一个对象后,对他所指向的变量间接操作,而引用是相应对象的别名。 Q19:将引用作为函数参数和返回值有哪些特点?有哪些注意事项? 作为函数参数时候不产生拷贝,直接传递内存地址的值; 作为返回值的时候必须是有效的,而指针可以是无效的,例如空指针等等。 Q20:阐述extern “C”和extern的作用? extern "C"告知编译器以C的形式编译,因为C中没有函数重载,函数在底层的签名是函数名,而C++的函数签名是函数名,返回值参数类型1参数类型2... extern作用声明外部变量,现在现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是互相透明的,也就是说,在编译时,全局变量的可见域限制在文件内部。 Q21:多态的作用? 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 Q22:内存对齐:提高访问内存的速度,内存是一个连续的块,以四个字节对一个对齐单位。 Q23:什么是内联函数? 内联函数指的是用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数从源码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能够形成内联函数,需要看编译器对该函数定义的具体处理。 Q24:内联函数和宏定义的区别? 内联函数在编译器展开,宏在预编译时展开。 在编译的时候,内联函数可以直接被镶嵌在目标代码中,而宏只是一个简单的文本替换。 内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏就不具有这样的功能。 Q25:为什么需要内存对齐? 需要字节对齐的根本原因在于CPU访问数据的效率问题。自然对齐下访问内存的次数要小于未自然对齐的次数。 Q26:描述内存分配方式以及它们的区别? 1)从静态存储区分配。内存在程序编译的时候就已经编译好了,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。 3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生理期由程序员决定,使用非常灵活,但问题也很多。 Q27:简述数组和指针的区别? 数组要么在静态存储区被创建(如全局变量),要么在栈上被创建。指针可以随时指向任意类型的内存块。 用运算符sizeof()可以计算出数组的容量(字节数)。sizeof(p),p为指针得到的是一个指针变量的字节数,而不是p所指的内存容量。C++/C语言没有办法知道指针所指向的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。 Q28:指针常量和常量指针 const int p; //p为常量,初始化后不可更改 const int* p; //*p 为常量,不能通过*p改变它指向的内容 **指针常量const int *p,*p是常量,不能通过*p类改变指向的内容** **常量指针int *const p,p是常量,初始化后不能指向其他内容** int const* p: //*p为常量,同上 int* const p: //p为常量,初始化后不能再指向其他内容

Q29:static和const分别怎么用?类里面static和const可以同时修饰成员函数吗?

static的作用: 1,局部变量: 在局部变量之前加上关键字static,局部变量就被定义成为了一个局部静态变量。 1)内存中的位置:静态存储区 2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非它被显示初始化) 3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。 注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置(从原来的栈中存放改为静态存储区)及其生命周期(局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对它进行访问),但未改变其作用域。 2,全局变量 在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。 1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在) 2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非它被显示初始化) 3)作用域:全局静态变量在声明它的文件之外是不可见的。准确的将从定义之处开始到文件末尾。 注:static修饰全局变量,并为改变其存储位置及生命周期,而是改变了作用域,使当前文件外的源文件无法访问该变量,好处如下:(1)不能被其他文件所访问,修改 。(2)其他文件中可以使用相同名字的变量,不会发生冲突。对全局函数也是有隐藏作用。

对于类中

1,成员变量 用static修饰类的数据成员实际使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象。因此,static成员必须在类外进行初始化(初始化格式:int base :: var = 10;),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化。 特点: 不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。 静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。 静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为所属类类型的指针或引用。 2,成员函数 用static修饰成员函数,使这个类只能存在这一份函数,所有对象共享该函数,不含this指针。 静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。base::func(5,3);当static成员函数在类外定义时不需要加static修饰符。 在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。因为静态成员函数不包含this指针。

不可以同时用const和static修饰成员函数

C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。 也可以这样理解:二者的语意是矛盾的。static的作用是表示只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类的静态变量没有关系。因此不能同时用他们。 const的作用: 1,限定变量为不可修改 2,限定成员函数不可以修改任何数据成员 3,const与指针 const charp 表示指向的内容不能改变 char const p 就是将p声明为常指针,它的地址不能改变,是固定的,但是它的内容可以改变。 Q30:为什么不能两次握手:防止已失效的连接请求又传送到服务器端,因而产生错误 假设改为两次握手,client端发送的一个连接请求在服务器滞留了,这个连接请求是无效的,client已经是closed的状态了,而服务器认为client想要建立一个新的连接,于是向client发送确认报文段,而client端是closed状态,无论收到什么报文都会丢弃。而如果是两次握手的话,此时就建立连接了。服务器此时会一直等待client端发来数据,这样就浪费掉很多server端的资源。 三次握手主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的。 TCP可靠传输的实现: TCP连接的每一个端口都必须设有两个端口,一个发送端口,一个接收端口。TCP的可靠传输机制用字节的序号进行控制。TCP所有的确认都是基于序号而不是基于报文段。 发送过的数据未收到确认之前必须保留,以便超时重传时使用。发送窗口没收到确认不动,收到新的确认后前移。 发送缓存用来暂时存放:发送应用程序传送给发送方TCP准备发送的数据;TCP已发送出但尚未收到确认的数据。 接收缓存用来暂时存放:按序到达的、但尚未被接收到应用程序读取的数据;不按序到达的数据。

最新回复(0)