Skip to content

C易错点总结(语法篇)

杂项

标识符: 字母、数字、下划线(首字母不能是数字

输入输出

scanf的格式字符串

float用%f输入,double用%lf,否则会出错

double x;   
scanf("%f", &x);  //应用lf

printf的格式字符串

%%%转义

%o,%x,%u表示无符号的八进制,十六进制,十进制 (%#o会加上0,0x标记)

printf("%o %#o %x %#x",20,20,20,20);
输出//24 024 14 0x14

%md系列:

%md,表示输出的宽度为m.当位数< m 左补空格否则按实际输出。常用于输出表格。

%0md, 类似%md,但左补0

%+md在有符号正数前显示+

%-md 让数据左对齐,和%md默认右对齐相反

printf("%2d\n",2218);//输出2218(位数>2)按实际
printf("%+6d\n",2218);//□□ 2218 (□表示空格)
printf("%6d\n",2218); //□+2218
printf("%-6d\n",2218);//2218□□
printf("%+-6d\n",2218);//+2218□
printf("%06d\n",2218);//002218
浮点数系列:

%e指数形式输出浮点数(科学计数法)。注意%e%f默认位数都是6位

printf("%e",22.18) //2.218000e+001

%.nfn位小数.double加上l

%m.nf表示浮点数一共(整数+小数点+小数)m位,其中n位是小数。跟%md一样,如果超过就原样输出,右对齐。同样可以使用-修饰来左对齐

printf("%-6.1f",123.18);//123.2□

数据类型,运算符,表达式

数据类型

浮点类型

char类型

在对char进行算术运算的时候可以当成整形,但注意char的范围是[-128,127],在给char赋值时要防止溢出

看到\后面有数字的默认八进制,\012 \111表示八进制ASCII码,\x41十六进制,所以在一串字符中看到\0不一定就是字符串结束标志,要看后面有没有其他数字。

printf("%d",strlen("abc\01234\0xy"));//输出6

ASCII码表中0-9 < A-Z < a-z , 9-A,Z-a中间还有别的字符

类型转换

C里面类型转换必须要在前面加括号 (类型名),不能当成函数用 (int)a√,int(a)×

运算符

sizeof是一个运算符,不是函数

%的参数必须是整形,a%b正负取决于a

运算符“+”可以作为单目运算符。(如+x,-x)

赋值运算的嵌套 x=y=zx=(y=z)都可以,但左边必须是变量,不能是表达式

优先级与表达式求值(重要!)

总结:

单目(~,!,强制类型转换(type),++,--) > 算术 > 比较>,< > 比较相等==,!= > 按位与 > 按位或 > 逻辑与 > 逻辑或 > 三目条件`?:``>赋值

一般方法 1. 先看优先级,找到优先级最低的运算符,然后分别处理该运算符左右两边的表达式(一般先算左边的,但实际上求值顺序是不确定的),直到分解完毕。 2. 一个数两边符号优先级相同,看左结合/右结合,如果是右结合就先算右边,结果再和左边算 如x+=x-=10,相当于x+=(x-=10) . 赋值是右结合的,其他如a<b<c是左结合的 3. 逻辑运算符两边注意短路!(&&看到0,||看到1就停),后面的不算了 4. 逻辑表达式,把整数转成0/1,一定要看清是与还是或 5. 看到有前导零的整数如0234,一定是八进制。0x123是十六进制 6. 逗号表达式从左到右执行,返回最后一个的值 7. 涉及到除法的时候注意是整形还是浮点型 8. 看清是=还是==,注意赋值运算符的返回值

例: 表达式 ~(~2<<1)的值是5。(取反优先级最高,注意取反不是求补码,~2=01(二进制))

表达式 `(z=0, (x=2)||(z=1),z)`` 的值是0 (注意短路)

例题:

#include<stdio.h>
int main(){

char ch = 'w';
int a = 2, b = 3, c = 1, d, x=10;
printf("%d", a > b == c);// (a>b)==c 0
printf("%d", d = a > b);//d=(a>b) 0
printf("%d", ch >'a' + 1); //ch>('a'+1) 1
printf("%d", d = a + b > c); //(a+b)>c 1
printf("%d", b - 1 == a != c); // (b-1==a)!=c 左结合0
printf("%d", 3 <= x <= 5); //(3<=x)<=5
//输出001101

char ch = 'w';
int a = 2, b = 0, c = 0;
float x = 3.0; 
printf("%d", a && b);//1
printf("%d", a || b && c); //(a||(b&&c))
printf("%d", !a && b);
printf("%d", a||3+10&&2);// a||((3+10)&&2)
printf("%d", !(x == 2));
printf("%d", !x == 2);
printf("%d", ch || b);
//输出0101101

还有一些编程时的易错点

  1. 算术运算符>位运算
  2. 比较运算符>位运算,如a&b!=0会变成a&(b!=0)
  3. 比较运算符>赋值,如 c=getchar()!='\n' 会变成c=(getchar()!='\n')而出错

选择结构

if

  • else优先匹配最后一个未匹配的if,跟缩进无关
  • 注意空语句if();
  • 注意没有大括号的一连串语句if()a;b;(只有a在内部) if()a,b;(a,b都在内部)

对于for,while的循环也同理,常在这里设坑。

if(a>=1)
    if(a==0) xxx
else yyy
//等价于
if(a>=1){
    if(a==0) xxx
    else yyy
}

switch

  • switch中不能有两个重复的case标记
  • case后面必须是常量表达式
  • 无break和default可以通过编译
switch(k){
    default: break;
    case 1: n+=k;
    case 2:
    case 3: n+=k;
}
  • 若有语句但无break,把接下来所有case的语句都执行,直到有break或者到末尾
  • 若没有语句,和下面的case公用(如case2和case3) default同理

多个switch嵌套注意哪里有break

循环结构

同样注意无括号for(;;)a;b;

while中的循环体语句只能是一条语句(用{}括起来的语句与逗号表达式算一条)。

for语句转while,初始化要写全

数组

数组的定义

类型名 数组名[行长度][列长度];

数组名是一个地址常量,因此不能对其++,--等运算!但是可以用*取值

一维数组: - 全部赋初值:可省略下标 - 部分赋初值,按顺序赋,剩下的赋值0(即使是局部变量也是)

二维数组: 定义时可省略第一维大小,第二不可

  • 按行初始化:每一行看成一维数组,注意不够的时候这一行后面补0
  • 按存放顺序(每一行行连续)
a[3][4]={{1,2,3},{4,5,6}}; //第一行是{1,2,3,0}

数组的访问

和定义不同,访问数组时不可省略任何一个下标:a[i][j]是合法的,a[][3]是不合法的

字符数组

字符数组的初始化

以下四种形式等价

char s[6] ={"Happy"};
char s[6] ="Happy";//{}可省略
char s[6] = {'H', 'a', 'p', 'p', 'y', '\0'};
static char s[6] = {'H', 'a', 'p', 'p', 'y'};//注意需要保证s[6]=0='\0'

注意数组大小须>=字符串长度+1,因为有\0

//a比b长度长
char a[]=ABCDEF;
char b[]={A, B,C,D,E,F};

\0的ASCII码值为0

注意初始化完之后,不能用a="abcd"对char数组赋值,因为a是常量

string.h(不要忘记include

  • strlen:返回到第一个\0为止的长度,不包括\0. 而sizeof返回整个数组大小(每个元素一个字节)

    例:a[80]="abc\0bcd\0",sizeof(a)=80,strlen(a)=3

  • strcmp(a,b) 字典序大于b(ASCII)返回正数,等于返回0,小于返回负数 注意如果用a>b,比较的是字符串起始地址的大小

    strcmp("sea","Sea")>0//ASCII小写>大写
    strcmp("Sea","Sea ")<0
    

  • strcat(a,b) 把b接到a后面(\0被放在最后面),返回a地址

  • strcpy(a,b) 把b复制到a中(a的内容被覆盖),返回a地址

  • strlwr转小写,strupr转大写

指针

变量的指针

注意定义时的int *p应该理解为*p的类型是int,而p才是指针

[F] 执行语句int *p = 1000;后,指针变量p指向地址为1000的变量。

只是指向地址为1000的内存空间,无意义

int *p, q, r; 定义pint*, q,rint

执行语句int *p,p只能指向int类型的变量

指针的运算与优先级(重要)

[T] 直接访问就是直接利用变量的地址直接进行访问。

没有两个指针之间的加法!!!(只有指针+整数,指针-指针,指针间的关系运算) 指针的运算符-,++,=,==. ==

指针+k,地址的值加上k×数据类型的大小(字节) short 2Byte; int 4Byte; char 1Byte; double 8Byte

p=&x

优先级++>*.如*p++等价于*(p++),(*p)++等价于(x++)

一维数组的指针

a[b]=*(a+b) 注意==数组名是常量,不能参与++,--运算==

int f(long a[]){return (int)sizeof(a);}
f(a)返回的不是数组a的大小,而是指针变量long*a 的大小

字符串与字符指针

字符串常量"hello"的值是一个地址,指向该字符串首字符

char sa[]="array";//数组名sa
char *sp="point";//字符指针sp指向"point"的起始地址
printf("%s","string"+1);//输出tring

字符数组与字符指针的区别

sa="hello";//非法! 数组名sa是常量,不能赋值

char *s;scanf("%s",s);//错误! s未赋值
char *s,str[20];s=str;scanf("%s",s);

函数

C语言中默认返回类型为int

形参和实参都是变量时,不可能占用同一内存空间(复制)

实参可以是变量,常量,表达式,形参只能是变量。形参和实参顺序相同,数量相同

形参建议与对应的实参类型一致(但也不一定,(可以强制类型转换),要看具体题目)

声明(注意结尾分号)

定义(没有分号)

若函数返回值为指针,可以返回0

变量的作用域与生存周期

变量初始化的含义,就是在定义变量时对变量赋值。(√)

作用域

局部与全局: 重名的时候局部有限

生存周期

main中的局部变量一开始就被分配,其他函数在被调用之前不分配。调用结束的时候回收。

static 和全局默认初始化为0 auto初始值为随机值

结构

初始化

struct stu{
    int x,y;
    char ch[10];
};
struct stu a={1,2,"12345"};//不要忘记写struct
//{}按顺序初始化,注意前面必须有struct 类型名
struct{
    int x,y;
}a,b,c;//类型名可省略,但是不能在其他地方使用

使用与优先级

结构成员运算符.(优先级最高),指向运算符->

优先级.,->>++,-->*

如 - ++p->str=++(p->str)==,先算-> - p++->x=(p++)->x - *p->y只有在y是指针变量的时候有意义,=*(p->y),先执行->

同类型的结构变量可以直接赋值

结构指针(*p).a=p->a 注意==第一种用法必须要加括号以及括号的位置,*p.a不合法==,因为会先算.

宏是一种字符串替换

当做算术运算使用的时候,注意是直接替换,没有括号

#define M(x,y) x*y
M(a+b,c);//=a+b*c

文件

文件的基础知识

[T]从文件的逻辑结构上看,c语言把文件看作数据流,并将数据按顺序以一维方式组织存储。

文件的分类

  • 文本文件(C源程序
  • 二进制文件(可执行文件、目标文件

缓冲文件系统

  • 对于每个文件分配缓冲区(是内存单元)。一般512B(与扇区大小相同)

文件指针

文件指针指向文件缓冲区中文件数据的存取位置。[×]

这是因为文件指针本身是一个结构指针,指向一个FILE类型的变量

在成功打开一个文件后,可以使用【文件指针】来获取文件缓冲区的FILE结构信息。

文件指针的定义:FILE* fp

文件的的打开与关闭

fopen

调用:fp=fopen(filename,mode) 参数:

返回值: - 打开成功,返回文件指针的地址 - 打开失败,返回值为NULL(0)

fclose

调用fclose(fp)

返回值:

  • 关闭成功返回NULL
  • 关闭失败返回非零值

注意fopen和fclose的返回值不一样。打开失败是NULL, 关闭成功是NULL

文件输入输出

fscanf和fprintf

fscanf(fp,格式化字符串,参数表)

#include<stdio.h>
int main(){
    FILE* fp1,fp2;//定义文件指针

    fp1=fopen("A.c","r");//打开文件指针
    fp2=fopen("B.c","w");
    if(fp1==NULL){//如果打开失败,文件指针返回NULL
        printf("Error");
        exit(0);
    }
    fscanf(fp1,"%s",&x);
    fprintf(fp2,"%d",y);
    if(fclose(fp1)){
        printf("Error");
    }
    fclose(fp2);
}

fgetc和fputc

调用: ch=fgetc(fp)getchar()一样,fputc(ch,fp) fgetc(stdin)表示从屏幕读入,fputc(stdout)

返回值: 如果读/写失败返回EOF

文件的打开方式只能是读或读写,不能是追加

fgets和fputs

调用:fgets(s,n,fp)

参数: s是字符指针。n是指定读入的字符个数。最多读取n-1个字符(最后一个自动补\0)

返回值: 如果成功,返回字符串的地址。如果失败,返回空指针。

调用: fputs(s,fp)

返回值:如果成功,返回写的最后一个字符。失败返回EOF

fread和fwrite

调用:fread(buffer,size,count,fp)

参数:

  • buffer:代表一个指针,指向要读入数据的存放地址
  • size: 代表要读入的字节数
  • count: 要读入的次数
  • fp: 文件指针

fread(a,4,5,fp)代表从fp中读入数据存到数组a中,每次读4个字节,读5次

返回值:返回成功读取的个数。

调用: fwrite(buffer,size,count,fp). buffer是要写的数据地址。同理。

其他文件函数

rewind

调用:rewind(fp). 让文件指针重新指向首地址。

fseek

改变fp的值,实现对文件的随机读写

调用:fseek(fp,offset,from)

参数:

  • fp 文件指针
  • offset 移动偏移量
  • from 只能取三种值,由三个常量定义
  • SEEK_SET (0) 文件开头
  • SEEK_CUR (1) 文件当前位置
  • SEEK_END (2) 文件末尾

fseek(fp,-2,2)表示把文件指针从文件尾前移2个字节

ftell

返回文件指针位置,相对起始位置的偏移量(字节)

feof

若到了文件末尾,返回1.否则返回0

优先级 运算符 名称或含义 使用形式 结合方向 说明
1 [] 数组下标 数组名[常量表达式] 左到右
() 圆括号 (表达式)
函数名(形参表)
. 成员选择(对象) 对象.成员名
-> 成员选择(指针) 对象指针->成员名
2 - 负号运算符 -表达式 右到左 单目运算符
(类型) 强制类型转换 (数据类型)表达式
++ 自增运算符 ++变量名
变量名++
单目运算符
-- 自减运算符 --变量名
变量名--
单目运算符
* 取值运算符 *指针变量 单目运算符
& 取地址运算符 &变量名 单目运算符
! 逻辑非运算符 !表达式 单目运算符
~ 按位取反运算符 ~表达式 单目运算符
sizeof 长度运算符 sizeof(表达式)
3 / 表达式 / 表达式 左到右 双目运算符
* 表达式*表达式 双目运算符
% 余数(取模) 整型表达式%整型表达式 双目运算符
4 + 表达式+表达式 左到右 双目运算符
- 表达式-表达式 双目运算符
5 << 左移 变量<<表达式 左到右 双目运算符
>> 右移 变量>>表达式 双目运算符
6 > 大于 表达式>表达式 左到右 双目运算符
>= 大于等于 表达式>=表达式 双目运算符
< 小于 表达式<表达式 双目运算符
<= 小于等于 表达式<=表达式 双目运算符
7 == 等于 表达式==表达式 左到右 双目运算符
!= 不等于 表达式!= 表达式 双目运算符
8 & 按位与 表达式&表达式 左到右 双目运算符
9 ^ 按位异或 表达式^表达式 左到右 双目运算符
10 | 按位或 表达式|表达式 左到右 双目运算符
11 && 逻辑与 表达式&&表达式 左到右 双目运算符
12 | 逻辑或 表达式|表达式 左到右 双目运算符
13 ?: 条件运算符 表达式1? 表达式2: 表达式3 右到左 三目运算符
14 = 赋值运算符 变量=表达式 右到左
/= 除后赋值 变量/=表达式
*= 乘后赋值 变量*=表达式
%= 取模后赋值 变量%=表达式
+= 加后赋值 变量+=表达式
-= 减后赋值 变量-=表达式
<<= 左移后赋值 变量<<=表达式
>>= 右移后赋值 变量>>=表达式
&= 按位与后赋值 变量&=表达式
^= 按位异或后赋值 变量^=表达式
|= 按位或后赋值 变量|=表达式
15 , 逗号运算符 表达式,表达式,… 左到右
优先级问题 表达式 经常误认为的结果 实际结果
. 的优先级高于 *(-> 操作符用于消除这个问题) *p.f p 所指对象的字段 f,等价于:
(*p).f
对 p 取 f 偏移,作为指针,然后进行解除引用操作,等价于:
*(p.f)
[] 高于 * int *ap[] ap 是个指向 int 数组的指针,等价于:
int (*ap)[]
ap 是个元素为 int 指针的数组,等价于:
int *(ap [])
函数 () 高于 * int *fp() fp 是个函数指针,所指函数返回 int,等价于:
int (*fp)()
fp 是个函数,返回 int,等价于:
int
( fp() )
== 和 != 高于位操作 (val & mask != 0) (val &mask) != 0 val & (mask != 0)
== 和 != 高于赋值符 c = getchar() != EOF (c = getchar()) != EOF c = (getchar() != EOF)
算术运算符高于位移 运算符 msb << 4 + lsb (msb << 4) + lsb msb << (4 + lsb)
逗号运算符在所有运 算符中优先级最低 i = 1, 2 i = (1,2) (i = 1), 2

ASCII

Comments