C++面试2022/7月线下面试经验总结题

0、指针和引用区别

1、定义:指针是一个变量,存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,是原变量的一个别名,没有占用额外的内存空间。
引用就是指针,是一个特殊的有约束的指针。变量名在汇编后就不复存在?
2、引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。
3、指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了
4、可以有const指针,但是没有const引用;
5、指针可以有多级,但是引用只能是一级;
6、sizeof“引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;
7、指针和引用的自增(++)运算意义不一样
8、如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏;
9、指针作为函数传递时需要拷贝内存,引用作为函数传递时不需要拷贝内存。因此当函数参数传递时,很多时候使用&或者const&传递参数节省内存;
10、引用的底层也是指针实现的,内置类型指针传递和引用传递的汇编代码是一样的。因为引用的高效,在于对大的数据,不用直接的复制数据, 指针传递时需要分配内存空间,间接寻址。而引用不需要分配空间直接寻址速度快。

1、什么是多态性

不同类的对象对同一个消息的响应
多态性是一种泛型技术(用不变的代码实现可变的算法)
实现多态的条件是
1.有类的继承关系,且继承关系中的每一个类都有虚函数
2.用父类的指针指向子类的对象
3.用父类的指针来调用子类对象中的成员函数

2、介绍虚函数

虚函数是实现多态性的基础,使用的其核心目的是通过基类访问派生类定义的函数。所谓虚函数就是在基类定义一个未实现的函数名,用virtual关键字说明的函数。
在基类中定义了一个虚函数,所有可以在其子类重新定义父类的做法这种行为成为覆盖(override),或者为重写。

虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。
纯虚函数: virtual void abmethod(int value) = 0;

纯虚类:含有一个纯虚函数的类叫纯虚类,它不可被实例化。有纯虚函数的类就是抽象类,也叫做接口类。抽象类无法实例化出对象。抽象类的子类也无法实例化出对象,除非重写父类的虚函数。

定义一个函数为虚函数,不代表函数为不被实现的函数 。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数 ;
定义一个函数为纯虚函数,才代表函数没有被实现。定义他是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

注意:
构造函数不能是虚函数
虚析构函数
类的静态成员函数不能是虚函数(静态成员函数是所有)
内联函数不能是虚函数:inline是编译器将函数内容替换到函数调用的地方,是静态编译的,但是虚函数动态调用的,是只有在函数运行的时候才能知道调用的是哪个函数,所以在编译的时候,incline它不知道是子类函数父类的虚函数,因此编译器会忽略。

C++ 11 增加了两个继承控制关键字:override 和 final
override:保证在派生类中声明的重载函数,与基类的虚函数有相同的签名;
final:阻止类的进一步派生 和 虚函数的进一步重写。

虚函数与虚指针

虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类,这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
与虚函数相关的是虚函数指针与虚函数表。
虚函数指针是指向虚函数表的,而虚函数表是存放虚函数地址的一个表,程序运行时会从该表中找到虚函数指针指向的函数进行调用,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。而虚函数指针是存放在类的实例中的。虚函数表存放在类的实例的内存中。

虚继承

虚继承(Virtual Inheritance):为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上 virtual 关键字就是虚继承。

虚继承主要用于解决菱形继承问题,解决一个孙子类继承爷爷类两次的问题。防止出现二义性。虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

3、为什么可以有虚析构函数,不能有虚构造函数。

为什么构造函数不能是虚函数 ,理由是:
在编译阶段,编译器会在类中添加一个看不见的成员,即虚函数表指针,并在构造函数中对虚函数表指针进行初始化,通过虚函数表指针才能够找到虚函数表,进而实现不同虚函数的调用,因此在执行构造函数之前,编译器并不知道虚函数表的初始值是谁,更不可能成功编译本虚构造函数!

如果一个父类有子类,那么这个父类的析构函数一定是虚析构函数,原因是:
如果父类的析构函数不是虚析构,那么当用delete删除一个指向子类对象的父类指针时,将调用父类的析构函数,子类只释放了来自父类的那部分成员变量,而子类自己扩展的成员没有被释放,造成内存泄露

4、一个生成的空类包含什么

缺省构造函数。
缺省拷贝构造函数。
缺省析构函数。
缺省赋值运算符。
缺省取址运算符。
缺省取址运算符 const。
注意:只有当实际使用这些函数的时候,编译器才会去定义它们。

类和函数的区别。

区别: 面向对象注重对类的使用,类中封装了数据和对其的操作函数,会自动处理一些不同的函数的调用工作(多态,重载等),以减轻对函数的依赖。面向过程则注重对函数(功能模块)的应用。
联系:面向对象只是将数据和函数“打包”,并记入了一些自动判断并调用“函数”的功能。所以,面向对象也是建立在函数的基础上,只是减弱了开发人员对函数的依赖度,将重点转向数据。

5、野指针

定义:指向不可用内存的指针
造成野指针的三个原因
1.指针变量没有被初始化
2.指针所指向的内存被释放了,但是指针本身却没有被置为null
3.指针超过了变量的作用范围(局部变量)
指针悬挂:指向一个已经释放的内存空间

6、友元

友元可以分为友元类和友元函数,友元的关系是单向的且不可继承。友元在一定程度上破坏了类的封装性,因为一旦一个类或者函数定义为某个类的友元,则可以访问该类的私有成员。友元只能在类中声明,但可以在任何地方定义,友元关键字只需要在声明的时候加入即可,这一点和内联函数恰好相反。

7、智能指针

除了静态内存和栈内存外,每个程序还有一个内存池,被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,必须销毁。

动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

更加安全的使用动态内存,智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象,从而避免内存泄漏。
智能指针负责管理动态分配的内存,它会自动释放new出来的内存,从而避免内存泄漏!

如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存。智能指针就是通过这个原理来解决指针自动释放的问题!

标准库提供的两种智能指针的区别在于管理底层指针的方法不同,shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。

为什么智能指针可以像普通指针那样使用???
因为其里面重载了 * 和 -> 运算符, * 返回普通对象,而 -> 返回指针对象。

1、auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!
auto_ptr是用于C++11之前的智能指针。由于 auto_ptr 基于排他所有权模式:两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr 主要有三大问题:

复制和赋值会改变资源的所有权,不符合人的直觉。
在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
不支持对象数组的操作。

C++11用更严谨的unique_ptr 取代了auto_ptr!
2、unique_ptr 和 auto_ptr用法几乎一样,除了一些特殊。
unique_ptr特性
基于排他所有权模式:两个指针不能指向同一个资源。
无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值。
保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
在容器中保存指针是安全的。

3、unique_ptr 排他型的内存管理有很大的局限,无法解决多个指针变量共享问题。
shared_ptr ,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它。
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!

4、weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->,但可以使用 lock 获得一个可用的 shared_ptr 对象。

弱指针的使用;
weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针
弱指针也可以获得引用计数;
wpGirl_1.use_count()
弱指针不支持 * 和 -> 对指针的访问;
在必要的使用可以转换成共享指针 lock();

智能指针的使用陷阱

1、不要把一个原生指针给多个智能指针管理;
int *x = new int(10);
unique_ptr< int > up1(x);
unique_ptr< int > up2(x);
// 警告! 以上代码使up1 up2指向同一个内存,非常危险
或以下形式:
up1.reset(x);
up2.reset(x);

2、记得使用u.release()的返回值;
在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了.

3、禁止delete 智能指针get 函数返回的指针;
如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!

4、禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
shared_ptr< int > sp1(new int(10));
// 一个典型的错误用法 shared_ptr< int > sp4(sp1.get());

9、C++内存分配机制

静态存储区分配、栈内存分配和堆内存分配。

堆:是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。程序员手动分配和释放。

栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

自由存储区:自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。

全局/静态存储区:这块内存是在程序编译的时候就已经分配好的,在程序整个运行期间都存在。例如全局变量,静态变量。

常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量(const),不允许修改。

堆和栈的区别
管理方式不同:栈是由编译器自动申请和释放空间,堆是需要程序员手动申请和释放;
空间大小不同:栈的空间是有限的,在32位平台下,VC6下默认为1M,堆最大可以到4G;
能否产生碎片:栈和数据结构中的栈原理相同,在弹出一个元素之前,上一个已经弹出了,不会产生碎片,如果不停地调用malloc、free对造成内存碎片很多;
生长方向不同:堆生长方向是向上的,也就是向着内存地址增加的方向,栈刚好相反,向着内存减小的方向生长。
分配方式不同:堆都是动态分配的,没有静态分配的堆。栈有静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 malloc 函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。
分配效率不同:栈的效率比堆高很多。栈是机器系统提供的数据结构,计算机在底层提供栈的支持,分配专门的寄存器来存放栈的地址,压栈出栈都有相应的指令,因此比较快。堆是由库函数提供的,机制很复杂,库函数会按照一定的算法进行搜索内存,因此比较慢。

10、new delete malloc free 的区别

1.申请的内存所在位置
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。

自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。

堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。

自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。

2.返回类型安全性
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。

malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图方法自己没被授权的内存区域。

3.内存分配失败时的返回值
new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。

4.是否需要指定内存大小
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。

5、是否调用构造函数/析构函数:new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会。

使用new操作符来分配对象内存时会经历三个步骤:
第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
第三部:对象构造完成后,返回一个指向该对象的指针。

使用delete操作符来释放对象内存时会经历两个步骤:
第一步:调用对象的析构函数。
第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。

6.对数组的处理
C++提供了new[]与delete[]来专门处理数组类型。
至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:

7.new与malloc是否可以相互调用
operator new /operator delete的实现可以基于malloc,而malloc的实现不可以去调用new

8.是否可以被重载
opeartor new /operator delete可以被重载。而malloc/free并不允许重载。

9.能够直观地重新分配内存
使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。

10.客户处理内存分配不足
在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数,这就是new-handler。
对于malloc,客户并不能够去编程决定内存不足以分配时要干什么事,只能看着malloc返回NULL。
new delete malloc free 的区别
11、对于简单数据类型如int[],使用new分配内存后,可以使用free来释放上述释放的内存,效果与delete相同。而复杂数据类型如一个类指针,则不能用free来进行内存的释放,因为free与delete不同,不会执行类的析构函数。

new和delete的本质是一个函数,malloc和free只是这两个函数中的一句调用语句,当用new申请内存,却用free释放,相当于用整个函数来申请内存并维护了一个逻辑使内存方便使用,但是只用了一个free语句就释放掉内存,使这个逻辑出现错误,因为这是个逻辑错误而不是语法错误,所以编译器不会识别。

如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。
如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错。

11、多态在对象层面如何实现

多态性是面向对象程序设计(OOP)的核心思想,多种形态。

多态的实现是在基类的函数前加上 virtual 关键字使其成为虚函数,并在派生类中重写该函数;该函数运行时会根据引用或指针绑定的对象的真实类型来决定要执行的版本。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明成虚函数。
基类通过在其成员函数的声明语句之前加上关键字 virtual 使得该函数执行动态绑定。关键字 virtual 只能出现在类内部的声明语句之前而不能用于类外部的函数定义。
任何构造函数之外的非静态函数都可以是虚函数。

在函数体的位置(即在声明语句的分号之前)书写 =0 就可以将一个虚函数说明为纯虚函数
其中,=0 只能出现在类内部的虚函数声明语句处。
一个纯虚函数无需定义。我们可以将纯虚函数的函数体定义在类的外部,但不能在类的内部为一个 =0 的函数提供函数体。
含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类抽象基类负责定义接口,而后续的其他类可以覆盖该接口。我们不能(直接)创建一个抽象基类的对象。

** 虚表**
编译器会为每个包含虚函数的类创建一个虚函数表(或称虚表),该表是一个一维数组,在这个数组中存放每个虚函数的入口地址。编译器会在每个对象的前四个字节中保存一个虚表指针(即 vptr,一般作为类对象的第一个成员),指向对象所属类的虚表。虚表和类是对应的,虚表指针和对象是对应的。在构造函数中进行虚表的创建和虚表指针的初始化。

12、几种函数传递参数的方式

1)值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

2)指针传递:
形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作

3)引用传递:
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

13、重载、重写、重定义区别

  1. 重载 overload  
    定义:在同一个作用域内,两函数的函数名可以相同,但是参数不能完全相同,可以是参数类型不同或者是参数个数不同,至于返回值,不影响重载。
    C++代码在编译时会根据参数列表对函数进行重命名,例如void Test(int a, int b)会被重命名为_Test_int_int,void Test(int x, double y)会被重命名为_Test_int_double。所以说函数重载从底层上看它们还是不同的函数。

注意main函数不能重载,每个程序的main函数只有一个;
特点:(4) virtual关键字可有可无 (5) 返回值可以不同 (6) 访问修饰符可以不同

  1. 重写 override(覆盖)
    定义:子类重写基类的虚函数
    特点:(1) 函数名相同 (2) 作用域不同 (3) 参数列表相同 (4) 基类函数必须有virtual关键字且不能有static (5) 返回值相同 (6) 重写函数的访问修饰符可以不同;

覆盖指的是子类覆盖父类函数(被覆盖)
1.分别位于子类和父类中
2.函数名字与参数都相同,返回值可以不相同
3.父类的函数是虚函数(virtual)

重定义 overwrite(隐藏)
定义:子类重定义基类的函数 ,重定义也叫隐藏,指的是在继承关系中,子类实现了一个和父类名字一样的函数,(只关注函数名,和参数与返回值无关),子类的函数就把父类的同名函数隐藏了。

特点:(1) 函数名相同 (2) 作用域不同 (3) 参数列表可以不同 (4) virtual关键字可有可无 (5) 返回值可以不同 (6) 访问修饰符可以不同

隐藏指的是子类隐藏了父类的函数(还存在)
1:子类的函数与父类的名称相同,但是参数不同,父类函数被隐藏
2:子类函数与父类函数的名称相同,参数也相同,但是父类函数没有virtual,父类函数被隐藏

14、深拷贝和浅拷贝的区别

默认拷贝构造函数可以完成对象的数据成员简单的复制,这也称为浅拷贝。对象的数据资源是由指针指向的堆时,默认的拷贝构造函数只是将指针复制。

总结: 浅拷贝会把指针变量的地址复制; 深拷贝会重新开辟内存空间。

总结:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

15、预处理、编译、汇编、链接的过程

一.预处理
预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令,比如“#include”,“#define”等,主要处理规则如下:
将所有的“#define”删除,并且展开所有的宏定义。
处理所有条件预编译指令,比如“#if”,“#ifdef”,“#elif”,“#else”,“#endif”。
处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
删除所有的注释“//”和“/**/”。
添加行号和文件名标识,比如#2“a.c”2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
保留所有的#pragma编译器指令,因为编译器需要使用它们。

二.编译
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析,代码优化及优化后生成相应的汇编代码文件。
代码优化的优缺点:
优点:提高效率
缺点:多线程下不可控

三.汇编
汇编过程就是由汇编器将汇编代码转变成机器可以执行的二进制指令。

四.链接
把各个模块之间相互引用长度部分都处理好,使得各个模块之间能够正确的衔接。将各个目标文件链接起来生成最终的可执行文件。

链接过程可以具体的分为以下四步:
1合并段和符号表,合并多个文件的符号表及各段内容,放入一个新的文件中。
2符号解析,在每个文件符号引用(引用外部符号)的地方找到符号的定义。这就是符号解析。
3地址和空间分配,符号解析成功后,为程序分配虚拟地址空间。
4符号重定位 // 指令段,符号重定向就是对.o文件中.text段指令中的无效地址给出具体的虚拟地址或者相对位移偏移量。

链接又分为:
静态链接
动态链接

五.扩展
目标文件就是源代码经过编译后但未进行链接的那些中间文件。Linux下的 .o文件就是目标文件,目标文件和可执行文件内容和格式几乎都一样,所以我们可以广义地将目标文件和可执行文化看成一类型文件。
目标文件除了含有汇编后的机器指令代码,数据外,还包括了链接时所需要的一些信息,比如符号表,调试信息,字符串等。一般目标文件将这些信息按不同的属性,以“节”的形式存储,有时候也叫做“段”。

16、虚继承

Mysql

数据库选择前五条数据的语句怎么写。

  1. Oracle数据库
    SELECT * FROM tablename WHERE ROWNUM <= N;

  2. SQL Server数据库
    SELECT TOP N * FROM tablename ;

  3. MySQL数据库
    SELECT * FROM tablename LIMIT N;

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值