注:个人针对于课本的易错点进行了相关的整理。整理的不专业,多多见谅。
C语言中的易出错的点
这个笔记综合了
0. 常量&变量
常量 | 整型常量 | -345,1000,0 |
实型常量 | 1) 十进制小数形式,-123.456 2) 指数形式-10.34E-12 | |
字符常量 | 普通字符 | 一个字符,单撇号’ a’’Z’’?’’#’ |
转义字符 | 表格0.1转移字符表 | |
字符串常量 | “123” “boy” | |
符号常量 | #define PI 3.1415926 #define PRINCE 40 //注意结尾没有分号,尽量大写 注意:符号常量占用内存,只是一个临时的符号而已。预编译后这个符号就不存在了,故不能对他赋新值。; | |
变量 | 变量在程序中使用时,必须预先说明它们的存储类型和数据类型。 <存储类型> <数据类型 > <变量名> ; <存储类型>可以是关键词auto、register、static和extern之一; register : 寄存器,是cpu内存的存储空间 static : 静态区 , C 程序把存储控件分成3部分, 堆区,栈区,静态区 extern : 表示这个变量是外部变量 auto : 是变量的默认形式 | |
常变量 | const float pi=3.1415926 ;// 定义常变量 #define PI 3.1415926 //定义符号变量 注意:常变量占用内存,只是值不能被改变。常变量具有符号常量的优点,使用方便 | |
标识符 | C的合法的符号: (各种名字) 1) 标识符由一个或多个字母、数字或下划线组成 : 组成:字母数字下划线 2)标识符的第一个字符必须是字母或下划线 : 第一个字符不能是数字 3)标识符不能与任何关键字相同 :不能和系统的关键字重名 |
\0 | 空字符null |
\’’ | ‘’ |
\? | ? |
\\ | \ |
\a | Alert |
\b | Backspace |
\f | Form feed 换下页的开头 |
\n | 换行 |
\r | 回车 |
\t | 水平制表 |
\v | 垂直指标 |
\oo \o \ooo o表示一个八进制数字 | 与该八进制对应的ASC码 |
\xh[h..] H表示一个十六进制数字 | 与该十六进制对应的ASC码 |
表格0.1转移字符表
1. 数据类型:
基本类型 | 整型的数据 |
字符型char
布尔型bool | ||||||||||||||||||||||||||||||||||
浮点类型 | A. 浮点数据 具有小数点的实数,3.1455 0.31455e12
| |||||||||||||||||||||||||||||||||||
枚举类型 |
|
怎么确定常量的类型?
1) 整型的常量。没有小数点,int,long int ,
2) 浮点型常量。C编译系统中把浮点型常量double按照双精度处理。
float a = 3.14159 f4字节,但是3.14159按照d8字节,编译会有警告。
可以在常量后面计入强制的转化类型。float a =3.1.159f;
Long double a = 1.23L;后者1.23l
2. 运算符的优先级: 记忆技巧:“出 单、算、 关、落 条 幅 。逗”
初等运算符()[] - > . 单目运算 ! -- ++ & * ~ sizeof 算术运算符 关系运算符 >,>=,<,<= (高) == !=(低) 逻辑运算符 & ^ | && || (高---低) 条件运算符 ? : 赋值运算符 = /= *= %= -= += <<=左 >>=右 &= ^= |= 逗号运算符 , 出单、算关、罗 条 幅 |
3. 数据赋值语句
1) a=(a=b)=3*4 错误 因为a是左值,(a=b)不是左值。
2) 赋值语句也可以出现在其他语句中(如输出语句,循环语句等)如printf(”%d”,a=b);
- 4. 赋值过程中的类型转换
f/d->int 舍去小数点后的数
int->f/d 数值不变23 23.0
d->f 超出的时候显示err
char->int ASCII码
int(多)->int(少 )只保留低位。
5. int a=3,b=3,c=3 不能是int a=b=c=3;
6. 常用的函数:
printf(“%d,%c\n”,I,c) | di xX o u c s Ee f gG %% \n\t\v\f | L #(x o efg ) - 0 空格+ m.n |
Scanf(“a=%f,b=%f,c=%f”,&a,&b,&c) | ----------- | ----------*
遇空格、TAB、或回车 遇宽度结束 遇非法输入 |
puts(char数组名) gets(char数组名) | ||
getchar(‘a’or‘\n’or ’\101’ or x ) putchar() | putchar(getchar()); printf(“%c”,getchar()); |
swith(表达式) //表达式的值只能是整数 ,或则字符 { case 常量1 : 语句; case 常量2 : 语句; case 常量3 : 语句; …….. case 常量n : 语句; default : 语句; } |
break : | 在循环体中:跳出循环体, 结束所在循环,接着进行循环体后面的语句。 在switch中, 结束switch选择,执行switch之外的语句。 注意:只能用在循环语句中或者switch语句中,不能单独使用 |
continue | 结束本次循环,继续下次循环。 即掉过循环体中下面上尚未循环的语句,转到循环体结束之前,接着执行for语句中的表达式三,然后进行下一次是否执行循环的判断。后面的代码就不再执行了。 |
return | 在函数体内, 函数返回。 如果在main函数内, main函数返回, 程序结束.\ |
7. 各种排序法比较
8. 二维数组
a[2-1][2*8-1] 不能a[2-1,2*8-1
9. 字符串数组
结束表字符‘\0’空符号,在ASC码为0,不显示,空操作符,表示什么也不操作,仅供辨别,不会产生其他作用。
char c[]=”I am happy”11个字符=10个显示字符+1个‘\0’
等价于char c[]={‘i’,’ ‘,’a’,’m’, ‘h’,’p’,’y’,’y’,’\0’}11个
不等价于char c[]={‘i’,’ ‘,’a’,’m’, ‘h’,’p’,’y’,’y’ }10个
10. 字符串数组函数
puts(str)只能是一个数组gets(str)仅仅是一个数组 |
strcat(str1,str2) 格式:strcat(字符数组1,字符数组2) 功能:把字符数组2连到字符数组1后面 返值:返回字符数组1的首地址 说明:?字符数组1必须足够大 ?连接前,两串均以‘\0’结束;连接后,串1的‘\0’取消, 新串最后加‘\0’ char *strncat(char *dest, const char *src, size_t n); |
strcpy(str1,str2) 把二给一 strncpy(str1,str2)把2 的前N个给1的前n个 字符串拷贝函数strcpy 格式:strcpy(字符数组1,字符串2) 功能:将字符串2,拷贝到字符数组1中去 返值:返回字符数组1的首地址 说明:? 字符数组1必须足够大 ? 拷贝时‘\0’一同拷贝 ? 不能使用赋值语句为一个字符数组赋值 char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n); strcmp(str1,str2)字符串比较函数, 格式:strcmp(字符串1,字符串2) if(strcmp(str1,str2)>0) printf(“yes”); 功能:比较两个字符串 比较规则:对两串从左向右逐个字符比较(ASCII码), 直到遇到不同字符或‘\0’为止 返值:返回int型整数, a. 若字符串1< 字符串2, 返回负整数 b. 若字符串1> 字符串2, 返回正整数 c. 若字符串1== 字符串2, 返回零 说明:字符串比较不能用“==”,必须用strcmp strlen计算实际长度 不包括‘\0’ 格式:strlen(字符数组) 功能:计算字符串长度 返值:返回字符串实际长度,不包括‘\0’在内 0 =’\0’ ASC码对应的 0 ‘0’字符零 就是asc码的48 strlwr(字符串) 转换为小写 strupy(字符串)转换为大写 |
11. 函数 :定义 声明 调用(嵌套or递归)
定义:有参变量=声明部分+语句部分
类型名 函数名(参数列表)
{
}
函数类型决定返回值类型
12. 数组作为函数参数
数组元素作函数实参,不能做形参。 数据传递方式: 值传递。
数组名做实参or形参(数组名or指针)
地址传递方式 :改变形参的值,可以对实参进行改变 :形参可以改变实参的值
多维数组名作为函数参数
13. 局部变量和全局变量
变量分为局部变量和全局变量(外部变量)建议在非必要时间不要定义全局变量。
| 类型 | 注意点 | ||
局部变量 | auto变量 | 1) auto变量将来存放动态存储类别不在静态区,调用后就立即释放,到栈。 2) 每次都重新赋值,每次都执行赋初值语句。 3) 如果不对这个变量初始化,这个变量初始值为随机数。 | 静态存储区,动态存储区,cpu寄存器 | |
static局部变量 | 1) static局部变量,在静态存储区,结束后不消失,继续保留其原值,即占用的存储单元不释放, 2) (引用切不改变值)static只赋初值一次,以后每次调用都比在重新赋值,只是保留上次函数调用时结束的值。该变量已有值(就是上一次函数调用结束的值)。 3) 如果不对这个变量初始化,这个变量初始值为0或者空字符‘\0’. 4) 静态变量在函数调用后结束后仍然存在,虽然其值存在,但是只能是本函调用,其他函数调用不了。 | |||
寄存器register变量 | 1) Cpu Register:当一个变量使用频繁,需要很多次循环时(比如运算的时候),每次存储浪费时间,所以用register int f; | |||
外部extern变量 | ||||
全局变量 | 全局变量是定义在函数体外的变量, 这个变量将来存放到静态区,如果不对这个变量初始化,这个变量的初始值为0; 1) 在一个文件内扩展外部变量的作用域, int main(){ extern int A,B,C;//把外部函数变量作用域从此开始。本来main中不能引用外部变量abc的,现在main中的extern对ABC进行了外部变量声明,把ABC作用域扩展到该位置,这样在main中就能合法的使用全局变量ABC 了。 提倡是吧外部函数的定义放在引用他的所有函数之前没这样可以尽量避免在函数中多加一个extern声明。Extern声明外部变量时,类型名可以写,也可以不用写,例如extern A,B,C;因为他不是定义变量步指定类型,只是写出外部变量名即可。 2) 将外部变量的作用域扩展到其他文件。 一个文件中的变量想引用另一个文件中的变量;在一个文件中定义外部变量NUM,而在另一个文件中用extern对NUM作为“外部变量声明”。既extern NUM 例如file1.c IntA ; Int main() File 2.c Extern A; Int power(intA) 这种方法扩展全局变量的作用域应该十分慎重,因为在执行一个文件中操作中,可能会改变该全局变量的值,会影响到另一个文件中全局变量的值,从而影响到另一个文件中的值。 Extern找到就找到,找不到就报错。 3) 将外部变量的作用域限制在本文件中。 只把限制在本文件而不能被其他文件引用 static声明(在局部变量前是把他分配到静态区) File 1.c file 2.c Static int A; extern A; Int main() {} void fun (int n){ A=A*N;//出错} | 静态存储区 | ||
注意点 | 如果函数体内的局部变量和全局变量重名 | |||
13. 变量的声明和定义
声明部分和执行语句
声明部分的作用是对有关的表示符号(变量,函数,结构体,共用体等)的属性进行声明。
对于函数而言,声明和定义的区别是明显的,函数的声明是函数的原型,而函数的定义是对函数功能的定义。对被 调用的函数的声明是放在主调函数的声明部分中的,而函数的定义明显不在声明部分的范围,他是一个独立的模块。
声明部分出现的变量有两种情况,一种是建立存储空间的如int a 定义性声明--------定义
一种是不需要建立存储空间的 extern a 引用性声明----声明
注意:有一个简单的结论,
在函数中出现的对变量的声明(除了用extern声明的以外)都是定义。
在函数中对其他函数的声明不是函数定义。
int main()
{
extern A;//这个是声明,不是定义,是指定了A 的作用域从这里开始。
…
}
int A;//这个是定义
14. 内部函数和外部函数
函数能否被其他源文件调用,分为内部函数和局部函数。
内部函数:Static 类型名 函数名 (形参表);内部函数又称为静态函数。
通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。这就提高了程序的可靠性。
外部函数:extern int
fun (int a ,int b);在main()函数中的声明
extern int fun (int a ,int b)
15. 指针
定义->引用->做函数参数viod swap(int *p1,int *p2);
数组元素的指针 | int Int // p=&a[0]等价于p=a; | |||||||||||||||||||||||||||||||
引用数组元素时指针的运算 | 1) P+1===++p==&a[1] p++====&a[0],p=12) P=&a[0];p+i和a+i就是数组元素a[i]的地址 3) *(p+i)*(a+1)是p+I 或者a+i所指向的数组元素
如果指针变量p1和p2都指向同一个数组,如执行p2-p1,结果p2-p1的值(两个地址之差)除以数组元素的长度 ,结果也就是两个指针之间的相差的元素的个数。 假设p2指向实型数组元素a[5],p2值是2020,p1指向a[3],值是2012,则p2-p1的结果是(2020-2012)/4=2 | |||||||||||||||||||||||||||||||
通过指针引用数组元素 | a[i]===*(a+i)===*(p+i)
for(i=0;i<10;i++); printf(“%d”,*(a+i))等价于printf(”%d”,a[i]); 3. 用指针变量指向数组元素。 就是地址的偏移 for(p=a;p<(a+10);P++) printf(“%d”,*p); 第三种的速度快 注意: 1) 通过改变指针变量的值来指向不同的元素。 2) 如果不用p++而是用数组名a变化的方法是不可以的!因为a是一个指针常量,是&a[0],比如 for(p=a;a<(p+10);a++) Printf(“%d”,*a)是不行的。 3) 注意指变量的当前值。 练习8.6 有一个整形数组a,10个元素,要求输出数组的全部元素。
思路输入&输出
注意的是p++ *p 和++p *p的区别: | |||||||||||||||||||||||||||||||
用数组名作函数参数 | 1) 当数组名作为参数的时候,如果形参数组中的个元素的值发生改变,实参数组元素的值随之改变。 原因是: 数组元素做参数 :值传递:Int swap(int x,int y)。 把值给了xy但是a[1]a[2]不变。 Swap(a[1],a[2]) 这里是做实参; 数组名做参数:地址传递:f(int x[],int n)===f(int*p,int n)改变实际参数中具体的值。 Int main() {void Int arry[10]; .. Fun(aarry,10); Return } | |||||||||||||||||||||||||||||||
数组名做参数 | fun(int
实参数组名 代表一个固定的地址或者指针常量 形参数组名并不是一个固定的地址,而是按照指针变量处理。 | |||||||||||||||||||||||||||||||
形参和实参都用数组名 F(a,10)... Int f(int x[],int n ) 实参用数组名,形参用指针变量For(a,10) Void f(int *x,int n) 实参形参都用指针变量 F(p,10) Void f(int *x ,int) 实参为指针变量,形参为数组名。F(p,10); 注意: 如果在main函数中不设数组,只设指针变量,编译会出错,原因是指针变量arr没有确定值,谈不上指向那个变量,如果要使用指针变量的话,指针变量需要有确定值。 | ||||||||||||||||||||||||||||||||
通过指针引用多维数组
| 多维数组元素的地址 | 1) int a[3][4]; 2) 二维数组首元素的地址:对于二维数组而言,a代表的是二维数组首元素的地址,即是由四个整形元素组成的 一维数组。因此a代表的首行。 a[0] a+1是&a[1] 3) a第0行的第1列元素的地址*(a+0)+1
| ||||||||||||||||||||||||||||||
指向多维数组的指针变量 | 1) 指向数组元素的指针 例子8.12 3*4的二维数组 for(p=a[0];p<a[0]+12;p++) {if ((p-a[0])%4==0)printf(“\n”); Printf(“*4d”,*p); } Printf(“\n”); 2) 指向由m个元素组成的一维数组的指针变量。
| |||||||||||||||||||||||||||||||
16. const型指针
const类型修饰符可以将指针变量常量化,主要有
下面三种形式。
(1).第一种:
const <数据类型>*<指针变量名称>[= <指针运算表达式>] ;
常量化指针目标是限制通过指针改变其目标的数值。
例如: int a = 100;
const int *p = &a
主要目的是: 不允许通过指针改变a 的值
(2).第二种:
<数据类型> *const <指针变量名称>= <指针运算表达式>;
例如: int a = 100;
int * const p = &a
主要目的: 只要修饰指针,
指针的内容不能修改,变量的内容可以改变
(3).第三种:
const
<数据类型> * const
<指针变量名> = <指针运算表达式> ;
例如:
例如: int a = 100;
const int * const p = &a
主要目的: 既要修饰指针,
指针的内容不能修改,也要修饰变量,变量的内容也不可以改变
17.void类型的指针、空指针、野指针
void型的指针变量是一种不确定数据类型的指针变量,它可以通过强制类型转换让该变量指向任何数据类型的变量或数组。
一般形式为:
void
*<指针变量名称> ;
例如: void * p = &a
野指针:定义指针打不给他付初值
空指针 #define
null 0;
空指针与野指针
在LInux的C中, NULL被定义为空指针 , NULL的值就是0
#define NULL ((void *)0)
NULL与0之间的关系, NULL是void * 类型的数据
0 只是一个数,
野指针: 定义一个局部指针变量不对其赋值, 这个指针的值是一个随机数,这种指针就是野指针
定义一个全局指针变量不对其赋值, 这个指针的值是0
18. 指针引用字符串
int *p=”I love you” int a[14]=“I love you”
1) 之后指针指向了数组地址才可以被改变值。要指针指向了具体的字符串的时候,作为常量不能被改变了。
2) 字符指针指示指向了一个字符的地址,而不是具体的内容。当局部变量释放了后,输出的指针的内容只是个随机的地址
3) 指针p可以++,但是数组名a是常量&a[0]不能进行++运算
4)
在输出的时候只能是字符数组的printf(“%s”,string)可以自动输出全部,但是int型不可以。
实参类型 | 变量名 | 数组名 |
要求的形参类型 | 变量名 | 数组名或指针变量 |
传递的信息 | 变量的值 | 实参数组的首元素地址 |
通过函数调用能否改变实参的值 | 不能 | 能 |
19. 函数指针和指针函数
函数指针:char (*mystrcpy)(char *dst,char *src)
指针函数:是指一个函数的返回值为地址量的函数 char * mystrcpy(char
*dst,char *src)
int (*p)(int,int);
p =max;
c=*(a,b);
c=(*p)(a,b)
对指向函数的指针变量不能进行算术均算,如p+n,p++
20. 指针数组和多重指针
指针 数组: char *buf[4];
数组 指针: char (* buf)[4];
指向一位数组的一个指针
多重指针:存储类型 数据类型 ***
** 指针名字
21.动态分配函数
(void * )malloc (unsigned int size) 不成功返回NULL
(void * )calloc (unsigned n ,unsigned int size);比较大,n*size字节
void free (void * p);
(void * )realloc (void *p ,unsigned int size);不改变p值,知识改变size。
22.指针的小结
p=student,&a,&arry[1],array,p2,NULL(0)(不是没有赋值,是赋0,不指向任何)。
指针可以比较大小,相减,但是不能加.
23.结构体
有不同类型数据组成的组合型数据结构那叫做结构体
1) 字对齐: 2字节+2字节--->4 4字节+2字节 --->8字节
2) sizeof(str) == strlen(str) + 1
3) 变量:定义声明 ->初始化->引用
struct student
{
int num;Char
name[20];Int age ;Float score;Char addr[30];
} Struct student student1,student2 ;
说明:
- 结构体类型和变量不同,只能对变量进行赋值等运算。
- 编译的时候,对类型不分配空间,只对变量分配空间。
- 结构体类型的成员名可以与程序中的变量名相同,但是两者表示不同的对象。
- 对结构体的变量中的成员(即‘域’),可单独使用,他的作用和地位相当于普通的变量
4) a={10101,”lilin”,’m’,”123beijingroad”}
Printf(“NO.:%d\nname:%S\n\naddr:%S\n”,a.num,a.name,a.sex,a.addr);
也可以只是对于某一成员进行初始化,其他未初始化数字型默认为0,字符型默认为‘\0’,指针型的是NULL。
如:struct student b ={.name = “zhang fang”,.sex=’m’};
只能对最低级的成员进行赋值或者存取以及运算。
Student1.num 错误Student1.birthday.month正确
5)
结构体数组
{成员列表} 数组名[ 数组长度]
struct Person leader[3];
6)
结构体指针
1) 指向结构体变量的指针。
跟我们普通的定义和理解是一样的
struct student stu_1;//struct student类型的变量stu——1
struct student *p;//指向struct student类型数据的指针变量p
p=&stu_1;//p指向stu_1;
stu.成员名字 如stu.num
(*p).成员名 如(*p)student
p->成员名 如p->num;
2)
指向结构体数组的指针
例子: 有三个学生信息,放在结构体数组中,要求输出全部学生的信息。
#include<>
struct student *p;
for(P=stu;p<stu+3;p++)
{}
注意:
1. P的初值是stu,p指向stu的第一个元素,p加1后,p就指向下一个元素。
(++p)->num p+1 , 然后得到p,指向的元素中的num成员(即10102)
(p++)->num
p->num 的值,然后得到p+1,指向stu[1]
2. p是指向stustudent类型的指针,不是指向数组元素的某一个成员,如p=stu[1].name错误
如果是指向某一个成员的地址给p,需要强制转换。p=(struct student *)stu[0].name
24. 共同体
1) union 共用体名字
{
成员表列;
} 变量表列;
Union { int i;Char ch;Float f; }a,b,c; | Union { int i;Char ch;Float f; }; Union Data a,b,c; |
2)
共同的区间相同的地址----覆盖---起作用的事最后一次赋值的
3) 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
例如: a=1; m=a;都是不对的。但是允许共用体变量之间相互赋值。
4)
其他的用法跟结构体一样
25.枚举类型
定义 |
声明:enum【枚举名】{枚举元素列表} enum Weekday {sun,mon,tue,thu,fri,sat}; 定义变量:enum Weekday workday,weekend; 但是枚举变量workday,weekend只能是枚举元素中的一个值。
enum{sun,mon,tue,wed,thu,fri,sat} workday,weekend; 注意 1) 枚举的元素是常量,不能做变量,不能复制。 2) 每一个枚举元素都代表一个整数。默认为sun,mon,tue,wed,thu,fri,sat 0 ,1 ,2,3,4,5,6 如果赋值语句workday=mon; 相当于woekday=1; printf(“%d”,workday); 也可以认为的指定枚举元素的数值,在定义枚举类型时显式的指定。 enum weekday {sun=7,mon=1,tur,wed,thu,fri,sat}weekday,week_end; sun是7,mon是1,之后的都加1. 由于枚举变量的值是整数,所以把枚举数据作为整型数据的一种,即用户自行定义的整数类型。 3) 枚举元素可以用来判断比较 If(workday==mon)… If (workday>sun)… 枚举比较是按指定的整数(或者默认规则来处理的)数据来比较的。 |
例子 | 口袋有三个红黄蓝白黑5中颜色的球若干个。每次从口袋中先后取出三个球,问得到3种不同颜色的球的可能取法,输出每种排列的情况。 |
26.typedef
old new;
typedef int
haha;
typedef
student{结构体} haha;
typedef
int NUM[100];//声明NUM为整形数组类型名
typedef
char * string; //声明string为字符指针类型
string p,a[10]; //p字符指针,a[10]char指针数组
typedef int(*pointer)();//pointer指向函数的指针类型,返回值为整数值。
pointer p1,p2;//pw为pointer型指针
27.文件
文件 | 1) 2) 3) 4) 文件指针变量不是指向外部介质上的数据文件的开头,而是指向内存中文件信息区的开头。 | |||||||||||||||
打开、关闭文件 | fopen()
| |||||||||||||||
顺序读写数据文件 | 字符 |
| ||||||||||||||
字符串 |
| |||||||||||||||
用格式化的方式读写文件 |
| |||||||||||||||
二进制行形式向文件读写一组数据 |
| |||||||||||||||
随机读写数据文件 | 文件位置标示及其定位 文件位置标记:
| |||||||||||||||
文件读写的出错检测 |
| |||||||||||||||
练习 | 从键盘中读入若干个字符串,对他们按字符大小排序,然后把排好顺序的字符送到磁盘文件中保存。 | |||||||||||||||
练习2 | 键盘中输入10个学生的有关数据,然后把他们转存到磁盘文件上去。 |
28.printf(“%s”,p);