让人挠头的C语言测试题

前言

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

题目

#include 
#include 

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 

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

让人挠头的C语言测试题_第1张图片
3

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

答案:a


#include 

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 

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 

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 

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 

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 

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 

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 

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;
让人挠头的C语言测试题_第2张图片
12

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

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

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

答案:c

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


#include 

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 

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 
#include 

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 

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语言测试题)