经典面试

1、const 关键字

  char const *p = &c;    //不能改c中的内容,p是指向字符型常量的指针                                                                                             const char *p = &c;    //不能改c中的内容,p是指向字符型常量的指针
  char *const p = &c;    //不能改p中的内容,即不能改p指向的地址,p是指向字符型变量的常指针
  const char *const p=&c; //不能改c中的内容,也不能改p中的内容,p是指向字符型常量的常指针

2、static 关键字

   第一:在修饰变量的时候,static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,Static修饰的局部变量存 放在全局数据区的静态变量区,初始化的时候自动初始化为0,直到程序运行结束以后才释放
   第二:static修饰全局变量,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以
   第三:static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用

3、volatile 关键字

  volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接   从变量地址中读取数据。表示一个变量也许会被后台程序改变,关键字 volatile 是与 const 绝对对立的。它指示一个变量也许会   被某种方式修改,这种方式按照正常程序流程分析是无法预知的。
  作用: 1、并行设备的硬件寄存器,存储器映射的硬件寄存器通常也要加 voliate,因为每次对它的读写都可能有不同意义。
              2、一个中断服务子程序中会访问到的非自动变量,由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存    取外部RAM的优化。
             3、多线程应用中被几个任务共享的变量,当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声   明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行,volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。
   一个参数既可以是const还可以是volatile吗?
   答:可以,例如只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为 程序不应该试图去修改它。

4、局部变量和全局变量的区别:
        1. 作用域不同:全局变量的作用域为整个程序,而局部变量的作用域为当前函数或循环等
        2. 内存存储方式不同:全局变量存储在全局数据区中,局部变量存储在栈区
        3. 生命期不同:全局变量的生命期和主程序一样,随程序的销毁而销毁,局部变量在函数内部或循环内部,随函数的退出或 循环退出就不存在了
        4. 使用方式不同:全局变量在声明后程序的各个部分都可以用到,但是局部变量只能在局部使用。函数内部会优先使用局部 变量再使用全局变量

5、要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。
   int *ptr = (int *)0x67a9;
   *ptr = 0xaa66;

6、编译和链接有什么不同?
     编译生成的是目标文件(object  *.o),编译过程中对于外部符号不做任何解释和处理。外部符号对应的就是“符号”
     链接生成的是可执行程序,链接将会解释和处理外部符号。外部符号对应的是地址

7、已知strcpy函数的函数原型是:char *strcpy(char *strDest, const char *strSrc),请不调用库函数编写函数strcpy

char* strcpy(char *strDest, const char *strSrc)
{
    assert((strDest != NULL) && (strSrc != NULL));
    char* addr = strDest;
    while ((*strDest++ = *strSrc++) != '\0');
    return addr;
}

8、已知strcmp函数的函数原型是:char *strcmp(char *s1, const char *s2),请不调用库函数编写函数strcmp

int strcmp(char *s1, const char *s2)
{
     int i = 0;
     while(s1[i] == s2[i] && s1[i] != '\0')//数组型
	 {
         i++;
	 } 
     return s1[i] - s2[i];  
     while(*s1 == *s2 && *s1 != '\0')  //指针型
	 {
         s1++;
         s2++;
     }       
	 return *s1 - *s2;
 }

9、请不调用库函数编写函数reverse实现字符串的逆序输出,已知reverse函数的函数原型是:char* reverse(char *str)

char* reverse(char *str)
{
    int len = strlen(str);
    char tem;
    for (int i = 0; i <= len / 2; i++)
    {
        tem = str[i];
        str[i] = str[len - 1 - i];
        str[len - 1 - i] = tem;
    }
    return str;
}

10、已知myStrcat函数的函数原型是:char *myStrcat(char dstStr[],char srcStr[]),请不调用库函数编写函数myStrcat

char *myStrcat(char dstStr[],char srcStr[])
{
    int len1=strlen(dstStr);
    int len2=strlen(srcStr);
    for(int i=0;i<len2;i++)
    {
        dstStr[len1+i]=srcStr[i];
    }
    return dstStr;
}

11、队列和栈的区别
     队列是先进先出,只能在一端插入另一端删除,可以从头或尾进行遍历(但不能同时遍历)
     栈是先进后出,只能在同一端插入和删除,只能从头部取数据

12、数组与链表的区别
  数组中的数据在内存中的按顺序存储的,而链表是随机存储的!要访问数组中的元素可以按下标索引来访问,速度比较快,如果对他进行插入操作的话, 就得移动很多元素,所以对数组进行插入操作效率很低!由于链表是随机存储的,链表在插入,删除操作上有很高的效率(相对数组),如果要访问链表中的某个元素的话,那就得从链表的头逐个遍历,直到找到所需要的元素为止,所以链表的随机访问的效率就比数组要低  

13、下列程序输出结果是

 int main()
 {
    int a[5]={1,2,3,4,5};
    int *ptr=(int *)(&a+1);//&a相当于变成了行指针,加1则变成了下一行首地址
    printf("%d,%d",*(a+1),*(ptr-1));
 }
 输出结果:*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5

14、linux 系统一般提问常用调试工具的操作
       gdb调试器
            编译时:gcc -g main.c -o main
                gdb main
                l (list)---查看代码
                b 20 ---在20行设置断点
                b 9 if i>98
                info b ---查看断点信息
                d 断点编号----删除断点
                r run ---运行程序
                next/step/continue
                n--不进入函数内部,一次执行完一句 
                s--进入函数内部单步执行
                c--从断点继续执行到程序结束
                print i---- 打印变量i的值
                display i
                q quit--退出调试状态
     makefile工具
            目标:依赖文件  -----makefile中第一个出现的目标是最终目标
            命令执行的条件(满足其一):
                        目标不存在
                        依*新
            makefile的变量
                自定义变量
                    DEP=main.o add.o sub.o
                    $(DEP)
                    $(CC) $(CFLAGS)
                系统预定义变量
                自动变量
                    $@    ----目标文件的完整名
                    $<    ----依赖的第一个文件的完整名
                    $^    ----依赖的所有的文件,并以空格隔开
           makefile 的规则     
                普通规则
                模式规则
                    VAR=main.o add.o sub.o
                    main:$(VAR)
                    gcc -o $@ $(VAR)
                    %.o:%.c    
                    gcc -c $< -o $@
                    .PHONY:clean
                    clean:
                        rm -f *.o main
                隐式规则
                    main:     注意:隐式规则使用时不能有其它编译选项,目标名与源文件名相同
            make 的常用选项
                make -f xxx
                make -C dir/
            以dir_makefile目录下的simplemakefile作为makefile来组织编译程序
            make -C ./dir_makefile/  -f simplemakefile
            双目标
                main::a.c
                    gcc -o main a.c
                main::b.c
                    gcc -o main b.c

15、驱动程序流程及功能
  对设备初始化和释放
  把数据从内核传送到硬件和从硬件读取数据
  读取应用程序传送给设备文件的数据和回送应用程序请求的数据
  检测和处理设备出现的错误

16、中断过程的一般步骤:请求中断→响应中断→关闭中断→保留断点→中断源识别→保护现场→中断服务子程序→恢复现场→中断返回。
请求中断
        当某一中断源需要CPU为其进行中断服务时,就输出中断请求信号,使中断控制系统的中断请求触发器置位,向CPU请求   中断。系统要求中断请求信号一直保持到CPU对其进行中断响应为止。
中断响应
        服务子程序。对于外部中断,CPU在执行当前指令的最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同   时在系统开中断(即IF=1)的情 况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求           INTR的应答,系统自动进入中断响应周期。
关闭中断
        CPU响应中断后,输出中断响应信号,自动将状态标志寄存器FR或EFR的内容压入堆栈保护起来,然后将FR或EFR中的中 断标志位IF与陷阱标志位TF清零,从而自动关闭外部硬件中断。因为CPU刚进入中断时要保护现场,主要涉及堆栈操作,           此时不能再响应中断,否则将造成系统混乱。
保护断点
        保护断点就是将CS和IP/EIP的当前内容压入堆栈保存,以便中断处理完毕后能返回被中断的原程序继续执行,这一过程也 是由CPU自动完成。
中断源识别
      当系统中有多个中断源时,一旦有中断请求,CPU必须确定是哪一个中断源提出的中断请求,并由中断控制器给出中断服 务子程序的入口地址,装入CS与IP/EIP两个寄存器。CPU转入相应的中断服务子程序开始执行。
保护现场
      主程序和中断服务子程序都要使用CPU内部寄存器等资源,为使中断处理程序不破坏主程序中寄存器的内容,应先将断点 处各寄存器的内容压入堆栈保护起来,再进入的中断处理。现场保护是由用户使用PUSH指令来实现的。
中断服务
      中断服务是执行中断的主体部分,不同的中断请求,有各自不同的中断服务内容,需要根据中断源所要完成的功能,事先 编写相应的中断服务子程序存入内存,等待中断请求响应后调用执行。
恢复现场
      当中断处理完毕后,用户通过POP指令将保存在堆栈中的各个寄存器的内容弹出,即恢复主程序断点处寄存器的原值。
中断返回
      在中断服务子程序的最后要安排一条中断返回指令IRET,执行该指令,系统自动将堆栈内保存的 IP/EIP和CS值弹出,从 而恢复主程序断点处的地址值,同时还自动恢复标志寄存器FR或EFR的内容,使CPU转到被中断的程序中继续执行。

16、冒泡排序
       基本思想:从无序序列头部开始,进行两两比较,根据大小交换位置,直到最后将最大(小)的数据元素交换到了无序队列 的队尾,从而成为有序序列的一部分,下一次继续这个过程,直到所有数据元素都排好序。算法的核心在于每次通过两两比较交换位置,选出剩余无序序列里最大(小)的数据元素放到队尾。

void bubbleSort(int *array) {
    int n=sizeof(array) / sizeof(int);//算出数组长度
    for (int i = 0; i<n - 1; i++)
        for (int j = 0; j < n - i - 1; j++)
        {
            //如果前面的数比后面大,进行交换
            if (array[j] > array[j + 1]) {
                int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp;
            }
        }
}

17、选择排序
       基本思想是:在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列,然后再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

void SelectionSort(int array[])
{
    int n=sizeof(array) / sizeof(int);//算出数组长度
    for (int i = 0; i < n - 1; i++)         // i为已排序序列的末尾
    {
        int min = i;
        for (int j = i + 1; j < n; j++)     // 未排序序列
        {
            if (array[j] < array[min])              // 找出未排序序列中的最小值
            {
                min = j;
            }
        }
        if (min != i) // 放到已排序序列的末尾,该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法
        {
	  int temp = array[min];
	  array[min] = array[i];
	  array[i] = temp;  
        }
    }
}

18、插入排序
   基本思想是:对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

void InsertionSort(int array[])
{
    int n=sizeof(array) / sizeof(int);//算出数组长度
    for (int i = 1; i < n; i++)         // 类似抓扑克牌排序
    {
        int get = array[i];                 // 右手抓到一张扑克牌
        int j = i - 1;                  // 拿在左手上的牌总是排序好的
        while (j >= 0 && array[j] > get)    // 将抓到的牌与手牌从右向左进行比较
        {
            array[j + 1] = array[j];            // 如果该手牌比抓到的牌大,就将其右移
            j--;
        }
        array[j + 1] = get; // 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边(相等元素的相对次序未变,所以插入排序是稳定的)
    }
}

猜你喜欢

转载自blog.csdn.net/lly_3485390095/article/details/83116070