前言
题目来自于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
解析:
- f1返回局部变量x的地址,而局部变量在函数返回后已销毁,因此返回的指针指向未定义
- f2中ptr未初始化,
*ptr = 10
直接crash - 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!
网友评论