C Programming
PTA homework of ZJU Fundamentals of Programming and Algorithm, 2020 Fall & Winter.
Week 1
Q1-6. C 语言的标识符由字母、数字和其他任意字符组成。(T/F)
Answer
F。应当由字母、数字和下划线组成,其中首字符不能为数字。实际运行中,包含 $ 的标识符 (identifier) 也可以编译通过。在这样的标识符中,$ 字符也可以作为首字符
Week 3
Q2-6. 在嵌套使用 if
语句时,C 语言规定 else
总是 ______。
Answer
和之前与其最近的且不带 else 的 if 配对
Week 4
Q1-3. sizeof( )
是 C 语言的一个函数,可以计算参量所占内存的字节数。如 sizeof(int)
可计算整型所占的内存字节数。(T/F)
Answer
F。sizeof() 是 C 语言的一个运算符(这就是一个坑题)
Q1-4. 通过函数调用只能获得一个返回值。(T/F)
Answer
F。1 个或 0 个(这也是一个坑题)
Q1-5. 函数可以嵌套调用但不能嵌套定义。(T/F)
Answer
T。
Q2-1. 在 C 语言程序中,若对函数类型未加显式说明,则函数的隐含类型为(
Answer
int
期中 Ⅰ 段:运算符优先级
优先级 | 运算符 | 结合性 / 目数 |
---|---|---|
1 | [] 数组 () | |
1 | -> . 结构 | |
2 | - 取负 ++ -- 自增减 | 右 / 单 |
2 | (类型) | 右 |
2 | * & 指针 / 地址 | 右 / 单 |
2 | !~ 非 / 按位反 | 右 / 单 |
2 | sizeof | 右 |
3 | * / % | / 双 |
4 | + - | / 双 |
5 | << >> | / 双 |
6 | >(=) <(=) | / 双 |
7 | == != | / 双 |
8/9/10 | & / ^ / | 位 | / 双 |
11/12 | && || 逻辑 | / 双 |
13 | ?: | 右 / 三 |
14 | 各种赋值 | 右 |
15 | , |
需要关注的点:
- 大小比较符 (6) 高于等于比较符 (7),都处于中游
- 第 1 级 yyds:数组
[]
、结构.->
、括号()
可认为与相关操作数一体 - 关注第 2 级(除第 1 级外,第 2 级几乎最高,且都有右结合的特殊性)
- 赋值没地位,逗号最没地位
- 数字计算中最常用的
*/%
(3) 与+-
(4) 符合认知,仅居于 1、2 之下 - 以
*/%
和+-
为表格的中心,可见所有的比较符都在其下面判断条件中
*/%+-
对比较符号间不需要括号 - 以
*/%
和+-
为表格的中心,可见几乎所有逻辑和位运算都在其下面!~
是“反面”例子,居于第 2 级 - 逻辑:非 > 与 > 或
- 位运算:反 > 与 > 异或 > 或
- 特殊的三目运算符:只比最没地位的两个好一点,有右结合的特殊性
- 右结合:第 2 级,
?:
条件和赋值 - 具体例子:第 2 级右结合的体现
*p++
(char*)(&n)
等价于(char*)&n
Week 10
Q1-1. 指针只有加减操作,没有乘除操作。指针可以加常数、减常数;相同类型的指针可以相加、相减。(T/F)
Answer
F。相同类型的指针不能相加,因为相加之后没有什么意义
Q1-2. 假设有定义如下: int array[10];
, 则该语句定义了一个数组 array。其中 array 的类型是整型指针(即: int *
)(T/F)
Answer
F。array 的类型是数组名。array 存储的是 array[0] 的地址,array 代表的是整个数组。因此 &array 和 array 虽然数值相同但意义不同,可以从 (&array)+1 和 array+1 中看出——前者移动了 40 个 Byte,后者移动了 4 个 Byte
Q2-1. If variables are defined and assigned correctly, the expression __ is wrong.
A. a&b
B. a^b
C. &&x
D. a, b
Answer
C。&x 访问到了 x 的地址,但是这个地址是直接访问到的而不是从某个存储它的变量中读到的,所以不能再取一次地址
Q2-2. According to the declaration: int a[10], *p=a;
,
the expression __ is wrong.
A. a[9]
B. p[5]
C. *p++
D. a++
Answer
D。数组名可以看成 const 指针,不能做 ++ 运算
Q2-3. char str[10]; str="string";
(T/F)
Answer
F。用字符串字面量("xxx")初始化:字符数组只能在被定义时这么干,定义了之后不行。
字符串在被定义时和定义了之后都可以这么干。
Q2-6. According to the declaration: int (*p)[10];
,
p
is a(n) __.
Answer
pointer。p 是一个数组指针 / 行指针,指向若干个长度为 10 的数组 / 若干一行有 10 个元素的行,类型与传参时的二维数组相同 ( 二维数组作为函数的参数时就可以这么写 )。
但注意二维数组名的类型并不是指针,而是数组。二维数组是 " 数组的数组 ",其数组名自然也是数组,但是传参 / 赋值的时候由于隐式转化的问题,类型会与此相同。
Q2-7. Among the following statements, __ is equivalent to the declaration: int *p[4];
.
A. int p[4];
B. int **p;
C. int *(p[4]);
D. int (*p)[4];
Answer
C。再次体现了优先级问题,[]
是第 1 级的,*
是第 2 级的。
int *p[4]
中,p 是一个数组,存储着 4 个指针元素。
D: 因此 int *p[4]
中的 p 和 int (*p)[4]
中的 p 存在类型上的差异————前者是数组,后者是指针。
虽然 int *p[4]
的 p 是一个数组,但考虑到数组名隐式转化为指针的问题,在传参 / 赋值的时候会认为 p 的类型是 int**
。不过本身类型并不一样,所以 B 也错。
而 int (*p)[4]
的类型就是 (int*)[4]
,和 int**
不同。这在下一题会有明显的体现。
Q2-8. For the function declaration void f(char ** p)
,
the definition __ of var
makes the function call f(var)
incorrect。
A. char var[10][10];
B. char *var[10];
C. void *var = NULL;
D. char *v=NULL, **var=&v;
Answer
A。A 中 var 的类型是二维数组,传参时的类型为 (*char)[10]
,虽然是个 ( 数组 / 行 ) 指针,但并不是二重指针。这是因为传参时的隐式转化只能转化一层,因此一维数组传参时完全等价于 const 指针,但是二维数组就无法等价于二重指针。
另外 *var
确实代表 var[0]
这个数组,**var
也确实就是 var[0][0]
,但是注意这里的 *
是运算符而不是类型定义。作为类型定义时,数组和指针是需要区分的。
B 中 var 的类型是指针数组,但是由于数组隐式转化的问题,其传参时表现出来的类型就是 char**
,因此是正确的。
C 中 var 的类型是无类型指针,它可以指向任何东西。如果它指向的是一个 char*
,那么它就符合 char**
的类型要求了,因此正确。
D 中 var 的类型是正统的 char**。
关于隐式转化,这是我在网络上看到的一种说法,大概解释如下
隐式转化的解释
数组名代表整个数组,但是存储着其第一个元素的地址
因此数组名既是数组,又可以看成一个指针 ( 类型依然是数组 )
在传参 / 赋值时,其代表数组的意义并没有什么意义,应当看成一个指针,因此会有“数组隐式转化为指针”的现象
这种转化只能转化一层,原因是转化一层之后它已经是个指针可以进行传参 / 赋值了,编译器不需要再考虑它指的是什么东西
因此二维数组传参 / 赋值时只会隐式转化为 (char*)[10]
(以上题为例char**
Q2-10. 对于以下变量定义,正确的赋值是(
char *pc[5], s[10];
A. pc = s;
B. *pc = s[0];
C. *pc = s;
D. *pc = &s;
Answer
C。有了以上的铺垫,感觉这里就很自然了。
A,pc 的类型是数组,此处作为赋值的左值相当于 const 指针,不能被赋值。
BCD,*pc
就是 pc[0]
,一个字符串 (char*
)
B,s[0]
是字符 (char
),不能赋值给字符串 (char*
)
D,&s
是整个字符数组 s 的地址,如果存储起来类型就是 (char*)[10]
,当然也不能赋值给字符串 (char*
)
C,s 是字符数组,赋值时隐式转化为 char*
,可以被赋给 *pc
Week 11
Q1-2. 不同类型的指针变量是可以直接相互赋值的。(T/F)
Answer
F。会报错,一般需要强制类型转换。
但是存在例外,例如 void*
是可以直接赋值给其他指针的。
Q2-8. For definitions: char s[2][3]={"ab", "cd"}, *p=(char *)s;
the expression __ is correct and its value is equivalent to the element s[1][1]
.
A. *(s+3);
B. *s+2;
C. p[1][1];
D. *++p+2;
Answer
D。A 没有意义。*s
是 "ab",*(s+1)
是 "cd"( 移动步长为sizeof(s[0])
)
B 的输出为 NULL,*s+1
是 "b",*s+3
是 "cd" ( 移动步长为sizeof(s[0][0])
)
CD 中,s 存储的是 s[0]
的地址,其地址与 s[0][0]
相同。对 p 赋值时强制转化为 (char*
),则 p 被 s[0]
隐式转化的指针 (const 指针 ) 赋值,成为存储 s[0][0]
地址的指针。因此 C 失去意义。
则 D 的 ++p
使得 p 指向 s[0][1]
,*
取到了 s[0][1]
,然后注意 +2 不是指针的移动而是单纯对取到的 s[0][1]
进行数值的 +2,因此 *++p+2='b'+2='d'
,而不是 *++p+2=s[1][1]='d'
,值的相等只是一个巧合。所以,有 *++p+3='e'
,*++p+4='f'
……
Q2-9. 以下哪个定义中的 p
不是指针,请选择恰当的选项:
A. char **p;
B. char (*p)[10];
C. char *p[6];
D. 给出的三项中,p
都是指针
Answer
A、B 都是指针,C 是指针数组
Q2-10. 若有定义 char *str[]={"Python", "SQL", "JAVA", "PHP", "C++"};
则表达式 *str[1] > *str[3]
比较的是:
A. 字符 P 和字符 J
B. 字符串 SQL 和字符串 PHP
C. 字符串 Python 和字符串 JAVA
D. 字符 S 和字符 P
Answer
D。考察的依然是 []
对 *
的优先级问题。
如 *str[1]
首先计算 char*
类型的 str[1]
,也就是第一个字符串 "SQL"。
然后 *str[1]
得到的就是 "SQL" 的第一个字符 "S"。
Week 12
Q1-1. 结构体变量可以作数组元素 (T/F)
Answer
T。结构体数组!
Q1-2. 结构体成员的类型必须是基本数据类型 (T/F)
Answer
F。链表?自定义类型也可以,只要是完整的可以让结构体估计大小的类型。
Q1-3. 结构体类型本身不占用内存空间,结构体变量占用内存空间 (T/F)
Answer
T。