1.指针和引用的区别
1.指针有自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用;
4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
5.可以有const指针,但是没有const引用;
6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能改变;
9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露
2. 堆和栈的区别
1)栈,由编译器自动管理,无需我们手工控制;堆,申请释放工作由程序员控制
2)堆的生长方向是向上的,也就是向着内存地址增加的方向;栈的生长方向是向下的,是向着内存地址减小的方向增长。
3)对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题
4)一般来讲在 32 位系统下,堆内存可以达到4G的空间,但是对于栈来讲,一般都是有一定的空间大小的(2M)。
3. new与malloc区别
- new分配内存按照数据类型进行分配,malloc分配内存按照大小分配;
- new不仅分配一段内存,而且会==调用构造函数==,但是malloc则不会;delete销毁的时候会调用对象的析构函数,而free则不会;
- new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化;
- new是一个==操作符==可以重载,malloc是一个==库函数==;
- new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。
- new和new[]的区别,new[]一次分配所有内存,多次调用构造函数,分别搭配使用delete和delete[],同理,delete[]多次调用析构函数,销毁数组中的每个对象。
4. Struct和class的区别
默认的继承访问权限struct是public的,class是private的。
5.define 和 const 的区别(编译阶段、安全性、内存占用等)
define只是在预编译时进行字符置换,把程序中出现的字符串PI全部换成3.14159。在预编译之后,程序中不再有PI这个标识符。PI不是变量,没有类型,不占用存储单元,而且容易出错
6.在C++中const和static的用法(定义,用途)
● 请说一下static的作用
- 全局静态变量
内存中的位置:静态存储区,在整个程序运行期间一直存在。初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化)。
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。 - 局部静态变量
其余同全局静态变量。
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。 - 静态函数
数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突。 - 类的静态成员
静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。 - 类的静态函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,通过对象来引用。
● 说说const的作用,越多越好
- const修饰全局变量、const修饰局部变量为不可修改;
- const在*左侧,const就是用来修饰指针所指向的变量,即指针指向为常量const int * a;
- const在*右侧,const就是修饰指针本身,即指针本身是常量,指针本身不能改变,指针指向的内容可以改变 int * const a;
- const修饰引用做形参,提高效率;
- const修饰成员变量,必须在构造函数列表中初始化;
- const修饰成员函数,const成员函数不能修改类中的成员变量,只读不写。当const在函数名前面的时候修饰的是函数返回值
7.计算下面几个类的大小:
class A {};: sizeof(A) = 1;
class A { virtual Fun(){} };: sizeof(A) = 4(32位机器)/8(64位机器);
class A { static int a; };: sizeof(A) = 1;
class A { int a; };: sizeof(A) = 4;
class A { static int a; int b; };: sizeof(A) = 4;
● 构造与析构顺序徐
static E e2;
C c;
int main()
{
A *a = new A();
B b;
static D d;
delete a;
return 0;
}
class E constructor
class C constructor
class A constructor
class B constructor
class D constructor
class A destructor
class B destructor
class D destructor
class C destructor
class E destructor
8.请你来说一说重载和重写
重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中
重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数。
9.C ++内存管理(热门问题)
在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。
代码段: 包括只读存储区和文本区,其中只读存储区存储==字符串常量==,文本区存储==程序的机器代码==。
数据段:存储程序中==已初始化的全局变量和静态变量==
bss 段:存储==未初始化的全局变量和静态变量(局部+全局)==,以及所有被初始化为0的全局变量和静态变量。
映射区:存储==动态链接库==以及调用mmap函数进行的文件映射
栈:使用栈空间存储函数的返回地址、参数、局部变量、返回值
堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
10.结构体内存对齐方式和为什么要进行内存对齐?
内存对齐的原则
- 从0位置开始存储;
- 变量存储的起始位置是该变量大小的整数倍;
- 结构体总的大小是其最大元素的整数倍,不足的后面要补齐;
- 结构体中包含结构体,从结构体中最大元素的整数倍开始存;
- 如果加入pragma pack(n) ,取n和变量自身大小较小的一个。
原因
A 不是所有的硬件平台都能访问任意地址上的任意数据的;
B 未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问,==加快速度==
11 .C++中内存泄漏的几种情况
在类的构造函数和析构函数中没有匹配的调用new和delete函数
没有正确地清除嵌套的对象指针
在释放对象数组时在delete中没有使用方括号
缺少拷贝构造函数
没有将基类的析构函数定义为虚函数
野指针:指向被释放的或者访问受限内存的指针。
造成野指针的原因:
指针变量没有被初始化,指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL.
指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。
造成的后果
性能不良,内存会耗尽
12.内存泄漏如何排查
调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。
linux工具之检测内存泄漏-valgrind,功能强大,不仅仅是内存泄漏检测工具。
13.怎么有效解决内存泄漏问题?
智能指针。因为智能指针可以自动删除分配的内存。智能指针和普通指针类似,只是不需要手动释放指针,而是通过智能指针自己管理内存的释放
14. 多态的实现(和下个问题一起回答)
15. C++虚函数相关(虚函数表,虚函数指针),虚函数的实现原理(热门,重要)
- C++多态的实现?
多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定. - 动态多态的实现:
C++中会为每一个类内部生成一个虚函数表,该表中存放着虚函数的地址,每个对象的地址的首部都有一个虚指针,指向虚函数表。通过对象执行虚函数时,都会通过虚指针找到虚函数表,再从虚函数表中找到虚函数的地址进行调用。当子类在改写了父类中的虚函数时,子类的虚指针指向的虚函数表中的存放的虚函数指针不再是父类的虚函数地址了,而是子类所改写父类的虚函数地址。 - 虚函数的作用?
a) 虚函数用于实现多态,这点大家都能答上来
b) 但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。 -
子类析构函数必须是虚函数,构造函数不能是虚函数。析构和构造函数调用虚函数与普通函数一样
构造函数不能是虚函数,每个对象的虚函数表指针是在构造函数中初始化的,因为构造函数没执行完,所以虚函数表指针还没初始化好,构造函数的虚函数不起作用,所以构造函数不能是虚函数。如果有子类的话,析构函数必须是虚函数。否则析构子类类型的指针时,析构函数有可能不会被调用到。
16.引用是否能实现动态绑定,为什么引用可以实现
因为对象的类型是确定的,在编译期就确定了
指针或引用是在运行期根据他们绑定的具体对象确定。
17.深拷贝和浅拷贝的区别
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
18.调用拷贝构造函数的三种情况
1.当用类的对象去初始化同类的另一个对象时。
2.当函数的形参是类的对象,调用函数进行形参和实参结合时。
3.当函数的返回值是对象,函数执行完成返回调用者时。
19.说一说c++中四种cast转换
C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast
1、const_cast
用于将const变量转为非const
2、static_cast
用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;
3、dynamic_cast
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。
向上转换:指的是子类向基类的转换 向下转换:指的是基类向子类的转换
它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
4、reinterpret_cast
几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;
20.成员初始化列表的概念,为什么用成员初始化列表会快一些。
在类构造函数中,不在函数体内对变量赋值,而在参数列表后,跟一个冒号和初始化列表。
①:成员类型是没有默认构造函数的类。若没有提供显示初始化,则类创建对象时会调用默认构造函数,如果没有默认构造函数,则必须显示初始化。
②:const成员或者引用类型的成员。因为const对象或者引用类型只能初始化,不能赋值。
为什么成员初始化列表效率更高?
因为对于非内置类型,少了一次调用默认构造函数的过程。
21.C++11新特性
nullptr, auto自动类型推导,范围for循环,初始化列表, lambda表达式等
1.nullptr
C++11 引入了 nullptr 关键字,专门用来区分空指针和0
2.auto
auto用于从初始化表达式中推断出变量的数据类型。注意:声明的变量必须要初始化,否则编译器不能判断变量的类型。auto不能被声明为返回值,auto不能作为形参,auto不能被修饰为模板参数
3.decltype 关键字
decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似 ,decltype(表达式)
功能:编译器分析表达式并得到它的类型,却不实际计算表达式的值。
4.lambda表达式
lambda表达式是匿名函数,可以认为是一个可执行体functor
[捕获区](参数区){代码区};
• [a,&b] 其中 a 以复制捕获而 b 以引用捕获。
• [this] 以引用捕获当前对象( *this )
• [&] 以引用捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象,若存在
• [=] 以复制捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象,若存在
• [] 不捕获,大部分情况下不捕获就可以了
5. 右值引用与移动语义
左值和右值的区分标准在于能否获取地址。右值无法获取地址,但不表示其不可改变,当定义了右值的右值引用时就可以更改右值。
移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。
1.标准库move函数
根据右值引用的语法规则可知,不能将右值引用绑定到一个左值上,c++11引入右值引用,并且提供了move函数,用来获得绑定到左值上的右值引用。
Int &&iii = move(ii)
调用move之后,必须保证除了对ii复制或销毁它外,我们将不再使用它,在调用move之后,我们不能对移动源后对象做任何假设。
std::forward是有条件转换。只有在它的参数绑定到一个右值时,它才转换它的参数到一个右值。当参数绑定到左值时,转换后仍为左值。
6. 智能指针
智能指针:行为类似于指针的类对象 ,它的一种通用实现方法是采用引用计数的方法。
1.智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。
2.每次创建类的新对象时,初始化指针并将引用计数置为1;
3.当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
4.对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数+1;
5.调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
1.unique_ptr
unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)
2.shared_ptr
shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。注意避免循环引用.
3.weak_ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。