C易错点总结(语法篇)
杂项
标识符: 字母、数字、下划线(首字母不能是数字
输入输出
scanf的格式字符串
float用%f
输入,double用%lf
,否则会出错
printf的格式字符串
%
用%%
转义
%o,%x,%u
表示无符号的八进制,十六进制,十进制 (%#o
会加上0,0x标记)
%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
%.nf
n位小数.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=z
或x=(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
还有一些编程时的易错点
- 算术运算符>位运算
- 比较运算符>位运算,如
a&b!=0
会变成a&(b!=0)
- 比较运算符>赋值,如
c=getchar()!='\n'
会变成c=(getchar()!='\n')
而出错
选择结构
if
- else优先匹配最后一个未匹配的if,跟缩进无关
- 注意空语句
if();
- 注意没有大括号的一连串语句
if()a;b;
(只有a在内部)if()a,b;
(a,b都在内部)对于for,while的循环也同理,常在这里设坑。
switch
- switch中不能有两个重复的case标记
- case后面必须是常量表达式
- 无break和default可以通过编译
- 若有语句但无break,把接下来所有case的语句都执行,直到有break或者到末尾
- 若没有语句,和下面的case公用(如case2和case3) default同理
多个switch嵌套注意哪里有break
循环结构
同样注意无括号for(;;)a;b;
while中的循环体语句只能是一条语句(用
{}
括起来的语句与逗号表达式算一条)。
for语句转while,初始化要写全
数组
数组的定义
类型名 数组名[行长度][列长度];
数组名是一个地址常量,因此不能对其++,--等运算!但是可以用*取值
一维数组: - 全部赋初值:可省略下标 - 部分赋初值,按顺序赋,剩下的赋值0(即使是局部变量也是)
二维数组: 定义时可省略第一维大小,第二不可
- 按行初始化:每一行看成一维数组,注意不够的时候这一行后面补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
\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,比较的是字符串起始地址的大小
-
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;
定义p
为int*
, q,r
是int
执行语句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)
注意==数组名是常量,不能参与++,--运算==
字符串与字符指针
字符串常量"hello"
的值是一个地址,指向该字符串首字符
字符数组与字符指针的区别
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
不合法==,因为会先算.
宏
宏是一种字符串替换
当做算术运算使用的时候,注意是直接替换,没有括号
文件
文件的基础知识
[T]从文件的逻辑结构上看,c语言把文件看作数据流,并将数据按顺序以一维方式组织存储。
文件的分类
- 文本文件(C源程序
- 二进制文件(可执行文件、目标文件
缓冲文件系统
- 对于每个文件分配缓冲区(是内存单元)。一般512B(与扇区大小相同)
文件指针
文件指针指向文件缓冲区中文件数据的存取位置。[×]
这是因为文件指针本身是一个结构指针,指向一个FILE类型的变量
在成功打开一个文件后,可以使用【文件指针】来获取文件缓冲区的FILE结构信息。
文件指针的定义:FILE* fp
文件的的打开与关闭
fopen
调用:fp=fopen(filename,mode)
参数:
- filename 是表示文件路径的字符串,可用绝对路径或相对路径,记得用
\\
转义,如(“C:\\Documents\\abc.txt”
) - mode有很多种,详见(81条消息) 用C语言打开文件的几种方式及区别_Math X CS的博客-CSDN博客_c语言打开文件,或者CPP ref
- "r"表示只读,文件不存在时出错
- "w"表示只写,若文件存在会清空从头开始写,文件不存在会新建
- "a"表示追加,若文件存在会从末尾开始写,否则会新建
- 加上"b"表示二进制文件
- 加上"+"表示读写。但是读/写之前要进行rewind操作
返回值: - 打开成功,返回文件指针的地址 - 打开失败,返回值为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 |