C语言
2021-06-29 17:34:22 9 举报
AI智能生成
谭志强C语言第三版
作者其他创作
大纲/内容
工控的未来趋向于小型化电路板,微型芯片驱动
1C语言概述
1C语言出现历史背景
早期
汇编语言
依赖计算机硬件,可读性和移至性差
C语言发展
B语言 离硬件远
C89 ANSI公布的C语言标准
C99 ISO1999年公布的标准
2C语言的特点
1 语言简洁紧凑
2 运算符丰富
3数据类型丰富
4 具有结构化的控制语句
5语法限制不太严格
6C语言允许直接访问物理地址
7 生成代码质量高,程序执行效率好
8 用C语言编写程序可移植性好
1.3 简单的C语言程序介绍
#include<stdio.h>
standard input &standard output
void main()
main 主函数 VOID是空类型。函数不产生返回值。每个C语言都必须有一个main。
{ printf("this is a c program.\n'');}
主函数的输出语句
/**/
表述注释部分,不运行
printf("sum is %d\n,sum);
%d 输入输出格式字符串
“sum is ”按原样输出,%d的位置代替一个十进制数值,
逗号,后面是要输出的变量
\n 用于换行
scanf("%d,%d",&a,&b)
scanf 输入函数
&是取地址,表示变量a,b的地址
scanf printf 内容都用“内容,变量或地址
C程序的组成和形式
C程序是由函数构成,仅有一个main函数,函数相当于其他语言的子程序
ANSI C 有一百多个库函数, turbo c 有三百多个库函数
一个函数由两部分组成
函数的首部
int max (int x,int y)
int 函数类型
max 函数名
int x
函数参数类型 ,函数参数名
int y
一个函数名后面必须跟一堆圆括号
函数可以没有参数 main()
函数体
声明部分
在这部分中定义所用到的变量和所调用函数的声明
int a,b,c
执行部分
若干语句组成
C程序总是从main 函数开始执行,无论main函数在整个程序中的位置
C程序书写格式自由,运行一行写几个语句
每个语句和数据声明的最后必须有一个分号。
C语言本身没有输入输出语句,输入输出由库函数scanf和printf完成。
1.4 运行C程序的步骤与方法
运行C的步骤
编辑源程序得到f.c
对源程序f.c进行编译
编译后得到f.obj
将目标程序f.obj与系统提供的库函数连接,得到f.exe
上机运行C程序的方法
打开VS,选择新建项目
在新建项目界面内,选择VC++ ,因为C++兼容C语言,项目名称随意修改,不用添加后缀
在应用程序设置中,附加选项选择空项目,不选的话会生成一些默认代码,对初学者无用
右键选择源文件,选择添加,在添加菜单中选择新建项
添加新建项后命名,C代码要修改后缀,将cpp改为c。向程序说明这是一个C语言代码
写好代码后,不要直接按F5,程序框会一闪而过,可以按Ctrl+F5调试
错误列表可以在视图中添加
编辑默认Debug ,如果开发完成就要选择Release,这样被人在没有VS的环境下也可以使用
2 算法
结构化程序设计方法
自顶向下
类似于写作文前先写提纲
逐步细化
将问题由抽象逐步具体化
只有上层设计正确才能向下细化
模块化设计
注意模块的独立性,完成一项功能,把一个大任务分为若干个子任务
结构化编码
数据类型,运算符,表达式
C语言的数据类型
基本类型
整型
短整型short int
基本整型int
长整型long int
字符型char
浮点型
单精度float
双精度型double
长双精度型 long double
枚举类型enum
构造类型
数组类型
结构体类型struct
共用体类型union
指针类型*
空类型void
常量与变量
常量与符号常量
在程序运行过程中不能被改变的量
0,-3整型常量,4.6,-1.23为浮点型常量,‘a’字符常量
#define PRICE 30
#define 定义PRICE代表常量30
PRICE 是符号常量,与变量不同,不能被赋值
习惯上,符号常量用大写,变量用小写
好处1:含义清楚,可以见名知意
好处2:需要改变常量时,能够一改全改
变量
在内存中具有特定属性的一个存储单元,可以放数据。值可以改变
一个变量应该有一个名字,以名字代表变量地址
在程序编译时,系统给每个变量分配对应的内存地址,从变量读取值,实际上是通过变量名找到相应的内存地址
标识符
对变量,符号常量,函数,数组等数据命名的字符
只能由字母,数字,下划线组成
编译系统将大写字母和小写字母认为是不同的字符
每个变量指定一个确定的类型
便于编译时检测运算是否合法
整型数据
整型常量表示方法
十进制
123
八进制
以0开头
0123
十六进制
0x开头
0x123
整型变量
整型数据在内存中的存放形式
数据在内存中以二进制存放
正数以源码存放
i=10
001010
负数以补码存放
i=-10
对-10取绝对值
10
绝对值的源码
001010
取反
110101
加1
110110
最左端为0表示数值正,最左端为1表示数值负
整型变量的分类
int
范围为 -2^15到2^15-1
-32768到32767
将变量变为无符号 unsigned
不指定默认signed
不能存放负数
存放的正数范围扩大一倍
0-65535
int
16bit
long int
32bit
整型变量的定义
变量的定义一般放在一个函数的开头的声明部分
整型数据的溢出
int 最大值32767 +1 得到-32768
因为最高位由0变成1
整型常量的类型
一个整数在-32768到32767 分配位int型
一个整数在-2147483648到2147483647 分配为long型
一个整数后面加字母u或U分配为无符号类型
一个整数后面加字母1或L,认为是long int
浮点型数据
浮点型常量的表示方法
十进制小数形式
0.123
指数形式
123.456e0=123.456
e之前必须有数字
e之后指数必须为整数
一个浮点数可以有多个指数表达形式1.23456e2
规范化指数形式
在字母e之前的小数部分,小数点左边有1位非零数字
2.34e2
浮点型变量
浮点型数据在内存中的存放形式
一般占用4个内存字节32bit
浮点型数据按照指数形式存储
数字符号+小数部分+指数部分
浮点型变量的分类
单精度float
32位,有效数字6-7位
float x,y;
双精度double
64位,有效数字15-16位
double z;
长双精度long double
128位 ,有效数字18-19位
浮点型数据的舍入误差
浮点型变量存储单位有限,能提供的有效数字也是有限的。在有效位以外的数字将被舍去
浮点型常量的类型
系统将浮点型常量作为双精度来处理
一个浮点型常量可以赋值给一个float ,double,long double型变量
字符型数据
字符常量
用单撇号括起来的一个字符
特殊的字符常量-控制字符-转义字符,将\后的字符转换为另外意义
在屏幕中不能显示,以\开头
\n 换行
\t水平制表
一个制表区占8列
第一个制表区1-8,调到9
\b退格
\r回车
不换行,跳回到本行最左端
\f换页,将当前位置移到下页开头
\\代表一个反斜杠
\'代表一个单引号
\''代表一个双引号
\ddd1-3位八进制所代表的字符
表示一个ASCII八进制字符,如\101代表字符A
\xhh1-2位十六进制代表的字符
字符变量
用来存放字符常量,只能放一个字符,不能放字符串
char c1.c2
c1='a';c2='b';
在系统中占用一个字节(byte)
字符数据在内存中的存储形式及其使用方法
并不是把字符本身放入内存,而是将字符对应的ASCII码放入存储单元
ASCII码对应为整数,以整数的形式存储在内存中二进制
字符串常量
用一对双括号括起来的字符序列
'a'是字符常量,“a”字符串常量
char c;
c=‘a’正确
c="a" 错误,不能把字符串常量赋值给字符变量
区别:字符串常量的结尾会加一个字符串结束标志'\0'
字符串常量“CHINA”CHINA\0是6个字符。
变量赋初值
可以在定义变量时赋初值 int a=3;
也可以对定义变量的一部分赋值
int a,b,c=5;
对多个定义变量赋值
int a=3,b=3,c=3;
不可写成 a=b=c=3;
各类数值型数据间的混合运算
不同类型数据先转换为同一类型,然后再进行运算
float必定转为double,char,short必定转为int
低到高,int ,unsigned,long,double
两个类型混合运算,低的转为高的,必定的按必定转换
算术运算符和算术表达式
C语言运算符简介
算术运算符
+-*/%
关系运算符
><,==,>=,<=,!=
逻辑运算符
!,&&,||
位运算符
<<,>>,~,|,^,&
赋值运算符
=
条件运算符
?:
逗号运算符
,
指针运算符
*,&
求字节数运算符
sizeof
强制类型转换运算符
(类型)
分量运算符
.,->
下表运算符
[]
算术运算符合算术表达式
基本算术运算符
+-*/%
两个整数相除,结果为整数,舍去小数部分
算术表达式和运算符的优先级与结合性
先乘除后加减
a-b*c.先做乘后做减
如果运算符两侧的数据类型不同,先自动进行类型转换
强制类型转换运算符
可以利用强制类型转换运算符将一个表达式转换为所需的类型
(double)a
(类型名)(表达式)
强制类型转换得到的是所需类型的中间数据,原来变量的类型未发生变化
两种类型转换
系统自动进行
3+5.2
强制类型转换
自动类型转换不能实现目的
float a , a%3 不合法
可以改为(int) a%3
在函数调用时,为了使实参和形参类型一致,可以用强制类型转换
自增,自减运算符
++i 和i++
是变量的值加1,。
++i是先执行i=i+1.再使用i的值
i++是先使用i 的值,再执行i=i+1
--i和i--
自增自减运算符只能用于变量,不能用于常量或表达式
5++,(a+b)++ 都不合法
++ 和--结合方向是自右向左
-i++相当于(-i)++,不合法
常用于循环语句,使变量自动加1
也可用于指针变量,使指针指向下一个地址
有关表达式使用中的问题说明
C语言中没有规定表达式中的子表达式的求值顺序,允许编译器自动安排,因此为避免歧义,可以增加表达式
C语言中对两个字符组成的运算符,如i+++j,竟可能的自左向右将若干字符组成一个运算符。为避免歧义,应写成(i++)+j
printf("%d,%d",i,i++)
有的系统从左到右求值输出3,3
有的系统从右到做,输出4,3
最好写成,j=i++ ;printf("%d,%d",j,i);
赋值运算符和赋值表达式
赋值运算符
a=3;
类型转换
如果赋值运算符两侧的类型不一致,但都是数值型或字符型时,在赋值是要进行类型转换
将浮点型赋值给整型变量,舍弃小数部分
int i; i=3.56; 结果是i=3
将整型数据赋值给单双精度变量,数值不变,但以浮点数形式保存到变量中
float a;a=23;结果a=23.0000
将double数据赋值给float,截取7位有效数字,存放到float变量,如果数字范围超出float范围,会报错
float a; double d=123.456789e100; f=d;出错
将float 赋值给double,数值不变,有效位数拓展到16位,内存中以8个字节存储
字符型数据赋值给整型变量,由于字符只占1个字节,而整型变量占2个字节,因此将字符数据(8个二进制位)放到整型变量的低8位
如果所用系统将字符处理位无符号字符类型,则将字符的8位放到整型变量的低8位,高8位补0
如果将字符处理为带符号的,若字符最高位位0,则整型变量高8位补0,若字符最高位位1,则高8位全补1
将一个int,short,long数据赋值给char变量,只讲低8位原封不动地送到char变量
将带符号的整型数据int赋给long变量,将整型数据16位送到long的低16位
如果int是正,则long型变量的高16位补0
如果int是负,则long的高16位补1
将long型数据赋值给int型变量,只将long的低16位原封不动送到整型变量中
复合的赋值运算符
在=之前加上其他运算符构成
a+=3 等价于 a=a+3
x*=y+8等价于x=x*(y+8)
凡是二元运算符,都可以于赋值符一起组成复合赋值符
+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=
简化程序,提高编译效果
赋值表达式
将一个变量和一个表达式连接起来的式子 a=3;
求解过程
先求赋值运算符右侧的表达式的值
然后赋给赋值运算符左侧的变量。
赋值表达式左侧标识符称为左值
变量可以为左值,表达式,字符常量不能作为左值
表达式右侧标识符称为右值
凡是左值都可以作为右值
赋值表达式的表达式可以是一个赋值表达式,a=(b=5);按照自右向左的结合顺序
逗号运算符合逗号表达式
逗号运算符,用一个逗号将两个表达式连接起来
3+5,6+8
求解过程:先求表达式1,再求解表达式2.整个逗号表达式的值是表达式2的值
作用:想分别得到各个表达式的值,而并非一定需要得到和使用整个逗号表达式的值,常用于for循环语句中
并不是任何地方出现的逗号都是逗号运算符
printf(“%d,%d,%d”,a,b,c);
逗号只是参数的间隔
顺序程序设计
c语句概述
C语句用来完成一定操作任务,声明部分的内容不应称为语句。
C程序,由若干个源程序文件组成,一个源文件可以由若干个函数和预处理命令以及全局变量声明组成
C语句的分类
控制语句
if()else
for()
while()
do while()
continue
break
switch
goto
return
()括号表示判别条件。
函数调用语句
由一个函数调用加一个分号构成
printf(“this is c .”);
表达式语句
一个表达式加一个分号构成,分号是语句中不可缺少的组成部分
a=3;
空语句
什么都不做
复合语句
用{}括起来的语句
赋值语句
if条件可以包含赋值表达式但不能包含赋值语句
if((a=b;)>0)t=a;
数据输入输出的概念及在C语言中的实现
输出输出是从计算机外部输出设备或输入设备
C语言本身 不提供输入输出语句,输入输出由C函数库来实现
在使用系统库函数,要用预编译命令#include将有关头文件包括到用户源文件。
字符数据的输入输出
putchar函数
向终端输出一个字符
putchar(c)
c可以是变量或整形变量
getchar函数
从终端输入一个字符
getchar()
getchar 只能接受一个字符
得到的字符可以复制给字符变量或整型变量
格式输入与输出
printf(格式控制,输出表列)
格式控制
用双撇号括起来的字符串
格式说明,由%和格式字符组成
%d,%f
普通字符
需要原样输出的字符
输出列表
需要输出的数据,可以是表达式
格式控制和输出列表都是printf函数的参数
格式字符
d格式,用来输出十进制整数
%d按十进制整数实际长度输出
%md为指定的输出字段的宽度
%ld,输出长整型数据
o格式符,以八进制输出
由于内存单元中各位按八进制输出,输出不带符号,将符号位也当做数据
x格式符,以十六进制形式输出
同样不会出现负的十六进制数
u格式
用来输出unsigned数据,以十进制形式输出
s格式符用来输出一个字符串
printf("%s","china");输出China
%ms,输出字符串占m列,如果字符串长度大于m,将字符串全部输出,小于m左补空格
%-ms,如果串长小于m,则在m列范围内,字符串向左靠,右补空格
%m.ns 输出占m列,但只取字符串中左端n个字符,这n个字符输出在m列的右侧,左补空格
%-m.ns m,n的含义同上,n个字符输出在m列范围左侧,右补空格。如果n>m,则自动取n值
f格式符用来输出实数,以小数形式输出
%f不指定字段宽度,由系统自动指定,使整数部分全部输出,并输出6位小数
%m.nf指定输出数据共占m列,其中有n位小数,如果数值长度小于m则左端补空格
%-m.nf与%m.nf基本相同,只是使输出的数值向左端靠,右端补空格
如果想输出%,应该在格式控制用两个%
scanf(格式控制,地址列表)
一般形式
格式控制与printf含义相同,地址列表有若干个地址组成的列表,可以是变量地址,或字符串首地址
格式说明
与printf相识,以%开头以一个格式字符结束,中间可以插入附加字符
格式字符
d,i用来输入有符号的十进制整数
u用来输入无符号的十进制整数
o用来输入无符号八进制
x,X用来输入无符号的十六进制
c用来输入单个字符
s用来输入字符串,将字符串送到一个字符数组,在输入时以非空白字符开始,以第一个空白字符结束。
f用来输入实数
说明
可以指定输入数据所占的列数,系统自动截取所需的数据
scanf(“%3d%3d”,&a,&b)输入123456
系统把123赋值给a,456赋值给b
如果在%后有一个*附加说明符,表示跳过它指定的列数
输入数据不能规定精度scanf(“%7.2f”,&a)不合法
使用scanf函数时应注意的问题
scanf函数中“格式控制”后面应当时变量地址,而不是变量名
scanf(“%d,%d”a,b)是错误的
如果在格式控制字符串中除了格式说明以外还有其他字符,则在输入数据是在对应位置应输入与这些字符相同的字符
scanf(“%d,%d”,&a,&b)
输入时应输入3,4
3后面是逗号,它与scanf的格式控制中的逗号对应
如果输入时不用逗号而用空格或其他是不对的
在用%c格式输入字符时,空格字符和转义字符都作为有效字符输入
scanf("%c%c%c",&c1,&c2,&c3);
若输入a空格b空格c
那么c1=a,c2=空格,c3=b;
在输入数据时,遇到以下情况时认为该数据结束
空格,回车,跳格
遇到指定宽度结束
遇到非法输入
选择结构程序设计
关系运算符和关系表达式
关系运算符及优先次序
<,<=,>,>=,==,!=
前四种运算符优先级别相同,后两种也相同
前四种大于后两种
关系运算符优先级低于算术运算符
关系运算符的优先级高于赋值运算符
关系表达式
用关系运算符将两个表达式连接起来的式子
a+b>c+c
关系表达式的值是一个逻辑值
逻辑运算符和逻辑表达式
逻辑运算符及其优先次序
&&与
||或
!非
&&和||是双目运算符要有两个运算量
!是一目运算符,只要一个运算量
!优先于&&,&&优先于||
逻辑运算符中的&&和||低于关系运算符,!高于算术运算符
逻辑表达式
逻辑表达式的值是逻辑量,真或假
表达式自左向右扫描
逻辑表达是的求解中,并不是所有逻辑运算符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的解时,才执行该运算
if语句
if语句的3中形式
if(表达式)语句
if(x>y)printf("%d",x);
if(表达式)语句1else语句2
if(x>y)print("%d",x);
else print("%d",y);
if(表达式1)语句1
else if(表达式2)语句2
...
else if (表达式n) 语句n
else 语句m
else if(表达式2)语句2
...
else if (表达式n) 语句n
else 语句m
3种形式的if语句中在if后面都有表达式,一般为逻辑表达式或关系表达式
第二第三种形式的if语句,在每个else前面都有一分号,整个语句结束处都有一分号
在if和else后面可以只含一个内嵌操作语句,也可以有多个操作语句用
if语句的嵌套
if()
if()语句1
else 语句2
else
if() 语句3
else语句4
if()语句1
else 语句2
else
if() 语句3
else语句4
else总是与它上面的最近未配对的if配对
条件运算符
当if的表达式无论真假都执行赋值语句且向同一变量赋值时,可以用一个条件运算符来处理
if(a>b)max=a;
else max=b;
else max=b;
max=(a>b)?a:b;
(a>b)?a:b是条件表达式,a>b为真,取值a,否则取值b
条件运算符要求有3个操作对象,称三元运算符 表达式1?表达式2:表达式3
条件运算符执行顺序,先求解表达式1,若非0则求解表达式2此时表达式2的值作为整个条件表达式的值。若为1求表达式3
条件运算符优先于赋值运算符,因此在上面的式子中先求条件表达式,再将它赋值给max
条件运算符的优先级低于关系运算符和算术运算符
可写成 max=a>b?a:b
条件运算符的结合方向自右向左
表达式2和2可以是数值表达式,也可以是赋值表达式或函数表达式
条件表达式中,表达式1的类型可以与表达式2,3类型不同
switch语句
switch(表达式)
{
case 常量表达式1;语句1
case 常量表达式2;语句2
...
case 常量表达式n;语句n
default;
}
{
case 常量表达式1;语句1
case 常量表达式2;语句2
...
case 常量表达式n;语句n
default;
}
switch 后面括号内的表达式,其值可以是整型,字符型,枚举型数据
当表达式的值与摸一个case后面的常量表达式的值相等时,就执行case后面的语句。若case没有与表达式匹配的值,就执行default
每个case常量表达式的值必须不相同
执行完一个case后面的语句后,流程控制转移到下一个case继续执行,case 常量表达式只是起语句标号作用,并不是在该处进行条件判断。
如果要执行一个case分支后跳出switch,可以用一个break语句来终止。
如果要执行一个case分支后跳出switch,可以用一个break语句来终止。
case 的位置不影响执行结果
多个case可以共用一组执行语句
case 'a';
case'b';
case 'c'; printf(">60\n");break;
case'b';
case 'c'; printf(">60\n");break;
循环控制
goto语句及其用goto语句构成循环
goto语句为无条件转向语句
goto 语句标号;
语句标号用标识符表示,它的定名规则与变量名相同,由字母,数字和下划线组成,第一个字符必须为字母或下划线
goto, laber_1
goto 123 错误
结构化设计方法主张限制使用goto,因为会使程序可读性变差
主要用途
与if语句一起构成循环结构
从循环体中跳转到循环体外,但在C语言中可以用break语句和continue语句跳出循环体因此goto使用机会少
while
while(表达式)语句
当表达式非0时,执行语句中内嵌语句,先判断后执行
循环体如果包含一个以上语句,应该用花括号括起来,以复合语句形式出现,如果不加花括号,执行到while后面第一个分号处
在循环体中应该有趋于结束的语句,否则while循环永不结束
do...while语句
do 循环体语句
while(表达式);
while(表达式);
先执行循环体,再判断循环条件
如果循环条件为真,返回重新执行循环体语句
用for语句实现循环
for(循环表达式赋初值;循环条件;循环变量增值)语句
可用于循环次数不确定而只给出循环结束条件的情况。可以替代while
先求解循环赋初值
求解循环条件,若值为真,则执行for语句中指定的内嵌语句。再求解循环变量增值,转回执行循环条件
如果表循环条件为假结束for循环
for语句的一般形式中表达式1可以省略,其后的分号不能省略
for(;i<10;i++)
如果表达式2省略,既不判断循环条件,循环无终止地进行下去,也就是认为表达式2始终未真
表达式3也可以省略,但应设法保证循环能正常结束
可以省略表达式1和3,只给循环条件
表达式1可以设置循环变量初值,也可以是循环变量无关的其他表达式
for(sum=0;i<=100,i++)
表达式一般是关系表达式或逻辑表达式,但也可以是数值表达式或字符表达式只要其值非零就执行
for(i=0;(c=getchar())!='\n';i+=c)
键盘输入enter键以后将一批数据一起送到内存缓冲区
不是敲一个字符马上输出一个字符
循环的嵌套
一个循环体内包含另外一个完整循环体。
while循环,do..while循环,for循环可以相互嵌套
集中循环的比较
4中循环可以处理同一问题,一般可代替,但不提倡用goto
while和do...while为了使循环正常结束,应在循环体中包含趋于结束的语句
用while和do...while,循环变量初始化应该在语句之前完成。for可以在表达式1实现初始化
while,do...while ,for 可以用break语句跳出循环,用continue语句结束本次循环
break语句和continue语句
break语句
break语句可以跳出switch,也可用来跳出循环体,提前结束循环
一般形式,break;
break只能用于循环语句和switch语句
continue语句
一般形式 continue;
结束本次循环,进行下一次执行判断
与break的区别是不终止循环过程,之终止循环中的一次
数组
一维数组的定义与引用
一维数组的定义
类型说明符 数组名【常量表达式】;
int a[10];
定义了一个整型数组,名为a,有10个元素
数组名的命名规则和变量名相同,遵循标识符命名规则
在定义数组是,需要制定数组中的元素个数,方括号中的常量表达式用来表示元素的个数
inta[10] 注意不存在a[10]元素,最大a[9]
常量表达式可以是常量和符号常量,不能是变量,c语言不允许对数组大小做动态定义
int n;
scanf("%d",&n);
int a[n];定义数组错误
scanf("%d",&n);
int a[n];定义数组错误
一维数组元素的引用
数组必须先定义然后使用,C语言规定只能逐个引用数组元素,不能一次引用整个数组
数组元素表达形式
数组名[下标]
下标可以是整型常量或整型表达式
一维数组的初始化
在定义数组时对数组元素赋初值
int a[2]={0,1,2};
可以只给一部分元素赋值
inta[2}={10,1};
只给前两个赋值,后一个元素为0
如果想使一个数组中的全部元素值为0
int a[2]={0,0,0,}
int a[2]={0};
int a[10]={0*10};错误
对全部元素赋值时,由于数据个数已确定,因此可以不指定数组长度
int a[]={1,2,3,4,5};
二维数组的定义与引用
二维数组的定义
一般形式
类型说明符 数组名[常量表达式][常量表达式];
float a[3][4]
3行4列的float类型数组
在C语言中,二维数组的元素是按行存放的,内存中先放第一行元素,在放第二行元素
二维数组的引用
数组名[下标][下标]
a[2][3]
第二行第三列
数组元素可以出现在表达式中,也可以被赋值
b[1][2]=a[2][3]/2
注意下标值应该在定义数组的大小范围内
二维数组初始化
分行给二维数组赋值
int a[2][2]={{0,1},{2,3}}
所有有数据写到一个花括号,容易混淆
可以对部分元素赋初值
int a[2][2]={{1},{2}},只对各行的第一列元素赋值,其他元素自动为0
如果全部元素都赋初值,则定义数组时对第一位的长度可以不指定
字符数组
字符数组的定义
用来存放字符数据的数组
char c[10]
由于字符型与整形是互相通用的,也可以定义一个整型数组存放字符数据
字符数组的初始化
逐个字符赋值给数组中的各个元素
char c[2]={'a','b','c'}
如果提供初值个数与预期的数组长度相同,可以省略数组长度,系统会自动确定长度
字符数组的引用
可以引用字符数组的一个元素,得到一个字符
字符串和字符串结束标志
C语言中字符串是作为字符数组来处理的
C语言规定以\0作为字符串结束标志位,字符串作为一维数组存放在内存中
程序依靠检测\0来判定字符串是否结束,、0在ASCII码中代表控操作符,什么都不做
用字符串常量来是字符数组初始化,省去花括号
charc[]="i am happy";
因为字符串常量最后系统加上了\0
如果想用一个新字符替代原有字符串,如果没有加\0那么新旧字符连成一片
char c[]={"happy"}
char c[]={"ok"}
那么会输出okppy,必须在ok后加上\0代表结束
字符数组的输入输出
逐个字符输入输出
用格式符%c输出或输入一个字符
用%s将整个字符串一次输出或输入,意思是string
charc[]={"hello"};
printf(“%s”,c)
输出hello
输出字符不包括\0
用%s格式符输出字符串时,printf函数的输出项是字符数组名,不是数组元素名
printf("%s",c[0])是错误的
如果数组长度大于字符串的实际长度,也只输出遇到\0结束
如果一个字符数组中包含一个以上\0,到第一个就结束
可以用scanf("%s",c)输入一个字符串
如果利用scanf输入多个字符串,则在输入时以空格分隔
char str1[5],str2[5],str3[5];
scanf("%s%s%s",str1,str2,str3);
输入how are you?
那么str1={how\0\0},str2={are\0\0},str3={you?\0}
如果改为charstr[13]
那么str={how\0\0...}
scanf的输入项是字符数组名,就不要用地址符&,因为C语言中数组名代表数组起始地址
字符串处理函数
C函数库提供
puts(字符数组)
将一个字符串输出到终端
可以用printf代替,用的不多
gets(字符数组)
从终端输入一个字符串到字符数组得到一个函数值,该函数值是字符数组的起始地址
gets(str)
输入OK
将OK送个字符数组str
puts 和gets只能输入输出一个字符串
strcat(字符数组1,字符数组2)
作用是连接连个字符数组中的字符串
把字符串2,连接到字符串1后面,结果放入字符数组1
调用后得到一个函数值,字符数组1的地址
说明1 字符数组1必须足够大,能容纳连接的新字符串
说明2,连接前两个字符串后面都有\0,连接后只保留一个
strcpy(字符数组1,字符串2)
作用将字符串2复制到字符数组1中
字符数组1必须定义的足够大,以便容纳被复制的字符串
字符数组1必须写出数组名形式如str1,字符串2可以是字符数组名也可以是字符串常量
如果在复制前未对字符数组1赋值,则字符数组的内容是无法预知的
不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组
str1=“China”
str1=str2
错误,只能用strcpy函数赋值
可以用strnpy函数将字符串2中前面n个字符复制到字符数组1中去
strncpy(str1,str2,2)
将str2前面2个字符赋值到str1
strcmp(字符串1,字符串2)
比较字符串1和字符串2
对两个字符自左向右逐个字符相比,直到出现不同字符或遇到\0
如果字符全部相同,则认为相等,若不同则以第一个不同字符的比较结果为准
在英文字典中位置靠后为大
compare在computer之后因此computer>compare
小写字母大于大写字母
比较结果由函数值待会
如果字符串相等,则函数值=0
如果字符串1>字符串2,则函数值为正整数
如果字符串1<字符串2,则为负整数
两个字符串不能比较
if(str1>str2)printf("yes") 错误
只能用strcmp((str1,str2)>0) print("yes")
strlen(字符数组)
测试字符串的长度,函数值为字符串长度不包含\0
strlwr(字符串)
将字符中的大写字母改为小写字母
strupr(字符串)
将字符串中的小写字母改大写
函数
概述
一个大的程序分为若干程序模块,每个模块实现一个特定功能。
子程序的功能是用函数来完成的,一个主函数可以由若干个其他函数构成
说明
一个C程序有一个或多个源程序构成,分解为多个源程序可以分别编写,分别调试,提高效率
一个源程序是一个编译单元,不是以函数为单元编译的
C程序执行从main函数开始,在main调用其他函数,在执行完后返回到main函数,在main函数中结束整个程序运行
所有函数都是平行的,在定义函数时分别进行,相互独立。一个函数不属于另外一个函数。函数间可以互相调用,但不能调用main
从用户角度看
标准函数
库函数,系统提供的,不用用户自己写
用户自己定义的函数
用来解决用户的专门问题
从函数形式分
无参函数
主调函数不想被调函数传递参数,一般用来执行操作
有参函数
主调函数通过参数向被调函数传递数据
一般情况会被调函数会返回一个函数值,给主调函数使用
函数定义的一般形式
无参函数的一般形式
类型标识符 函数名()
{
声明部分
语句部分
}
{
声明部分
语句部分
}
有参函数一般形式
类型标识符 函数名(形式参数列)
{
声明部分
语句部分
}
{
声明部分
语句部分
}
空函数
类型标识符 函数名()
{ }
{ }
在主函数中的作用是标记一个函数,现在没有作用,等以后扩充
函数参数和函数的值
形式参数和实际参数
再定义函数括号中的变量位形参,在主调函数调用函数时给被调函数的参数为实参
说明
在未调用函数时,形参不占用内存,只有发送调用时,才分配形参内存,调用后释放
实参可以是常量,变量或表达式
max(3,a+b)
在被定义的函数中(被调函数),必须指定形参数据类型
实参与形参的类型应当相同
实参int,形参float 出错
实参向形参数据传递是“值传递”单向,只由实参传给形参,不能反向
内存中实参和形参在不同的内存单元,在调用函数时,给形参分配存储单元。
将实参对应的值传递给形参,调用结束后,形参单元被释放,实参保留原值。
将实参对应的值传递给形参,调用结束后,形参单元被释放,实参保留原值。
函数返回值
希望通过函数调用使主调函数得到一个确定的值
说明
函数的返回值是通过被调函数中的return语句获得,return语句将被调函数中的一个确定值带回主调函数中去
一个函数可以由一个以上的return语句,执行到那个语句,返回那个值
return的值应当与定义函数时指定的类型相同
对于不带回值得函数,应当用void定义函数为无类型
函数的调用
函数调用的一般形式
函数名(实参表列)
如果调用无参函数,实参表列可以没有,但括号不能省略
如果实参表列包含多个实参,各个参数用逗号隔开
实参与形参个数相等,类型陪陪,且按照顺序一一对应
函数调用的方式
在程序出现的位置
作为函数语句
把函数调用作为一个语句,不要求函数带回值,只完成一定的操作
作为函数表达式
函数出现在一个表达式中,这时要求带回一个确定的值参加表达式运算
作为函数参数
函数调用作为另外一个函数的实参,要求带回值
m=max(a,max(b,c))
对被调函数的声明和函数原型
一个函数调用另外一个函数需要的条件
1被调用的函数必须是已经存在的函数
2 如果使用库函数,在文件开头用#include命令将有关库函数包含到文件中
#include<stdio.h>
3如果使用用户自己定义的函数,而改函数的位置再调用它的函数的后面,应该在主调函数中对被调函数做声明
函数定义是对函数功能的确立,函数声明是把函数的信息通知编译系统,以便在调用函数时系统按此进行对照检查
函数原型的两种形式
在调用函数中声明被调函数称为函数原型
如果乜有对函数声明,编译系统就无法确定被调函数
函数类型 函数名(参数类型1,参数类型2,...)
int max(int ,float,int...)
函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2...)
int max(int a,float b...)
第一种是基本形式,第二种增加变量名只是为了便于阅读,系统不检测参数名,因此参数名是什么无所谓
如果被调函数出现在主调函数前,可以不加声明,因为系统已经知道函数信息
如果已在文件开头所有函数之前,对本文件所调用的函数进行声明,则不对在各函数中对调用函数再做声明
int max(int,int )开头声明
void main()
{
不必对max再声明
}
int max()
{函数定义}
void main()
{
不必对max再声明
}
int max()
{函数定义}
函数的嵌套调用
C语言的函数定义是独立的,在定义函数时,一个函数不能包含另外一个函数
Pascal 可以在定义函数体内包含其他函数体定义,称为嵌套定义,这个嵌套只能被它包含的函数调用
C语言 不能嵌套定义函数,但可以嵌套调用函数,在调用一个函数的过程中,又调用另外一个函数
只是使用函数,不是定义函数
执行过程
执行main开头部分
遇到调用语句,调用函数a,执行a函数
a函数定义中,调用了b函数,执行b函数
执行完b函数,跳回到a,继续执行a
被调函数a执行完,退回到main执行
函数的递归调用
在调用一个函数过程中又出现直接或间接地调用函数本身
int f(int x)
{
int y,z;
z=f(y);
return(2*z);
}
{
int y,z;
z=f(y);
return(2*z);
}
在调用函数f的过程中,又调用f函数,直接调用自己
如果在函数1中调用函数2,而函数2又要再次调用函数1,是间接调用
直接调用和间接调用都会无终止的进行调用,程序不运行出现这个情况,只能出现有限次,有终止的递归调用
用if语句在某一条件才继续执行递归调用
数组作为函数参数
数组元素可以作为函数实参,用法与变量相同。数组名也可以作为实参和形参,传递数组的首元素
数组元素作为函数实参
函数 int large(int x, int y);
large(a[i];b[i])数组元素做实参
large(a[i];b[i])数组元素做实参
数组名作函数参数
用数组名作函数参数,此时形参应当用数组名或指针变量
float average(float array【10】)函数声明
float score[10];定义一个10元素数组
a= average(score)数组名作函数参数
float score[10];定义一个10元素数组
a= average(score)数组名作函数参数
说明
用数组作函数参数应该在主调函数和被调函数分别定义数组
array 是形参数组名,score是实参数组名,分别在其所在函数定义,不能只在一方定义
实参数组与形参数组类型应一直,否则出错
在被调用函数中声明了形参数组大小为10,但实际上其指定大小不起作用。系统不对形参数组大小做检测,
只将实参数组的首元素地址穿给形参数组。他们具有相同的地址和存储单元。
只将实参数组的首元素地址穿给形参数组。他们具有相同的地址和存储单元。
形参数组可以不指定大小,在定义数组时在数组后面跟一个空的方括号。
用数组名做函数实参,不是把数组元素的值传递给形参,而是把实参数组首地址传给形参数组
多维数组名作函数参数
由于二维数组是由若干一维数组组成,在内存中按列存放。因此必须定义列数。
在第二维大小相同的情况下,形参数组的第一维可以与实参数组不同。
局部变量和全局变量
局部变量
在一个函数内部定义的变量是内部变量,只在函数范围有效,称为局部变量
说明
主函数中定义的变量也是局部变量,主函数不能使用其他函数中定义的变量
不同函数中可以使用相同名字的变量,他们代表不同对象,互不干扰。
形参也是局部变量,在定义函数内有效
在一个函数内部,可以在复合语句中定义变量,这些变量只在复合语句中有效。
复合语句也称为分程序,程序块
复合语句也称为分程序,程序块
全局变量
系统编译的单元是源程序文件,一个源文件包含多个函数,在函数外定义的变量
称为全局变量,有效范围从定义变量开始到本源文件结束
称为全局变量,有效范围从定义变量开始到本源文件结束
全局变量可以被范围内的所有函数共用
说明
全局变量是问了增加函数间数据联系
由于函数调用只能带回一个返回值,因此可以利用全局使函数调用改变多个值
全局变量第一个字母用大写表示
不在必要时不使用全局变量
占用存储单元
函数的通用性降低,因为执行时要依赖外部变量。
太多的全局变量,降低程序的清晰性
同一个源文件,外部变量与局部变量同名,那么在局部变量作用范围内,外部变量被屏蔽
变量的存储类别
动态存储方式与静态存储方式
变量按作用域分
局部变量
全局变量
按存在时间分
静态存储方式
在程序运行时分配固定的存储空间
动态存储方式
根据需要进行动态分配存储空间
内存中用户的存储空间分三部分
程序区
静态存储区
动态存储区
数据存放在静态存储区和动态存储区
全局变量全部存放在静态存储区
动态存储区中存放
函数形式参数,调用时分配,调用结束释放
自动变量
函数调用时的现场保护,和返回地址
在C语言中,每个变量或函数由两个属性
数据类型
int float char
数据存储类别
内存中存放的方式
auto
static
register
externa
auto
函数的局部变量,如不声明位static,则动态地分配存储空间。调用结束释放
auto 可省略,默认自动存储
static
有时希望函数中局部变量的值在函数调用结束后不消失而保留原值,其占用的存储单元不释放。下次调用函数式,改变量已有值。
在局部变量前加上关键词static 做声明
说明
静态局部变量属于静态存储类别,在静态存储区分配存储单元,在整个程序运行过程都不释放。自动变量属于动态存储类别
占动态存储区空间而不占静态存储区空间,函数调用结束后释放
占动态存储区空间而不占静态存储区空间,函数调用结束后释放
对静态局部变量在编译时赋初值,只赋值一次,每次调用函数保留当前只。对自动变量赋初值,
不是在编译时进行,而是在函数调用时进行,每次调用都赋值
不是在编译时进行,而是在函数调用时进行,每次调用都赋值
在定义局部变量不赋初值的话,对静态局部变量来说,编译时自动赋值0或空字符。
对自动变量来说,如果不赋初值则他的值是一个不确定的值。
对自动变量来说,如果不赋初值则他的值是一个不确定的值。
虽然静态局部变量在函数调用结束后仍然存在,但其他函数时不能引用的
当多次调用静态局部变量时,无法确认当前值是多少,因此非必要,不多用
register
一般情况变量的值存放在内存中,当程序用到哪个变量的值,由控制器发出指令将内存中概变量的值送到运算器中
经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放
经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放
如果有一些变量频繁使用,则存取变量的过程花费不少时间,为了提高执行效率,
C语言允许将局部变量的值放在CPU中的寄存器。需要用是直接从寄存器取出参加运算,
不必到内存中去存取。可以提高效率。这种变量称为寄存器变量,用关键字register 声明
C语言允许将局部变量的值放在CPU中的寄存器。需要用是直接从寄存器取出参加运算,
不必到内存中去存取。可以提高效率。这种变量称为寄存器变量,用关键字register 声明
说明
只有局部自动变量和形式参数可以作为寄存器变量,其他不行。
在调用时存放,函数结束时释放
系统中寄存器数目有限,不能无限定义
局部静态变量不能定义为寄存器变量
register static int a
错误,静态存储区和寄存器二者取一
当今的系统能够自动将频繁使用的变量放到寄存器,而不需要设计者指定,因此register声明不必要
external外部变量
外部变量是在函数外部定义的全局变量,作用域从变量定义开始,到本程序文件末尾
有时需要用extern来声明外部变量,以扩展外部变量的作用域。
在一个文件内声明外部变量
如果外部变量不定义在文件开头,那么他的作用域只从定义位置开始
如果在定义位置前,函数想引用外部变量,则在引用前加关键字extern对变量声明
extern a, b;
一般会把外部变量定义放在所有函数之前,可以避免加extern
在多个文件的程序中声明外部变量
两个源文件都要用同一个外部变量a。不能在两个文件中各自定义,否则会出现重复定义
正确做法:在任意一个文件定义外部变量a
file1.c
int a =0;
int a =0;
在另外一个文件中extern对a作外部变量声明
file2.c
extern a;
extern a;
系统编译时,系统会知道a是一个已在别处定义的外部变量,并将作用域拓展到本文件。
系统编译遇到extern时,先在本文件中找外部变量定义,如果找到,在本文件中拓展作用域。
如果找不到,就在连接是从其他文件里找外部定义。
如果找不到,就在连接是从其他文件里找外部定义。
用static 声明外部变量
有时在程序设计中希望某些外部变量只限被本文件引用,而不能被其他文件引用。
这是可以在定义外部变量是加一个static声明
这是可以在定义外部变量是加一个static声明
加上static声明,只能用于本文件的外部变量称静态外部变量
关于变量的声明和定义
需要建立存储空间的声明称为定义
int a;
不需要建立存储空间的声明称为声明
extern a;
外部变量定义和外部变量声明的含义不同,外部变量定义只能有一次,而同一文件的声明可以有多次。
对外部变量的初始化只能在定义时进行,所谓的声明作用说明变量在后面定义了。作用拓展到前面
extern只用于声明,而不用与定义
static声明变量的作用
对局部变量用static声明,变量在整个程序执行期间不释放。
全局变量用static声明,变量的作用域限定在本文件
内部函数和外部函数
根据函数能否被其他源文件调用,将函数分为内部函数和外部函数
内部函数
如果函数只能被本玩家中其他函数调用,称为内部函数。在定义内部函数时,在函数名和函数类型前加static
static 类型标识符 函数名(形参表);
static int fun(int a ,int b)
内部函数又称静态函数,因为它用static声明。
外部函数
在定义函数时,如果在函数首部最左端加extern,表示此函数是外部函数,可供其他文件调用
extern int fun(int a.int b);
这样函数fun就可以为其他文件调用,如果省略extern,默认为外部函数。
在需要调用此函数的文件中,extern对函数作声明,表示该函数时再其他文件中定义的外部函数
extern void enter_string(char str[]);
C语言允许声明函数时省写extern,直接用函数原型
进一步理解函数原型
能够把函数作用域拓展到定义该函数的文件外。
只要在调用函数中包含函数原型即可,函数原型通知系统。该文件稍后定义,或在另外一个文件中定义
调用函数原型需要查函数库,且在程序中写出来比较麻烦,因此用math.h即可
预处理命令
C源程序中加入预处理命令,改进程序设计环境,提高编程效率。
这些预处理命令有ANSI C同一规定,但它不是C语言的组成部分,不能直接对他们进行编译。
必须在对程序进行通常编译前,先对程序中的这些特殊命令进行预处理。
这些预处理命令有ANSI C同一规定,但它不是C语言的组成部分,不能直接对他们进行编译。
必须在对程序进行通常编译前,先对程序中的这些特殊命令进行预处理。
若程序中用#define定义一个符号常量A ,则在预处理时将程序中所有A都置换为指定的字符串
预处理命令不是C语言的一部分,也不是c语句
C提供的预处理功能
宏定义
文件包含
条件编译
为了与C语句区别开来,一般在命令前加上#
宏定义
不带参数的宏定义
用一个指定的标识符来代表字符串
# define 标识符 字符串
# define PI 3.14
作用是在本程序文件中用指定的标识符PI来代替3.14,在编译预处理后,程序出现的所有PI都用3.14代替
标识符也称 宏名,在预编译时将宏名替换成字符串的过程称为宏展开
说明
宏名一般习惯大写。以便与变量区分
使用宏名代替一个字符串,可以减少程序中重复书写某些字符串,减少工作量,宏名具有某种意义,易于理解含义PI 和3.14
当需要改变常量时,只需改宏定义命令行,程序所有命令能全部修改。提高效率
# define ARRAY_SIZE 1000
int a[ARRAY_SIZE];
int a[ARRAY_SIZE];
宏定义的字符串不做正确性检查 ,如写成#define PI 3.a4 .预处理时也照样代入。
宏定义的C语句,不必再行末加分号。如果加了分号,会把分号当做字符串的一部分
#define 命令出现在程序中函数的外面,宏名的有效范围在定义命令之后到本源文件结束
可以用 #undef 命令终止宏定义的作用域
在进行宏定义时,可以引用已定义的宏名,可以层层置换
# define R 3.0
# define PI 3.14
# define L 2*PI*R
# define PI 3.14
# define L 2*PI*R
用宏名定义宏
对程序中用双撇号括起来的字符串内的字符,即使与命名相同,也不置换
宏定义只做字符替换,不分配内存空间
带参数的宏定义
# define 宏名(参数表)字符串
带参数的宏定义不是进行简单的字符串替换,还要进行参数替换
#define S(a,b)a*b
area=S(3,2);
area=S(3,2);
用3和2替换宏定义中的形式参数a,b
因此area=6
在程序中如果有带实参的宏,则按命令行中指定的字符串从左到右置换,如果串中包含宏的形参,
则将程序语句中相应的实参代替形参。如果宏定义的字符串中的字符不是参数字符,则保留
则将程序语句中相应的实参代替形参。如果宏定义的字符串中的字符不是参数字符,则保留
说明
#define S(r) 3.14*r
area=S(a+b)
替换后变成 3.14*a+b,
不符合预期
area=S(a+b)
替换后变成 3.14*a+b,
不符合预期
所以在定义时,最好写成 #define S(r) 3.14*(r)
在定义宏时,宏名与带参数的括号之间不应加空格,否则将空格以后的字符都作为替代字符串的一部分
被认为是不带参数的宏定义
被认为是不带参数的宏定义
带参数宏与函数区别
函数调用时,先求实参的值再代入形参。而使用带参数的宏只进行字符替换
函数调用为形参分配内存,而宏展开不分配内存,不进行值传递,和返回值
函数的实参和形参都有数据类型,而宏没有类型,只是一个符号代表
函数调用只能得到一个返回值,而宏可以得到几个结果
#define CIRCLE(R,L,S,V)L=2*PI*R;S=PI*R;S=PI*R*R;V=4/3*PI
宏使用次数多时,宏展开后源程序变长,而函数调用不会使源程序变长
宏替换不占运行时间,只占用编译时间,而函数调用占用运行时间。宏代表简短的表达式比较合适。
巧妙利用宏定义,可以事先将程序的输出格式定义好,减少在输出语句中每次都要写格式的麻烦
#define PR prinrf
#define NL "\n"
#defin D "%d"
PR(D1,a);
PR(D2,a,b);
#define NL "\n"
#defin D "%d"
PR(D1,a);
PR(D2,a,b);
可以定义各种格式宏,把他们单独放到一个文件,用#include包容到自己的程序中。可以提高编程效率
文件包含处理
文件包含指,一个源文件将另外一个源文件的全部内容包含尽量。用#include来实现文件包含操作
#include“文件名”
#include<文件名>
包含过程是,将被包含的文件复制到文件中,作为一个源文件单元编译
文件包含命令很有用,可以节省程序设计人员的重复劳动。例如把固定的常量用宏定义组成一个文件,然后用#include包含,这样就不需要重复定义。相当于工业中的标准件。
常在文件头部被包含的文件称为标题文件或头文件,常以.h结尾。用.c为后缀,或没有后缀都可以,但用.h更能表示文件性质
如果需要修改程序中常用的一些参数,可以不必修改每个程序,只需把这些参数放到一个头文件中,在需要时修改头文件即可。但需要重新编译
头文件除了可以包括函数原型和宏定义外,还可以包括结构体类型定义和全局变量定义
说明
一个#include命令只能指定一个被包含文件,如果要包含n个文件,要用n个#include命令
如果文件1包含文件2,而文件2用到文件3的内容,则应在文件1中先包含文件3再包含文件2
在一个被包含文件中可以包含另外一个文件,文件包含可以嵌套
上面的问题如果文件2 包含了文件3 那么在文件1中只要包含文件2就行
用<>和“”的区别
用<>系统到存放C库函数头文件目录中寻找要包含的文件,称为标准方式
用“”系统先在用户当前目录中寻找要包含的文件,若没有,再按标准方式查找
为了提高效率,如果调用库文件用<>,如果包含用户文件用“”
被包含文件与其所在的文件编译后成为同一个文件,因此文件若有全局静态变量,在文件1中也有效,不必用extern 声明
条件编译
一般情况下源程序的所有行都参加编译,但有时希望程序中一部分内容在满足条件才编译。
条件编译的形式
#ifdef 标识符
程序段1
#else
程序段2
#endif
程序段1
#else
程序段2
#endif
若指定的标识符已经被#define定义过
则编译接到编译程序段1,否则编译程序段2
则编译接到编译程序段1,否则编译程序段2
#else 可以没有
#ifdef 标识符
程序段1
#endif
程序段1
#endif
程序段可以是语句组也可以是命令行
条件编译的好处:如果一个源程序在不同计算机运行,而不同计算机又有一定差异,如有的是32位有的是64位,
这往往需要对源程序作修改,降低了程序的通用性。
这往往需要对源程序作修改,降低了程序的通用性。
#ifdef COMPUTER_A
#define INTEGER_SIZE 16
#else
#define INTERGER_SIZE 32
#endif
#define INTEGER_SIZE 16
#else
#define INTERGER_SIZE 32
#endif
如果编译前出现#define COMPUTER_A 0,或#define COMPUTER_A
只要COMPUTER_A被定义过,则在程序编译时就编译#define INTERGER_SIZE 16
只要COMPUTER_A被定义过,则在程序编译时就编译#define INTERGER_SIZE 16
这样源程序不必作修改就可以用于不同类型的计算机系统。
在调试程序时,常常希望输出一些所需的信息,
而在调试完成后不再输出这些信息
而在调试完成后不再输出这些信息
#ifdef DEBUG
printf("x=%d,y=%d\n",x,y,x);
#endif
printf("x=%d,y=%d\n",x,y,x);
#endif
如果在它前面有一下命令行
#define DEBUG
在程序运行时输出x,y,z得的值,以便调试分析
程序调试完后,只需将#define命令行删除即可
#define DEBUG
在程序运行时输出x,y,z得的值,以便调试分析
程序调试完后,只需将#define命令行删除即可
有人觉得不用条件编译也可以,在调试时加一批printf语句,
调试后将printf一一删除。但如果调试时加的printf语句较多时
修改的工作量很大。用条件编译,则不必一一删改printf,
只需删除前面的一条#define DEBUG命令即可
如同一个开关,同一控制的作用
调试后将printf一一删除。但如果调试时加的printf语句较多时
修改的工作量很大。用条件编译,则不必一一删改printf,
只需删除前面的一条#define DEBUG命令即可
如同一个开关,同一控制的作用
#ifndef 标识符
程序段1
#else
程序段2
#endif
程序段1
#else
程序段2
#endif
条件编译第二种形式,将#ifdef 改为#ifndef 作用是若标识符未被定义过编译程序段1,否则编译程序段2
# if 表达式
程序段1
#else
程序段2
#endif
程序段1
#else
程序段2
#endif
当指定的表达式值为真时编译程序段1,否则编译程序段2
#define LETTER1
主程序
while(c=str[i]!='\0')
{ #if LETTER
if(c>=‘a’&& c<='z')
c=c-32;
#else
if (c>'A'&&C<='Z')
c=c+32;
#endif
主程序
while(c=str[i]!='\0')
{ #if LETTER
if(c>=‘a’&& c<='z')
c=c-32;
#else
if (c>'A'&&C<='Z')
c=c+32;
#endif
先定义LETTER 为1 这样条件编译时LETTER为真
则对第一个if语句进行编译,运行时使小写字母变大写。
有读者问,不用条件编译也可。但是不用的话所有语句都编译
当条件编译时,可以减少编译语句
则对第一个if语句进行编译,运行时使小写字母变大写。
有读者问,不用条件编译也可。但是不用的话所有语句都编译
当条件编译时,可以减少编译语句
指针
地址和指针的概念
系统编译变量时,会给变量分配内存,整型2个字节,浮点型4个字节。内存区每个字节都有一个编号
称为地址。相当于旅馆的房间号。
称为地址。相当于旅馆的房间号。
在程序中一般是通过变量名来对内存单元进行存取,实际上编译后的变量名就是地址。
print(“%d”,i)
找到变量i的地址2000然后有2000取出数据把它输出
这种按变量地址存取变量的方法称为直接访问
间接访问
将变量i的地址存放到另一个变量中
i_add=&i;
访问时,先找到存放变量i的地址变量i_add,从其中得到变量i 的地址。
再去找i的存储单元,然后对它进行存取访问
再去找i的存储单元,然后对它进行存取访问
为了将数值3送入变量中,有两种方式
将3送到变量i所标志的单元中
将3送到变量i_point所指向的单元中
地址称为指针,意思是通过它能找到以它为地址的内存单元。
一个变量的地址称为变量指针,如果有一个变量专门用来存放另外一个变量的地址称为指针变量。上面的i_add
变量的指针和指向变量的指针变量
指针变量常用*符号表示指向的对象。
*i_add=3;
将3赋值给指针变量i-add
定义一个指针变量
指针变量不同于其他数据类型变量,它是用来存放地址的,必须将它定义为指针类型
int *add1,*add2;
定义了两个指向整型的指针变量。左端的int 称为基类型。
基类型用来指定指针变量指向的变量类型。
一般形式
基类型*指针变量名;
float*add3
char* add4
如何让指针变量指向另一变量
赋值语句是一个指针变量得到另一个变量的地址
add1=&i;
add2=&j;
add2=&j;
将变量i的地址存放到指针变量add1.
注意
指针变量前的*表示该变量的类型为指针变量,指针变量名是add1,add2
在定义指针变量是必须指定基类型,因为不同类型数据在内存中所占字节数不同。
因为在指针的运算中,会使指针移动,移动要根据变量的类型来确定,如float4个字节
int 2个字节。
因为在指针的运算中,会使指针移动,移动要根据变量的类型来确定,如float4个字节
int 2个字节。
只有整型变量地址才能放到指向整型的指针变量中
指针变量的引用
指针变量只能存放地址,不要将一个整数赋给指针变量
*i_add=100;错误
运算符
&取地址运算符
*指针运算符,取指针指向对象的内容
说明
&*add1
&和*另个运算符优先级相同,但按自右向左结合。因此先进行*add运算,再执行&运算
add1=&a;
那么*add取出指针add执行对象的内容。在&*add指向内容的地址,与&a相同
add1=&a;
那么*add取出指针add执行对象的内容。在&*add指向内容的地址,与&a相同
*&a
&a指向变量a,*&a,等价于*a等价于变量a
指针变量作为函数参数
函数参数可以是指针类型,作用是将一个变量的地址送到另外一个函数中
VOID swap(int *pi,int*p2)
函数的参数是定义为指向整型的两个指针变量
如果想通过函数调用得到n个要改变的值
在主调函数设定n个变量,用n个指针指向他们
然后将指针变量作为实参,将这n个变量的地址传给所调用的函数的形参
通过改变形参指针变量,改变改n个变量的值;
主调函数就可以使用这些改变了的值
void swap(int*p1,int*p2)
swa[(add1.add2)
void swap(int*p1,int*p2)
{ int temp;
temp=*p1;
*p1=*p2;
*p2=temp;}
swa[(add1.add2)
void swap(int*p1,int*p2)
{ int temp;
temp=*p1;
*p1=*p2;
*p2=temp;}
*p1,*p2实参变量指向变量的值
不能企图通过改变指针形参的值使指针实参的值改变
{int *p;
p=p1;
p1=p2;
p2=p;}
p=p1;
p1=p2;
p2=p;}
P1p2实参变量的值
不能通过调用函数改变实参指针变量的值,但可以改变实参指针
所指向变量的值。
所指向变量的值。
数组与指针
指针变量可以指向变量也可指向数组元素
数组元素的指针
定义指向数组元素的指针,与指向变量指针定义相同
int a[10];
int *p;
int *p;
指针类型需要与数组类型相同
p=&a[0];把元素a[0]的地址赋值给指针变量p
由于数组名代表数组中首元素的地址,因此p=&a[0]等价于p=a
通过指针引用数组元素
int *p;
p=&a[0];
*p=1;表示将1赋值给p当前所指向的数组元素
p=&a[0];
*p=1;表示将1赋值给p当前所指向的数组元素
C语言规定,如果指针变量p指向数组的一个元素,那么p+1指向同一个数组中的下一个元素,而不是将p的值简单加1
如果是int型,p的地址+2个字节,如果是float型数组p的地址+4个字节
如果是int型,p的地址+2个字节,如果是float型数组p的地址+4个字节
如果p的初值是a[0],那么p+i和a+i就是a[i]元素的地址。指向数组第i个元素
如果p的初值为a[0]
*(p+i)或*(a+i)是p+i或a+i所指向的数组元素即a[i]
[]是变址运算符,将a[i]按a+i计算地址,然后找出地址单元中的值
指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价
引用一个数组元素
下标法
a[i]
指针法
*(a+i)或*(p+i)a是数组名,p是指向数组元素的指针变量p=a
输出数组全部元素
下标法
for(i=0;i<10;i++)
printf("%d",a[i]);
printf("%d",a[i]);
通过数组名计算数组元素地址
for(i=0;i<10;i++)
printf("%d",*(a+i));
printf("%d",*(a+i));
用指针变量指向数组元素
for(p=a;p<(a+10);p++)
printf("%d",*p);
printf("%d",*p);
方法1和方法2效率相同,方法1a[i]是转换为*(a+i)处理,先计算元素地址。
方法3效率高,用指针指向元素,不必每次都重新计算地址
用下标法比较直观,能直接知道是第几个元素。
使用指针变量指向数组元素时,要注意
不可以改变数组名
可以通过改变指针变量的值指向不同的元素,
但改变数组名如a++是不合法的,因为数组名代表
数组首元素地址,是一个指针变量。它的值在运行
期间固定不变。
但改变数组名如a++是不合法的,因为数组名代表
数组首元素地址,是一个指针变量。它的值在运行
期间固定不变。
注意指针变量的当前值
int *p,i,a[10];
p=a;
for(i=0;i<10;i++)
scanf("%d",p++)
printf("\n");
for(i=0;i<10;i++,p++)
printf("%d",*p);
p=a;
for(i=0;i<10;i++)
scanf("%d",p++)
printf("\n");
for(i=0;i<10;i++,p++)
printf("%d",*p);
输入与输出不一致
因为执行完第一个for循环后
指针指向了a[9],再读取时
p++导致指针指向位置区域
应该在第一个for执行完后加上
p=a;
因为执行完第一个for循环后
指针指向了a[9],再读取时
p++导致指针指向位置区域
应该在第一个for执行完后加上
p=a;
注意指针变量的运算
p++
使p指向下一个元素,再执行*p得到下一个元素值
*p++
++与*同优先级,结合方向自右向左
等价于*(p++)
先到p指向变量的值,在使p+1
*(++p)
先使p加1再去*p
用数组名作函数参数
int a[10];
f(a)
f(a)
数组名作函数参数
void swap(int x,int y)
swap(a[1],a[2])
swap(a[1],a[2])
数组元素a[1],a[2]作为实参通过值传递单向传给形参变量x,y
当x,y交换后,a[1],a[2]的值不会发生变化。
当x,y交换后,a[1],a[2]的值不会发生变化。
f(int a[],int n);
编译时,系统将形参a[]按指针变量处理
f(int*a,int n)
编译时,系统将形参a[]按指针变量处理
f(int*a,int n)
在函数调用时,系统建立一个指针变量a,用来存放从主调函数传递过来的实参数组首元素地址。
当a接收了实参数组首元素地址后,a就指向a[0],a+1指向a[1]。那么*a,*a+1就是元素a[0],a[1].
当a接收了实参数组首元素地址后,a就指向a[0],a+1指向a[1]。那么*a,*a+1就是元素a[0],a[1].
常用这种方法通过调用一个函数来改变实参数组的值。
以变量名和数组名作为函数参数的比较
变量名
要求形参类型
变量名
传递信息
变量的值
通过函数调用能否改变实参的值
不能
数组名
要求的形参类型
数组名或指针变量
传递的信息
实参数组首元素地址
通过函数调用能否改变实参的值
能
实参数组名代表固定地址,或指针常量,但形参数组并不是一个固定的地址值,而是作为指针变量。
在函数调用开始时,它的值等于实参数组首元素的地址。在函数执行期间可以再被赋值
在函数调用开始时,它的值等于实参数组首元素的地址。在函数执行期间可以再被赋值
总结
一个实参数组,如果想在函数中改变次数组中元素的值,实参与形参的对应关系有
1形参和实参都用数组名
int a[10]
void f(int x[],int n)
f(a,10)
void f(int x[],int n)
f(a,10)
形参数组名接受了实参数组首元素地址,认为在函数调用期间
形参数组与实参数组共用一段内存单元。
形参数组与实参数组共用一段内存单元。
2 实参用数组名,形参用指针变量
int a[10]
f(a,10)
void f(int*x,int n)
f(a,10)
void f(int*x,int n)
函数开始执行时,x执行a[0],x=&a[0]
3 实参形参都用指针变量
int a[10],*p=a;
f(p,10);
void f(int*x,int n )
f(p,10);
void f(int*x,int n )
实参p与形参x都是指针变量,先使实参指针p指向数组a,p=&a[0].
然后将p的值传递给形参指针变量x,x的初始值也是&a[0]。通过x
的值改变可以是x指向数组a的任一元素
然后将p的值传递给形参指针变量x,x的初始值也是&a[0]。通过x
的值改变可以是x指向数组a的任一元素
4实参为指针变量,形参为数组名
int a[10],*p=a;
f(p,10);
void f(int x[],int n)
f(p,10);
void f(int x[],int n)
实参p为指针变量,指向a[0],形参为数组名x,系统将x作为指针变量处理
如果用指针变量作为实参,必须使指针变量有确定值,指向一个已定义的单元
多维数组与指针
多维数组元素的地址
a
二维数组名,指向一维数组a[0],第0行首地址
2000
a[0] *(a+0),*a
0行0列元素地址
2000
a+1,&a[1]
1行首地址
2008
a[1],*(a+1)
1行0列元素a[1][0]的地址
2008
a[1]+2,*(a+1)+2,&a[1][2]
1行2列元素a[1][2]的地址
2012
*(a[1]+2),*(*(a+1)+2),a[1][2]
1行2列元素的值
元素值为1
为什么a+1和*(a+1)都是2008
a+1是地址,*(a+1)是地址内容为什么是同一个值?
a+1是序号为1的行的首地址,而*(a+1)不是a+1的内容。因为
a+1并不是一个变量的存储单元。所有它就不是取地址内容了
*(a+1)就是a[1],而a[1]是一维数组名,是地址指向a[1][0]
a+1并不是一个变量的存储单元。所有它就不是取地址内容了
*(a+1)就是a[1],而a[1]是一维数组名,是地址指向a[1][0]
二维数组和一维数组的区别
二维数组指向行,a+1的1代表一行中的全部元素所占的字节数。
一维数组名是指向列元素的,a[0]+1中的1代表一个元素所占的字节数。
二维数组指向行的指针前加一个*转换为指向列的指针,如a和a+1,是指向行的指针
*a和*(a+1)变成指向列的指针,分别指向a[0][0]和a[1][0]
*a和*(a+1)变成指向列的指针,分别指向a[0][0]和a[1][0]
在指向列的指针前加上&得到a[0],由于a[0]与*(a+0)等价,因此&a[0]与&*a等价。
与a等价,它指向二维数组的0行
与a等价,它指向二维数组的0行
不能把&a[i]理解为a[i]的地址,因为不存在a[i]这个变量。无法取地址,&a[i]和a[i]值是一样的。
只是含义不同,&a[i]或a+i指向行,而a[i]或*(a+i)指向列
只是含义不同,&a[i]或a+i指向行,而a[i]或*(a+i)指向列
指向多维数组元素的指针变量
使用指针变量输出二维数组元素的值
int a[2][2]={1,2,3,4}
int *p;
for(p=a[0];p<a[0]+4;p++)
{if((p-a[0]%2==0)printf(\n");
print("%d",*p);
int *p;
for(p=a[0];p<a[0]+4;p++)
{if((p-a[0]%2==0)printf(\n");
print("%d",*p);
每次p+1使指针指向像一个元素
if语句使输出2个数据后换行
if语句使输出2个数据后换行
如果要指定某个元素输出应该事先计算该元素在数组中的相对位置
a[i][j]的位置为i*m+j,m是二维数组的列数。
a[3][4]的数组 如果开始指针指向a[0][0],为了得到a[2][3]的值,
可以用*(p+2*4+3)表示,p+11是a[2][3]的地址。a[i][j]的地址为
&a[0][0]+4*(i*m+j)
可以用*(p+2*4+3)表示,p+11是a[2][3]的地址。a[i][j]的地址为
&a[0][0]+4*(i*m+j)
a[i][j]之前有i行元素,在a[i][j]之前还有j个元素。因此
在a[i][j]之前共有i*m+j个元素
在a[i][j]之前共有i*m+j个元素
指向由m个元素组成的一维数组的指针变量
使p不是指向整型变量,而是指向一个包含m个元素的一维数组,如果p指向a[0]p=&a[0],
则p+1不是指向a[0][1],而是指向a[1],p的增值以一维数组的长度为单位。
则p+1不是指向a[0][1],而是指向a[1],p的增值以一维数组的长度为单位。
int a[2][2]={1,2,3,4};
int (*p)[2],i,j;
p=a;
scanf("i=%d,j=%d",&i,&j);
printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));
int (*p)[2],i,j;
p=a;
scanf("i=%d,j=%d",&i,&j);
printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));
当输入i=1,j=1时输出a[1,1]=4;
int (*p)[2]表示p是一个指针变量,它指向包含2个整型元素的一维数组。括号不可少,
因为[]运算级别高。p先与[2]结合得到*p[2]就是指针数组。
int a[2] 定义一个有两个元素的整型数组
int(*p)[4] 表示*p有4个元素,每个元素为整型。p所指的对象是由4个整型元素的数组
也就是p是指向一维数组的指针。此时p只能指向一个包含4个元素的一维数组,p的值就是
一维数组的起始地址
因为[]运算级别高。p先与[2]结合得到*p[2]就是指针数组。
int a[2] 定义一个有两个元素的整型数组
int(*p)[4] 表示*p有4个元素,每个元素为整型。p所指的对象是由4个整型元素的数组
也就是p是指向一维数组的指针。此时p只能指向一个包含4个元素的一维数组,p的值就是
一维数组的起始地址
程序中p+i是二维数组a的i行起始地址 *(p+2)是a[2],*(p+2)+3 是a[2]的3列的元素地址
最后*((p+i)+j)就是取数组第i行第j 列的元素
最后*((p+i)+j)就是取数组第i行第j 列的元素
用指向数组的指针作为函数参数
一维数组名可以作为函数参数,多维数组名也可以作为函数参数。再用指针变量作为形参以接受实参数组名传递过来的地址时有两种方法
用指向变量的指针变量
用指向一维数组的指针变量
一个班3个学生,各学4门课。计算总平均分,和第n个学生的成绩
void average(float *p,int n);
void search(float(*p)[4] , int n)
float score[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
average(*score,12);
search(score,2)
void search(float(*p)[4] , int n)
float score[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
average(*score,12);
search(score,2)
字符串与指针
字符串的表示形式
有两种方法可以访问字符串
1用字符数组存放一个字符串,然后输出该字符串
char string[]="i love china!";
printf("%s\n",string);
printf("%s\n",string);
string是数组名代表字符数组首元素地址,string[4]代表数组中第四个元素"v"
*(string+4)就是string[4]。string+4是一个地址。
*(string+4)就是string[4]。string+4是一个地址。
2 用字符指针指向一个字符串
可以不定义字符数组,而定义一个字符指针。用字符指针指向字符串中的字符
char *string="i love china!";
printf("%s\n",string);
printf("%s\n",string);
c中对字符串常量是按字符数组处理的,在内存中开辟一个字符数组用来存放该字符串常量。
对字符指针变量string初始化,实际上是吧字符串第1个元素地址赋值给string。
对字符指针变量string初始化,实际上是吧字符串第1个元素地址赋值给string。
在输出是,要用printf(%s\n",string);
%s是输出字符串时所用的格式符,在输出项中给出字符指针变量名string。
系统会先输出它所指向的第一个字符数据,然后自动使string+1指向下一个字符。
直到遇到结束标志\0为止。
系统会先输出它所指向的第一个字符数据,然后自动使string+1指向下一个字符。
直到遇到结束标志\0为止。
字符指针作为函数参数
将一个字符串从一个函数传递给另外一个函数,可以用地址传递的办法。用字符数组名做参数,也可以用指向字符的指针变量做参数。
在背调用的函数中可以改变字符串的内容。在主调函数中可以得到改变了的字符串
在背调用的函数中可以改变字符串的内容。在主调函数中可以得到改变了的字符串
用函数调用实现字符串赋值
用字符数组做参数
void copy string(char from[],char to[]);
char a[]="i am a teacher"
char b[]= "you are a student";
copy_string(a,b)
函数定义
void copy_string(char from[],char to[])
char a[]="i am a teacher"
char b[]= "you are a student";
copy_string(a,b)
函数定义
void copy_string(char from[],char to[])
a和b是字符数组,copystring函数将from[i]赋值给to[i]。直到from[i]的值等于'\o'
形参用字符指针变量
void copy_string( char from[],char to[]);
char from[]="i am a teacher";
char to []="you are a student"
char *a = from,*b=to;
copy_string(a,b)
函数定义
void copy_string(char* from,char*to)
char from[]="i am a teacher";
char to []="you are a student"
char *a = from,*b=to;
copy_string(a,b)
函数定义
void copy_string(char* from,char*to)
形参from和to是字符指针变量。在调用copy_string是将数组a首元素地址传递给from,
把数组b首元素地址传给to
把数组b首元素地址传给to
对使用字符指针变量和字符数组的讨论
字符数组由若干个元素组成,每个元素放一个字符。字符指针变量存放的是地址。决不是将字符串放到字符指针变量中
赋值方式,对字符数组只能对各个元素赋值,不能用一下办法对字符数组赋值
char str[14];
str="i love china";
char str[14];
str="i love china";
而对于字符指针变量可以采用下面方式赋值
char *a;
a="i love china";
char *a;
a="i love china";
对字符指针变量赋初值
char*a="i love china"等价于
char*a
a="i love china”
char*a
a="i love china”
如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量
给指针变量分配内存单元,在其中可以放一个字符变量的地址。也就是说,该指针变量可以指向
一个字符型数据。但如果未赋予地址值,它并未指向一个确定的字符数据
给指针变量分配内存单元,在其中可以放一个字符变量的地址。也就是说,该指针变量可以指向
一个字符型数据。但如果未赋予地址值,它并未指向一个确定的字符数据
char str[10];
scanf("%s",str);可以
scanf("%s",str);可以
char *a;
scanf("%s",a);不提倡
scanf("%s",a);不提倡
编译时虽然给指针变量a分配了内存单元,但a的值并未指定,就是a存放的是一个
不可预知的地址。在执行scanf将一个字符串输入到a所指向的未知地址中,
会破坏程序,甚至破坏系统。
不可预知的地址。在执行scanf将一个字符串输入到a所指向的未知地址中,
会破坏程序,甚至破坏系统。
提倡 char*a str[10];
a=str;
scanf("%s",a);
a=str;
scanf("%s",a);
指针变量的值是可以改变的
char*a="i love china"
a=a+7;
printf("%s\n",a)
运行结果
china
a=a+7;
printf("%s\n",a)
运行结果
china
可以用带下标的字符指针变量引用字符串中的字符
char*a="i love china"
printf("%c",a[5])
输出 e
printf("%c",a[5])
输出 e
程序虽未定义数组a但字符串在内存中以字符数组存放,a[5]按*(a+5)处理
用指针变量指向一个格式字符串,可以替代printf函数中的格式字符串
char*format;
format="a=%d,b=%f\n";
printf(format,a,b);
相当于 printf(“a=%d,b=%f\n”,a,b)
format="a=%d,b=%f\n";
printf(format,a,b);
相当于 printf(“a=%d,b=%f\n”,a,b)
指向函数的指针
用函数指针变量调用
可以用指针变量指向整型变量,字符串,数组,指向一个函数。函数在编译时分配一个入口地址。
这个函数的入口地址就是函数指针。可以用一个指针变量指向函数。然后通过指针变量调用函数。
这个函数的入口地址就是函数指针。可以用一个指针变量指向函数。然后通过指针变量调用函数。
int max(int ,int);
int(*p)(int,int);
p=max
c=(*p)(a,b);
int(*p)(int,int);
p=max
c=(*p)(a,b);
int(*p)(int,int)用来定义p是一个指向函数的指针变量,该函数有两个整型参数。
函数值为整型。
函数值为整型。
*p的括号不能省略,表示p先与*结合是指针变量,然后再与后面的(参数)结合
表示指针变量指向函数。前面的int表示函数返回值为整型。
表示指针变量指向函数。前面的int表示函数返回值为整型。
赋值语句p=max将函数max入口地址赋值给指针变量p,函数名代表函数的入口地址。
p只能指向函数入口处而不能指向函数中的某一条指令。
p只能指向函数入口处而不能指向函数中的某一条指令。
说明
指向函数的指针变量的一般定义形式
数据类型(*指针变量名)(函数参数表列)
函数调用可以通过函数名调用,也可通过函数指针调
定义了函数指针变量p,可以先后指向同类型的不同函数
给函数指针变量赋值时,只需给出函数名而不必给出参数 p=max;
因为是将函数入口地址赋给p,而不涉及实参形参问题
因为是将函数入口地址赋给p,而不涉及实参形参问题
用函数指针变量调用函数时,只需将(*p)代替函数名即可
对指向函数指针变量,p+n,p++无意义
用指向函数的指针作为函数参数
指向函数指针变量可以作为函数参数,相当于调用函数时传入两个子函数
void sub(int(*x1)(int),int(*x2)(int,int))
为什么不直接用函数名作为实参,而用指针呢?
如果函数只调用f1,f2两个函数,可以用函数名,但如果
下次调用f3,f4那么用指针变量更方便。只要每次调用sub函数时
给出不同函数名做实参即可。
下次调用f3,f4那么用指针变量更方便。只要每次调用sub函数时
给出不同函数名做实参即可。
返回指针值的函数
一个函数可以返回指针类型数据,也就是返回一个地址
定义形式
类型名 *函数名(参数表列)
int *a(int x,int y);
a是函数名,调用后能得到一个指向整型数据的指针。x,y是函数a的形参
在a的两侧,*的优先级低于(),因此a先与()结合。得到a()这是函数形式
在函数前有个*表示此函数时指针型函数。前面的int表示返回指针指向整型变量
在a的两侧,*的优先级低于(),因此a先与()结合。得到a()这是函数形式
在函数前有个*表示此函数时指针型函数。前面的int表示返回指针指向整型变量
指针数组和指向指针的指针
指针数组
一个数组,若其元素均为指针类型数据,称为指针数组。每个元素都是指针变量
类型名 *数组名[数组长度]
int *p[4];
由于[]的优先级比*高,因此p[]结合是数组。
*p[]表示数组是指针类型。
*p[]表示数组是指针类型。
为什么用指针数组,因为比较适合用于指向若干个字符串,使字符串处理更加方便。例如图书馆有若干本书
想把书名放在一个数组中,然后对这些书目进行排序查询。按一般的方法,字符串本身就是一维数组。做成集合需要用二维数组。
但定义二维数组是,需要制定雷叔,也就是每本书名(字符串的长度)。对于长度不一的书名来说会造成内存浪费
想把书名放在一个数组中,然后对这些书目进行排序查询。按一般的方法,字符串本身就是一维数组。做成集合需要用二维数组。
但定义二维数组是,需要制定雷叔,也就是每本书名(字符串的长度)。对于长度不一的书名来说会造成内存浪费
可以分别定义一些字符串,然后用指针数组中的元素指向各个字符串。如果想对字符串排序,不必改动字符串位置
,只需改动指针数组中个元素的指向。且移动指针比移动字符串所花的时间少的多。
,只需改动指针数组中个元素的指向。且移动指针比移动字符串所花的时间少的多。
指向指针的指针
char *name[]={"follow me","basic","great wall","fortran",computer"};这是一个指针数组,name代表指针数组的首元素地址
,name+1是name[1]的地址。地址的内容是指针。可以设置一个指针变量p,使它指向指针数组元素。
,name+1是name[1]的地址。地址的内容是指针。可以设置一个指针变量p,使它指向指针数组元素。
定义指向指针的指针变量
char **p;由于*的结合性从右到左,因此**p相当于*(*p),*p是指针变量定义
前面再加*表示指针变量p表示指针变量p是指向一个字符指针变量。
前面再加*表示指针变量p表示指针变量p是指向一个字符指针变量。
指针数组作为main函数的形参
指针数组的一个重要应用是作为main的形参。实际上main()是可以带参数的
void main(int argc, char *argv[])
argc 和argv是函数的形参。由于main是被系统调用
那么不可能在程序中获得参数。实际上实参和命令一起给出
也就是在命令行中包括命令名和需要传递给main函数的参数
那么不可能在程序中获得参数。实际上实参和命令一起给出
也就是在命令行中包括命令名和需要传递给main函数的参数
命令行的一般形式
命令名 参数1 参数2...参数n
命令名和个参数之间用空格分隔,命令名是main所在执行文件名。假设文件名为flie1,参数是字符串 China,Beijing
命令行可以写成 file1 China Beijing
命令行可以写成 file1 China Beijing
注意以上参数与main函数中形参的关系。在main函数中argc代表命令行中的参数个数。文件名也算参数。
现在argc等于3,main中argv是一个指向字符串的指针数组,因此argv[0]指向file1首地址,argv[1]指向China首地址
现在argc等于3,main中argv是一个指向字符串的指针数组,因此argv[0]指向file1首地址,argv[1]指向China首地址
利用指针数组做main形参,可以向程序传送命令行参数。这些字符串长度事先不知道。而且个参数字符串长度一般并不相同。命令行参数书目也可以是任意的,指针数组能满足上述要求
指针类型小结
有关指针的数据类型
int i;
定义整型变量
int*p;
p 为指向整型数据的指针变量
int a[n];
定义整型数组a,有n个元素
int *p[n]
定义指针数组p,元素是指向整型数据的指针
int (*p)[n]
p为指向含n个元素的一维数组的指针变量
int f()
f函数返回整型数据
int *p()
p 为返回一个指针的函数,该指针指向整型数据
int (*p)()
p为指向函数的指针,所指的函数返回整型数据
int**p
p是一个指向指针的指针变量,所指的指针指向整型数据
指针运算小结
指针变量加减
将指针变量的原值(地址)和指向变量所占用内存单元字节数相加,p+i表示 p+c*i .c代表字节数
指针变量赋值
将一个变量地址付给指针变量
p=&a;
p=数组名
p=&a[i]
p=函数名
p=指针
空指针
p=null,使指针变量不指向任何变量。
任何指针都可以与null比较 判断是否有意义
任何指针都可以与null比较 判断是否有意义
if(p==null)
两个指针变量相减
如果两个指针变量都指向同一个数组中的元素,那么两个指针变量之差是两个指针之间的元素个数
void指针类型
定义一个指针变量,但不指定它是指向哪一种类型数据。它可以用来指向一个抽象类型数据,在将他的值赋给另一指针变量是要进行强制类型转换
char *p1
void*p2
p1=(char*)p2
void*p2
p1=(char*)p2
结构体与共用体
概述
构造类型数据-数组,每种元素相同
但有时候需要将不同类型数据组合成一个有机整体,以便引用,这些组合的整体之间有相互联系。
一个学生的学号,姓名,性别,年龄,成绩等信息。这种包含不同数据类型的组合项称为结构体
一个学生的学号,姓名,性别,年龄,成绩等信息。这种包含不同数据类型的组合项称为结构体
声明一个结构体类型的一般形式
struct 结构体名
{成员表列}
struct 结构体名
{成员表列}
struct student
{
int number;
char name【20】;
char sed;
int age;
float score;
};
{
int number;
char name【20】;
char sed;
int age;
float score;
};
不要忽略最后的分号
定义结构体类型变量的方法
前面定义了一个结构体类型,相当于模型,其中无数据,系统不对其分配实际内存。
应当定义结构体类型变量,并在其中存放具体数据,可以有3中方法
应当定义结构体类型变量,并在其中存放具体数据,可以有3中方法
1 先声明结构体类型在定义变量
struct student student1,student2;
结构体类型名 结构体变量名
定义了student1和student2为struct student 类型的变量。他们具有structstudent类型结构,
系统会为它们分配内存。
系统会为它们分配内存。
如果程序规模大,往往将对结构体类型的声明集中放到一个头文件.h中,哪个源文件需要就用#include包含
2 声明类型的同时定义变量
struct student{int number;char name【20】;char sed;int age;float score;}student1,student2;
把变量加到定义结构体后面
3 直接定义结构体变量
struct
{
成员表列
}变量名表列;
{
成员表列
}变量名表列;
不出现结构体名
说明
类型与变量是不同概念,类型不分配空间,也不能运算赋值。变量可以
结构体成员,可以单独使用,它的作用与地位相当于普通变量
成员可以是一个结构体变量
struct date{int month;int day; int year;};
struct student{ int num;char name[20];char sex;struct data birthday;}
birthday 是结构体变量做成员
struct student{ int num;char name[20];char sex;struct data birthday;}
birthday 是结构体变量做成员
成员名可以与程序中的变量名相同,二者代表不同对象
结构体变量的引用
引用结构体变量规则
不能将一个结构体变量作为一个整体进行输入输出
只能对结构体变量中的成员输入输出。
只能对结构体变量中的成员输入输出。
printf(“%d,%s,%c,%d,%f、n”,student1);不合法
引用结构体变量中成员的方式
结构体变量名.成员名
student1.num=10010;表示变量中的num学号可以赋值
结构体变量名.成员名
student1.num=10010;表示变量中的num学号可以赋值
.是成员运算符,它在所有运算符中优先级最高,因此把student1.num作为一个整体看待
如果成员本身是一个结构体类型,可以用若干个成员运算符
一级一级查找最低成员
一级一级查找最低成员
student1.birthday.month
对结构体变量的成员可以像普通变量一样进行各种运算
sum=student1.score+student2.score;
可以引用结构体变量成员的地址,也可以引用结构体变量的地址
printf(“%o”,&student1);
结构体变量的初始化
和其他类型变量一样,结构体变量定义时可以指定初始值
结构体数组
定义数组元素为结构体类型的数组
struct student{int number;char name【20】;char sed;int age;float score;};
struct student stu[3];
struct student stu[3];
定义了一个数组stu,数组有3个元素,均为struct student 类型
结构体数组的初始化
struct student{int number;char name【20】;char sed;int age;float score;};
struct student stu[3]={{10101,“li lin”,"m",18,87}{"10102","zhang fun","m",19,99}{...}};
struct student stu[3]={{10101,“li lin”,"m",18,87}{"10102","zhang fun","m",19,99}{...}};
指向结构体类型数据的指针
一个结构体变量的起始地址,用一个指针变量指向它。指针变量也可以用来指向结构体数组中的元素
指向结构体变量的指针
struct student
{long num;
char name[20];
char sex;
float score;}
struct student stu_1;
struct student*p;
p=&stu_1;
printf("NO:%ld\n",(*p).num)
{long num;
char name[20];
char sex;
float score;}
struct student stu_1;
struct student*p;
p=&stu_1;
printf("NO:%ld\n",(*p).num)
首先定义一个struct student 类型的结构体变量stu_1.
再定义一个指向struct student类型的指针变量p,将stu_1
的地址赋值给p。(*p).num是p指向结构体stu_1的成员num
再定义一个指向struct student类型的指针变量p,将stu_1
的地址赋值给p。(*p).num是p指向结构体stu_1的成员num
以下三种访问结构体成员形式等价
结构体变量.成员名
(*p).成员名
p->成员名
常用于替代(*p).成员名
指向结构体数组元素的指针
struct student{int number;char name【20】;char sed;int age;float score;};
struct student stu[3]={{10101,“li lin”,"m",18,87}{"10102","zhang fun","m",19,99}{...}};
struct student *p;
for(p=stu;p<stu+3;p++)
printf("%5d%-20s%2c%4d\n",p->num,p->name.p->sex,p->age);
struct student stu[3]={{10101,“li lin”,"m",18,87}{"10102","zhang fun","m",19,99}{...}};
struct student *p;
for(p=stu;p<stu+3;p++)
printf("%5d%-20s%2c%4d\n",p->num,p->name.p->sex,p->age);
定义指向struct student 类型的指针p,
在for语句中使p的初值为结构体数组stu
那么p++将指向stu数组的下一行第一个元素
在for语句中使p的初值为结构体数组stu
那么p++将指向stu数组的下一行第一个元素
指向结构体数组元素的指针不能用于指向数组元素的成员
p=stu[1].num不合法
但可以用强制类型转换,就可以将成员地址转换成p类型
p=(strut student*)stu[0].name
现在p的值是stu[0].name的地址。p的类型不变
p=stu[1].num不合法
但可以用强制类型转换,就可以将成员地址转换成p类型
p=(strut student*)stu[0].name
现在p的值是stu[0].name的地址。p的类型不变
用结构体变量和指向结构体的指针作函数参数
将一个结构体变量的值传递给函数由3中方法
1 用结构体变量的成员作参数
如stu[1].num作函数实参,将实参传递给形参
2用结构体变量作实参
将结构体变量所有内容全部按顺序传递给形参,形参必须是同类型的结构体变量。这种传递方式在空间和时间上开销较大。
使用较少
使用较少
void print(struct student);
struct student stu;定义结构体变量
print(stu);
定义函数
print(struct student stu)
struct student stu;定义结构体变量
print(stu);
定义函数
print(struct student stu)
3 用指向结构体变量的指针作实参。将结构体变量的值传形参
void print(struct student*);
print(&stu);
定义函数
void print(struct student *P)
print(&stu);
定义函数
void print(struct student *P)
用指针处理链表
链表概述
链表是一种常见的重要数据结构,动态地进行存储分配的一种结构。用数组放数据是,必须事先定义数组长度。
如果A班有100人,B班有30人。如果要用同一个数组存放不同班级的学生数据,必须定义长度为100的数组。如果事先无法
确定班级认识,则数必须定的足够大。这显然会造成内存浪费。
如果A班有100人,B班有30人。如果要用同一个数组存放不同班级的学生数据,必须定义长度为100的数组。如果事先无法
确定班级认识,则数必须定的足够大。这显然会造成内存浪费。
链表是根据需要开辟内存单元
head-A-B-C-D
head-A-B-C-D
链表有一个头指针变量head,它存放一个地址,该地址指向一个元素。也称节点
。每个结点都应包括两部分:用户需要用的数据和下一个结点的地址。因此从head
一直往下指直到最后一个元素。该元素不再指向其他元素,称为“表尾”地址部分
放一个NULL,空地址表示链表到此结束。
。每个结点都应包括两部分:用户需要用的数据和下一个结点的地址。因此从head
一直往下指直到最后一个元素。该元素不再指向其他元素,称为“表尾”地址部分
放一个NULL,空地址表示链表到此结束。
链表中各元素在内存中可以是不连续的。要找到某一元素,必须先找到上一个元素,如果没有head
整个链表都无法访问。一个结点应包含一个指针变量,用来存放下一个结点的地址。
整个链表都无法访问。一个结点应包含一个指针变量,用来存放下一个结点的地址。
结构体变量作为链表中的结点是最合适的。一个结构体变量可以包含多种数据。
struct student
{
int num;
float score;
struct student*next;
}
struct student
{
int num;
float score;
struct student*next;
}
结构体中num,score用来存放用户数据。next是指向结构体student类型指针
用来指向下一个成员。每个结点都属于struct student类型。它的成员存放下一个结点。
用来指向下一个成员。每个结点都属于struct student类型。它的成员存放下一个结点。
简单链表
定义链表结构体:struct student{long num; float score;struct student*next;}
定义结构体变量和指针: struct student a,b,c*head,*p;
把要保存的数据给链表元素 a.num=10101;a.score=89.5;b.num=10103;b.score=90;c.num=10107;c.score=85;
给链表指针赋值: head=&a;a.next=&b;b.next=&c;c.next=NULL;
p=head;
do
{ printf("%ld%5.1f\n",p->num,p->score);输出链表元素的成员。
p=p->next;}while(P!=NULL)使指针变量指向下一个,判断NULL停止
定义结构体变量和指针: struct student a,b,c*head,*p;
把要保存的数据给链表元素 a.num=10101;a.score=89.5;b.num=10103;b.score=90;c.num=10107;c.score=85;
给链表指针赋值: head=&a;a.next=&b;b.next=&c;c.next=NULL;
p=head;
do
{ printf("%ld%5.1f\n",p->num,p->score);输出链表元素的成员。
p=p->next;}while(P!=NULL)使指针变量指向下一个,判断NULL停止
所有结点在程序中定义,不是临时开辟,用完也不能释放
称为静态链表
称为静态链表
处理动态链表所需的函数
链表结构动态分配存储,在需要时才开辟下一个结点
malloc函数
函数原型 void*malloc(unsigned int size);
在内存的动态存储区分配一个长度为size的连续空间。函数的返回值是一个分配与的起始地址,如函数未成功执行返回NULL
calloc函数
函数原型:void*calloc(unsigned n,unsigned size);
在内存的动态存储区分配n个长度为size的连续空间。函数返回一个指向分配域起始位置的指针。不成功返回NULL
calloc 函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。
calloc 函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。
free函数
函数原型:void free(void *P);
释放由p指向的动态存储区,使这部分内存区能被其他变量使用。p是最近一次调用calloc和malloc 的返回值
free函数无返回值。
free函数无返回值。
建立动态链表
在程序执行过程中从无到有地建立起一个链表。一个一个地开辟结点和输入各结点数据,并建立前后联系
定义链表结构体:struct student{long num; float score;struct student*next;}
定义一个带回链表头的指针的函数
struct student*creat(void)
{定义头部指针struct student*head;
定义结构体指针p1p2,struct student*p1,*p2;
p1p2都指向一个开辟单元,p1=p2=(struct student*)malloc(LEN);
输入数据scanf(“%ld,%f”,&p1->num,&p1->score);
头指针赋值空 head=NULL;
只有输入数据不为空时,传入有效地址
while(p1->num!=0)
{n=n+1;if(n==1)head=p1;else p2->next=p1;}
p1赋值给p2,p1指向新开的存储区 p2=p1;p1=(struct student*)malloc(LEN);
检测下一个输入scanf("%d,%f",&p1->num.&p1->score);
}
如果检测到输入的学号为0,结束链表 p2->next=NULL;
返回头部指针
}
在main中调用create函数:
void main()
{create()}
定义一个带回链表头的指针的函数
struct student*creat(void)
{定义头部指针struct student*head;
定义结构体指针p1p2,struct student*p1,*p2;
p1p2都指向一个开辟单元,p1=p2=(struct student*)malloc(LEN);
输入数据scanf(“%ld,%f”,&p1->num,&p1->score);
头指针赋值空 head=NULL;
只有输入数据不为空时,传入有效地址
while(p1->num!=0)
{n=n+1;if(n==1)head=p1;else p2->next=p1;}
p1赋值给p2,p1指向新开的存储区 p2=p1;p1=(struct student*)malloc(LEN);
检测下一个输入scanf("%d,%f",&p1->num.&p1->score);
}
如果检测到输入的学号为0,结束链表 p2->next=NULL;
返回头部指针
}
在main中调用create函数:
void main()
{create()}
调用create函数,函数的返回值是缩减链表的第一个结点地址
#define LEN sizeof(struct student) sizeof 是求字节数运算符
LEN代表struct student类型数据的长度
LEN代表struct student类型数据的长度
malloc(LEN)的作用是开辟一个长度为LEN的内存区,因为LEN等于结构体student的长度
所以开辟了一个新的student的长度。因为malloc返回的指针不指向任何类型。因此赋值给p1,p2
需要对其进行强制类型转换(struct student*)转换成指向结构体student类型的指针。
所以开辟了一个新的student的长度。因为malloc返回的指针不指向任何类型。因此赋值给p1,p2
需要对其进行强制类型转换(struct student*)转换成指向结构体student类型的指针。
这个算法思路是让p1指向新开辟的结点,p2指向链表中最后一个结点。然后把p2所值得next连接到p1
p2->next=p1
p2->next=p1
输出链表
首先要知道链表第一个结点地址,也就是head的值,然后设一个指针变量p,先指向第一个结点地址。输出p的内容后
使p后移一个结点,在输出,知道链表的尾结点
使p后移一个结点,在输出,知道链表的尾结点
定义链表输出函数
void print(struct student*head)
{struct student*p
printf(“\nNOW,These %d records are:\n”,n)
p=head;
if(head!=NULL)
do
{printf("%d%5.1f\n",p->num,p->score);
p=p->next;}while(P!=NULL);
}
void print(struct student*head)
{struct student*p
printf(“\nNOW,These %d records are:\n”,n)
p=head;
if(head!=NULL)
do
{printf("%d%5.1f\n",p->num,p->score);
p=p->next;}while(P!=NULL);
}
p先指向第一个结点head,输出完后,p=p->next.把p当前指向的结构体next地址给p
p就指向第二个地址了
p就指向第二个地址了
对链表的删除操作
从动态链表中删除一个结点,并不是真正从内存中抹去,而是把它从链表中分离处理,只要撤销原来的链接关系即可
写一个函数用于删除动态链表中指定的结点
思路:加入以学号作为删除结点的标志。从p指向的第一个结点开始,检查该结点中num值是否等于输入的要求删除的那个学号
如果相等就删除结点,如果不相等p后移一个结点继续判断。设置两个指针变量p1p2,先使p1指向第一个结点。第一种情况
要删除head那个结点,那么把p1指向的结点赋值给head就可以了。如果不是第一个结点,则将p1->next=p2->next.把要删除的
结点的next传给前一个的next
如果相等就删除结点,如果不相等p后移一个结点继续判断。设置两个指针变量p1p2,先使p1指向第一个结点。第一种情况
要删除head那个结点,那么把p1指向的结点赋值给head就可以了。如果不是第一个结点,则将p1->next=p2->next.把要删除的
结点的next传给前一个的next
对链表的插入操作
将一个结点插入到一个已有的链表中
加入学生链表是按照学号大小顺序排列。必须解决两个问题
1在什么位置插入
p1指向待插入结点,p2指向第一个结点
p1->num与p2->num比较。如果p1->num大于p2->num那么不在这插入
p2指向下一个结点继续比较。直到p1->num小于p2->num。找到插入点
p1->num与p2->num比较。如果p1->num大于p2->num那么不在这插入
p2指向下一个结点继续比较。直到p1->num小于p2->num。找到插入点
2怎么实现插入
如果插入点不在第一个结点,那么将p1赋值给p2之前的next,将p2赋值给p1->next
如果在第一个结点,将p1赋值给head,p1的next=p2
如果在第一个结点,将p1赋值给head,p1的next=p2
共用体
共用体概念
几个变量互相覆盖,使几个不同变量共占同一段内存的结构
一般形式
union 共用体名
{成员表列}表列表列;
{成员表列}表列表列;
union data
{int i;
char ch;
float f;}a,b,c
{int i;
char ch;
float f;}a,b,c
也可将类型声明与变量定义分开
union data{int i;char ch;float f;}
union data a,b,c;
union data{int i;char ch;float f;}
union data a,b,c;
共用体与结构体定义相似,但含义不同
结构体变量所占内存长度是各成员内存长度之和。每个成员分别占有自己的内存单元
共用体所占内存长度等于最长的成员长度。上面定义的成员float最长
因此共用体变量a,b,c各占4个字节。
因此共用体变量a,b,c各占4个字节。
共用体变量的引用方式
不能引用共用体变量,只能引用共用体变量中的成员。
a.i,a.ch
共用体类型数据的特点
同一个内存段,可以用来存放集中不同类型的成员。但一瞬间只能存放其中一种,也就是只有一个成员有效
共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。
a.i=1;
a.c='a'l
最后只有a.c有效
a.c='a'l
最后只有a.c有效
共用体变量的地址和它的各个成员地址都是同一地址
&a&a.i,&a.c...
不能对共用体变量名赋值,也不能企图引用变量名来的到一个值
a=1不合法
不能把共用体变量作为函数参数,也不能使用函数带回共用体变量,但可以使用指向共用体变量的指针。也可以作为结构体成员
枚举类型
变量的值一一列举处理,值只限于列举出的范围,声明枚举类型用enum
enum weekday{sun,mon,tue,wed,thu,fri,sat};
定义一个枚举类型变量
enum weekday workday,weekend;
workday ,weekend 是枚举变量
说明
枚举元素按常理处理,不能赋值,sun=0不合法
枚举元素作为常量是有值得,系统按定义顺序从0,1,2.分配
workday=mon;
printf("%d",workday);
得到结果为1
printf("%d",workday);
得到结果为1
枚举值可以做比较判断 workday=mon >sun
一个整数不能直接赋值给枚举变量,因为他们不是同种类型。可以用强制转换 workday=(enum weekday)2;
用typedef 命名已有类型
可用typedef 定义自己想要的数据类型
typedef int INTERGER;
定义变量
INTERGER i,j;等价于int i,j;
定义变量
INTERGER i,j;等价于int i,j;
指定用INTERGER 代表int类型
可以声明结构体类型
typedef struct
{
int month;
int day;
int year;
}DATE;
{
int month;
int day;
int year;
}DATE;
定义了新类型DATE ,可以用DATE定义变量 DATE birthday;
声明一个新类型名的方法
先按定义变量的方法写出定义体 如 int i;
以数组为例 int n[100];
将变量名换成新类型名
int i; 换成 COUNT
int NUM[100]
在最前面加typedef
typedef int COUNT;
typedef int NUM[100]
然后用新的类型名去定义变量
COUNT a,b;
NUM n;
习惯把新类型名用大写字母表示
说明
typedef 声明各种类型,但不能用来定义变量
typedef int a,b 不合法
用typedef 只是对已经存在的类型指定一个新的类型名,而没有创造新的类型
COUNT 和int等价换了个名字而已
typedef 和#define 有相识之处,但不同在于define只做预处理简单替换,typedef是声明类型
不同源文件中用同一类型数据,常用typedef声明一些数据类型,把他们单独放在一个文件中,然后再需要时用#include包含
使用typedef有利于程序通用与移植。因为有的电脑int 是2个字节,有的电脑是4个字节 需要将int改为long
那么如果有type int INTERGER ,只要在定义文件中改typedef long INTERGER ,一改全改
那么如果有type int INTERGER ,只要在定义文件中改typedef long INTERGER ,一改全改
位运算
位运算符合位运算
&按位与,|按位或,^按位异或
~取反,<<左移,>>右移
除~其他均为二目运算符,需要两个都有运算量
运算量只能是整型或字符型数据,不能为实行数据
按位与&
参加运算的两个数据,按二进制进行与运算。两个二进制都为1则改值为1,否则为0
负数以补码形式表示二进制参加运算
负数以补码形式表示二进制参加运算
特殊用途
清零,想要将一个单元清零,找一个二进制数相与为0
取一个数中某些指定位,如果要低4位可以将高4位清零
按位或|
按位异或^ XOR
两个二进制同号结果为0,异号为1 0^0=0,0^1=1
用途
使特定位翻转 ,假设有1010,需要翻转可以与1111进行异或运算
结果为0101
结果为0101
与0异或,保留原值
交换两个值,不用临时变量
a=a^b;
b=b^a;
a=a^b;
b=b^a;
a=a^b;
取反运算符~
一目运算符 把1变0,0变1
左移运算符<<
用来将一个数的各二进位全部左移若干位,右边补0
右移运算符>>
将a的各二进制位右移2位,移到右端低位舍弃。对无符号位高位补0。对有符号位有的计算机保留符号位,有的直接补0
位运算赋值运算符
位运算符与赋值运算符可以组成复合运算符 &=,|=,>>=,<<=,^=
a&=b 等价于a=a&b
不同长度的数据进行位运算
如果两个数据长度不同的数据进行位运算如long和int。系统会将二者右端对齐。如a&b,如果b为正数,
则左侧16位补满0,若b 为负数,左端补满1.如果b为无符号整数型,左侧填满0
则左侧16位补满0,若b 为负数,左端补满1.如果b为无符号整数型,左侧填满0
位段
有时存储一个信息不必用一个或多个字节。如I/O口输出。在计算机用于过程控制,参数检测,数据通信
控制信息往往只占一个字节中的一个或几个二进制位。
控制信息往往只占一个字节中的一个或几个二进制位。
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”,“位域”
利用位段能够用较少的位数存储数据
利用位段能够用较少的位数存储数据
struct packed_data
{unsigned a:2;
unsigned b:6;
unsigned c:4
unsigned d:4;
int i;} data;
{unsigned a:2;
unsigned b:6;
unsigned c:4
unsigned d:4;
int i;} data;
其中a,b,c,d 分别占2位,6位,4位,4位,i为整型共占4个byte
也可以使各位段不恰好占满一个字节
也可以使各位段不恰好占满一个字节
对位段中数据引用的方法
data.a=2;data.b=7,data.c=9;注意位段允许的最大范围
如写成data.a=8,因为a只占2位,最大值为3,超出最大值
自动赋予它的数的低位。如8的二进制1000,取低2位为00.所有a=0
如写成data.a=8,因为a只占2位,最大值为3,超出最大值
自动赋予它的数的低位。如8的二进制1000,取低2位为00.所有a=0
关于位段的定义和引用说明
位段成员必须指定位unsigned或int类型
若某一位段要从另一个字开始存放可以如下定义
unsigned a:1;
unsigned b:2;
unsigned :0;
unsigned c:3;
unsigned a:1;
unsigned b:2;
unsigned :0;
unsigned c:3;
unsigned :0定义了一个长度为0的位段,作用是使下一个位段
从下一个存储单元开始存放。
从下一个存储单元开始存放。
一个位段必须存储在同一个存储单元中,不能跨越两个单元
可以定义无名位段
unsigned :2(空着不用)
位段长度不能大于存储单元长度,也不能定义位段数组
位段可以用整型格式输出printf(“%d,%d,%d”,data.a,data.b,data.c)也可以用八进制十六进制输出
位段可以在数值表达式中引用,它会被系统自动转换为整型数据
data.a+5/data.b
文件
C文件概述
文件一般指存储在外部介质上数据的集合
C语言把文件看作是一个字符的序列,字符按顺序排列。数据的组织形式分为ASCII码和二进制文件。
ASCII文件称文本文件。
ASCII文件称文本文件。
C 语言中文件是一个字节流或二进制流。把数据看作是一连串字符。输入输出的数据流开始和结束
仅受程序控制而不受物理符号如回车,换行控制。在输出是不会以回车换行符作为记录结束标志。
仅受程序控制而不受物理符号如回车,换行控制。在输出是不会以回车换行符作为记录结束标志。
过去的c版本对文件有两种处理方法
缓冲文件系统
系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。
从内存向磁盘输出数据必须先送到内存中的缓冲区。缓冲区装满
后才一起送到磁盘区。如果从磁盘向内存读入数据。一次从磁盘文件
将一批数据输入到内存缓冲区,然后再从缓冲区逐个地将数据送到程序
数据区。
从内存向磁盘输出数据必须先送到内存中的缓冲区。缓冲区装满
后才一起送到磁盘区。如果从磁盘向内存读入数据。一次从磁盘文件
将一批数据输入到内存缓冲区,然后再从缓冲区逐个地将数据送到程序
数据区。
非缓冲文件系统
系统不自动开辟确定大小的缓冲区,有程序为每个文件设定缓冲区
在UNIX系统下用缓冲文件系统处理文本文件,用非缓冲文件系统处理二进制文件,ANSI C不采用非缓冲文件系统。
文件类型指针
缓冲文件系统关键概念文件指针,每个被使用的文件都在内存中开辟一个区,用来存放文件的有关信息(文件名,文件状态,文件位置)
这些信息是保存在一个结构体变量中。结构体类型是由系统定义的,取名FILE
这些信息是保存在一个结构体变量中。结构体类型是由系统定义的,取名FILE
可以用FILE定义若干个变量,用于存放文件信息
FILE[5],定义了一个结构体数组f,它有5个元素。可以用来存放5个文件信息。
可以定义文件型指针变量
FILE *fp
指向FILE类型结构体变量的指针变量,通过该指针变量能够找到与它相关的文件。
如果有n个文件,定义n个变量指向文件,实现对文件的访问
如果有n个文件,定义n个变量指向文件,实现对文件的访问
文件的打开和关闭
对文件读写之前应该打开文件,使用结束后关闭文件
文件的打开fopen函数
ANSI C 规定了标准库,用fopen函数打开文件。调用方式
FILE *fp;
fp=fopen(文件名,使用文件方式);
FILE *fp;
fp=fopen(文件名,使用文件方式);
fp=fopen("a1","r");表示要打开文件名为a1的文件。使用文件的方式为“读取read”
函数返回指向a1文件的指针并赋值给fp,这样fp就和文件a1关联了。
函数返回指向a1文件的指针并赋值给fp,这样fp就和文件a1关联了。
使用文件方式
r 只读
为输入打开一个文本文件
w只写
为输出打开一个文本文件
a 追加
向文本文件尾添加数据
rb只读
为输入打开一个二进制文件
wb只写
为输出打开一个二进制文件
ab追加
向二进制文件尾添加数据
r+读写
为读写打开一个文本文件
w+读写
为读写建立一个新的文本文件
a+读写
为读写打开一个文本文件
rb+读写
为读写打开一个二进制文件
wb+读写
为读写新建一个二进制文件
ab+读写
为读写打开一个二进制文件
说明
用r只能输入计算机,不能向文件输出数据。而且文件必须已经存在
用w方式打开的文件只能用于向该文件写数据,不能向计算机输入。如果原来不存在,则打开是新建
一个以指定名字命名的文件。如果原先存在一个以该文件命名的文件,则打开是将文件删除,重新建立一个新文件
一个以指定名字命名的文件。如果原先存在一个以该文件命名的文件,则打开是将文件删除,重新建立一个新文件
如果希望向文件末尾添加新的数据,不删除原有数据。应该用a方式打开。但如果文件不存在则出错。打开后
指针移到文件尾
指针移到文件尾
可以fopen函数带回的空指针NULL判断打开任务是否出错
if (fp=fopen("file1","r")==NULL)
{printf("cannot open this file\n");
exit(0);
} exit 函数的作用是关闭所有文件,终止正转执行的程序。
if (fp=fopen("file1","r")==NULL)
{printf("cannot open this file\n");
exit(0);
} exit 函数的作用是关闭所有文件,终止正转执行的程序。
文件的关闭fclose函数
关闭是使文件指针变量不再指向该文件,文件指针变量与文件脱钩。
fclose(文件指针)
fclose(fp)
当顺利执行完关闭操作返回0,否则返回EOF(-1)
文件的读写
fputc函数
把一个字符写到磁盘文件中去
fputc(ch,fp)
ch要输出的字符常量,也可以是字符变量。fp是文件指针变量
当顺利执行完关闭操作返回0,否则返回EOF(-1)
当顺利执行完关闭操作返回0,否则返回EOF(-1)
fgetc函数
从指定文件读入一个字符
文件必须是以读或读写方式打开
文件必须是以读或读写方式打开
ch=fgetc(fc)
fp为文件型指针变量,ch为字符变量。fgetc函数带回一个字符
赋值给ch。当顺利执行完关闭操作返回0,否则返回EOF(-1)
赋值给ch。当顺利执行完关闭操作返回0,否则返回EOF(-1)
注意:ASCII码不可能出现-1,当读入字符等于-1时,表示读入的
已不是正常的字符,而是文件结束符。但对于二进制文件是可能出现-1的。
这时有用数据被当做文件结束。为了解决这个问题,ANSI C提供feof函数来判断
文件是否真的结束。 如 feof(fp) 的值为1,文件结束,否则为0
已不是正常的字符,而是文件结束符。但对于二进制文件是可能出现-1的。
这时有用数据被当做文件结束。为了解决这个问题,ANSI C提供feof函数来判断
文件是否真的结束。 如 feof(fp) 的值为1,文件结束,否则为0
while(!feof(fp))
{c=fgetc(fp);...}
{c=fgetc(fp);...}
判断文件是否结束
fread 函数和fwrite函数
用于一次读写一组数据
fread(buffer,size,count,fp)
buffer是指针,数据存放地址。size 要读写的字节数。
要进行读写多少个size字节的数据项。fp文件型指针
要进行读写多少个size字节的数据项。fp文件型指针
fprintf和fscanf函数
从磁盘读写数据
fprintf(文件指针,格式字符串,输出表列)
fscanf(文件指针,格式字符串,输入表列)
fprintf(fp,"%d,%6.2f",i,t)作用是将整型变量i 和实型变量t的值按%d和%6.2f的格式输出到fp指向的文件上
3,4.50
fscanf(fp,"%d,%f",&i,&t)将文件中数据3赋值给i,将4.5赋值给变量t
putw和getw
用来对磁盘文件读写一个字
fgets和fputs
fgets(str,n,fp);
从指定文件读入一个字符串
n为要求得到的字符个数,但只从fp指向的文件输入n-1个字符,然后再最后加\0字符。
一共n个字符放到str数组中。fgets返回str的首字符地址
一共n个字符放到str数组中。fgets返回str的首字符地址
fputs(“China”,fp)
fputs向指定文件输出一个字符串。
把字符串China输出到fp指向的文件
文件的定位
文件中有一个位置指针,指向当前读写的位置。每次读写一个字符,读写完成后,该位置指针自动移动指向下一个字符位置
如果想改变这个规律。强制使位置指针指向其他指定位置
如果想改变这个规律。强制使位置指针指向其他指定位置
rewind函数
使位置指针重新返回文件的开头,次函数没有返回值
rewind(fp)
fseek 函数和随机读写
流式文件可以顺序读写,也可以随机读写。关键在于控制文件的位置指针。将位置指针
子主题
0 条评论
下一页