C++面筋

一、一个C++源文件从文本到可执行文件的过程?

通常来说一个C++源文件到可执行文件需要经过四个步骤:

(1)预处理,产生.ii文件

(2)编译,产生汇编文件(.s文件)

(3)汇编,产生目标文件(.o或.obj文件)

(4)链接,产生可行执行文件(.out或.exe文件)

(1)在预处理阶段主要执行以下操作:

a.对所有的“#define”进行宏展开;

b.处理所有的条件编译指令,比如“#if”,“#ifdef”,“#elif”,“#else”,“#endif”

c.处理“#include”指令,这个过程是递归的,也就是说被包含的文件可能还包含其他文件

d.删除所有的注释“//”和“/**/”

e.添加行号和文件标识

f.保留所有的“#pragma”编译器指令

经过预处理后的.ii文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.ii文件中。

(2)在编译阶段主要就是将预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件(.s文件)

(3)在汇编阶段,主要是将汇编代码转变成机器可以执行的代码,每一个汇编语句几乎都对应一条机器指令。最终产生目标文件(.o或.obj文件)。

(4)链接的过程主要包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)

二、线程和进程的区别与联系

进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。

线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。

线程与进程的区别主要体现在5个方面:

  1. 定义方面:进程是程序在某个数据集合上的一次运行活动;线程是进程中的一个执行路径。(进程可以创建多个线程)
  2. 角色方面:在支持线程机制的系统中,进程是系统资源分配的单位,线程是CPU调度的单位。
  3. 资源共享方面:进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。
  4. 独立性方面:进程有自己独立的地址空间,而线程没有,线程必须依赖于进程而存在。
  5. 开销方面。进程切换的开销较大。线程相对较小

三、数组和指针的区别

指针可以随时指向任意类型的内存块,而数组可以在静态存储区被创建。数组和指针的区别主要体现在以下两个方面

1.修改内容的不同,数组可以通过下标操作对元素值进行修改,而指针不行。

2.所占字节数不同,sizeof(p)得到的是一个指针变量的字节数,而不是p所指的内存容量。

四、内存分配

bss段:bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。

             bss是英文Block Started by Symbol的简称。

             bss段属于静态内存分配。 

data段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。

          数据段属于静态内存分配。 

text段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。

         这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。

          在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。 

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。

           当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);

           当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

栈(stack): 栈又称堆栈,是用户存放程序临时创建的局部变量,

           也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。

五、为什么通常来说栈比堆要快?

首先, 栈是本着LIFO原则的存储机制, 对栈数据的定位相对比较快速, 而堆则是随机分配的空间, 处理的数据比较多, 无论如何, 至少要两次定位. 

其次, 栈是由CPU提供指令支持的, 在指令的处理速度上, 对栈数据进行处理的速度自然要优于由操作系统支持的堆数据. 

再者, 栈是在一级缓存中做缓存的, 而堆则是在二级缓存中, 两者在硬件性能上差异巨大. 

最后, 各语言对栈的优化支持要优于对堆的支持, 比如swift语言中, 三个字及以内的struct结构, 可以在栈中内联, 从而达到更快的处理速度.。

六、TCP三次握手和四次挥手

TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,如图1所示。

(1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。

(2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。

(3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据。

关闭连接采用四次挥手:

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。 

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。 

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。 

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。 

七、C++中Overload,Override,Overwrite的区别?

Overload(重载):在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型、顺序不同),即函数重载。
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。

Override(覆盖):是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

Overwrite(重写):是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

八、C++ 虚函数的实现

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

九、AVL树、红黑树、B/B+树和Trie树的比较

AVL树、红黑树、B/B+树和Trie树

stl中的map和set用的是红黑树。B+树一般用于数据库系统中做索引,Trie树用来做字符匹配,Windows对进程地址空间的管理用到了AVL树。

十、 C++编译器自动为类产生的四个缺省函数是什么?
默认构造函数、析构函数、拷贝构造函数、赋值函数

十一、请简述C/C++语言中栈空间和堆空间的主要区别(栈为什么比堆快)?

  • 1申请方式
    stack: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
    heap: 需要程序员自己申请,并指明大小,在c中malloc函数 ,在C++中用new运算符
  • 2 申请后系统的响应
    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
  • 3申请大小的限制
    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
  • 4申请效率的比较:
    栈由系统自动分配,速度较快。但程序员是无法控制的。
    堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
  • 5堆和栈中的存储内容
    栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
  • 6存取效率的比较
    char s1[] = "aaaaaaaaaaaaaaa";
    char *s2 = "bbbbbbbbbbbbbbbbb";
    aaaaaaaaaaa是在运行时刻赋值的;
    而bbbbbbbbbbb是在编译时就确定的

十二、memcpy和memmove的区别

  • memcpy不考虑内存重叠问题,效率高,在应用程序方面(已知两块内存不会重叠),memcpy更合适。
  • memmove考虑内存重叠问题
    • 在dest头部在src范围内时:src的尾部(即dest的头部)在复制过程中被src修改,导致出错
    • memmove加了一次判断判,次情况时进行逆序赋值

十三、叙述一下static对变量、对函数、对内函数内部的变量的各种使用的区别

  • 修饰全局变量,变量被称为全局静态变量,存储在静态区
    • 目的:限定作用域为当前文件,其他文件不可访问该变量
  • 修饰局部变量,即局部静态变量,存储在静态区
    • 目的:函数结束时不销毁,使得下次调用时不需要再次开辟空间,同时保留原内容。
    • 虽然生命周期为整个进程,但仍不能被其他函数、变量访问
    • 局部静态变量不可重入,多线程时要注意线程安全。
  • 修饰函数
    • 使的其他源文件不可访问该函数,达到类似C++ private的效果

十四、C++中引用与指针的区别

引用与指针有着相同的地方,即指针指向一块内存,他的内容是所指内存的地址,引用是某块内存的别名。但是两者并非完全相同,他们之间也存在着差别,具体表现在以下几个方面:

1)从本质上讲,指针是存放变量地址的一个变量,在逻辑上是独立的,他可以被改变,即其所指向的地址可以被改变,其指向的地址中存放的数据也可以被改变。而引用只是一个别名,他必须被初始化,并且引用的对象在其整个生命周期是不能被改变的,即自始至终只能依附于同一个变量。

2)作为参数传递时,两者不同。指针是值传递,引用是址传递。

3)引用使用时不需要解引用,而指针需要解引用。

4)引用不可以为空,而指针可以为空

5)对引用使用sizeof得到的是变量的大小,而对指针进行sizeof操作得到的是指针本身大小。

十五、C++11新特性

final: 显式禁止类被继承

default:显式实现默认构造/析构函数

nullptr:强类型的空指针

auto:自动类型推导

constexpr:允许将变量声明为constexpr类型让编译器来验证变量的值是否是一个常量表达式

noexpect:声明函数不能抛出任何异常,若定义了noexpect的函数抛出异常,则程序终止

override:显式的在派生类中声明哪些虚函数需要重写,若未重写则编译器会报错

lambda表达式: 就地编写匿名函数(闭包)

可变参数模板:可以在模板参数列表里书写任意数量类型名

模板别名:使用C++的模板别名特性可以简化容器的类型声明

模板元编程:在编译期计算类型,根据模板参数推导返回值。

十六、可变参数模板

可变参数模板是模板编程时,模板参数的个数可变的情况。C++11之前,模板在声明时必须有固定数量的参数模板。C++11允许模板定义有任意类型任意数量的参数模板。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(面筋)