导图社区 C语言详解
还在为C语言而发愁吗?那就快快收藏这张图吧!时至今日, C语言仍然是计算机领域的通用语言之一,学好C语言,再学其他的语言就简单了,正所谓C乃万能语言之基础,套路都差不多。
编辑于2019-12-06 01:29:55C 语言详解
一、概述
计算机发展
世界上第一台现代意义上的计算机于1946年在美国问世,其名称是ENIAC电子数字积分计算机,它的主要原件是电子管。在ENIAC问世的同时,冯·诺伊曼提出了存储程序的概念。
计算机语言
世界上第一个高级语言是FORTRAN(??应该是Basic),后来陆续出现很多种高级语言,目前广泛应用的高级语言有C/C++、Java和C#等。
高级语言
翻译程序通常有两种工作方式,即编译方式和解释方式。编译方式是将源程序全部翻译为二进制程序后再执行,再此完成翻译工作的程序称为“编译程序”,编译后的二进制 程序称为“目标程序”;解释方式是翻译一句执行一句,边解释边执行,在此完成翻译工作的程序称为“解释程序”。
源程序
源程序是指用___高级语言____编写的程序,它必须翻译___目标程序____才能够执行
使用高级语言编写的程序称为“源程序”,源程序无法在计算机上直接运行,必须将其翻译为二进制程序后才能执行
汇编程序
汇编程序将___汇编语言___编写的程序转换为目标程序
在汇编阶段,汇编程序对汇编语言源程序进行汇编,生成一个可重定位的目标文件
算法及其描述方法
解决问题的步骤序列就是算法
1、可执行性 2、确定性 3、有穷性 4、有输入信息的说明 5、有输出信息的步骤
算法描述方法
1、自然语言
用自然语言描述算法通俗易懂,但文字冗长,容易出现“歧义”。
2、传统流程图
流程图是用一些图形表示各种操作。用图形描述算法形象直观、易于理解
3、N-S流程图
三种基本结构:顺序结构、选择结构、循环结构。
顺序结构
选择结构
二分支选择结构
多分支选择结构
循环结构
当型循环
直到型循环
4、伪代码
伪代码是用介于自然语言和计算机语言之间的文字和符号描述算法
5、计算机语言
计算机是无法识别流程图和伪代码的。只有用计算机语言编写的程序才能被计算机执行(当然还要编译为目标程序才能被计算机识别和执行)。
程序和程序设计方法
计算机程序(简称程序)是指根据算法描述,用计算机语言表示的能被计算机识别和执行的指令集合。
程序
程序是指一组计算机能够识别和执行的指令集合
第一个程序This is a c program .
结构化程序设计方法的优点
1、采用自顶向下,逐步求精的方法使得整个设计方案层次分明,程序员容易编码实现,读者容易阅读理解。对于复杂的程序,可以“先易后难,先抽象后具体”,使得程序设计工作整体思路清楚,目标明确,程序员能够有条不絮地推进。
2、程序由相互独立的模块构成,因此在设计某个模块时,不会受到其他模块的牵连,因而可将较为复杂的问题化简为一系列简单模块的设计。模块的独立性还为扩充已有的系统、建立新系统带来方便。
面向对象程序设计
面向对象程序设计与传统的结构化程序设计相比,面向对象程序设计吸取了结构化程序设计的一切优点(自顶向下、逐步求精的设计原则),而二者之间的最大差别表现在
1、面向对象方法采用数据抽象和信息隐藏技术使组成类的数据和操作是不可分割的,避免了结构化程序由于数据和过程分离引起的弊病。
2、面向对象的程序是由类、对象(类的实例)和对象之间的动态联系组成的。而结构化程序是由结构化的数据、过程的定义以及调用过程处理相应的数据组成的。
二 C语言基础知识
C语言发展和特点
特点
(1)结构化语言 (2)运算能力强大 (3)数据类型丰富 (4)具有预处理能力 (5)可移植性好 (6)程序执行效率高 (7)程序设计自由度大
C语言源于ALGOL60语言,于20世纪60年代初提出。 1963年,英国剑桥大学将ALGOL60语言发展成为组合程序设计语言(CPL)。 1970年英国剑桥大学的Martin Richards对CPL进行简化,开发出基本组合程序设计语言(BCPL)。 1970年,美国贝尔实验室的 Ken Thompson以BCPL语言为基础,设计出很简单且很接近硬件的B语言(取BCPL的首字母)。 1972年,美国贝尔实验室的 D.M.Ritchie 在B语言的基础上最终设计出了一种新的语言,他取了BCPL的第二个字母作为这种语言的名字,即C语言。 1975年UNIX第六版发布后,C语言终于获得了计算机专业人士的广泛支持。 1978年,美国贝尔实验室正式推出了C语言。 1983年美国国家标准协会(ANSI)根据C语言问世以来的的各种版本,对C语言发展和扩充制定了第一个C语言标准草案,称为83ANSIC。 1989年ANSI发布了一个完整的C语言标准ANSI X3.159-1989,称为ANSIC或C89。 1990年,国际标准化组织(ISO)接受C89为ISO国际标准,也称为C90。 1999 年 , ISO 对 C 语 言 标 准 进 行 修 订 , 发 布 了 新 的 C 语 言 国 际 标 准 -ISO/IEC9899:1999,简称C99。
C语言基本语法
字符集
字符是组成语言最基本的元素。C语言的字符集由字母、数字、空格、标点和特殊字符组成。C语言的字符集就是ASCII字符集:美国标准信息交换码,主要包括: 1、阿拉伯数字:0、1、2、…、9(共10个) 2、大小写英文字母:A~Z、a~z(共52个) 3、非字母、非数字的可显示字符(33个) 助记 数大小:0,A,a;48,65,97
关键字
C语言中具有特殊含义的单词称为“关键字”,又称为“保留字”,主要用于构成语句。C语言共有32个关键字,所有的关键字均由小写字母组成。
32个关键字:(由系统定义,不能重作其它定义)
auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef unsigned union void volatile while
各关键字含义
标识符
标识符是字符的序列,除了库函数的函数名等由系统定义外,其余均由用户自行定义。用户根据需要对C程序中用到的变量、符号常量、自定义函数等进行命名,形成用户标识符。 C语言规定,用户标识符仅由大小写英文字母、数字和下划线组成,且第一个字符不能是数字。 用户自定义的标识符既不能是C语言的关键字,也不能与用户已编写的函数或C语言的库函数重名。
标识符 ❖定义:标识变量名、符号常量名、函数名、数组名、文件名的字符串序列——名字。 ❖命名规则: ⚫ 只能由字母、数字、下划线组成,且第一个字符必须是不能是数字 ⚫ 大小写字母含义不同,一般用小写 ⚫ 不能使用关键字 ❖使用:先定义、后使用 注意: 标识符应该“见名知意”,如 total , max 标识符应该“不宜混淆”,如 l与1 , o与0
词汇分类
C语言的词汇分为六类,即常量、标识符、关键字、运算符、注释符和分隔符, 其中: 1、注释符 C语言的注释符有两种,一种是以“/*”开头,并以“*/”结尾的多行注释。 另一种是以“//”开头的单行注释。两者的区别在于,第一种注释可以跨行,第二种注释不可以跨行。 2、分隔符 C语言的分隔符包括逗号和空格两种。逗号主要用在类型说明和函数参数表中,分隔各个变量;空格多用于语句各单词之间,作为间隔符。
常量
⚫ 符号常量:用标识符代表常量 ◆一般用大写字母: PRICE 、 PI ◆定义格式: #define 符号常量 常量 ◆其值在作用域内不能改变和再赋值。 符号常量的优点是: 见名知意、一改全改
例 符号常量举例 #define PRICE 30 #include <stdio.h> void main() { int num,total; num=10; total=num*PRICE; printf("total=%d\n",total); } 运行结果: total=300
C语言基本语句分类
按照语句功能或构成的不同,可将C语言的语句分为数据定义语句、赋值语句、函数调用语句、表达式语句、流程控制语句、复合语句和空语句等等,C语言的每个语句都以分号“;”结束。
C程序基本组成
C程序是由语句组成的,通常包括一个或多个函数,其中有且只有一个函数称为主函数,其函数名为main。 C程序的组成特点: 1、每个C程序由一个或多个函数组成。每个C程序有且仅有一个主函数,除主函数外,可以没有其他函数,也可以有一个或多个其他函数。 2、函数是C程序的基本单位。每个函数是由函数首部和函数体两部分组成。 3、C程序中每一语句后面都以分号“;”作为语句的结束,但预处理命令、函数首部和右花括号“}”之间不能加分号。 4、C语言本身没有输入/输出语句,而是通过调用库函数scanf()、printf()、getchar()和putchar()实现的。 5、C程序的任意位置都可以加注释/*......*/,注释可以多行,但不可嵌套。 6、C程序的书写格式灵活,一行内可书写多条语句,一条语句也可以写在多行上,可以任意插入空格和回车。 7、C程序的执行总是从主函数开始,并在主函数结束。主函数和其他函数的位置是任意的,其他函数总是通过函数调用语句被执行。 8、C程序中可以有预处理命令,通常预处理命令应放在程序的最前面。 9、主函数可以调用任何其他函数,任何其他函数之间可以互相调用,但不能调用主函数。
C语言结构特点
❖函数与主函数 ⚫ 程序由一个或多个函数组成 ⚫ 必须有且只能有一个主函数main(),可以放在程序中任一位置 ⚫ 程序执行从main开始,在main中结束,其它函数通过嵌套调用得以执行。 ❖程序语句 ⚫ C程序由语句组成 ⚫ 用“;”作为语句终止符 ❖注释 ⚫ /* */为注释,不能嵌套 ⚫ 不产生编译代码 ❖编译预处理命令
例 从键盘输入两个整数,输出其中较大的数 #include <stdio.h> void main( ) { int max(int x,int y); //max子函数放在后面,必须先声明,如放在main函数前面,则不需事先声明 int a,b,c; //声明部分,定义变量 scanf( “%d,%d”,&a,&b); //scanf语句中“&a”的含义是“取地址” c=max(a,b); //调用max函数,返回值赋给c printf(" max = %d",c); } int max(int x, int y) //定义max子函数,函数值、形参x、y为整型 { int z; if(x>y) z=x; else z=y; return( z ); //通过 max 函数将 z 值带回调用处 } 输入:10,20 输出:max = 20
C语言格式特点
❖习惯用小写字母,大小写敏感 ❖不使用行号,无程序行概念 ❖可使用空行和空格 ❖常用锯齿形书写格式 优秀程序员的素质之一 使用TAB缩进 {}对齐 有足够的注释 有合适的空行
main() { int i,j,sum; sum = 0; for(i = 1; i < 10; i++) { for(j = 1; j < 10; j++) { sum += i*j; } } printf("%d\n,sum"); }
C程序开发环境
C程序的运行步骤: 1、编辑:进入C语言编译系统,建立源程序文件,扩展名为“c”或“cpp”,编辑并修改、保存源程序。 2、编译:计算机不能识别和执行高级语言编写的源程序,必须经过编译程序对源程序进行编译。 3、连接:经过编译生成的目标程序还无法在计算机上直接运行,因为源程序中输入、输出以及常用的函数运算是直接调用函数库中的库函数,因此必须把库函数的处理过程链接到经编译生成的目标程序中,生成可执行文件,其扩展名为“exe”。 4、运行:一个C源程序经过编译、链接后生成可执行文件,可在编译系统或操作中运行。
C程序的运行
(一)启动Microsoft Visual C++ 6.0 //或 Bloodshed Dev-C++ (二)新建项目 1、在主窗口的主菜单栏中选择“File”菜单项,在弹出的子菜单中选择“New”命令。 2、弹出的“New”对话框中,切换到“Projects”选项卡,在左侧列表框中选择“Wine 32 Console Application”项,然后在右侧的“Project name”文本框中输入新建工程的名称,如HelloWorld,在“Location”文本框中输入工程的存储路径。 3、在弹出的“Wine 32 Console Application”对话框中选中“An empty project”单选按钮,单击“Finish”按钮。 4、在弹出的“New Project Information”对话框中单击“OK”按钮,项目工作区中显示新建的空工程“HelloWorld”。 (三)新建源程序 1、在主窗口的主菜单栏中选择“File”菜单项,在弹出的子菜单中选择“New”命令。 2、在弹出的“New”对话框中,切换到“Files”选项卡,在左侧的列表框中选择“C++ Source File”项,表示新建一个C/C++源程序文件,勾选右侧的“Add to project”复选框,并选择刚刚创建的空工程“HelloWorld”,在“File”文本框中输入新建源程序文件的名称,如“hello.c”。 3、单击“OK”按钮,可以看到程序编辑窗格即可编辑程序。 (四)编译源程序 在主菜单栏中选择“Build”菜单项,在弹出的子菜单中选择“Compile hello.c”命令,或者单击工具栏中的“Compile”按钮。在编译过程中,主窗口下方会弹出调试信息窗格,此窗格会输出编译的信息。如无错误,输出“0 error(s),0 warning(s)”;如果有错误,则输出相关的错误信息。 (五)链接源程序 编译成功之后进行连接操作。在主菜单栏中选择“Build”菜单项,在弹出的子菜单中选择“Build HelloWorld.exe”命令,或者单击工具栏中的“Build”按钮。 (六)运行程序 连接成功之后即可运行程序。在主菜单栏中选择“Build”菜单项,在弹出的子菜单中选择“Execute HelloWorld.exe”命令,或者单击工具栏中的“Execute Program”按钮。
新建项目
C程序的调试
(一)设置断点 调试C程序时,首先需要判断程序可能从哪条语句开始出现错误,将光标移动到该语句所在行,然后单击“Insert/Remove Breakpoint”按钮或<F9>按钮,此时若在语句行左侧出现一个红点,则表示断点设置成功。 通过设置断点,检查运行到断点处某些表达式的值,以确定错误的大致位置; 通过核对某些表达式的值,以确定错误的原因。 (二)调试界面 断点设置成功后,单击“Go”按钮,程序开始运行。运行到断点所在语句时,程序将自动暂停执行,进入调试状态,代码行左侧的黄色箭头指示程序当前的执行位置。界面下方出现两个窗格,左侧窗格为自动变量框,可以看到各变量的当前值,右侧窗格为观察框,在“Name”列输入变量名后,“Value”列显示该变量的当前值。 (三)单步调试 “Step Over ”和“Step Into”都是单步调试程序,二者的区别在于遇到的函数调用时,“Step Into”进入函数内部逐步执行,而“Step Over”并不进入函数内部执行。
程序的调试
三 数据类型、运算符和表达式
1.数据类型
数据既是程序加工和处理的对象,也是程序加工和处理的结果。 1、基本类型包括:整型(有符号短整型:short[int],有符号基本整型:int,有符号长整型:long[int];无符号整型:无符号基本整型unsigned[int],无符号长整型unsigned long,无符号短整型unsigned short)、实型(又称浮点型:单精度型:float, 双精度型:double)、字符型(char)和枚举型(enum)四种。 2、构造类型是由基本数据类型按一定规则组合而成的复杂数据类型,包括数组型、结构体类型和共用体类型。 3、指针类型是一种更加灵活的数据类型,用来表示内存地址。 4、空类型是从语法完整性角度给出的数据类型,表示不需要数据值,因而没有类型。 数据类型决定: 1. 数据占内存字节数 2. 数据取值范围 3. 可以进行的操作
基本类型
整型
有符号整型
有符号短整型:short[int](2 byte)
有符号基本整型:int(4 byte)
有符号长整型:long[int](8 byte)
无符号整型
无符号短整型:unsigned short(4 byte)
无符号基本整型:unsigned[int] (8 byte)
无符号长整型:unsigned long (16 byte)
实型(浮点型)
单精度型:float
双精度型:double
字符型
char b = 'a' 特别注意使用半角的单引号
枚举型(enum)
构造类型
数组类型
结构体类型
共用体类型
指针类型
空类型(void)
2.常量
常量又称为常数,是在程序运行过程中其值不能改变的数据。
整型常量
整型常量通常称为整数,不带有小数部分,包括正整数、负整数 和 0。 C语言中,整型常量有三种书写形式: 1、八进制整型常量,必须以0开头,即以0作为八进制整数的前缀,是由数字0~7组成的数字串。 2、十进制整型常量,即通常的整数,没有前缀,是由数字0~9组成的数字串,是C语言中用的最多的一种形式。 3、十六进制整型常量,必须以0X或0x开头,即以0X或0x作为十六进制整数的前缀,是由字符0~9、a~f或A~F组成的字符串。
实型常量
实型常量又称为浮点型常量或实数,实型常量只使用十进制。 C语言的实型常量有两种书写形式: 1、十进制小数形式,由整数部分、小数点和小数部分组成,其中小数点必须有,整数部分和小数部分可以省略其中一个,正、负通过前面的“+”(可以省略)和“-”区分。 2、指数形式(用E或e表示),由尾数部分、阶码标志(小写字母e或大写字母E)和指数部分组成,其一般形式为aEb或aeb,其中尾数部分a的位数决定了数值的精度,指数部分b的位数决定了可表示的数值范围。a和b均有正负之分,且b必须为整数。
字符常量
字符常量包括普通字符常量和转义字符常量两种。 普通字符常量是用一对单引号括起来的单个字符,例如‘a’、‘?’均为普通字符常量。C语言还允许使用一种特殊形式的字符常量,即以反斜杠“\”开头的转义字符,它只代表某一特定的ASCII码字符。 C语言规定字符常量中的字母是区分大小写的。 每个字符常量在内存中占用1字节,具体存放的是该字符对应的ASCII码值。 需要注意的是,C语言中空字符和空格符号不同。空字符用转义字符“\0”表示,其值是0;空格符号是ASCII字符集中的一个可显示字符,其ASCII值是32,在屏幕上显示为空格。
需要注意的是,C语言中空字符和空格符号不同。空字符用转义字符“\0”表示,其值是0;空格符号是ASCII字符集中的一个可显示字符,其ASCII值是32,在屏幕上显示空格。
字符串常量
字符串常量又称为“字符串”,是用双引号括起来的零个或多个字符的序列。字符串中字符的个数称为字符串长度。 编译时自动在字符串的末尾处加一个转义字符‘\0’作为字符串的结束标志,即长度为n个字符的字符串占用n+1字节。 字符常量与字符串常量是不同的,其区别在于: 1、定界符不同,字符常量使用单引号,而字符串常量使用双引号。 2、长度不同,字符常量的长度固定为1;而字符串常量的长度,可以是0,也可以是某个整数。 3、存储要求不同,字符常量存储的是字符的ASCII码值,在内存中只占用1字节; 而字符串常量,除了要存储有效的字符外,还要存储一个字符串结束标志‘\0’。 ❖定义:用双引号(“ ”)括起来的字符序列 “How do you do” , “CHINA” , “a” , “$123.45” ❖存储:每个字符串尾自动加一个 ‘\0’ 作为字符串结束标志 例 字符串“hello”在内存中; h e l l o \0 例 空串 “” \0 ❖字符常量与字符串常量不同 例 ‘a’ a ; “a” a \0 例: char ch; ch=‘ A’; 没有字符串变量,只能用字符数组存放
符号常量
符号常量使用之前必须先定义,其定义的一般格式: #define 符号常量 常量 其中,常量可以是任意类型。符号常量一般采用大写字母表示,而变量一般采用小写字母表示。符号常量的定义一般放在程序的开头,每个定义必须独占一行,因为不是语句,所以后面没有分号“;”。 使用符号常量的优势在于: 1、提高程序的可读性 2、便于程序修改
小结
常量和符号常量 ❖ 定义:程序运行过程中,其值不能被改变的量(常数) ❖ 分类:直接常量、符号常量 ⚫符号常量:用标识符代表常量 ◆一般用大写字母: PRICE 、 PI ◆定义格式: #define 符号常量 常量 ◆其值在作用域内不能改变和再赋值。 符号常量的优点是: 见名知意、一改全改
【例】、正确的C语言整型常数是(A) A. 3 B. 3.0 C. 1E0.5 D. false 解析:C语言中,整型常量有三种形式: 1.八进制整型常量,必须以0开头,即以0作为八进制整数的前缀,是由数字0~7组成的字符串。 2.十进制整型常量,即通常的整数,没有前缀,是由数字0~9组成的字符串。 3.十六进制整型常量,必须以0X或0x开头,即以0X或0x作为十六进制整数的前缀,是由字符0~9、a~f或A~F组成的字符串。
符号常量
例 符号常量举例 #define PRICE 30 //以后只要修改单价就可以了,一改全改 #include <stdio.h> void main() { int num,total; num=10; total=num*PRICE; printf("total=%d\n",total); } 运行结果: total=300
3.变量
❖ 定义:其值可以改变的量。 ❖ 定义格式:数据类型 变量名; ❖ 变量应该有名字,并在内存中占据一定的存储单元。 ❖ 变量名和变量值有不同的含义 ⚫ 变量名实为一个符号地址
例 变量的使用 main() { int a; //a为变量名,开辟空间,默认4 byte a=3; //变量值;存储单元存储了3这个值 printf(“a=%d",a); }
整型变量
整型变量分为四种: 1、基本整型,类型关键字为 int。 2、短整型,类型关键字为 short[int]。中括号表示运用时可省略。 3、长整型,类型关键字为 long[int] 。 4、无符号整型,又称为无符号基本整型(unsigned [int])、无符号短整型(unsigned short)和无符号长整型(unsigned long)三种。 在16位编译器中,一般一个int型变量占用2字节(默认的看电脑配置,配置越高,分配空间越大),且long型(4字节)≥int型(2字节)≥short型(2字节);而在32位编译器中,一个int型变量占用4字节,long型变量占用4字节,short型变量占用2字节。
❖整型数据在内存中的存放形式 ⚫ 数据在内存中以二进制补码形式存放 ⚫ 每一个整型变量在内存中占2个字节
实型变量
实型变量分为两种: 1、单精度实型,类型关键字为float,一般占用内存 4 字节,保留6~7位有效数字。 2、双精度实型,类型关键字为double,一般占用内存 8 字节,保留15~16位有效数字。 例如:float a; a=123456.789 //实际a的值是 123456.7 double b; a=123456.789 //实际b的值是 123456.789
字符型变量
字符型变量的类型关键字是char,存放的是字符的ASCII码值(无符号整数),占用内存1字节。 需要注意的是,字符串只能是常量,C语言中没有字符串变量,而是用字符数组存放字符串变量。
变量的定义
C语言规定,任何变量必须先定义后使用。变量定义语句的一般格式: 数据类型符 变量名1[,变量名2,…]; 对变量进行定义时,需要注意以下几个方面: 1、变量定义可以存放在函数之外,也可以放在函数体或符合语句中。 2、被定义为整型(包括int、short和long)的变量,若其值为-128~127,则可以作为字符型变量使用。 3、被定为无符号整型(包括unsigned、unsigned short和unsigned long)的变量,若其值0~255,也可以作为字符型变量使用。 4、被定义为字符型的变量,可以作为整型(包括int、short和long)变量使用,其将值为-128~127,也可以作为无符号整型(包括unsigned、unsigned short和unsigned long)变量使用,其值将为0~255。 5、变量定义后,系统自动为其分配连续的内存单元,所占用的内存字节数取决于变量的数据类型。
变量的初始化
变量的初始化是给变量赋值的一种方式,定义变量的同时给变量赋初值称为变量的初始化。 变量初始化语句的一般格式: 数据类型符 变量名1=初值1[,变量名2[=初值2],...]; int radius=2,area;//radius 面积 short m=1,n=2; long l1=123L,l2;
变量赋初值
❖变量的使用:先定义,后使用 //定义的目的是分配内存单元 ❖变量定义位置:一般放在函数开头 ❖变量初始化:可以在定义时赋初值 例: 错!int a=b=c=3; //属于赋值语句,没有预先分配内存单元 可以定义如下: int a, b, c; a=b=c=3;
有名常量的定义
C语言中,如果定义了变量并赋予其初值,但不希望在程序中对其值进行修改,则可以将该变量定义为有名常量。 有名常量定义语句的一般格式: const 数据类型符 变量名1=初值1[,变量名2=初值2,...]; const int j=1,k=2; //用const只能赋值一次,以后再赋值会报错 const char ch1=‘y’,ch2=‘N’;
4.运算符和表达式
运算符分类
运算符是进行各种运算的符号,例如常用的加、减、乘、除符号,即是运算符。 ❖基本运算符 算术运算符:(+ - * / % +(取正) - (取负) ) 自增、自减运算符 (++ --) 关系运算符:(< <= == > >= !=) 逻辑运算符:(! && ||) 位运算符 :(<< >> ~ | ^ &) 赋值运算符:(= += -= *= /=) 条件运算符:(?:) 逗号运算符:(,) 数据长度运算符:(sizeof) ❖专用运算符 强制类型转换:(类型) 成员运算符:(. ->) 下标运算符:([]) 指针运算符:(* &) 学习运算符应注意: ❖运算符功能 ❖与运算量关系 ⚫ 要求运算量个数 ⚫ 要求运算量类型 ❖运算符优先级别 ❖结合方向 ❖结果的类型
结合性(非常重要)
结合性是C语言的独有概念。除单目运算符、赋值运算符和条件运算符是右结合之外,其他的运算符都是左结合
算术运算符
C语言的算术运算符包括+、-、*、/、%、+(取正)和-(取负)。 1、单目运算符+、-的优先级相同,结合性是从右至左。 2、双目运算符*、/、%的优先级相同,结合性是从左至右。 3、双目运算符+、-的优先级相同,结合性是从左至右。 4、单目算术运算符优先于双目算术运算符。 5、*、/、%优先于+、-。 需要注意的是: 1、除法运算的结果和运算对象的数据类型有关,若两个整数相除,则运算称为整除, 其商为整数,小数部分被舍弃。 2、模运算,要求两侧的运算对象必须为整型,否则出错。
基本算术运算符
❖基本算术运算符: + - * / % ⚫ 结合方向:从左向右 ⚫ 优先级: - ---->* / % -----> + - (2) (3) (4) 说明: ⚫ “-”可为单目运算符时,右结合性 ⚫ 两整数相除,结果为整数 ⚫ %要求两侧均为整型数据 ⚫ + - * / 运算的两个数中有一个数为实数,结果是double型
【例】
1、设int a=10;则执行语句a+=a-=a+a; 后a的值是________。 答案:-20 解析:先计算a-=a+a等价于a=a-(a+a)=10-20=-10;再计算a+=-10等价 于a=a+(-10)=-10-10=-20。 2、设int x=10, a=0, b=25;,条件表达式x<1?a+10︰b的值是(25 ) 解析:设int x=10, a=0, b=25;,条件表达式x<1?a+10︰b中x<1是假的取 b的值为25。
自增、自减运算符
自增和自减运算符分别为++和--,它们都是单目运算符,用于对整型、字符型和指针型变量的值加1和减1的运算,运算结果仍是原来的数据类型。 1、运算符放在变量之前,即++变量、--变量,则先使变量的值加(或减1),然后再以变化后的值参与其他运算,即先加(或减)1、后运算。 2、运算符放在变量之后,即变量++、变量--,则变量先参与其他运算,然后再使变量值加(或减)1,即先运算、后加(或减)1。 ⚫ 作用:使变量值加1或减1 ⚫ 种类: ◆前置 ++i, --i (先执行 i+1 或 i-1,再使用 i 值) ◆后置 i++,i-- (先使用 i 值,再执行 i+1 或 i-1) 例 j=3; k=++j; //k=4,j=4 j=3; k=j++; //k=3,j=4 j=3; printf(“%d”,++j); //4,j=4 j=3; printf(“%d”,j++); //3,j=4 a=3;b=5;c=(++a)*b; //c=20,a=4 a=3;b=5;c=(a++)*b; //c=15,a=4 ⚫ 几点说明: ◆++ -- 不能用于常量和表达式,如 5++,(a+b)++ ◆++ -- 结合方向: 自右向左 ◆优先级:【+ - ++ --】 >【* / %】 >【+ -】 //前面 【+ -】是单目运算符 取正、取负,后面的 【+ - 】是加、减 ◆该运算符常用于循环语句中,使循环变量加减1 例 -i++ <--> -(i++) //等价,【从右至左】结合 i=3; printf(“%d”,-i++); ❖有关表达式使用中的问题说明 ⚫ 不同系统对运算符和表达式的处理次序不同,尽可能写通用性强的语句 ⚫ 不要写有歧义和不知系统如何执行的程序 例如:int x=10,y=5; x++,x+y++的值是 16 使用自增和自减运算符时,需要注意以下几点: 1、自增、自减运算,常用于循环语句中,使循环控制变量加(或减)1,以及指针变量中,使指针向下(或上)一个地址。 2、自增、自减运算符,不能用于常量和表达式。 3、在表达式中连续使同一个变量进行自增或自减运算时,很容易出错,所以最好避免这种用法。 自增和自减运算符的优先级如下: 1、自增和自减运算符优先于双目算术运算符; 2、自增、自减运算符和单目算术运算符 +、- 的优先级相同,结合性是从右至左。
算术表达式
表达式中的运算符都是算术运算符的称为算术表达式,通常算术表达式由运算对象(常量、变量和函数等)、圆括号和算术运算符组成。 算术表达式的构成规则如下: 1、数值型常量、数值型变量和数值型函数调用是算术表达式。 2、+(算术表达式)、-(算术表达式)是算术表达式。 3、++整型变量、--整型变量、整型变量++、整型变量--是算术表达式。 4、(算术表达式)双目算术运算符(算术表达式)是算术表达式。 5、有限次地使用上述规则获得的表达式都是算术表达式。
关系运算符
关系运算符包括>、>=、<、<=、== 和 != 六种,它们是双目运算符。本质上,关系运算符是比较运算,即两个数据进行比较,判断两个数据是否符合给定的关系。 如果关系运算的结果是逻辑值“真”(即关系成立),用整数“1”表示;如果关系运算的结果是逻辑值“假”(即关系不成立),用整数“0”表示。 关系运算符的优先级如下: 1、算术运算符优先于关系运算符。 2、>、>=、<、<= 优先于 ==、!=。 3、>、>=、<、<=的优先级相同,结合性是从左至右。 4、==、!= 的优先级相同,结合性是从左至右。 需要注意的是,C语言中“等于”这一关系运算符是双等号“==”,而不是单等号“=”(赋值运算符)。 关系表达式是用关系运算符将两个表达式连接起来的表达式。 关系表达式的一般形式:表达式 关系运算符 表达式 表达式主要是算术表达式,也可以是字符型数据或关系表达式、逻辑表达式、条件表达式、赋值表达式、逗号表达式等。由于条件、赋值、逗号运算符的优先级低于关系运算符,所以应注意加圆括号。
逻辑运算符
基本赋值运算符
基本赋值运算符即赋值运算符“=”,它是双目运算符,赋值运算符的左边必 须是变量,右边是表达式,其作用是将一个表达式的值赋给一个变量。 赋值运算符的优先级: 1、算术运算符优先于关系运算符优先于双目逻辑运算符优先于赋值运算符; 2、赋值运算符的结合性是从右至左。
逻辑运算符包括&&、|| 和!三种。即与或非。 逻辑运算符的优先级如下: 1、!优先于双目算术运算符优先于关系运算符优先于&&优先于||。 2、单目逻辑运算符!和单目算术运算符的优先级相同,结合性均从右至左。 3、双目逻辑运算符“&&”和“||”的结合性是从左至右。 主要注意以下几点: 1、对于逻辑运算结果,“1”表示逻辑真,“0”表示逻辑假;对于逻辑运算对象“非0”表示逻辑真,“0”表示逻辑假。 2、逻辑与“&&”相当于“同时”,逻辑或“||”相当于“或者”,逻辑非“!”相当于“否定”。 3、逻辑运算符两侧的运算对象可以是任何类型的数据,如整型、实型、字符型等。 4、一旦“&&”和“||”能够确定表达式的值,则不再继续运算,即并不是所有的表达式都被运算。 逻辑表达式是用逻辑运算符将一个或多个表达式连接起来,进行逻辑运算的式子。 逻辑表达式的构成规则: 单目逻辑运算符 表达式 表达式 双目逻辑运算符 表达式 其中,表达式主要是关系表达式,也可以是字符型数据或算术表达式、条件表达式、赋值表达式、逗号表达式等。由于条件、赋值、逗号运算符的优先级低于逻辑运算符,所以应注意加圆括号。
复合赋值运算符
复合赋值运算符是在赋值运算符的前面再加一个双目运算符构成的,它是双目运算符,复合赋值运算符的左边必须是变量,右边是表达式。 复合赋值运算符的一般使用形式: 变量 双目运算符=表达式 等价于 变量=变量 双目运算符 (表达式) 复合赋值运算符的优先级: 1、算术运算符优先于关系运算符优先于双目逻辑运算符优先于复合赋值运算符。 2、复合赋值运算符和赋值运算符的优先级相同,结合性都是从右至左。 a+=3 <--> a=a+3 x*=y+8 <--> x=x*(y+8) x%=3 <--> x=x%3
赋值表达式
由赋值运算符或复合赋值运算符将一个变量和一个表达式连接起来的表达式,称为赋值表达式。 赋值表达式的构成规则: 变量=表达式 变量 复合赋值运算符 表达式 其中,表达式可以是算术表达式、关系表达式、逻辑表达式、条件表达式、赋值表达式和逗号表达式等。由于逗号运算符的优先级低于赋值运算符,所以应注意加圆括号。 例: a=b=c=5 //表达式值为5,a,b,c值为5 a=(b=5) // b=5;a=5 a=5+(c=6) //表达式值11,c=6,a=11 a=(b=4)+(c=6) //表达式值10,a=10,b=4,c=6 a=(b=10)/(c=2) //表达式值5,a=5,b=10,c=2
逗号运算符和逗号表达式
逗号运算符又称为顺序求职运算符,它是双目运算符,运算对象是表达式,常用于for循环语句中。 ❖形式:表达式1,表达式2,……表达式n ❖结合性:从左向右 ❖优先级: 15,级别最低 ❖逗号表达式的值:等于表达式n的值 ❖用途:常用于循环 for 语句中 例 a=3*5,a*4 //a=15,表达式值60 a=3*5,a*4,a+5 //a=15,表达式值20 例 x=(a=3,6*3) //赋值表达式,表达式值18,x=18 x=a=3,6*a //逗号表达式,表达式值18,x=3 例 a=1;b=2;c=3; printf(“%d,%d,%d”,a,b,c); //1,2,3 printf(“%d,%d,%d”,(a,b,c),b,c); //3,2,3 逗号运算符的优先级: 1、任何运算符优先于逗号运算符,即逗号运算符的优先级最低。 2、逗号运算符的结合性是从左至右。 需要注意的是,并不是任何地方出现的逗号都是逗号运算符。很多情况下,逗号仅用作分隔符。 用逗号运算符把两个或多个表达式连接起来构成逗号表达式,构成规则:表达式1,表达式2 其中,表达式可以是算术表达式、关系表达式、逻辑表达式、条件表达式、赋值表达式和逗号表达式等。
例: 逗号表达式使用 main() { int x,y=7; float z=4; //z存的是4.0 x=(y=y+6,y/z); //表达式y/z即13/4.0;x是整形变量 printf("x=%d\n",x); } 运行结果:X=3
条件运算符和条件表达式
条件运算符是C语言中唯一的一个三目运算符,它是由?和:组合成的,其三个运算对象均为表达式,其中第一个运算对象可以是任何类型的表达式,通常理解为逻辑表达式。 条件运算符的一般使用形式:表达式1?表达式2:表达式3 条件运算符的执行过程是先表达式1,若非0,则条件表达式的值是表达式2的值;若为0,则条件表达式的值是表达式3的值。 条件运算符的优先级: 1、其他运算符优先于条件运算符优先于赋值和复合赋值运算符优先于逗号运算符。 2、条件运算符的结合性是从右至左。 int i=1,j=1,k=2,m=0; i==j?(k=3) : (m=-3) // 3,k的值是3,i,j,m的值不变
位逻辑运算符
位逻辑运算符将数据中的每个二进制位上的“0”或“1”作为逻辑值,按位进行逻辑运算。位逻辑运算符分为按位非(~)、按位与(&)、按位或( | )和按位异或(^)(又称按位加)四种,其中按位非(~)是单目运算符,其余三种都是双目运算符。 3 & 9=00000001 3 | 9=00001011 3 ^ 9=00001010 1、运算对象只能是整型或字符型数据。除按位非为单目运算符外,其余均为双目运算符。 2、参与位逻辑运算时,运算对象以二进制形式进行相应的按位运算。 位逻辑运算符的优先级: 1、按位非(~)优先于双目算术运算符优先于关系运算符优先于 & 优先于 ^ 优先于 | 优先于双目逻辑运算符。 2、按位非(~) 与单目逻辑运算符、自增、自减、单目算术运算符、长度运算符的优先级相同,结合性是从右至左。
位移位运算符
位移位运算符(左移(<<)乘、右移(>>)除相应数字的位数)是将数据作为二进制数,进行向左(<<)或向右(>>)移动若干位的运算,分为左移和右移两种,均为双目运算符,其中第一个运算对象的移位对象,第二个运算对象是移动的二进制位数。 1、运算对象只能是整型或字符型数据。 2、参与位移位运算时,运算对象以二进制形式进行相应的按位运算。 位移位运算符的优先级: 1、算术运算符 优先于位移位运算符优先于关系运算符。 2、位移位运算符的优先级相同,结合性是从左至右。 unsigned int b=248; b=b>>2 运算后的值是62 (相当于248/4);即2^1, 2^2, 2^3, 2^4,.......//如需位移n位,则转换为2的n次方 int x=4,y=2; 表达式(x>>y)+(x<<y)的值是 17
长度运算符
长度运算符是单目运算符,其运算对象可以是任何数据类型符或变量。 长度运算符的使用形式:sizeof(数据类型符)或 sizeof(变量) 长度运算符的优先级: 1、与单目算术运算符、单目逻辑运算符、自增和自减运算符的优先级相同。 2、上述优先级相同的运算符的结合性都是从右至左。 sizeof(int) 4 sizeof(float) 4 sizeof(double) 8
5.数据类型转换
自动类型转换: 参与运算的各个数据都转换成数据较长的数据类型,然后计算,结果的类型就是数据长度较长的数据类型。本质上是较低类型的转换,即“就高不就低”或“就长不就短”的原则。 ❖什么情况下发生 ⚫ 运算转换------不同类型数据混合运算时 ⚫ 赋值转换------把一个值赋给与其类型不同的变量时 ⚫ 输出转换------输出时转换成指定的输出格式 ⚫ 函数调用转换------实参与形参类型不一致时转换 ❖运算转换规则:不同类型数据运算时先自动转换成同一类型 先将运算结果的数据类型自动转换为左边变量的数据类型,然后再赋予该变量。本质上是“就左不就右”的转换原则。 例:设int a=1;float b=2.1;char c=’a’; 则表达式a=b+c的计算过程是先计算b+c,按照“就高不就低”的规则,将c转换为实型(变量c的值是97,转化为97.0),计算结果是实型,即99.1,由于变量a是整型,按照“就左不就右”的规则,所以将实型自动转换为整型赋予变量a,即99赋值给变量a。 除了自动类型转换和赋值类型转换外,C语言也允许强制类型转换。 数据类型强制转换的一般格式:(数据类型符)(表达式) C语言首先按照自动类型转换原则计算表达式的值,然后将其转换为指定的数据类型。需要注意的是,若表达式仅仅是单个常量或变量时,外面的一对圆括号可以省略;若是含有运算符的表达式,则必须用一对圆括号括起来。 int a; float b=1.9; 执行语句 a=(int)b后变量a和b的值分别是 1,1.9 ❖强制类型转换 ❖一般形式:(类型名)(表达式) 强制类型转换运算符 例: (int)(x+y) (int)x+y (double)(3/2) (int)3.6 ❖说明:强制转换得到所需类型的中间变量,原变量类型不变 ❖精度损失问题 较高类型向较低类型转换时可能发生
进行数据类型转换时,需要注意以下几点: 1、强制类型转换获得的是一个所需类型的中间量,原来表达式的类型并不发生变化。 2、表达式计算过程中,参与运算的常量或变量的类型自动转换是临时的,它仅产生 一个临时的、类型不同的数据参与运算,常量或变量的原类型和值均不改变。 3、运算结果赋予变量的类型转换过程中,无论是自动变量的还是强制的,当数据长 度较长的结果存入数据长度较短的变量时,将截去超出的部分,有可能造成错误。 4、数据类型转换将占用系统时间,过多的转换将降低程序的运行效率,所以除了必 要的数据类型转换外,应尽量选择好数据类型,避免不必要的转换。
【例】
1、设int x=2,y=4;值为非0的表达式是(C) A. 1/x*y B. 1/(x*y) C. 1.0/x/y D. 1/x/ (double)y 答案:C 解析:1/x*y中1、x和y都是整型,即在1整除x时,值为0,即1/x*y最终的值为0;1/(x*y)中1是整型常量,即在1整除x*y时,值为0;1.0/x/y中1.0表示1.0为实型,即在1.0整除x时,是可以保留小数的,即1.0/x/y最终的值为非0;1/x/ (double)y中1和x都是整型,即在1整除x时,值为0。 2、设int a=1;float b=2.1;char c=‘a’;,则表达式a=b+c最后的类型为(A ) A. int B. float C. double D. char 答案:A 解析:表达式a=b+c的计算过程是先计算b+c,按照“就高不就低”的规则,将c转换为实型,c的值为97.0,计算结果为实型,即99.1,由于变量a是整型,按照“就左不就右”的规则,则表达式a=b+c最后的类型为int型。 3、设char c1; , 则c1中为小写英文字母字符的正确表达式为((c1>='a’)&&(c1<='z’)) 解析:逻辑符号“&&”是要同时满足左右两个表达式才为真,“||”是满足左右两个表达式中的一个就可以,小写字母的范围是从a到z,并且小写字母是字符,要用单引号括起来,即c1中为小写英文字母字符的正确表达式为(c1>='a’)&&(c1<='z’)
例3.8 #include <stdio> main() { float x; int i; x=3.6; i=(int)x; //表达式仅一个变量时,括号可以省略 printf(“x=%f,i=%d”,x,i); } 结果:x=3.600000,i=3
四 结构化程序设计
结构化程序设计方法
结构化程序设计是以模块功能和处理过程设计为主的详细设计的基本原则。 具体的,采取以下方法可以保证获得结构化程序: 1、自顶向下,逐步求精 2、模块化设计 3、结构化编码:经模块化设计后进入编码阶段,任何程序都由顺序、选择和循环三种基本结构组成。
结构化程序三种基本结构
⚫ 顺序结构是程序的一个基本结构,它是按照书写顺序依次执行操作。 ⚫ 选择结构又称分支结构,是根据某个或某些条件,从若干个操作中选择某个操作执行的一种控制结构。具体的,选择结构分为单分支、双分支和多分支选择结构三种。 ⚫ 循环结构是由循环控制条件控制循环体是否重复执行的一种控制结构。循环结构分为当型循环结构、直到型循环结构和次数型循环结构三种。
❖控制语句:共9种
⚫完成一定的控制功能,这些语句能够根据一定的测试条件决定某些语句是否被执行,如分支、循环、跳转等语句。 if( )~else~ (条件语句) for( )~ while( )~ (循环语句) do~while( ) continue (结束本次循环语句) switch (多分支选择语句) break (终止switch或循环语句) goto (转向语句) return (从函数返回语句)
❖连续赋值语句
int a , b , c ; a=b=c=1 ; 步骤: 连续赋值语句应“从右向左”计算 a=b=c=1 ; 等价于: a=(b=(c=1) ); 1. 把常数 1 赋给变量c,表达式(c=1) 的值为1; 2. 把表达式(c=1)赋给变量b,表达式(b=(c=1) )的值为1; 3. 将右侧表达式的值1赋给a , a =1 。
顺序结构程序设计
赋值语句的格式包括两种: 1、第一种 【格式】变量 = 表达式; 【功能】计算表达式的值,然后赋予变量。 2、第二种 【格式】变量 复合赋值运算符 表达式; 【功能】将变量和表达式进行制定的算术或位运算后,再将运算结果赋予变量。 本质上,赋值语句是赋值表达式后面跟一个分号。
求三解形面积
【例】 输入三角形边长,求面积 #include <math.h> //文件包含预处理命令 #include <stdio.h> void main( ) { float a, b, c, s, area; //变量定义 scanf("%f, %f, %f", &a, &b, &c); s=1.0/2*(a+b+c); //此处要非常小心,因为如果是整形1的话,1/2为0,那就悲剧了 area=sqrt(s*(s-a)*(s-b)*(s-c)); //sqrt开根号 printf("a=%7.2f, b=%7.2f, c=%7.2f, s=%7.2f\n",a,b,c,s); //占位符,表示占7个字符,并且保留2位小数 printf("area=%7.2f\n",area); //输出数据 } 输入:3,4,6 输出:a= 3.00, b= 4.00, c= 6.00 s= 6.50 area= 5.33
例 从键盘输入大写字母,用小写字母输出 #include "stdio.h" void main() { char c1,c2; c1=getchar( ); printf("%c,%d\n",c1,c1); c2=c1+32; printf("%c,%d\n",c2,c2); } 输入:A 输出:A,65 a,97
函数调用语句
函数调用语句的格式和功能: 【格式】函数名(参数1,参数2,…,参数n); 【功能】调用指定的库函数或用户自定义函数,对圆括号中的参数进行该函数指定的运算,运算结果作为函数的返回值返回。 本质上,函数调用语句是函数调用后面跟一个分号。
表达式语句
表达式语句的格式和功能如下:如 c = (a>b) ? a : b ; 【格式】表达式; 【功能】计算表达式的值。 本质上,表达式语句的表达式后面跟一个分号。需要注意的是,任何表达式都可以构成语句。
复合语句
复合语句的格式和功能如下: 【格式】{ 语句1; 语句2; … 语句n; } 【功能】依次执行语句1,语句2,…,语句n。 【说明】1、复合语句可以包含多条语句,但整体上是作为一条语句看待。 2、复合语句中若有数据定义语句,则应放在复合语句中其他语句的前面。
int i=0,sum=0; while(i<=100) { sum=sum+i; i=i+1; }
字符输出函数putchar( )
【调用格式】putchar(ch) 【参数】“ch”可以是一个整型变量、字符型变量、整型常量或字符型常量,也可以是一个转义字符或整型表达式,但不能是字符串。 【功能】把一个字符输出到标准输出设备(显示器)。此外,就功能而言“%c”格式说明符的printf()函数可以完全代替putchar()函数的功能。 【返回值】输出ch对应的单个字符。 【说明】 1、putchar()函数只能用于单个字符的输出,一次输出一个字符。 2、C源程序中若使用了putchar()函数,则必须在C源程序(或文件)的开头加上编译预处理命令 #include<stdio.h> 或 #include"stdio.h"。
putchar 函数(单字符输出函数)
格式: putchar( ‘字符’);或 putchar( 字符变量); 强调:被输出的单个字符必须被‘ ’括起来
❖输出一个字符
【例】putchar 字符变量 #include <stdio.h> void main() { char a,b,c; a = 'B', b = 'O', c = 'Y'; // putchar('B'); putchar('O'); putchar('Y'); 效果一样 putchar(a); putchar(b); putchar(c); // printf("%c%c%c",a,b,c); 效果一样 } 运行结果:BOY
❖输出控制字符
/* 例 putchar(‘控制字符’) */ #include <stdio.h> void main() { char a,b; a=‘O'; b=‘k'; putchar(a); putchar(‘\n’); putchar(b); } 运行结果:O k
❖输出转义字符
/*例 putchar( ) 含有转义符* / #include <stdio.h> void main() { char a; a='B'; putchar('\101'); putchar(a); // \101字符常量%ddd,表表1~3位八进制数 } 运行结果:AB
字符输入函数getchar( )
【调用格式】getchar() 【参数】无参数 【功能】从标准输入设备(键盘)输入一个字符。另外,就功能而言带“%c”格式说明符的scanf( )函数可以完全替代getchar( )函数的功能。 【返回值】输出的单个字符。 【说明】 1、getchar()函数只能用于单个字符的输入,一次输入一个字符。输入的字符也是按字符处理。当输入多于一个字符时,只接收第一个字符。 2、C源程序()函数输入的字符可以赋予一个字符变量或整型变量,也可以不赋给任何变量,而作为表达式的一部分。 3、C源程序中若使用了getchar()函数,则必须在C源程序(或文件)的开头加上编译预处理命令#include<stdio.h>或#include"stdio.h"。
getchar 函数(单字符输入函数)
格式: getchar( ) ; 强调:输入单个字符后,必须按一次回车,计算机才接受输入的字符。
/*例 */ #include <stdio.h> void main() { char c; c=getchar( ); // scanf("%c",&c); 等价 putchar(c); } 运行结果: a 回车 a # include <stdio.h>不能少!
格式输出函数printf( )
【调用格式】printf ( 格式控制字符串, 输出项表 ) 【参数】“格式控制字符串”是由控制输出格式的字符组成的字符串。 “输出项表”是用逗号分隔的若干个表达式。 【功能】按照用户指定的格式把指定的数据输出到标准输出设备(显示器)。 【返回值】返回值为整型,等于正常输出的数据个数。 【说明】 “格式控制字符串”又称“转换控制字符串”,是用一对双引号括起来的字符串,其作用是将输出项表中的数据从内存的二进制形式转换成指定的格式输出。格式控制字符串由格式说明符、附加格式说明符、转义字符和普通字符组成。 1、格式说明符 格式说明符由“%”和“格式字符”组成,其作用是将要输出的数据按格式说明符指定的格式输出。
printf函数(格式输出函数)
❖printf函数的一般格式 格式: printf( 格式控制,输出表列); ⚫ 格式控制:用双引号括起来的字符串,包含两种信息 ◆格式说明:%[修饰符]格式字符,指定输出格式 ◆普通字符:原样输出 ⚫ 输出表列:要输出的数据,可以是变量或表达式,可以没有,多个时以“,”分隔) 例: printf( “%d %d”,a,b); printf( “a=%d b= %d\n”,a,b);
❖ d 格式符:输出十进制整数,有3种用法 ⚫ %d 格式:按数据实际长度输出,数据范围 -32768~32767。 ⚫ %md 格式:m指定输出字段的宽度 ◆数据位数小于m,左端补空格,反之按实际输出。 int a=123 , b=12345 ; printf(“%4d %4d ”, a , b); 运行结果: _123 12345 ⚫ %ld 格式:输出长整型数据 ◆可以用%mld格式指定输出列宽 long c=135790 printf(“%ld \n”, c); printf(“%8ld ”, c); 运行结果: 135790 _ _135790 // 注:列宽8,135790 > 32767 ( int 型数据的最大值)
/* %d 格式的 printf( ) 函数使用 */ #include <stdio.h> void main() { int a=3 , b=4; printf(“%d %d\n ”,a,b); printf(“a=%d , b=%d\n”,a,b); } 运行结果: 3 4 a=3, b=4 //格式说明决定最终输出的格式 //格式说明应与输出列表项个数相同,顺序一致 //格式说明通常用小写字母表示
❖ o 格式符:八进制输出整数 ⚫ 是将内存中的二进制位整个按八进制输出,所以输出,值没有符号。 ⚫ 可以指定输出宽度 %mo,长整型可以用 %lo 格式输出。
❖x 格式符:十六进制输出整数 ⚫ 同 o 格式符,无符号,即无负十六进制数。 ⚫ 可以指定输出宽度 %mx ,长整型可以用 %lx 格式输出。 int a= -1; printf(“%d , %o , %8o , %x ”, a , a , a , a); 输出: -1,177777,_ _177777,ffff // 11 11 11 11 11 11 11 11,-1在内存中的存放形式(补码)
❖u 格式符:十进制输出unsigned型数据 ⚫ int型可以用 %u 格式输出,unsigned型也可以用 %d、%o 和 %x 格式输出。
例 无符号数据的输出 #include <stdio.h> void main() { unsigned int a=65535; int b= -2; printf(“a=%d , %o , %x , %u\n ”,a,a,a,a); printf(“b=%d , %o , %x , %u\n” ,b,b,b,b); } 运行结果: a= - 1 , 177777 , ffff , 65535 b= - 2 , 177776 , fffe , 65534
❖c 格式符:输出一个字符 ⚫ 值在0~255的整数,可以用 %c 形式输出为字符
例 字符数据的输出 #include <stdio.h> void main() { char c=‘a’; int i=97; printf(“%c , %d\n ”,c,c); printf(“%c , %d\n” ,i ,i); } 运行结果: a , 97 a , 97
❖s 格式符:输出一个字符串 ⚫ 有%s,%ms,% -ms,%m.ns,% -m.ns五种用法
例 字符串的输出 #include <stdio.h> void main() { printf(“%3s , %7.2s , %.4s , %-5.3d\n ”, “CHINA”,“CHINA”, “CHINA”, “CHINA”); } //m自动 = n输出4 运行结果: CHINA , _ _ _ _ _CH , CHIN , CHI _ _
❖f 格式符:输出实数 ⚫ %f 格式:整数部分全部输出,小数6位。可以有非有效数字输出,因为单精度有效位7位,双精度16位。 ⚫ %m.nf 格式:占m列,其中n位小数,左补空格。 ⚫ % -m.nf 格式:右补空格
例 %f格式输出实数时的有效位数 #include <stdio.h> void main() { float x,y; x=111111.111;y=222222.222; printf("%f\n",x+y); } 运行结果: 333333.328125
例 %f格式输出双精度实数时的有效位数 #include <stdio.h> void main() { double x,y; a = 1111111111111 . 111111111 ; b=2222222222222.222222222; printf(“%f\n”,x+y); } 运行结果: 3333333333333.333010
例 %f格式输出实数时指定小数位数 #include <stdio.h> void main() { float f=123.456; printf(“%f %10f %10.2f %.2f %-10.2f\n”,f,f,f,f,f); } 运行结果: 123.456001_ _123.456001_ _ _ _ _ _123.46_ _123.46_ _123.46_ _ _ _
❖ e 格式符:指数形式输出实数 ⚫ %e 格式:不指定m和n,小数6位,指数部分共5位,其中e和指数符号各1位,指数值3位。 ⚫ %m.ne和% -m.ne格式:m、n、-的含义同前面。 没有n时,自动=6
❖ g 格式符:输出实数 ⚫ 可以自动根据数值大小选择 f 或 e 格式(选列少的) ⚫ 不输出无意义的零
❖ 格式字符表
输出不同类型的数据,需要使用不同的格式字符,其中: 格式字符d——以有符号的十进制整数形式输出。 格式字符f——以小数形式、按系统默认的宽度,输出单精度和双精度实数。 格式字符c——输出一个字符(只占一列宽度)。 格式字符s——输出一个字符串。
2、附加格式说明符 在“%”和“格式说明符”之间还可以加入附加格式说明符,以说明输出数据的标志、域宽(所占列数)、精度和长度修饰符。
3、转义字符 转义字符由一个反斜杠和一个字符组成。 4、普通字符 普通字符是指除格式说明符、附加格式说明字符和转义字符之外的其他字符,输出时按原样输出,起到提示的作用。 5、输出项表 输出项表中给出各个输出项,输出项可以是变量、表达式、常量或函数调用等。
格式输入函数scanf( )
【调用格式】scanf ( 格式控制字符串, 输入项首地址表 ) 【参数】“格式控制字符串”是由控制输入格式的字符组成的字符串。 “输入项首地址表”由若干个输入项首地址组成,相邻两个输入项首地址之间用逗号分隔。 【功能】从键盘按照“格式控制字符串”中规定的格式读取若干个数据,然后按照“输入项首地址表”中的顺序,依次将数据存入相应的变量对应的地址。 【返回值】输入的数据个数 【说明】 1、格式控制字符串 格式控制字符串可以包含三种类型的字符,即格式说明符、空白字符(空格、回车和<Tab>键)和非空白字符(又称普通字符) scanf()函数的格式说明符的一般形式: % [ * ] [宽度] [ F | N ][ h | l ] 格式字符 (1)宽度。宽度是指定该项输入数据所占列数。 (2)赋值抑制字符*。赋值抑制字符表示本输入项对应的数据读入后,不赋予相应的变量(该变量由下一个格式字符输入) (3)类型修饰符——F、N、h、l 分别表示远指针、近指针、短整型和长整型。
scanf 函数(格式输入函数)
❖一般形式 格式: scanf (格式控制,地址表列 ) ; ⚫ 功能:按指定格式从键盘读入数据,存入地址表指定的存储单元中,并按回车键结束 ⚫ 格式控制:含义同printf函数 ⚫ 地址表列:变量地址或字符串地址,地址间“,”分隔。 ⚫ 强调:地址列表中每一项必须以取地址运算符 & 开头。 例 scanf(“%4d%2d%2d”,&yy,&mm,&dd); 输入 19991015 回车 则 1999--> yy, 10 --> mm, 15 --> dd 例 scanf(“%3d%*4d%f”,&k,&f); 输入 12345678765.43回车 则 123 --> k, 8765.43 --> f 例 scanf(“%2d %*3d %2d”,&a,&b); 输入 12 345 67回车 则 12 --> a, 67 --> b 例 scanf(“%3c%2c”,&c1,&c2); 输入 abcde 则 ‘a’--> c1, ‘d' --> c2 ❖输入分隔符的指定 ⚫ 一般以空格、TAB或回车键作为分隔符 ⚫ 输入数据时,遇非法输入则认为数据结束 ⚫ 其它字符做分隔符:格式串中两个格式符间有其它字符,则输入时对应位置也要有相同的字符。 例 scanf(“%d:%d:%d”,&h,&m,&s); 输入 12:30:45 则12 --> h, 30 --> m, 45 --> s 例 scanf(“%d%c%f”,&a,&b,&c); 输入1234a123o.26 //o 非法字符 则 1234 --> a, ‘a’--> b, 123 --> c
例 用scanf函数输入数据 #include <stdio.h> voidmain() { int a , b , c ; scanf(“%d%d%d”,&a, &b, &c) ; printf(“%d, %d, %d\n”,a, b, c) ; } 输入:3_4_5 输出:3,4,5
格式字符表
输入项首地址表
输入项首地址表是由接收输入数据的变量地址组成的,地址之间用逗号“,”分隔。 例: 1、执行语句printf(〞%s\n, 〞〞 World\0Wide\0Web〞);后的输出结果是_______。 答案:World 解析:执行语句printf(〞%s\n, 〞〞 World\0Wide\0Web〞);后的输出结果是World,是因为%s\n在输出的时候遇到\0就结束输出。 2、设int a;,从键盘输入数据给变量a的输入语句是________。 答案:scanf(“%d”,&a); 解析:格式输入函数scanf()的格式是scanf(格式控制字符串,输入项首地址表),故从键盘输入数据给变量a的输入语句是scanf(“%d”,&a);。
选择结构程序设计
单分支选择结构
C语言中实现单分支选择结构的语句是if语句。if语句的一般形式: 【格式】if(表达式)语句; 【功能】计算表达式的值,若为“真”(非0),则执行语句;否则不执行语句。 【说明】 1、if语句中的“表达式”必须用一对圆括号括起来。 2、“表达式”可以是任何类型,除了常用的关系表达式或逻辑表达式外,也允许是其他类型的数据,如整型、实型或字符型等。 3、语句可以是任何语句(除了数据定义语句),也可以是另一个if语句(称为嵌套的if语句)。
if 语句(条件选择语句) If语句的三种形式 ❖形式一: ⚫ 格式:if (表达式) 语句
执行过程
例 输入两个实数,按由小到大的次序输出两数 #include <stdio.h> void main() { float a,b,t ; scanf("%f,%f",&a,&b); if(a>b) {t=a;a=b;b=t;} printf("%5.2f,%5.2f",a,b); }
输入:3.6, -3.2 回车 输出:-3.20, 3.60
双分支选择语句
C语言中实现双分支选择语句的是if-else语句。if-else语句的一般形式: 【格式】if(表达式)语句1; else 语句2; 【功能】计算表达式的值,若为“真”(非0),则执行语句1;否则执行语句 【说明】 1、if语句中的“表达式”必须用一对圆括号括起来。 2、“表达式”可以是任何类型,除了常用的关系表达式或逻辑表达式外,也允许是其他类型的数据,如整型、实型或字符型等。 3、语句1和语句2可以是任何语句(除了数据定义语句),也可以是另一个i f-else语句(称为嵌套的if-else语句)。 4、“语句1”和“语句2”可以是一条简单的语句,也可以是复合语句。不管是简单语句还是复合语句中的各个语句,每条语句后面的分号必不可少。 5、else子句是if语句的一部分,必须与if配对使用,不能单独使用。 ❖形式二: ⚫ 格式: if (表达式) 语句1 else 语句2
执行过程
例:输入两个数并判断两数是否相等 #include <stdio.h> void main() { int a,b; printf("Enter integer a:"); scanf("%d",&a); printf("Enter integer b:"); scanf("%d",&b); if(a= =b) printf("a==b\n"); else printf("a!=b\n"); }
运行: Enter integer a:12 Enter integer b:12 a==b
运行: Enter integer a:12 Enter integer b:9 a!=b
❖形式三: if-else-if语句的嵌套(分支嵌套的特殊形式) 这种嵌套是在每一层else分支下嵌套一个if-else语句,即: if(表达式1) 语句1 else if(表达式2) 语句2 else if(表达式3) 语句3 … else if (表达式n) 语句n else 语句n+1 需要注意以下几点: 1、从上至下逐一对表达式进行判断,只有遇到某一表达式值为“真”(非0)时,则 执行与之相对应的语句,执行完毕后跳出整个选择结构;如果没有任何一个表达式值为“真”,则执行最后一个else后面的语句n+1. 2、不管有多少分支,程序在执行完一个分支后其余的分支将不再执行。 3、else if不能写成elseif,即else和if之间需要有空格分隔。 4、if语句的嵌套层数没有限制,可以多重嵌套。多重嵌套可以扩展选择的分支数量,但嵌套的层数太多会增加变成的难度。因此,嵌套的层数不宜过多。
执行过程
❖几点说明: ⚫ if后面的表达式类型任意 if(a = =b&&x= =y) printf(“a=b,x=y”); if(3) printf(“OK”); if(‘a’) printf(“%d”,a); ⚫ if 后面的语句可以是复合语句,必须要加{ } 考虑下面程序的输出结果: main() { int x,y; scanf(“%d,%d”,&x,&y); if(x>y) x=y; y=x; //没有{ }花括号 else x++; y++; //没有{ }花括号,Compile Error! printf(“%d,%d\n”,x,y); }
例:判断输入字符种类 #include<stdio.h> void main() { char c; printf("Enter a character:"); c=getchar(); if(c<32) printf("The character is a control character\n"); else if(c>='0'&&c<='9') printf("The character is a digit\n"); else if(c>='A'&&c<='Z') printf("The character is a capital letter\n"); else if(c>='a'&&c<='z') printf("The character is a lower letter\n"); else printf("The character is other character\n"); }
运行:Enter a character F1 回车 The charcter is a conrol character
例:输入三个数,按由小到大的次序输出 #include <stdio.h> void main() { float a,b,c,t ; scanf("%f,%f,%f",&a,&b,&c); if(a>b) {t=a;a=b;b=t;} if(a>c) {t=a;a=c;c=t;} if(b>c) {t=b;b=c;c=t;} printf(“%5.2f,%5.2f,%5.2f”,a,b,c); }
输入:3,7,1 回车 输出:1.00,3.00,7.00
if语句的嵌套(分支嵌套的一般形式) 这种嵌套是在if-else语句的任一分支上嵌套单分支的if选择结构或双分支的if选择结构。 ❖一般形式: if(表达式1) if(表达式2) 语句1; else 语句2; else if(表达式3) 语句3; else 语句4; if后面和else后面的语句可以再包含if语句。需要注意的是,else总是与前面最近的并且没有与其他else匹配的if相匹配。 ⚫ If语句中又包含一个或多个if语句称为if语句的嵌套。 ⚫ 实际上只要将前述if语句的形式1和2中的内嵌语句用 一个if语句代替,即成为if语句的嵌套。 ⚫ 嵌套的if语句还可以嵌套另一个if语句,形成多重嵌套。
例:输入两个数并判断其大小关系 #include <stdio.h> void main() { int x,y; printf("Enter integer x,y:"); scanf("%d,%d",&x,&y); if(x!=y) if(x>y) printf("X>Y\n"); else printf("X<Y\n"); else printf("X==Y\n"); }
Enter integer x,y:12,23 X<Y Enter integer x,y:12,6 X>Y Enter integer x,y:12,12 X==Y
条件运算符
if语句中,当表达式为“真”和“假”时,都只执行一个赋值语句给同一个变量赋值时,可以用条件运算符处理。 if (a>b) max=a; else max=b; 表达式1 ? 表达式2 : 表达式3 max=(a>b)? a:b; 条件运算符是 C 语言中唯一的三目运算符
例 输入一个字母,大写转小写,然后输出字母 #include <stdio.h> void main() { char ch; scanf("%c",&ch); ch=(ch>=‘A’ && ch<=‘Z’)? (ch+32) : ch; printf(“%c“,ch); }
输入:A 输出:a
多分支选择语句
除了if语句的嵌套可以实现多分支选择结构外,C语言还提供了一种直接实现多分支选择结构的switch语句。 switch语句的一般形式: 【说明】 switch(表达式) { case 常量表达式1:语句组1;break; case 常量表达式2:语句组2;break; … case 常量表达式n:语句组n;break; [default:语句组n+1;[break;]] } 【功能】 1、switch后面的“表达式”常用的是字符型或整型表达式。 2、“常量表达式”是由常量或符号常量组成的表达式,所有常量表达式的值必须互不相同。 3、当switch后面的“表达式”的值,与某个case后面的“常量表达式”的值相等时,则执行case后面的语句(组),当执行到语句break;时,跳出该switch语句,转而执行switch语句的下一条语句。 4、如果switch后面“表达式”的值没有与任何一个case后面的“常量表达式”的值相等,则执行default后面的语句(组)。之后,执行switch语句的下一条语句。 【说明】 1、每个case后面的“常量表达式”的值,必须各不相同,否则会出现相互矛盾的现象。 2、case后面的“常量表达式”仅起到语句标号的作用,并不进行条件判断。系统一旦知道入口标号,即从此标号开始执行,不再进行标号判断,所以必须加上语句break;,以便结束switch语句。 3、程序的执行结果与各case和default子句的先后次序无关。 4、多个case子句,可共用同一个语句(组)。 5、switch语句实现的多分支选择结构,完全可以用if语句或if语句的嵌套实现。 6、break语句是C语言的一种语句,其功能是中断正在执行的语句。在switch语句中其作用是执行完某一语句(组)后,跳出该switch语句,无条件转至switch语句的下一条语句。若省略break语句,则执行完某一语句(组)后,将继续执行switch语句中其后的所有语句(组)。 7、default及其后面的语句(组)可以省略。省略时,若表达式的值和n个常量表达式的值均不同,则不执行任何操作就跳出该switch语句。 8、语句组不需要用一对花括号{}括起来。 9、switch语句允许嵌套。
switch语句(多分支选择语句)
❖switch几点说明 ⚫ C1,C2,…Cn是常量表达式,且值必须互不相同 ⚫ 常量表达式起语句标号作用,必须用break跳出 ⚫ case后可包含多个可执行语句,且不必加{ } ⚫ switch可嵌套 ⚫ 多个case可共用一组执行语句
#include <stdio.h> main() { char grade; grade =getchar(); switch(grade) { case ‘A’ : printf (“85~100 \n”); break; case ‘B’ : printf (“70~84 \n”); break; case ‘C’ : printf (“60~69 \n”); break; case ‘D’ : printf (“<60 \n”); break; default : printf (“Error \n”); } }
A 回车 85~100
循环结构程序设计
while语句
while语句主要用于实现当型循环结构,其一般形式: 【格式】 while(表达式) 语句; 【功能】计算表达式的值,当其值为“真”(非0),则执行语句;重复执行上述操作,直至表达式的值为“假”(0)时为止。 【说明】 1、语句又称为“循环体”,可以是任何语句(除了数据定义语句外),通常是复合语句。 2、表达式称为“循环控制条件”,可以是任何类型的表达式,常用关系或逻辑表达式。 3、先判断后执行循环体。即表达式不成立时,循环体最少执行0次。 4、当循环体内无改变表达式的语句(例如i++)时,while(1)将是死循环。 5、若循环体中又含有“循环语句”,则称为循环的嵌套,即多重循环。 6、在书写格式上建议语句(循环体)比while缩进若干格,以便识别重复执行的的操作。
while语句实现“当型”循环结构
❖一般形式: while(表达式) 循环体语句; ❖功能:先判断表达式,若为真则执行循环体,再判断表达式,重复上述过程,直到表达式为假时退出循环。 ❖关于while循环语句: ⚫while循环先判断表达式,后执行循环体。循环体有可能一次也不执行。 ⚫循环体若包含一个以上语句,应该用 {} 括起来。 ⚫循环体应包含有使循环趋向结束的语句; ⚫下列情况,退出while循环 ◆条件表达式不成立(为零) ◆循环体内遇 break , goto ⚫无限循环 while(1) 循环体 ;
例 用while语句显示1~10的平方 #include <stdio.h> void main() { int i=1; while(i<=10) { printf("%d*%d=%d\n",i,i,i*i); i++; } }
1*1=1 2*2=4 3*3=9 4*4=16 5*5=25 6*6=36 7*7=49 8*8=64 9*9=81 10*10=100
do-while语句
do-while语句主要用于实现直到型循环结构,其一般形式: 【格式】do 语句; while(表达式) ; /*本行的分号不能省略*/ 【功能】 1、执行语句。 2、计算表达式,如果其值为“真”(非0),则转置1继续循环;否则,转至3。 3、执行do-while语句的下一条语句。 【说明】 1、语句又称为“循环体”,可以是任何语句(除了数据定义语句外),通常是复合语句。 2、表达式称为“循环控制条件”,可以是任何类型的表达式,常用关系或逻辑表达式。需要注意的是,表达式必须用圆括号括起来,其后有一个分号。 3、先执行循环体,在判断循环控制条件。适用于无论循环控制条件是否成立,先执行一次循环体的情形。此外,do-while语句能够实现的功能for语句也能实现,而且更简洁。 4、若循环体中又含有“循环语句”,则称为循环的嵌套,即多重循环。 5、在书写格式上建议do和while对其,以便识别重复执行的操作。 ❖do-while语句实现“当型”循环结构。 ❖一般形式: do 循环体语句; while(表达式) ; ❖功能:先执行循环体,然后判断表达式。若为真,则再次执行循环体,否则退出循环。 ⚫ do-while 循环,循环体至少执行一次; ⚫ while和do-while可以解决同一问题,两者可以互换。 ⚫ while后的表达式一开始就为假时,两种循环结果不同。
例 用do-while语句构成循环 #include <stdio.h> void main( ) { int i,sum=0; scanf("%d",&i); do { sum+=i; i++; } while(i<=100); printf("%d",sum); } 输入:101 输出:101
对比
#include <stdio.h> void main() { int i,sum=0; scanf("%d",&i); while(i<=100) { sum+=i; i++; } printf("%d",sum); }
输入:100 输出:5050 输入:101 输出:0
for语句
for语句主要用于实现次数型循环结构。for语句的一般形式: 【格式】 for(表达式1; 表达式2; 表达式3) 语句; 【功能】 1、计算表达式1,实现变量赋初值。 2、计算表达式2,如果其值为非0,则执行3;否则转至4。 3、执行语句,并计算表达式3, 然后转至2。 4、不执行语句,退出for循环,执行for语句的下一条语句。 【说明】 1、“表达式1”的作用是对循环控制变量赋初值;“表达式2”的作用是控制循环的条件;“表达式3”的作用是循环后修正变量,使循环趋于结束。 2、“表达式1”可以是任何类型,其后有一个分号,“表达式1”可以省略。 3、“表达式2”可以是任何类型,其后有一个分号,“表达式2”可以省略。 省略时相当于“表达式2”的值永远为“真”(非0),所以循环将一直执行,这种情况称为“死循环”,一般应避免。 4、“表达式3”可以是任何类型,其后没有分号,“表达式3”可以省略。 5、“语句”又称为“循环体”,可以是任何语句(除数据定义语句外),通常为复合语句。 6、“表达式1”“表达式2”和“表达式3”均可以省略,甚至三者全部省略,但它们之间的分号不能省略。 7、书写格式上建议以for为准,语句缩进若干格,以便识别重复执行的操作。 8、当“表达式1”和“表达式3”省略时,相当于while循环,即先判断后执行循环体,循环体至少执行0次;当“表达式3”省略时,则循环体内应有改变“表达式2”的语句;当“表达式2”省略时,无终止条件,则循环体内应有跳出该循环的语句,如break、goto、exit()或return等。
for 语句是 C 语言中最为灵活,使用最广泛的循环语句,可完全替代while,do-while语句。 ❖一般形式 for(表达式1;表达式2;表达式3) 循环体语句; ❖常用形式 for(循环变量赋初值;循环条件;循环变量增值) 循环体语句; 几点说明: ❖for语句中表达式1、2、3类型任意,都可省略,但分号“;”不能省。 ❖无限循环:for(;;)不断执行循环体,循环不终止。 ❖For语句可以转换成while结构 表达式1; while(表达式2) { 循环体语句; 表达式3; }
例 用for语句构成循环 #include <stdio.h> void main( ) { int i,sum=0; for(i=1; i<=100; i++) //循环步长 sum+=i; printf("%d",sum); }
❖几种形式: ⚫ 省略表达式1:应在for之前为变量赋初值。 ⚫ 省略表达式2:循环条件始终为“真”,循环不终止。 ⚫ 省略表达式3:应另外设法使程序能够结束。 ⚫ 省略表达式1、3:完全等同于while语句。 ⚫ 三个表达式都省略:无初值,不判断条件,循环变量不增值,死循环。 ⚫ 表达式1、3可以是与循环无关的表达式,也可以是逗号表达式。 for ( s=0 , i=1 ; i<=100 ; i++ ) s=s+i; ⚫ 表达式2可以是关系、逻辑、算术、字符表达式,非0时,执行循环体,为0时退出循环。
/*正常形式*/ main( ) { int i ,sum=0; for(i=1;i<=100;i++) sum=sum+i; printf("%d",sum); }
/*省略表达式1*/ main( ) { int i,sum=0; i=1; for(;i<=100;i++) sum=sum+i printf("%d",sum); }
/*省略表达式3*/ main( ) { int i,sum=0; for(i=1;i<=100;) { sum=sum+i i++;} printf("%d",sum); }
/*省略表达式1、3*/ main( ) { int i,sum=0; i=1; for(;i<=100;) { sum=sum+i i++;} printf("%d",sum); }
break语句
【格式】break; 【功能】强制结束当前的循环,不在执行循环体中break语句后面的语句。 【说明】 1、break语句能用在三种循环语句和switch语句中,其作用是跳出本层循环或跳出switch语句。 2、通常,break语句与if语句联合使用,表明程序在满足一定条件下提前结束循环,在循环体中单独使用break语句是没有意义的。
Break用法
❖功能:在循环语句和switch语句中,终止并跳出循环体或开关体。 ❖说明: ⚫ break只能终止并跳出最近一层的结构。 ⚫ break不能用于循环语句和switch语句之外的任何其它语句之中。 ❖一般形式:break;
例 break举例:小写字母转换成大写字母,直至输入非字母字符 #include <stdio.h> void main() { char c; while(1) { c = getchar(); if(c >= 'a' && c <= 'z') putchar(c-32); else if(c >=‘A' && c<=‘Z') putchar(c); else break; //使无限循环结束 } }
continue语句
【格式】continue; 【功能】立即结束本次循环,转去判断循环控制条件是否成立,即跳过continue之后的语句,重新判断循环控制条件,决定是否继续执行循环。 【说明】 1、continue语句只能用于三种循环语句的循环体中。 2、通常,continue语句与 if 语句联合使用。在循环体中单独使用continue语句是没有意义的。 Continue语句 ❖功能:结束本次循环,跳过循环体中尚未执行的语句,进行下一次是否执行循环体的判断。 ❖continue 语句仅用于循环语句中。
break 和 continue 语句的区别
❖continue 语句只结束本次循环,break 语句则是结束整个循环。 ❖continue 语句只用于while, do-while, for循环语句中,break语句还可以用于 switch 语句中。 break语句和continue语句的区别主要体现在: 1、break语句能用于三种循环语句和switch语句,continue语句只能用于三种循环语句。 2、continue语句的作用是强制结束本次循环,而不是终止整个循环过程; break语句的作用是终止整个循环过程,不再判断循环控制条件是否成立。 需要注意的是,循环嵌套时break语句和continue语句均只影响包含它们的最内层循环,与外层循环无关。
循环的嵌套
循环语句的循环体内又包含另一个完整的循环结构称为循环的嵌套。多重循环是嵌套的循环结构,外面的循环语句称为“外层循环”,“外层循环”的循环体中的循环称为“内层循环”。原则上,循环嵌套的层数是任意的。for语句、while语句和do-while语句都允许循环的嵌套。 多重循环中,内层循环必须完整地包含在外层循环的循环体中,不能出现内、外层循环体交叉的情况,但是允许在外层循环体中包含多个并列的内层循环。
五 数组
数组是具有相同数据类型的一组有序数据的集合。 数组中的数据称为数组元素,数组元素通过数组名和下标引用,下标是数组元素在数组中的位置序号,表示该数组元素在数组中的相对位置。
一维数组
一维数组的定义: 数组同变量一样,必须先定义后使用。数组占用内存单元,在定义数组时必须指定数组元素的数据类型和数组元素的个数,这样才能为其分配相应的内存单元。 一维数组是只有一个下标的数组,其定义的一般形式: 数据类型符 数组名1[长度1],数组名2[长度2],…; 1、“数据类型符”是指数组元素的数据类型。数据类型可以是任何基本类型。 2、“数组名”与变量名一样,必须遵循标识符的命名规则。 3、“长度”必须是整型的常量表达式,必须用一对方括号括起来,表示数组的元素个数(又称数组长度),可由整型常量或符号常量组成,但其中不能出现变量。 需要注意的是,在数组定义时“长度”外面的一对方括号,以及数组元素引用时“下标表达式”外面的一对方括号,都是C语言语法规则要求的。 4、数组元素的下标是元素相对于数组首地址或起始地址的偏移量,所以从 0 开始顺序编号。 5、数组名表示整个数组所占用的内存空间的首地址。同一数组中的所有元素,按其下标的顺序占用若干连续的存储单元。 6、一个数组定义语句中可以只定义一个数组,也可以定义多个数组,还可以同时定义数组和变量。
#include <stdio.h> void main() { int i,a[10]; for(i=0;i<=9;i++) a[i]=i; for(i=9;i>=0;i- -) printf(“%d”,a[i]); } 运行结果: 9 8 7 6 5 4 3 2 1 0
符号常量: #define LENGTH 10 int a[LENGTH]
一维数组元素的引用
引用一维数组中任意一个元素的方法: 数组名[下标] 1、“下标”可以是一个整型常量、整型变量或整型表达式,其取值范围是0~(长度-1)。需要注意的是,在C程序运行过程中,系统并不自动检查数组元素的下标是否越界,即下标可以不在0~(长度-1)的合法范围内。 2、任何一个数组元素,本质上就是一个变量,它具有和相同类型单个变量一样的属性,可以被赋值,可以接收键盘输入的数据,也可以组成表达式。如果数组元素参与表达式运算,则必须已被赋值。 ❖定义方式: 数据类型 数组名[常量表达式]; ❖数组必须先定义,后使用 ❖只能逐个引用数组元素,不能一次引用整个数组。 ❖数组元素表示形式: 数组名[下标] 其中:下标可以是常量或整型表达式 例 int a[10]; printf(“%d”,a); // (Error) 必须 int j; for(j=0; j<10; j++) printf(“%d\t”,a[j]);
一维数组的初始化
数组元素和变量一样,可以在定义时对数组元素赋初值,称为数组的初始化。 一维数组初始化的一般格式:数据类型符 数组名[长度]={初值表},…; 1、如果对数组的全部元素赋初值,定义时数组长度可以省略(系统根据初值个数自动确定);如果被定义数组的长度,与初值个数不同,则数组长度不能省略。 2、“初值表”中初值个数、可以少于数组元素的个数,即允许只给部分元素赋初值。若只对数组的前若干个元素赋初值,则没有给出初值的元素均有默认的初值。 对于数值型数组默认的初值为0;对于字符型数组,默认的初值为空字符“‘\0’”(ASCII码值为0)。 具体地,一维数组的初始化可以分为以下几种情况: 1、给一维数组的全部元素赋初值。 例如:设int a[4]={1,2,3,4}; 2、给一维数组的部分元素赋初值。 例如:设int a[4]={1,2}; 3、初值的个数不能超过一维数组元素的个数。 例如:int a[4]={1,2,3,4,5}; //ERROR 一维数组a的长度是4,而初值的个数是5,初值的个数超出了数组长度,则编译时系统会报错。 4、给一维数组的全部元素赋初值时允许省略数组长度的说明。 例如:int a[4]={1,2,3,4}; 则可以写成:int a[]={1,2,3,4}; 需要注意的是,只能给数组元素赋值,不能给数组名赋值,因为数组名代表数组的首地址,数组名是常量。
实现的方法: ❖在定义数组时对数组元素赋初值。 int a[5]={1,2,3,4,5}; //在定义数组时,为数组元素赋初值;(在编译阶段使之得到初值) 等价于:a[0]=1; a[1]=2; a[2]=3; a[3]=4; a[4]=5; ❖只给一部分元素赋值。 如 int a[5]={6,2,3}; 等价于: a[0]=6; a[1]=2;a[2]=3; a[3]=0; a[4]=0; 如 int a[3]={6,2,3,5,1}; // (Error) ❖数组元素值全部为0。 int a[5]={0,0,0,0,0}; 或int a[5]={0}; ❖对整个数组元素赋初值时,可以不指定长度。 int a[]={1,2,3,4,5,6}; 编译系统根据初值个数确定数组大小
例 用数组来处理求(斐波那契数列)Fibonacci数列问题 #include <stdio.h> void main() { int i; int f[20]={1,1}; //核心语句 for(i=2; i<20; i++) f[i]=f[i-2]+f[i-1]; //核心语句 for(i=0; i<20; i++) { if(i%5==0) printf("\n"); //每行打印5个数 printf("%12d",f[i]);} }
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。 斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368........ 这个数列从第3项开始,每一项都等于前两项之和
二维数组
二维数组的定义
二维数组也需要先定义后使用,二维数组的定义形式: 数据类型符 数组名[行长度][列长度],…; 1、数据类型可以是任何基本类型。 2、数组名和变量名一样,必须遵循标识符的命名规则。 3、行长度说明二维数组有几行,列长度说明二维数组有几列。行长度和列长度都是一个“整型常量表达式”,表达式中不能出现变量。 4、二维数组的元素个数 = 行长度 * 列长度。 5、二维数组元素在内存中的排列顺序是“按行存放”,即先顺序存放第一行的各个元素,再存放第二行的各个元素,依此类推。 6、可以把二维数组看作是一种特殊的一维数组,其每个元素都是一个一维数组。 7、一个数组定义语句中可以只定义一个二维数组,也可以定义多个二维数组;可以在一条定义语句中同时定义一维和二维数组,同时还可以定义变量。 二维数组的初始化分为以下几种情况。 1、按行给二维数组的全部元素赋初值,其一般形式: 数据类型符 数组名[行长度][列长度]={{第0行初值表},{第1行初值表},…,{最后一行初值表}}; 2、按二维数组在内存中的排列顺序给各元素赋初值,即不分行给二维数组的全部元素赋初值,其一般形式: 数据类型符 数组名[行长度][列长度]={初值表}; 3、只对每行的前若干个元素赋初值,此时所有未赋初值的二维数组二维数组元素均为0值(即对于整型二维数组是0,对于实型二维数组是0.0,对于字符型二维数组是‘\0’)。 4、只对前若干行的前若干个元素赋初值,此时所有未赋初值的的数组元素均为0值。 5、如果对全部元素都赋初值,则“行长度”可以省略。需要注意的是只能省略“行长度”,但第二维的长度即“列长度”不能省略。使用这种方式赋初值,允许给出的初值不是列长度的整数倍。此时,行长度=初值个数整除列长度后加1。 6、如果分行给所有行的前若干个元素赋初值,则行长度可以省略。
❖二维数组理解
二维数组元素的引用
定义了二维数组后,就可以引用该数组的任意元素。二维数组元素的引用方法: 数组名[行下标][列下标] 1、“行下标”和“列下标”可以是整型常量、整型变量、整型表达式或符号常量。 2、“行下标”和“列下标”均从0开始,都应在已定义数组大小的范围内,不能越界。行下标的合法取值范围是0~(长度-1),列下标的合法取值范围是0~(长度-1)。 3、与一维数组元素的引用方法相同,任何一个二维数组元素均可以看作一个变量,它具有和同类型单个变量一样的属性,可以被赋值,可以接收键盘输入的数据,也可以组成表达式。如果二维数组元素参与表达式运算,则必须已被赋值。
二维数组程序举例
例 将二维数组行列元素互换,存到另一个数组中 #include <stdio.h> void main() { int a[2][3]={{1,2,3},{4,5,6}}; int b[3][2], i, j; printf("array a:\n"); for(i=0; i<=1; i++) //外循环,当i=0时,内循环 j 从0~2做一遍,然后再返回外循环 { for(j=0; j<=2; j++) { printf("%5d", a[i][j]); b[j][i]=a[i][j];} //核心语句 printf("\n"); } printf("array b:\n"); for(i=0; i<=2; i++) { for(j=0; j<=1; j++) printf("%5d", b[i][j]); printf("\n");} }
例 求二维数组中最大元素值及其行列号 #include <stdio.h> void main() { int a[3][4]={{1,2,3,4}, {9,8,7,6},{-10,10,-5,2}}; int i,j,row=0,colum=0,max; max=a[0][0]; for(i=0; i<=2; i++) for(j=0; j<=3; j++) if(a[i][j]>max) { max=a[i][j]; row=i; colum=j; } printf("max=%d, row=%d, colum=%d\n", max, row, colum); }
字符数组和字符串
字符数组的定义和初始化
一维字符数组,用于存储和处理一个字符串,其定义格式与一维数值型数组一样。 一维字符数组的定义形式: char 数组名[数组长度]={初值表}; 其功能是定义一维字符型数组,并为其赋初值。 例 char c[10], ch[3][4]; 二维字符数组用于同时存储和处理多个字符串,其定义格式与二维数值型数组一样。二维字符数组定义形式: char 数组名[行长度][列长度]={{初值表},{初值表},…,{初值表}}; 其功能是定义二维字符型数组,并为其赋初值。 字符数组 字符数组:存放字符数据的数组。 一维字符数组:存放一个字符串(每个数组元素存放一个字符) 二维字符数组:存放多个字符串(行数是字符串的个数) 字符数组的定义 ❖形式: ⚫ char 数组名[常量表达式] ⚫ char 数组名[常量表达式][常量表达式] ⚫ 常量表达式:整数、字符、符号常量 例 char c[10], ch[3][4]; ❖可以用整型数组存放字符型数据,但浪费存储空间。
一维字符数组的初始化
1、一维字符数组的初始化 (1)用字符初始化字符数组 例如: char str1[8]={‘p’,‘r ’,‘o’,‘g’,‘r ’,‘a’,‘m’,‘\0’};//一个一个字符输入 char str2[5]={‘C’,‘h’,‘i’,‘n’,‘a’}; (2)用字符串初始化字符数组 例如:char str1[8]={“program”} 字符串是用双引号括起来的一串字符。在字符串末尾系统会自动地添加一个字符串结束标志符‘\0’,表示字符串在此位置结束。字符‘\0’的ASCⅡ码值为0,称为空字符,它不作为字符串的有效标志,只起到标志结束的作用,但需要占用1字节的内存空间。 (3)用字符的ASCⅡ码值初始化字符数组。 例如:char str1[8]={112,114,111,103,114,97,109,0}; (4)初始化时如果只提供了部分元素的值。未提供初值的元素自动赋值为‘\0’。 例如:char str1[10]={“program”}
【例】
正确定义数组并初始化的是(C) A、int a[3]={1,2,3,4,5}; B、int a[3]={a,b,C,d,e}; C、int b[] [3]={1,2,3,4,5,6}; D、int b[3] [ ]-{1,2,3,4,5,6}; 解析: 一维数组如果对数组全部元素赋初值,定义时数组长度可以省略;如果被定义数组的长度,与初值个数不同,则数组长度不能省略。 二维数组如果对全部元素都赋初值,则“行长度”可以省略。需要注意的是只能省略“行长度”,但第二维的长度即“列长度”不能省略。
二维字符数组的初始化
2、二维字符数组的初始化 (1)用字符初始化二维字符数组。 例如:char s[2][10] ={{ ‘c’,‘o’,‘m’,‘p’,‘u’,‘t’,‘e’,‘r ’},{ ‘s ’,‘c ’,‘i’,‘e’,‘ n’,‘c ’,‘e ’ } }; (2)用字符串初始化二维字符数组。 例如:char s[2][10]={“computer ”,“science”}; 二维字符数组在初始化时,未提供初值的元素自动赋值为‘\0’。
字符数组的初始化 ❖逐个字符赋值 ❖用字符串常量
字符数组的输入和输出
1、一维字符数组的输入和输出 (1)使用scanf()和printf()函数 逐个字符输入和输出。在scanf()函数或printf()函数中用“%c”格式说明符每 次输入或输出一个字符,重复该过程完成整个字符串的输入和输出。 将字符串整个输入和输出。在scanf()函数或printf()函数中用“%s”格式说明 符一次性输入或输出整个字符串。 (2)使用gets()和puts()函数。使用字符串输入函数gets()或字符串输出函数 puts()将字符串整个输入或输出。
字符数组的引用
输出一个字符串 #include <stdio.h> void main() { char c[10]={'I',' ','a','m',' ','a',' ','b','o','y'}; int i; for(i=0; i<10; i++) printf("%c",c[i]); printf("\n"); }
字符数组的输入输出 ❖逐个字符I/O: %c ❖整个字符串I/O: %s 用字符数组名,不要加&; 输入串长度<数组维数 遇空格或回车结束自动加‘\0’ 用字符数组名, 遇‘\0’结束
二维字符数组的输入和输出
(1)二维字符数组的输入。 对于二维字符数组,除了可以使用初始化的方法赋值外,还可以从键盘赋值。 二维字符数组的每一行可以看作一个一维字符数组,可以将二维字符数组的每 一行作为一个一维字符数组进行输入和输出。 (2)二维字符数组的输出。 二维字符数组的输出一般有三种方式
//方法一: int i, j; for(i = 0; i < 3; i++) { for(j = 0; s[i][j] != '\0'; j++) printf("%c", s[i][j]); printf("\n");}
/*方法二:s[i]-->即每行的首地址, 分别输出第0行、第1行和第2行的内容*/ int i, j; for(i = 0; i < 3; i++) printf("%s\n", s[i]);
/*方法三:s[i]-->即每行的首地址 分别输出第0行、第1行和第2行的内容*/ int i, j; for(i = 0; i < 3; i++) puts(s[i]);
❖用字符串常量初始化字符数组
【例】
下列数组初始化后,元素值不全为0的是(B) A、int a[5]={0}; B、int a[5]={'0'}; C、static int a[5]; D、int a[5]={0,0,0,0,0}; 解析: int a[5]={'0'};是定义了一个长度是5的一维数组,并为第一个元素赋初值,即a[0]为‘0’,ASCII码为48,其余未赋初值的元素的默认值仅为‘\0’。
设有数组定义语句“int a[50];”,下列数组元素引用中下标不越界的是(A) A、a[0] B、a[-1] C、a[50] D、a[51] 解析: 一维数组中“下标”的取值范围是0~(长度-1)。数组a[50]的下标范围为0~49。
字符串的定义和初始化
字符串是指若干有效字符的序列,可以包括字母、数字、专用字符和转义字符等。 赋值方法有: ⚫ 第一种方式是按单个字符的方式赋初值,其必须有一个是字符串的结束标志符‘\0’; ⚫ 第二种方式是直接在初值表中给出一个字符串常量。 需要注意的是,由于系统在存储字符串常量时,会在字符串末尾自动加上一个结束标志符‘\0’,所以无需认为添加。另外,由于字符串结束标志符‘\0’也需要在字符数组中占用一个元素的存储空间,即1字节,因此在说明字符数组长度时,至少为字符串所需长度加1。
字符串的输入和输出
1、使用函数scanf( )和printf( ) (1)逐个字符的输入和输出。在scanf()或printf()函数中使用“%c”格式说明符每次输入或输出一个字符,重复执行多次完成整个字符串的输入或输出。 (2)字符串的整体输入和输出。在scanf()或printf()函数中使用“%s”格式说明符一次性输入或输出整个字符串。 说明: (1)scanf()函数或printf()函数中,与“%s”格式说明符对应的输入项或输出项应是地址。 (2)使用“%s”格式说明符输入字符串时,系统会自动在字符串末尾加一个‘\0’,将字符串连同添加的‘\0’一起存入字符数组中。 (3)使用“%s”格式说明符输入字符串时,遇到空格或回车则认为字符串输入结束。 (4)使用“%s”格式说明符输出字符串时,一旦遇到‘\0’则结束输出(‘\0’不输出),其后字符不在输出。 2、使用函数gets( )和puts( ) 使用字符串输出函数gets()或字符串输入函数puts()将字符串整体输入或输出。 字符串输入函数gets( ) 【调用格式】gets(字符数组名) 【参数】字符数组名是已经定义的字符数组名。 【功能】从标准输入设备即键盘上输入一个字符串(可以包含空格,仅以回车作为结束标志),并将其存储到指定的字符数组中。 【返回值】字符数组的首地址。 【说明】 1、gets( )函数输入的字符串的长度没有限制,编程者应保证字符数组有足够大的空间,存放输入的字符串。 2、gets( )函数和使用“%s”格式说明符的scanf()函数都可以从键盘输入字符串,但两者是有区别的。对于scanf()函数,回车和空格符都看成是输入字符串的结束标志;对于gets()函数输入字符串中允许包含空格,只有回车才看作是输入字符串的结束标志。
❖字符串输入函数gets
⚫ 格式:gets (字符数组) ⚫ 功能:从键盘输入一个以回车结束的字 符串放入字符数组中,并自动加‘\0’。 ⚫ 说明:输入串长度应小于字符数组维数
例:gets 和scanf 输入比较 scanf可以多写几个"%s" #include <stdio.h> void main( ) { char a1[15], a2[15] ; gets(a1); scanf(“%s”,a2); //遇到 空格 或‘/0’都认为结束 printf (“a1=%s\ n”,a1); printf (“a2=%s\ n”,a2); }
输入: china beijing china beijing 输出: a1=china beijing a2=china
❖字符串输出函数puts( )
字符串输出函数puts( ) 【调用格式】puts( ) 【参数】字符数组名是已经存放字符串的字符数组名。 【功能】把字符数组名中所存放的字符串,输出到标准输出设备即显示器,并以‘\n’取代字符串的结束标志‘\0’。所以用puts()函数输出字符串时,不要求另外加换行符。 【返回值】无。 【说明】 1、字符串中允许包含转义字符,输出时产生一个控制操作。 2、puts()函数和使用“%s”格式说明符的printf()函数都可以输出字符串,但两者是有区别的。对于printf()函数,不输出字符串结束标志符;对于puts()函数,字符串结束标志符‘\0’转换为‘\n’输出。此外,puts()函数一次只能输出一个字符串,而printf()函数用来输出字符串时一次可以输出多个。
字符串处理函数 包含在头文件 string.h 中 ❖字符串输出函数 puts ⚫ 格式: puts(字符数组) ⚫ 功能:向显示器输出一个字符串(输出完,换行) ⚫ 说明:字符数组必须以‘\0’结束。可以包含转义字符。 输出时‘\0’转换成‘\n’,即输出字符后换行。
例: #include <stdio.h> void main( ) { char a1[ ]=“china\ nbeijing” ; char a2[ ]=“china\ 0beijing” ; puts(a1); puts(a2); //遇'\0'后结束输出并转换成‘\n’换行 puts(“WUHAN” ); }
运行结果: china beijing china WUHAN
字符串比较函数strcmp( )
字符串比较函数strcmp( ) 【调用格式】strcmp( 字符串1, 字符串2 ) 【参数】“字符串1”和“字符串2”可以是字符串常量,也可以是已经存放字符串的字符数组名。 【功能】比较两个字符串的大小,即两个字符串从左到右逐个字符进行比较 (按照ASCⅡ码值的大小进行比较),直到出现不同字符或遇到‘\0’为止。 如果“字符串1”等于“字符串2”,则函数返回值为0。 如果“字符串1”大于“字符串2”,则函数返回值是正整数(大于0的整数)。 如果“字符串1”小于“字符串2”,则函数返回值是负整数(小于0的整数)。 【说明】 1、如果一个字符串是另一个字符串从头开始的子串,则母串为大。 2、不能使用关系运算符“==”比较两个字符串,只能调用strcmp()函数进行处理。
❖字符串比较函数strcmp ⚫ 格式:strcmp(字符串1,字符串2) ⚫ 功能:比较两个字符串 ⚫ 比较规则:对两串从左向右逐个字符比较(ASCII码), 直到遇到不同字符或‘\0’为止。 ⚫ 返回值:返回int型整数。其值是ASCII码的差值 a. 若字符串1< 字符串2, 返回负整数 b. 若字符串1> 字符串2, 返回正整数 c. 若字符串1= = 字符串2, 返回零 ⚫ 说明:字符串比较不能用“==”,必须用strcmp, 虽然编译无错,但结果不对 if(str1==str2) printf(“yes”); // ERROR if(strcmp(str1,str2)==0) printf(“yes”);
例:字符比较 #include <stdio.h> void main( ) { int i, j, k; char a1[ ]=“wuhan”, a2[ ]=“beijing” ; i=strcmp(a1,a2); j=strcmp(“china”, “korea”); k=strcmp(a2, “beijing” ); printf(“i=%d\ nj=%d\ nk=%d\ n”, i, j, k); }
i=21 i=w-b=119-98=21 j=-8 j=c-k=99-107=-8 k=0 k=b-b=98-98=0 运行结果: i=21 j=-8 k=0
字符串复制函数strcpy( )
【调用格式】strcpy( 字符数组名, 字符串, [整型表达式] ) 【参数】“字符数组名”是已经定义的字符数组名。 “字符串”可以是字符串常量,也可以是已经存放字符串的字符数组名。 “整型表达式”可以是任何整型表达式,这一参数可以省略。 【功能】将“字符串”的前“整型表达式”个字符组成新的字符串复制到“字符数组”中,若省略“整型表达式”,则将“字符串”完整地复制到“字符数组中”,字符数组的原有内容被覆盖。 【返回值】字符数组的首地址 【说明】 1、字符数组长度必须足够大,以便容纳复制过来的字符串。复制时,连同字符串结束标志‘\0’一起复制。 2、不能用赋值运算符“=”将一个字符串直接赋值给一个字符数组,只能调用strcpy( )函数处理。
❖字符串拷贝函数strcpy ⚫ 格式:strcpy(字符数组1,字符串2) ⚫ 功能:将字符串2,拷贝到字符数组1中去 ⚫ 返值:返回字符数组1的首地址 ⚫ 说明: (1)字符数组1必须足够大,>字符串2 (2)字符数组1必须是数组名形式(str1), 字符串 2可以是字符数组名或字符串常量。 (3)拷贝时‘\0’一同拷贝 (4)不能使用赋值语句为一个字符数组赋值 例 char str1[20],str2[20]; str1={“Hello!”}; // ERROR str2=str1; // ERROR (5)可以只复制字符串2中的前几个字符,来取代字符数组1的前几个字符。 strcpy(str1,str2,2) —— 复制前2个。
例 有3个字符串,要求找出其中最大者。srt[i],以行为单位 #include <stdio.h> #include <string.h> void main() { char string[20],str[3][20]; int i; for(i=0;i<3;i++) gets(str[i]); if(strcmp(str[0],str[1])>0) strcpy(string,str[0]); else strcpy(string,str[1]); if(strcmp(str[2],string)>0) strcpy(string,str[2]); printf("\nThe largest string is:\n%s\n",string); }
字符串连接函数strcat( )
【调用格式】strcat( 字符数组名, 字符串 ),参数第一个一定是字符数组 【参数】“字符数组名”是已经定义的存放字符串的字符数组名。 “字符串”可以是字符串常量,也可以是已经存放字符串的字符数组名。 【功能】把“字符串”连接到“字符数组”中字符串的尾端(最后一个有效字符的后面),组成新的字符串并存储到“字符数组”。“字符数组”中原来的结束标志,被“字符串”的第一个字符覆盖,而“字符串”在操作中未被修改。 【返回值】字符数组的首地址。 【说明】 1、由于没有边界检查,编程者应注意保证字符数组长度足够大,以便容纳连接后的新字符串;否则,会因长度不够出现问题。 2、连接前两个字符串都有结束标志‘\0’,连接后字符数组中存储的字符串结束标志‘\0’被舍弃,只在新字符串的最后保留一个‘\0’。
❖字符串连接函数strcat ⚫ 格式:strcat (字符数组1,字符数组2) ⚫ 功能:把字符数组2连到字符数组1后面 ⚫ 返值:返回字符数组 1 的 首地址 ⚫ 说明: (1)字符数组1必须足够大 (2)连接前,两串均以‘\0’结束;连接后,串1的‘\0’取消,新串最后加‘\0’。
例:用strcat连接两个字符数组 #include <stdio.h> void main( ) { char str1[30]={“People’s Republic of “}; char str2[]={China”}; printf (“%s\n”,strcat(str1,str2)); } //或直接 strcat(str1,"China"),效果一样
str1: People’s Republic of \0 str2: china\0 str1: People’s Republic of china\0
测字符串长度函数strlen( )(其中len是length的缩写)
【调用格式】strlen(字符串) 【参数】“字符串”可以是字符串常量,也可以是已经存放字符串的字符数组名。 【功能】测字符串(字符串常量或字符数组)的实际长度(不包含字符串结束标志符‘\0’)。 【返回值】字符串的实际长度。
❖字符串长度函数 strlen ⚫ 格式:strlen(字符数组) ⚫ 功能:计算字符串长度 ⚫ 返值:返回字符串实际长度,不包括‘\0’在内 例 对于以下字符串,strlen(s)的值为: (1)char s[10]={‘A’,‘\0’,‘B’,‘C’,‘\0’,‘D’}; (2)char s[ ]=“\t\v\\\0will\n”; (3)char s[ ]=“\x69\082\n”; 结果:1 3 1
例:测试字符串长度 #include <stdio.h> void main( ) { char a1[10]=“ china” ; printf (“%d\ n”,strlen(a1)); printf (“%d\ n”, strlen(“beijing\ 0wuhan”)); } 运行结果:5 7
例 把输入的字符串逆序排列,并显示(交换算法)。 /*逆序排列用交换算法,求出字符串最后一个字符下标, 然后将第一个和最后一个交换,第二个和倒数第二个交换,.......。*/ #include <stdio.h> void main() { char str[80], temp; int i, j; printf("Enter a string:\n"); scanf("%s",str); for(i=0, j=strlen(str)-1; i<j ; i++,j--) { /*交换 i, j 两个元素*/ temp=str[i]; str[i]=str[j]; str[j]=temp; } printf("\nReversed string:\n%s\n",str);}
字符串大写字母转换成小写函数strlwr( )
【调用格式】strlwr(字符串) 【参数】“字符串”可以是字符串常量,也可以是已经存放字符串的字符数组名。 【功能】将字符串中的大写字母转换成小写字母,其他字符(包括小写字母和非字母字符)不转换。
字符串小写字母转换成大写函数strupr( )
【调用格式】strupr(字符串) 【参数】“字符串”可以是字符串常量,也可以是已经存放字符串的字符数组名。 【功能】将字符串中的小写字母转换成大写字母,其他字符(包括大写字母和非字母字符)不转换。
❖大写字母转换成小写字母函数strlwr ⚫ 格式:strlwr(字符串) ❖小写字母转换成大写字母函数strupr ⚫ 格式:strupr(字符串)
例:字符转换 #include <stdio.h> void main( ) { char a1[6]=“CHinA”, a2[ ]=“wuHAn” ; printf (“%s\ n”,strlwr(a1)); printf (“%s\ n”,strupr(a2)); } 运行结果:china WUHAN
六 函数
函数的概念和模块化程序设计
C语言函数的特点和分类: 特点: 1、一个C程序由一个或多个函数组成,其中必须有且只能有一个main函数(称为主函数)。 2、C程序的执行从主函数开始,如果在主函数中调用其他函数,调用后返回到主函数,并在主函数中结束整个程序的运行。 3、主函数可以调用其他函数,但其他函数不能调用主函数。主函数由操作系统调用,其他函数之间可以互相调用。 4、函数之间,没有从属关系,互相独立,不能嵌套定义。 分类: 1. 从函数定义的角度,分为库函数和用户函数。 2. 从函数有无参数的角度,函数分为有参函数和无参函数。 3. 从函数有无返回值的角度,函数分为有返回值函数和无返回值函数。 4. C语言允许将一个源程序分放在不同的程序文件中,采用分块编译、链接生成一个目标程序,其中每个程序文件称为一个”编译单元”。每个编译单元可以包含若干个函数。从函数作用范围的角度,函数分为外部函数和内部函数。外部函数是可以被任何编译单元调用的;内部函数只能在本编译单元被调用。
函数的概念
一个大的程序一般应分为若干个程序模块,每个模块实现一个特定的功能,这些模块称为子程序,在C语言中子程序用函数实现。
函数的一般格式
任何函数(包括主函数)都是由函数首部和函数体两部分组成。 函数定义的一般形式: 数据类型符 函数名 ( 形式参数表 ) { 数据定义语句部分; 执行语句部分; } 1、函数首部 “数据类型符”规定本函数返回值的数据类型,可以是各种基本数据类型,也可以是指针类型(只要在函数名前面加一个*)。需要注意的是,如果函数无返回值,数据类型符使用void。 需要注意的是,函数首部末尾不能加分号。 “函数名”是一个标识符,在同一个编译单元中的函数不能重名。 “形式参数表”是使用逗号分隔的若干个形式参数及其数据类型的说明,具体格式: 数据类型符 形式参数1[,数据类型符 形式参数2,…] 2、函数体 “数据定义语句部分”由若干个数据定义语句组成,用来定义本函数中使用到的变量、数组和指针变量等。 “执行语句部分”由本函数中完成函数功能的程序段组成。如果是有返回值的函数,则其中至少有一条返回语句“return(表达式);”,表达式的值就是本函数的返回值。返回语句中的圆括号可以省略。函数的返回值是通过函数中的返回语句获得的。返回语句的一般格式是return(返回值表达式);。如果是无返回值的函数,则返回语句应为“return;”,也可以省略返回语句。返回语句在函数体的执行语句部分的位置是任意的。返回语句的功能是结束本函数的运行,返回到主调函数的函数调用语句继续执行。 【说明】 C语言中,所有函数(包括主函数)都是平行的。一个函数的定义,可以放在程序中的任意位置,主函数之前或之后。但是在一个函数的函数体内不能再定义另一个函数,即不允许函数的嵌套定义。
各类函数定义的格式
无参函数的定义
1、无参函数的定义 如果一个函数在被调用时不需要主调函数向其传递数据,则该函数可以定义为无参函数,其定义的一般格式: 数据类型符或void 函数名(void) { 数据定义语句部分; 执行语句部分; } 括号中的void表示函数没有参数,void也可以省略,而只在函数名后面,跟一对空括号,但这对空括号不能省略。 无参函数的定义形式 ❖类型标识符: ⚫用于指定函数带回的值的类型,不写时为int型。 ⚫不带回值时可以不写。
例 无参函数 printstar( ) { printf(“**********\n”); } 或 printstar(void ) { printf(“**********\n”); }
有参函数的定义
2、有参函数的定义 如果一个函数在被调用时需要主调函数向其传递数据,则该函数可以定义为有参函数,其定义的一般格式: 数据类型符或void 函数名(形式参数表) { 数据定义语句部分; 执行语句部分; }
有参函数定义的一般形式【例】 有参函数 int max(int x, int y) //现代风格,其中默认为int类型,定义时不写int也可以,如max(int x, int y) { int z; z=x>y?x:y; return(z); }
空函数
❖为扩充功能预留,在主调函数中先占一个位置。 类型标识符 函数名() { } 例 空函数 dummy( ) { }//函数体为空 对形参的声明的传统方式 ❖即把对形参的声明放在函数定义的下一行 类型标识符 函数名(形参表) 形参类型说明 { 说明部分 语句 }
例 有参函数(传统风格) int max(x,y) int x , y ; { int z; z=x>y?x:y; return(z); }
无返回值函数的定义
3、无返回值函数的定义 void 函数名(形式参数表或void) { 数据定义语句部分; 执行语句部分; }
有返回值函数的定义
4、有返回值函数的定义 数据类型符 函数名(形式参数表或void) { 数据定义语句部分; 执行语句部分; } 有返回值函数和无返回值函数的主要区别体现在两个方面。 一是数据类型符,有返回值函数的数据类型符可以选取任意的数据类型符,而无返 回值函数的数据类型符只能是void;二是有返回值函数的函数体中必须有语句 return(表达式);而无返回值函数的函数体中的语句return;可以有也可以省略。
【例】
1.利用定义函数时设置的形参和调用函数时给出的实参来传递数据的是(A) A、值传递和地址传递 B、值传递和返回值 C、地址传递和全局变量传递 D、返回值和全局变量传递 解析: 函数返回值的类型即函数类型,是指返回给主调函数的结果的类型,应根据具体函数的功能确定。所以,函数返回值的类型是由定义的函数类型决定。
2.在C语言中,函数返回值的类型是由(A) A、定义的函数类型决定 B、return语句中表达式的类型决定 C、调用该函数的主调函数类型决定 D、调用该函数时系统状态决定 解析: 函数返回值类型即函数类型,是指返回给主调函数的结果的类型,应根据具体函数的功能确定。
返回值类型
函数返回值的类型即函数类型,是指返回给主调函数的结果的类型,应根据具体函数的功能确定。如果函数不返回任何值,则函数返回值类型定义为void,称为“无类型”。
函数调用的一般格式
函数调用的一般形式:函数名([实际参数表]); 1、实参的个数、类型和顺序,应该与被调函数所要求的形参个数、类型和顺序 一致,才能正确地进行函数之间的数据传递。 2、当有多个实参时,实参之间用逗号分隔。 3、无参函数没有参数,则调用时函数名后面跟一对空的圆括号,但这对圆括号 必须保留。
【例】
关于函数,下列说法正确的是(B) A、可以嵌套定义 B、定义时函数名前可以带星号 C、必须有返回语句 D、必须有形式参数 解析: 函数首部:“数据类型符”规定本函数返回值的数据类型,可以是各种基本数据类型,也可以是指针类型(只要在函数名前面加一个*)。
函数的形式参数和实际参数
形参是在函数定义时设置的,用来接收从主调函数传来的对应的实参数据。实参 是调用函数时的实际参数,实参可以是常量、变量或表达式,也可以是函数的 返回值,无论哪种形式必须有确定的值。实参在数量、类型和顺序上与形参必须 一一对应和匹配。 形式参数和实际参数 ❖形式参数:定义函数时函数名后面括号中的变量名 ❖实际参数:调用函数时函数名后面括号中的表达式 ❖几点说明: ⚫实参可以是常量、变量或表达式。必须有确定的值。当函数调用时, 将实参的值传递给形参,若是数组名,则传送的是数组首地址。 ⚫形参必须指定类型,只能是简单变量或数组,不能是常量或表达式 ⚫形参与实参类型一致,个数相同顺序相同。 ⚫若形参与实参类型不一致,自动按形参类型转换———函数调用转换 ⚫形参在函数被调用前不占内存;函数调用时为形参分配内存;调用结束,内存释放 ⚫实参对形参的数据传送是值传送,也是单向传送,当被调函数的形参发生变化时 ,并不改变主调函数实参的值。形、实参占据的是不同的存储单元
例 比较两个数并输出大者 #include <stdio.h> void main() { int max(int x,int y); int a,b,c; scanf("%d,%d",&a,&b); c = max(a,b); //实参 printf("Max is %d",c);} max(int x, int y) //形参 { int z; z=x>y ? x:y; return(z); }
运行: 6,8 回车 Max is 8
例:形、实参占据的是不同的存储单元 #include <stdio.h> void main( ) { int a=2,b=3; printf (“a=%d, b=%d\ n”,a, b); printf(“&a=%x,&b=%x\n", &a, &b); add(a,b); printf(“a=%d,b=%d\n”, a,b); printf(“&a=%x,&b=%x\n”, &a, &b); } add(int x,int y) { x=x+8; y=y+12; printf(“x=%d,y=%d\ n”,x,y); printf(“&x=%x,&y=%x\n”,&x,&y); }
函数的返回
返回语句有以下两种使用方式: 1、无返回值的函数 return; 2、有返回值的函数 return(返回值表达式);或return 返回值表达式; 函数的返回值是通过被调函数中的返回语句获得的。若函数没有返回值,则应将函 数返回值的类型定义为void,在函数体中可以没有返回语句,也可以使用无返回值 的return语句,但是不能使用有返回值的return语句;若函数有返回值,则应将函 数定义为非void类型,在函数体中只能使用有返回值的return语句,而且必须有该 语句。函数的返回值只能有一个,但可以有多个return语句,一旦执行到其中一个 return语句,则立即返回主调函数,被调函数中的其他语句不再执行。 return语句中表达式的数据类型应与函数首部定义的函数返回值的类型一致。 函数的返回值 ❖返回语句形式: return(表达式);或 return 表达式; ❖功能:使程序控制从被调用函数返回到调用函数中,同时把返值带给调用函数 ❖说明: ⚫函数的返回值,必须用 return 语句带回。 ⚫return 语句只能把一个返值传递给调用函数。 ⚫函数中可有多个return语句,执行哪一个由程序执行情况来定。 if(a>b) return(a); else return(b); ⚫return 后的值可以是一个表达式,如:return(x > y ? x : y); ⚫返回值的类型为定义的函数类型,不指定的按整型处理。 如: int max(int x, int y) float min(float a,float b) double abc(float d1,float d2)
例:无返回值函数 void swap(int x,int y ) { int temp; temp=x; x=y; y=temp; }
例: 函数返回值类型转换 #include <stdio.h> void main() { int max(float x,float y); float a,b; int c; scanf("%f,%f",&a,&b); c=max(a,b); printf("Max is %d\n",c); } max(float x, float y) { float z; z=x>y?x:y; return(z); }
输入:1.5,2.5 输出:Max is 2
函数声明
函数声明又称为函数原型,其一般格式: [存储类型][数据类型符] 函数名(形参类型[形参名1][,形参类型[形参名2],…]); 若被调函数为: 1、库函数,除了少数库函数(如scanf()、printf())外,都要求在本文件开头 用文件包含命令#include<头文件名.h>或#include“头文件名.h”包含被调函 数的信息。 2、用户函数,若主调函数与被调函数不在同一个编译单元,则在定义函数的编 译单元中必须将该函数定义为外部函数,同时在主调函数的函数体或主调函数 所在编译单元的开头将被调函数按照如下格式进行声明: 函数声明 extern 数据类型符 函数名(形参表); 需要注意的是,函数声明是一条语句,末尾加分号;而函数首部不是语句,末 尾不加分号。 说明: 1、函数声明告知编译程序函数返回的数据类型、函数所要接收的参数个数、类 型和顺序,编译程序利用函数声明校验函数调用是否正确。 2、函数声明中可以只说明形参类型和形参个数,而无须说明形参名。 3、函数声明可以在主调函数中,也可以在所有函数的外部。 4、函数声明、函数定义、函数调用要保持一致。 5、如果程序中没有包含函数声明,则编译程序会用第一次出现时的该函数来构 造函数声明。
例 对被调用的函数作声明 #include <stdio.h> /* float x, y; 假如此处声明全局变量x,y; 其作用域是以下全部区域,则声明时无需加参数,包括被调用函数(即形参)也不用带参数,此时如果改变了x,y的值,同样会被执行,即共用x,y存储单元的空间,操作的是与x,y相同的地址public */ void main() { float add(float x,float y ); /*对被调用函数的声明,声明时可以不用写x,y;即float add(float, float);如果被调用函数写在main之前,则此处无需声明*/ float a,b,c; scanf("%f,%f",&a,&b); float add(float,float); c=add(a,b); printf("sum is %f",c); } float add(float x, float y) /*函数首部*/ { float z; //函数体 z=x+y; return(z); }
输入:3.6 ,6.5 输出:sum is 10.100000
函数的参数和数据传递方式
C语言规定在函数之间传递数据包括值传递、地址传递、返回值和全局变量传递四种方式。 值传递方式: 值传递方式是在形参和实参之间传递数据的一种方式,值传递方式传递的是参数值。 判断是否是值传递方式的唯一方法是看函数定义时的形参是不是变量形式。如果形参是变量,则是值传递方式。 值传递方式能够确保不管在被调函数中怎样操作或改变形参的内容,但主调函数中的实参并末发生变化。实参对形参的数据传递是单向的,即只能从主调函数将实参值传递给被调函数的形参,而不能把被调函数的形参值反向传递给主调函数的实参。
数组元素作为函数实参
数组元素只能用做函数实参,和变量作为函数实参一样。在调用函数时,把数组元素的值传递给形参,实现单向的值传递方式。 需要注意的是: 1、数组元素作为实参时,只要数组的类型和函数形参的类型一致即可,并不要求函数的形参也是下标变量。 2、普通变量或下标变量作为函数参数时,形参变量和实参变量由编译程序分配不同的内存单元。
数组名作为函数参数
数组名作为函数参数时,既可以作为形参,也可以作为实参。数组名作为函数参 数时,要求形参和相应的实参必须是同类型的数组,都必须有明确的数组定义。 如果形参是数组名,则传递方式称为“地址传递方式”。 数组名作为形参时,实参向形参传递的是实参数组的首地址,实参数组和形参 数组的各元素按照存储结构一一对应共享存储空间。 ❖数组名作函数参数时,实参和形参两个数组共占同一段内存单元,形参数组 的元素值改变会使实参数组元素的值同时变化。
例 用选择法对数组中的10个整数按由小到大排序 #include <stdio.h> void main() { void sort(int array[ ],int n); int a[10], i; printf(“enter the array\n”); for(i=0; i<10; i++) scanf("%d",&a[i]); sort(a,10); printf(“the sorted array: \n”); for(i=0; i<10; i++) printf("%d ",a[i]); printf("\n");}
选择法排序思路(与冒泡排序互逆) void sort(int array[ ],int n) { int i,j,k,t; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(array[j]<array[k]) k=j; t=array[i]; array[i]=array[k]; array[k]=t; } }
利用返回值的数据传递方式
⚫ 利用返回值的数据传递方式并不是在形参和实参之间进行数据传递,而是通 过函数调用直接返回一个值到主调函数。 ⚫ 利用返回值传递数据,在定义函数时需要注意以下两点: 1、函数首部中需要有“数据类型符”,说明该该函数返回值的数据类型。 2、函数体中需要有语句“return(表达式);”,其中的表达式即是函数的返回值。
变量的存储类型和作用域
⚫ C语言中变量的存储类型分为自动型(auto)、寄存器型(register)、外部型(extern)和静态型(static)四种。 ⚫ 变量的存储方式分为静态存储方式和动态存储方式两大类。 ⚫ 自动型变量和寄存器型变量属于动态存储方式;所谓动态就是每声明一次,就会重新分配存储单元,原来的值就会消失。 ⚫ 外部型变量和静态型变量属于静态存储方式(static)。静态则是始终在同一个存储单元。
自动型变量auto
【格式】[auto] 数据类型 变量表; 关键字auto可以省略。当省略auto时,C语言默认auto型,即定义变量时不特别声明存储类型的都默认自动型变量。 【说明】 1、自动型变量存放在内存的动态存储区,属于动态存储方式。从分配到释放之间是自动型变量的生存期。 2、定义自动型变量时如果没有初始化,则其值是不确定的,是系统赋予的随机数;如果初始化,则赋初值操作是在进入所定义的函数或复合语句时进行的,且每次都要重新初始化。 3、由于自动型变量的作用域和生存期都局限于定义它的个体内,因此不同的个体中允许使用重名的变量而不会混淆。
静态型变量static
【格式】static 数据类型 变量表; 【说明】 1、静态型变量存放在内存的静态存储区,属于静态存储方式。静态变量的的生存期与程序的运行期相同。因为函数中的静态变量在函数调用结束时不释放占用的存储空间,因此其值能保留下来,供下一次调用该函数时使用。 2、定义静态型变量时如果没有初始化,则编译系统自动赋值0(整型和实型)或‘\0’(字符型)。 3、静态型变量如果初始化,只有第一次执行定义语句时随着分配内存赋予初值,且只赋一次初值。 4、静态型变量在程序运行期间始终存在,但在其作用域以外不能使用。
寄存器型变量register
【格式】register 数据类型 变量表; 【说明】 1、只有局部变量才能定义为寄存器型变量,全局变量不行。 2、对于占用字节数较多的变量,如long、float和double型的变量一般不能定义为寄存器型变量。 3、对寄存器型变量的实际处理随系统而异。 4、由于CPU具有的通用寄存器数量有限,所以允许定义的寄存器型变量的数量也有限,不能定义任意多个寄存器型变量,具体限制取决于运行环境和编译系统。
外部型变量extern
【格式】extern 数据类型 变量表; 【说明】 外部型变量是专门用于在多个编译单元之间传递数据的。
全局变量和局部变量
生存期覆盖了定义点到整个程序结束的变量称为“全局变量”;生存期只覆盖了某个函数(或复合语句)的变量称为“局部变量”。 全局变量存放在静态存储区,属于静态存储方式。全局变量的生存期与程序运行期相同。 定义全局变量时如果没有初始化,则自动赋值0(整型和实型)或‘\0’(字符型)。 1、用extern在一个文件内扩展全局变量的作用域。全局变量的作用域是从它的定义处开始到本程序文件末尾。 2、用extern将全局变量的作用域扩展到其他文件。 3、用static将全局变量的作用域限制在本文件。 对于局部变量而言,声明存储类型的作用是指定变量的存储位置(静态存储区或动态存储区)和生存期;对于全局变量而言,声明存储类型的作用是扩展或限制变量的作用域。
变量按其作用域,可分为局部变量和全局变量。 局部变量——内部变量 ❖定义:在函数内定义,只在本函数内有效 ❖说明: ⚫main中定义的变量只在main中有效 ⚫不同函数中同名变量,占不同内存单元 ⚫形参属于局部变量 ⚫可定义在复合语句中有效的变量 ⚫局部变量可用存储类型:auto register static (默认为auto)
例 不同函数中同名变量 void main() { int a,b; a=3; b=4; printf("main:a=%d,b=%d\n",a,b); sub(); printf("main:a=%d,b=%d\n",a,b); } sub() { int a,b; a=6; b=7; printf("sub:a=%d,b=%d\n",a,b); }运行结果: main:a=3,b=4 sub:a=6,b=7 main:a=3,b=4
例 复合语句中变量 #define N 5 void main() { int i; int a[N]={1,2,3,4,5}; for(i=0;i<N/2;i++) { int temp; temp=a[i]; a[i]=a[N-i-1]; a[N-i-1]=temp; } for(i=0;i<N;i++) printf("%d ",a[i]); } 运行结果:5 4 3 2 1
全局变量——外部变量 ❖定义:在函数外定义,可为本文件所有函数共用,也叫外部变量。 ❖有效范围:从定义变量的位置开始到本源文件结束,及有extern说明的其它源文件 ❖几点说明: ⚫全局变量的使用,增加了函数间数据联系的渠道,同一文件中的所有函数都能 引用全局变量的值,当某函数改变了全局变量的值时,便会影响其它的函数。
内部变量和外部变量
1、内部变量 在一个函数(或复合语句)内部定义的变量称为“内部变量”。 关于内部变量的作用域,需要注意以下几点: (1)主函数中定义的内部变量,只能在主函数中使用,其他函数不能使用。 (2)形参变量也是内部变量,仅限于函数内使用。 (3)允许在不同函数中使用重名的变量,它们代表不同的对象,分配不同的内存单元,互不干扰,也不会发生混淆。 (4)复合语句中也可以定义变量,所定义的变量是内部变量,其作用域只在该复合语句范围内。
2、外部变量 在函数外部定义的的变量称为“外部变量”。外部变量不属于任何一个函数,其 作用域是从定义外部变量的位置开始到本源程序文件结束。 对于外部变量,需要注意以下几点: (1)在同一源程序文件中,允许外部变量和内部变量重名。 (2)外部变量的作用域是从定义点到本文件结束。
变量的使用说明
1、外部变量只能定义为无存储类型或静态型(static),不能定义为自动型(auto)或寄存器型(register)。内部变量可以定义为自动型和寄存器型和静态型。 2、外部变量都是全局变量,在整个程序运行期间不释放所分配的内存空间,其作用域是从定义点带源程序结束,即在定义后的任何一个函数中均可使用。 3、声明为auto或register类型的内部变量是局部变量,只在所定义的函数或复合语句中存活,其作用域是所定义的函数或复合语句,一旦离开了所定义的函数或复合语句则不能在再使用,如果再次进入所定义的函数或复合语句,则需重新定义后再使用。 4、声明为static存储类型的内部变量是局部变量,在整个程序运行期间都不释放。其作用域只是所定义的函数或复合语句,一旦离开所定义的函数或复合语句则不能再使用,但该变量仍在生存期,所分配的内存并不释放,仍保留退出时的值。 5、外部变量是不能重名的。
利用全局变量的数据传递方式
利用全局变量传递数据的方式并不是在形参和实参之间传递数据,而是利用在主调函数和被调函数都有效的全局变量,在主调函数和被调函数之共享数据。 全局变量有两种: 一是在任何函数之外定义的全局变量,其作用域覆盖了定义点到整个源程序结束的所有函数,这种全局变量叫作“外部变量”; 二是在函数体内部声明为static型的变量,该变量从函数返回后,仍保留所分配的内存,但是不能使用,其作用域仍是该函数体内。这种全局变量叫作“内部变量”。
【例】
若全局整型变量未初始化,则默认的初始值是____0____。 解析:定义全局变量时如果没有初始化,则自动赋值0(整型和实型)或‘\0’(字符型)。
若未指定局部变量的存储类型,则其隐含的存储类型是___auto型或自动型_____。 解析:当省略auto时,C语言默认为auto型,即定义变量时不特别声明存储类型 的都默认为自动型变量。
用 static 声明局部变量
若希望函数调用结束后,局部变量的值保留,则指 定该变量为静态局部变量,用static对变量加以声明。
例 局部静态变量值具有可继承性 main() { void increment(void); increment(); increment(); increment(); } void increment(void) { int x=0; //每次调用时都重新开辟空间,重新赋值 x++; printf(“%d\n”,x); } 运行结果:1 1 1
main() { void increment(void); increment(); increment(); increment(); } void increment(void) { static int x=0; //第二、第三次调用时不再重新开辟空间,不再赋值,直接做x++ x++; printf(“%d\n”,x); } 运行结果:1 2 3
函数的嵌套调用和递归调用
C语言允许函数的嵌套调用和递归调用。 1. 函数的嵌套调用 函数的嵌套调用是指在调用函数时,被调函数又调用了其他函数或自身。当被调 函数是函数自身时,称为函数的递归调用,递归调用是嵌套调用的一种特例。 2. 函数的递归调用 在调用一个函数的过程中又直接或间接地调用其自身,称为递归调用。递归调用 又称为自调用函数。
函数的嵌套调用
C中的函数: 不允许嵌套定义,函数间的关系是平行的、独立的。 允许嵌套调用,即在调用某函数过程中又调用另一函数。
例 输入两个整数,求平方和 #include <stdio.h> int fun1(int x,int y); void main(void) { int a,b; scanf(“%d%d”,&a,&b); printf(“The result is:%d\n”,fun1(a,b) ); } int fun1(int x,int y) { int fun2(int m); return ( fun2(x)+fun2(y) ); } int fun2(int m) { return (m*m); } 输入: 3 4 输出: The result is: 25
#include <stdio.h> int dif(int x,int y,int z); int max(int x,int y,int z); int min(int x,int y,int z); void main() { int a,b,c,d; scanf("%d%d%d",&a,&b,&c); d=dif(a,b,c); printf("Max-Min=%d\n",d); } int dif(int x,int y,int z) { return max(x,y,z) - min(x,y,z); } int max(int x,int y,int z) { int r; r=x>y?x:y; return(r>z?r:z); } int min(int x,int y,int z) { int r; r=x<y?x:y; return(r<z?r:z); }
函数的递归调用
递归:在函数调用过程中,直接或间接的调用自身。 递归调用方式 ❖直接递归调用:在函数体内又调用自身
int f(int x) { int y,z; …… z=f(y); ……. return(2*z); }
例 有5个人,第5个人比第4个人大2岁,第4个人比第3个人大2岁,……第2个人比第1个人大2岁,第1个人10岁,问第5个人多大? age(int n) { int c; if(n==1) c=10; else c=2+age(n-1); return(c); } #include <stdio.h> void main( ) { printf(“%d\ n”, age(5)); } 运行结果:18
有些问题,可以用递推,也可以用递归的方法解决。 ❖递推:从一个已知的事实出发,按一定规律推出下 一个事实,再从已知的新的事实,推出下一个新的 事实.
例 用递推法求n! ,即从1开始, 乘2, 乘3....一直到n #include <stdio.h> void main( ) { int i, s=1; for(i=1;i<=5;i++) s=s* i; printf(“s=%d\ n”,s); } 运行结果:s=120
❖递归:在函数调用自身时,要给出结束递归的条件。 ⚫ 先回推再递推 ⚫ 如:n!, 5!=5 × 4! 4!=4 × 3! 3!=3 × 2! 2!=2 × 1! 1!=1 0!=1
例 用递归方法求n! #include <stdio.h> void main() { float fac(int n); int n; float y; printf("Input a integer number:"); scanf("%d",&n); y=fac(n); printf("%d! =%15d",n,y); } float fac(int n) { float f; if(n<0) printf("n<0,data error!"); else if(n==0||n==1) f=1; else f=fac(n-1)*n; return(f);} 运行: input a integer number:10 10! = 3628800
常用库函数
1. 计算整型绝对值函数 【函数首部】int abs(int x) 【返回值】返回整数x的绝对值。 2. 计算长整型绝对值函数 【函数首部】long labs(long x) 【返回值】返回长整型数 x 的绝对值。 3. 计算实型绝对值函数 【函数首部】double fabs(double x) 【返回值】返回双精度实数 x 的绝对值。 4. 计算小于或等于 x 的最大整数函数 【函数首部】double floor(double x) 【返回值】返回小于或等于 x 的最大整数对应的双精度实数。 5. 计算大于或等于x的最小整数函数 【函数首部】double ceil(double x) 【返回值】返回大于或等于x的最小整数对应的双精度实数。 6. 计算平方根函数 【函数首部】double sqrt(double x) 【返回值】返回x的平方根。 【说明】x的值≥0。 7. 计算常用对数函数 【函数首部】double log10(double x) 【返回值】返回常用对数log以10为底(x)的值。 【说明】x的值>0。 8. 计算自然对数函数 【函数首部】double log(double x) 【返回值】返回自然对数ln(x)的值。 【说明】x的值>0。 9. 计算指数函数 【函数首部】double exp(double x) 【返回值】返回e的x次幂的值。 10. 计算10的x次幂函数 【函数首部】double pow10(int x) 【返回值】返回10的x次幂的值。 11. 计算x的y次方函数 【函数首部】double pow(double x,double y) 【返回值】返回x的y次方的值。 【说明】不能出现x、y均<0;或x≤0,而y不是整数的情况。
12. 计算正弦函数 【函数首部】double sin(double x) 【返回值】返回正弦函数sin(x)的值。 【说明】x以弧度为单位。如果是角度,则用 x*3.1415926/180转换为弧度。 13. 计算余弦函数 【函数首部】double cos(double x) 【返回值】返回正弦函数cos(x)的值。 【说明】x以弧度为单位。如果是角度,则用 x*3.1415926/180转换为弧度。 14. 计算正切函数 【函数首部】double tan(double x) 【返回值】返回正弦函数tan(x)的值。 【说明】x 以弧度为单位。如果是角度,则用 x*3.1415926/180转换为弧度。 15. 字符串转换成浮点数函数 【函数首部】double atof(char *x) 【返回值】返回 x 所指向的字符串转换成的实数。 【说明】x 所指向的字符串中存放的应当是一个实数形式 16. 字符串转换成整数函数 【函数首部】int atoi(char *x) 【返回值】返回 x 所指向的字符串转换成的整型数。 【说明】x 所指向的字符串中存放的应当是一个整数形式。 17. 判断是否字母函数 【函数首部】int isalpha(int x) 【返回值】若 x 中存放的字符是字母。则返回非0(真);否则,返回0(假)。 18. 判断是否小写字母函数 【函数首部】int islower(int x) 【返回值】若 x 中存放的字符是小写字母。则返回非0(真);否则,返回0(假)。 19. 判断是否大写字母函数 【函数首部】int isupper(int x) 【返回值】若 x 中存放的字符是大写字母。则返回非0(真);否则,返回0(假)。 20. 判断是否数字字符函数 【函数首部】int isdigit(int x) 【返回值】若 x 中存放的字符是数字字符。则返回非0(真);否则,返回0(假)。 21. 将大写字母转换为小写字母函数 【函数首部】int tolower(int x) 【返回值】若 x 中存放的字符是大写字母。则返回是对应的小写字母;若 x 中存放 的字符不是写大字母。则返回等于x的原值。 22. 将小写字母转换为大写字母函数 【函数首部】int toupper(int x) 【返回值】若 x 中存放的字符是小写字母。则返回是对应的大写字母;若 x 中存放 的字符不是写小字母。则返回等于x的原值。
七 指针
指针和指针变量
C语言中指针是一种数据类型。指针是存放数据的内存单元地址
计算机系统的内存拥有大量的存储单元(每个存储单元的大小为1字节),为了便于管理,必须为每个存储单元编号,该编号就是存储单元的“地址”。每个存储单元拥有一个唯一的地址
指针变量除了可以存放变量的地址外,还可以存放其他数据的地址,例如可以存放数组和函数的地址
指针变量的初始化
指针变量的定义和初始化
指针变量定义的一般形式
【格式】数据类型符 *指针变量名[=初始地址值],…;如 int a, *p = &a
【功能】定义指向“数据类型符”的变量或数组的指针变量,同时为其赋初值
【说明】
“*”表示定义的是一个指针变量。指针变量的前面必须有“*”号
在定义指针变量的同时也可以定义普通变量或数组等
“数据类型符”是指针变量所指向变量的数据类型,可以是任何基本数据类型,也可以是其他数据类型
“初始地址值”通常是“&变量名”“&数组元素”或“一维数组名”,这里的变量或数组必须是已定义的
在定义指针变量时,可以只给部分指针变量赋初值
指针变量的初始化,除了可以是已定义变量的地址,也可以是已初始化的同类型的指针变量,也可以是NULL(空指针)
指针变量初始化时,指针变量的“数据类型符”必须与其“初始地址值”中保存的数据的类型相同
指针变量的一般使用
给指针变量赋值
【格式】指针变量 = 地址型表达式
其中,“地址型表达式”即运算结果是地址型的表达式。C语言规定,变量地址只能通过取地址运算符获得,即“&”,其运算对象是变量或数组元素名,运算结果是对应变量或数组元素的地址
需要注意的是,虽然地址是一个整数,但是C语言中不允许把整数看成“地址常量”,所以此处的“地址型表达式”不能是整数
直接使用指针变量
【格式】指针变量名
需要使用地址时,可以直接引用指针变量名
通过指针变量引用所指向的变量
【格式】*指针变量名
C程序中,“*指针变量名”代表其指向的变量或数组元素,其中的“*”称为指针运算符。需要注意的是,这种引用方式要求指针变量必须已经定义且有值
指针的基本运算
取地址运算符&
取地址运算符“&”的功能是取变量的地址,它是单目运算符。取地址运算符的运算对象必须是已经定义的变量或数组元素,但不能是数组名(数组名本身就是地址了)。运算结果是运算对象的地址。如int a[10]; (p = a;)==(p = &a[0])意思一样。&*a==a;(!错)*&a,&a已经是地址了
【例】若定义了int a[10],*p;,将数组元素a[8]的地址赋给指针变量p的赋值语句是____p=&a[8];_____。 解析:在程序中用赋值的方式,即指针变量=&数组名[下标],所以将数组元素a[8]的地址赋给指针变量p的赋值语句是p=&a[8];。
指针运算符*
指针运算符“*”的功能是取指针变量所指向地址中的内容,与取地址运算符“&”的运算是互逆的,它是单目运算符。指针运算符的运算对象必须是地址,可以是已赋值的指针变量,也可以是变量或数组元素的地址,但不能是整数,也不能是非地址型的变量。运算结果就是地址对应的变量;指针的声明和引用是两回事。
取地址运算符和指针运算符的优先级和结合性
取地址运算符、指针运算符和自增、自减等单目运算符的优先级相同
所有单目运算符的结合性为从右至左
指针变量的引用
例 通过指针变量访问整型变量 #include <stdio.h> void main( ) { int a, b, *p1, *p2 ; a=100; b=10; p1=&a; p2=&b; printf(“a=%d, b=%d\ n”,a, b); printf(“* p1=%d, * p2=%d\ n”, *p1, * p2); printf(“&a=%x,& b=%x\ n”,&a, &b); printf(“p1=%x, p2=%x\ n”, p1, p2); printf(“& p1=%x, &p2=%x\ n”, &p1, & p2); }
运行结果: a=100, b=10 *p1=100, *p2=10 &a=ffd4, &b=ffd6 p1=ffd4, p2=ffd6 &p1=ffd8, &p2=ffda
指针变量作为函数参数——地址传递
函数参数的值传递
形参与实参变量各自使用各自的空间
❖特点:共享内存,“双向”传递
/*将数从大到小输出(使用指针变量作函数参数)*/ #include<stdio.h> void main() { void swap(int *p1, int *p2); //声明需要调用的函数 int a, b ; int *pointer_1, *pointer_2; scanf("%d,%d", &a, &b); pointer_1 = &a; Pointer_2 = &b; if(a < b)swap(pointer_1, pointer_2); //此处可写成if(a<b)swap(&a, &b);即地址传递;此时main()不用定义指针变量 printf("\n%d,%d\n", a, b); } void swap(int *p1, int *p2) //操作的是相应相同的空间 { int temp; temp = *p1; *p1 = *p2; *p2 = temp; }
思考
例:设int a=10,*p=&a; 则执行printf(〝%d\n〞,*p+a); 的结果是(20) 解析:int a=10,*p=&a; ,定义指针变量a以及指针变量p,同时对指针变量p进行初始化,将指针变量p指向整型变量a,*p代表a的值。故在执行printf(〝%d\n〞,*p+a);输出的结果是20
例:设int A=100, *p1=&A, **p2=&p1; 则表达式**p2 的值是__100__。 解析:因为int A=100, *p1=&A, **p2=&p1;,所以最后**p2指向A的值,即表达式**p2 的值是100
指针和数组
指针和一维数组
数组的指针是指向数组在内存的起始地址,数组元素的指针是指向数组元素在内存的起始地址
若将指针变量指向一维数组,可以采用以下两种方法
在数据定义语句中用赋初值的方式,即*指针变量=数组名;
在程序中用赋值的方式,即指针变量=数组名;。
若将指针变量指向一维数组元素,可以采用以下两种方法
在数据定义语句中用赋初值的方式,即*指针变量=&数组名[下标];。
在程序中用赋值的方式,即指针变量=&数组名[下标];。
当指针变量指向一维数组,利用指针变量引用一维数组元素的方法如下
引用下标为0的数组元素 *(指针变量+0)或 *指针变量 或 指针变量[0]
引用下标为i的数组元素 *(指针变量+i) 或 指针变量[i]
当指针变量指向下标为i的一维数组元素时,利用指针变量引用数组元素的方法如下
引用下标为i的数组元素 *(指针变量+0) 或 *指针变量
引用下标为i-k的数组元素 *(指针变量-k)
引用下标为i+k的数组元素 *(指针变量+k)
当指针变量指向一维数组时,引用下标为 i 的一维数组元素可以采用四种方法
*(指针变量+i) 如 *(p+0)
*(数组名+i) 如 *(a+0)
指针变量[i] 如 p[0]
数组名[i] 如 a[0]
【例】设int a[10],*p=a;,数组元素a[4]的正确引用是( A) A. *(p+4) B. p+4 C. *p+4 D. a+4 解析:当指针变量指向一维数组,利用指针变量引用一维数组元素的方法:引用 下标为i的数组元素即*(指针变量+i)或指针变量[i],故在int a[10],*p=a;中数 组元素a[4]的正确引用是*(p+4)。
指向数组元素的指针 例:int a[10]; int *p; p=&a[0]; 或 p=a; /*定义后赋值,两者等价*/
定义指针变量时赋初值: 例: int *p=&a[0]; int *p=a;
如 int i, *p; p=1000; i=p; 错误的
不能把一个整数-->p,也不能把p的值-->整型变量
通过指针引用数组元素 如果: int a[10]; int *p; p=&a[1]; /* p指向数组元素a[1] */ 则: *p=1 表示对p当前指向的数组元素a[1]赋予值1 而:p+1指向同一数组的下一个元素a[2]。 p的值(地址)加了2个字节,p+1=p+1×d(整型,d=2; 实型,d=4;字符型d=1)指针变量所指数组元素的地址的计算, 与数组数据类型有关。 设 p=&a[0] 则 ⑴ p+i和a+i就是a[i]的地址a+i×d ⑵ *(p+i)或*(a+i)是p+i或a+i指向的数组元素a[i] ⑶ 指向数组的指针变量可带下标,p[i]与*(p+i)等价
*(p+i)、*(a+i)、a[i]、p[i] 等价
【例】
若定义了char ch[]={″abc\0def″},*p=ch;则执行printf(″%c″,*p+4);语句的输出结果是(e) 解析: char ch[] = {"abc\0def"}定义了一个字符数组ch,*p=ch定义了一个p的字符指针,指向了ch的首地址,*p其实是字符a, 所以*a+4就是'e'
设static int a[3]={1,2,3};int*p[ ]={&a[0],&a[1],&a[2]};则与数组元素a[1]等价的是(*p[1]) 解析: 因为设static int a[3]={1,2,3};故int*p[ ]={&a[0],&a[1],&a[2]};中int*p[ ]={1,2,3};所以与数组元素a[1]等价的是*p[1]。
一级指针变量与一维数组的关系 int *p 与 int q[10] ⚫ 数组名是指针(地址)常量 ⚫ p=q; p+i 是q[i]的地址 ⚫ 数组元素的表示方法: 下标法和指针法, 即:若p=q, 则:p[i] <--> q[i] <--> *(p+i)<--> *(q+i) ⚫ 形参数组实质上是指针变量。 即:int q[ ] <--> int *q ⚫ 在定义指针变量(不是形参)时,不能把int *p 写成int p[]; ⚫ 系统只给p分配能保存一个指针值的内存区(一般2字节);而给q分配2*10字节的内存区
【例】
关于指针运算符“*”的说法错误的是(B) A、是单目前缀运算符 B、运算对象只能是指针变量 C、运算结果是运算对象对应的变量或数组元素等 D、运算结合性是自右向左 解析: 指针运算符“*”的功能是取指针变量所指向地址中的内容,与取地址运算符“&”的运算是互逆的,它是单目运算符。指针运算符的运算对象必须是地址,可以是已赋值的指针变量,也可以是变量或数组元素的地址,但不能是整数,也不能是非地址型的变量。
以下程序输出的结果是(50) main() { int **k,*a,b=50; a=&b;k=&a; printf("%d\n",**k); }
A、运行出错 B、50 C、a的地址 D、b的地址 解析: k为指向指针的变量,k指向指针变量a,a指向变量b。最后输出**k的值等价于*a,等价于b,所以输出50。
a++不合法
算术运算
指针变量可以进行的算术运算包括: 1、指针变量±整数。 2、指针变量++/++指针变量。 3、指针变量--/--指针变量。 4、指针变量1-指针变量2。 由于指针运算符 * 与自增运算符++、自减运算符—的优先级相同,结合方向都是 从右至左,因此需要注意以下各种形式的含义不同。
*++px; //先使指针变量px+1,再取新的指针变量px所指向地址的内容 *px++; //先取指针变量px所指向地址的内容,再使指针变量px+1 *(++px); //等价于 *++px; *(px++); //等价于*px++; ++*px; //等价于++(*px); 将指针变量px所指向地址的内容+1 *--px; //等价于*(--px); 先使指针变量px-1, 再取新的px所指向地址的内容 *px--; //等价于*(px--); 先取指针变量px所指向地址的内容,再使指针变量px-1 --*px; //等价于--(*px); 将指针变量px所指向地址的内容-1 px-py //是指两个相同类型的指针可以进行减法运算,运算结果是两个指针之间的数据个数,而不是两个指针的地址之差。
【例】
设int a[]={11,21,31,41,51,61},*p=a;,表达式*p++的值是(11) 解析: *p=a;等价于p等于数组a[],p++是先引用然后再加1,所以表达式*p++的值是数组a[0]的值为11。
设int a[]={1,2,3,4),b,*p=a;,执行语句b=*++p;后,b的值是(2) 解析: int a[]={1,2,3,4),b,*p=a;定义了一维数组a[]和整型变量b,且指针变量p指向一维数组a[]。*++p是使指针变量p加1再执行运算,然后取新的指针变量p所指向地址的内容,即语句b=*++p是把p为1的新的地址赋给b,即b=1代表的是一维数组a[1]的元素,即b的值为2。
关系运算
两个类型相同的指针变量可以运用关系运算符比较大小,表示两个指针变量所指向地址位置的前后关系,即前者为小,后者为大。 需要注意的是,如果两个指针变量不是指向同一个数组,则比较大小没有实际意义。
指针和字符串
指向字符串的指针变量: 将指针变量指向字符串的方法如下: 1、在数据定义语句中用赋初值的方式 *指针变量=字符串 2、在程序中用赋值的方式 指针变量=字符串; 需要注意的是,这两种方法并不是将字符串赋予指针变量,而是将存放字符串的 连续内存单元的首地址赋予指针变量。 当指针变量指向字符串时,则可以利用指针变量处理字符串。处理方式主要有: 1、处理整个字符串 输出整个字符串 printf("%s",指针变量); 输入整个字符串 scanf("%s",指针变量); 2、处理字符串中的单个字符 使用指向字符串的指针变量处理字符串中单个字符的例子。
1、处理整个字符串 #include<stdio.h> int main(void) { char *string = "I love China."; printf("%s\n", string); return 0; } 程序运行结果如下: I love China.
2、处理字符串中的单个字符 #include<stdio.h> int main(void) { char *string = "I love China."; for(; *string !="\0"; string++) printf("%c", *string); /*利用for 循环和指向字符串的指针变量 string 逐一输出字符串各字符*/ printf("\n"); return 0; } 程序运行结果如下: I love China.
❖用字符数组实现
字符串的表示形式 字符串: 用双引号括起的一串字符。 可赋给字符型的数组或指针变量, 可通过字符型数组名或字符型指针变量输出。 例 定义字符数组 #include <stdio.h> void main( ) { char string[]=“I love China!”; //string[]数组名,数组首地址 printf("%s\n",string); printf("%s\n",string+7); } 输出: I love China!
❖用字符指针实现
⚫ 字符串的指针就是字符串的首地址,即第一个字符的地址,可以使用字符指针变量来保存这个地址。 ⚫ 使用字符指针可以处理字符串 ⚫ 字符指针的定义及使用 ◆定义和初始化。 例: char *string="I love China!"; ◆在程序中可以直接把字符串常量赋给 一个指针变量。 例:char *string; string="I love China!";
例 定义字符指针 #include <stdio.h> void main( ) { char *string=“I love China!”; printf(“%s\n”,string);}
改动后: #include <stdio.h> void main( ) { char *string=“I love China!”; printf(“%s\n”,string); string+=7; while(*string) //while(*string !=‘\0’)等价 { putchar(string[0]); string++; } } 输出: I love China! China!
❖用下标法存取字符串中的字符
例 将字符串a复制为字符串b #include <stdio.h> void main( ) { char a[ ]="I am boy.",b[20]; int i; for(i=0;*(a+i)!='\0';i++) //地址访问a[ ]复制到b[ ] : *(a+i) = a[i] *(b+i)=*(a+i); //相当于b[i]=a[i] *(b+i)='\0'; //不参与以上的循环,没大括号{ } printf("string a is: %s\n",a); printf("string b is: "); for(i=0; b[i] != '\0';i++) //下标法输出,可以用printf("%s", b);直接输出 printf("%c",b[i]); //写成 *b 相当于 b[0],即首地址 printf("\n"); } 运行结果: string a is: I am boy. string b is: I am boy.
p[4] , a[4], *(p+4), *(a+4)效果一样
指向字符数组的指针变量
C语言中,字符串是按字符数组进行处理的,系统存储一个字符串时先分配一个 起始地址,从该地址开始连续存放字符串中的字符。这一起始地址即字符串首字 符的地址。所以,可以将一个字符串赋值给一个字符数组,也可以赋值给一个字 符指针变量。
字符指针和字符数组的区别
字符指针和字符数组的区别主要体现在: 1、存储内容不同 2、赋值方式不同 3、字符指针变量在定义后应先赋值才能引用 4、指针变量的值是可以改变的,字符指针变量也不例外;而数组名代表数组的首地址,是一个常量,而常量是不能改变的。p[i]与a[i]一样,但*p++可以,*a++则不行,*(a+1)则可以,因为常数是不能进行增1运算的。
对使用字符指针变量和字符数组的讨论 char *cp; 与 char str[20]; 的区别 ❖str由若干元素组成,每个元素放一个字符;而cp中存 放字符串首地址 ❖赋值方式: ⚫ 字符数组只能对元素赋值。 char str[20]; //char str[] = "I love China";这样赋值是可以的 str=“I love China!”; (错!)//单独拿 str 表示首地址,只能 str[i] 逐个赋值 ⚫ 字符指针变量可以用: char *cp; cp=“I love China!”; (✓) ⚫ 赋初值:char *cp="China!"; 等价 char *cp; cp=“China!”; char str[14]={"China"};不等价 char str[14]; str[ ]="China"; (错!)
【例】设char*st=〞China,Beijing〞;,执行语句printf(〞%s\n〞,st+6);后 的输出结果是_____Beijing______。 解析:char*st=〞China,Beijing〞;是定义字符型指针st,在输出语句中st+6 是指针的首地址指向第六位进行输出,即在执行语句printf(〞%s\n〞,st+6); 后的输出结果是Beijing。
指针和函数
指针变量作为函数参数
指针变量既可以作为函数的形参,也可以作为函数的实参。指针变量作为函数参 数,形参和实参之间的数据传递方式本质上是值传递,只是在调用函数时传递的 内容是地址,这样使得形参变量和实参变量指向同一个变量。若被调函数中有对 形参所指变量内存的改变,实际上是改变了实参所指变量的内容。
数组名作为函数参数
数组名作为函数形参时,接收实参数组的首地址;数组名作为函数实参时,将数 组的首地址传递给形参数组。引入指向数组的指针变量后,数组及指向数组的指 针变量作为函数参数时,可有四种等价形式: 1、形参、实参均为数组名。 2、形参、实参均为指针变量。 3、形参为指针变量、实参为数组名。 4、形参为数组名、实参为指针变量。
用数组名作函数参数
数组名作函数参数,是地址传递 当用数组名做函数实参时相当于将数组的 首地址传给被调函数的形参,此时,形参数组 和实参数组占用的是同一段内存,所以当在被 调函数中对形参数组元素进行修改时,实参数 组中的数据也将被修改,因为它们是同一个地址。
数组名作函数参数 void main( ) { f(int arr[ ], int n); int array[10]; ┆ f(array, 10); ┆ } void f(int arr[ ], int n) //编译时arr按指针变量处理,所以,此句与 f(int *arr , int n)等价。 { ┆ }
将数组a中n个整数按相反顺序存放。 思路:数组元素头尾对调。四种调用方式。
⑴ 实参与形参均用数组 #include <stdio.h> void main() { void inv(int x[ ], int n); int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,"a[i]); printf("\n"); inv(a,10); printf("The array has been inverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } void inv(int x[ ], int n) //逆置算法 { int temp,i,j,m=(n-1)/2; for(i=0;i<=m;i++) { j=n-1-i; temp=x[i]; x[i]=x[j]; x[j]=temp; } return; }
⑵ 实参用数组,形参用指针变量 #include <stdio.h> void main() { void inv(int *x, int n); int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,"a[i]); printf("\n"); inv(a,10); printf("The array has been inverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } void inv(int *x, int n) //逆置算法 { int temp,*p,*i,*j,m=(n-1)/2; i=x; j=x+n-1; p=x+m; for(;i<=p;i++,j--) { temp=*i; *i=*j; *j=temp; } return; }
(3)实参与形参均用指针变量 #include <stdio.h> void main() { void inv(int *x, int n); int i,arr[10],*p=arr; printf("The original array:\n"); for(i=0;i<10;i++,p++) scanf("%d",p); p=arr; inv(p,10); //由于上面循环指针p已走到最后,此处p=arr;是将指针再指向首地址 printf("The array has been inverted:\n"); for(p=arr;p<arr+10;p++) printf("%d",*p); printf(“\n"); } void inv(int *x, int n) //逆置算法 { int *p, m, temp,*i,*j; m=(n-1)/2; i=x; j=x+n-1; p=x+m; for(;i<=p;i++,j--) { temp=*i; *i=*j; *j=temp; } return;}
⑷ 实参用指针变量,形参用数组 #include <stdio.h> void main() { void inv(int x[ ], int n); int i,a[10],*p=a; for(i=0;i<10;i++,p++) scanf("%d",p); p=a; //由于上面循环指针p已走到最后,此处p=a; 是将指针再指向首地址 inv(p,10); printf("The array has been inverted:\n"); for(p=arr;p<arr+10;p++) printf("%d ",*p); printf(“\n "); } void inv(int x[ ], int n) //逆置算法,x[]与*x等价,x[]括号里不能填数如x[10] { int t,i,j,m=(n-1)/2; for(i=0;i<=m;i++) { j=n-1-i; t=x[i]; x[i]=x[j]; x[j]=t; } return; }
归纳:用数组做函数参数有如下四种情况: 1、实参形参都用数组名: int a[10]; inv(int x[ ],int n) inv(a,10) { …... } 2、实参用数组名,形参用指针变量: int a[10]; inv(int *x,int n) inv(a,10) { …... } 3、实参形参都用指针变量: int a[10]; inv(int *x,int n) int *p=a; {……} inv(p,10) 4、实参用指针变量,形参用数组名: int a[10]; inv(int x[ ],int n) int *p=a; {…...} inv(p,10)
指针型函数的定义
⚫ C语言中,函数可以返回整型、实型、字符型数据,也可以返回指针类型数据, 即返回一个地址。指针型函数是指函数的返回值是指针型,即这类函数的返 回值必须是地址值,调用该类函数时,接收返回值的必须是指针变量、指针 数组元素等能够存放地址值的对象。 ⚫ 定义指针型函数的格式和有返回值的函数的定义格式基本相同,唯一的区别 是在函数名前面加一个“*”,表示函数的返回值是指针型数据。 指针型函数的调用和一般函数的调用方法完全相同,但需要注意的是只能使用指 针变量或指针数组元素接收指针型函数的返回值,不能使用数组名接收指针型函 数的返回值,因为函数名是地址常量,不是地址型变量,不能接收地址型变量数 据。
【例】若定义的函数为float*fun( ){……},则函数的返回值是(float型指针 ) 解析:指针型函数是指函数的返回值是指针型,即函数float*fun( ){……},的返回 值为float型指针。 【例】关于函数正确的说法是 返回值的类型可以是指针 解析:C语言中,函数可以返回整型、实型、字符型数据,也可以返回指针类型 数据,即返回一个地址。指针型函数是指函数的返回值是指针型。
指针数组
指针数组的定义
指针数组是数组中的元素均为指针变量。int *p[10] 先右结合,即先运算数组p[10],先明确其是一个整型数组,然后数组里面的元素存的都是指针变量,即内容为地址信息,即int *p[10] 是一个指针数组。int (*p)[10] 表示的是指向一个包含10个元素的一维数组p[9]的一个指针 【格式】数据类型符 *指针数组名[数组长度]; 【功能】定义指针数组,有“长度”个数组元素。 【说明】 1、指针数组名是标识符,前面必须加“*”号。 2、定义指针数组的同时可以定义普通变量、数组和指针变量等。 3、“数据类型符”可以是任何基本数据类型。“数据类型符”不是指针数组元 素中存放的数据类型,而是其所指向数据的数据类型。
指针数组的初始化
例如:char *ps[]={"China","America","Russia",NULL};//指针数组可以指n个串 定义了一个用于指向字符型数据的指针数组ps,其长度为4,同时对指针数组元 素赋初值,前面三个是字符型指针,最后一个是空指针。 例如,int a[3][3]={1,2,3,4,5,6,7,8,9}; int *p[3]={a[0],a[1],a[2]};/*利用二维数组元素初始化指针数组p,指向首地址1,4,7*/
指针数组元素的赋值
1、将数组名赋予指针数组各元素 例如:char s[4][20]={“China”,”America”,”Russia”,NULL}; char *p[4]; p[0]=s[0];/*s[0]与&s[0][0]是一回事,给指针数组元素p[0]赋值s[0],s[0]是字符串“China” 的首地址*/ 2、将字符串直接赋予指针数组元素 例如:char *p[4]; p[0]=“China”;/*直接给指针数组元素p[0]赋值为字符串“China”的首地址*/
指针元素的使用
指针数组元素的表示方法和普通数组元素的表示方法完全相同。 指针数元素的使用和指针变量的使用完全相同,可以对其赋予地址值,可以利用 其引用所指向的变量或数组元素,也可以参与运算。 其中算术运算和关系运算一般只用于指针数组元素指向同一个指针数组。
pointer[i]+j、array[i]+j、 &array[i][j] 等价,均指向array[i][j] *(pointer[i]+j)、 *(*(pointer+i)+j)、 pointer[i][j] 与array[i][j] 等价。 #include <stdio.h> #include <string.h> int main(void) { int array[3][3]={1,2,3,4,5,6,7,8,9),i,j; /*定义一个整形二维数组 array*/ int *pointer[3]={array[0],array[1],array[2]}; //利用指针的可移动性质 /*定义一个指向整形数据的指针数组 pointer 并初始化, 即将二维数组array的每行元素的首地址赋予指针数组的各元素*/ for(i=0;i<3;i++) //控制行输出 for(j=0;j<3;j++) //控制列输出 printf("%4d",pointer[i][j]); /*利用指向二维数组元素的指针数组输出二维数组各元素的值*/ return 0; }
输入三个国家的名称,按字母顺序排序后输出。 #include <stdio.h> #include <string.h> int main(void) { char *s[]={"China","America","Russia"},*p; /*定义指针数组s和指针变量p*/ int i,j,k=3; for(i=0; i<k-1; i++) //两重循环,相当于冒泡排序 for(j=0; j<k-1; j++) if(strcmp(s[j], s[j+1]) > 0) { p=s[j]; s[j]=s[j+1]; s[j+1]=p; } for(i=0;i<k;i++) printf("%s\n",s[i]); return 0; }
八 结构体类型和自定义类型
结构体类型定义
C语言提供了一种构造类型,即允许用户根据需要利用已有的数据类型自定义的一种数据类型,它由若干个数据类型相同或不同的数据项组合而成。为了存放类型不同的相关数据项,C语言提供了另一种构造类型,即结构体类型。 结构体类型定义的一般形式: struct 结构体类型名 /*struct是结构体类型的关键字*/ { 数据类型符 成员名1; 数据类型符 成员名2; … 数据类型符 成员名n; }; /*此行分号不能缺少*/ “结构体类型名”是用户命名的标识符;“数据类型符”可以是基本数据类型说明符,也可以是已定义的某种结构体类型;“成员名”可以是标识符、标识符[长度]、*标识符。 说明: 1、struct是结构体类型的关键字,“结构体类型名”是由用户自行命名,应遵循标识符的命名规则。 2、一对花括号括起来的部分是结构体类型的成员说明表,一个结构体类型可以包含任意个成员,这些成员可以是任何类型,包括各种基本数据类型和构造类型。 3、分号是结构体类型定义语句的结束标志,不能省略。 4、结构体类型定义的位置一般在程序开头,所有函数之前,此时所有函数都可以利用它来定义变量;若结构体类型定义在函数内部,则只能在本函数内部使用。 5、结构体类型仅是一种数据类型,系统不会为其成员分配内存。
【例】
结构型变量占用内存的字节数是( 各成员占用内存字节数之和) 解析:定义结构体类型变量,系统才为其分配内存,所分配的内存字节数等于该 结构体类型所有成员占用的字节数之和。
结构体类型变量
用户自定义的结构体类型,与系统定义的基本数据类型(如int、char等)一样,可用来定义该类型的变量。 定义结构体类型变量的三种方法: 1、先定义结构体类型、后定义结构体类型的变量(间接定义法) 2、在定义结构体类型的同时,定义结构体类型变量(直接定义法) 3、省略结构体类型名,直接定义结构体类型变量 struct { … }a,b; 第二种和第三种定义结构体类型变量的一般格式: struct [结构体类型名] { … }结构体类型变量表; 结构体类型变量初始化的格式与一维数组相似。 结构体类型变量={初值表}; ❖结构体成员名与程序中变量名可相同,两者不代表同一个对象。 struct student { int num; //stu.num char name[20]; float score; } stu; int num; //与stu.num不代表同一个对象
struct std_info //定义结构体类型(间接定义法) { int no; char name[10]; char sex; int score[3]; } .... struct std_info a,b; //定义结构体类型的变量a 和 b
struct std_info /*定义结构体类型(直接定义法)*/ { int no; char name[10]; char sex; int score[3]; } a = {1,"xiaoming","F",{95,100,100}}; /*定义结构体类型的同时定义结构体类型的变量并赋初值*/ .... struct std_info b = {1,"xiaoqiang","F",{98,98,88}}; /* 该语句是正确的,因为前面已定义了结构体类型std_info */
/*声明结构体类型的同时定义结构体变量*/ struct student { //定义结构体类型 int num; char name[20]; char sex; int age; float score; char addr[30]; } stu1,stu2 ; //定义结构体变量 //注:只有在定义了结构体变量后系统才为其分配内存。
结构体变量的引用
引用规则
❖ 结构体变量不能整体引用,只能引用变量成员 结构体变量名.成员名// . 成员(分量)运算符优先级: 1 ; 结合性: 从左向右 struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; } stu1,stu2; // 引用 stu1.num=10; stu1.age++; stu1.score=85.5; stu1.score+=stu2.score;
main() { struct student { int No; float score; } stu1,stu2; scanf(“%d,%f”,&stu1); ( X ) scanf(“%d,%f”,&stu1.No, &stu1.score); ( √ ) printf(“%d,%f”,stu1); ( X ) printf(“%d,%f” , stu1.No, stu1.score); ( √ ) stu2=stu1; ( √ ) }
结构体类型变量成员的引用方法
结构体类型变量是通过成员运算符“.”逐一访问其成员,一般形式: 结构体类型变量名.成员名 说明: 1、“.”是成员运算符,它是双目运算符,前面的运算对象必须是结构体类型的变量名(或结构体类型数组元素),后面的运算对象是该结构体类型的成员名。 2、成员运算符“.”的优先级最高,与圆括号运算符()、下标运算符[]的优先级相同,结合性是从左至右。 3、C语言规定不能对一个结构体类型变量整体地进行输入和输出操作,只能对结构体类型变量的成员进行输入和输出操作。 4、结构体类型变量的成员可以像普通变量一样进行赋值和运算。 5、相同类型的结构体类型变量之间可以互相赋值。 6、两个结构体类型变量不能用关系运算符“==”或“!=”进行比较操作。 结构体类型变量成员地址的引用方法: &结构体类型变量名.成员名 /*成员是变量形式的变量地址*/ 结构体类型变量名.成员名 /*成员是数组形式的数组首地址*/ &结构体类型变量名.成员数组[下标] /*成员是数组,其数组元素的地址*/ 需要注意的是,存放结构体类型变量成员地址的指针变量的数据类型必须与该成员的数据类型相同。 结构体类型变量地址的引用方法: &结构体类型变量名 需要注意的是,存放结构体类型变量地址的指针变量必须是相同类型的结构体类型指针变量。
结构体类型数组
结构体类型数组的定义和初始化: 定义结构体类型数组的三种方法: 1、先定义结构体类型,再定义结构体类型数组并初始化。 2、定义结构体类型的同时定义结构体类型数组并初始化。 3、定义无名称的结构体类型的同时定义结构体类型数组并初始化。 结构体类型数组定义的一般格式: struct 结构体类型名 { 成员列表; }; struct 结构体类型名 结构体类型数组名[数组长度]; 与普通数组一样,结构体类型数组也可以在定义时进行初始化,其初始化的格式: 结构体类型数组名[n]={{初值表1},{初值表2},…,{初值表n}}; 定义结构体类型数组并初始化的例子。
//方法一: struct std_info /*先定义结构体类型*/ { int no; char name[10]; char sex; }; ... struct std_info s[3] = {{50,"xiaoming","f"},{51,"xiaofang","m"},{52,"xiaoli","m"}}; /*后定义一个结构体类型数组s,共有三个元素,即s[0]~s[2],每个数组元素都是结构体类型,即每个数组元素都包括结构体类型std_info的三个成员*/
//方法二: struct std_info /*定义结构体类型的同时定义结构体类型数组s 并赋初值*/ { int no; char name[10]; char sex; }s[3] = {{50,"xiaoming","f"},{51,"xiaofang","m"},{52,"xiaoli","m"}};
//方法三: struct /*定义无名称结构体类型的同时定义结构体类型数组 s 并赋初值*/ { int no; char name[10]; char sex; }s[3] = {{50,"xiaoming","f"},{51,"xiaofang","m"},{52,"xiaoli","m"}};
结构体类型数组元素成员的引用方法: 结构体类型数组名[下标].成员名 结构体类型数组元素成员地址的引用方法: &结构体类型数组名[下标].成员名 存放结构体类型数组元素成员地址的指针变量的数据类型必须与结构体类型中该 成员的数据类型相同。 结构体类型数组元素成员地址的引用例子
结构体类型数组元素成员的引用例子 #include<stdio.h> struct s { int num; char color; char type; }car[] = {{101,"G","c"},{201,"Y","m"},{105,"R","l"},{222,"B","s"},{308,"P","b"}}; /*定义结构体类型数组car,同时进行初始化*/ int main(void) { int i; printf("number color type\n"); for(i = 0; i < 5; i++) printf("%-9d%-6c%c\n",car[i].num,car[i].color,car[i].type); /*依次输出结构体类型数组的每个元素的各个成员值*/ return 0; }
程序运行结果如下: number color type 101 G c 201 Y m 105 R l 222 B s 308 P b
结构体类型数组元素成员地址的引用例子 #include<stdio.h> #define NAMESIZE 20 struct std_info { int name[NAMESIZE]; int age; char sex; }s[10]; /*定义结构体类型数组s*/ int main(void) { int i; printf("Please input student data\n"); i = 0; while(i < 4) /*从键盘输入四个学生的信息分别存入结构体类型数组s*/ { printf("name: age: sex:"); scanf("%s", s[i].name); scanf("%d", &s[i].age); gechar(); scanf("%c", &s[i].sex); i++ } return 0; }
程序运行结果如下: Please input student data name: age: sex: Kelvin 25 M 回车 name: age: sex: Tom 22 M 回车 name: age: sex: Jack 28 M 回车 name: age: sex: Rose 27 M 回车
结构体类型指针
指向结构体类型变量的指针变量: 定义指向结构体类型变量的指针变量和定义结构体类型变量的方法基本相同,区 别是在结构体类型指针变量名的前面加一个“*”。 一般地,如果指针变量pointer已指向结构体类型变量var,则以下三种形式等价: 方式一:var.成员 方式二:pointer->成员 方式三:(*pointer).成员 /*“*pointer”外面的一对圆括号不能省略*/
❖使用结构体指针变量引用成员形式 例 int n; int *p=&n; *p=10; <--> n=10 struct student stu1; struct student *p=&stu1; stu1.num=101; <--> (*p).num=101 以下三种形式等价: 结构体变量名.成员名 stu.num =101; (*结构体指针名).成员名 (*p).num=101 //( )不能少! 结构体指针名−>成员名 p−>num =101 ->指向运算符 优先级: 1 结合方向:从左向右
【例】
设struct{int a;char c;}m,*p=&m;,用指针引用成员a的形式是(*p).a或p->a 【解析】:一般地,如果指针变量pointer已指向结构体类型变量var,则三种形式等 价:var.成员、pointer->成员、(*pointer).成员,即用指针引用成员a的形式是 (*p).a也可以为p->a。
设 struct student { char name[10]; int sex; int num; } s,*p=&s; 以下对结构型变量s中成员num的非法引用是(D ) A:p->num B:(*p).num C:s.num D:student.num 【解析】:指向结构体类型变量的指针变量的三种等价形式:结构体类型变量.成员、指针变量->成员、(*指针变量).成员。student.num中的student不是结构体类型变量。
指向结构体类型变量的指针变量 需要注意以下几点: 1、方式一中,成员运算符“.”左侧的运算对象只能是结构体类型变量。 2、方式二中的“->”称为指向成员运算符或简称为指向运算符。该运算符是双目运算符,其左侧的运算对象必须是结构体类型变量(或数组元素)的地址,也可以是已指向结构体类型变量(或数组元素)的指针变量,否则会出错;右侧的运算对象必须是该结构体类型的成员名。指向运算符的优先级和“()”“[]”“.”是同级的,结合性是从左至右。 3、方式三中的“*指针变量”代表了它所指向的结构体类型变量,利用成员运算符“.”来引用,等价于“结构体类型变量.成员名”。“*指针变量”必须用圆括号括起来,因为运算符“*”的优先级低于运算符“.”,若没有圆括号则优先处理运算符“.”,将出现“指针变量.成员名”会造成语法错误。
指针变量指向结构体类型数组元素 如果一个结构体类型数组元素的地址已赋予相同结构体类型的指针变量(即指针 变量指向结构体类型数组元素),可以使用以下两种方式引用该元素的成员,其 作用完全相同。 方式一:(*指针变量).成员名 方式二:指针变量->成员名 这里的指针变量必须已经指向某一结构体类型数组元素。
指针变量指向结构体类型数组 当一个结构体类型数组的首地址已经赋予相同结构体类型的指针变量(即该指针 变量已经指向结构体类型数组),可以使用以下两种方式引用下标为i的数组元 素的成员,其作用完全相同。 ⚫ 方式一:(*(指针变量+i)).成员名 ⚫ 方式二:(指针变量+i)->成员名 这里的指针变量必须已经指向某一结构体类型数组,则上述两种引用方式均等价 于“数组名[i].成员名”。 说明: 1、如果指针变量p已经指向一个结构体类型数组,则p+1指向结构体类型数组 的下一个元素,而不是当前元素的下一个成员。 2、如果指针变量p已经指向一个结构体类型变量(或结构体类型数组),则不 能再使之指向结构体类型变量(或结构体类型数组元素)的某一成员。
利用全局变量传递结构体类型数据 利用全局变量传递结构体类型数据时,只要在程序的开头定义结构体类型及其变 量或数组,则可以在其后面定义的函数间传递结构体类型数据。 利用返回值传递结构体类型数据时,只能返回一个结构体类型数据,定义时函数 的类型必须是已定义的结构体类型,利用语句return(表达式);返回的表达式值也 必须是同一结构体类型的数据。调用函数后的返回值必须用同一结构体类型变量 或数组元素接收。
利用形参和实参结合传递结构体类型数据 1、地址传递方式 地址传递方式中,形参可以是结构体类型数组或指针变量,对应的实参必须是同 一结构体类型的变量地址、数组名、数组元素地址或已赋值的结构体类型的指针 变量。 2、值传递方式 值传递方式中,形参为结构体类型变量,对应的实参必须是同一结构体类型的变 量或数组元素。
自定义类型
typedef定义类型别名的一般形式: 【格式】typedef 原类型符 新类型符; 【参数】“原类型符”可以是基本类型符,也可以是用户自定义的无名结构体类型。“新类型符”是用户自定义的一个标识符,通常采用大写字母。 【功能】将“原类型符”定义为用户自定义的“新类型符”,以后可以使用“新类型符”定义相应数据类型的变量、数组、指针变量、结构体类型和函数。 【说明】 1、typedef只能定义类型名,不能定义变量名,即定义类型名的别名,并不能创造一个新的类型。 2、typedef与宏定义#define具有相似之处,但二者是不同的。前者是在编译时完成的;后者是在编译预处理时完成的,且只能进行简单的字符串替换。 【格式】typedef 基本数据类型符 用户类型符; 【功能】将“基本数据类型符”定义为“用户类型符”。 例如,为实型float定义一个别名REAL的过程如下: 1、按照定义实型变量的方法,写出定义体,即float f; 2、将变量名换成别名,即float REAL; 3、在定义体最前面加上typedef,即typedef float REAL; 4、已定义完新类型名,可用此新类型名定义变量,即REAL x,y; 【格式】typedef 基本数据类型符 用户类型符[数组长度]; 【功能】以后可以使用“用户类型符”定义“基本数据类型符”的数组,其长度 为定义时确定的数组长度。 例如,typedef int ARRAY[10];/*定义ARRAY为整型、长度为10的数组的用户类型符*/ ARRAY a,b,c;/*等价于int a[10],b[10],c[10];*/ ARRAY是长度为10的整型数组,可以用ARRAY定义多个相同类型和长度的数组。 【格式】typedef struct { 数据类型符 成员名1; 数据类型符 成员名2; … 数据类型符 成员名n; } 用户类型符; 【功能】以后可以使用“用户类型符”定义含有n个成员的结构体类型变量、数组和指针变量等。 例如,为结构体类型date定义一个别名DATE。 struct date { int year,month,day; }; 1、按照定义结构体类型变量的方法,写出定义体,即struct date{…}d; 2、将变量名换成别名,即struct date{…}DATE; 3、在定义体最前面加上typedef,即typedef struct date{…}DATE; 4、已定义完新类型名,可用此新类型名定义变量,即DATE s1,s2; 【格式】typedef 基本数据类型符 *用户类型符; 【功能】以后可以使用“用户类型符”定义“基本数据类型符”类型的指针变量 或数组等。
typedef int *p_I; /*定义p_I为整型指针的用户类型符*/ typedef char *p_C[5]; /*定义P_C 为长度是5 的字符型指针数组的用户类型符*/ int main(void) { P_I p1,p2; // 等价于int *p1,*p2 P_C p3,p4; //等价于char *p3[5],*p4[5] }
【例】
设typedef int ARRAY[10];,与ARRAY a,b;等价的是( B) A. int a,b; B. int a[10],b[10]; C. int a[10],b; D. int a,b[10]; 【解析】:typedef int ARRAY[10];定义ARRAY为整型、长度为10的数组的用户类 型符,ARRAY a,b;等价于int a[10],b[10];。
九 编译预处理
⚫ 宏定义命令
编译预处理命令 编译预处理是指对源程序进行编译之前,先对源程序中的各种预处理命令进行处理;然后再将处理结果和源程序一起进行编译,以获得目标代码。 C语言包括两种宏定义命令,即无参宏定义命令和有参宏定义命令。 通常,宏定义用于定义程序中的符号常量、类型别名、运算式代换和语句代换等。
无参宏定义
无参宏定义的一般格式: 【格式】#define 宏名 字符序列 【说明】 1、“宏名”是标识符的一种,命名规则与标识符相同,通常采用大写字母,以便与变量区分;“字符序列”可以是常量、表达式各种符号等。“宏名”和“字符序列”之间使用空格符分隔。 2、宏定义不是C语言的语句,所以不能在行尾加分号。否则,宏展开时会将分号作为字符串的一个字符,用于替换宏名。 3、宏展开时,预处理程序仅按宏定义简单地替换宏名,而不做任何检查。 4、对于双引号括起来的字符串内的字符,即使与宏名重名,也不进行宏展开。 5、宏定义命令#define出现在函数的外部,宏名的作用域是从宏定义开始到本文件结束。通常,宏定义命令放在文件的开头。 6、宏定义时,可以引用已定义的宏名,宏展开是逐层替换的。 使用宏定义的优点在于: 1、提高源程序的可读性。 2、提高源程序的可修改性,修改宏定义中的“字符序列”可以起到一改全改的作用。 3、避免源程序中重复书写字符串。
有参宏定义
【格式】#define 宏名(形参表) 带参数的字符序列 有参宏定义的调用和宏展开格式: 宏名(实参表) 宏展开的过程是:若程序中有带实参的宏,则按照#define指定的字符序列从左至右进行替换;若宏定义中包含形参,则用实参直接替换宏定义命令行中相应的形参,其中实参可以是常量、变量或表达式;若宏定义的字符序列中的字符是非形参字符则替换时保留不变。 【说明】 1、定义有参宏时,宏名与左圆括号之间不能留有空格。否则,C编译程序将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。 2、有参的宏展开只是将实参作为字符串,简单地替换形参字符串,而不进行任何语法检查。
【例】
设有宏定义#define X 10-4, 则 printf(“ %d\n”,6*X);的输出结果是(56) 【解析】:宏定义命令将源程序中出现的所有X都用常数10-4替换,即在执行输出函 数 printf(“ %d\n”,6*X);时,输出6*X的值,把X用10-4替换,即6*10-4=56,故输出的结果是56。
设#define X 5+9,则执行语句printf("%d",3*X);后输出的结果是(24)。 【解析】:因为#define X 5+9,所以3*X=3*5+9=24,执行语句printf("%d",3*X); 后输出的结果是24。
带参数的宏定义 ❖一般形式: #define 宏名(参数表) 宏体 //宏体 不能加空格 ❖功能:进行字符串替换,并进行参数替换 例 #define S(a,b) a*b ……….. area = S(3,2); 宏展开:area = 3*2; ❖宏展开:形参用实参换,其它字符保留 ❖宏体及各形参外一般应加括号() /*【例】*/ #define POWER(x) x*x x = 4; y = 6; z = POWER(x + y); 宏展开:z = x + y*x + y; //简单替换 一般写成:#define POWER(x) (x)*(y) //加括号 宏展开:z = ((x+y)*(x+y)); ❖带参宏定义与函数的区别 ⚫ 函数调用时,先求实参表达式的值,再带入形参。 宏只进行简单字符替换,不求值。 ⚫ 函数调用在程序运行时处理和分配临时内存单元。 宏展开在编译时进行,不分配内存单元,无值传递和返值。 ⚫ 函数要定义形实参且类型一致,宏无类型,其参数无类型。 ⚫ 函数只有一个返回值,宏可以设法得到几个结果。 ⚫ 宏展开使源程序变长,函数调用源程序不变长。 ⚫ 宏替换不占运行时间,只占编译时间。函数调用占运行时间。
例 使用带参数的宏 #include <stdio.h> #define PI 3.1415926 #define S(r) PI*r*r void main() { float a,area; a=3.6; area=S(a); printf("r=%f\narea=%f\n",a,area); }
运行: r=3.600000 area=40.715038
例 用宏定义和函数实现同样的功能 #define MAX(x,y) (x)>(y)?(x):(y) ……. main() { int a,b,c,d,t; ……. t=MAX(a+b,c+d); …… } 宏展开:t=(a+b)>(c+d)?(a+b):(c+d);
用函数实现 int max(int x,int y) { return(x>y?x:y); } main() { int a,b,c,d,t; ……. t=max(a+b,c+d); ……… }
例 用宏代表输出格式 #include <stdio.h> #define PR printf #define NL "\n" #define D "%d" #define D1 D NL #define D2 D D NL #define D3 D D D NL #define D4 D D D D NL #define S "%s"
例 用函数输出格式 void main() { int a,b,c,d; char string[]="CHINA"; a=1;b=2;c=3;d=4; PR(D1,a); PR(D2,a,b); PR(D3,a,b,c); PR(D4,a,b,c,d); PR(S,string); }
运行结果: 1 12 123 1234 CHINA
例 使用宏带回几个结果 #include <stdio.h> #define PI 3.1415926 #define CIRCLE(R,L,S,V) L=2*PI*R; S=PI*R*R; V=4.0/3.0*PI*R*R*R void main() { float r,l,s,v; scanf("%f",&r); CIRCLE(r,l,s,v); printf("r = %6.2f,l = %6.2f,s = %6.2f,v = %6.2f\n", r,l,s,v); } //展开后: void main() { float r,l,s,v; scanf("%f", &r); l=2*3.1415926*r; s=3.1415926*r*r; v = 4.0/3.0*3.1415926*r*r*r; printf("r = %6.2f, l = %6.2f, s = %6.2f, v = %6.2f\n", r,l,s,v); }
运行: 3.5 回车 r=3.50,l=21.99,s=38.48,v=179.59
⚫ 文件包含命令
文件包含是指一个源文件可以将另一个源文件的全部内容包含进来。文件包含命令可以将多个模块共用的数据(如符号常量和数据结构)或函数,集中到一个单独的文件中。 文件包含命令的格式: 【格式一】#include“文件名” 【格式二】#include<文件名> 上述两种格式的区别仅在于: 1、格式一,系统首先到当前源文件所在的目录查找被包含文件,如果没有找到,再 到系统指定的“包含文件目录”去查找,必要时在文件名前加上所在的路径。 2、格式二,直接到系统指定的“包含文件目录”去查找。 【说明】 1、编译预处理时,预处理程序将查找指定的被包含文件,并将其复制到#include命令出现的位置上。 2、文件头部的被包含文件称为“标题文件”或“头部文件”,常以“h”(head)作为扩展名,简称头文件。 3、一条文件包含命令,只能指定一个被包含文件。 4、由于库函数以及某些宏定义都存放在系统文件中,一般都存放在系统指定的路径下,所以将存放库函数的头文件包含在源程序时一般建议采用格式二,即#include<文件名>。 5、文件包含命令允许嵌套,即一个被包含的文件中可以再使用文件包含命令包含另一个文件,而在该文件中还可以再包含其他文件。 6、若被包含文件的内容发生变化,则应该对包含此文件的所有源文件重新进行编译。 “文件包含”处理 功能:一个源文件可将另一个源文件的内容全部包含进来 一般形式: #include"文径件名" 或 #include <文件名> 【<>】 直接按标准目录搜索 【“”】先在当前目录搜索,再搜索标准目录 ;可指定路径 处理过程:预编译时,用被包含文件的内容取代该预处理命令,再将“包含”后的文件作为一个源文件单位进行编译,得目标文件.obj
⚫ 条件编译命令
C语言提供了条件编译命令,使得用户可以选择对源程序的一部分内容进行编译, 即不同的编译条件产生不同的目标程序。 条件编译命令具有以下几种形式: 【格式一】 #ifdef 标识符 程序段1 [#else 程序段2] #endif 【功能】当“标识符”已经被#define命令定义,则编译程序段1;否则编译程序段2。其中[ ]中的内容,即#else部分可以省略,即: #ifdef 标识符 程序段1 #endif 【格式二】 #ifndef 标识符 程序段1 [#else 程序段2] #endif 【功能】当“标识符”没有被#define命令定义,则编译程序段1;否则编译程序段2。其中[ ]中的内容,即#else部分可以省略,即: #ifndef 标识符 程序段1 #endif 【格式三】 #if 表达式 程序段1 [#else 程序段2] #endif 【功能】当表达式为“真”(非0)时,则编译程序段1;否则编译程序段2。其中,[ ]中的内容,即#else部分可以省略。 需要注意的是,其中的“表达式”是在编译阶段计算值的,所以此处的“表达式”不能是变量,必须是常量或用宏定义命令#define定义的标识符。
十 文件
⚫ 文件概述
C语言将文件看做是由一个一个的字符(ASCII码文件)或字节(二进制文件)组成的,这种文件称为流式文件。 文件定义 文件是存储在外部存储介质上的数据集合,这些数据的类型可以是整型、实型、字符型、字符串或结构体类型等。目前,外部存储介质主要是磁盘,存储在磁盘上的文件称为“磁盘文件”。 文件命名 为了标识磁盘上的不同文件,每个磁盘文件都必须有一个文件名,其一般组成为: 盘符:路径\文件主名[.扩展名] 其中,盘符表示文件所在的磁盘,可以是A、B、C和D等。路径是由目录序列组成,目录之间的分隔符为“\”,需要注意的是,如果路径出现在字符串中,C语言规定其中的符号“\”需要写成转义字符“\\”。文件主名和扩展名均是由字母和数字等字符组成的。 C语言中,常见的扩展名: c:C源程序文件 obj:C源程序经过编译后生成的目标文件。 exe:目标文件经过链接后生成的可执行文件。 一般地,数据文件的扩展名常用dat,文本文件的扩展名常用txt。 文件分类 1、按照文件的内容划分 可分为程序文件和数据文件,程序文件又可以分为源文件、目标文件和可执行文件。 2、按照文件的组织形式划分 可分为顺序存取文件和随机存取文件。 (1)顺序存取文件简称为“顺序文件”,数据写入文件的方式是后输入的数据放在以前输入数据的后面,按照数据的先后次序一个接一个地存放。 (2)随机存取文件简称为“随机文件”,每个数据在磁盘中所占用的长度都相同。 3、按照文件的存储形式划分 可分为文本文件和二进制文件。 (1)文本文件中数据转换为对应的ASCⅡ码来存放,每字节存储一个字符,因此便于对字符逐一进行处理。 (2)二进制文件中数据按照其二进制形式来存放,即把内存中的数据,原样输出到磁盘文件中。 4、按照文件的存储介质划分 可以分为普通文件即存储介质文件(磁盘、磁带等)和设备文件即非存储介质(键盘、显示器和打印机等)文件。
【例】
C语言中,可以处理的两类文件分别是文本文件和(二进制文件)。
文件存取方式
文件存取方式主要包括顺序存取和随机存取两种: 1、顺序存取是自上而下逐一地存取文件的内容。保存数据时,将数据附加到文件的尾部。顺序存取方式常用于文本文件,被存取的文件则称为顺序文件。 2、随机存取是以一个完整的单位进行数据的读取和写入。随机存取方式常用于二进制文件,被存取的文件则称为随机文件。
文件系统
C语言使用的文件系统分为缓冲文件系统(标准I/O)和非缓冲文件系统(系统I/O): 1、缓冲文件系统是指系统自动地为每个正在使用的文件在内存开辟一个缓冲区。从内存向磁盘输出数据时,必须首先输出到缓冲区。待缓冲区装满后,再一起输出到磁盘文件。从磁盘文件向内存读入数据时,恰好相反,即首先将一批数据读入到缓冲区,再从缓冲区将数据逐一送到程序数据区。 2、非缓冲文件系统是指用户在程序中为每个文件设定缓冲区。
⚫ 文件的打开和关闭函数
文件打开函数
【函数首部】FILE *fopen(char *filename,char *mode) 【参数】filename 字符型指针,所指向的字符串是要打开的“文件名”。 mode 字符型指针,所指向的字符串是对打开文件规定的“使用方式”。 “使用方式”是由 r、w、a、t、b 和 + 六个字符组成。如果使用方式中有b字符, 则针对的是二进制文件;如果使用方式中没有b字符,则针对的是文本文件;如 果使用r打开一个文件时,则该文件必须已经存在。 【功能】按mode指定的“使用方式”打开filename指定的“文件名”对应的文件, 同时自动地为该文件分配一个内存缓冲区。 【返回值】若打开文件正确,则返回一个“文件型”指针,程序通过该指针获得文 件信息,访问文件并进行各种操作。若打开文件出错,则返回值为NULL,其中 NULL是在头文件stdio.h中定义的符号常量,其值为0,含义是“空”。 库函数exit()的具体说明: 【函数首部】void exit([程序状态值]) 【功能】关闭已经打开的所有文件,结束程序运行返回操作系统,并将“程序状态 值”返回给操作系统。当“程序状态值”为0时,表示程序正常退出;当它为非0时, 表示程序出错退出。
文件关闭函数
【函数首部】int fclose(FILE *fp) 【参数】fp 文件型指针,通过函数fopen()获得,且指向一个已经打开的文件。 【功能】关闭fp所指向的文件,同时自动释放分配给该文件的内存缓冲区。 【返回值】若正确关闭指定的文件,则返回0;否则返回非0。
标准设备文件的打开和关闭
程序开始运行时,系统自动打开三种标准设备文件,并分别定义了相应的文件型指针。 其中: 1、stdin指向标准输入(通常为键盘)。如果程序指定要从stdin所指向的文件输入数据,即从键盘输入数据。 2、stdout指向标准输出(通常为显示器)。如果程序指定要向stdout所指向的文件 输出数据,即向显示器输出数据。 3、stderr指向标准错误输出(通常为显示器)。 三种标准设备文件使用后,不必关闭。因为系统退出时将自动关闭。
文件的打开与关闭
文件的打开与关闭 C文件操作用库函数实现,包含在stdio.h 文件使用方式:打开文件→文件读/写→关闭文件 系统自动打开和关闭三个标准文件: ❖标准输入------键盘 stdin ❖标准输出------显示器 stdout ❖标准出错输出-----显示器 stderr 文件的打开(fopen函数) ❖函数原型: FILE *fopen(char *name,char *mode) ❖调用方式:fopen("文件名","使用文件方式") 例:FILE *fp; fp=fopen("d:\\user\\myfile.txt","r"); //r->Read ❖返回值:正常打开,返回指向文件结 构体的指针;打开失败,返回NULL 文件的关闭(fclose函数) ❖作用:使文件指针变量与文件“脱钩”,释放文件结构体和文件指针 FILE *fp; fp=fopen(“a.txt”,“r”); fclose(fp); ❖返回值:用于表示文件是否被正确地关闭,如果文件顺利关闭,该值 为0,否则为-1(EOF)。 ⚫ 返回值可以用ferror函数测试
FILE *fp; fp=fopen(“aa.c”,“w”); if(fp==NULL) { printf(“File open error!\n”); exit(0); /*关闭所有文件终止调用*/ } /*注意: r:读方式; w:写方式; a:追加方式; rb/wb/ab:二进制方式; +:既可读又可写 */
【例】
若使用fopen函数打开一个新的二进制文件,对该文件进行读写操作,则文件使用方式字符串应该是____wb+______。 解析:“wb+”以读写方式打开一个二进制文件,允许读取和写入数据。
当打开文件出现错误时,系统函数fopen()的返回值是(NULL ) 解析:文件打开函数的返回值是若打开文件正确,则返回一个“文件型”指针,程序 通过该指针获得文件信息,访问文件并进行各种操作。若打开文件出错,则返回值为 NULL,其中NULL是在头文件stdio.h中定义的符号常量,其值为0,含义是“空”。
⚫ 文件的读/写函数
文件尾测试函数feof()
文件尾测试函数: 文件执行读操作时,通常使用feof( )函数来判断是否到达文件尾。如果遇到文件尾, 则不能继续读取数据。 【函数首部】int feof(FILE *fp) //eof:end of 【参数】fp文件型指针,通过fopen()函数获得,指向一个已经打开的文件。 【功能】判断fp所指向的文件是否到达文件尾。 【返回值】若遇到文件尾,则返回值是非0;否则,返回值是0。 feof()函数同时适用于文本文件和二进制文件。对二进制文件执行读操作时,必须使 用feof()函数来判断是否到达文件尾;对文本文件执行读操作时,如果遇到文件尾, 则返回一个文件结束标志EOF(EOF是一个符号常量,其值在头文件stdio.h中被定义 为-1,含义是“文件尾”)。
文件的读写fputc函数
【函数首部】int fputc(char ch,FILE *fp) 【参数】ch写到文件中的字符,既可以是字符常量,也可以是字符变量或字符表达式。 fp 文件型指针,通过函数fopen()获得,且指向一个已经打开的文件。 【功能】将ch中的字符写到fp所指向文件的当前位置,同时将读/写位置指针后移1字 节,即指向下一个读/写位置。 【返回值】若正确写入字符,则返回刚写入到文件的字符;否则,返回EOF。 本函数主要用于文本文件,也可以用于二进制文件。对于文本文件,写入的是单个字 符。对于二进制文件,写入的是1字节数据。当正确地写入一个字符或1字节数据后, 文件内部的读/写位置指针会后移1字节,即指向下一个读/写位置。 【函数首部】int fgetc(FILE *fp) 【参数】fp 文件型指针,通过函数fopen()获得,指向一个已经打开的文件。 【功能】从fp所指向文件的当前位置读取一个字符,同时将读/写位置指针后移1字节,即指向下一个读/写位置。 【返回值】若正确读取字符,则返回读取的单个字符;否则,返回EOF。
文件的读写 文件打开之后,就可以对它进行读与写的操作了。 读/写文件中的一个字符 ❖fputc函数(putc函数) ⚫ 函数原型:int fputc(int c, FILE *fp) //字符常量或变量,文件指针变量 ⚫ 功能:把一字节代码c写入fp指向的文件中 ⚫ 返值:正常,返回c;出错,为EOF(-1) ❖fgetc函数(getc函数) ⚫ 函数原型:int fgetc(FILE *fp) //文件指针变量 ⚫ 功能:从fp指向的文件中读取一字节代码 ⚫ 返值:返回读到的代码值;读到文件尾或出错为EOF(-1) ❖feof函数 ⚫ 调用方式:feof(fp) ⚫ 功能:对于二进制文件读取时判断文件是否结束。 ⚫ 返值:结束-1;反之0。
例 从键盘输入一些字符,逐个把它们送到磁盘(文件)上去,直到输入一个“#”为止 #include <stdio.h> #include <stdlid.h> void main() { FILE *fp; char ch , filename[10]; scanf("%s“, filename); if((fp=fopen(filename,"w"))==NULL) //以写的方式打开文件,并判断是否是空 { printf("cannot open file\n");//包括磁盘满的情况,即新建也建不了的情况 exit(0); } /*终止程序*/ ch=getchar(); /*接收执行scanf时最后输入的回车符*/ ch=getchar(); /*第一个输入的字符被赋给变量ch*/ while(ch!='#') { fputc(ch,fp); /*字符被写入filename表示的文件中*/ putchar(ch); /*字符被输出到显示器*/ ch=getchar(); } putchar(10); /*向屏幕输出一个换行符*/ fclose(fp); /*关闭文件*/ }
写字符串函数fputs( )
【函数首部】int fputs(char *str,FILE *fp) 【参数】str 字符型指针,可以是一个字符常量,或字符数组名,或字符指针变量名。 fp 文件型指针,通过函数fopen( )获得,指向一个已经打开的文件。 【功能】把str所指向的一个字符串,舍去字符串结束标志符‘\0’后写入fp所指向文件的当前位置。同时将读/写位置指针向后移动strlen(字符串长度)字节。 【返回值】若正确写入字符串,则返回写入文件的实际字符数;否则,返回EOF。
读字符串函数fgets( )
【函数首部】char *fgets(char *str,int length,FILE *fp) 【参数】str 字符型指针,可以是一个字符数组名,或字符指针变量名。 length 整型,可以是整型常量、变量或表达式。 fp 文件型指针,通过函数fopen()获得,指向一个已经打开的文件。 【功能】从fp所指向文件的当前位置读入一个字符串,即读取length-1个字符,并在尾部自动补充一个字符串结束标志符‘\0’,组成字符串后存入str所指定的内存区;同时,将读/写位置指针后移实际读出的字符个数字节。如果在读取前n-1个字符中遇到文件尾EOF或回车,则读取结束,并在读取的字符后面补充一个字符串结束标志符‘\0’组成字符串。 【返回值】若正确读取字符串,则返回str对应的地址;否则,返回NULL。
例 从键盘读入字符串存入文件,再从文件读回显示 #include<stdio.h> void main() { FILE *fp; char string[81]; if((fp=fopen("file.txt","w"))==NULL) { printf("cann't open file");exit(0); } while(strlen(gets(string))>0) //长度大于0,获得正常的字符串 { fputs(string,fp); //把输入的字符串存入文件“file.txt” fputs("\n",fp); } fclose(fp); if((fp=fopen("file.txt","r"))==NULL) //只读方式打开文件 { printf("cann't open file");exit(0); } while(fgets(string,81,fp)!=NULL) fputs(string,stdout); //printf("%s", string); stdout:标准输出设备,即显示器 fclose(fp); }
写数据块函数fwrite( )
【函数首部】int fwrite( void *buffer,int size,int count,FILE *fp ) 【参数】buffer 字符型指针,存放写入文件数据类型的起始地址,可以是存放数据的变量地址或数组首地址,也可以是指向某个变量或数组的指针变量。 size 无符号整数,可以是整型常量、变量或表达式,表示从文件写入文件的每个数据所占用的字节数,通常使用表达式sizeof( 数据类型符 )。 count 无符号整数,可以是整型常量、变量或表达式,表示要写入文件的数据的个数。 fp 文件型指针,通过函数fopen()获得,指向一个已经打开的可写文件。 【功能】将从 buffer (缓冲区) 开始的 count 个数据(每个数据占用size字节)一次性写入fp所指向的文件。同时,将读/写位置指针后移size*count字节。 【返回值】若正确写入数据块,则返回值是count;否则,返回值是0。
读数据块函数fread( )
【函数首部】int fread(void *buffer,int size,int count,FILE *fp) 【参数】buffer 字符型指针,存放从文件中读取数据的起始地址,可以是存放数据的变量地址或数组首地址,也可以是指向某个变量或数组的指针变量。 size 无符号整数,可以是整型常量、变量或表达式,表示从文件读取的每个数据所 占用的字节数,通常使用表达式sizeof(数据类型符)。 count 无符号整数,可以是整型常量、变量或表达式,表示从文件中读取数据的个 数。 fp 文件型指针,通过函数fopen()获得,指向一个已经打开的可读文件。 【功能】从fp所指向文件的当前位置开始,一次性读入count个数据(每个数据占用size字节),并将读入的数据存放到以buffer为首地址的内存区;同时,将读/写位置指针后移size*count字节。 【返回值】若正确读取数据块,则返回值是count;否则,返回值是0。 需要注意的是,用fread()和fwrite()函数进行数据读/写时,必须采用二进制。
格式化写函数fprintf( )
【函数首部】int fprintf(FILE *fp,char *format[,argument…]) 【参数】fp 文件型指针,通过函数fopen()获得,指向一个已经打开的可写文件。 format 格式字符串。 argument 输出列表。 【功能】将argument的值以format指定的格式写入fp所指向的文件。 【返回值】若正确写入数据,则返回实际输出字符的个数;否则,返回值是负数。
格式化读函数fscanf( )
【函数首部】int fscanf(FILE *fp,char *format[,argument…]) 【参数】fp 文件型指针,通过函数fopen()获得,指向一个已经打开的可读文件。 format 格式字符串。 argument 输入列表。 【功能】根据format指定的格式从fp所指向文件中读取数据保存至argument所指 向的内存单元。 【返回值】若正确读入数据,则返回值是已读入的数据个数;否则,返回值是EOF。
【例】
若文件型指针fp已指向某文件的末尾,则函数feof(fp)的返回值是___非0_____。 解析:文件尾测试函数feof()的功能是若遇到文件尾,则返回值是非0;否则, 返回值是0。
在文件使用中,EOF的值是___-1______。 解析:对文本文件执行读操作时,如果遇到文件尾,则返回一个文件结束标志 EOF(EOF是一个符号常量,其值在文件头stdio.h中被定义为-1,含义是“文件 尾”)。
⚫ 文件的定位函数
文件指针复位函数rewind()
【函数首部】int rewind(FILE *fp) 【参数】fp 文件型指针,通过函数fopen()获得,指向一个已经打开的文件。 【功能】将fp所指向文件的位置指针重置到文件头,即把文件的位置指针重新定 位到fp所指向文件的起始位置。 【返回值】若执行正常,返回值是 0;否则,返回值是非0。 rewind函数 ❖函数原型: void rewind(FILE *fp) ❖功能:重置文件位置指针到文件开头 ❖返值:无
例 对一个磁盘文件进行显示和复制两次操作 #include <stdio.h> void main() { FILE *fp1,*fp2; fp1=fopen("c:\\c\\user\\ch4.c","r"); //带路径,双反斜杠\\ fp2=fopen("d:\\c\\user\\ch41.c","w"); while(!feof(fp1)) putchar(fgetc(fp1)); //从文件里循环的获取一个字符输出到屏幕 rewind(fp1); //跳到文件开始位置 while(!feof(fp1)) fputc(getc(fp1),fp2); //从fp1文件循环获取一个字符,写入fp2,直到指针到文件尾 fclose(fp1); fclose(fp2); }
文件随机定位函数fseek( )
文件随机定位函数 fseek( ) 可以将文件的位置指针移动到文件中的任何一个地方, 一般用于二进制。 【函数首部】int fseek(FILE *fp,long offset,int origin) 【参数】fp 文件型指针,通过函数fopen()获得,指向一个已经打开的文件。 offset 长整型数据,表示位移量,是从origin为起始位置,向后(当位移 量>0时)或向前(当位移量<0时)移动的字节数。 origin 表示起始位置。 【功能】将fp所指向文件的位置指针从origin指定的起始位置移动offset所指定 的字节数,改变文件位置指针的位置,即指向新的位置。 【返回值】若定位成功,则返回值是0;否则,返回值是非0。