c++ 面试常见问答
一、指针和引用的区别
- 本质:指针是一个实体,需要分配内存空间,引用只是变量的别名,不需要分配内存空间
- 内存分配:指针与引用都需要分配内存区域,引用在定义的时候必须初始化,一旦引用与某个变量绑定后就不再改变
- 级数:有多级指针,没有多级引用
- 自增含义:指针自增代表指向下一个空间,引用自增代表引用的变量值加1
- sizeof 大小:sizeof引用得到的是指向变量的大小,sizeof 指针得到的是指针本身的大小
- 访问方式:引用访问一个变量是直接访问,指针访问一个变量是间接访问
- 安全检查:使用指针前最好做类型检查,防止野指针的出现,引用可以防止野指针的出现
- 引用底层是通过指针实现的
- 作为参数时的不同:传指针的实质是传值,传递的值是指针的地址,传引用的实质是传地址,传递的是变量的地址
二、static 关键字的用法和作用
- 告知编译器讲变量存放在程序的静态存储区而非 栈上/堆上 空间(编译时初始化)
- 同时编译多个文件时,所有未加static前缀的全局变量都有全局可见性,即a.cpp可以使用extern 调用b.cpp里面的全局变量;若加了static 修饰,则该全局变量只对该文件有效,可以解决命名冲突的问题
- 优势:可以节省内存,因为它是所有对象所公有的
- 注意:
- 函数体内的static 变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次
- 模块内的static 变量可以被模块内的所有函数访问,但不能被其他模块使用
- static 类成员函数不能访问非 static 成员变量 ---> static 函数内没有this 指针,无法访问私有变量
三、const 关键字
阻止一个变量被修改
函数声明内可以使用const 修饰形参,表明其是一个输入参数,在函数内部不可以改变其值
const 的几种用法:
- const int *p ---- 常量指针,不可以通过这个指针改变其所指向的变量的值
- int * const p ---- 指针常量,p的值不可变
- const int* const p ---- 指向常量的常指针
四、虚函数、内联函数
虚函数用于实现运行时的多态,实现方式:C++ 内存模型规定最开始的四个字节(32位机器)为指向虚函数表的指针(vptr), 当把子类的实例赋值给基类指针,基类的vptr就会指向子类的虚函数表
内联函数在编译时将所有运用内联函数的地方替换为代码块
五、构造函数不可以为虚函数,析构函数需要为虚函数
- 虚函数表是在构造函数内初始化的,构造函数设置为虚函数,会导致对应类无法初始化一个对象
- 析构函数如果不设置为虚函数,则在多态情况下,析构基类的同时,无法析构基类指向的子类
六、容器内部元素的删除
- 顺序容器
- erase 迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器失效,不可以使用erase(it++) 的方式,但是 erase(it)的返回值是下一个迭代器,it=erase(it)
- 关联容器
- erase 迭代器只是使得被删除元素的迭代器失效,返回值为void, 采用 erase(it++)的形式删除迭代器
七、c++ 对struct进行了进一步的扩展,使struct在c++中可以和class一样当作类使用,唯一与class不同的地方在于struct 的成员默认访问权限是public, class 是private
八、c/c++ 内存分配
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量的值,其操作方式类似于数据结构中的栈
- 堆区:由程序员分配释放,若程序员不释放,程序结束时由os回收(比较危险,程序员自己不回收容易引起内存泄漏)
- 全局区(静态区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量,在相邻的另一块区域
- 文字常量区:常量字符串就是放置在这里的,程序结束后由系统释放
- 程序代码区:存放程序的二进制代码
九、野指针和内存泄漏
- 野指针:指向被释放的内存或者没有访问权限的内存
- 内存泄漏:由于程序员的疏忽或错误导致程序未能释放掉不再使用的内存的情况,解决办法:智能指针
十、new 和 malloc 区别
- new 运算符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,malloc 需要显示给出所需内存的大小
- new 操作符分配成功时,返回对象类型的指针,malloc内存分配成功,则返回void *,必须要通过强转变成所需要的类型
十一、空类的大小
- 空类的大小是1,c++ 要求对于每个类的实例都要有独一无二的地址,编译器会自动为空类分配一个字节大小,这样子保证了每个实例均有独一无二的地址
- 带虚函数的c++ 空类大小不为1,因为有vptr的存在
十二、虚函数的代价
- 带有虚函数的类,每一个虚函数类会产生一个虚函数表,用来存放指向虚成员函数的指针,从而增加类的大小
- 带有虚函数的类的每一个对象,都会有一个指向虚表的指针,会增加对象的占用空间大小
- 虚函数不可以作为内联函数
十三、c++ 类型转换
- static_cast ----- 一般的转换
- dynamic_cast ----- 通常用于基类派生类之间的转换
- const_cast ----- 主要用于针对const的转换
- reinterpret_cast ----- 用于没有任何关联的转换,比如一个字符指针转换为一个整数
十四、空类有哪些默认的函数
- 缺省构造函数
- 拷贝构造函数
- 析构函数
- operator= 赋值运算符重载函数
- 取地址运算符重载函数
十五、代码段从0地址开始的吗
否,0地址开始处的空间是不被使用的(逻辑地址)
十六、c++11 新特性
- 右值引用
- 初始化列表
- 类型推导
- 基于范围的for循环
- lamda 表达式
- 空指针 nullptr
- 智能指针
- 正则表达式
十七、迭代器失效的几种情况
- 对于序列式容器,删除当前的iterator,会使得后面所有元素的迭代器失效
- 使用pushback后,导致vector扩容,进而导致迭代器失效
- list中,元素的删除操作会导致指向该元素的迭代器失效
十八、模板函数不可以是虚函数
模板函数需要根据调用情况生成不同的实例,虚函数表的大小却需要在编译时确定
十九、虚函数可以定义为static吗?
不可以,虚函数是通过this指针调用的,而static函数内部没有this指针
二十、静态库与动态库区别
静态库在编译时就被载入可执行程序,体积较大,动态库在编译时加入标记,在运行时才加载动态库,体积较小,而且可以提高代码的可复用度
二十一、一个进程可以创建多少线程,和什么有关
一个进程可以创建的线程数由可用虚拟空间和线程的栈大小共同决定
只要虚拟空间足够,那么新线程的创建就会成功
二十二、线程间什么是共享的,什么是不共享的
堆,全局变量,静态变量是共享的
栈与寄存器不是共享的
二十三、线程间通信机制
- 锁机制
- 信号量机制
- 信号机制
- volatile 全局变量
二十四、实例化一个对象要经过哪几个阶段
- 分配空间:对于全局对象,静态对象,以及分配在栈区域的对象,他们的内存分配在编译时已经确定了,堆上的对象,它们的内存分配是在运行时动态进行的
- 初始化虚函数表,虚函数指针
- 赋值,执行构造函数体
二十五、const和宏定义的区别
- 类型与安全检查不同,宏定义是字符串替换,const是常量的声明,需要在编译时进行类型检查
- 存储方式不同:宏定义是直接替换,不分配内存,存储在程序的代码段内;const常量需要进行内存分配,存储在数据段
- 定义域不同,宏定义不受定义域限制,const定义域为该函数体
- define 定义后可以通过 undef 取消,const常量定义后在定义域内永远有效
二十六、std::move
将左值转化为右值,右值仍为右值,常配合 std::forward使用实现完美转发
实现:
template <class T> typename tinySTL::remove_reference<T>::type&& move(T&& t) noexcept { using return_type = typename tinySTL::remove_reference<T>::type&&; return static_cast<return_type>(t); }
如果一个函数模板参数类型为T&&,其中T是需要推到的类型,那么T&&表示万能引用。
万能引用实际用到的技术是引用折叠。
引用折叠的规则可以概括为:
X& &、X& && 和 X&& & 折叠成 X&;
X&& && 折叠成 X&&。
如果T = X&是左值引用,则展开后得到了X& &&,根据上面的规则,最终得到X&。
如果T = X&&是右值引用,则展开后得到了X& &&,根据上面的规则,最终得到X&&。
所以,当t为左值或者左值引用时,进过引用折叠,得到的类型是T&。最后就是将左值转换为右值并返回了。