复试之C/C++系列问题

C/C++中为什么有引用

C++之所以增加引用类型, 主要是把它作为函数参数,以扩充函数传递数据的功能。

引用是一个常量指针占四个字节,编译器在编译时对引用作了更严格的限制,与普通指针相比更加安全。详细见C++的那些事:你真的了解引用吗

指针和引用的区别

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(3)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化
(4)指针的值在初始化后可以改变,即指向其它的存储单元,而引用初始化后就不会再改变。
(5)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小。
(6)作为参数传递时,二者有本质不同:指针传参本质是值传递,被调函数的形参作为局部变量在栈中开辟内存以存放由主调函数放进来的实参值,从而形成实参的一个副本。而引用传递时,被调函数对形参的任何操作都会通过一个间接寻址的方式影响主调函数中的实参变量。

指针数组、数组指针

(1)指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
(2)数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。

char *arr[4] = {"hello", "world", "shannxi", "xian"}; 
//arr[4]是一个定义的数组。把它对应到对应到内存中,arr是一个在栈区,有四个元素的数组,而每一个元素又是一个指针,所以说它的四个元素各占四个字节,所以变量arr的大小是16个字节。arr+1会跳过四个字节。也就是一个指针的大小 。
char (*pa)[4]; //pa是一个指针指向一个char [4]的数组

C++内存布局

(1)栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量值等,其操作方法类似数据结构中的栈。
(2)堆区(heap):一般由程序员分配释放,与数据结构中的堆毫无关系,分配方式类似于链表。
(3)全局/静态区(static):全局变量和静态变量的存储是放在一起的,在程序编译时分配。
(4)文字常量区:存放常量字符串。
(5)程序代码区:存放函数体(类的成员函数、全局函数)的二进制代码

int a=0; //全局初始化区
char *p1; //全局未初始化区
void main()
{
int b; //栈
char s[]="bb"; //栈
char *p2; //栈
char *p3="123"; //其中,“123\0”常量区,p3在栈区
static int c=0; //全局区
p1=(char*)malloc(10); //10个字节区域在堆区
strcpy(p1,"123"); //"123\0"在常量区,编译器 可能 会优化为和p3的指向同一块区域

C/C++内存分配有三种方式:

(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。
动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏。另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

malloc/free 、new/delete区别

(1)malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
(2)对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
(3)C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
(4)new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。

字节对齐问题

字节对齐是C/C++编译器的一种技术手段,主要是在可接受空间浪费的前提下,尽可能地提高对相同元素过程的快速处理(比如32位系统,4字节对齐能使CPU访问速度提高)

字节对齐的原则

(1)结构体中每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会填充字节

(2)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会填充字节。

内联函数有什么优点?内联函数和宏定义的区别

优点:

函数会在它所调用的位置上展开。这么做可以消除函数调用和返回所带来的开销(寄存器存储和恢复),而且,由于编译器会把调用函数的代码和函数本身放在一起优化,所以也有进一步优化代码的可能。
内联函数使用的场合:对于简短的函数并且调用次数比较多的情况,适合使用内联函数。

内联函数和宏定义区别:

1)内联函数在编译时展开,而宏在预编译时展开
2)在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
3)内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。
4)宏不是函数,而inline是函数

以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

覆盖、重载、隐藏的区别

(1)重载:重载翻译自overload,是指同一可访问区内被声明的几个具有不同参数列表(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
(2)覆盖:重写翻译自override,是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致,只有函数体不同。

(3)重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

  • 如果派生类的函数和基类的函数同名,但是参数不同,此时不管有无virtual,基类的函数被隐藏;
  • 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时基类函数被隐藏。

虚函数有什么作用?

(1)虚函数的功能是使子类可以用同名的函数对父类函数进行覆盖,并且在通过父类指针调用时,如果有覆盖则自动调用子类覆盖函数,如果没有覆盖则调用父类中的函数,从而实现灵活扩展和多态性

(2)如果是纯虚函数,则纯粹是为了在子类覆盖时有个统一的命名而已,子类必须覆盖纯虚函数,则否子类也是抽象类;

(3)含有纯虚函数的类称为抽象类,不能实例化对象,主要用作接口类。

更详细的讲解见C++虚函数表解析

虚析构函数有什么作用?

(1)析构函数的工作方式是:最底层的派生类的析构函数最先被调用,然后调用每一个基类的析构函数;

(2)在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非虚的析构函数,则可能导致运行时派生类不能被销毁。然而基类部分很有可能已经被销毁,这就导致“部分析构”现象,造成内存泄漏;

(3)给基类一个虚析构函数,删除一个派生类对象的时候就将销毁整个对象,包括父类和全部的派生类部分。

构造函数与析构函数的异同点

构造函数特点

(1)构造函数的名字必须与类名相同;

(2)构造函数可以有任意类型的参数,但不能有返回类型

(3)定义对象时,编译系统会自动调用构造函数;

(4)构造函数是特殊的成员函数,函数体可以在类体内也可以在类体外;

(5)构造函数被声明为公有函数,但它不能像其他成员函数那样被显式调用,它是在定义对象的同时被调用的。

析构函数特点

(1)析构函数的名字必须与类名相同,但它前面必须加一个波浪号

(2)析构函数没有参数,也没有返回值,而且不能被重载,因此在一个类中只能有一个析构函数

(3)当撤销对象时,编译系统会自动调用析构函数;

(4)析构函数可以是virtual,而构造函数不能是虚函数

成员函数和友元函数的区别

相同点:

  1. 对类的存取方式相同,可以直接存取类的任何存取控制属性的成员
  2. 可以通过对象存取形参、函数体中该类类型对象的所有成员

不同点:

  1. 成员函数有this指针,而友元函数没有
  2. 友元函数不能被继承

vector迭代器的几种失效的情况

1.当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。

2.当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时first和end操 作返回的迭代器都会失效。

3.当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。

有哪些东西是编译期间确定的,哪些是运行期间确定的?

考察编译和运行的了解。编译期间确定数组大小空间,宏定义,内联函数展开,extern变量等。运行期间确定new大小,多态类对象的函数调用,未赋值全局指针的指向等。

参考

C/C++ 经典面试题总结

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2015-2024 YuleZhang's Blog
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信