c++经典面试题(二)
C和C++的区别
思想上,C是面向过程的结构化编程语言,C++是面向对象的(核心三要素:封装,继承,多态),当然C++具体到底层实现的内部,仍然是通过一个个过程实现的高层的抽象功能;
具体操作上,C++使用new/delete进行内存的申请释放,C则是malloc/free。C++支持函数重载,范式编程,模板类扥操作。
C++ 11 中的新特性还包括:智能指针(unique_ptr,shared_ptr,weak_ptr), move语义,forward完美转发,右值引用,auto关键字等
堆和栈的区别(内存分配方面)
堆区,分配程序申请的内存空间,需要释放,地址向上增长,比栈慢,灵活,空间大,存在内存碎片化问题。
栈区,编译器自动分配释放,存放函数参数,局部变量等,地址向下增长,后进先出,空间连续,速度快,但是程序无法控制,空间有限。
全局区(静态区),存放全局变量,静态变量等,初始化与未初始化部分分开存放
文字常量区,存放常量字符串等
程序代码区,存放函数体的二进制代码
C++文件编译执行的步骤(4个)
在linux环境下,C++编译执行的步骤分为如下四步:
预处理,主要是各种条件编译,宏定义替换,头文件包含的处理,后缀为.i
编译,将代码转换成汇编语言,包括词法分析,语法分析,语义分析,代码优化(o2,o3等优化级别)等具体处理。后缀为.s
汇编,生成目标代码(机器代码),后缀为.o
链接,将目标代码链接所需的库,生成可执行文件
static关键字的作用
修饰函数内变量,则该静态变量内存只分配一次,下次进入函数时不会重新初始化,保留上次结果
修饰模块内(可简单理解为,修饰一个cpp文件内的)全局变量,则该全局变量只能在模块内全局,修饰模块内函数,则该函数只能模块内调用。无法extern
修饰类内的成员变量,则该静态成员变量对于类的所有对象只有一份拷贝。修饰类内的成员函数,该函数不接受this指针,只能访问静态成员变量。
volatile关键字的作用
由于访问寄存器速度更快,编译器可能会对此进行优化,导致脏数据的产生,在多线程时问题更明显,volatie可以使得每次修改都落实,每次都从内存中取数据,让编译器不对被修饰的变量进行优化。
define const的区别
define,没有类型,立即数,预处理中转换,不可被指针指,可以为函数。修饰函数内变量,则该静态变量内存只分配一次,下次进入函数时不会重新初始化,保留上次结果
const,有类型,存在内存的静态区,编译中处理,可以指,不可以为函数。
new/delete,malloc/free的区别
malloc从堆里获得空间,堆是通过内存块的管理来实现的,因此: 程序申请内存+管理内存=实际使用内存 申请新的空间时,系统判断有无合适的内存块,有则分配,无则新申请。管理内存中包括该内存块大小,是否可用等信息,因此free的时候不需要知道大小。 区别主要在于:
malloc/free只负责动态分配/释放空间,而new/delete还包括对对象的构造(析构),初始化等操作
malloc/free是标准库函数,申请内存时需要设置大小,new/delete是操作符,且new的时候不需要指定大小(当然内部最底层的实现其实也是调用了malloc)
malloc返回的是void*指针或者null,new返回的是类型对应指针/异常
多态与虚函数
多态
静态多态,又可以称为编译多态,在编译时即可确认,主要包括函数重载,范型编程,模板类等
动态多态,又可以称为运行时多态,在运行时才可以确认,主要是虚函数的使用。
虚函数
virtual 关键字修饰类的成员函数, 其中virtual void f()=0; 为纯虚函数,抽象类,无法实例化
对于每个包含虚函数的类,维护一个唯一的虚函数表来控制运行时调用的函数。维护虚函数表需要额外开销,因此并不应该将每个函数设置为虚函数,而且虚函数主要是针对希望被继承时改写的函数。
非成员函数,内联函数,静态成员函数,拷贝构造,构造函数,友元函数不能设置为虚函数,析构函数可以设置为虚函数(若为基类,应设为虚函数)
类的继承
继承时,父类的内存在前,子类在后。先构造父类再构造子类。先析构子类再析构父类。 父类指针可以指向子类对象,反过来时可能因为缺少相应方法引起异常。 若希望类被继承,则应在析构函数前加virtual public 继承: public protected的成员保持不变,private成员子类无法访问 protected继承:public protected成员转变为protected,private成员子类无法访问 private继承:public protected成员转变为private,private成员子类无法访问 指针/引用
指针* ,指向对象地址,本身也是对象(根据寻址范围确定4字节,还是8字节)
引用&,对象别名,必须初始化一个已有对象,且总为最初绑定对象。也需要占用空间,本质上底层也是通过指针实现,只不过隐藏了
智能指针
主要是防止忘记delete,多次释放等操作带来的内存不安全。
unique_ptr,所有权归一个指针所有,只能通过move操作移动,无法=拷贝赋值
shared_ptr,允许多指一,通过统计引用次数判断是否释放资源,存在循环引用等问题
weak_ptr,辅助shared_ptr,不参与计数,无法访问资源,但是可以用做监控
STL标准模板库
从广义上分为容器,算法,迭代器
容器:各种数据结构,序列式容器:vector 顺序表(数组),list 双向链表,deque 循环队列等。关联式容器:set,map 红黑,unordered_set, unordered_map hash表
算法:各种常用算法,sort,find,copy等函数模板
迭代器:容器与算法间的胶合剂,提供一种访问容器内元素的方式,而无需暴露其内部结构,是STL的关键所在。
STL优点:C++内置,高可重用,高性能,可移植,跨平台。将数据与操作分离,数据由容器管理,操作可以由定制的算法定义。
算法:各种常用算法,sort,find,copy等函数模板
- 迭代器:容器与算法间的胶合剂 常见操作 .begin() .end() .rbegin() .rend() .assign() .size() .capacity() .empty() .resize() .reserve() .push_back() .pop_back() .insert() .pop() .push() .top() .front() .back() .erase() .clear() .swap()
容器适配器 stack< T > 栈 queue< T > 队列 priority_queue< T > 优先队列(堆)
左值与右值
左值 :非临时性的对象,持久存在,可寻址,左值引用& 右值 :临时性的对象,语句结束后销毁,通常不可寻址,右值引用&& 常见注意事项
一个声明的右值引用其实是左值,因为定义了变量名。
函数中传递参数时,传的右值引用,但参数本身是一个引用,也是左值,因此不会调用函数的右值版本,而是调用左值版本,引起不必要的拷贝。解决方法:forward完美转发,move等
forward完美转发,按照原来的类型转发,move以右值引用的形式
template函数参数推导,形参为T&时,实参无论是左引用还是右引用,均推导为T&。形参为T&&时,推导类型与实参一致,T&->T&,T&&->T&&