来源:阿秀的学习笔记
21.C++和Java的区别
1.Java语言给开发人员提供了更为简洁的语法;完全面向对象,JVM可以安装在任何系统上,可移植性强;
2.Java没有指针,实现了真正的数组,避免了由于指针越界带来的内存安全问题
3.Java编写一次代码就可以到处运行,Java一般生成字节码,在JVM中运行;’C++可以在不同系统运行,但需要不同的编码
4.Java用接口代替了C++中的抽象类
垃圾回收:
C++用析构函数回收垃圾,写c和c++程序时注意内存申请和释放
Java不需要回收垃圾,系统自动进行,不用考虑垃圾碎片问题
应用场景:
C++可以直接编译成exe文件,在桌面程序上C++更实用,指针是C++的优势,可以直接对内存操作
web端java有多种框架
C++更适用于底层和控制
22.C++中struct和class 的区别
相同点:
两者都拥有成员函数,公有和私有部分
任何class可以完成的工作,struct也可以完成
不同点:
struct默认公有,class默认私有
23.define宏定义和const的区别
编译:
define是在编译的预处理阶段;const是在编译、运行的时候进行的
安全性:
define只做替换,不进行类型检查,const有数据类型,进行类型安全性检查
内存:
define宏定义的数据不分配空间,const只是变量的值不能改变,但是要分配空间
24.C++中const和static的作用
25.C++的顶层const和底层const
顶层const: int *const b1 = &a;指的是const所修饰的变量本身是常量,无法修改,指的是指针
底层const:const int* b2 = &a;指的是const所修饰的所指向的对象是常量,指的是所指变量
区分作用:执行对象的拷贝时有限制,常量的底层const不能赋值给非常量的底层const
使用命名的强制类型转换函数const_cast时,只能改变运算对象的底层const
26.数组名和指针的区别?
二者均可以通过增减偏移量来访问数组中的元素
数组名不是真正的指针,可以理解为常指针,不能实现自增、自减操作
当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增自减操作
sizeof不能得到数组大小了
27.final和override关键字
28.拷贝初始化和直接初始化
在进行类类型对象时,初始化的拷贝形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,拷贝初始化调用拷贝构造函数,首先使用构造函数创建临时对象,再用拷贝构造函数将临时对象拷贝到正在创建的对象。
29.初始化和赋值的区别
30.extern “C”的用法
1.C++中调用C语言代码
2.在C++头文件中使用
31.野指针和悬空指针
野指针:没有被初始化的指针。解决:要么初始化要么置空
悬空指针:指针最初指向的内存已经被释放了的一种指针。解决:释放操作后立即置空。C++智能指针本质上避免悬空指针的产生
32.C和C++类型安全
33.C++中的重载、重写和隐藏的区别
重载:在同一范围内定义中的同名成员函数才存在重载关系。主要特点是函数名相同,参数类型和数目不同。
重写:在派生类中覆盖基类中的同名函数,重写就是重写函数体,要求基类函数必须是虚函数。且:与基类的虚函数有相同的参数个数,参数类型,返回值类型。
重载和重写的区别:
重写是父类和子类之间的垂直关系,重载是不同函数之间水平关系。
重写要求参数列表相同,重载要求参数列表不同,返回值不要求
重写关系中,调用方法根据对象类型来决定,重载根据调用时实参表与形参表的对应关系来决定。
隐藏:
派生类中的函数屏蔽了基类中的同名函数
两个函数参数相同,但是基类函数不是虚函数
34.C++中有几种构造函数
默认构造函数
初始化构造函数
拷贝构造函数
移动构造函数
委托构造函数
转换构造函数
35.浅拷贝和深拷贝的区别
浅拷贝:只拷贝指针,不开辟新的地址,拷贝的指针和原来的指针指向同一地址,如果原来的指针所指向的资源释放了,则再释放拷贝的指针会出现错误
深拷贝:不仅拷贝值,还要给值分配新的内存地址,即使原来的对象被析构掉也不影响,不影响。
36.内联函数和宏定义的区别
宏定义只做简单字符串替换;内联函数进行参数类型检查
内联函数直接嵌入到代码中,减少函数调用开销,并且进行参数类型检查,具有返回值,可以实现重载。
适用场景:
使用宏定义的地方都可以使用inline
作为类成员接口函数来读写类的私有成员或保护成员,会提高效率。
37.public,protected和private访问和继承权限/public/protected/private的区别?
38.如何用代码判断大小端存储?
大端存储:字数据的高字节存储在低地址中
小端存储:字数据的低字节存储在低地址中
使用强制类型转换:int转换为char,借助int型转换为char型只会留下低地址的部分
39.volatile、mutable和explicit关键字的用法
40.什么情况下会调用拷贝构造函数
用类的一个实例化对象去初始化另一个对象的时候
函数的参数是类的对象时
函数的返回值是函数体内局部对象的类的对象时
41.C++中有几种类型的new
plain new;nothrow new和placement new
1.plain new是普通的new,在分配内存异常抛出的bad_alloc而不是返回NULL,因此判断返回值是否为NULL是徒劳的
2.nowthrow在空间分配异常情况下不抛出异常,返回NULL
3.placement new:允许在一块已经成功分配的内存上重新构造函数或对象数组。placement不进行内存分配,只调用对象的构造函数
42.C++的异常处理的方法
数组下标越界;
除法除数为0;
动态分配空间时空间不足
1.try,throw,catch关键字
先执行try包裹的语句块,如果执行过程中没有异常发生,不会进入任何catch,如果异常,则使用throw进行抛出异常,再由catch进行捕获
2.函数的异常声明列表
在函数声明和定义时,指出所抛出异常的列表
3.C++标准异常类
bad_typeid
bad_cast
bad_alloc
out_of_range
43.static的用法和作用
1.隐藏。没加static前缀的全局变量和函数都具有全局可见性
2.static保持变量内容的持久
3.默认初始化为0
4.C++中的类成员声明static
44.指针和const 的用法
1.当const修饰指针时,const位置不同,修饰的对象不同
2.顶层指针,const所修饰的对象是变量本身是常量,无法改变,指的是指针本身是常量
3.底层指针,const所修饰的是指针所指向的对象,指的是所指变量是常量
45.形参与实参的区别
1.形参变量只有被调用时才被分配内存单元,调用结束即可释放所分配的单元。因此,形参只在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
2.实参可以是一个常量、变量、函数、表达式等,一定要提前赋值,才能传递给形参。
3.实参和形参在数量、类型、顺序上应该严格一直,否则会发生“类型不匹配”问题
4.函数调用中发生的数据传送是单向的,只能实参传递给形参。
5.当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,实参内容不变。
46.值传递、指针传递、引用传递的区别和效率
1.值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象,或是大的结构体对象,将耗费一定的时间和空间
2.指针传递:同样是一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。
3.引用传递:同样是一个形参向函数所属的栈拷贝数据的过程,但针对的是地址,相当于为该数据所在的地址起了一个别名。
效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。
47.静态局部变量什么时候初始化?
1.初始化只有一次,但可以多次赋值,在主程序之前,已经为其分配好了内存。
2.静态局部变量和全局变量一样,数据都存在全局内存中,所以在执行主程序之前,编译器已经为其分配好了内存。C语言和C++中静态局部变量的初始化节点有所不同,C语言中,初始化发生在代码执行之前,编译器分配内存之后,所以函数中的变量无法对静态局部变量赋值,在程序运行结束之后,变量所处的全局内存会被收回。
3.C++中由于初始化需要构造函数和析构函数,所以一般是在相关代码执行时,才会进行初始化,因为在构造函数和析构函数中经常需要进行某些程序中需要进行的特定操作,并非简单地分配内存。
48.const关键字的作用有哪些?
1.阻止一个变量被改变,在定义该const变量时,通常进行初始化,因为以后没有机会去改变了。
2.对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const。
3.在一个函数声明中,const可以修饰形参,表明是一个输入参数,在函数内部不能改变其值。
49.什么是类继承?
has-A包含关系,用以描述一个类由多个部件类构成,一个类的成员属性是另一个已经定义好的类
use-A 一个类使用另一个类,通过类之间的成员函数相互联系,通过传递参数实现
is-A 继承关系,关系具有传递性
2.继承的相关概念
一个类继承了另一个类的属性和方法,这个新的类包含上一个类的属性和方法,这个新的类被称为子类或继承类,上一个类被称为父类或基类。
3.继承的特点
子类拥有父类所有的属性和方法,子类可以拥有父类没有的属性和方法,子类对象可以当做父类对象是用
4.继承中的访问控制
public、protected、private
5.继承中的构造函数和析构函数
6.继承中的兼容性原则
54.new和delete 的实现原理,delete是如何知道释放内存的大小的?
1.new简单类型直接调用operator new分配内存
复杂类型调用operator new分配内存,然后在分配内存上调用构造函数
对于简单类型,new[]计算好大小后直接调用operator new;
对于复杂结构类型,new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小。
(1)new表达式调用一个名为operator new的函数,分配一块足够大的原始的内存
(2)调用构造函数以构造这些对象,并为其传入初始值。
(3)对象分配了空间并构造完成,返回一个指向该对象的指针。
2.delete简单数据类型默认只是调用free函数,复杂数据类型先调用析构函数,然后再调用operator delete。针对简单类型,delete和delete[]等同,假设指针p指向new[]分配的内存。因为要四字节存储大小,实际分配的内存地址为[p-4],系统记录的也是这个地址,delete[]实际释放的就是p-4指向的内存。
3.需要在new[]一个对象数组时,需要保存数组的大小,专门分配四个字节保存数组的大小,在delete[]时就可以取出这个保存的数,就知道需要调用多少次了。
55.malloc申请的存储空间能用delete释放吗?
不能,malloc/free主要为了兼容C,new/delete完全可以取代malloc/free
malloc/free操作必须明确大小,而不能用在动态类上
new/delete会自动进行类型检查和大小,malloc/free不能执行构造函数和析构函数,所以动态对象是不行的
56.malloc和free的实现和原理
brk是将堆顶指针向高地址移动,获得新的内存,如果大于128k,采用mmap在进程的虚拟空间中找一块闲的内存。两种内存都是虚拟内存,发生缺页中断时,操作系统负责分配物理内存,进行映射。
malloc是从堆里面申请内存,也就是说函数返回的指针指向堆里的一块内存。操作系统有一个记录空闲地址的内存链表。当操作系统申请内存时,就会遍历该链表,找到第一个空间大于该申请内存的堆节点,并将该点删除。
57.malloc、realloc、calloc的区别
malloc申请的空间的值是随机初始化的、calloc申请的空间的值是初始化为0的;
realloc给动态空间分配额外的空间,用于扩充容量。