C++面试题

一、什么是智能指针
智能指针就是用一个对象维护另一个内存空间,当我们new出一块空间地址后,就不用手动的去delete它,而是交给智能指针帮我们做这件事。简单的说智能指针就是自动的帮我们释放new的内存,而不用手动释放。智能指针实现的原理就是通过调用析构函数来间接释放new的内存。

C++11 中智能指针包括以下三种:

  • 共享指针(shared_ptr):资源可以被多个指针共享,使用计数机制表明资源被几个指针共享。通过 use_count() 查看资源的所有者的个数,可以通过 unique_ptr、weak_ptr 来构造,调用 release() 释放资源的所有权,计数减一,当计数减为 0 时,会自动释放内存空间,从而避免了内存泄漏。
  • 独占指针(unique_ptr):独享所有权的智能指针,资源只能被一个指针占有,该指针不能拷贝构造和赋值。但可以进行移动构造和移动赋值构造(调用 move() 函数),即一个 unique_ptr 对象赋值给另一个 unique_ptr 对象,可以通过该方法进行赋值。
  • 弱指针(weak_ptr):指向 share_ptr 指向的对象,能够解决由shared_ptr带来的循环引用问题。
    智能指针的实现原理: 计数原理。

二、static
静态成员变量:

  1. 静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和private、public、protected 访问规则。
  2. 静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。
  3. 静态成员变量可以作为成员函数的参数,而普通成员变量不可以。
  4. 静态数据成员的类型可以是所属类的类型,而普通数据成员的类型只能是该类类型的指针或引用。

静态成员函数:

  1. 静态成员函数不能调用非静态成员变量或者非静态成员函数,因为静态成员函数没有 this 指针。静态成员函数做为类作用域的全局函数。
  2. 静态成员函数不能声明成虚函数(virtual)、const 函数和 volatile 函数。

三、虚函数的实现机制
实现机制:虚函数通过虚函数表来实现。虚函数的地址保存在虚函数表中,在类的对象所在的内存空间中,保存了指向虚函数表的指针(称为“虚表指针”),通过虚表指针可以找到类对应的虚函数表。虚函数表解决了基类和派生类的继承问题和类中成员函数的覆盖问题,当用基类的指针来操作一个派生类的时候,这张虚函数表就指明了实际应该调用的函数。
虚函数表相关知识点:

  • 虚函数表存放的内容:类的虚函数的地址。
  • 虚函数表建立的时间:编译阶段,即程序的编译过程中会将虚函数的地址放在虚函数表中。
  • 虚表指针保存的位置:虚表指针存放在对象的内存空间中最前面的位置,这是为了保证正确取到虚函数的偏移量。
    注:虚函数表和类绑定,虚表指针和对象绑定。即类的不同的对象的虚函数表是一样的,但是每个对象都有自己的虚表指针,来指向类的虚函数表。

四、多态类中的虚函数表建立在什么阶段?
答案与解析:是在编译时创建的的虚函数表,编译器对每个包含虚函数的类创建一个虚函数表(vtable),在vtable中,放置这个类的虚函数地址。编译器另外还为每个特定类的对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。
在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。

五、构造函数、析构函数是否需要定义成虚函数?为什么?

构造函数一般不定义为虚函数,原因:

  • 从存储空间的角度考虑:构造函数是在实例化对象的时候进行调用,如果此时将构造函数定义成虚函数,需要通过访问该对象所在的内存空间才能进行虚函数的调用(因为需要通过指向虚函数表的指针调用虚函数表,虽然虚函数表在编译时就有了,但是没有虚函数的指针,虚函数的指针只有在创建了对象才有),但是此时该对象还未创建,便无法进行虚函数的调用。所以构造函数不能定义成虚函数。
  • 从使用的角度考虑:虚函数是基类的指针指向派生类的对象时,通过该指针实现对派生类的虚函数的调用,构造函数是在创建对象时自动调用的。
  • 从实现上考虑:虚函数表是在创建对象之后才有的,因此不能定义成虚函数。
  • 从类型上考虑:在创建对象时需要明确其类型。
    析构函数一般定义成虚函数,原因:
    析构函数定义成虚函数是为了防止内存泄漏,因为当基类的指针或者引用指向或绑定到派生类的对象时,如果未将基类的析构函数定义成虚函数,会调用基类的析构函数,那么只能将基类的成员所占的空间释放掉,派生类中特有的就会无法释放内存空间导致内存泄漏。

六、空类占多少字节?C++ 编译器会给一个空类自动生成哪些函数?
空类声明时编译器不会生成任何成员函数:
对于空类,声明编译器不会生成任何的成员函数,只会生成 1 个字节的占位符。
空类定义时编译器会生成 6 个成员函数:

当空类 A 定义对象时,sizeof(A) 仍是为 1,但编译器会生成 6 个成员函数:缺省的构造函数、拷贝构造函数、析构函数、赋值运算符、两个取址运算符。

七、实例化一个对象需要哪几个阶段

1.分配空间
创建类对象首先要为该对象分配内存空间。不同的对象,为其分配空间的时机未必相同。全局对象、静态对象、分配在栈区域内的对象,在编译阶段进行内存分配;存储在堆空间的对象,是在运行阶段进行内存分配。
2.初始化
首先明确一点:初始化不同于赋值。初始化发生在赋值之前,初始化随对象的创建而进行,而赋值是在对象创建好后,为其赋上相应的值。这一点可以联想下上一个问题中提到:初始化列表先于构造函数体内的代码执行,初始化列表执行的是数据成员的初始化过程,这个可以从成员对象的构造函数被调用看的出来。
3.赋值
对象初始化完成后,可以对其进行赋值。对于一个类的对象,其成员变量的赋值过程发生在类的构造函数的函数体中。当执行完该函数体,也就意味着类对象的实例化过程完成了。(总结:构造函数实现了对象的初始化和赋值两个过程,对象的初始化是通过初始化列表来完成,而对象的赋值则才是通过构造函数的函数体来实现。)
注:对于拥有虚函数的类的对象,还需要给虚表指针赋值。

没有继承关系的类,分配完内存后,首先给虚表指针赋值,然后再列表初始化以及执行构造函数的函数体,即上述中的初始化和赋值操作。
有继承关系的类,分配内存之后,首先进行基类的构造过程,然后给该派生类的虚表指针赋值,最后再列表初始化以及执行构造函数的函数体,即上述中的初始化和赋值操作。

八、静态绑定和动态绑定是怎么实现的?
静态类型和动态类型:

  • 静态类型:变量在声明时的类型,是在编译阶段确定的。静态类型不能更改。
  • 动态类型:目前所指对象的类型,是在运行阶段确定的。动态类型可以更改。
    静态绑定和动态绑定:
  • 静态绑定是指程序在 编译阶段 确定对象的类型(静态类型)。
  • 动态绑定是指程序在 运行阶段 确定对象的类型(动态类型)。
    静态绑定和动态绑定的区别:
  • 发生的时期不同:如上。
  • 对象的静态类型不能更改,动态类型可以更改。

九、深拷贝和浅拷贝的区别
如果一个类拥有资源,该类的对象进行复制时,如果资源重新分配,就是深拷贝,否则就是浅拷贝。

  • 深拷贝:该对象和原对象占用不同的内存空间,既拷贝存储在栈空间中的内容,又拷贝存储在堆空间中的内容。
  • 浅拷贝:该对象和原对象占用同一块内存空间,仅拷贝类中位于栈空间中的内容。
    当类的成员变量中有指针变量时,最好使用深拷
    贝。因为当两个对象指向同一块内存空间,如果使用浅拷贝,当其中一个对象的删除后,该块内存空间就会被释放,另外一个对象指向的就是垃圾内存。

十、指针和引用的区别?

  • 指针所指向的内存空间在程序运行过程中可以改变,而引用所绑定的对象一旦绑定就不能改变。(是否可变)
  • 指针本身在内存中占有内存空间,引用相当于变量的别名,在内存中不占内存空间。(是否占内存)
  • 指针可以为空,但是引用必须绑定对象。(是否可为空)
  • 指针可以有多级,但是引用只能一级。(是否能为多级)

十一、指针和引用的区别?

  • 指针所指向的内存空间在程序运行过程中可以改变,而引用所绑定的对象一旦绑定就不能改变。(是否可变)
  • 指针本身在内存中占有内存空间,引用相当于变量的别名,在内存中不占内存空间。(是否占内存)
  • 指针可以为空,但是引用必须绑定对象。(是否可为空)
  • 指针可以有多级,但是引用只能一级。(是否能为多级)

十二、常量指针和指针常量
常量指针:本身是一个指针,只不过其指向一个常量。即其指向是可以改变的,但是指针指向的内容不可改变。
const int * p;
int const * p;

指针常量:指针本身是个不可以改变指向,但是其指向的内容可以改变。
const int var;
int * const c_p = &var;

十三、数组指针和指针数组
数组指针:本身是一个指针,其指向一个数组。
int (*p)[10]

指针数组:本身是一个数组,其数组元素是一个指针。
int *a[10]

十四、函数指针和指针函数
函数指针:本身是一个指针,其指向一个函数。
指针函数:本身是一个函数,其返回值是一个指针类型。

十五、include " " 和 <> 的区别

  • 查找文件的位置:include<文件名> 在标准库头文件所在的目录中查找,如果没有,再到当前源文件所在目录下查找;#include"文件名" 在当前源文件所在目录中进行查找,如果没有;再到系统目录中查找。
  • 使用习惯:对于标准库中的头文件常用 include<文件名>,对于自己定义的头文件,常用 #include"文件名"

十六、了解哪些设计模式?
设计模式含义:
设计模式是一套被反复使用、多数人知晓、经过分类编目、代码设计经验的总结。

设计模式有 6 大设计原则:

  • 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
  • 开放封闭原则:软件实体可以扩展,但是不可修改。即面对需求,对程序的改动可以通过增加代码来完成,但是不能改动现有的代码。
  • 里氏代换原则:一个软件实体如果使用的是一个基类,那么一定适用于其派生类。即在软件中,把基类替换成派生类,程序的行为没有变化。
  • 依赖倒转原则:抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程,不要对实现编程。
  • 迪米特原则:如果两个类不直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某个方法的话,可以通过第三个类转发这个调用。
  • 接口隔离原则:每个接口中不存在派生类用不到却必须实现的方法,如果不然,就要将接口拆分,使用多个隔离的接口。
    设计模式分为三类:
  • 创造型模式:单例模式、工厂模式、建造者模式、原型模式
  • 结构型模式:适配器模式、桥接模式、外观模式、组合模式、装饰模式、享元模式、代理模式
  • 行为型模式:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式
    下面介绍常见的几种设计模式:
  • 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 工厂模式:包括简单工厂模式、抽象工厂模式、工厂方法模式
    • 简单工厂模式:主要用于创建对象。用一个工厂来根据输入的条件产生不同的类,然后根据不同类的虚函数得到不同的结果。
    • 工厂方法模式:修正了简单工厂模式中不遵守开放封闭原则。把选择判断移到了客户端去实现,如果想添加新功能就不用修改原来的类,直接修改客户端即可。
    • 抽象工厂模式:定义了一个创建一系列相关或相互依赖的接口,而无需指定他们的具体类。
  • 观察者模式:定义了一种一对多的关系,让多个观察对象同时监听一个主题对象,主题对象发生变化时,会通知所有的观察者,使他们能够更新自己。
  • 装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成派生类更为灵活。

十七、三大特性
C++的三大特性,分别为封装、继承以及多态。
封装

  1. 封装的概念:封装指的是把数据以及实际操作代码集合起来放到一个对象中,并且尽可能隐藏对象内部的具体实现。此时我们的对象好像一个不透明的黑盒子,我们从外面看不到里面的具体情况,并且也不能在外面去随意修改我们的内部成员函数以及代码,对象中提供了对外的接口,也就是我们的成员函数,用来提供给外部调用以此来访问对象内部的成员函数。我们一般把对象的特性称为成员变量,把对象的行为称为成员函数,我们提供给对外的接口,用来内外通信。一般我们对象内部会标注各个成员的访问权限,一般把数据成员声明为私有的,避免被外界随意更改,在有继承关系的类中,可以把部分成员数据声明为保护成员,以便能够被子类所继承,另外所提供的外部接口声明为公有的。
  2. 封装的具体含义:
  • 一方面指的是我们的数据和操作代码封装在对象里面,各个对象之间相互独立,互不干扰,此时对象之间具有高度内聚以及较低的耦合,这也是我们程序设计的一大原则,即高内裤低耦合。
  • 另一方面指的是我们隐藏一些数据和操作代码,即隐藏我们的内部实现,只留下少量的接口与外界联系,接收外界的消息。
    3. 封装的好处:将使用者和设计相互分离开来,我们的使用者不必关心我们对象内 部的具体实现,只需要调用设计者提供给我们的共有函数来访问对象的数据成员,丰 庄再一定程度上降低了我们操作对象的复杂度,并提供了代码重用性,从而减轻了开发一个软件系统的难度。

继承
继承使得我们的代码能够重用,而不必再次声明一个大部分功能相同的类。使用继承机制我们只需要在派生类中继承我们的一个基类,便能拥有基类中的一些数据和成员方法,同时派生类自己本身也可以声明自己的数据和方法。这也与我们软件重用思想是相一致的,但是我们在使用继承的同时,也要考虑到基类中的任何变换都可能影响到派生类,所以我们可以使用对象组合,降低这种依赖关系。这与我们的软件设计原则中的合成复用原则是相一致的。

多态
多态指的是不同对象对于同一消息所做出的不同反应。多态是相对于继承来说的,多态可分为静态多态以及动态多态。静态多态是在编译阶段所绑定的,而我们的动态多态则是在运行阶段所动态绑定的。
静态多态
静态多态的实现形式主要为函数重载以及模板,函数重载我是这样理解的,即不关心函数的返回值,函数名相同,函数参数不同,在静态多态中根据函数参数来调用对应的函数。
动态多态
动态多态在返回值类型、函数名、函数参数上都是相同的,实现动态多态的前提是基类中的函数须声明为虚函数或者纯虚函数,在运行过程中动态的进行函数绑定,即定义基类指针指向子类对象,基类指针指向谁则调用谁的函数。虚函数的实现原理涉及到虚函数表,什么是虚函数表?虚函数表是在编译阶段系统自动创建的,系统会为每个含有虚函数的类创建一个虚函数表,这个表保存了所对应类的虚函数地址,除此之外系统还会为每个类的特定对象创建一个虚函数指针,虚函数指针保存在所在对象的内存空间的最前面位置,以此能够保证正确的取到虚函数的偏移量。另外,这个指针指向了对象所在类的虚函数表,在程序运行阶段,通过初始化虚函数指针使其正确指向所属类的虚函数表,从而在调用虚函数时,能够正确调用到函数。
多态性在一定程度上增加了程序的灵活性以及重用性,为软件的开发和维护提供了极大的便利,尤其是其才用了虚函数表和动态连编机制后,允许用户用更为明确、易懂的方式去建立通用的软件。

十八、什么是内存泄漏?如何避免?
内存泄漏指的是我们程序员申请一段内存后,由于后续操作不当导致我们所申请的那段内存段失去控制,使得我们无法再次访问该段内存空间以及无法再次使用此内存段,导致内存泄漏。一般少量的内存泄漏不会导致很大的问题,但是积少成多,过多的内存泄漏会造成很严重的问题,譬如会导致程序的崩溃,所以内存泄漏带来的问题是很大的,如何避免内存泄漏主要有以下几点:

  1. 保证new和delete以及malloc和free的成对存在,即每申请一段内存都要有相对应的释放语句,此外还要保证释放后的指针置空,保证其不会成为野指针,这一点也是很重要的。
  2. 一般来说在有继承关系的基类中,最好把析构函数声明为虚函数,这样能够保证其在派生类中能够调用到自己的析构函数以完成自己最后的清尾工作。
  3. 一般来内存申请放在构造函数里,内存释放放到析构函数里,这也和上面所提到的把析构函数定义为虚函数是相对应的。
  4. 为了避免我们程序员自身的疏忽导致一些不可避免的内存泄漏问题,我们可以使用C++所提供给我们的一个自动释放内存的工具,也就是智能指针,我们使用智能指针这个工具可以很好的避免内存泄漏这个问题。
    什么是智能指针?
    智能指针是我们C++11所出来的一个用来辅助我们自动释放内存的一个工具,能够很好的避免内存泄漏这个问题,智能指针的原理其实就是采用了计数这个原理。我们常用的智能指针大致分为4种,分别为自动指针、共享指针、独享指针以及弱指针,现在常用的为后三种,自动指针现在被舍弃了。

十九、什么是STL模板库?
STL简介
STL标准模板库是一个基于泛型编程思想的程序库,内嵌于C++编译器。它提供了几乎所有常见的数据结构类型及其算法,是对基础数据结构的补充和强化,它有两个特征:

  1. 数据结构和算法相分离。STL模板库基于泛型编程思想,其算法不依赖于数据结构进行实现。说直白点就是,其中的一个算法可以操作几种不同的容器数据,例如:sort()可以作用于vector、stack、list、queue等。
  2. STL不是面向对象。STL中的数据结构和算法不具备明显的继承关系,这使得两者之间相互独立,降低了两者之间的耦合性,这符合泛型编程的思想,除此之外STL还采用了一些封装的思想,即让使用者不必关心其内部底层是如何实现的,只需要调用对外接口使用就行。

STL的组件
STL由六个组件组成,分别为

  1. 容器:像我们的各种数据结构,譬如vector、stack、list、deque、map等等。他们都是以模板类的方式提供的。
  2. 算法:指我们调用的函数,譬如sort、clear等等。
  3. 迭代器。
  4. 仿函数。
  5. 空间适配器。
  6. 容器适配器。

我常用的几个模板库

  1. Vector :连续存储的容器,动态数组,其在堆上分配空间。底层原理实现为数组,另外其容量是以两倍的容量扩增的。其在最后插入和删除比较快,支持随机访问,访问速度较快,但是在中间插入和删除的话速度很慢,效率很低。适合于领唱查找的情况。
  2. List:动态链表,在堆上分配空间,每次插入一个元素就会分配一个内存,每次删除一个元素就会释放内存。底层原理是双向链表,不支持随机访问,插入删除比较方便,适合于经常发生插入删除以及不经常查找的情况。
  3. Queue
  4. Map
  5. Stack

二十、深度优先遍历和广度优先遍历?
深度优先遍历:
深度优先遍历即从根节点开始,一直往下遍历,类似于根左右的顺序进行遍历,当遍历到一个节点,此时这个节点没有还未访问的邻接点时,便开始往上回溯查找存在邻接节点并且未被访问的节点,在沿着这条路遍历下去,如此重复上述过程,深度优先遍历类似于我们二叉树中的前序遍历、中序遍历以及后序遍历,深度优先遍历用到的数据结构为栈,这是栈的特点先进后出是相一致的。

广度优先遍历:
广度优先遍历类似于我们的层序遍历,其是用队列来实现的,依次把每一层的节点入队列,然后在依次出队列并且把出队列的点的所有邻接点入队列,重复以上操作,直到队列为空。我们的A*算法就用到了广度优先遍历。

A*算法:
通过一个计算耗时函数F=G+H来计算总的耗时量,其中F代表总耗时,G代表从当前格子到八个方向的各种所耗时,H表示当前格子到终点的预计耗时。寻路的步骤大致为以下步骤:

  1. 选定一个起点把它加入到待检查节点的列表,找出该起点到8个方向可走的格子,并且设置可走格子的父节点为该起点,依次算出其到这些可走格子的耗时量G,然后将该起点从待检查节点的链表移入已检查节点的链表,并把可走格子移入待检查节点的链表,并分别算出它们的H,即从当前格子到终点预计耗时。
  2. 从待检查节点链表中选择F值最小的节点作为新的起点,重复之前的操作,但要注意几点:
  • 可走格子已经在已检查节点链表中,则忽略该格子。
  • 如果可走格子不在待检查节点链表中,则计算相应的F H G值,并设置其父节点,放入待检查节点链表中。
  • 如果可走格子在待检查节点链表中,则重新计算其相应的G值,如果该G值比原来的G值小,则重新设置其父节点为当前起点,并更新其F和G值。

二十一、什么是三次握手?什么是四次挥手?
三次握手:

  1. 第一次挥手,客户端向服务端发送一条报文,大致意思就是请求跟服务端进行连接。
  2. 第二次挥手,服务端向客户端发送一条消息,告诉我们的客户端,服务端已经收到了你的请求并且正在建立连接。
  3. 第三次挥手,客户端给我们服务端发送一条消息表明我已经和你建立了链接。
    四次挥手:
  4. 第一次挥手,客户端向服务端发送消息表明我已经不需要和你建立连接了,请求断开连接。
  5. 第二次挥手,服务端收到断开连接请求后,随机发送一条消息给客户端,表明自己还有数据没有传送给你,请一下。
  6. 第三次挥手,服务端等待一段时间后便和客户端断开连接并再次发送一条消息给客户端,表明自己已经与你断开了连接。
  7. 第四次挥手,客户端收到服务端断开连接消息后给服务端发送消息表明自己已经断开了连接。

二十二、进程和线程的区别?

  1. 进程是资源分配的最小单位,线程是程序执行的最小单位。
  2. 进程有自己独立的内存空间,每启动一个进程,系统就会为它分配地址空间。
  3. 线程之间的通信更方便,同一进程下的线程在同一内存空间下,数据是共享的。
  4. 多进程比多线程更加健壮,一个进程死了不会影响其他的进程,因为他们有自己独立的地址空间,但是一个线程死了,所在的进程也死了。

二十三、热更新
原理:unity游戏热更新包含两个方面,一个是资源的更新,一个是脚本的更新。unity提供的可以热更新的方案就是AB包。资源、代码都可以打成AB包,放到服务器上,然后对比版本,进行热更新。unity3D的热更新会涉及3个目录:游戏资源目录、数据目录、网络资源地址。

  1. 游戏资源目录:游戏的安装目录
  2. 数据目录:我们手动创建的一个目录,该目录可读可写。
  3. 网络资源地址:我们常称为服务器地址,是用来存放游戏资源的网址。

步骤:

  1. 将资源打包成AB包。
  2. 将生成的所有文件的MD5码和生成的版本文件分别放到所对应的txt文本中
  3. 上传AB包到我们的网络资源地址中。
  4. 游戏首次启动后,便开始对我们的文件进进行解压,这一操作一般只执行一次。
  5. 解压资源包后开始分析判断当前游戏的版本号与我们服务器上的版本号是否一致,如果不一致则弹出相应的UI提示我们的玩家进行更新。
  6. 玩家选择更新后,则把服务器上的MD5码值与我们本地的进行对比,并把不一致的替换掉,替换完成后修改相应的版本号。

二十四、C#与Lua之间的交互?
xlua 腾讯所维护的一个插件工具,用于两者的交互
交互原理:C#与lua进行交互主要通过虚拟栈实现,栈的索引为正数,则1表示栈底,如果索引为负数,则-1表示栈顶。

  1. C#调用lua:由C#先将数据放入栈中,由lua去栈中获取数据,然后返回数据对应的值到栈顶,再由栈顶返回至C#。
  2. Lua调用C#:首先生成C#源文件所对应的Wrap文件或者编写C#源文件所对应的c模块,然后将源文件内容通过Wrap文件或者C模块注册到Lua解释器中,然后由Lua去调用这个模块的函数。1、加载C#命名空间 2、加载命名空间下的类 3、实例化类对象。其中访问变量用.号,访问普通成员函数用冒号,访问静态成员函数用的是.号(类名)
  • CSharp操作lua要引入LuaInterface库文件
  • lua操作CSharp要引入Luanet这个模块
  • github上可以获取到的一个开源工程

原生交互:
CSharp中的普通函数注入到lua脚本中:lua.RegisterFunction(注册到lua以后方法的名字,程序对象,程序的类型)

CSharp中的静态函数注入到lua脚本中:lua.RegisterFunction(注册到lua以后的方法名称,空,程序的类型)

C#与Lua交互过程:
 C# Call Lua :由C#文件先调用Lua解析器底层dll库(由C语言编写),再由dll文件执行相应的Lua文件;
 Lua Call C# :
(1)Wrap方式:首先生成C#源文件所对应的Wrap文件,由Lua文件调用Wrap文件,再由Wrap文件调用C#文件;
(2)反射方式:当索引系统API、dll库或者第三方库时,如果无法将代码的具体实现进行代码生成,可采用此方式实现交互。缺点:执行效率低。

二十五、类的对象在堆区,它的成员数据在哪个区?

  1. 成员函数存放在代码区。
  2. 静态成员变量存放在静态全局区,其在类定义时就已经分配好了。其属于类。

二十六、队列和栈的区别?

  1. 操作的名称不同:队列的插入称为入队,删除称为出队,栈的插入称为进栈,删除称为出栈。
  2. 操作的限定不同:队列在队尾进队头出,栈只能在栈顶进栈或者出栈,无法对栈底进行操作。
  3. 操作的规则不同:队列是先进先出,栈是先进后出。
  4. 遍历数据速度不同:队列根据地址至真进行遍历,头部和尾部都能进行遍历,但不能同时遍历,并且无需开辟空间,遍历速度要快。栈只能从顶部遍历,并且要额外开辟空间,速度相对要慢。

二十七、传输控制协议TCP 和 用户数据报协议UDP的区别?

  1. TCP是面向字节流的,基本传输单位是TCP报文段;UDP是面向报文的,基本传输单位是是用户数据报;
  2. TCP 注重安全可靠性,连接双方在进行通信前,需进行三次握手建立连接。UDP 是无连接的,使用最大努力交付,即不保证可靠交付。
  3. UDP 不需要连接等待,所以数据传输快,而 TCP 传输效率相对较低
  4. TCP首部开销是20个字节;UDP的首部开销是8个字节,这也是减少网络传输开销的一方面
  5. TCP有拥塞控制和流量控制,而UDP没有拥塞控制和流量控制TCP支持点对点通信,提供全双工通信,不提供广播或多播服务;UDP支持一对一、一对多、多对一、多对多的通信模式。

你可能感兴趣的:(笔记,c++)