个人C++学习笔记

5 篇文章 0 订阅

关注本人公众号有更多学习资料!

微信公众号同名

C++面试基本上自己搞清楚 C++基础、OOP思想、STL库、内存管理、设计模式 有这些知识储备面试被问到C++这块应该就没什么问题了 。

C++基础

说实话,就一个C++基础,要是掌握的不是很好的话,就能被面试官给问到s,它重不重要,你品你细品。

直接上干货!

1、指针和引用的区别

  • 指针是一个变量,存储的是一个地址,指向内存的一个存储单元;引用跟原来的变量实质上是同一个东西,是变量的别名。
  • 引用不可以为空,被创建时必须初始化;指针可以是空值,可以在任何时候被初始化。
  • 可以有const指针,但是没有const引用。
  • 指针可以有多级,但是引用只有一级。
  • 指针在初始化后可以改变,只想其他的存储单元,而引用在进行初始化后就不会再改变。
  • "sizeof引用"得到的是所指向的变量的大小,而"sizeof指针"得到的是指针本身的大小。
  • 如果返回动态内存分配的对象或者内存,必须使用引用,指针可能引起内存泄漏。

2、堆和栈的区别

  • 申请方式:栈是系统自动分配,堆是程序员主动申请。
  • 申请后系统响应:分配栈空间,如果剩余空间大于申请空间则分配成功,否则分配失败栈溢出;申请堆空间,堆在内存中呈现的方式类似于链表(记录空闲地址空间的链表),在链表上寻找第一个大于申请空间的节点分配给程序,将该节点从链表中删除,大多数系统中该块空间的首地址存放的是本次分配空间的大小,便于释放,将该块空间上的剩余空间再次连接在空闲链表上。
  • 栈在内存中是连续的一块空间(向低地址扩展)最大容量是系统预定好的,堆在内存中的空间(向高地址扩展)是不连续的。
  • 申请效率:栈是有系统自动分配,申请效率高,但程序员无法控制;堆是由程序员主动申请,效率低,使用起来方便但是容易产生碎片。
  • 存放的内容:栈中存放的是局部变量,函数的参数;堆中存放的内容由程序员控制。

3、new和delete是如何实现的,new与malloc的异同处
在使用的时候 new,delete 搭配使用,malloc 和 free 搭配使用。

属性:malloc/free 是库函数,需要头文件的支持;

new/delete 是关键字,需要编译器的支持参数:

new 申请空间时,无需指定分配空间的大小,编译器会根据类型自行计算;

malloc 在申请空间时,需要确定所申请空间的大小返回值:

new 申请空间时,返回的类型是对象的指针类型,无需强制类型转换,符合类型安全的操作符;

malloc 申请空间时,返回的是 void* 类型,需要进行强制类型的转换,转换为对象类型的指针分配失败:new 分配失败时,会抛出 bad_alloc 异常,malloc 分配失败时返回空指针

重载:new/delete 支持重载,malloc/free 不能进行重载

自定义类型实现:new 首先调用 operator new 函数申请空间(底层通过 malloc 实现),然后调用构造函数进行初始化,最后返回自定义类型的指针;delete 首先调用析构函数,然后调用 operator delete 释放空间(底层通过 free 实现)。malloc/free 无法进行自定义类型的对象的构造和析构

内存区域:new 操作符从自由存储区上为对象动态分配内存,而 malloc 函数从堆上动态分配内存。(自由存储区不等于堆)

4、C和C++的区别
C 是面向过程的编程,特点是函数;C++ 是面向对象的编程,特点是类。(特性)

C 主要用在嵌入式开发、驱动开发和硬件直接打交道的领域;C++ 可以用于应用层的开发、用户界面开发等和操作系统直接打交道的领域。(应用领域)

C++ 继承了C的底层操作特性,增加了面向对象的机制,增加了泛型编程、异常处理、运算符重载,还增加了命名空间,避免了命名冲突。(相较于 C 的升级)

5、C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制)

二者在语言特性上有很大的区别:
(1)指针:C++ 可以直接操作指针,容易产生内存泄漏以及非法指针引用的问题;JAVA 并不是没有指针,虚拟机(JVM)内部还是使用了指针,只是编程人员不能直接使用指针,不能通过指针来直接访问内存,并且 JAVA 增加了内存管理机制。
(2)多重继承:C++ 支持多重继承,允许多个父类派生一个类,虽然功能很强大,但是如果使用的不当会造成很多问题,例如:菱形继承;JAVA 不支持多重继承,但允许一个类可以继承多个接口,可以实现 C++ 多重继承的功能,但又避免了多重继承带来的许多不便。
(3)数据类型和类:C++ 可以将变量或函数定义成全局,但是JAVA是完全面向对象的语言,除了基本的数据类型之外,其他的都作为类的对象,包括数组。

垃圾回收:

(1)JAVA 语言一个显著的特点就是垃圾回收机制,编程人员无需考虑内存管理的问题,可以有效的防止内存泄漏,有效的使用空闲的内存。

(2)JAVA 所有的对象都是用 new 操作符建立在内存堆栈上,类似于 C++ 中的 new 操作符,但是当要释放该申请的内存空间时,JAVA 自动进行内存回收操作,C++ 需要程序员自己释放内存空间,并且 JAVA 中的内存回收是以线程的方式在后台运行的,利用空闲时间。

应用场景:

(1)java 运行在虚拟机上,和开发平台无关,C++ 直接编译成可执行文件,是否跨平台在于用到的编译器的特性是否有多平台的支持。
(2)C++ 可以直接编译成可执行文件,运行效率比 JAVA 高。
(3)JAVA 主要用来开发 web 应用。
(4)C++ 主要用在嵌入式开发、网络、并发编程的方面。

6、auto_ptr 可以由shared_ptr或unique_ptr替换
(1)赋值语句如果不是出于这个原因,他们将所有移动语义的新东西添加到auto_ptr而不是弃用它;赋值语义是最不受欢迎的特性。
(2)auto_ptr因为赋值语义不清晰还会导致传入函数的拷贝后丢失资源
(3)auto_ptr不能在STL容器中使用,因为它有一个不符合容器CopyConstructible要求的复制构造函数;unique_ptr不实现复制构造函数,因此容器使用替代方法,unique_ptr可以在容器中使用,对于std算法比shared_ptr更快。如果两个auto_ptr指针指向同一个对象时,当该对象的生存周期结束后,系统会调用析构函数,这样导致的结果是程序对同一个对象删除了2次,造成程序出错。
那么该如何解决这个问题呢?这个问题看似很难解决,其实解决的方法却有很多:
a、通过定义赋值运算符,实现深拷贝,从而两个指针指向不同的两个对象,其中一个对象是另一个对象的副本。
b、将对象的删除所有权设置成唯一。即只允许一个对象拥有删除对象的权力,其他指针只能指向该对象,不享有删除对象的权力。auto_ptr、unique_ptr都是用这样的机制,但是后者比前者更加的严格。
c、采用引用计数的方法。当有一个一个指针指向对象时,引用计数加1,当指向该对象的指针销毁时,引用计数减1,当且仅当最后一个指向该对象的指针销毁时,也就是引用计数为0时,对象才能够被删除。智能指针shared_ptr就是采用该机制来实现的。auto_ptr导致的问题在于,当所有权转让之后,原来的指针被用来访问原来指向的对象,当程序访问该指针指向的内容时,会发现这是一个悬空指针,程序就会出错,但是如果换成shared_ptr程序就能正常运行。

7、静态联编和动态联编
静态联编是指在编译阶段就将函数实现和函数调用关联起来,也叫早绑定。对函数的选择是基于指向对象的指针(或者引用)的类型,C语言中,所有的联编都是静态联编,并且任何一种编译器都支持静态联编。

动态联编是指在程序执行的时候才将函数实现和函数调用关联,也叫运行时绑定或者晚绑定。对函数的选择不是基于指针或者引用,而是基于对象类型,不同的对象类型将做出不同的编译结果。涉及到多态和虚拟函数就必须要用动态联编。

8、多态
多态性是指允许不同的子类型的对象对同一消息做出不同的响应。

多态性简单的概括为“一个接口,多种方法”,C++多态性主要是通过虚函数实现的,虚函数允许子类重写。

多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。

函数的调用在编译器编译期间就乐意确定函数的地址,并产生代码,是静态的,地址是早绑定。函数调用的地址不能再编译器期间确定,需要在运行时才确定,这就属于晚绑定。

编译时多态性:通过函数重载和模板实现。

运行时多态性:通过虚函数实现。

9、虚函数
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

虚函数的好处就是可以定义一个基类指针,其指向一个继承类,当通过基类的指针去调用函数时,可以在运行时决定该调用基类的函数还是继承类的函数。虚函数是实现多态(动态绑定)接口函数的基础。

10、大小端
大端对齐:数据的低位存储在内存的高地址上,数据高位存储存储在内存的低地址上(字符串存储)。

小端对齐:数据的低位存储在内存的低地址上,数据高位存储存储在内存的高地址上。

11、++i和i++
赋值顺序不同:++i是先加后赋值;i++是先赋值后加。

效率不同:++i比i++效率要高一些。

i++不能作为左值,而++i可以。

12、全局变量
全局变量初始化而且初始值不为0,那么这样的全局变量放在内存的 .data 段;全局变量初始化为0或者未初始化,这样的全局变量放在 .bss 段。

静态数据区:常量(一般常量、字符串常量)、未初始化的全局变量和静态变量、已初始化的全局变量和静态变量。

13、数组和链表的区别
数组:

逻辑结构:

数组在内存中连续。

使用数组之前,必须实现固定数组长度,不支持动态改变数组大小。

数组元素增加时,有可能会数组越界。

数组元素减少时,会造成内存浪费。

数组增删时需要移动其他元素。

内存结构:数组从栈上分配内存,使用方便,但是自由度小。

访问效率:数组在内存中顺序存储,可通过下标访问,访问效率高。

越界问题:数组的大小是固定的,所以存在访问越界的风险。

使用场景:

空间:数组的存储空间时栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据。

时间:数组访问效率高。当线性表的操作主要是进行查找,很少插入和删除时,宜采用数组结构。

链表:

逻辑结构:
链表采用动态内存分配的方式,在内存中不连续
支持动态增加或者删除元素。
需要时可以使用malloc或者new来申请内存,不用是使用free或者delete来释放内存。

内存结构:链表从对上分配内存,自由度大,但是要注意内存泄漏。

访问效率:链表访问效率低,如果想要访问某个元素,需要从头遍历。

越界问题:只要可以申请得到链表空间,链表就无越界风险。

使用场景:

空间:链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储。

时间:链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构。

14、关键字
const:

C中:const就是当作一个变量来编译生成指令的。const修饰的量,可以不用初始化,不叫常量,叫做常变量。

C++中:所有出现const常量名字的地方,都被常量的初始化替换了!const必须初始化,叫常量。

const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数。

const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的。

const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。

在C++中,只有被声明为const的成员函数才能被一个const类对象调用。

static:

static函数,主要用来限制作用域,只限本源文件有效。

修饰全局变量,限定在本源文件有效。

全局变量的空间的开辟在静态区,加static修饰后仍然在静态区。唯一有变化的就是作用域,加static作用域限定在本文件,不加static的全局变量作用域为整个源程序(只需在别的源文件extern声明)。

修饰局部变量,延长生命周期。

static局部变量的存储在静态存储区,生命周期直到程序结束,它的值始终存在,static局部变量只被初始化一次,下次使用的依然是上一次的值。它的作用域没有改变,只在声明的语句块内能够访问。

可以避免重命名的问题。

内核代码这么多,很容易就遇到了重命名问题,如果声明为内部函数,即使出现重命名,编译器也不会报错。

可以提高代码的健壮性。

volatile:

如果一个变量使用volatile修饰,那么当使用它的值时,一定会从内存中读出。

mutable:

在C++中,mutable是为了突破const的限制而设置的。可以用来修饰一个类的成员变量。被mutable修饰的变量,将永远处于可变的状态,即使是const函数中也可以改变这个变量的值。

15、程序运行的顺序
预编译
(1)宏定义指令,如# define Name TokenString,#undef等。对于前一个伪指令,预编译所要作得的是将程序中的所有Name用TokenString替换,但作为字符串常量的Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。
(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。
(3)头文件包含指令,如#include “FileName"或者#include 等。在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C++源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
(4)特殊符号,预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

预编译程序所完成的基本上是对源程序的"替代"工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。

编译
经过预编译得到的输出文件中,将只有常量。如数字、字符串、变量的定义,以及C++语言的关键字,如main,if,else,for,while,{,},+,-,*,\,等等。预编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

汇编
汇编过程实际上是把汇编语言代码编译成目标机器指令的过程。对于被翻译系统处理的每一个C++语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

链接
由汇编程序生成的目标文件并不能立即执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数等等。所有的这些问题都要经过链接程序的处理才能得到解决。

16、深浅拷贝、写实拷贝
深拷贝:不仅拷贝指针,对指针指向的内容进行拷贝,深拷贝后两个指针指向两个不同的地址。

浅拷贝:只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。

写时拷贝:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟地址结构,但是不为这段分配物理内存,它们共享父进程的物理空间,当父进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

17、纯虚函数和虚函数的区别?
虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。

虚函数可以直接被使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类只有声明而没有定义。

虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。

虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。

虚函数和纯虚函数的定义中不能有static标识符,被static修饰的函数在编译时候要求前期绑定,然而虚函数却是动态绑定,而且被两者修饰的函数声明周期也不一样。

虚函数必须实现,如果不实现,编译器将报错。

虚函数,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。

实现了纯虚函数的子类,改纯虚函数在子类中就编程了虚函数,子类的子类可以覆盖。该虚函数,由多态方式调用的时候动态绑定。

虚函数是C++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的。

包含纯虚函数的类叫做抽象类(也称为接口类),抽象类不能实例化处对象。

18、智能指针
unique_ptr

表示互斥权,拷贝构造函数和拷贝赋值运算符都delete了。

一个unique_ptr拥有一个对象,在某一时刻,只能由一个unique_ptr指向一个给定的对象。当unique_ptr被销毁,所致的对象也被销毁。

unique_ptr不能拷贝,不能赋值,可以移动

为动态分配的内存提供异常安全,unique_ptr可以理解为一个简单的指针(指向一个对象)或一对指针(包含释放器deleter的情况)

将动态分配内存的所有权传递给函数

从函数返回动态分配的内存

shared_ptr
shared_ptr表示共享所有权,可以共享一个对象。当两段代码都没有独享所有权(负责销毁对象)时,可以使用shared_ptr。shared_ptr是一种计数指针,当计数变为0时释放所指向的对象。

其实就是一个指针套上了释放器,套上了计数器,拷贝的时候增加了引用,赋值也增加了引用,相应的也会有递减了引用计数

weak_ptr
是一种不控制所指向对象生存期的智能指针,指向由一个shared_ptr管理的对象

weak_ptr,不影响shared_ptr的引用计数。一旦shared_ptr被销毁,那么对象也会被销毁,即使weak_ptr还指向这个对象,这个对象也会被销毁。

19、函数重载,重写,同名隐藏的对比
重载:两个或多个函数在同一作用域函数名相同、参数列表不同

重写:两个函数分别在基类和派生类的作用域中,函数名、参数、返回值类型都必须相同(协变除外),基类虚函数必须为虚函数,派生类函数最好也为虚函数。

同名隐藏:两个函数分别在基类和派生类的作用域中函数名相同,基类和派生类同名函数不是重写就是同名隐藏。

20、抽象类
在虚函数后面加上 = 0,该虚函数就为纯虚函数。包含纯虚函数的类叫做抽象类(也称为接口类),抽象类不能实例化出对象。 派生类继承之后也不能实例化出对象,只有重写虚函数,派生类才能实例化为对象。纯虚函数规范了派生类必须重写,接口继承。

实现继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。

接口继承:虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要将函数定义为虚函数。

21、函数指针
函数指针指向的是函数而非对象;和其他指针一样,函数指针指向某种特定类型。

函数的类型由它的返回类型和形参类型决定,与函数名无关。

22、指针常量和常量指针
指针常量:指针值不能改变的指针,只能指向变量,但可以修改指向的实体变量的值。也就是说指针指向的那个实体变量,一旦指向它就不能再指向其他实体变量,但是这个实体变量是可以改变的。

指针变量:指针值可以改变的指针,只是指向变量。

常量指针:指针指向一个实体常量,这个实体常量所放置的常数是不能改变的,但是这个指针可以指向不同的实体常量

常量指针常量:可以理解为是2和3两个约束条件的集合,首先指针指向的是一个常量,然后呢,这个常量的值也是不能改变的。

以上这些就是面试高频出现的题目,真心建议收藏!!! 奈何字数太多容易对大家造成视觉疲劳,今天就罗列出来这些C++基础重点,后面的一定也不会鸽大家!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值