标签:efi nis 执行 文件格式 cond 难点 概念 var clear
目录
类与对象的区别是什么?
什么是虚拟构造函数以及析构造函数
虚函数之所以能够做到动态调用,是因为程序在执行阶段才确定调用,也就是晚绑定。而早绑定在编译阶段就已经确定下一步该调用哪个函数。
晚绑定的本质是:当实例化一个带有虚函数(继承下来的虚函数也可以)类对象时,编译器会生成一个VPTR指针和VTABLE表,VTABLE表中存放所有“虚函数地址”;VPTR指向VTABLE的首地址。不管对象如何被强换(子类转换为基类),还是在传引用或传指针的过程中,它的地址都不会变;只要我们握有对象的地址,就可以通过对象地址找到VPTR,通过VPTR找到VTABLE,通过VPTABLE找到虚函数,从而调用正确的虚函数。
VPTR的位置都一样,一般都在对象的开头。VTABLE其实就是一个函数指针的数组,VPTR正指向VTABLE的第一个元素(第一个虚函数);如果VPTR向后偏移一个位置,那么它应该指向了VTABLE中的第二个函数了。
(注:如果子类没有实现虚函数,会继承基类的虚函数,依然建立自己的虚函数表;如果子类有新的虚函数,会添加到虚函数表中)
基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。
?在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。
?具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
静态成员函数可以不通过类来调,但是虚函数一定要用类来调
还没有对象,就没有指向虚表的指针,就不可以通过虚表去调用,这是一个先有鸡还是先有蛋的问题
inline函数在编译时被展开,在调用处将整个函数替换为代码块,省去了函数跳转的时间,提高了SD,减少函数调用的开销,虚函数是为了继承后对象能够准确的调用自己的函数,执行相应的动作。
主要的原因是:inline函数在编译时被展开,用函数体去替换函数,而virtual是在运行期间才能动态绑定的,这也决定了inline函数不可能为虚函数。(inline函数体现的是一种编译机制,而virtual体现的是一种动态运行机制
注意:派生类里的析构函数最好给成虚函数。否则派生类中如有空间的开辟那么有可能造成内存泄露?
如何定义一个抽象类
## 1.纯虚函数定义
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性
纯虚函数的声明有着特殊的语法格式:virtual 返回值类型成员函数名(参数表)=0;
(1)类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样的话,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
(2)虚函数在子类里面也可以不重载的;但纯虚函数必须在子类去实现,这就像Java的接口一样。通常把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为很难预料到父类里面的这个函数不在子类里面不去修改它的实现。
(4)带纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。这样的类也叫抽象类。抽象类和大家口头常说的虚基类还是有区别的
在C#中用abstract定义抽象类,而在C++中有抽象类的概念,但是没有这个关键字。抽象类被继承后,子类可以继续是抽象类,也可以是普通类。虚基类:被“virtual”继承的类,也就是说任何类都可以成为虚基类。抽象类:至少包含一个纯虚函数的类,其不能被实例化,哪怕该纯虚函数在该类中被定义。二者没有任何联系。虚基类?就是解决多重多级继承造成的二义性问题
接口是为了约束对象具有的同一类的行为而定义的。
而继承是为了表现同类对象的实体关系而定义的
拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)
当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。
自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。下面举个深拷贝的例子。
``````c++
using?namespace?std;
class?CExample?{
private:
???? int?a;
public:
???? CExample(int?b)
???? {?a=b;}
???? void?Show?()
???? {
????????cout<<a<<endl;
????}
};
int?main()
{
???? CExample?A(100);
???? CExample?B=A;
???? B.Show?();
???? return?0;
}?
#include?<iostream>
using?namespace?std;
class?CExample?{
private:
????int?a;
public:
????CExample(int?b)
????{?a=b;}
????
????CExample(const?CExample&?C)
????{
????????a=C.a;
????}
????void?Show?()
????{
????????cout<<a<<endl;
????}
};
int?main()
{
????CExample?A(100);
????CExample?B=A;
????B.Show?();
????return?0;
}?
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& C)
{
a=C.a;
str=new char[a]; //深拷贝
if(str!=0)
strcpy(str,C.str);
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}?
Overload:顾名思义,就是Over(重新)——load(加载),所以中文名称是重载。它可以表现类的多态性,可以是函数里面可以有相同的函数名但是参数名、返回值、类型不能相同;或者说可以改变参数、类型、返回值但是函数名字依然不变。Override:就是ride(重写)的意思,在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。
相当佩服第一个把这两个词翻译过来的人,相当贴切!方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了,而且如果子类的方法名和参数类型和个数都和父类相同,那么子类的返回值类型必须和父类的相同;如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。也就是说,重载的返回值类型可以相同也可以不同。
1、重载
重载就是简单的复用一个已经存在的名字,来操作不用的类型。这个名字可以是一个函数名,也可以是一个操作符。由于主要是针对函数的重载,所以对于操作符的重载在后续进行解释。
虽然可以通过默认参数的方式可以使用不同数据的参数可以调用同一个函数,但是对于不同参数类型的操作,可就是爱莫能助了。为了实现同一个函数来实现不同类型的操作,这就需要C++中的一个重要的特性:重载。
实现函数重载的主要条件是:
?1)首先发生重载的函数需要在相同的作用域中;
2)函数名称需要相同;
3)函数的参数类型不相同;
4)与virtual关键字无关;
下面的示例中,Base类中的?getIndex(int x)和getIndex(float x)为相互重载,与virtual无关。当调用getIndex函数的时候根据传入的参数选择不同的函数进行执行。
#include <iostream>
#include <stdio.h>
using namespace std;
class Base
{
public:
virtual void ?getIndex(int x)
{
cout<<"Base x="<<x<<endl;
}
virtual void ?getIndex(float x)
{
cout<<"Base x="<<x<<endl;
}
};
class Derived:public Base
{
public:
virtual void ??Derived(string x)
{
cout<<"Derived x="<<x<<endl;
}
};
int main()
{
Base base_obj;
base_obj.getIndex(2.14f);//Base float x=2.14
base_obj.getIndex(214); //Base int x=214
return 0;
}
2、重写
有时候希望同一个方法在基类和派生类中表现不同的行为。也就是说通过不同的对象调用,来实现不同的功能。这就是面向对象中的多态,同一个方法在不同的上下文中表现出多种形态。重写的时候就引入了virtual,将需要在派生类中重写的函数在基类中声明为virtual类型的。
? ? ? ?实现重写的特性
? ? ? 1)在基类中将需要重写的函数声明为virtual;
? ? ? 2)派生类类和基类中的函数的名称相同;
? ? ? 3)函数的参数类型相同;
? ? ?4)在不同的作用范围中;(基类和派生类中)
`
#include <iostream>
#include <stdio.h>
using namespace std;
class Base
{
public:
virtual void ?getIndex(int x)
{
cout<<"Base int x="<<x<<endl;
}
virtual void ?getIndex(float x)
{
cout<<"Base float x="<<x<<endl;
}
};
class Derived:public Base
{
public:
void ?getIndex(float x)
{
cout<<"Derived x="<<x<<endl;
}
};
int main()
{
Derived derived_obj;
Derived *pd;
Base *pb;
pd = &derived_obj;
pb = &derived_obj;
pd->getIndex(2.14f);//Derived x=2.14
pb->getIndex(2.14f);//Derived x=2.14
return 0;
}
?C语言代码是以文件为单位来组织的,在一个源程序的所有源文件中,一个外部变量(注意不是局部变量)或者函数只能在一个源程序中定义一次,如果有重复定义的话编译器就会报错。伴随着不同源文件变量和函数之间的相互引用以及相互独立的关系,产生了extern和static关键字。
?## 一,static全局变量
下面,详细分析一下static关键字在编写程序时有的三大类用法:? ?? ???一,static全局变量? ?? ?? ???我们知道,一个进程在内存中的布局如图1所示:
? ?? ?其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量(其他段中还有很多乱七八糟的段,暂且不表)。在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝。? ???当一个进程的全局变量被声明为static之后,它的中文名叫静态全局变量。静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效,其他源文件无法访问它。所以,普通全局变量穿上static外衣后,它就变成了新娘,已心有所属,只能被定义它的源文件(新郎)中的变量或函数访问。
普通的局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放之。? ?? ? static局部变量中文名叫静态局部变量。它与普通的局部变量比起来有如下几个区别:? ?? ?? ??
1)位置:静态局部变量被编译器放在全局存储区.data(注意:不在.bss段内,原因见3)),所以它虽然是局部的,但是在程序的整个生命周期中存在。? ?? ?? ???
2)访问权限:静态局部变量只能被其作用域内的变量或函数访问。也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数和源文件访问。? ?? ?? ??
3)值:静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。
预处理器、编译器、汇编器和链接器的工作是什么?
hello程序的生命周期是从一个源程序(hello.c)(称为高级c语言)开始,被其它程序转化为一系列的低级机器语言指令,这些指令按照一种称为可执行目标程序的格式打包好,以二进制磁盘文件的形式保存。 例:unix>?gcc -o hello hello.c可以实现源文件向目标文件的转化,该过程由编译程序完成。 hello.c ?---->hello.i ?---->hello.s ?---->hello.o ?-->hello
拿一个简单的例子,例子叫做Base.c,内容如下:#include <stdio.h>/这是一条注释/int main(){printf("Hello world\n");return 0;}
(1). 预处理(cpp):预处理器不止一种,而C/C++的预处理器就是其中最低端的一种——词法预处理器,主要是进行文本替换、宏展开、删除注释这类简单工作。gcc -E 选项可以得到预处理后的结果,扩展名为.i;C/C++预处理不做任何语法检查,不仅是因为它不具备语法检查功能,也因为预处理命令不属于C/C++语句(这也是定义宏时不要加分号的原因),语法检查是编译器要做的事情;预处理之后,得到的仅仅是真正的源代码;GCC确实很强大,如果是用VC这种IDE,恐怕就不能看到预处理后的结果。? ? e.g. 所谓预处理,就是把程序中的宏展开,把头文件的内容展开包含进来,预处理不会生成文件,所以需要重定向
(2). 编译器(ccl):将文本文件.i翻译成文本文件.s,得到汇编语言程序(把高级语言翻译为机器语言),该种语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。gcc -S 选项可以得到编译后的汇编代码,扩展名为.s;汇编语言为不同高级语言的不同编译器提供了通用的输出语言,比如,C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。e.g.
(3). 汇编(as):将.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中(把汇编语言翻译成机器语言的过程)。gcc -c?选项可以得到汇编后的结果,扩展名为.o;.o是一个二进制文件,它的字节编码是机器语言指令而不是字符。如果在文本编辑器中打开.o文件,看到的将是一堆乱码。把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。e.g.
(4).链接(ld):gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去。?函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。
之所以成为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:l??静态库对函数库的链接是放在编译时期完成的。l??程序在运行时与函数库再无瓜葛,移植方便。l??浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
Linux下使用ar工具、Windows下vs使用lib.exe,将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索。一般创建静态库的步骤如图所示:
图:创建静态库过程
动态库通过上面的介绍发现静态库,容易使用和理解,也达到了代码复用的目的,那为什么还需要动态库呢?为什么还需要动态库?为什么需要动态库,其实也是静态库的特点导致。l??空间浪费是静态库的一个问题。
l??另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
动态库特点总结:l??动态库把对一些库函数的链接载入推迟到程序运行的时期。l??可以实现进程之间的资源共享。(因此动态库也称为共享库)l??将一些程序升级变得简单。l??甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程
进程是资源分配最小单位,线程是程序执行的最小单位;
5.CPU切换一个线程比切换进程花费小;
6.创建一个线程比进程开销小;
7.线程占用的资源要?进程少很多。
8.线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
9.多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
10.进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;
?C++语言定义中说,每一种指针类型都有一个特殊值----"空指针"。
??空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数;而未初始化的指针则可能指向任何地方。
? 空指针不是野指针。每种指针类型都有一个空指针,而不同类型的空指针内部表示可能不尽相同。尽管程序员不必知道内部值,但编译器必须时刻明确需要哪种类型的空指针,以便在需要时加以区分。
(4.1)你可以认为 NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL会比 0 有更好的兼容性,但事实并非如此。
? ?(4.2)尽管符号常量经常代替数字,以备数字的变化,但这不是NULL 替代 0 的原因。语言本身确保了源码中的 0(用于指针上下文)会生成空指针。NULL只是用做一种格式习惯。
5)NULL可以确保是零,但空指针却不一定零
? ? ?空指针的内部(或者运行前)表达式可能不完全是零,而且对不同的指针类型可能不一样。真正的值只有编译器的开发者才关心。C++程序的作者永远看不到它们,这一点不用担心,明白就好。
【注意】
(5.1)空指针不一定是0,而NULL肯定是0.
(5.2)赋值为空指针的变量,可确保变量不指向任何对象或函数。合理地使用空指针可以有效地避免内存泄露,提高程序的执行效率。
ls | grep a
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。
虚拟内存别称虚拟存储器(Virtual Memory)。电脑中所运行的
程序均需经由内存执行,若执行的程序占用内存很大或很多,则会导致内存消耗殆尽。为解决该问题,Windows中运用了虚拟内存技术,即匀出一部分硬盘空间来充当内存使用。当内存耗尽时,电脑就会自动调用硬盘来充当内存,以缓解内存的紧张。若计算机运行程序或操作所需的随机存储器(RAM)不足时,则 Windows 会用虚拟存储器进行补偿。它将计算机的RAM和硬盘上的临时空间组合。当RAM运行速率缓慢时,它便将数据从RAM移动到称为“分页文件”的空间中。将数据移入分页文件可释放RAM,以便完成工作。 一般而言,计算机的RAM容量越大,程序运行得越快。若计算机的速率由于RAM可用空间匮乏而减缓,则可尝试通过增加虚拟内存来进行补偿。但是,计算机从RAM读取数据的速率要比从硬盘读取数据的速率快,因而扩增RAM容量(可加内存条)是最佳选择。?[2]?虚拟内存是Windows 为作为内存使用的一部分硬盘空间。虚拟内存在硬盘上其实就是为一个硕大无比的文件,文件名是PageFile.Sys,通常状态下是看不到的。必须关闭资源管理器对系统文件的保护功能才能看到这个文件。虚拟内存有时候也被称为是“页面文件”就是从这个文件的文件名中来的。?[2]?内存在计算机中的作用很大,电脑中所有运行的程序都需要经过内存来执行,如果执行的程序很大或很多,就会导致内存消耗殆尽。为了解决这个问题,WINDOWS运用了虚拟内存技术,即拿出一部分硬盘空间来充当内存使用,这部分空间即称为虚拟内存,虚拟内存在硬盘上的存在形式就是 PAGEFILE.SYS这个页面文件。?[2]?
Linux中断(interrupt)子系统之一:中断系统基本原理
浅谈c++ new and delete or new [] and delete []
malloc(sizeof(0))
sizeof
malloc
inux中fork()函数详解
main()
{
fork();
fork();
fork();
printf("hello world");
}
44.?嵌入式系统通常要求程序员访问特定的内存位置。在某个项目中,需要将绝对地址0x67a9处的整数变量设置为0xaa55。编译器是一个纯ANSI编译器。编写代码来完成这项任务。
45.?中断是嵌入式系统的重要组成部分。因此,许多编译器供应商提供了对标准C的扩展来支持中断。通常,这个新关键字是_interrupt。下面的代码使用了_interrupt来定义一个中断服务例程(ISR)。对代码进行注释。
__interrupt double compute_area(double radius)
{
double area = PI * radius *
radius;
printf("\nArea = %f", area);
return area;
}
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") :puts("<= 6");
}
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/1‘s complement of zero /
int a = 5, b = 7, c;
c = a+++b;
Create a design that could be implemented to produce the game snakes and??ladders.
标签:efi nis 执行 文件格式 cond 难点 概念 var clear
原文地址:https://www.cnblogs.com/codeAndlearn/p/11827160.html