• c++ 面试基本知识点整理(1)


    类与对象的区别是什么?

    目录

    • [ ]1.指针与引用(难点!)
      . 类与对象的区别是什么?
      类与对象的区别是什么?
      答:(1)类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。
        (2) 对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。
      (3)类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类.类描述了一组有相同特性(属性)和相同行为(方法)的对象

    2. 什么是虚拟构造函数以及析构造函数

    什么是虚拟构造函数以及析构造函数

    2.1 虚函数的实质

    虚函数之所以能够做到动态调用,是因为程序在执行阶段才确定调用,也就是晚绑定。而早绑定在编译阶段就已经确定下一步该调用哪个函数。
    晚绑定的本质是:当实例化一个带有虚函数(继承下来的虚函数也可以)类对象时,编译器会生成一个VPTR指针和VTABLE表,VTABLE表中存放所有“虚函数地址”;VPTR指向VTABLE的首地址。不管对象如何被强换(子类转换为基类),还是在传引用或传指针的过程中,它的地址都不会变;只要我们握有对象的地址,就可以通过对象地址找到VPTR,通过VPTR找到VTABLE,通过VPTABLE找到虚函数,从而调用正确的虚函数。
    VPTR的位置都一样,一般都在对象的开头。VTABLE其实就是一个函数指针的数组,VPTR正指向VTABLE的第一个元素(第一个虚函数);如果VPTR向后偏移一个位置,那么它应该指向了VTABLE中的第二个函数了。
    (注:如果子类没有实现虚函数,会继承基类的虚函数,依然建立自己的虚函数表;如果子类有新的虚函数,会添加到虚函数表中)

    2.2 基类的析构函数必须用虚函数

    基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。

    2.3 以下函数不能使用虚函数

    1)普通函数

    2)友元函数

    在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。
     具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。

    3)静态成员函数

    静态成员函数可以不通过类来调,但是虚函数一定要用类来调

    4)构造函数,拷贝函数

    还没有对象,就没有指向虚表的指针,就不可以通过虚表去调用,这是一个先有鸡还是先有蛋的问题

    5)内联函数;

    inline函数在编译时被展开,在调用处将整个函数替换为代码块,省去了函数跳转的时间,提高了SD,减少函数调用的开销,虚函数是为了继承后对象能够准确的调用自己的函数,执行相应的动作。
    主要的原因是:inline函数在编译时被展开,用函数体去替换函数,而virtual是在运行期间才能动态绑定的,这也决定了inline函数不可能为虚函数。(inline函数体现的是一种编译机制,而virtual体现的是一种动态运行机制

    注意:派生类里的析构函数最好给成虚函数。否则派生类中如有空间的开辟那么有可能造成内存泄露?

    3. 如何定义一个抽象类

    如何定义一个抽象类

    1.纯虚函数定义

    纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”

    2.引入原因

    1. 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
    2. 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。     为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

    3.抽象类

    1. 带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限
    2. 抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。   
      3.抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。
    3. 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个 抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。

    4.抽象类的规定

    1. 抽象类只能用作其他类的基类,不能建立抽象类对象。
    2. 抽象类不能用作参数类型、函数返回类型或显式转换的类型。
    3. 可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性

    5. 纯虚函数声明

    纯虚函数的声明有着特殊的语法格式:virtual 返回值类型成员函数名(参数表)=0;

    6.虚函数和纯虚函数有以下所示方面的区别

    (1)类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样的话,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
    (2)虚函数在子类里面也可以不重载的;但纯虚函数必须在子类去实现,这就像Java的接口一样。通常把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为很难预料到父类里面的这个函数不在子类里面不去修改它的实现。
    (4)带纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。这样的类也叫抽象类。抽象类和大家口头常说的虚基类还是有区别的
    在C#中用abstract定义抽象类,而在C++中有抽象类的概念,但是没有这个关键字。抽象类被继承后,子类可以继续是抽象类,也可以是普通类。虚基类:被“virtual”继承的类,也就是说任何类都可以成为虚基类。抽象类:至少包含一个纯虚函数的类,其不能被实例化,哪怕该纯虚函数在该类中被定义。二者没有任何联系。虚基类 就是解决多重多级继承造成的二义性问题

    7.抽象类与接口得区别

    接口是为了约束对象具有的同一类的行为而定义的。
    而继承是为了表现同类对象的实体关系而定义的

    1. 类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类叫做抽象类.而接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”.抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中.
    2. 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;
    3. 一个类一次可以实现若干个接口,但是只能扩展一个父类
    4. 接口可以用于支持回调,而继承并不具备这个特点.
    5. 抽象类不能被密封。
    6. 抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的. 
      7.(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。
    7. 抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。
    8. 好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染
    9. 尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知.(结合堆栈原理理解)。同时,有心的朋友可以留意到微软在构建一个类时,很多时候用到了对象组合的方法。比如asp.net中,Page类,有Server Request等属性,但其实他们都是某个类的对象。使用Page类的这个对象来调用另外的类的方法和属性,这个是非常基本的一个设计原则。   
      11.如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法.

    4. 拷贝构造函数的定义

    4.1 拷贝构造函数的使用

    拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)

    4.2 使用场景

    当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:

    1. 一个对象以值传递的方式传入函数体
    2. 一个对象以值传递的方式从函数返回
    3. 一个对象需要通过另外一个对象进行初始化。

    4.3 编译原理

    如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。
    自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。

    4.4 浅拷贝与深拷贝

    1. 在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。  
    2. 深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。下面举个深拷贝的例子。
    #include <iostream>
    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;
    } 
    
    ``````  c++
    #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;
    } 
    

    5. 重载与重写的区别

    Overload:顾名思义,就是Over(重新)——load(加载),所以中文名称是重载。它可以表现类的多态性,可以是函数里面可以有相同的函数名但是参数名、返回值、类型不能相同;或者说可以改变参数、类型、返回值但是函数名字依然不变。Override:就是ride(重写)的意思,在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。
    相当佩服第一个把这两个词翻译过来的人,相当贴切!方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了,而且如果子类的方法名和参数类型和个数都和父类相同,那么子类的返回值类型必须和父类的相同;如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。也就是说,重载的返回值类型可以相同也可以不同。

    overloading

    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;
    }
    

    overriding

    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;
    }
    

    6. extern 与 static

    extern 与 static

    C语言代码是以文件为单位来组织的,在一个源程序的所有源文件中,一个外部变量(注意不是局部变量)或者函数只能在一个源程序中定义一次,如果有重复定义的话编译器就会报错。伴随着不同源文件变量和函数之间的相互引用以及相互独立的关系,产生了extern和static关键字。

    ## 一,static全局变量
    下面,详细分析一下static关键字在编写程序时有的三大类用法:        一,static全局变量           我们知道,一个进程在内存中的布局如图1所示:0f94bebca90657bfab9617d603fb665b.gif
          其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量(其他段中还有很多乱七八糟的段,暂且不表)。在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝。     当一个进程的全局变量被声明为static之后,它的中文名叫静态全局变量。静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效,其他源文件无法访问它。所以,普通全局变量穿上static外衣后,它就变成了新娘,已心有所属,只能被定义它的源文件(新郎)中的变量或函数访问。

    二,static局部变量

    普通的局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放之。       static局部变量中文名叫静态局部变量。它与普通的局部变量比起来有如下几个区别:          
    1)位置:静态局部变量被编译器放在全局存储区.data(注意:不在.bss段内,原因见3)),所以它虽然是局部的,但是在程序的整个生命周期中存在。           
    2)访问权限:静态局部变量只能被其作用域内的变量或函数访问。也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数和源文件访问。          
    3)值:静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。

    7. 预处理器、编译器、汇编器和链接器的工作是什么?

    预处理器、编译器、汇编器和链接器的工作是什么?

    hello程序的生命周期是从一个源程序(hello.c)(称为高级c语言)开始,被其它程序转化为一系列的低级机器语言指令,这些指令按照一种称为可执行目标程序的格式打包好,以二进制磁盘文件的形式保存。  例:unix> gcc -o hello hello.c可以实现源文件向目标文件的转化,该过程由编译程序完成。  hello.c  ---->hello.i  ---->hello.s  ---->hello.o  -->hello3c712544ec382f526d50b7b6779c9516.png
    拿一个简单的例子,例子叫做Base.c,内容如下:#include <stdio.h>/这是一条注释/int main(){printf("Hello world ");return 0;}
    (1). 预处理(cpp):预处理器不止一种,而C/C++的预处理器就是其中最低端的一种——词法预处理器,主要是进行文本替换、宏展开、删除注释这类简单工作。gcc -E 选项可以得到预处理后的结果,扩展名为.i;C/C++预处理不做任何语法检查,不仅是因为它不具备语法检查功能,也因为预处理命令不属于C/C++语句(这也是定义宏时不要加分号的原因),语法检查是编译器要做的事情;预处理之后,得到的仅仅是真正的源代码;GCC确实很强大,如果是用VC这种IDE,恐怕就不能看到预处理后的结果。    e.g. 所谓预处理,就是把程序中的宏展开,把头文件的内容展开包含进来,预处理不会生成文件,所以需要重定向fd0b505a91edae0bd1a7c186fe84d7f8.png
    (2). 编译器(ccl):将文本文件.i翻译成文本文件.s,得到汇编语言程序(把高级语言翻译为机器语言),该种语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。gcc -S 选项可以得到编译后的汇编代码,扩展名为.s;汇编语言为不同高级语言的不同编译器提供了通用的输出语言,比如,C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。e.g.9acd9303b416827125b3441c80024db9.png
    (3). 汇编(as):将.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中(把汇编语言翻译成机器语言的过程)。gcc -c 选项可以得到汇编后的结果,扩展名为.o;.o是一个二进制文件,它的字节编码是机器语言指令而不是字符。如果在文本编辑器中打开.o文件,看到的将是一堆乱码。把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。e.g.6401599175ff51010634b5808a6da2bf.png
    (4).链接(ld):gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去。 函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。c0a07f72a43c784ee8d7c13bd58cd20a.png

    8. 静态库与动态链接库的区别

    博客

    1.静态库

    之所以成为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:l  静态库对函数库的链接是放在编译时期完成的。l  程序在运行时与函数库再无瓜葛,移植方便。l  浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

    Linux下使用ar工具、Windows下vs使用lib.exe,将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索。一般创建静态库的步骤如图所示:a531b64c631dd3864c379a63420a299c.png
    图:创建静态库过程

    2.动态链接库

    动态库通过上面的介绍发现静态库,容易使用和理解,也达到了代码复用的目的,那为什么还需要动态库呢?为什么还需要动态库?为什么需要动态库,其实也是静态库的特点导致。l  空间浪费是静态库的一个问题。3942847132da489a2e8aca5f1b567d8b.png
    l  另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。e2a2ab4e7d1d7114b217d39d1c9dcada.png
    动态库特点总结:l  动态库把对一些库函数的链接载入推迟到程序运行的时期。l  可以实现进程之间的资源共享。(因此动态库也称为共享库)l  将一些程序升级变得简单。l  甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。

    9. 线程与进程的区别

    根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

    1. 在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

    2. 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

    3. 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

    包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程
    进程是资源分配最小单位,线程是程序执行的最小单位;

    1. 进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;

    5.CPU切换一个线程比切换进程花费小;

    6.创建一个线程比进程开销小;

    7.线程占用的资源要⽐进程少很多。

    8.线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)

    9.多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);

    10.进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

    10. What are the three types of test cases you should go through when  unit testing?

    11 How can you define a structure with bit field members?

    12. Write Unix script to find the specific string from the given file?

    13. How to find a child process in Unix?

    14. What is difference between named pipes and unnamed pipes?

    C++语言定义中说,每一种指针类型都有一个特殊值----"空指针"。
      空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数;而未初始化的指针则可能指向任何地方。
      空指针不是野指针。每种指针类型都有一个空指针,而不同类型的空指针内部表示可能不尽相同。尽管程序员不必知道内部值,但编译器必须时刻明确需要哪种类型的空指针,以便在需要时加以区分。
    (4.1)你可以认为 NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL会比 0 有更好的兼容性,但事实并非如此。
       (4.2)尽管符号常量经常代替数字,以备数字的变化,但这不是NULL 替代 0 的原因。语言本身确保了源码中的 0(用于指针上下文)会生成空指针。NULL只是用做一种格式习惯。

    5)NULL可以确保是零,但空指针却不一定零
         空指针的内部(或者运行前)表达式可能不完全是零,而且对不同的指针类型可能不一样。真正的值只有编译器的开发者才关心。C++程序的作者永远看不到它们,这一点不用担心,明白就好。
    【注意】
    (5.1)空指针不一定是0,而NULL肯定是0.
    (5.2)赋值为空指针的变量,可确保变量不指向任何对象或函数。合理地使用空指针可以有效地避免内存泄露,提高程序的执行效率。

    16. What is your understanding of this statement?

    ls | grep a

    17. In Unix, what is a defunct process?

    杀死失效进程

    18. What is the difference between hard real-time and soft real-time  OS?

    19. What type of scheduling is there in RTOS?虚拟内存是

    虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。

    虚拟内存别称虚拟存储器(Virtual Memory)。电脑中所运行的
    ca643ce04309c8aabd12196066c804f6.jpeg

    程序均需经由内存执行,若执行的程序占用内存很大或很多,则会导致内存消耗殆尽。为解决该问题,Windows中运用了虚拟内存技术,即匀出一部分硬盘空间来充当内存使用。当内存耗尽时,电脑就会自动调用硬盘来充当内存,以缓解内存的紧张。若计算机运行程序或操作所需的随机存储器(RAM)不足时,则 Windows 会用虚拟存储器进行补偿。它将计算机的RAM和硬盘上的临时空间组合。当RAM运行速率缓慢时,它便将数据从RAM移动到称为“分页文件”的空间中。将数据移入分页文件可释放RAM,以便完成工作。 一般而言,计算机的RAM容量越大,程序运行得越快。若计算机的速率由于RAM可用空间匮乏而减缓,则可尝试通过增加虚拟内存来进行补偿。但是,计算机从RAM读取数据的速率要比从硬盘读取数据的速率快,因而扩增RAM容量(可加内存条)是最佳选择。 [2] 虚拟内存是Windows 为作为内存使用的一部分硬盘空间。虚拟内存在硬盘上其实就是为一个硕大无比的文件,文件名是PageFile.Sys,通常状态下是看不到的。必须关闭资源管理器对系统文件的保护功能才能看到这个文件。虚拟内存有时候也被称为是“页面文件”就是从这个文件的文件名中来的。 [2] 内存在计算机中的作用很大,电脑中所有运行的程序都需要经过内存来执行,如果执行的程序很大或很多,就会导致内存消耗殆尽。为了解决这个问题,WINDOWS运用了虚拟内存技术,即拿出一部分硬盘空间来充当内存使用,这部分空间即称为虚拟内存,虚拟内存在硬盘上的存在形式就是 PAGEFILE.SYS这个页面文件。 [2] 

    22. What is IOCTL, how it is used in user and kernel driver code?

    23. What is difference between sleep_on ( ) and  interruptible_sleep_on ( )

    24. What is difference between wake_up ( ) and  wake_up_interruptible ( ) APIs in linux kernel ? When should use  which one, how it should be decided?

    25. What are spin lock, are they better then mutex? How SpinkLocks  work in SMP and UP architectures.

    自旋锁

    26. What are fork exec,IPC? How many types of IPC mechanism you  know? After fork, does new process get file handles and locks?

    Linux下Fork与Exec使用

    26  DMA controller.

    DMA

    28. Cache coherency- MESI /MSI protocol

    MSI协议

    29 Can u have reentrant code inside interrupt handler?

    30 What will happen/can u have printf/printk inside an interrupt  handler?

    Linux中断(interrupt)子系统之一:中断系统基本原理

    31. What is context switch? When do u need it?

    进程切换之context_switch详解

    32 How to check whether a linked list is circular?

    什么是linkedlist?

    33. What is difference between delete and delete [];

    浅谈c++ new and delete or new [] and delete []

    34 How do you write a function which takes 2 arguments - a byte and a  field in the byte and returns the value of the field in that by

    35. 空指针的用法

    空指针的用法

    36. What does the following code mean?

    malloc(sizeof(0))
    sizeof
    malloc

    36 What do the following codes mean?

    inux中fork()函数详解
    main()
    {
    fork();
    fork();
    fork();
    printf("hello world");
    }

    38. Which way of writing infinite loops is more efficient than others?

    39. Using the #define statement, how would you declare a manifest  constant that returns the number of seconds in a year? Disregard  leap years in your answer.

    40. Write the "standard" MIN macro-that is, a macro that takes two  arguments and returns the smaller of the two arguments.

    C/C++-技巧-宏

    41. Infinite loops often arise in embedded systems. How do you code an  infinite loop in C?

    嵌入式死循环

    42. const用法

    • const int a;
    • int const a;
    • const int *a;
    • int * const a;
    • int const * a const;

    43. Embedded systems always require the user to manipulate bits in  registers or variables. Given an integer variable a, write two code  fragments. The first should set bit 3 of a. The second should clear  bit 3 of a. In both cases, the remaining bits should be unmodified.

    43。嵌入式系统总是要求用户操作寄存器或变量中的位。给定一个整数变量a,编写两个代码片段。第一个应该设置a的第3位,第二个应该清除a的第3位。

    44. Embedded systems are often characterized by requiring the  programmer to access a specific memory location. On a certain  project it is required to set an integer variable at the absolute  address 0x67a9 to the value 0xaa55. The compiler is a pure ANSI  compiler. Write code to accomplish this task.

    44. 嵌入式系统通常要求程序员访问特定的内存位置。在某个项目中,需要将绝对地址0x67a9处的整数变量设置为0xaa55。编译器是一个纯ANSI编译器。编写代码来完成这项任务。

    45. Interrupts are an important part of embedded systems.  Consequently, many compiler vendors offer an extension to standard  C to support interrupts. Typically, this new keyword is  __interrupt. The following code uses __interrupt to define an  interrupt service routine (ISR). Comment on the code.

    45. 中断是嵌入式系统的重要组成部分。因此,许多编译器供应商提供了对标准C的扩展来支持中断。通常,这个新关键字是_interrupt。下面的代码使用了_interrupt来定义一个中断服务例程(ISR)。对代码进行注释。

    __interrupt double compute_area(double radius)
    {
    double area = PI * radius *
    radius;
    printf("
    Area = %f", area);
    return area;
    }
    
    

    46. What does the following code output and why?

    void foo(void)
    {
    unsigned int a = 6;
    int b = -20;
    (a+b > 6) ? puts("> 6") :puts("<= 6");
    }

    47. Comment on the following code fragment.

    unsigned int zero = 0;
    unsigned int compzero = 0xFFFF;
    /*1's complement of zero */

    48. C allows some appalling constructs. Is this construct legal, and  if so what does this code do?

    int a = 5, b = 7, c;
    c = a+++b;
    Create a design that could be implemented to produce the game snakes and  ladders.

  • 相关阅读:
    代码手动修改约束(AutoLayout)
    iOS开发中手机号码和价格金额有效性判断及特殊字符的限制
    Mac下如何显示隐藏文件/文件夹
    Exp8 Web综合 20181308
    20181308 邵壮 Exp7 网络欺诈防范
    20181308 邵壮 Exp6 MSF应用基础
    重现Vim任意代码执行漏洞(CVE-2019-12735) 20181308
    密码引擎-加密API研究 20181308邵壮
    密码引擎-加密API实现与测试 20181308邵壮
    Exp5 信息搜集与漏洞扫描 20181308邵壮
  • 原文地址:https://www.cnblogs.com/codeAndlearn/p/11827160.html
Copyright © 2020-2023  润新知