重拾C/C++(面试用)

1.内存有哪几种类型

C中,内存分为5个区:堆(malloc)、栈(如局部变量、函数参数)、程序代码区(存放二进制代码)、全局/静态存储区(全局变量、static变量)和常量存储区(常量)。此外,C++中有自由存储区(new)一说。
C/C++内存分配方式可以分为三种:
(1)从静态存储区域分配:内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快,不容易出错,因为有系统善后。例如全局变量、static变量;
(2)在栈上分配:在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置与处理器的指令集中,效率很高,但是分配的内存容量有限;
(3)从堆上分配:即动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁的分配和释放不同大小的堆空间会产生堆内碎块。

2.栈和堆的区别

1)堆存放动态分配的对象——即那些在程序运行时动态分配的对象,比如new出来的对象,其生存周期由程序控制;
2)栈用来保存定义在函数内的非static对象,如局部变量,仅在其定义的程序块运行时才存在;
3)静态内存用来保存static对象,类static数据成员以及定义在任何函数外部的变量,static对象在使用之前分配,程序结束时销毁;
4)栈和静态内存的对象由编译器自动创建和销毁。

(ps:堆和自由存储区的区别:总的来说,堆是C语言和操作系统的术语,是操作系统维护的一块动态内存分布,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。;自由存储区是C++中通过new与delete动态分配和释放对象的抽象概念。它们并不是完全一样,但是基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。)

3.程序编译的过程

程序编译的过程中就是将用户的文本形式的源代码(c/c++)转化成计算机可以直接执行的机器代码的过程。主要经过四个过程:预处理、编译、汇编和链接。
预处理:.cpp —> .i (.cpp —> 展开头文件、库文件、宏定义);
汇编: .i —> .s (把代码转化为汇编代码);
编译阶段: .s —> .obj (把汇编代码转化成二进制)
链接阶段: .obj —> .exe (把.obj文件链接成可执行文件)。

4.左值和右值

左值是C语言的术语,用于标识特定数据对象的名称或表达式。因此,对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签。对于早期的C语言来说,提到左值意味着:1.它指定了一个对象,所以引用内存中的地址;2.它可用在复制运算符的左侧。但是后来标准中新增了const限定符。用const创建的变量不可修改。因此,const标识符满足上面的第一项,但是不满足第二项。一方面C继续把标识对象的表达式定义为左值,一方面某些左值却不能放在赋值运算符的左侧。有些左值不能用于赋值运算符的左侧。此时,标准对左值的定义已经不能满足当前的状况。为此,C标准新增了一个术语:可修改的左值,用于标识可修改的对象。所以,赋值运算符的左侧应该是可修改的左值。当前标准建议,使用术语对象定位值更好。
右值指的是能赋值给可修改左值的量,且本身不是左值。
不是很严谨的来说,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式)。举例来说我们定义的变量 a 就是一个左值,而malloc返回的就是一个右值。或者左值就是在程序中能够寻值的东西,右值就是一个具体的真实的值或者对象,没法取到它的地址的东西(不完全准确),因此没法对右值进行赋值,但是右值并非是不可修改的,比如自己定义的class, 可以通过它的成员函数来修改右值。(可以取地址的,有名字的,非临时的就是左值 不能取地址的,没有名字的,临时的,通常生命周期就在某个表达式之内的就是右值)。

5.引用和指针的区别

1)指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。
2)引用在定义的时候必须进行初始化,并且不能改变。指针在定义的时候不一定要初始化,并且指向的空间可变。
3)有多级指针,但是没有多级引用,只能有一级引用。
4)指针和引用的自增运算结果不一样(指针是指向下一个空间,引用时引用的变量值加1)。
5)sizeof引用得到的是所指向变量(对象)的大小,而sizeof指针得到的是指针本身的大小。
6)引用访问一个变量是直接访问,而指针访问一个变量是间接访问。
7)作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引用的实质是传地址,传递的是变量的地址。
8) 不存在指向空值的引用,但是存在指向空值的指针。

6.形参与实参的区别

形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
实参可以是常量、变量、表达式、函数等,无论参数是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等方法使实参获得确定值,会产生一个临时变量。
函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。

  • 值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是大的结构体对象,将耗费一定的时间和空间。(传值)
  • 指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)
  • 引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)

7.C++关键字

关键字(保留字):C++系统中预定义的、在语言或编译系统的视线中具有特殊含义的单词。

ifelsewhilesignedthrowunionthis
intchardoubleunsignedconstgotovirtual
forfloatbreakautoclassoperatorcase
dolongtypedefstaticfriendtemplatedefault
newvoidregisterexternreturnenuminline
tryshortcontinuesizeofswitchprivateprotected
asmwhilecatchdeletepublicvolatilestruct

8.static

1)隐藏。(static函数,static变量均可)。当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
2)默认初始化为0(static变量)
3)保持变量内容的持久(记忆功能)
4)C++的类成员声明static:在对象内的static全局变量可以被对象内所用函数访问,但不能被对象外其它函数访问;在对象内的static函数只可被这一对象内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;在类中的static成员函数属于整个类所拥有,只能访问类的static成员变量。static类对象必须要在类外进行初始化;static类成员函数是没有this指针的;正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问 static修饰的类成员;static成员函数不能被virtual修饰。

9.struct

C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)。C++中,struct的成员默认访问说明符为public(为了与C兼容),class中的默认访问限定符为private,struct增加了访问权限,且可以和类一样有成员函数。

10.const

1)修饰普通类型的变量,使其值不允许修改。
2)修饰指针变量,A:const修饰指向的内容,则内容为不可变量;B:const修饰指针,则指针为不可变量;C:const修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。
3)const参数传递和函数返回值,A:值传递的const修饰传递,一般这种情况不需要const修饰,因为函数会自动产生临时变量复制实参值;B:当const参数为指针时,可以防止指针被意外篡改;C:自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取const外加引用传递的方法。
4)const修饰类成员函数,其目的是防止成员函数修改被调用对象的值

11.C和C++中的强制类型

C中是直接在变量或者表达式前面加上(小括号括起来的)目标类型来进行转换,一招走天下,操作简单,但是由于太过直接,缺少检查,因此容易发生编译检查不到错误,而人工检查又及其难以发现的情况;而C++中引入了下面四种转换:
1)static_cast:用于基本类型间的转换,用于有继承关系类对象间的转换和类指针间的转换,不能用于基本类型指针间的转换。
2)dynamic_cast:用于有继承关系的类指针间的转换,用于有交叉关系的类指针间的转换,具有类型检查的功能,需要虚函数的支持。
3)reinterpret_cast:用于指针间的类型转换,用于整数的指针间的类型转换。
4)const_cast:用于去掉变量的const属性,转换的目标类型必须是指针或者引用。

12.extern “C”

C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后与C语言的不同,为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern “C”。

13.ifndef/define/endif的用法以及和program once的区别

相同点是防止头文件被重复包含;不同点是ifndef由语言本身提供支持,但是program once一般由编译器提供支持,也就是说,有可能会出现编译器不支持的情况(主要是比较老的编译器)。通常运行速度上ifndef一般慢于program once,特别是在大型项目上,区别会比较明显,所以越来越多的编译器开始支持program once。ifndef作用于某一段被包含(define和endif之间)的代码,而program once则是针对包含该语句的文件,这也是为什么program once速度更快的原因。

14.C++三大特性:继承、多态、封装

继承:被继承的是父类(基类),继承出来的类是子类(派生类),子类拥有父类的所有的特性。继承方式有公有继承、私有继承,保护继承。优点:继承减少了重复的代码、继承是多态的前提、继承增加了类的耦合性;缺点:如果继承下来的子类不适合解决新问题,父类必须重写或替换,那么这种依赖关系就限制了灵活性,最终限制了复用性。
多态性是指对不同类的对象发出相同的消息将会有不同的实现;C++有两种多态,称为动多态(运行期多态)和静多态(编译器多态),静多态主要是通过模板来实现,而动多态是通过虚函数来实现的。即在基类中存在虚函数(一般为纯虚函数)子类通过重载这些接口,使用基类的指针或者引用指向子类的对象,就可以调用子类对应的函数,动多态的函数调用机制是执行期间才能确定的,所以他是动态的。优点:大大提高了代码的可复用性;提高了了代码的可维护性,可扩充性;缺点:易读性比较不好,调试比较困难; 模板只能定义在头文件中,当工程大了之后,编译时间十分的变态;
封装性是隐藏类的属性和实现细节,仅仅对外提供接口,封装性实际上是由编译器去识别关键字public、private和protected来实现的,体现在类的成员可以有公有成员(public),私有成员(private),保护成员(protected)。私有成员是在封装体内被隐藏的部分,只有类体内说明的函数(类的成员函数)才可以访问私有成员,而在类体外的函数时不能访问的,公有成员是封装体与外界的一个接口,类体外的函数可以访问公有成员,保护成员是只有该类的成员函数和该类的派生类才可以访问的。优点:隔离变化;便于使用;提高重用性;提高安全性;缺点:如果封装太多,影响效率;使用者不能知道代码具体实现。

15.链表和数组有什么区别

(1)存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块不连续的动态空间,长度可变。每个节点要保存相邻结点指针;
(2)数据查找:数组的线性查找速度更快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低;
(3)数据插入或删除;链表可以快速插入和删除结点,而数组则可能需要大量数据移动;
(4)越界问题:链表不存在越界问题,数组有越界问题。

16.typedef和define有什么区别

(1)用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义敞亮,以及书写复杂使用频繁的宏;
(2)执行时间不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查;
(3)作用域不同:typedef有作用域限定,define不受作用域的约束,只要在define声明后得引用都是正确的;
(4)对指针的操作不同:typedef和define定义的指针时有很大的区别。

17.简述strcpy、sprintf和memcpy的区别

(1)操作对象不同:strcpy的两个操作对象均为字符串,sprintf的操作对象可以是多种数据类型,目的操作对象是字符串,memcpy的两个对象就是两个任意可操作性的内存地址,并不限于何种类型数据;
(2)执行效率不同:memcpy最快,strcpy次之,sprintf的效率最低;
(3)实现功能不同:strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他类型数据格式到字符串的转化,memcpy主要是内存块间的拷贝。

18.简述多态实现的原理

编译器发现一个类中有虚函数,便会立即为此类生成虚函数标vtable。虚函数标的各表项为指向对应虚函数的指针。编译器还会在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)指向虚函数表。调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable,将类与此类的vtable联系了起来。另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体类的this指针,这样依靠此this指针即可得到正确的vtable。如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。

19.内联函数特征及其优缺点

特征:
  相当于把内联函数里面的内容卸载调用内联函数处;
  相当于不用执行进入函数的步骤,直接执行函数体;
  相当于宏,却比宏多了类型检查,真正具有函数特性;
  编译器一般不内联包含循环、递归、switch等复杂操作的内联函数;
  在类声明中定义的函数,除了虚函数的其他函数都会自动隐式的当成内联函数。
编译器对inline函数的处理步骤:
  1.将inline函数体复制到inline函数调用点处;
  2.为所用inline函数中的局部变量分配内存空间;
  3.将inline函数的输入参数和返回值映射到调用方法的局部变量空间中;
  4.如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用goto)。
优点:
  1.内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度;
  2.内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会;
  3.在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能;
  4.内联函数在运行时可调试,而宏定义不可以。
缺点:
  1.代码膨胀:内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另外,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间;
  2.inline函数无法随着函数库升级而升级。Inline函数的改变需要重新编译,不像non-inline可以直接链接;
  3.是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值