美文网首页
让人挠头的C语言测试题

让人挠头的C语言测试题

作者: 01_Jack | 来源:发表于2019-10-31 15:53 被阅读0次

    前言

    题目来自于https://kobes.ca/ctest ,共16题。

    题目

    #include <stdio.h>
    #include <setjmp.h>
    
    static jmp_buf buf;
    
    int main(void)
    {
       volatile int b = 3;
    
       if (setjmp(buf) != 0)
       {
          printf("%d\n", b);
          exit(0);
       }
       b = 5;
       longjmp(buf, 1);
    }
    

    解析:理解setjmp与longjmp后,本题很容易解答。首次调用setjmp时,会标记jmp_buf buf并返回0,当调用longjmp后,会跳回setjmp并返回longjmp的第二个参数val(当val为0时,setjmp返回1),所以当配对的longjmp调用后,setjmp返回值永不为0。

    答案:5


    #include <stdio.h>
    
    int main(void)
    {
       struct node
       {
          int a;
          int b;
          int c;
       };
       struct node s = { 3, 5, 6 };
       struct node *pt = &s;
    
       printf("%d\n", *(int*)pt);
    
       return 0;
    }
    

    解析:这一题很简单,pt为结构体s的指针,将pt强转为int *,而结构体的第一个成员变量a就是int类型,所以此时对(int *)pt取值得到的就是a的值。

    答案:3

    拓展:如果将题目改成这样

    int main(void)
    {
          struct node
        {
           char a;
           char b;
        };
        struct node s = { 2, 1 };
        struct node *pt = &s;
    
        printf("%d\n", *(short*)pt);    // 258
       return 0;
    }
    
    

    由于char占用1byte,short占用2byte,因此刚好可以读取a、b中的值。对于结构体s来说,由于a、b都是char类型,a的二进制数据为00000010b,b的二进制数据为00000001b。在小段模式下,s中存储的二进制数据为00000001_00000010b,转换成十进制为258,所以最终输出258。


    int foo(int x, int n)
    {
       int val = 1;
    
       if (n > 0)
       {
          if (n % 2 == 1)
             val *= x;
    
          val *= foo(x * x, n / 2);
       }
       return val;
    }
    
    
    3

    解析:其实就是初中数学题,本题控制变量为n,推导出n分别为奇偶数的表达式即可。

    答案:a


    #include <stdio.h>
    
    int main(void)
    {
       int a[5] = { 1, 2, 3, 4, 5 };
       int *ptr = (int*)(&a + 1);
    
       printf("%d %d\n", *(a + 1), *(ptr - 1));
    
       return 0;
    }
    

    解析:&a是指向a[5]的指针,此时每移动一个单位指针步长为5,所以&a + 1指向a[5],这是一个未定义的值。将&a + 1赋值给ptr,此时ptr同样指向a[5],由于指针类型为int *,所以ptr - 1指针移动一个步长,此时指向a[4]。而a + 1指向a[1],所以最终输出2和5。

    答案:2 5


    #include <stdio.h>
    
    void foo(int[][3]);
    
    int main(void)
    {
       int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
    
       foo(a);
       printf("%d\n", a[2][1]);
    
       return 0;
    }
    
    void foo(int b[][3])
    {
       ++b;
       b[1][1] = 9;
    }
    

    解析:如果第四题理解没有难度,这一题也不难理解。调用foo函数++b后,b指向{4, 5, 6}这个数组,此时的b[1][1]数值为后一个数组{7, 8, 9}中的8,赋值后数组{7, 8, 9}变为{7, 9, 9},所以此时a[2][1]取出的数据为{7, 9, 9}数组的9。

    答案:9


    #include <stdio.h>
    
    int main(void)
    {
       int a, b, c, d;
       a = 3;
       b = 5;
       c = a, b;
       d = (a, b);
    
       printf("c=%d  ", c);
       printf("d=%d\n", d);
    
       return 0;
    }
    

    解析:考察运算符的优先级,()>=>,
    c = a, b先运算=,所以c等于a等于3;
    d = (a, b)先运算()内的表达式,a, b的结果为b,所以d等于b等于5

    答案:c=3 d=5


    #include <stdio.h>
    
    int main(void)
    {
       int a[][3] = {1, 2, 3, 4, 5, 6};
       int (*ptr)[3] = a;
    
       printf("%d %d ", (*ptr)[1], (*ptr)[2]);
    
       ++ptr;
       printf("%d %d\n", (*ptr)[1], (*ptr)[2]);
    
       return 0;
    }
    

    解析:这是第四题的升级版,ptr为int [3]类型指针并指向a[0],所以此时(*ptr)[1]等价于a[0][1](*ptr)[2]等价于a[0][2]。++ptr后,ptr相当于指向a[1],所以此时(*ptr)[1]等价于a[1][1](*ptr)[2]等价于a[1][2]

    答案:2 3 5 6


    #include <stdlib.h>
    
    int *f1(void)
    {
       int x = 10;
       return &x;
    }
    
    int *f2(void)
    {
       int *ptr;
       *ptr = 10;
       return ptr;
    }
    
    int *f3(void)
    {
       int *ptr;
       ptr = malloc(sizeof *ptr);
       return ptr;
    }
    

    Which of these functions uses pointers incorrectly?

    (a) f3 only
    (b) f1 and f3
    (c) f1 and f2
    (d) f1, f2, and f3

    解析:

    1. f1返回局部变量x的地址,而局部变量在函数返回后已销毁,因此返回的指针指向未定义
    2. f2中ptr未初始化,*ptr = 10直接crash
    3. f3正确的为ptr分配了一块内存空间

    答案:c


    #include <stdio.h>
    
    int main(void)
    {
       int i = 3;
       int j;
    
       j = sizeof(++i + ++i);
    
       printf("i=%d j=%d\n", i, j);
    
       return 0;
    }
    

    解析:sizeof并不会对括号内的表达式做运算,只是检测表达式的类型,因此sizeof(++i + ++i)等价于sizeof(int),所以j等于4。

    答案:i=3 j=4

    拓展:sizeof存在类型提升,比如:

    int a = 1;
    double b = 3.14;
    sizeof(a + b);
    

    a为int类型,b为double类型,此时等价于sizeof(double)


    #include <stdio.h>
    
    void f1(int*, int);
    void f2(int*, int);
    void (*p[2])(int*, int);
    
    int main(void)
    {
       int a = 3;
       int b = 5;
    
       p[0] = f1;
       p[1] = f2;
    
       p[0](&a, b);
       printf("%d %d ", a, b);
    
       p[1](&a, b);
       printf("%d %d\n", a, b);
    
       return 0;
    }
    
    void f1(int *p, int q)
    {
       int tmp = *p;
       *p = q;
       q = tmp;
    }
    
    void f2(int *p, int q)
    {
       int tmp = *p;
       *p = q;
       q = tmp;
    }
    

    解析:考察基本功,典型的值传递与地址传递。f1与f2 ,a为值传递,b为地址传递。所以a可变,b不可变。f1执行后p指向了q,所以a的值等于q等于b等5,q的值改变不会影响b,f2同理。

    答案:5 5 5 5

    拓展:地址传递的本质仍然是值传递,只不过传递的是地址的值。比如函数f1中的p和q,其实都是局部变量,之所以可以通过p改变a的值,实际是通过*p对p指向的内存地址重新赋值。p本身也是可变的,如果重新对p赋值,此时改变*p并不会对a造成影响:

    void f1(int *p) {
        *p += 5;
    }
    
    void f2(int *p) {
        int x = 10;
        p = &x;
        *p += 5;
    }
    
    int main() {
        int a = 1;
        int b = 1;
        f1(&a);
        f2(&b);
        printf("%d\n", a);  // 6
        printf("%d\n", b);  // 1
        
        return 0;
    }
    
    

    对于C++来说,还存在另一种参数传递方式,引用传递。
    两者的区别在于:
    地址传递压栈的是指针的副本,通过对副本指针寻址从而改变实参的值;
    引用传递是真正的传址,形参实参都指向同一块内存,只是名字不同而已

    void f3(int &p) {
        p += 5;
    }
    
    
    int main() {
        int a = 1; 
        f3(a);
        printf("%d\n", a);  // 6
        
        return 0;
    }
    

    #include <stdio.h>
    
    void e(int);
    
    int main(void)
    {
       int a = 3;
       e(a);
    
       putchar('\n');
       return 0;
    }
    
    void e(int n)
    {
       if (n > 0)
       {
          e(--n);
          printf("%d ", n);
          e(--n);
       }
    }
    

    解析:考察点是递归调用,调用关系如下:

    e(3)->{
        e(2)->{
            e(1)->{
                e(0)->{},
                0,
                e(-1)->{}
            },
            1,
            e(0)->{}
        },
        2,
        e(1)->{
                e(0)->{},
                0,
                e{-1}->{}
        }
    }
    

    答案:0 1 2 0


    typedef int (*test)(float*, float*);
    test tmp;
    
    12

    解析:函数指针的定义方式为:

    函数返回值类型 (* 指针变量名) (函数参数列表)

    test显然是个函数指针,他指向返回值为int,两个参数都为float *的函数。test的类型为int(*)(float *, float *)

    答案:c

    拓展:函数指针与指针函数
    函数指针是个指针,他指向某个函数;
    指针函数是个函数,他返回某个指针。


    #include <stdio.h>
    
    int main(void)
    {
       char p;
       char buf[10] = {1, 2, 3, 4, 5, 6, 9, 8};
    
       p = (buf + 1)[5];
       printf("%d\n", p);
    
       return 0;
    }
    

    解析:考察C语言的语法,buf[5]等价于buf + 5(buf + 1)[5]等价于buf + 1 + 5,等价于buf + 6,即buf[6]

    答案:9


    #include <stdio.h>
    
    void f(char**);
    
    int main(void)
    {
       char *argv[] = { "ab", "cd", "ef", "gh", "ij", "kl" };
       f(argv);
    
       return 0;
    }
    
    void f(char **p)
    {
       char *t;
    
       t = (p += sizeof(int))[-1];
    
       printf("%s\n", t);
    }
    

    解析:如果十三题理解了,这一题也不难。(p += sizeof(int))[-1]等价于p + sizeof(int) - 1,等价于p + 4 - 1,等价于p + 3,即p[3]

    答案:gh


    #include <stdarg.h>
    #include <stdio.h>
    
    int ripple(int n, ...)
    {
       int i, j, k;
       va_list p;
    
       k = 0;
       j = 1;
       va_start(p, n);
    
       for (; j < n; ++j)
       {
          i = va_arg(p, int);
          for (; i; i &= i - 1)
             ++k;
       }
       va_end(p);
       return k;
    }
    
    int main(void)
    {
       printf("%d\n", ripple(3, 5, 7));
       return 0;
    }
    

    解析:初始变量n等于3,j等于1,外层for循环每次j自增1,这些条件确保了i刚好可以被可变参数列表中的3和7赋值。而i &= i - 1是在控制内部for循环的循环次数。
    下面分别对i等于5和7时,进行i &= i - 1运算i结果:

    i
    
    起始值:101b  (5的二进制表达)
    第一次运算后的结果:100b
    第二次运算后的结果:000b
    运算2次后为0
    
    起始值:111b    (7的二进制表达)
    第一次运算后的结果:110b
    第二次运算后的结果:100b
    第三次运算后的结果:000b
    运算3次后为0
    

    当i等于0时for循环终止,其实这种for循环写法的循环次数等价于起始值转成二进制中1的个数,而i &= i - 1就是在消除变量二进制最低位的1。

    所以内部for循环共循环了5次,k起始值为0,5次++k运算后,k等于5

    答案:5


    #include <stdio.h>
    
    int counter(int i)
    {
       static int count = 0;
       count = count + i;
       return count;
    }
    
    int main(void)
    {
       int i, j;
    
       for (i = 0; i <= 5; i++)
          j = counter(i);
    
       printf("%d\n", j);
       return 0;
    }
    

    解析:考察局部静态变量,没啥好说的j = 0 + 1 + 2 + 3 + 4 + 5

    答案:15


    Have fun!

    相关文章

      网友评论

          本文标题:让人挠头的C语言测试题

          本文链接:https://www.haomeiwen.com/subject/nlyovctx.html