基本信息

授课教师: yhj
上课教材: 用不着
编译环境: gcc version 13.1.0 (x86_64-win32-seh-rev1, Built by MinGW-Builds project)
参考资料: C小程 琐碎知识点整理 | C 语言应试笔记 | 《C 陷阱与缺陷》笔记 | 何钦明PTA答案

P.S. 感恩学长学姐们的无私分享

 

MISC

  1. 设有声明:char *s1="xyz",*s2="123",t1[10],*t2;则能完成字符串 s1 和 s2 的值交换选项有( C

    A.  t1=s1;s1=s2;s2=t1;
    B. strcpy(t1,s1);strcpy(s1,s2);strcpy(s2,t1);
    C. t2=s1;s1=s2;s2=t2;
    D. strcpy(t2,s1);strcpy(s1,s2);strcpy(s2,t2);

    解释: 首先要明确题干中变量的类型,s1,s2以及t2的类型都是字符型指针,而t1的类型是字符数组(C语言没有字符串类型,题干中的字符串指的应该是抽象的字符串)
    然后我们逐个选项观察,A选项试图将s1赋值给t1,这个操作就是不合法的。在C中数组(不论是什么数组)是不可作为左值进行赋值操作的,因此A直接pass;
    B选项使用了string.h库中的strcpy函数,这个函数接受两个字符型指针char*,从前者的’\0’开始,把后者连带着’\0’拷贝到前者。但是strcpy要求目标指针(也就是前者)是可变的,而s1是不可变的,因此运行时会在第二句语句报出段错误Segmentation fault;
    C选项没有问题,C语言虽然不支持字符型指针向字符数组赋值,但是支持字符型指针向字符型指针赋值,因此s1("xyz"的首元素的地址)赋给了t2并完成接下来的交换操作;
    D选项不合法之处与B类似,不再赘述。
    这道题涉及到了很多东西,对C的理解更上一层楼,不失为又新又好。

  2. 以下程序的输出结果是( ONALAMB )。

    #include <stdio.h>
    #include <string.h>
    typedef char (*AP)[5];
    AP defy(char *p)
    {
    int i;
    for(i=0; i<3; i++)
    p[strlen(p)] = 'A';
    return (AP)p + 1;
    }
    void main()
    {
    char a[]="FROG\0SEAL\0LION\0LAMB";
    puts( defy(a)[1]+2 );
    }

    解释: 行3 首先定义了一个新的变量类型AP表示一个大小(注意不是字节)为5的字符型指针。函数defy接受一个字符型指针,重复三次以下操作:p[strlen(p)]='A';,这句话的意思是将p所指向的字符串的后导’\0’替换为’A’,最后将p强制类型转换为AP(即p变成了逻辑上的大小为n*5的二维字符数组)并+1(意思即是指向下5个字符)。
    接下来看主函数部分,定义了字符数组a,初始化为由’\0’分隔的4个4个字符,接下来将a传入defy函数,根据上面的分析我们可以知道a的值被修改为了"FROGASEALALIONALAMB",接下来p(注意修改p不会影响a,指针的传参本身也是传值的)被强转为AP,于是p的逻辑结构变为{“FROGA”,“SEALA”,“LIONA”,“LAMB”}(注意只是逻辑结构,因此前面三个字符串并没有后导’\0’)。接下来p+1返回的便是以"SEALA"的’S’的地址为值的一个n*5的二维字符数组。
    最后在puts函数里面,返回的defy(a)被取了第1个字符串即"LIONA"的首地址后又向右移动了两个字符大小的地址,即指向了’O’,于是puts会打印从’O’到其后的第一个’\0’前的所有字符,即ONALAMB
    这道题要求我们十分清楚每个变量在每个操作后的类型,搞明白类型后就简单了。

  3. 关于变量声明的解读方法,以int **a[2][3];为例:
    采用从a开始,向右向左看,组词造句的方法 | 此处参考

    • a is … int;
    • a is array of 2 … int;
    • a is array of 2 of array of 3 … int;
    • a is array of 2 of array of 3 pointer(s) to … int;
    • a is array of 2 of array of 3 pointer(s) to pointer(s) to int;
    • a 是一个2*3的数组,每个数组元素都是一个指向整型指针的指针

    作为习题,请读者分析一下以下声明中,a的类型和a的字节数(64位操作系统):static char *(*(**a[2][3])())[8];


    key: a is array of 2 of array of 3 pointer to pointer to function returning pointer to array of 8 pointer to static char.
    sizeof(a) = 48 = 2 * 3 * 8

  4. 关于特殊转义符

    转义序列 含义
    \a BEL(响铃)
    \b BS(退格)
    \f FF(水平制表符)
    \n LF(换行)
    \r CR(回车)
    \t HT(制表符)
    \v VT(垂直制表符)
    \\ \
    \’
    \" "
    \0 NULL
    \ddd 八进制
    \xhh 十六进制

    最后两个表示对应进制下用ASCII码表示的字符

 

0919

  1. 程序调试就是找出并改正C源程序中的语法错误。(F)
  2. 上机运行以下程序(例1-1),输入整数13,输出结果是错误的,其原因是( )。
    #include <stdio.h>              /* 编译预处理命令 */
    int main(void) /* 主函数 */
    {
    int n; /* 变量定义 */
    int factorial(int n); /* 函数声明 */

    scanf("%d", &n); /* 输入一个整数 */
    printf("%d\n", factorial(n)); /* 调用函数计算阶乘 */

    return 0;
    }

    int factorial(int n) /* 定义计算 n! 的函数 */
    {
    int i, fact = 1;

    for(i = 1; i <= n; i++){
    fact = fact * i;
    }

    return fact;
    }
    运算结果超出了整数的取值范围
    解释: 13!=6227020800>2161=6553513!=6227020800>2^{16}-1=65535

 

0926

  1. 执行以下程序段,输入1.0 0.01 365,输出1.0#0.010#365。(T
    int day;
    double factor, initial;

    scanf("%lf %lf %d", &initial, &factor, &day);
    printf("%.1f#%.3f#%d", initial, factor, day);
    解释: 在printf()里面,%f等价于%lf,因此不会出错(scanf()会出错)
  2. 执行以下程序段,并回答下列问题。请注意,直接填数字,前后不要加空格等任何其他字符。
    int fahr;
    double celsius;
    for (fahr = 121 ; fahr <= 125; fahr++) ;
    celsius = 5.0 * (fahr - 32) / 9.0;
    printf("%4d%6.1f\n", fahr, celsius);
    循环体语句共执行了(0)次
    当循环结束时,变量fahr的值是(126
    解释: 无聊粪题的代表

 

1007

  1. 为了检查以下if-else语句的两个分支是否正确,至少需要设计3组测试用例,其相应的输入数据和预期输出结果是( ) 。
    int x, y;
    scanf("%d%d", &x, &y);
    if(x >= y){
    printf("max = %d\n", x);
    }else{
    printf("max = %d\n", y);
    }
    输入3和4,输出4;输入5和5,输出5;输入4和3,输出4。
    解释: 要让x大于,等于,小于y的情况都出现一遍

 

1010

  1. if-else语句的一般形式如下,其中的语句1、语句2只能是一条语句。(T

    if (表达式) 
    语句1
    else
    语句2

    解释: 语句块视作多条语句,因此只能是一条语句

  2. 省略else的if语句的一般形式如下,若表达式的值为“真”,则执行语句1;否则,就什么也不做。(T

    if (表达式) 
    语句1

    解释: 省略else是真没有else,因此程序就是孤零零的if,文字游戏的粪题

  3. 如果变量已经正确定义,则执行以下程序段后,x的值不变。(F

    x = 4; 
    if (x < 0){
    y = -1;
    }else if (x = 0){
    y = 0;
    }else{
    y = 1;
    }

    解释: 注意行4 是赋值运算符,因此x会被赋值为0,y会被赋值为1

  4. 执行语句putchar(‘S’);后,在屏幕上显示的输出结果是’S’。(F
    解释: 输出的结果没有引号

  5. 设变量已正确定义,执行以下程序段,顺序输入三个字符’Q’,则输出Q。(F

    ch = getchar(); 
    putchar(ch);

    解释: 跑出来的结果是对的(输出Q),暂时不知道出题人玩的什么文字游戏,或者他用的什么古早编译器,当成对策题记住好了

  6. 写出以下程序段的运行结果。请注意,直接填单词、字符或者两者的组合,前后不要加空格等任何其他字符。

    double grade; 
    scanf ("%lf", &grade);
    if(grade < 60); {
    printf("Fail");
    }
    printf("?");

    输入50,输出( Fail?

    输入90,输出( Fail?
    解释: 眼力题,注意行3 后面有个分号,出题人大概是没活整了

  7. 写出以下程序段的运行结果。请注意,直接填单词,前后不要加空格等任何其他字符。

    mynumber = 38;
    scanf ("%d", &yournumber);
    if(yournumber == mynumber){
    printf("Right");
    }
    if(yournumber > mynumber ){
    printf("Big");
    }else{
    printf("Small");
    }

    输入20,输出small
    输入38,输出(RightSmall
    输入50,输出(Big
    解释: 眼力题,注意行6 的if前面没有else,因此输入38会输出两个结果

 

1017

  1. while语句的一般形式如下,其中的循环体语句只能是一条语句。(F
    while (表达式)
    循环体语句
    解释: 同1010-1
  2. 写出以下程序段的运行结果。请注意,直接填数字、单词或者两者的组合,前后不要加空格等任何其他字符。
    char op;
    int value1, value2;
    scanf("%d%c%d", &value1, &op, &value2);
    switch(op){
    case '+': printf("%d", value1 + value2);
    default: printf("Error");
    case '-': printf("%d", value1 - value2);
    }
    输入11+1,输出(12Error10
    输入10$100,输出(Error-90
    解释: switch-case等价于goto,没有break正常往下执行
  3. 若变量已正确定义,写出以下程序段的运行结果。
    scanf ("%lf", &eps);
    i = 0;
    flag = 1;
    denominator = 1;
    item = 1.0;
    s = 0;
    while(fabs(item) >= eps){
    item = flag * 1.0 / denominator;
    s = s + item;
    i++;
    flag = -flag;
    denominator = denominator + 2;
    }
    printf ("%.2f#%d\n", s, i);
    输入2,输出( 0.00#
    输入1,输出( 0.67#
    输入0.2,输出0.72#
    解释: 我不知道考试的时候怎么算这种鸟题
  4. 若变量已正确定义,写出以下程序段的运行结果。
    scanf ("%d", &k); 
    while(k >= 0){
    scanf ("%d", &k);
    printf("%d#", k);
    }
    输入1 2 3 0 -1,输出( 2#3#0#-1#
    输入1 2 3 -1 9,输出( 2#3#-1#
    解释: 注意scanf重复了,因此第一个值会被丢弃,并且判断在输出之后,因此第一个负值照常输出

 

1024数组

  1. 数组定义后,只能引用单个的数组元素,而不能一次引用整个数组。(F
    解释: 出题人的意思是没有array[0:100]的操作,那的确没有
  2. 如果变量定义如下,则正确的语句是(BCE)。
    int  k, a[10];

    A. a[-1] = -1;
    B. a[0] = 23;
    C. k = 3;
    a[k - 2] = a[9] + 1;
    D. for(k = 1; k <= 10; k++){
    printf("%d ", a[k]);
    }
    E. for(k = 0; k < 10; k++){
    scanf("%d ", &a[k]);
    }
    解释: 我不知道出题人对“正确”是个什么定义,如果是过编,那不好意思,都能过编。上面是题外话,C语言会给数组赋初值0,因此C没问题,AD都越界了
  3. 在以下描述中,(ABCE)是正确的。
    A.  int a[5] = {1, 2, 3, 4, 5};
    定义了数组a,并对数组元素赋初值。此时,a[0]为1,a[1]为2,a[2]为3,a[3]为4,a[4]为5
    B. static int b[10];
    定义了静态数组b,且10个数组元素的初值都为0
    C. int fib[45];
    定义了数组fib,且45个数组元素的值都为0
    D. static int week[7] = {1, 2, 3};
    定义了静态数组week,并对数组 week 的前3个元素week[0]~week[2]赋初值,week[3]~week[6]值都是不确定的。
    E. int cnt[10] = {1};
    定义了数组cnt,并对cnt[0]赋初值1,其余元素的初值为0
    解释: 原因同1024数组-2

 

1031

  1. 以下程序段的功能是输出1~100之间每个整数的各位数字之和。(F
    for(num = 1; num <= 100; num++){ 
    s = 0;
    do{
    s = s + num % 10;
    num = num / 10;
    }while(num != 0);
    printf("%d\n", s);
    }
    解释: 应该是每个数字的逆转数

P.S.1031的题目集简直是粪题大赏,实在不想看,这里省略

 

1107

  1. 08是正确的整型常量。(F
  2. 表达式0195是一个八进制整数。(F
  3. 下面程序段的输出是(18)。
    int x=023;
    printf("%d\n",--x);
    解释: 在C语言里面,字符插入整型表示一些特殊进制或计数法:
    //前缀
    printf("%d\n",0b10); //2
    printf("%d\n",010); //8
    printf("%d\n",10); //10
    printf("%d\n",0x10); //16 或是0X10
    printf("%lf\n",2e3); //2000.000000
    //后缀 LU不敏感大小写,也可以改变顺序
    printf("%ld\n",123L); //长整型 long (int)
    printf("%lld\n",123LL); //长长整型 long long (int)
    printf("%u\n",123U); //无符号整型 unsigned (int)
    printf("%llu\n",1LLU); //无符号长长整型 unsigned long long (int)
    //注意:C只规定了sizeof(short)<=sizeof(int)<=sizeof(long)<=sizeof(long long)
    //具体占用多少字节及数据范围由编译器决定
    //我的编译器分别是2 4 4 8
    08是八进制,不能有8
  4. 在C语言程序中,若对函数类型未加显式说明,则函数的隐含类型为(int)。
    解释: C是int,C++是void
  5. 以下正确的函数定义形式是()。
    double fun(int x, int y)
    解释: 牛角尖大赏,定义不能有分号,声明可以

 

1114

  1. 执行语句int *p = 1000;后,指针变量p指向地址为1000的变量。(F
    解释: 编译报错,即使改为int* p=(int*)1000;访问时依然会出现段错误

  2. 表达式 (z=0, (x=2)||(z=1),z) 的值是1。(F
    解释: 逗号运算符,从左往右,返回最后一个表达式的值;||具有短路求值的特性,因此z=1没有执行

  3. 运算符( )的优先级最高。
    解释:
    参考:C 运算符优先级

    类别 运算符 结合性
    后缀 () [] -> . ++ -- 从左到右
    一元 + - ! ~ ++ – (type) * & sizeof 从右到左
    乘除 * / % 从左到右
    加减 + - 从左到右
    移位 << >> 从左到右
    关系 < <= > >= 从左到右
    相等 == != 从左到右
    位与 AND & 从左到右
    位异或 XOR ^ 从左到右
    位或 OR | 从左到右
    逻辑与 AND && 从左到右
    逻辑或 OR || 从左到右
    条件 ?: 从右到左
    赋值 = += -= *= /= %=>>= <<= &= ^= |= 从右到左
    逗号 , 从左到右

    那本没用教材里面的表格比较细致,有个大概印象就行,要注意的是:

    • 逻辑非和按位非优先级特别高
    • 后缀的+±-是i++这样的,一元的+±-的–i这样的
    • 一元类别里面的±是正负号,&是取址,*是取值,(type)是强转类型,sizeof是取类型的字节数
    • 表示下标的>对单个数操作的>数字运算>移位>关系>位运算>逻辑运算>条件运算>赋值运算>逗号
  4. 假设整型数据用两个字节表示,则用二进制表示-127的原码为(11111111)、反码为(10000000)、补码为(10000001
    解释:

    数字类型 原码 反码 补码
    正数 43 00101011 相同 00101011 相同 00101011
    负数 -43 正数原码,符号位置1 10101011 除符号位取反 11010100 反码加1 11010101
    +0 00000000 00000000 00000000
    -0 10000000 11111111 加一后溢出 00000000

    0的补码唯一,补码可以直接相加

  5. 写出以下程序段的运行结果。

    int a = 2, b = 0, c = 0;
    printf("%d\n", !a && b);
    printf("%d\n", a||3+10&&2);

    第1行输出(0
    第2行输出(1
    解释: 行3 注意运算符优先级,先+,再&&,最后||

  6. 写出以下程序段的运行结果。

    char ch = 'w';
    int b = 0;
    printf("%d\n", ch || (b = 10));
    printf("%d\n", b);

    第1行输出(1
    第2行输出(0
    解释: 行3 注意||具有短路求值的特性,b=10未执行,因此行4 的输出是0

  7. 写出满足下列条件的C表达式。
    x 和 y 不同时为零。
    !(x==0 && y==0)
    x!=0 || y!=0
    解释: 注意理解题意,不/同时为0

 

期中考试

  1. 可以用关系运算符中的“==”运算符,来判断两个数是否相等。( F
    解释:
    float f=0.1
    printf("%d\n",f==0.1); \\0
    printf("%d\n",f!=0.1); \\1
    由于浮点数编码的原因,浮点数无法精确地在计算机中被储存,因此==无法判断浮点数之间是否相等,粪题对策题
  2. 设变量定义为 int a[2]={1,3}, *p=&a[0]+1;,则*p的值是( 3 )。
    解释: p是指针,是a+1

 

1121

  1. 对于以下程序段,则叙述正确的是( D )。
    char s[ ]="china"; 
    char *p;
    p = s;

    A. s和p完全相同
    B. 数组s中的内容和指针变量p中的内容相等
    C. 数组s的长度和p所指向的字符串长度相等
    D. *p与s[0]相等
    解释: s是字符数组,p是字符指针,不一样;内容不一样,s是字符串,p只是一个地址;长度不等,字符数组包括了’\0’,字符串不包括;*p和s[0]相等,因为s的首地址赋给了p

 

1128

  1. 若程序中有下面的说明和定义,则会发生的情况是( 程序将顺利编译、连接、执行 )。
    struct abc { 
    int x;
    char y;
    };
    struct abc s1, s2;
    解释: 查了个答案说是编译出错,但是那个答案少了个分号这个没问题,跑过

 

1226

文件输入输出,由于没有NOIP经历,因此这是一个我个人基本没涉猎过的领域,做个小笔记

文件类型分类与读取模式分类

  • 文本文件 - 文本模式
  • 二进制文件 - 二进制模式

样例程序

#include<stdio.h>

int main(){
FILE *fp = fopen(“temp.txt”, “r”); //以只读模式打开文本文件
//fopen失败则返回NULL,有时我们要做错误预警,这里就省去了
char ch;
ch = fgetc(fp); //获取文件第一个字符
while(ch!=EOF){ //EOF表示文件的末尾,值等于-1
ch = fgetc(fp);
putchar();
}
if(fclose(fp)!=0){ //关闭文件fp,成功返回0,失败则返回-1
printf(“ERROR\n”);
}
FILE *in, *out;
in = fopen(“temp.in”, “r”);
out = fopen(“temp.out”, “w”); //以覆写模式,即将长度截为0的模式打开
int x;
fscanf(in, “%d”, &x);
fprintf(out, “%d\n”, x); //在文件in中读取一个数字,写入文件out
fclose(in);
fclose(out);
}

键盘,屏幕输入输出也是文件输入输出的一种

  • 标准输入:stdin
  • 标准输出:stdout

fscanf(stdin, “%d”, &x)scanf(“%d”, &x)等价

fprintf(stdout, “%d\n”, x)printf(“%d\n”, x)等价

其他文件读写函数

  • fgets(s, SIZE, fp)s为存储的字符数组,SIZE为待输入字符串的大小,fp为文件指针
  • fputs(s, fp)s为待输出的字符指针,fp为文件指针
  • fseek(fp, nums, ori)fp为文件指针,nums为偏移字节数,ori为起始位置
  • ftell(fp)返回fp距离文件开始处的字节数

其他读写模式

模式 含义
r 只读
w 覆写
a 追加
r+ 更新
w+ 更新并覆写
a+ 更新并追加
加个b 以二进制读写