zoukankan      html  css  js  c++  java
  • 面试笔试大概会出现的问题其一

    下面这些是我在面试和笔试的时候碰到过的比较常见的问题,有些不难,但是就内容太多了,一时难以想起。

    1.公有私有保护成员、公有私有保护继承

      公有成员:在类外可以通过对象直接访问。

      私有成员:对外隐藏,对子类开放,在类内部使用(在类的成员函数中使用)。

      保护成员:对外隐藏,只在类内部使用(在类中的成员函数使用)。

      公有继承:基类的公有成员和保护成员作为派生类的成员时,都保持原有的状态,但是基类的私有成员仍然是私有的,不能被子类访问。

      私有继承:基类的公有成员和保护成员成为了派生类的私有成员,并且不能被派生类的子类访问。

      保护继承:基类的公有成员和保护成员都成为了派生了的保护成员,只能被派生类的成员函数或者友元访问,基类的私有成员仍然是私有的。

    2.什么是多态?多态的实现原理是什么?

      多态就是在基类的函数前面加上virtual关键字,然后在派生类中重写这个函数(覆盖虚表),运行的时候就会根据实际的对象类型来调用相应的函数,如果是派生类,就调用派生类的函数,如果是基类,就调用基类的函数。

    3.数组与链表的异同

      数组是将元素在内存中连续存放的,每个元素占用的内存相同,所以可以通过数组下标快速访问数组中的元素,如果在数组中增加一个元素,那么就要移动大量的数据,删除也是一样。如果是需要快速访问,但是很少插入和删除的数据,就应该用数组。

      链表中的元素在内存中是不按顺序存放的,是通过元素中的指针练习到一起,每个节点包括两部分,元素数据和指向下一个节点地址的指针。如果要访问链表中的元素,需要从第一个元素开始,一直找到元素的位置,插入和删除对于链表十分简单。如果是需要经常插入和删除的数据,用链表就比较方便。

    4.什么是线性表?线性表有哪些种类?

      线性表是数据结构的一种,一个线性表是n个具有相同特性的数据元素的优先序列。线性表有以下特点:

      (1)集合中必然存在唯一的一个“第一个元素

      (2)集合中必然存在唯一的一个"最后一个元素"

      (3)除最后一个与元素之外,每个元素都有唯一的后继

      (4)除第一个元素之外,每个元素都有唯一的前驱。

      线性表分为顺序存储和连式存储。

      顺序存储:顺序表,使用数组实现,一组连续的存储单元,数组的大小有两种方式制定,静态分配和动态扩展。

        优点:随机访问特性,查找O(l)时间,存储密度高,逻辑上相邻的元素,物理上也相邻。

        缺点:插入和删除需要移动大量的元素。

      链式存储:单链表、双链表、循环链表、静态链表。

    5.队列与栈的异同

      栈和队列都是线性表,都是限制了插入删除点的线性表。

      共同点:都是只能在线性表的端点插入和删除。

      不同的点:栈的插入和删除都是在线性表的同一个端点,俗称栈顶,不能插入和删除的端点称为栈顶,特性是先进后出。

           队列是在线性表的表头插入,表位删除,表头一般称为对头,表位称为队尾,特性是先进后出。

    5.获取触摸屏信息的函数(项目)

      linux系统中有个input_event()函数可以获取触摸屏的触摸数据,可以获取的事件类型有按键事件、同步事件、相对坐标事件、绝对坐标事件。

    6.linux系统文件类型

      普通文件(-):ELF文件,文本文件

      目录文件(d)

      字符设备文件(c):访问字符设备

      块设备文件(b):访问块设备

      链接文件(l):相当于快捷方式

      管道文件(p):用于管道通信

      套接字文件(s):用于socket通信

    7.TCP/UDP通信

      TCP的特点:面向连接,只支持一对一,面向字节流,可靠的传输

      UDP的特点:无连接,不可靠传输,支持一对一,一对多,面向报文

      TCP为什么是可靠传输:

        (1)通过TCP连接传输的数据无差错,不丢失,不重复,且按顺序到达。

        (2)TCP报文头里面的序号能使TCP的数据按序到达

        (3)报文头里的确认序号能保证不丢包,累计确认及超时重传机制。

        (4)TCP拥有流量控制及拥塞控制的机制。

    8.TCP的三次握手/四次挥手

      三次握手:

        (1)SYN请求建立连接:客户端向服务端发送请求报文;即SYN=1,ACK=0,seq=x。建立连接时,客户端发送SYN包(syn=j)到服务端,并进入SYN_SENT状态,等待服务器确认

        (2)ACK针对SYN的确认应答,SYN(请求建立连接):服务器收到SYN包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYNC_RECV状态

        (3)ACK针对SYN的确认应答:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务端进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

        这样连接建立完成,发送数据

      四次握手:

        (1)FIN请求切断连接:TCP发送一个FIN(结束),用来关闭客户到服务端的连接。客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

        (2)ACK针对FIN的确认应答:服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

        (3)FIN请求切断连接:服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

        (4)ACK针对FIN的确认应答:客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

      

    9.CJSON

    10.进程与线程

      进程是系统资源分配和调度的基本单位,是操作系统结构的基础。实际上就是在系统中正在运行的一个应用程序,程序一旦开始运行就是进程,进程----分配资源的最小单位。

      线程是操作系统进行运算调度的基本单位。系统分配处理器时间资源的基本单元,或者说是进程之类独立执行的一个单元执行流。

      1、一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动。

      2、资源分配给进程,同一个进程的所有线程共享该进程所有资源。
      3、CPU分配给线程,即真正在处理器运行的是线程。
      4、线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。

    11.算法复杂度

      

      稳定:如果a原本在b的前面,而a=b,排序之后a仍然在b的前面。

      不稳定:同上,但是排序后a在b的后面。

      时间复杂度:对排序算法的总操作次数,反应出当要排序的数列长度n变化时,操作次数呈现的规律。

      空间复杂度:指的是算法在计算机内执行的时候所需的存储空间的度量,也是数据规模为n的函数。

    12.什么事内存泄漏?内存泄漏有哪些?怎么避免内存泄漏?

      内存泄漏值得就是在程序动态申请的内存在使用完之后,却没有进行释放,导致这片内存没有被系统回收,就会占用着内存,导致程序的内存变大,系统内存不足。

      内存泄漏的分类如下:

        堆内存泄漏:堆内存指的就是通过new,malloc等函数从堆中申请的堆存,用完之后必须通过delete,free释放掉,如果没有释放,就会导致这片空间没有办法被再次使用。

        系统资源泄漏:程序在使用系统分配的资源(套接字、管道、文件描述符等)过后,没有释放,会导致资源浪费,甚至导致系统不稳定。

      避免内存泄漏的方法:(1)养成良好的编码规范,申请的空间用完之后记得释放。(2)采用智能指针来管理资源。(3)使用检测工具来检测内存是否泄漏。

    13.拷贝构造是什么?为什么要有拷贝构造?拷贝构造分为哪些类型?

      对于普通的数据类型来说,他们之间的赋值是很简单的,但是对于有着复杂内部结构的类对象来说,拷贝就很麻烦了。拷贝构造函数是一种特殊的构造函数,函数名必须和类名一致,必须的参数是本类型的一个引用变量。很多时候编译器会默认生产一个“默认的拷贝构造函数”,这个生成的函数很简单,只是对旧对象的数据成员的值进行拷贝,用来给新对象的数据成员进行赋值。

      拷贝构造函数分为浅拷贝和深拷贝,浅拷贝值得是在对象复制的时候,只是对对象中的数据成员进行简单的赋值,系统自动生成的拷贝构造函数就是浅拷贝,一般来说,浅拷贝都足够用了,但是当对象中存在动态成员,浅拷贝就会出现问题。这时候就要用到深拷贝了,深拷贝中,对于对象中的动态成员,会重新动态分配空间。当对象成员中有指针的时候,就要用到深拷贝,因为浅拷贝在对象结束的时候,会调用两次析构函数,但是只有一个东西要析构,就会造成指针悬挂(指针指向非法的地址)的现象。类中可以存在多个拷贝构造函数。

    14.什么是智能指针?智能指针有哪些?

      智能指针是一个类,类中的构造函数传入一个普通指针,析构函数释放传入的指针,这个类是栈上的对象,当程序或者函数结束的时候就会自动释放。

      常用的智能指针有:(1)std::suto_ptr  不支持赋值和复制,编译的时候也不会报错(2)unique_ptr  同样不支持赋值和复制,但是编译的时候会报错  (3)shared_ptr  可以随便赋值,当内存的引用计数变成0的时候,会被释放  (4)weak_ptr  

    15.虚函数

      用virtual关键字修饰的成员函数,如果一个类中包含虚函数,那么这个类的对象中就会包含一个虚表指针(存储在对象的最前面)。虚表是用来存储虚函数指针的。虚函数的调用过程:开始从虚表中查找,如果找到就执行,没有找到就去对象代码中找,找到就执行,还是没有找到就报错。

      覆盖:如果父类中的虚函数在子类中重新实现(同名同参),虚表中的虚函数就会被子类的虚函数覆盖(只覆盖虚表),如果子类添加新的虚函数,该函数就会添加在虚表的后面。

    17.什么是纯虚函数?

      定义了但是不实现的虚函数叫做纯虚函数,纯虚函数相当于接口函数,如果一个类中包含了纯虚函数,那么这个类就是抽象类。

    18.什么是抽象类?

      抽象类不能创建对象(接口),如果基类中的纯虚函数在派生类中没有全部实现,那么派生类还是抽象类。

    19.什么是虚析构?

      在多态的实现过程中,把子类的指针赋值为父类指针,最后delete父类的指针,这个时候就只会调用父类的析构函数,不会调用子类的自购函数,如果把父类的析构函数设置成虚析构,那么delete就会自动从子类开始析构。

    20.static关键字的作用

      static修饰的特点:

        (1)数据存储在数据段(局部全局)

        (2)只分配一次空间,也只初始化一次

        (3)修饰的变量或者函数只能在本文件中使用(文件作用域)

      static修饰的类型:

        (1)修饰成员变量

          1.static修饰的成员变量空间不在对象空间中

          2.static修饰的成员变量要在类外初始化(只初始化一次)

          3.static修饰的成员变量就是为这个类的对象所共用

          4.如果成员变量是公有的,就可以通过类名直接调用(先于类对象而存在)

        (2)修饰成员函数

          1.如果成员函数是公有的,就可以通过类名直接使用(先于类对象而存在)

          2.静态成员函数中不能使用非静态的成员

    21.const关键字的作用

      (1)修饰变量,将这个变量变成常量,取代了C中的宏定义

      (2)修指针和常量

      (3)修饰函数传入的参数

      (4)修饰函数返回值

      (5)修饰成员函数和对象,const对象只能访问const成员函数,而非const对象可以访问任意的成员函数。conts对象的成员是不能修改的,但是通过指针维护的对象可以修改。const成员函数不能改变对象的值,不管这个对象是否被const修饰。

    22.网络的七层模型、五层模型

      OSI参考模型,从上到下是:

        应用层:与其他计算机进行通讯的一个应用,解决通信双方数据传输的问题,传输协议有:HTTP FTP TFTP SMTP DNS HTTPS

        表示层:定义数据格式以及加密,加密格式有JPEG,ASCII,DECOIC

        会话层:定义了如何开始、控制和终止一个会话

        传输层:定义传输数据协议的端口号、流控和差错校验,协议有TCP,UDP

        网络层:进行逻辑地址寻址,实现不同网络之间的路径选择  协议有ICMP,IGMP,IP(IPV4,IPV6),ARP,RARP

        数据链路层:建立逻辑连接,进行硬件地址寻址,用MAC地址访问介质,但是发现错误不能纠正。

        物理层:建立、维护和断开连接。

      五层模型:

        应用层:为应用软件提供服务,屏蔽了网络传输的相关细节。

        传输层:向用户提供一个可靠的端到端的服务,屏蔽了下层通信的细节。

        网络层:为数据在节点之间传输创建逻辑链路。

        数据链路层:在通信的实体之间简历数据链路连接。

        物理层:定义物理设备之间如何传输数据。

    23.select、pool、epoll的区别与优缺点

      I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。select,poll,epoll都是IO多路复用的机制。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

      select

        原理:select 的核心功能是调用tcp文件系统的poll函数,不停的查询,如果没有想要的数据,主动执行一次调度(防止一直占用cpu),直到有一个连接有想要的消息为止。从这里可以看出select的执行方式基本就是不同的调用poll,直到有需要的消息为止。

        缺点:1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;

           2、同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;

           3、select支持的文件描述符数量太小了,默认是1024。

        优点:1、select的可移植性更好,在某些Unix系统上不支持poll()。

           2、select对于超时值提供了更好的精度:微秒,而poll是毫秒。

       poll

        原理:poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

        缺点:1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义;

           2、与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

        优点:1、poll() 不要求开发者计算最大文件描述符加一的大小。

           2、poll() 在应付大数目的文件描述符的时候速度更快,相比于select。

           3、它没有最大连接数的限制,原因是它是基于链表来存储的。

       epoll

        原理:epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时, 返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一 个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射技术,这 样便彻底省掉了这些文件描述符在系统调用时复制的开销。

        优点:1、支持一个进程打开大数目的socket描述符

           2、IO效率不随FD数目增加而线性下降

           3、使用mmap加速内核与用户空间的消息传递

      三者的对比与区别:

        1、select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

       2、select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

    24.SQL数据库有哪些数据类型?有哪些特殊的关键字?

    char(n) 存放固定长度的字符串,用户指定长度为n。如果没有使用n个长度则会在末尾添加空格。
    varchar(n) 可变长度的字符串,用户指定最大长度n。char的改进版,大多数情况下我们最好使用varchar。
    int 整数类型
    smallint 小整数类型
    numeric(p,d) 定点数,精度由用户指定。这个数有p位数字(包括一个符号位)d位在小数点右边。
    real ,double precision 浮点数和双精度浮点数。
    float(n) 精度至少位n位的浮点数

       部分关键字:SELECT、INSERT、DELETE、UPDATE

      特殊的关键字:primary key、foreign key references、not null

    25.sizeof  strlen的使用

      sizeof可以用来计算数据在内存中占用的存储空间,以字节为单位进行计数。sizoef(int)=4;sizeof(指针)=系统位数=4(32位系统)

      strlen用来计算指定字符的长度,但是不包括结束字符“ ”

    26.switch的使用

      switch中的case只能用来判断int型数据。

    27.与或非

      逻辑的与或非

        && 与操作,只有当两者都为真的时候,才会返回true,表达式会先去验证前面的一部分,如果前面一部分为真才回去验证后面的一部分,否则直接返回false。

        || 或操作,只要符号前后有一个为真,就会返回true,否则返回false。也是先验证前面的,前面的为false才会验证后面的,否则直接返回true。

        !逻辑非,true变成false,false变成true。

      按位与或非

        &、|、~  分别是按位与或非

    5&6 = 0101 & 0110 = 4
    5&&6  -> 计算原则: 非0即真   真&&真=5|6 = 0101 | 0110 = 7
    5||6  -> 计算原则: 非0即真   真||真=真

    28.数组、指针、数组指针、指针数组、函数指针、二级指针

       之前做过一个公司的题目,就40道选择题,20几道指针,指来指去,指的我头皮发麻。

      数组:

      (1)数组只有初始化的时候可以整体赋值,之后只能一个一个单独赋值了。

       (2)与普通变量一样,int数组在定义的时候不初始化,如果是全局变量,就全是0,如果是局部变量,就全是随机值。

      (3)数组名  当sizeof()作用于数组名的时候,数组名就是整个数组的内存空间,返回值就是整个数组的大小;当数组名没有被sizeof()修饰的时候,数组名就是数组元素的首地址。

    int A[3];
    A     数组名
    A[0]    数组首元素
    &A[0]    数组首元素的地址
    结论:A=&A[0]

      指针

        定义:指针指的是内存上的地址,例如:0x400000,指针是一个唯一的地址,能够确定申请到的内存空间在哪里。

        指针变量:专门用来存放地址的变量,例如:int *p;

        指针定义的步骤:

    1)先写一个*2)在*后面写一个指针变量名(*p)
    (3)确定该指针所指向的内容  例如:int a
    (4)将第三部的变量名去掉 例:int5)将第二部的结果放在第四部的结果后面 例:int *p,即指向一个int类型数据的指针变量

        结果:int *p;//指针变量,变量名:p;数据类型:int *;

    int a=100;
    int *p = &a;//将a的地址取出来给指针p

        已知指针变量的值,如果求出该指针指向的内容?

          取址符:&  已知变量a,求地址:&a

          解引用:*  已知指针p,求变量:*p

          &与*是一对逆运算。

        指针运算,加减法:     

    加法:
    int
    a; int *pa = &a; pa+1=? //+1是指向上移动一个单位,-是指向下移动,由于pa指向的是int型数据,pa+1就是向上移动一个单位
    //移动4个字节,移动的字节数是根据数据类型来确定的
    减法:
    int a,c;
    int *pa = &a;
    int *pb = &c;
    pa-pb=?;//结果就是两个指针在内存地址中相差多少个int型的字节

      复杂指针:二级指针、数组指针、函数指针。

      (1)二级指针:指向指针的指针(一般用二级指针,一般人不会闲得蛋疼去用三级、四级指针)   

    int a = 100;//在内存中连续申请4个字节的空间,使用变量a间接访问这片空间,将常量区100赋值给a。
    
    int *pa = &a;//在内存中连续申请四个字节的空间,使用变量pa间接访问这片空间,将整型变量a的地址赋值给pa,即pa所指向的内存地址中存储的内容是变量a 的地址。
    
    int **px = &pa;//在内存中连续申请4个字节的空间,使用变量px间接访问这片内存空间,将指针变量pa的地址赋值给px,即px所指向的内存空间存储的内容是指针pa的地址。

      (2)数组指针:指向数组的指针。

    int a;          int A[3];
    
    int *pa = &a;        int (*pa)[3] = A[3];//表示这个指针指向一个具有三个int型数据的数组   

         解引用:

    int a=100int *pa = &a;//*pa得到100,
    int A[3] = {100,200,300}; 
    int (*p)[3] = &A;

          问题一:*p得到什么?得到数组首元素的地址,*p = *(&A) = A = &A[0]

           问题二:p[0]/p[1]得到什么?什么也得不到,p[0] = *(p+0) = *p = &A[0],p[1] = *(p+1) = *(&A+1);//此时p+1已经越界访问了,解引用就是访问未知区域了。

           问题三:(*p)[1]得到什么?(*p)[1] = (*&A)[1] = A[1] = 200

          做指针运算的时候,记住两个公式:(1)&与*是一对逆运算,可以约掉;

                           (2)a[n]=*(a+n)

      (3)函数指针:指向一个函数的指针。

          定义函数指针:

    int a;      int fun(int x,int y)
    
    int *pa = &a;  int (*p)(int,int) = &fun;//这个指针指向一个返回值为int类型,形式参数有两个int型数据的函数

            数据类型:int (*p)(int,int)  变量名:p

          在C语言中,函数的名字实际上就是函数的地址,int fun(int x,int y),函数名fun等价于&fun,即 int(*)(int,int)=&fun;

          函数指针很少单独使用(定义一个指针,通过指针来调用函数),一般是用于接收函数的地址,及传递一个函数给另一个函数作为参数,形式参数就是一个函数指针。

    29. 指针的用法

      (1)在函数调用的时候改变某个变量的值。

        经典例子:int *p;func(&p);如果这里想要改变p的值,就必须传p的地址,而p本身就是个指针,就有了二级指针的概念,二级指针一般只会出现在函数的形式参数列表中,不会随意定义。

      (2)访问匿名内存的时候。

        在C语言中,由于立即寻址空间访问硬件以及资源的原理,很多资源只是有着具体的地址,但是却没有变量名(能够用名字访问内存的只有局部变量或者全局变量(函数名是一个地址)),因此我们经常直接引用地址。例如:某GPIO数据寄存器的内存地址是0x12345678,我们会将该地址转化为指针,然后直接解引用该内存地址。

    #define GPIO_DATA *((volatile unsigned int*)0x12345678

      (3)优化加速函数调用

    30.指针与数组的异同

      (1)数组名不能给其赋值(指针常量)

      (2)sizeof用来测量数组名,得到的是数组的大小,用来测量指针的话,永远是系统位数那么大

      (3)在函数传参的过程中,函数的形式参数就是一个指针

      (4)数组是不能通过函数返回出去的,语法不会报错,只是没有什么意义,会被释放掉

    31.数据占用的内存空间的大小

    数据类型 32位系统 64位系统
    void  0 0
    char 1 1
    int  4 4
    short int 2 2
    float 4 4
    double 8 8
    long 4 8
    long double 12 16
    long long 8 8
    指针 4 8

    32.大小端存储

      大端存储模式:数据的低位保存在内存的高地址中,数据的高位,保存在内存的低地址中。0x78563412

       小端存储模式:数据的低位保存在内存的低地址中,数据的高位,保存在内存的高地址中。0x12345678

    33.字符串处理函数的底层实现

    char *strcpy(char *dest,const *src);//把后面的src字符串的内容拷贝到dest字符串当中,拷贝结束是以遇到""才停下。
    char *strcat(char *dest,char *src);//将src字符串的内容合并到dest字符串的后面。
    int strcmp(const char *str1,const char *str2);//比较两个字符串是否相等,原理是用字符相减的形式,如果相等,返回值为0,;如果不相等,返回值根据ASCII原理,返回正值(str1>str2),负值(str1<str2)。
    size_t strlen(const char *s);//测量字符串的长度,不包括""结束符,该函数还可以用来测量指针。

      strcpy以及strncpy的实现:

    char *strcpy(char *s1,char *s2)
    {
        char *tmp=s1;
        while(*s1++=*s2++);
        return tmp;
    }
    
    char *strncpy(char *s1,char *s2,size_t n)
    {
        char *tmp=s1;
        while(n)
        {
            if(!(*s1++=*s2++))break;//遇到''就结束循环
            n--;
        }
    
        while(n--)*s1++='';//用''填充剩余的部分
        return tmp;
    }

      strcat以及strncat的实现:

    char *strcat(char *s,char *s)
    {
        char *tmp=s1;
        while(*s1)
            s1++;//前进到s1的末尾处
        while(*s1++=*s2++)
            ;//循环赋值直到遇到s2中的''
        return tmp;
    }
    
    char *strncat(char *s1,char *s2,size_t n)
    {
        char *tmp=s1;
        while(*s1)
            s1++;//前进到s1的末尾
        while(n--)
            if(!(*s1++=*s2++))break;//遇到''就循环结束
        *s1='';
        return tmp;
    }

      strcmp以及strncmp的实现

    int *strcmp(char *s1,char *s2)
    {
        while(*s1==*s2){
            if(*s1=='')
                return 0;
            s1++;
            s2++;
        }
        return (unsigned char)*s1-(unsigned char)*s2;
    }
    
    int *strncmp(char *s1,const char *s2,size_t n)
    {
        while(n && s1 && s2){
            if(*s1 != *s2)
                return (unsigned char)*s1-(unsigned char)*s2;
            s1++;
            s2++;
            n--;
        }
    
        if(!n)return 0;//相等
        if(*s1)return 1;//s1>s2
        return -1;//s1<s2
    }

       strlen的实现

    size_t strlen(const char *s)
    {
        size_t len=0;
        while(*s++)
            len++;
            return len;
    }

    34.简述嵌入式操作系统的软硬件结构,以及它与通用操作系统的区别?以自己熟悉的嵌入式 处理器为例,简述从上电到程序运行的过程。(这个是做广州数控的软件开发笔试时遇到的)

       嵌入式操作系统从组成上可以分为硬件系统和软件系统。硬件系统层由嵌入式微处理器、嵌入式存储器系统、通用设备和I/O接口等组成。软件层由操作系统和应用软件组成。

      嵌入式操作系统与通用操作系统的区别:(1)以应用为中心;(2)以计算机技术为基础;(3)软件和硬件可裁剪;(4)对系统性能要求严格;(5)软件的固件化;(6)需要专门的开发工具

       上电到程序运行的过程:上电-----基础硬件(CPU,时钟,内存,flash,串口初始化)的自启动,将系统硬件环境调整到一个合适的状态,为启动内核做准备。-----加载操作系统到内核,跳转到系统内核代码运行。

    35.volatile关键字

      volatile关键字用于修饰易变的变量,防止编译器优化。编译器在编译时,遇到运算的数据已经存在寄存器中,并且没有被修改,编译器就会直接使用在寄存器中的备份,而不用去内存中再取一次,这种操作就叫编译器的优化。被volatile关键字修饰的变量在使用的时候不允许使用在寄存器中的备份,必须去原始地址读取。

      volatile的三个例子:(1)并行设备的硬件寄存器,存储器映射的硬件寄存器通常加volatile;(2)一个中断服务程序中修改的供其他程序检测的变量;(3)多线程应用中被几个任务共享的变量

       一个变量既可以是const还可以是volatile,例如状态寄存器。一个指针也可以是volatile,例如中断服务子程序中修改一个纸箱buffer的指针。

    36.new可以指定初值吗?new的对象离开作用域之后会被析构吗?

      (根据网上查到的说法)如果new的是普通的数组,例如:int,char这些基本数据类型,数值是随机的;如果new的是对象数组,会被默认的构造函数初始化成默认值。

      new的对象,即使离开了作用域,也会一直存在,必须主动delete,否则会一直到程序结束才会调用析构函数。(内存泄漏)。

    37.算术表达式的前缀、中缀、后缀表达式

      我们日常生活一般使用的是中缀表达式,但是计算机很难理解中缀表达式,需要转换成前缀或者后缀表达式才能计算。

      前缀表达式就是运算符在数字(操作数)的前面。

      "x - + 5 6 7 8"运算步骤:将数字按照从右到左的顺序入栈,按照从左到右的顺序遍历运算符,第一个遇到"+",将栈顶的两个数字取出来,与"+"进行运算,栈顶的元素在运算符的前面,次顶的元素在运算符的后面,5+6运算的结果11再入栈,遇到"-",也是将栈顶的两个元素取出来运算,11-7的结果4再次入栈,最后遇到"x",取出两个元素运算,4x8的结果32就是这个前缀表达式的运算结果。

      后缀表达式就是运算符在数字(操作数)的后面。从左往右操作。

      "1 2 3 + 4 *5 - +"运算步骤:从左往右扫描先碰到+号,取+号前面两个操作数:2,3 得到:2+3.继续往下扫碰到*号,取4 和2+3 得到:(2+3)*4;-号,取(2+3)*4和5得到::(2+3)*4-5;+号:取(2+3)*4-5和1得到:1+(2+3)*4-5

      将中缀表达式转换成前缀表达式:(1)创建两个栈,一个存运算符一个存操作数,运算符(以括号为分界)在栈内遵循越往栈顶优先级不降低的原则进行排列;(2)从左到右扫描中缀表达式,从右边的第一个字符开始判断,如果当前字符是数字,则分配到数据串的结尾并将数字串直接输出;如果是运算符,则比较优先级,如果当前运算符的优先级大于栈顶运算符的优先级(当栈顶是括号的时候直接入栈),则将运算符直接入栈;否则将栈顶运算符出栈并输出,知道当前运算符的优先级大于等于栈顶运算符的优先级(如果栈顶是括号,直接入栈),再将当前运算符入栈。如果是括号,则根据括号的方向进行处理,右括号则直接入栈;否则,遇到与括号前将所有的运算符全部出栈并输出,遇到右括号之后将左右的两括号一起删除。(3)重复上述操作,直至扫描结束,将栈内剩余运算符全部出栈并逆序输出字符串,就转换完成了。

      例子:1+((2+3)*4)-5

      中缀表达式转换成后缀表达式,过程跟前面的差不多,只不过是方向反了,从左往右扫描。

      例子:1+((2+3)*4)-5

    38.属于同一进程的线程共享哪些资源?

       属于同一进程间的线程共享的资源有:(1)堆空间;(2)全局变量,静态变量;(3)文件设备资源;(4)进程的代码段,打开的文件描述符,进程的当前目录

        独享的资源有:(1)栈空间;(2)寄存器,程序计数器pc;(3)线程id,线程优先级;(4)错误返回

    39.进程资源死锁的最小值问题

      某计算机系统有8台打印机,有k个进程竞争使用,每个进程最多需要3台打印机,该系统可能发生死锁的最小值是(C)

      A.2  B.3  C.4  D.5

      解答:每个进程3台,不会产生死锁;对于三个进程,可以有两个进程分别获得3台,使其执行完释放后让第三个进程获得3台,所以也不会产生死锁;对于四个进程,假若每个进程各获得2台而同时需要另外一台,产生了死锁,所以产生死锁的最小值是4。

       类似题型(1):假设现在有P个进程,每个进程最多需要m个资源,并且有r个资源可用。什么样的条件可以保证死锁不会发生
      解:如果一个进程有m个资源它就能够结束,不会使自己陷入死锁中。因此最差情况是每个进程有m-1个资源并且需要另外一个资源。如果留下有一个资源可用,那么其中某个进程就能够结束并释放它的所有资源.使其它进程也能够结束。所以避免死锁的条件是:
    r≥p(m-1)+1
      由此条件解上题:r=8,m=3,带入公式得:2p≤7。即当P小于等于3时才可保证死锁不会发生,所以可能会产生死锁的最小值是4。
      类似题型(2):某系统中有3个并发进程,都需要同类资源4个,试问该系统不会发生死锁的最少资源数是多少
      解:带入上述条件公式:r≥3*(4-1)+1=10。所以答案为10个。.

    40.C/C++是不允许函数嵌套定义的,只能嵌套调用。

    41.定义管理进程间通信规则的是(A)

      A.协议文件  B.数据库文件  C.系统文件  D.通信文件

    42.波特率是什么?

      波特率表示每秒钟传送的码元符号的个数,是衡量数据传送效率的指标,它用单位时间内载波调制状态改变的次数来表示。

    43.struct以及union占用内存大小的计算

      结构体在构建的时候,内存对齐。

      计算方法:先找出里面元素最大的大小是多少(32位系统最大是4个字节,64位系统最大是8个字节),然后(1)按照顺序来分内存;(2)按照元素最大的大小来进行内存对齐申请;(3)如果剩下的空间不够放下一个元素,那个将开辟新的一层空间,直接跳过这个不够用的一层空间;(4)最后结尾如果还有剩余的内存,也会并入结构体的内存计算中。

      共用体

       共用体的定义:(1)共用体是一个结构;(2)它的所有成员相对于及地址的偏移量都是0;(3)次结构空间要大到足以容纳最宽的成员;(4)对齐方式要适合所有的成员(我是不怎么读得懂第二句)。

      计算共用体的大小:(1)大小足够容纳最宽的成员;(2)大小能被包含的所有基本数据类型的大小整除。

    union U
    {
        char s[9];
        int n;
        double d;
    };

      这里s占用9个字节,n占用4个字节,d占用8个字节,最大的是s,因此至少需要9个字节的空间,根据对齐方式,9既不能被4整除也不能被8整除,所以要补充到16,最后共用体的内存大小就是16。不用像结构体一样全部加起来,只要大小能被包含的所有基本数据类型的大小整除就行了。

    41.ASSERT()断言

    #include "assert.h" 
    void assert( int expression );

      assert的作用是计算表达式experssion,如果它的值是false,那么就会想stderr打印一条错误信息,然后调用abort来终止程序运行,使用assert的缺点是,频繁调用会极大地影响程序的性能,增加额外的开销,每个assert只检验一个条件。

    42.typedef和define的区别 

    #define SIZE 10
    typedef int elemType

      (1)语法格式不同;(2)define是一个纯粹的替换,typedef是自定义一个数据类型,并不是替换;(3)命名上,#define一般用一个大写的字符去代表,typedef一般用小写字符_t去代表;(4)typedef能够定义复杂类型,宏定义则不行。  

     43.设有语句char a =‘72’,则变量a=(A)

      A.包含1个字符     B.包含2个字符  C.包含3个字符    D. 说明不合法

       解:是一个转义字符,这里的72是8进制的,如果是92就是错误的了,9已经超出8进制的范围了。

    44.WIN32平台下定义,请问p1+5=? ;p2+5=? ; 

    unsigned char *p1;
    
    unsigned int *p2;
    
    p1=(unsigned char *)0x801000;
    
    p2=(unsigned int *)0x810000;

      解:0x801000+5*1=0x801005;p2+5= ; 0x810000+5*sizeof(long)4 = ..+在低字节中最大只能是15即为f=20又放不下,所以向高位进一,即低位16高位为1,那地位就剩4了,等于0x810014;

    PS:

    44.C++为什么要用模板类?

      (1)可用来创建动态增加或减少的数据结构;(2)它与某种特定类型无关,因此代码可重复使用;(3)它在编译时检查数据类型而不是运行时检查数据类型,保证了类型的安全;(4)它是平台无关的,具有很好的移植性。

    45.linux的文件权限操作

      Linux系统中文件的权限是针对三种对象的,当前用户“u”,用户所属的组“g”,其他用户“o”,针对这三类用户,有r,w,x这三种权限,代表着三种权限的数字分别是4,2,1,chmod 764 file,就是给file这个文件添加rwxrw-r-权限,chmod 777 file就是给file这个文件添加rwxrwxrwx权限。

    46.进程间通信、线程间通信

      共同点:都能提高程序的并发度,提高程序运行效率和响应时间。

      区别:每个进程都有自己的地址空间,线程则是共享地址空间。

      线程的优缺点:执行开销小,但不利于资源的管理和保护。  

      进程的优缺点:执行开销大,但有利于资源的管理和保护。

      进程间通信的方式:

        (1)管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,并且只能在有亲缘关系的进程间使用。

        (2)有名管道(namedpipe):有名管道也是半双工的通信方式,允许无亲缘关系的进程互相通信。

        (3)信号量(semophore):信号量是一个计数器,可以用来控制多个进程对资源的访问。通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也来访问该资源。因此,信号箱主要用来作为进程间以及属于同一进程的线程间的同步手段。

        (4)消息队列(messagequeue):消息队列是消息的链表,存放在内核中,有消息队列标识符。消息队列客服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

        (5)信号(signal):信号用于通知接收进程某个事件已经发生。

        (6)共享内存(shared memory):共享内存就是映射一段能被其他进程访问的内存,这段内存由一个进程创建,但是多个进程都可以访问。

        (7)套接字(socket):套接字可用于不同设备及其间的进程通信。

      线程间通信的方式:

        (1)锁机制:互斥锁、条件变量、读写锁

          互斥锁提供了排他方式,方式数据结构被并发修改;读写锁允许多个线程同时读取共享数据,但是写入操作却是互斥的;条件变量以阻塞的方式运行,直到某个特定的条件为真的时候,阻塞才会消失,条件变量一般与互斥锁一起使用。

        (2)信号量机制(semapore):包括无名线程信号量以及有名线程信号量。

        (3)信号机制(signal):类似于进程间的信号处理。

      线程间通信主要是用于线程同步,所以线程没有像进程通信那样用于数据交换的机制。

    47.堆和栈的区别

      在不同的语境之下,堆和栈代表不同的含义,有以下两种:(1)程序内存分布的场景下,堆和栈代表的是两种内存管理方式;(2)在数据结构的场景下,堆和栈代表两种常见的数据结构。

       (1)内存分配中的堆和栈

        栈空间是系统自动分配的,用于存放函数的参数值,局部变量;堆是程序员手动申请的空间,如果不手动释放空间,则在程序结束的时候,由系统自动回收。堆空间的分配过程:首先,在操作系统中有一个记录空闲地址的链表,当有程序动态申请这片内存空间的时候,就会遍历该链表,寻找第一个空间大于所申请的空间的节点,然后将该节点从链表中删除,并将这块空间分配给程序。另外,大多数程序会在这块内存空间的首地址中记录本次分配的大小,便于delete释放内存空间。

        堆和栈的区别:

        1.linux系统中的堆和栈空间是两片不同的空间,不能混为一谈。

        2.栈空间是系统分配的,系统释放,以函数为单位分配内存,局部变量,形参都在栈空间上。堆空间是用户手动申请的,C语言用malloc/free来申请和释放,C++用new/delete来申请和释放,由于堆空间要手动申请,不然内存会泄露,而栈空间就不会内存泄漏。

        3.栈空间的分配和释放速度和效率高,内存是连续的;堆空间的分配和释放效率相对低一些,内存不一定连续,容易产生内存碎片,但是灵活性高。

        4.栈是高地址向低地址扩散的连续内存,栈的大小一般是2M或者10M;对是由低地址向高地址扩散的非连续内存,对的大小的影响因素较多,和系统的虚拟内存大小有关系。

      (2)数据结构中的堆和栈

        栈是一种线性表,不过只允许在一段插入和删除,这一段称为栈顶,另一端称为栈底,栈具有先进后出的特性。

        堆是一种树形结构,是一个完全二叉树,满足所有的节点的值总是不大于或者小于它的双亲节点的值,即,在一个堆中,根节点是最大(大根堆)或者最小节点(小根堆)。

    48.拷贝函数  memcpy、sprintf、strcpy

      memcpy:  void *memcpy(void *dest, const void *src, size_t n);

      memcpy是memory copy的缩写,即内存复制,功能是从src的开始内存位置开始拷贝n个字节的数据到dest,如果dest存在数据,就被覆盖,memcpy返回的是dest的指针,memocpy函数定义在string.h头文件里面。

    char name[]="cout";
    char myname[32]=NULL;
    memcpy(myname,name,strlen(name)+1);

    memcpy的底层实现

    void *my_memcpy(void* dest,void* src,size_t n)
    {
        void *p=dest;
        while(n--){
            *(char*)dest=*(char*)src;
            dest=(char*)dest+1;
            src=(char*)src+1;
        }
        return p;
    }

      sprintf:  int sprintf(char *str, const char *format, ...) 发送格式化数据到str所指向的字符串,函数的返回值是str所指向的字符串的长度。

      作用:(1)格式化字符串;(2)连接字符串;

      strcpy:  char *strcpy(char *dest,const *src);

      功能:把后面的src字符串的内容拷贝到dest字符串当中,拷贝结束是以遇到""才停下。strcpy的底层实现:

    char *strcpy(char *s1,char *s2)
    {
        char *tmp=s1;
        while(*s1++=*s2++);
        return tmp;
    }

    49.linux系统下的.ko文件,.so文件分别是什么文件?

      .ko文件是内核模块文件,内核加载的某个模块,一般是驱动程序,.so文件是动态链接库文件,相当于windows下的.dll文件。

    50.编程实现二叉树先序遍历。

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct BTree{
        char data;
        struct BTree *lchild;
        struct BTree *rchild;
    }BiTree;
    
    //先序创建树
    BiTree *create_tree(){
        char ch='';
        scanf("%c",&ch);
        if(ch=='#'){
            return NULL;
        }else{
            BiTree *root=malloc(sizeof(BiTree));
            root->data=ch;
            root->lchild=create_tree();
            root->rchild=create_tree();
            return root;
        }
    }
    
    //三种遍历都是不停地递归,直到一边遍历完了,再遍历另一边
    //前序遍历
    void show_tree(BiTree *root){
        if(root==NULL)return;
        printf("%c",root->data);
        show_tree(root->lchild);
        show_tree(root->rchild);
    }
    
    //中序遍历
    void show_mid(BiTree *root){
        if(root==NULL)return;
        show_mid(root->lchild);
        printf("%c",root->data);
        show_mid(root->rchild);
    }
    
    //后续遍历
    void show_tail(BiTree *root){
        if(root==NULL)retrun;
        show_tail(root->lchild);
        show_tail(root->rchild);
        printf("%c",root->data);
    }
    
    //销毁二叉树
    void destroy_tree(BiTree *root){
        //后续遍历销毁
        if(root==NULL)return;
        destroy_tree(root->lchild);
        destroy_tree(root->rchild);
        free(root);
        root=NULL;
    }
    
    int main()
    {
        BiTree *root=create_tree();
        show_tree(root);
        show_mid(root);
        show_tail(root);
        return 0;
    }

    51.链表反转

    struct ListNode* reverse_List(struct ListNode *head){
        //双指针法
        if(head==NULL||head->next==NULL)return NULL;
        struct ListNode *p=NULL,*q=head,*tmp=NULL;
        while(q!==NULL){
            tmp=q->next;
            q->next=p;
            p=q;
            q=tmp;
        }
        retrun p;
    }

      TCP/UDP和三次握手/四次挥手摘自:https://www.jianshu.com/p/16742fbc1b29

      进程线程的部分内容摘自:https://blog.csdn.net/qq_40340448/article/details/81836065

      select、poll、epoll部分摘自:https://blog.csdn.net/BaiHuaXiu123/article/details/89948037 

      SQL部分摘自:https://blog.csdn.net/yinni11/article/details/79859659

      字符串拷贝函数的底层实现部分摘自:https://www.cnblogs.com/OctoptusLian/p/9082296.html

      35volatile关键字的例子摘自:https://www.cnblogs.com/smile-at-you/archive/2013/10/08/3357910.html

      37算数表达式的前中后缀部分摘自:https://blog.csdn.net/linsheng9731/article/details/23125353

      38线程资源贡献的部分摘自:https://blog.csdn.net/zengzhihao/article/details/80999562

      39进程资源死锁问题摘自:https://wenku.baidu.com/view/f64570dfba4cf7ec4afe04a1b0717fd5360cb2c3.html

      40.union部分摘自:https://www.cnblogs.com/weiyouqing/p/9685427.html

      44模板类部分摘自:https://blog.csdn.net/weixin_41066529/article/details/90215972

      46进程线程通信部分摘自:https://blog.csdn.net/liyue98/article/details/80112246

      47栈和堆的区别部分摘自:https://blog.csdn.net/PinkBananA_/article/details/91399849

  • 相关阅读:
    【BZOJ 2120】 数颜色
    【BZOJ 1878】 HH的项链
    【BZOJ 2038】小Z的袜子
    【BZOJ 2724】 蒲公英
    【POJ 2482】 Stars in Your Windows
    【POJ 2182】Lost Cows
    __align(num) 分析
    C# 获取图片某像素点RGB565值
    基于OpenCV的火焰检测(三)——HSI颜色判据
    基于OpenCV的火焰检测(一)——图像预处理
  • 原文地址:https://www.cnblogs.com/smallqizhang/p/12455781.html
Copyright © 2011-2022 走看看