理论考错题
x++是执行完下一句之后再加,++x是先加1再执行这一句
- According to the declaration: int x=23; the value of expression (x++*⅓) is
x++
是在这整个表达式执行完之后再加1,所以这个表达式的意思是23*⅓,答案是7
此时如果我们执行printf("%d\n", (x++*1/3));
的话,输出的是23
注意
x++是在这整个表达式执行完之后再加1
-1<=i<1
不是数学表达式
- The following code fragment prints out _____.
A.2 B.-1 C.0 D.3
编译器警告
comparisons like 'X<=Y<=Z' do not have their mathematical meaning,这种表示区间的写法不含有数学意义,但是在逻辑上正确
分析:因为-1<=i<1
没有数学意义,所以在判断的时候只会先判断-1<=i,然后再判断得到的值是否小于1,因为前一个式子得到的值等于1,所以循环里一个都不会执行,直接输出第10行的内容
按位运算符
- According to the declaration:
int a
; the value of expression(a&3)==(a%4)
is :
A. 0 B. 1 C. error D. uncertained
首先要明白&
和%
的含义,&
是按位与,%
是取余数
我们a是一个int类型的数字,也就是四个字节,每个字节有8位,也就是2的32次方,也就是32位.
(a&3)
的意思是取a的最后两位,也就是a这个未被初始化的变量的值和0000 0011
的二进制取与,得到的是a的最后两位数字
(a%4)
的意思是取a除以4的余数,(不是位运算),也就是a除以4的商的最后一位,也就是a在二进制下的最后两位数字
那么这两个表达式的结果都是a的最后两位数字,结果相同,也就是1
Note
&
是按位与,%
是取余数
&3
的效果就是取出最后三位二进制数,&4
的效果是取出倒数第四位二进制数
进制,位移运算符,按位运算符
-
The following code fragment prints out _____.
printf("%#o\n", (010<<1^1)&(0xe<<1));
-
010
- 在 C 语言中,以
0
开头的数字是八进制(base 8)数。 -
010
是八进制,等于十进制的8
:010
(八进制) =1 * 8^1 + 0 * 8^0 = 8
(十进制)。
-
<< 1
(左移一位) - 左移操作符
<<
会将数字的二进制位向左移动指定的位数。相当于将数字乘以 2 的幂。 -
对于
010 << 1
,即对8
(1000
二进制)左移 1 位:1000
左移 1 位变成10000
,即十进制的16
。
-
^ 1
(与 1 做异或运算) - 异或(
^
)运算符比较两个数的每一位,相同为0
,不同为1
。 -
对
16
(10000
二进制)与1
做异或运算:10000 ^ 1 = 10001
,即十进制的17
-
0xe
(十六进制数): 0xe
是十六进制字面量,e
在十六进制中等于十进制的14
。-
所以
0xe
就是十进制的14
,即二进制的1110
。 -
<< 1
(再次左移 1 位): -
对
0xe
(即14
,1110
二进制)左移 1 位:1110
左移 1 位变成11100
,即十进制的28
。
-
&
(按位与运算): - 按位与运算符
&
会将两个数的二进制位逐位比较,当两个数的对应位都为1
时结果为1
,否则为0
。 -
对
17
(10001
二进制)和28
(11100
二进制)做按位与:10001 & 11100 = 10000
,即十进制的16
。
-
%#o
(输出八进制并加上前导0
) %o
格式化符号会把数字输出为八进制格式。#
修饰符会在八进制数前加上0
前缀。- 所以,十进制的
16
转换为八进制是20
。
Note
<<
是左移操作符,^
是异或操作符,&
是按位与操作符。
#o
修饰符会在八进制数前加上 0
前缀。
0x 表示十六进制数,0 表示八进制数。
C | |
---|---|
命令行参数argv
和argc
,约定俗成的两个变量
- To execute the command: prog at my home, the value of ((argv+2)+1) is___.
main其实是这样
然后把那句话在命令行中输入.argv+2指向my这个数组.*(argv+2)
指向my的地址,也就是m这个字符.然后加1指向了y.然后再*取出y
所以答案是y
==和=的区别
- The following code fragment outputs the string str. The loop condition
str[i]!='\0'
can be replaced by _______.
A.str[i]
B.i < 80
C.!(str[i] = ‘\0’)
D.i <= 80
分析:
A. str[i]:
这个条件在 str[i] 为非零值(即非 \0)时为真,因此可以替代 str[i] != '\0'。
正确。
B. i < 80:
这个条件会遍历整个数组 str,即使字符串的实际长度小于 80。这会导致不必要的遍历,甚至可能访问到未初始化的内存。
不正确。
C. !(str[i] = '\0'):
这个条件实际上是将 str[i]
赋值为 \0,然后检查赋值后的值是否为假(即 \0)。这会导致字符串被截断,且逻辑错误。
不正确。
D. i <= 80:
这个条件会导致数组越界,因为 str 的最大索引是 79。
不正确。
C比较容易看错吧,注意分辨==
和=
的区别
数组定义时a是一个常量指针不能被修改,指向第一个元素
- With regard to the array definition “int a[4]; “,which expression in the follows is wrong _______? A.*a B.a[0] C.a D.a++
我们数组定义的时候a就是一个指针,指向的是数组的第一个元素,但是这也是一个常量指针,不能被修改,所以D错误
并且a
是数组的首地址,类型是 int*
,这是合法的
二维数组第二个参数不能少
- The following statements have defined different arrays. Which statement is wrong _______?
A.
int a[1][3]
; B.int x[2][2]={1,2,3,4};
C.int x[2][]={1,2,4,6};
D.int m[][3]={1,2,3,4,5,6};
二维数组第一个表示的是有几行,第二个表示的是有几列
也就是第一个表示的是有几个一维数组,第二个表示的是这个一维数组有几个元素
如果第二个参数没有确定的话,整个二维数组就无法构建,因为第一个一维数组结束后就是第二个的开头,需要第二个参数来确定
Note
二维数组第二个参数不能少
'\0'的ASCII码 && 注意case没有break的时候会顺延执行下一个case直到遇到break
- The following code fragment prints out:
C | |
---|---|
逐行分析:
char x='\0',y='1';
赋值x
赋值为'\0'
,即十进制的0
。y
赋值为'1'
,即十进制的1
。switch(!x) {
执行判断选择!x
结果为1
,所以执行case 1:
。case 1:
执行switch(y){
-
y
结果为'1'
,所以执行case '1': printf("*2*#");break;
。跳出的是里面的switch
语句。 -
因为case 1执行完了,所以不会再执行case 0,所以不会输出0#,但是case1没有break,执行了default,输出3#.
-
default: printf("*3*#");
执行 - 输出
*3*#
。
所以输出结果为 *2*#3*#
'\0'
The value of expression sizeof(“%%hello\nworld\n”)
is______.
考点:'\0'在sizeof中计入长度
Note
- sizeof 运算符返回变量或类型在内存中所占的字节数。 对于字符串,sizeof 运算符返回字符串的长度加 1(以 '\0' 结尾)。
- strlen 函数返回字符串的长度,不包括 '\0'。
静态变量
- The following program prints out _____.
static int k=0;
是静态变量,只会初始化一次,所以第一次调用的时候k是0,第二次调用的时候k是4,所以s=4,f(2)的时候k还是4,此时s=0,没有进入for循环
Note
静态变量只会初始化一次,生命周期是整个程序.静态变量的生命周期从程序开始运行时创建,到程序结束时销毁。即使变量在函数内部声明,它也不会在函数调用结束后被销毁,而是会一直保留其值。
- The following program prints out _____.
C | |
---|---|
这个不要把h
计入了,注意到前面的判断(我好蠢)
如果要计入的话,注意0的ASCII码是48,9的ASCII码是57
A的ASCII码是65,a的ASCII码是97
使用typeof定义一个指针型的函数
Use typedef
to define a pointer type that points to a function returning float type
答案:typedef float (*fp)();
使用 typedef
定义一个指针类型,指向一个返回 float
类型的函数。
C | |
---|---|
拓展:
- 使用typeof定义一个返回int类型的函数,其中这个函数会接受一个int类型的参数
typedef int (*int_func)(int);
- 使用typedef定义一个结构体,其中包含一个指向函数的指针
这个结构体包含一个指向函数的指针,指向的函数的返回值是 int
。
- 已知一个函数指针类型\,它所指的函数返回值类型为空类型,接收两个参数:一个是字符指针类型,一个是整型。请用typedef将该函数指针类型命名为FunType,具体形式为:_________.
typeof void (*FunType)(char*, int);
结构:typeof 返回类型 (*指针名)(参数类型1, 参数类型2, ...);
二维数组的指针a[i]
指向 第i行的第一个元素,不是第i个元素
-For the following array a, the value of *(a[1]+1) is _____.
int a[3][2]={1,2,3,4,5,6};
A. 3 B. 4 C. 5 D. 6
分析:
a[1]
是一个指针,指向数组a
的第二行的第一个元素。*(a[1]+1)
是一个指针,指向数组a
的第二行的第二个元素。- 也就是
4
。
scanf("%s",s)
会读取输入的第一个单词,以空格或换行符分隔
- When input :
How Are You?
ENTER, the output of the following program is_____.
C | |
---|---|
Note
scanf("%s", s); scanf 会读取输入的第一个单词(以空格或换行符分隔),因此 s 的内容为 "How"。
s 的值为:{'H', 'o', 'w', '\0'}。
所以输出
Bash | |
---|---|
注意函数中不仅有返回值,遇到printf也会输出
C | |
---|---|
Warning
注意函数里面的printf
#define
直接展开,和前面的容易产生优先级的错误
C | |
---|---|
#define F(k) k+3
是直接展开的,变成了2*k+3
P2(x, 9*x+32)
在x=1的时候,进入循环被执行,然后调用#define P2(a, b) P(a);P1(b);,展开后为P(1);P1(9*1+32);
注意到此时for没有括号,所以只能保住一个P(1)
在下个P2(x*9+32)
的时候就已经变成了50.
P2
展开后为P(1);P(9*1+32);P('#');
之后带入计算就好了
\101
是八进制数,对应的ASCII码是65,也就是A
zju\101\\TED
占几个字节?
z
:普通字符,占 1 个字节。j
:普通字符,占 1 个字节。u
:普通字符,占 1 个字节。\101
:转义字符,表示八进制数101
对应的 ASCII 字符。- 八进制
101
转换为十进制是65
,对应 ASCII 字符'A'
。 - 占 1 个字节。
\\
:转义字符,表示反斜杠\
。- 占 1 个字节。
T
:普通字符,占 1 个字节。E
:普通字符,占 1 个字节。D
:普通字符,占 1 个字节。
Text Only | |
---|---|
strlen
计算字符串的长度,不包括末尾的空字符\0
。- 因此,
strlen("zju\101\\TED")
的结果是 8。
C | |
---|---|
Warning
注意转义字符的使用,\101
是八进制数,对应的ASCII码是65,也就是A,代表的是一个字节
在 C 语言中,八进制转义序列的格式为:
\ooo
,其中ooo
是 1 到 3 位的八进制数字。
八进制转义序列 | 十进制值 | ASCII 字符 |
---|---|---|
'\0' |
0 | 空字符 |
'\12' |
10 | 换行符 |
'\40' |
32 | 空格 |
'\101' |
65 | A |
'\141' |
97 | a |
- 八进制转义序列的长度最多为 3 位,且每位数字必须在
0
到7
之间。 - 如果八进制数字超过 3 位,只有前 3 位会被解释为转义序列,后面的字符会被当作普通字符处理。
转义字符\
单独出现不合法
在 C 语言(以及其他许多编程语言)中,反斜杠 \
是一个转义字符,用于表示特殊字符或具有特殊含义的字符序列。
转义序列 | 含义 |
---|---|
\\ |
表示一个反斜杠 \ |
\' |
表示单引号 ' |
\" |
表示双引号 " |
\n |
表示换行符(Newline) |
\t |
表示水平制表符(Tab) |
\r |
表示回车符(Carriage Return) |
\b |
表示退格符(Backspace) |
\f |
表示换页符(Form Feed) |
\a |
表示响铃符(Alert/Bell) |
\0 |
表示空字符(Null) |
\xhh |
表示十六进制值的字符 |
\ooo |
表示八进制值的字符 |
- 单独出现的时候编译时会报错,例如:
error: stray '\' in program
。 - 如果需要在字符串或字符常量中表示一个反斜杠
\
,必须使用转义序列\\
。 - 如果需要在字符串中表示其他特殊字符(如换行符、制表符等),也需要使用相应的转义序列。
C | |
---|---|
- 单独使用
\
:C - 未闭合的转义序列:
C -
无效的转义序列:
C -
\
是转义字符,用于表示特殊字符或字符序列。 - 单独出现
\
是不合法的,必须与有效的转义序列一起使用。 - 如果需要表示反斜杠
\
,必须使用\\
。 - 转义字符在字符串和字符常量中非常常见,用于处理特殊字符或格式化输出。
判断函数的意思再做题
The output of the following program is _______.
先判断函数的意思是什么,再写,不要上来就编译,人不是编译器
fgets()函数用来逐行查找字符,返回指针
fgets() 函数的作用是从一个流中读取一行字符串,并存入一个缓冲区中。没成功读取就返回NULL。
可以用来逐行查找文件
命令行参数argv
和argc
(前面写过一点)
命令行参数的第一个参数是程序名,后面的参数是命令行参数。
这个时候,就算没有scanf这些输入输出函数,也可以用argv来处理命令行参数了
-
如果不写 argc 和 argv,程序默认无法访问命令行参数。
-
如果需要处理命令行参数,必须将 main 函数定义为 int main(int argc, char *argv[])。
-
argc 和 argv 是 C 语言中处理命令行参数的标准方式。一般约定俗成,代码可维护性高,不推荐改成别的名称(虽然可以改)
与的优先级比或高
&
的优先级比||
高,所以(x<=a)||(x>=b)&&(x<=c)
表达式会被解析为:
(x <= a) || ((x >= b) && (x <= c))
数组的指针和指针的数组
int *p[3];
和int (*p)[3]
有什么区别
int *p[3]
是定义了一个长度为 3 的数组,数组的每个元素都是 int*
类型的指针。
int (*p)[3]
是定义了一个指向长度为 3 的数组的指针,该指针指向 int[3]
类型的数组。
ps:int **p;
则是一个指向指针的指针,指向一个 int
类型的指针
++p和p++的区别
for ( ; sp++ <= sq--; ) *sp = *sq;
执行到*sp = *sq
时,sp
和sq
的都完成了自加和自减操作,所以*sp = *sq
是*sp = *(sq - 1)
。
不是当时的比较值.
字符串没填满数组时sizeof的输出是数组的长度
C | |
---|---|
sizeof输出的是数组的长度,也就是80
C | |
---|---|
这里输出的是s的长度,所以\0也会被计入,并且最后还有一个'\0',所以是23个字符
long的大小
C | |
---|---|
f()
传入一个long
类型的数组,返回a的大小,并且转换成int类型
全局变量未指明默认赋值为1,
C | |
---|---|
局部变量
注意在函数中的局部变量在函数结束之后会被释放掉,所以在函数中不能再使用之前的局部变量,只能使用函数中的局部变量
while(k-->0)
的不是赋值也不是word敲错了,是表示判断k>0然后自减
这个while(k-->0)
的意思是k--直到k小于0,然后打印d[k]的值,如果d[k]小于10,则打印'0'+d[k],如果d[k]大于等于10,则打印'A'+d[k]-10,也就是将10进制的数转化为16进制的数,不是赋值
int* p[]
表示这个数组存储的是地址,并且是指针类型的地址,可以p[k][m]
直接访问a[k]
后面的第m个元素
我们来逐行解释这个程序:
- 首先定义了一个函数
matrixTrace(int* m[],int n)
,这个函数的作用是计算矩阵的迹。 - 然后定义了一个
main()
函数,这个函数的作用是初始化矩阵,并调用matrixTrace()
函数。 - 然后初始化了一个
int
型数组a
和int
型指针数组m
- 然后用
for
循环初始化a
数组,并用for
循环初始化m
数组,m[k]
指向a
数组的第5*k
个元素。也就是m[k]
存储的是a[0]
到a[5*k]
的地址. - 然后调用
matrixTrace()
函数,并传入m
和5
作为参数。 m[k][k]
的意思是m[k]
指向的数组的a[5*k]
之后第k个元素的值。- 然后计算
m[k][k]
的和,并返回。 - 最后打印出
matrixTrace()
函数的返回值。
Note
当m[k]
是一个指针,指向数组 a 中的某个位置。m[k][k]
是对指针 m[k]
进行下标访问,相当于 *(m[k] + k)
注意题目的小细节,c[i] = b[2*i];
浮点数取特定位数
C | |
---|---|
都是四舍五入进位的,和转int不一样,转int是直接切掉小数部分取整数部分
- 设char s[]="ABC ABC"; 则strcmp(s, s+4)的值为_________.
strcmp(s, s+4)
的作用是比较两个字符串的大小,返回值为两个字符串的 ASCII 码差值。
int strcmp(const char *str1, const char *str2);
返回值 < 0:str1 小于 str2(按字典顺序)。
返回值 = 0:str1 等于 str2。
返回值 > 0:str1 大于 str2(按字典顺序)
strcmp 会逐个字符比较 str1 和 str2,直到遇到不同的字符或字符串结束符 \0。比较是基于字符的 ASCII 值进行的。
- 字符串
s
的内容: s
是一个字符数组,内容为"ABC ABC"
。-
在内存中,
s
的存储形式为:{'A', 'B', 'C', ' ', 'A', 'B', 'C', '\0'}
。 -
s+4
的含义: s
是数组的首地址,s+4
表示从s
的第 4 个字符开始的字符串。-
s+4
指向的内容是"ABC"
(从第 4 个字符'A'
开始,到字符串结束符'\0'
)。 -
strcmp(s, s+4)
的比较: strcmp
会逐个字符比较两个字符串,直到遇到不同的字符或字符串结束符\0
。- 比较过程如下:
s
的第一个字符是'A'
,s+4
的第一个字符也是'A'
,相等,继续比较。s
的第二个字符是'B'
,s+4
的第二个字符也是'B'
,相等,继续比较。s
的第三个字符是'C'
,s+4
的第三个字符也是'C'
,相等,继续比较。s
的第四个字符是' '
(空格),s+4
的第四个字符是'\0'
(字符串结束符)。- 空格字符
' '
的 ASCII 值是 32,而'\0'
的 ASCII 值是 0。 - 因为
' '
的 ASCII 值大于'\0'
,所以strcmp(s, s+4)
返回一个正数。
int *p[3];
是一个 指针数组,表示 p
是一个包含 3 个 int*
指针的数组。
适用于存储多个指针的场景。
int (*p)[3];
是一个 数组指针,表示 p 是一个指针,指向一个包含 3 个整数的数组。
需要(*p)[i]
来访问数组的元素
特性 | int *p[3]; (指针数组) |
int (*p)[3]; (数组指针) |
---|---|---|
类型 | 数组,每个元素是一个 int* 指针 |
指针,指向一个包含 3 个 int 的数组 |
内存布局 | 数组中有 3 个指针 | 指针指向一个数组 |
适用场景 | 存储多个指针,例如动态分配的数组 | 指向二维数组的行 |
示例 | int *p[3] = {&a, &b, &c}; |
int (*p)[3] = arr; |
访问方式 | *p[0] , *p[1] , *p[2] |
(*p)[0] , (*p)[1] , (*p)[2] |
设有定义int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
现要使p=a
;则p
的定义必须为_________.
A.int p[3][3];
B.int *p[3];
C.int (*p)[3];
D.int **p;
答案:C, 因为p
是一个指向数组的指针,指向一个包含 3 个整数的数组。所以用(*p)[3]
的方式来定义
运算顺序
先加减,后按位计算,最后再与或非
Note
但是按照位取反的优先级很高,和sizeof一样
主要的几个档次
- 数组下标
[]
,()
,.
,->
和后置的自增自减++
--
- 前置的自增自减,负号运算符号
-
,类型转换()
,地址解码*
,取地址&
,逻辑!
非,和取反~
- 乘除取模
- 加减
- 左移右移
>>
,<<
- 关系判断,
<
,>
,<=
,>=
, !=
和==
- 按位与
&
- 按位异或
^
- 按位或
|
- 逻辑与
&&
- 逻辑或
||
- 条件运算符
?:
- 赋值系列:
=,+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>=
- 逗号
,
C | |
---|---|
每一个地方都是陷阱
为什么定义成char p[3][4]={"ABC", "DEF", "XYZ"};
而不是char p[3][3]={"ABC", "DEF", "XYZ"};
因为字符串是以\0
结尾的,所以要多一个位置
所以*(*(q+1)-1) = *p[2];
被赋值的是'\0`
所以puts(*p);
输出的是ABCXDEF
静态全局变量和外部变量
用extern表示外部变量
动态内存申请
malloc函数用于动态分配内存,其原型为:
C | |
---|---|
该函数返回一个指针,指向分配的内存块的起始地址。如果分配失败,则返回NULL。
free函数用于释放由malloc函数分配的内存,其原型为:
C | |
---|---|
该函数释放由ptr指向的内存块。
C | |
---|---|
注意switch语句的使用,注意break是跳出哪一条语句的,不是有一个break就认为都能跳出不会直接执行了
若定义static int a[3][4]={{1,2,3},{4,5,6}}
,则表达式a[2][-1]+ a[1][1]
的值是_________.
注意啊,数组static没定义也是0,不是随机数
short类型的变量占2个字节,所以sizeof(short)
是2
long类型的变量占4个字节,所以sizeof(long)
是4
int类型的变量占4个字节,所以sizeof(int)
是4
double类型的变量占8个字节,所以sizeof(double)
是8
float类型的变量占4个字节,所以sizeof(float)
是4
Warning
float类型的变量占4个字节,double类型的变量占8个字节。
若定义short a[4]={11, 22, 33, 44}; ,则表达式 (int)&a[3] – (int)&a[0]的值是_________
三个2,答案是6
*p++,先解码再++,自增的也是p指向下一个地址
*++p,先++再解码,先指向下一个地址在解码
- 第一次调用
f(2)
: k
初始为 0,m
为 2。- 循环条件
k <= m
,循环 3 次(k = 0, 1, 2
)。 - 每次循环
s
自增 1,s
从 0 变为 3。 - 返回
s = 3
。 -
此时
k = 3
(因为++k
在最后一次循环后执行)。 -
第二次调用
f(1)
: k
的值为 3,m
为 1。- 循环条件
k <= m
不成立,循环不执行。 -
返回
s = 3
(s
没有变化)。 -
s = f(2) + f(1) = 3 + 3 = 6
。 -
调用
f(3)
: k
的值为 3,m
为 3。- 循环条件
k <= m
,循环 1 次(k = 3
)。 s
自增 1,s
从 3 变为 4。-
返回
s = 4
。 -
s = 6
(局部变量)。 f(3) = 4
。- 输出结果为
6#4#
。
Bash | |
---|---|
- 全局变量
s
在函数f
中被修改。 - 静态变量
k
保留了上一次调用的值。 - 局部变量
s
在main
函数中与全局变量s
同名,但作用域不同。 - 最终输出结果为
6#4#
。
!!! 注意全局变量和局部变量的作用域,重名的话以局部为准,看看有没有哪个函数被命名了两次的
结构体: