c
约 29668 字大约 99 分钟
2026-04-04
c/c++开发环境
Codeblocks
官网下载:codeblocks-17.12mingw-setup.exe(带编译器版本)
中文补丁:codeblocks.mo
安装中文补丁:需要找到软件安装文件夹下的share文件夹,
进入codeblocks文件夹下新建一个新的文件夹locale,进入locale文件夹,
再新建一个文件夹并命名为zh_CN。
将codeblocks.mo文件放于此处。启动软件后点击菜单栏:settings---> environment,点击environment,选择view,再从右侧第一个下拉栏选择Chinese (Simplified)后确认并重启软件即可。
线上开发环境
https://clin.icourse163.org/help.html(帮助说明)
vscode 插件
| 插件 | 作用 |
|---|---|
| c/c++ | c/c++程序编译运行 |
| c/c++ ghu global | c/c++程序编译运行 |
| code runner | 一键自动调试 |
ASCII

第1章程序设计概述
掌握在VC6.0集成开发环境下建立与运行c程序的方法(熟悉)
(1)启动VC++
(2)新建c语言程序文件
【1】选择“文件”--->“新建”,弹出“新建”对话框;
【2】选择“文件”选项卡,选中“C++ Source File”选项,在对话框右边的“目录”文本框中输入文件的存放路径;
【3】在“文件”文本框中输入文件的存放路径;
【4】在“文件”文本框中输入程序的文件名(*.c),单击“确定”按钮;
(3)输入程序,并保存文件
保存文件时,应指定文件扩展名为.c,否则将默认保存为.cpp
(4)编译连接程序
【1】“编译”--->“构件”命令;
【2】快捷键F7;
(5)执行程序
【1】“编译”--->“执行”;
【2】ctrl+F5;
(6)关闭程序工作区
程序(了解)
完成某些事务的一种既定方式和过程或对一系列动作的执行过程的描述
计算机程序(了解)
程序:为实现特定目标或解决特定问题而用计算机语言编写的、可以连续执行并能够完成一定任务的指令序列的集合 为实现特定目标或解决问题而用计算机语言编写的能够编译运行的完成特定任务的指令序列的集合
指令:计算机可以识别的指令
指令系统:一台计算机硬件系统所能识别的所有指令的集合
|——数据:对数据形式的表示和描述(数据流)【指定程序所使用数据的数据结构和组织形式】
程序——
|——算法:对数据进行操作的描述(控制流),指定操作的步骤【程序设计的算法】
程序=算法+数据结构
程序设计:给出解决特定问题程序的过程
(1)面向过程的结构化程序设计
主要原则:自顶向下、逐步求精、模块化、限制使用goto语句
(2)面向对象程序设计
软件
-->系统软件:围绕计算机系统本身开发的程序系统(windows、Unix、dos、语言编译、数据库管理) -->应用软件:为了某种目的编写的程序系统(文字处理、专用财务、人事管理、图像处理)
计算机语言(了解)
第一代语言:编写程序繁琐,容易出错
|——操作码的二进制编码
机器语言
|——操作数的二进制编码
————————————————————————
第二代语言:不易出错
汇编语言(符号语言)
必须转换成机器语言(目标程序),才能执行
————————————————————————
第三代语言:数据用十进制表示,过程化语言
算法语言(“高级语言”)
“源程序”:高级语言编写的程序,计算机要经过翻译才能识别;
|——解释方式
翻译方式
|——编译方式
BASIC语言采用解释方式翻译执行;
c语言源程序采用编译方式翻译执行;
算法源程序—编译—>目标代码(*.obj)—连接—>可执行文件(*.exe)—执行—>结果
二进制文件 二进制文件
————————————————————————————————————
第四代语言:非过程化语言
数据库查询语言(最简单的第四代语言)
——————————————————————
第五代语言:智能化语言
eg:PROLOG语言c程序的发展及其特点(了解)
1972年 c语言的产生 由BCPL和B语言演变而来
1973~1988年 c语言的发展 1977年《可移植的c语言编译程序》
1978年《The C Programming Language》
1988年 c语言的成熟 ANSI标准
特点:
(1)功能强大、灵活,可用于完成操作系统、文字处理、图形、电子表格等工作;
(2)结构紧凑、灵活方便;
(3)具有丰富的运算符;
(4)具有丰富的数据结构;
(5)语法限制不太严格、程序设计自由度大;
(6)结构式语言;
(7)适用范围大,可移植性好;
(8)允许直接访问物理地址,直接对硬件进行操作;
(9)程序生成代码质量高,程序执行效率高;
(10)主要编程语言之一
c语言程序的结构(掌握)
(1)c语言程序是由多个函数构成的
【1】函数是c语言程序的基本单位(c语言程序是由函数组成的);
c语言程序必须有且只有一个main函数,从main函数开始执行并且以main函数结束;
【2】一个c语言程序可包含0到n个用户自定义函数;
【3】在调用系统提供的库函数之前,只要将相应的头文件包含到本文件中即可;
(2)c程序的书写格式与规则
【1】c语句都以分号作为结束标志;
【2】c程序书写自由,一行内可写若干条语句,也可将一条语句写成多行;
【3】”{}“里面的部分,通常表示程序的某一层次结构;
”{}“一般与该结构语句的第一个字母对齐,可单独占一行;
【4】变量名和函数名必须是合法的标识符;
标识符时是一个名字,只能由字母、数字、下划线组成,并且第一个字符必须是字母或下划线;
c语言对字母大小写敏感;
(3)库函数 printf 和 scanf
(4)函数由函数首部和函数体两部分组成
【1】函数首部包括对函数返回值类型、函数名、形参的说明,函数名后的一对圆括号中放入形参,即使没有形参,也不能省略;
【2】函数体由函数首部下面最外层的一对大括号中的内容组成,包括变量声明语句和执行语句;
(5)c语言程序中的注释
”/*“和”*/“之间的所有字符都为注释内容,且注释内容可以跨行;
”/“和”*“之间不能有空格;
“/*”和“*/”之间不能再嵌套书写“/*”和“*/”;流程图表示算法(掌握)
简明直观、便于理解;
流程图的“流程方向”代表控制流,不受结构化制约而任意跳转;
每个符号对应一行源程序代码,大型程序可读性较差;
N-S图表示算法(掌握)
结构化程序设计,全部算法以一个矩形框表示;
4种基本结构的N-S图
(a)顺序结构

(b)选择结构

(c)当型循环结构

(d)直到型循环结构

三种基本结构和改进的流程图(掌握)
选择,顺序,循环
UML
以面向对象图的方式描述任何类型的系统,应用领域宽第2章数据类型、运算符与表达式
C的数据类型(掌握)
内存的最小储存单位称为“位”(bit),用于存放二进制数0或1;
由8个二进制位组成一个“字节”(byte);
若干个字节组成一个“字”,用一个“字”(word),用一个“字”存储一条机器指令或一个数据;
16位——>int 占2个字节 表示范围-32768~32768【TC】 32位——>int 占4个字节 表示范围-2147483648~214783648【vc++6.0】
数据类型
->基本数据类型
-->字符型
字符型(char);
无符号字符型(unsigned char)
-->整型
短整型(short int);
无符号短整型(unsigned short int);
整型(int);
无符号整型(unsigned int);
长整型(long int);
无符号长整型(unsigned long int)
-->浮点型
单精度(float);
双精度(double);
长双精度(long double)
-->枚举类型(enum)
->构造数据类型
->数组
->结构体(struct)
->共用体(union)
->文件类型(file)
->指针类型(*)
->空类型(void)32位计算机
| 类型(关键字) | 长度(字节) | 表示范围 |
|---|---|---|
| [signed]char | 1 | -128~127 即 -27~27-1 |
| unsigned char | 1 | 0~255 即0~2^8-1 |
| [signed]int | 4 | -2147483648~2147483648 即-231~231-1 |
| unsigned[int] | 4 | 0~4294967295 即0~2^32-1 |
| [signed] short [int] | 2 | -32768~32767 即-215~215-1 |
| Unsigned short [int] | 2 | 0~65535 即0~2^16-1 |
| Long [int] | 4 | -2147483648~2147483647 即-231~231-1 |
| Unsigned long [int] | 4 | 0~4294967295 即0~2^32-1 |
| float | 4 | -3.4*10-38~3.4*1038 |
| double | 8 | -1.7*10-308~1.7*10308 |
| Long double | 8 | -1.710^-308~1.710*308 |
常量与变量(理解)
##关键字(保留字)
不能将其用作变量名、函数名;
| auto | double | int | struct |
|---|---|---|---|
| break | else | long | switch |
| case | enum | register | typedef |
| char | extern | return | union |
| const | float | short | unsigned |
| continue | for | signed | void |
| default | goto | sizeof | volatile |
| do | if | while | static |
——系统预定义标识符
标识符 |
——用户自定义标识符
对变量、函数、标号和其他各种用户定义对象的命名
(1)第一个字符必须是字母或下划线,随后的字符必须是字母、数字或下划线;
(2)大小写敏感;
(3)不能是c语言关键字;
(4)变量命名要简洁有意义的英文单词;
(5)标识符的长度:c语言标准建议至少能识别31个字符;
常量与符号常量
常量
值不能被改变的量,可为任意数据类型
从其字面形式即可判断的常量:字面常量/直接常量
符号常量
格式:#define <符号常量名> <常量>
标识符可代表一个常量,代表常量的标识符为符号常量,一般使用大写英文字母表示;
符号常量的值在其作用域内不能改变,也不能再被赋值;
变量
定义变量的一般形式:类型标识符 变量名表;
程序在运行过程中其值可以发生改变的量,代表内存中具有特定属性的一个存储单元;
C语言中要求对所用到的变量作强制性定义,“先定义,后使用”
目的:(1)保证程序中的变量名使用正确;
(2)指定确定类型,分配相应存储单元;
(3)指定每一变量属于同一类型,便于编译检查所进行的运算是否合法;
整型数据(理解)
整型常量
(1)十进制整型常量;
(2)八进制整型常量,0开头;
换算成十进制(123)~8=1*8^2+2*8^1+3*8^0=83
(3)十六进制整型常量,0x开头,引导符0不可省略,x可大小写;
转换成十进制(123)~16=1*16^2+2*16^1+3*16^0=291
(0Xde)~16=13*16^1+14*16^0=208+14=222
整型常量后加l或L,长整型;358000L(长整型数据358000)
无符号的整型常量后缀,u或U;0XA5Lu(十六进制无符号长整型数据A5)
整型变量
1.整型数据在内存中的存放形式
在内存以二进制形式存放;
最高位存放符号,正整数的最高位为0,负整数的最高位为1;
数值在计算机内以补码的形式存放,正数的补码和原码相同,
负数的补码:
(1)写出该负整数对应的正整数的二进制码;
(2)将该二进制码各位取反,得到反码;
(3)将反码加1,得到补码;
2.整型数据的溢出
整型变量的最大允许值为2147483647=-2^31-1
变量变为long型,以%ld格式输出;
只有十进制才有正负号!实型数据(理解)
实型常量
实型常量就是实数(浮点数)
(1)十进制小数形式,由数码0~9以及小数点组成(必须含有小数点);
.231、300.
(2)指数形式,由十进制数、阶码(指数)标志e或E以及阶码组成;
-2.8E2、5.89e-3
实型常量的整数部分为0可省略;
-.57、-.175E3
数字E或e之前必须有数字,且e或E后指数必须为整数
规范化的指数形式:小数点左边有且仅有一位非零的数字;
6.33e6、3.145e-2
一个实型变量在输出时,是规范化指数形式输出的;
实型变量默认为double类型处理;
实型变量
1.实型数据在内存中按指数形式存储,分为 小数部分 和 整数部分 分别存放;
通常以24位表示小数部分(占的bit越多,数的有效数字越多,精度越高),8表示指数部分(占的位数越多,表示的数值范围越大);
2.实型数据的四舍五入
flaot只能接收7位有效数字;
double最多15位有效数字;
避免太大的数和很小的数一起运算,保证高精度运算时,采用double和long double变量;字符型数据(理解)
字符常量:用一对单引号括起来的一个字符;
字符是按其所对应的ASCLL码值来存储的,一个字符占一个字节;
'0'字符常量
0 整型常量
'a' | 97 |
|---|---|
'A' | 65 |
|---|---|
'0' | 48 |
'!' | 33 |
转义字符
ddd为八进制的ASCLL代码;
Hh为十进制的ASCLL代码;
\134表示反斜线;
\x0A表示换行;
a的ASCLL码值为97;
A的ASCLL码值为65;
| 转义字符 | 转义字符的含义 | ASCLL码(十进制) |
|---|---|---|
| \0 | 空字符(NULL) | 0 |
| \a | 鸣铃 | 7 |
| \b | 退格 | 8 |
| \t | 横向跳到下一制表位置 | 9 |
| \n | 回车换行 | 10 |
| \f | 走纸换页 | 12 |
| \r | 回车不换行,光标回到本行开头 | 13 |
| " | 双引号符 | 34 |
| ' | 单引号符 | 39 |
| \ | 反斜线符“\” | 92 |
| \ddd | 1~3位八进制数所代表的字符 | |
| \xhh | 1~2位十六进制数所代表的字符 |
字符串常量
由一对双引号括起来的由0个或多个字符组成的字符序列,任何字母、数字、符号和转义字符都可组成字符串;
字符串的存储方式:串中的每个字符(转义字符为一个字符)按照ASCLL码值的二进制形式存储在内存中,并在串尾加“串结束标志”,
长度为n个字符的字符串常量,在内存中占有n+1个字节的存储空间;
| C | h | i | n | a | \0 |
|---|---|---|---|---|---|
ASCLL码为0的字符‘\0’;
字符变量
字符变量用来存放字符型数据,一个字符变量只能存放一个字符
char c1,c2;
c1='a',c2='b';
字符数据和整型数据互通,字符型数据可以以整型数据和字符型数据输出
【unsigned char】字符型数据只占1字节,只能存放0~255内的整数;
字符型数据的取值范围为-128~127;
c语言运算符
(1)算术运算符
加【+】,减【-】,乘【*】,除【/】,求余【%】,自增【++】,自减【--】
(2)关系运算符
大于【>】,小于【<】,等于【==】,大于等于【>=】,小于等于【<=】,不等于【!=】
(3)逻辑运算符
与【&&】,或【||】,非【!】
(4)位操作运算符
按位与【&】,按位或【|】,按位非【~】,按位异或【^】,左移【<<】,右移【>>】
(5)赋值运算符
简单赋值【==】,复合运算赋值【+=,-=,*=,/=,%=】,复合位运算赋值【&=,|=,^=,>>=,<<=】
(6)条件运算符
【?:】
(7)逗号运算符
【,】
(8)指针运算符
取内容【*】,取地址【&】
(9)求字节数运算符
【sizeof】
(10)特殊运算符
括号【()】,下标【[]】,成员运算符【->,.】
(11)其他
函数调用运算符算数运算符
当“/”被用于整数或字符时,结果也为整数
10%3=1
10/3=1
向零取整
-5/3=-1;
-5%3=-2;
c语言中,整型数据、实型数据、字符型数据可混合运算
低精度->高精度
注意:
(1)标准c语言并没有规定表达式中的求值顺序
(2)c语言的运算符若同时出现在表达式中,竟可能多的将若干个字符组成一个运算符(自左向右)
(3)c语言没有统一规定调用函数时的实际参数的求值顺序(实参的求值顺序为从右至左)自增、自减运算符
++a 先加后用
a++ 先用后加
(1)自增运算符【++】和自减运算符【--】只能用于变量,不能用于常量或表达式;
(2)++和--运算的结合方向是“自右至左”;
使用中应注意的问题
(1)
| j=(++i)+(++i); | j=7+7=14,i=7 |
|---|---|
| j=(++i)+(++i)+(++i); | j=7+7+8=22,i=8 |
先计算两次(++i),然后再将自增的i相加;
| j=(j++)+(j++) | j=5+5=10,j=7 |
|---|---|
(2)c语言编译系统会尽可能多地将若干个字符组合成一个运算符;
i+++j -----> (i++)+j
自左至右
(3)实参的求值顺序是自右至左;
printf("%d,%d",i,i++); -------> 5,5
printf("%d,%d",I,++i);--------> 6,6
强制类型转换
#include <stdio.h>
int main(int argc, char *argv[])
{
float f;
int i;
f=7.5;
i=(int)f;
printf("f=%f,i=%d\n",f,i);
return 0;
}
//f=7.500000,i=7若a本身是整型变量,强制类型转换以后得到一个单精度的中间变量,用于赋值或参与运算,而a本身的类型保持不变赋值运算符(掌握)
“=”:将一个数据或表达式的值赋给一个变量;
类型转换
(1)双精度数据赋给单精度变量时,数值的范围不要超出单精度数的表示范围,否则会发生溢出;
(2)符号扩展 字符型--->整型
系统将字符型看作是无符号的,则将字符型的8位直接放到整数型的8位,高位补0
‘xFE’1111 1110
y:0000 0000 | 0000 0000 | 0000 0000 | 1111 1110 |
|---|---|---|---|
字符'xFE'被当作无符号字符型
系统将字符型看作是带符号的,则根据字符型数据的最高位来填补整数型的高24位
若字符型数据的最高位为“0”,则高24位补0;
若字符型数据的最高位为“1”,则高24位补1;
'xFE'1111 1110
y:1111 1111 | 1111 1111 | 1111 1111 | 1111 1110 |
|---|---|---|---|
字符'xFE'被当作带符号字符型算数表达式
用算数运算符和括号将运算对象【变量、常数、函数】(也称操作符)连接起来的、符合c语言语法规则的式子
赋值表达式(掌握)
赋值表达式:将一个变量和一个表达式连接起来的式子
变量 赋值运算符 表达式
c语言的编译程序总是将精度较低的类型向精度较高的类型转换;
强制类型转换运算符
(类型说明符)(表达式)
转换为类型说明符所表达的类型;
在强制转换时,得到的是所需类型的中间变量,原来变量或表达式的类型并没有发生变化;
(float)a
A本身是整型变量,仍然是整型变量;
“=”左边必须是一个变量;
“%”的操作数只能是整数;
逻辑运算符和逻辑表达式
关系运算符及关系表达式(掌握)
关系运算:对两个运算量进行比较,关系运算的结果为逻辑值(布尔值)优先值:算术运算 > 关系运算 > 赋值运算关系表达式:用关系运算符将两个表达式连接的式子关系表达式的值是一个逻辑值,即只有“真【1】”“假【0】”,为无符号整数(1)如果关系式两边的值类型不一致,例整型和实数型,系统自动按实数型比较;
(2)对于实数型,尽量避免x==y的比较,会有舍入误差;
(3)“==”,“=”不同;
(4)避免书写错误;逻辑运算符和逻辑表达式(掌握)
| 运算符 | 名称 | 例子 | 操作数 | 结合性 | 优先级 |
|---|---|---|---|---|---|
! | 逻辑非 | !a | 单目 | ——> | 高 |
&& | 逻辑与 | a&&b | 双目 | <—— | ` |
| ` | ` | 逻辑或 | `a |
&&:当且仅当两个运算量的值都为真,运算结果为真,否则为假;
||:当且仅当两个运算量的值都为假,运算结果为假,否则为真;
!:当运算量的值为真,运算结果为假,否则为真;
(1)逻辑运算的结果是逻辑值“真”或“假”;
(2)逻辑运算两侧的操作数,除了0和非0的整数外,也可为其他类型的任何数据;
(3)逻辑表达式的计算,只有在必须执行时才执行;对于&&运算:若计算出的第一个值为假的表达式是第一个,则后面的表达式都不用计算,表达式的结果一定为0;
对于||运算:若计算出的第一个值为非0的表达式是第一个,则后面的表达式都不用计算,表达式的结果一定为1;逗号运算符和逗号表达式(了解)
逗号表达式:把多个表达式连接起来组成一个表达式
表达式1,表达式2,……,表达式n
求值过程是分别求出每个表达式的值,并以最后的表达式n的值作为整个逗号表达式的值;
(1)程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定要求整个逗号表达式的值;
(2)并不是在所有出现逗号的地方都组成逗号表达式;
运算符的优先级与结合级
优先级别不同时,运算由优先级决定,优先级1最高,优先级15最低;
在同一优先级别中,运算的先后则由结合方向决定;

第3章程序结构
c语言中的语句(掌握)
c语言程序由函数组成,一个函数包含声明部分和执行部分,执行部分即由c语言语句组成,而声明部分并非c语言语句;
c语言语句分为5类:
(1)表达式语句。在任何表达式后面加一个分号“;”
a=b+c;
x+y;
i++;j--;(2)控制语句。9种:
条件语句
if(条件){…} else {…}多分支选择语句
switch循环语句
for(条件){…}
while(条件){…}
do{…}while(条件)结束本次循环语句
continue中止执行switch或循环语句
break转向语句
goto从函数中返回语句
return(表达式)(3)函数调用语句。函数加分号构成;
printf("…");(4)空语句。只有一个分号的语句,表示什么也不做。一般用于占位或者延时;
(5)复合语句。用{}把一些语句括起来,又称分程序;
{t=x;x=y;y=t;}若复合语句内只有一条语句,大括号可以省略;
格式化输入输出函数(掌握)
最常用是输入输出函数为scanf,printf,petchar,gutchar;
1.标准输出函数printf
printf("格式控制字符串",输出表列);(1)printf的一般表示形式:

(2)格式字符
1)d格式符(或i格式符),用来输出十进制整数;
%d,按整型数据的实际长度输出;
%md,m指输出字段的宽度,如果数据的位数小余m,则在左端补空格;若大于m,则按实际位数输出;
%ld,输出长整型数据;2)o格式符,以八进制形式输出整数型;八进制的整数型都被处理为无符号数,即符号位也一起作为八进制数的一部分输出;
int x=-1;
printf("%d,%o",x,x);
输出结果:-1,37777777777
(-1)的补码为:11111111 11111111 11111111 11111111,再转换为8进制
8 4 2 1,这种方法可以在2,8,16进制之间相互转换,涉及到8进制转化为16进制时,需要改为4,2,1计算,再转化为8,4,2,1进制转换:http://xinzhi.wenda.so.com/a/1537180588200142
3)x格式符,以十六进制输出整数型;不会输出现负的十六进制
int x=-1;
printf("%d,%x",x,x);
输出结果:-1,ffffffff4)u格式符,以十进制形式输出无符号数;
unsigned int x=65535;
int y=-2;
printf("%d,%o,%x,%u\n",x,x,x,x);
printf("%d,%o,%x,%u\n",x,x,x,x);
输出结果:x=65535,177777,ffff,65535
y=2,37777777776,fffffffe,4294967294转换成二进制,不带负号5)c格式符,用来输出一个字符;字符型转换为整型时,截取整数型最后一个字节的无符号数作为字符的ASCII值
char c='A';
int i=-159;
Printf("%c,%d\n",c,c);
Printf("%c,%d\n",i,i);
运行结果:A,65
a,-159
-159的补码:11111111111111111111111101100001
转换为十进制为:97
ASCII值对应的字符为a6)s格式符,用来输出一个字符串
%s,输出字符串,不含双撇号;
%ms,输出字符串占m列,串长度小于m则左边补空格,大于m按原长度输出;
%-ms,串长度小于m,则字符串向左靠,右边补空格;
%m.ns,输出占m列,只取左端的n个字符,n个字符在m列的右侧,左边补空格;
%-m.ns,n个字符输出在m列的左侧,右端补空格;n>m,m=n,保证字符正常输出;
Printf("%3s,%7.4s","good morning","good morning");
Printf("%-5.3s,%.6s","good morning","good morning");
运行结果:
Good morning, good
Goo good m7)f格式符,用来输出实型数(包括单精度和双精度),以小数形式输出
%f待输出数据的整数部分全部输出,小数部分输出6位;输出双精度时,也可用%lf;
输出的数字并非全部都是有效数字,单精度有效位数7位,双精度16位;
%m.nf,输出的数据占m列,n位小数,若数值长度小于m,左端补空格;
%-.nf,输出的数值向左靠,右端补空格
F的值为123.456,而输出的是123.456001,由于实数在内存中的存储误差引起的
8)e格式符,以指数形式输出实数
%e,规范化指数形式输出,6位小数,【指数占5位,其中“e”占1位,指数符号占1位,指数占3位】

输出实数占14列,随系统改变
%m.ne和%-m.ne

9)g格式符,用来输出实数,根据实数的大小,自动选取f格式或e格式(输出时选占宽度较小的一种),不输出无意义的0

说明:
【1】除了X,E,G外,其他格式字符必须用小写;
区别在于:当输出的数据里面含有字母时,以小写格式字符输出小写字符,以大写格式字符输出大写字符;
【2】可以在printf函数中的“格式控制”字符串内包含转义字符;
【3】输出“%”字符,连续使用两个%%表示;
【4】在%与格式字符之间可加入“+”、空格或“#”;标志 | 说明 |
|---|---|
+ | 输出数据的符号(正号或负号) |
空格 | 输出值为正时加空格,为负时加符号 |
# | 对c,s,d,u类无影响; 对o类,在输出时加前缀0; 对x类,输出时加前缀0x;` 对e、f、g类,当结果有小数时给出小数点 |

2.标准输入函数scanf
(1)scanf函数的一般形式 scanf("格式控制字符串",地址表列); “地址表列”:由若干个地址组成的表列,可以是变量地址或者字符串的首地址;
“&”是“地址运算符”,&a表示a在内存中的地址
以"%d%d%d"格式输入数据时,不能用逗号或其他符号作为两个数据间的分隔符【这三个数据是按顺序存放到变量a,b,c所在的内存地址中】
(2)格式说明
格式 | 说明(scanf格式字符) |
|---|---|
d,i | 输入带符号十进制整数 |
u | 输入无符号十进制整数 |
o | 输入无符号八进制整数 |
x,X | 输入无符号十六进制整数(大小写作用相同) |
c | 输入单个字符 |
s | 输入字符串,输入时以空格或按enter建结束 |
f | 输入实数型(用小数形式或指数形式均可) |
e,E,g,G | 与f作用相同,e与f、g可以相互替换(大小写作用相同) |
字符 | 说明(scanf的附加格式说明字符) |
|---|---|
l | 输入长整型数据(%ld,%lo,%lx,%lu)以及双精度型数据(%lf或%le) |
h | 输入短整型数据(%hd,%ho,%hx) |
域宽 | 指定输入数据所占宽度(列数),域宽应为正整数 |
* | 表示本输入项在读入后不赋给相应的变量 |
1)对无符号型变量所需的数据,可以用%u、%d或%o、%x格式输入
2)可以指定输入数据的宽度,系统自动按它截取所需的数据
3)若在%后有一个附加说明符“*”,表示跳过它指定的列数
4)输入数据时不能规定精度
(3)注意: 【1】在“格式控制字符串”中除了格式说明以外还有其他字符,则输入数据时在对应位置输入与这些字符相同的字符
与printf不同,scanf的格式控制字符串中一般不会出现转义字符
【2】用“%c”格式输入字符时,空格也应作为有效字符输入
【3】在输入数据时,遇到以下情况时认为该数据结束
空格、enter、tab键; 指定的宽度结束; 非法输入;
字符输入/输出函数(掌握)
1、字符输出函数putchar putchar函数的作用:向终端输出一个字符
格式:
putchar(ch)
ch为字符变量或整型变量
char ch='A'; /*可替换为int ch=65;*/
putchar(ch);2.字符输入函数getchar 作用:从终端设备输入一个字符,getchar没有参数
一般形式:
getchar();
函数的值就是从终端设备得到的字符
char ch;
getchar();
注意:putchar函数一次只能输出一个字符和getchar函数一次只能输入一个字符顺序结构程序举例(掌握)
顺序结构:程序严格按从上到下的顺序执行,没有分支、跳转或循环;
【例3.10】从键盘输入一个大写字符,要求用小写字母输出。程序如下:
image-20220512185208032 【例3.11】输入三角形的两边的长度及其夹角,求三角形的三边及面积
夹角转化为弧度值:alfa×π/180
第三边长度:c= sqrt(a2+b2-2abcos(alfa))
面积:s=1/2×absin(alfa)
image-20220512185254202
选择结构(分支结构)(掌握)
1.if语句的基本形式
(1)if语句单分支结构
if(表达式) 语句;【例3.15】找出两个整数中的较大数(单分支结构)
image-20220512185403540
(2)if语句双分支结构
if(表达式) 语句1;
else 语句2;【例3.16】找出两个整数中的较大数(双分支结构)
image-20220512185447479
2.if语句的嵌套
在if语句中又包含一个或多个if语句可以实现多分支结构;
(1)if-else if结构
if(表达式1) 语句1;
else if(表达式2) 语句2;
else if(表达式3) 语句3;
…
else 语句n;【例3.17】根据其ASCII码判断该字符属于控制字符(<32)、数字字符、大写字符、小写字符还是其他字符
image-20220512185555345 image-20220512185612346 image-20220512185629336
(2)嵌套的if-else结构
if(表达式1)
if(表达式2) 语句1;
else 语句2;
else
if(表达式3) 语句3;
else 语句4;若表达式1为真,表达式2为假,执行语句2;
若表达式1为假,表达式2为真,执行语句3;
表达式为非0即为真,0则为假
【例3.18】比较两个数的大小关系
image-20220512185736374 【例3.19】求一元二次方程:ax^2+bx+c=0的根1e-6足够接近0(为数字“1”)
image-20220512185811088 image-20220512185828596
注意:
1)在if语句中,条件判断表达式必须用括号括起来,后面不加分号,其后的语句后面加分号;
2)if和else同属于一个if语句,else不能作为语句单独使用;
3)if-else语句执行时,只执行if或else有关的语句;
4)if-else的后面,单条语句后加“;”,多条语句要用“{}”括起来,组成复合语句,{}后不必加;
5)在if关键字后面的括号中通常是逻辑表达式或关系表达式,也可是其他表达式(赋值表达式或一个变量),只要表达式的值为非零值,即为“真”;条件运算符(掌握)
是c语言中唯一一个三目运算符,即有三个参与运算的量;
一般形式:表达式1?表达式2:表达式3
求值规则:若表达式1为真,则表达式2的值为整个条件表达式的值,否则表达式3的值为条件表达式的值注意:
1)条件运算符的运算优先级低于逻辑运算符、关系运算符和算术运算符,但高于赋值运算符;
2)条件运算符的结合方向自右向左;
3)条件表达式中的“表达式2”“表达式3”可以是数值表达式、赋值表达式、函数表达式;
4)条件表达式中,表达式1的类型和表达式2、表达式3的类型可以不同;表达式2和表达式3的类型也可不同,类型为较高精度的类型;
【例3.20】输出两者中的较大者
image-20220512185944207
switch语句(掌握)
一般形式:
switch(表达式)
{
case 常量表达式1:语句序列1;
case 常量表达式2:语句序列2;
…
case 常量表达式n:语句序列n;
default:语句序列n+1;
}执行过程: 计算表达式的值,并逐个与其后的常量表达式值进行比较,当表达式的值与某个常量表达式的值相等时, 便开始执行其后的语句,然后不在进行判断,继续执行后面所有的case语句。若与所有常量表达式的值都不匹配,则执行default的值;
【例3.21】根据输入的整数,判断并输出是星期几
image-20220512190054442
程序说明: 1)switch语句的表达式可为整型、实型、枚举型,每个case语句后面的常量表达式的类型必须与switch后面的条件表达式类型相同; -->可为整型、字符型、bool型
2)每个case后的常量表达式的值不能相同;
3)case后面的常量表达式是switch后面括弧内的表达式可能取到的值;
4)case和其后的常量表达式中间要有空格;
5)在case后,允许多个语句构成复合语句,不能用“{}”括起来;
6)在switch结构中,switch、case、default都为关键字;case后面的常量表达式仅仅起语句标号作用,并不进行条件判断。 表达式的值和某标号相等则转向该标号执行,但不能跳出整个switch语句,而是继续执行所有剩下的case后的语句;
7)c语言还提供了一种break语句,用于跳出switch语句,从而避免输出不应有的结果;
8)添加break语句个case和default语句的顺序可以改变,否则不要随意改动;
9)多个case语句可共用一组执行语句;
选择程序结构举例(熟练)
【例3.22】输入三个整数x、y、z,把这三个数由小到大输出
image-20220512190246875 【例3.23】计算器程序,输入运算数和四则运算符,输出计算结果
image-20220512190307663 【例3.24】求奖金
image-20220512190327037
循环结构程序设计
特点:在给定条件成立时,反复执行某程序段,直到条件不成立;
循环条件:给定的条件;
循环体:循环体;
goto语句(了解)
【例3.25】用goto语句和if语句构成循环,求1+2+3+…+99+100的值
image-20220512190412196
程序中的goto语句是无条件转移语句
使用格式:
goto 语句标号;
语句标号可以是任何合法的标识符,只须在后面加一个“:”结构化程序设计方法主张限制使用goto语句,因为会造成程序层次不清不易读
两种用途:
1)与if语句一起构成循环结构;
2)从循环体跳到循环体外;
while语句构成的循环结构(掌握)
用来实现“当”型循环结构
一般形式:
while(表达式) 语句;
while后面用圆括号括起来的表达式,可为c语言任何表达式,即循环条件;
后面的语句为循环体,多语句要用大括号括起来组成复合语句;一定要有使使表达式的值为0的操作;
【例3.26】用while语句,求1+2+3+…+99+100的值
image-20220512190604768
1)i为循环变量,sum为累加变量,使用时先赋初值; 2)第一次循环时,循环条件的值为0,则循环一次也不执行; 3)在循环中一定要有使循环趋向结束的操作; 4)语句的先后位置必须符合逻辑;
【例3.27】统计输入的字符个数
image-20220512190701058
getchar()!='\n',表明只要不按下enter键就继续循环
结论: 1)表达式一般是关系表达式或逻辑表达式; 2)不加“{}”,while语句的范围只到while后面的第一个分号处; 3)避免死循环;
image-20220512192409644 image-20220512190754147
do-while语句构成的循环结构(掌握)
一般形式:
do
循环体语句;
while(表达式);说明: 1)do是c语言的关键字,必须和while联合使用; 2)while(表达式)后的“;”不可省略,表示do-while语句的结束; 3)表达式可以是c语言中任意合法的表达式; 4)循环体内需要多个语句,应用大括号括起来,组成复合语句;
循环至少执行一次,“直到型”循环
for循环(掌握)
一般形式:
for(表达式1;表达式2;表达式3;) 语句;
理解形式:
for(循环变量赋初值;循环条件;循环变量增值;) 语句;说明: 1)表达式1可省略,此时应在for语句之前给循环变量赋初值,后面的分号不能省略;
2)表达式2可省略,此时循环将无终止地进行下去;
3)表达式3可省略,此时需要另外设法保证循环能正常结束;
4)可省略表达式1和表达式3,此时for语句完全等同于while语句;
5)3个表达式都可省略 ,【for( ; ; ) 语句==while(1) 语句;】即死循环;
6)表达式1可以是与循环无关的其他表达式,表达式1和表达式3可以是逗号表达式;
7)表达式2可以是关系表达式,逻辑表达式,也可以是其他表达式,只要其值为非0,就执行循环体,
只有表达式2,无1和3,其作用是每读入一个字符后立即输出该字符,直到输入一个“换行符”为止;
【例3.30】打印200以内能被3整除的所有整数,每行打印10个数。
image-20220512190941515
循环的嵌套(掌握)
循环的嵌套:一个循环体内又包含另一个完整的循环结构; 多层循环:内嵌的循环中还可以嵌套循环;
三种循环(while循环、do-whie循环和for循环)可以互相嵌套
【例3.31】在屏幕上输出所有的3位以内的二进制数
image-20220512191028344
break语句和continue语句(掌握)
1.break语句
break语句只能用于switch语句或循环语句中,作用是跳出switch语句或跳出本层循环
【例3.32】找出第一个各位之和与各位之积相等的三位数
image-20220512191116178
注意:
当break处于嵌套循环中时,它将只跳出本身所在层次的循环,而对其他层次无影响;
break不能用于循环语句和switch语句之外的任何语句;
2.continue语句
continue语句只能用在循环体中,用于结束本次循环;
【例3.33】输出20以内不能被7整除的数
image-20220512191157907
continue语句用于结束本次循环,并回到循环入口处,判断循环条件是否成立;
break语句用于结束整个循环过程,不再判断执行循环的条件是否成立;
应用综合举例(理解)
【例3.34】编写程序统计输入的字符串中,大写字母、小写字母、数字字符和其他字符的个数,并按Enter键结束输入
可使用判断字符类型的isupper()等函数,要使用头文件ctype.h isupper(c)判断大写字母 islower(c)判断小写字母 isdigit(c)判断数字字符image-20220512191316572 【例3.35】求s=a+aa+aaa+aaaa+aaa...a的值,其中a是一个数字。例如2+22+222+2222+22222(此时共有5个数相加),几个数相加由键盘输入
image-20220512191343557 image-20220512191549474 image-20220512191456485 image-20220512191611720 image-20220512191626350 image-20220512191640124
选择、冒泡排序
#include<stdio.h>
//冒泡排序
#define M 5
int main()
{
int a[M],i,j,temp,flag=1;
printf("输入要排序的%d个数:",M);
for(i=0;i<5;i++)
{
scanf("%d",&a[i]);
}
for(i=1;i<M && flag==1;i++) //i=1因为j+1只需排序M-1次
{
flag=0;
for(j=0;j<5-i;j++)
{
if(a[j]>a[j+1])
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
flag=1;
}
}
}
printf("排序后\n");
for(i=0;i<M;i++)
{
printf("%d\t",a[i]);
}
printf("\n");
}//选择排序
#include<stdio.h>
#define M 5
int main()
{
int a[M],i,j,k,temp;
printf("输入要排序的%d个数:",M);
for(i=0;i<M;i++)
{
scanf("%d",&a[i]);
}
for(i=0;i<M;i++)
{
k=i;
for(j=i+1;j<M;j++) //每次循环会找到最小数的下标
{
if(a[k]>a[j])
{
k=j;
}
}
if(k!=i)
{
temp=a[k];
a[k]=a[i];
a[i]=temp;
}
}
printf("排序后\n");
for(i=0;i<M;i++)
{
printf("%d\t",a[i]);
}
printf("\n");
}程序结构小结(掌握)
C语言中的语句分为5类:
1)表达式语句
2)函数调用语句
3)复合语句
4)空语句
5)控制语句
“#”开头的是c语言的预处理命令
scanf函数将键盘输入的内容送入变量所在地址,“&”符号代表取地址;
操作对象只能为变量,因为表达式是没有存储地址的;
可以规定输入宽度,不能规定输入精度;
不能使用转义字符,会出错
switch语句中,case语句后面必须是常量,不能为变量或变量表达式
后面的表达式必须是整型,不能为浮点型,因为不能作精确比较
循环中要使用到的变量在进入循环前,必须有确定的值
第4章 数组
一维数组的定义
EPS:用来表示无限小值,用来保证精度,可预先定义
#define eps 1e-6数组:指一组类型相同的数据构成的有序集合;
数组元素(数组成员):一个个体概念,指数组中存放一个数据的单元;
定义方式:
类型说明符 数组名[常量表达式];| 类型说明符:指数组元素的数据类型; |
|---|
| 数组名:数组的标识符,命名标准遵循标识符命名规则; |
| 常量表达式:元素的个数,必须是整型值,可包含常数、符号常量、常量表达式,但不能包含变量; **不允许动态定义数组 ** |
注意:
【1】数组名不能与其他变量同名;
【2】常量表达式代表数组的元素个数,称为数组长度,不是下标的上界,数组元素的下标是元素相对于数组起始地址的偏移量,所以下标从0开始;
【3】分配内存单元,按其下标顺序连续存放。存储单元的长度==“数组长度 * 每个数组元素占有的字节数”;
数组元素a[i]的地址=数组首地址+i * 数组元素的数据类型所占有的单元数;
【4】数组名中存放的是一个地址常量,数组名代表整个数组的首地址;
一维数组的初始化
定义数组时也可对数组赋初值,其一般形式:
类型说明符 数组名[常量表达式]={值1,值2,...,值n};大括号内的数据将依次赋给数组元素;
初值的个数必须<=数组长度,不足部分系统自动为其赋值0,为全体元素赋值时,数组长度可以省略;
一维数组元素的引用
应用数组的任意一个元素的方法是:数组名[下标表达式],其取值范围:“0”-“元素个数-1”;
注意:只能逐个引用数组元素,不能一次引用整个数组;
一维数组程序举例
【例4.3】从键盘上输入8个整数,用冒泡排序法对8个数排序(由小到大),并在屏幕上显示出来。
image-20220512192543968 改进的冒泡排序法
image-20220512192606621 【例4.4】假设数组a中有10个互不相同且按从小到大排列的数,从键盘上再输入一个同类型的数x,在数组a中查找x,如果找到,输出相应的下标,否则输出“未找到”。
(1)顺序查找
image-20220512192640774 (2)对半查找【要求数组有序排列】
基本思想:首先比较x和数组a的中间元素,若相等,则查找成功;否则,若x较小,则它只可能在数组a的前半部分,反之它只可能在数组a的后半部分;
image-20220512192707310
二维数组的定义
当数组中每个元素带有两个下标时,称为二维数组;
一般形式:类列说明符 数组名[常量表达式1][常量表达式2];二维数组可看作一个矩阵,第一个下标为矩阵的行,第二个下标为矩阵的列;
存储单元的数目==“行长度 * 列长度 * 每个数组元素占有的字节数存放二维数组元素空间中的第一个单元为“二维数组首地址”,第一个单元的地址称为对应数组元素的地址;
二位数组在内存中的排序顺序是按行存放;
c语言允许使用多维数组
二位数组的初始化
(1)分行给二维数组赋初值
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
(2)可将所有数据写在一个大括号内,按数组排列的顺序对各元素赋初值
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
(3)可对部分元素赋初值
int a[3][4]={{1},{3,5,-6},{0,9}};
(4)若全部赋初值,定义数组时对第一维的长度可以不指定,但第二维的长度不能省
int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
等价于
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};二维数组元素的引用
引用二维数组元素时必须带有两个下标,引用形式:数组名[行下标表达式][列下标表达式]
下标可为整型表达式;数组元素可以出现在表达式中,也可被赋值;
使用数组元素时,下标值不得超越数组定义中的上、下界;
注意:系统不会自动检验数组元素的下标是否越界,地址越界实际上就是使用了“未知”的内存单元,从而可能对系统数据造成破坏
二维数组程序举例
【例4.6】有一个3 * 4的矩阵,要求编程序求出其中值最大的那个元素的值,以及其所在的行号和列号
image-20220512192932597 【例4.7】有5个学生,学习3门课程,已知所有学生的各科成绩。编程求出每个学生的平均成绩及每门课程的平均成绩。
image-20220512193002465
字符数组
c语言对字符串的约定
字符串(string)是由双引号括起来的零个或多个字符组成的有限字符 以'\0'作为字符串结束符,它占用一个存储空间,但不计入字符串长度
字符串的存储
(1)直接赋初值
char str[10]={"program"};
char str[10]="program";
系统自动用'\0'填充数组中剩余的单元;
(2)省略数组长度
char str[]="program";
(3)数组的方法赋值
char str[10]={'p','r','o','g','r','a','m','\0'};字符串的输入\输出
(1)逐个字符输入\输出
char str[10]="program";
int i;
for(i=0;str[i]!='\0';i++)
{
printf("%c",str[i]);
}
(2)字符串整体输入\输出
可利用%s对字符串整体输入\输出
注意:
1)使用%s对字符串进行输入或输出时,其参数为字符数组名
2)若输入的字符串中含有空格、回车或跳格,系统会将其替换为'\0'
3)不检查字符数组空间是否够用,输入字符大于字符数组长度,可能导致地址越界
4)应确保数组中包含结束符"\0"字符串处理函数
【1】字符串输入函数gets
调用格式:gets(字符数组名)
利用gets函数或"%s"输入字符串的区别在于gets函数将空格、跳格符作为普通字符读入,把换行符作为输入结束。
而"%s"将空格、跳格、换行都看作是输入结束
【2】字符串输出函数puts
调用格式:puts(字符数组名)
用puts函数输出的字符串中可以包含转义字符
char str[]={"China\nBejing"};
puts(str);利用puts函数或"%s"输出字符串时,区别在于puts函数会将字符串末尾的“\0”替换为换行并输出,而“%s”不会
【3】字符串连接函数strcat
调用格式:strcat(字符数组名1,字符数组名2)
说明:函数功能是把字符数组2中的字符串连接到字符数组1中字符串的后面,并删去字符串1后的字符串结束标识符“\0”
注意:
(1)字符数组1必须足够大,以便容纳连接后的新字符串
(2)连接前,两个字符串的后面都有一个'\0',该方法连接时将字符串1后面的'\0'删去,只在新字符串最后保留一个'\0'
【4】字符串赋值函数strcpy
调用格式:strcpy(字符数组名1,数组字符名2)
说明:把字符数组2为起始位置的字符串复制到字符数组1中,字符串结束标识“\0”也一同复制。字符数组2可以是一个字符串常量
注意:
(1)字符数组1的长度>字符数组2的长度
(2)字符数组1必须写成地址形式(或数组名形式),字符串2可以是地址,也可以是一个字符串常量
(3)复制时连同字符串后面的“\0”一起复制到字符数组1中
```c
char s1[10]="ABCDEFG",s2[10]="abc";
strcpy(s1,s2);
puts(s1);
运行结果:abc
```
此时s1数组的存储情况为:
a b | c \0 | E F | G \0 | \0 | \0 |
|---|---|---|---|---|---|
(4)不能用赋值语句将一个字符串常量或字符数组直接赋给一个字符数组
str1="China";//错误
str1=str2;//错误
应该改为:
strcpy(str1,"China");
strcpy(str1,str2);【5】字符串比较函数strcmp
调用格式:strcmp(字符数组名1,字符数组名2)
说明:按照ASCII码顺序比较两个数组中的字符串,并返回测试结果。
“字符串1==字符串2”,返回值==0
“字符串1>字符串2”,返回值>0
“字符串1<字符串2”,返回值<0若出现不相等的字符,则以第一个不相同的字符的比较结果为准
if(strcmp(str1,str2)==0) printf("yes");【6】测字符串长度函数strlen
调用函数:strlen(字符数组名)说明:函数功能是测量字符串的实际长度(不包含'\0')并作为函数值返回
b=strlen("abc\nde\t027"); b值为10头文件为<ctype.h>
isalpha是一种函数:判断字符ch是否为英文字母,若为英文字母,返回非0(小写字母为2,大写字母为1)。若不是字母,返回0
isupper()大写字母,当参数c为大写英文字母(A-Z)时,返回非零值,否则返回零
islower()小写字母,若参数c为小写英文字母,则返回非零值,否则返回NULL(0)
isdigit()数字字符,主要用于检查其参数是否为十进制数字字符,若参数c为阿拉伯数字0~9,则返回非0值,否则返回0
strncpy(p,p1,n)复制p1字符串n个长度到p
strncat(p,p1,n)附加p1字符串n个长度到p
strcasecmp()忽略大小写比较字符串
strncmp(p,p1,n)比较前n个字符串
strchr(p,c)在字符串中查找指定字符,在参数str所指向的字符串中搜索第一次出现字符c(一个无符号字符)的位置
strrchr(p,c)反向查找,查找字符在指定字符串中从右面开始的第一次出现的位置,如果成功,返回该字符以及其后面的字符,如果失败,则返回 NULL
strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串
字符串程序举例
【例4.9】输入两个字符串,按照从小到大的顺序将其连接并保存,然后输出连接字符串。要求:不改变原始字符串的内容;用字符串处理函数完成所有功能。
image-20220512193608080 【例4.10】已知字符串s,要求从s中指定位置截取指定长度的子串放入字符串t。所谓子串,指的是在某字符串中截取若干个连续字符组成的字符串,原始串称为主串或母串。
image-20220512193632551
第5章 指针
指针的数据类型
定义 | 含义 |
|---|---|
int i | 定义整型变量 i |
int *p | p 为指向整型数据的指针变量 |
int a[n] | 定义整型数组 a ,它有n个元素,数组名a为该数组的首地址 |
int *p[n] | 定义指针数组 p ,它由n个指向整型数据的指针元素组成 |
int (*p)[n] | p 为指向含n个元素的一维数组的指针变量 |
int **p | p 是一个指针变量,它指向一个指向整型数据的指针变量 |
在循环语句中,要注意在适当位置使指针重新指向首地址,以免下标越界
利用指针变量输入数据时不能加“&”,因为接受数据的参数变量必须是一个地址,由于指针变量本身就是一个地址,所以不能再加地址运算符“&”
void指针

指针变量的定义与应用
- 变量的地址和指针的概念
变量一经定义,编译系统会根据变量的类型,为其分配一定字节数的内存存储单元
内存区的每一个字节有一个编号,称为“地址”
程序经过编译后已经将变量名转换为变量的地址,对变量值得存取都是通过地址进行的
直接按变量地址存取变量值的方式称为“直接访问”方式
“间接访问”将变量的地址存放在另一个变量中
可以定义一种特殊的变量,它是专门用来存放地址的
“&”称为取地址运算符,其使用格式为 &变量名
一个变量的地址称为该变量的“指针”
“指向”是通过地址来体现的
a_pointer = &a;- 指针变量的定义
指针变量:专门存放指针的变量,必须将其定义为“指针类型”,其值为一个存储单元的地址
定义指针变量的一般格式为:
类型名 *指针变量名1,*指针变量名2,…;说明:
(1)“*”为指针定义符
(2)定义指针变量时必须指定其基类型
一个指针变量只有指向与其基类型相同的变量时,才能得到正确的结果
指针变量的赋值
【1】通过取地址运算(&)获取地址值
int a=40; int *a_p; a_p=&a; __________ int a=40; int *a_p=&a; 注意: 指针变量只能被赋予地址(指针),决不能赋予任何其他非地址类型数据(空指针情况除外)【2】利用另一个指针变量赋值
可把一个已经赋过值的指针变量的值赋给另一个指针变量,使这两个指针变量指向同一个变量 int k=1,*p,*q; q=&k; p=q; 注意: 当进行赋值运算时,赋值号两边指针变量的基类型必须相同【3】空指针
空指针是指对指针变量赋于0值而得到的 int *p; p=NULL; 其中p为空指针,NULL为符号常量,其值为0 注意: 指针变量未赋值时,不能使用; 指针变量赋0值后,可使用,只是不指向具体的变量【4】通过标准函数获得地址值(头文件stdlib.h或calloc.h)
通过调用malloc和calloc函数可以在内存中开辟动态存储单元并返回存储单元的起始地址

image-20220512194125125 - 对指针变量的操作
【1】间接访问运算符(“*”)
也称指针运算符或取内容运算符
(1)&i 的含义:按自右向左方向结合,等价于“(&i)”,即变量i (2)&pi 的含义:等价于“&(pi)”,即变量i的地址
【2】通过指针引用存储单元
(1)改变指针变量指向的变量的值时:指向关系不变,指向的对象的值改变
*p2=*p1;(2)改变指针变量的地址值:指向关系改变
p2=p1;(3)用“*指针变量”替换某变量
当px出现在赋值号左边时,代表的是指针所指向的存储单元(即变量x) 当px出现在赋值号右边时,代表的是指针所指向的存储单元的值(即变量的值100)
指针和一维数组
- 数组元素的指针
int a[10];
int p;
p=&a[0]; 等价于 p=a;
int a[10],*p=a;
数组名代表数组的首地址- 通过指针引用数组元素
指针变量p已指向数组中的某个元素,则p+1指向数组中的下一个元素。p+1所代表的实际地址是p+1*d,
其中d是数组元素对应类型的变量在内存中所占的字节数
引入指针变量后,可用三种方法访问数组元素
【1】通过数组的首地址引用数组元素
for(i=0;i<10;i++) printf("%4d",*(a+i));相当于
for(i=0;i<10;i++) printf("%4d",a[i]);【2】通过指针变量引用数组元素
for(p=a,i=0;i<10;i++) printf("%4d",*(p+i));此时并没有移动指针p移动指针
for(p=a;i=0;i<10;i++) {printf("%4d",*p);p++;}【3】用带下标的指针变量引用一维数组元素
方括号“[]”是一种变址运算符,运算过程:计算a+i,然后找出该地址单元中的值。当p指向a数组的首元素时,p[i]同a[i]等价,均表示数组a中下标为i的元素
综上所述,若p指向数组a的首元素,则数组元素a[i]的表达式有下面的几种等价方式:
a[i] | *(a+i) | *(p+i) | p[i]注意:
p[i]能表示数组元素a[i]的前提条件是p指向数组a的首元素,如当p指向元素a[3]时,p[2]就是a[5]


编程时需注意指针变量运算后的结果。若使p指向数组a的首元素(即p=a),则 (1)p++(或p+=1),使p指向下一个元素,即a[1]。若再执行*p,则得到元素a[1]的值
(2)*p++等价于*(p++),先得到p指向的变量的值(即*p),然后再使指针指向下一个元素,即p+1的值赋给p
(3)*(++p),先使p加1,使p指向下一个元素,再取*p 若p初值为a(即&a[0]),则输出*(p++)时,得到a[0]的值,而输出*(++p)时,得到a[1]的值
(4)(*p)++表示p所指向的元素值加1,即(a[0])++
(5)(p--)相当于a[i--],先对p进行“*”运算,再使p自减1*(++p)相当于a[++i],先使p自加1,再作运算 *(--p)相当于a[--i],先使p自减1,再作*运算
指针和二维数组
- 二维数组与一维数组的关系
对于二维数组a[3][4],a 的基类型是一维数组(含有4个整型元素的数组类型)
可将二维数组名 a 理解为指向行的指针(行指针),它指向一整行元素而非个别元素
a或a+0代表第0行的首地址,即&a[0];a+1代表第一行的首地址,即&a[1]

对第0行首地址与第一行首地址a与a+1,地址之差为一行的4个元素占得字节数16,此时加1后指针移动一行,共跳过4个数组元素
各行对应的一维数组名a[0]、a[2]、a[3]可看作指向列的指针(列指针),基类型为数组的基类型,即整型

- 二维数组元素地址的表示方法
若有如下定义:
int a[3][4],i,j;
则元素a[i][j](0<=i<3,0<=j<4)的地址有五种表示方法求得:
a数组元素a[i][j]有五种表示方法引用
- 指向二维数组元素的指针变量

- 指向二维数组行的指针变量(数组指针)
可定义指针变量,让其指向二维数组行,定义形式:类型说明符 (*指针变量名)[长度]; “类型说明符”:所指数组的数据类型 “*”:其后的变量时指针类型 “长度”:二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数

若对p赋值:p=a,表示p指向二维数组a的第0行,而p+i则指向数组的第i行
(p)[4]指向含有4个元素的一维数组,即p有4个元素;
对于二维数组来说,相当于行指针
指向二维数组行的指针变量是单个的变量




- 指针数组
当一个数组中的所有元素均为指针类型时,称该数组为指针数组
定义格式:类型说明符 *数组名[数组长度];
一队“[]”的优先级高于*号
指针数组类型表示多个指针(一组有序指针)



指针与字符串
使用指针指向字符串
单个字符串的处理


同时处理多个字符串

字符指针数组中的各个指针所指向的字符串是长度不规则的; 若使用二维数组时,每列下标应相同;
字符指针数组的列下标应定为n+1(加1为预留字符串结束标识符的空间),故字符指针数组浪费了很大的存储空间

- 指向指针的指针变量
定义一个指向的指针变量的格式如下:类型说明符 **指针变量名

指针数组的元素只能存放地址
#include<stdio.h>
int main()
{
char *cbooks[]={
"<c程序设计语言>",
"<c专家编程>",
"<c和指针>",
"<c Primer Plus>",
"<带你学c带你飞>"
};
char **byfishc;
char **jiayuloves[4];
byfishc=&cbooks[0];
jiayuloves[0]=&cbooks[0];
jiayuloves[1]=&cbooks[1];
jiayuloves[2]=&cbooks[2];
jiayuloves[3]=&cbooks[3];
printf("fishc出版的书:%s\n",*byfishc);
printf("小甲鱼喜欢的图书有:\n");
int i;
for(i=0;i<4;i++)
{
printf("%s\n",*jiayuloves[i]);
}
return 0;
}
const与指针
指向常量的指针
(1)指针可以修改为指向不同的常量
(2)指针可以修改为指向不同的变量
(3)可以修改解引用来读取指针指向的数据
(4)不可以通过解引用修改指针指向的数据
#include<stdio.h>
int main()
{
int num=520;
const int cnum=880;
const int *pc=&cnum;
printf("cnum:%d,&cnum:%p\n",cnum,&cnum);
printf("*pc:%d,pc:%p\n",*pc,pc);
printf("\n");
pc=#
printf("num:%d,&num:%p\n",num,&num);
printf("*pc:%d,pc:%p\n",*pc,pc);
printf("\n");
num=1024;
printf("*pc:%d,pc:%p\n",*pc,pc);
}
指向非常量的指针
(1)指针自身不可以被修改
(2)指针指向的值可以被修改
#include<stdio.h>
int main()
{
int num=520;
const int cnum=880;
int * const p=#
*p=1024;
printf("*p:%d\n",*p);
printf("num:%d\n",num);
}
指向常量的常量指针
(1)指针自身不可以被修改 (2)指针指向的值也不可以被修改
#include<stdio.h>
int main()
{
int num=520;
const int cnum=880;
const int * const p=&cnum;
printf("*p:%d\n",*p);
}
指向“指向常量的常量指针”的指针
#include<stdio.h>
int main()
{
int num=520;
const int cnum=880;
const int * const p=#
const int * const *pp=&p;
printf("**pp:%p,&p:%p\n",pp,&p);
printf("*pp:%p,p:%p,&num:%p\n",*pp,p,&num);
printf("**pp:%d,*p:%d,num:%d\n",**pp,*p,num);
}
指针总结
内存以字节开始编号 1 Byte = 8 Bits
赋初值 int a,*pa=&a; <--声明
-------------------------------
*pa --内容 --> int *pa 占4个字节
--> int a 占4个字节
两个地址不同
32位 指针为 4个字节
64位 8个字节
------------------------------
指针运算(地址的运算)
加减 ---> 移动指针 p(x)+sizeof*n
指针相减 --> 相隔数据的个数
----------------------------------------------
*p,p++ <=> *p++ <=> *(p++)
----------------------------------
指针与数组
数组名为数组起始地址
p=a;
a[3] *(p+3) *(a+3) p[i]
常量 变量
n=sizeof(a)/sizeof(int)
-------------------------------------------
指针与二维数组
p=&a[0][0]
a[0] a[1] a[2] --->一维数组名
数组名+1 移动一行元素
二维数组名 --> 行地址 *(a+1) <=> a[1]
(a+1)
行指针
int (*p)[3]
p=a; a[1][1] <==> p[1][1] p+i
*(*(a+i)+j)
*(*(p+i)+j)
-------------------------------------------
字符指针
char str[]="...";
char *p=str;
char *p="...";字符串常量,不可修改!
指针数组
int *p[3] []高于*
int *p={a[0],a[1]};
*(p[i]+j); *(*(p+i)+j)
*(a[i]+j);
p[0]=b[0]; a[0][1]
p[1]=b[1]; *(p[0]+1)
*(a[0]+1)
----------------------------------------
多级指针
int **p;
a[0] *p[0] **p
**(p+1) a[1] *p[1]
------------------------
void指针
void *p
使用时,*(int *)p
*((int *)p+i)
-------------------------
const变量
const int *p
修饰后不可变第六章 其他数据类型
结构体
结构体类型是用户自定义的新数据类型
结构体类型的声明
结构体类型声明的一般形式为

说明:
(1)struct是声明结构体类型时所必须使用的关键字,不能省略
(2)结构体类型名由用户自己定义,命名规则与变量名的命名规则相同
(3)结构体声明以分号结束
(4){}中包围的是组成该结构体的成员;对各成员都应进行类型声明,即 类型名 成员名;
每一个成员也称为结构体中的一个域
每个成员的数据类型既可以是(整型、字符型、浮点型等),也可以是(数组类型、结构体类型)
结构体中的成员可以单独使用,作用和地位相当于普通变量
(5)在函数内部声明只能在函数内部使用,在外部声明可被多个函数使用
结构体变量
- 结构体类型变量的定义

- 结构体变量的引用

- 结构体变量的初始化
结构体变量={初值表};注意:初值的数据类型必须和结构体变量中相应成员类型一致

- 结构体变量占用内存的大小
使用sizeof运算符,作用:求出变量或类型说明符所占用的内存空间的字节数
sizeof (变量或类型说明符);结构体数组
- 定义结构体数组

数组各元素在内存中连续存放,结构体数组所占内存大小为结构体类型的大小乘以数组元素的数量
- 结构体数组的初始化
结构体数组中的每个元素都是结构体
初始化格式:结构体数组[n]={{初值表1},{初值表2},...,{初值表n}};
结构体程序应用举例
【例6.3】简单的密码加密程序
加密过程是先定义一张字母加密对照表,将需要加密的一行文字输入加密过程,程序根据加密表中的对照关系,可以很简单地将输入的文字加密输出,对于表中未出现的字符则不加密,原样输出输入 a e b i c k d ; 输出 d i w a k b ; c #include<stdio.h> struct table { char input,output; }; struct table translate[]={{'a','d'},{'b','w'},{'c','k'},{'d',';'},{'e','i'},{'i','a'},{'k','b'},{';','c'}}; int main() { char ch; int str_long,i; str_long=sizeof(translate)/sizeof(struct table); printf("输入加密字段:"); while((ch=getchar())!='\n') { for(i=0;translate[i].input!=ch && i<str_long;i++); if(i<str_long) { putchar(translate[i].output); } else { putchar(ch); } } printf("\n"); }
image-20220512201105513
结构体与指针
- 指向结构体变量的指针

程序主要输出结构体变量student1的各个成员的值,采用三种方法:
| 结构体变量.成员名; | 具体为:student1.num |
|---|---|
| (*结构体指针变量名).成员名 | 具体为:(*p).num |
| 结构体指针变量名->成员名; | 具体为:p->num |
- 指向结构体数组的指针

说明:
(1)如果p的初值为stu,即指向第一个元素stu[0]
(2)当定义p是一个指向struct student类型数据的指针变量时,其应该用来指向一个struct student类型的变量,不能用来指向stu数组元素的某一成员
p=stu[1].name 此用法错误如需要将某一成员的地址赋给p,只可使用强制类型转换
p=(struct student *)stu[0].num;用指针处理静态链表简介
链表:通过指针(链)将一组节点连接在一起,链表是用指针变量将非连续的数据块连成一个整体的数据结构
解决: (1)数组中的数据需要连续的数据块存放的弊端 (2)内存分配零乱的问题
链表有一个“头指针”变量,存放一个地址,指向一个元素
节点:链表中的每一个元素
节点包括两部分:信息部分和链接节点的指针
链尾:最后一个元素不再指向其他元素,地址部分放入“NULL”,表示链表到此结束
必须提供头指针才能访问链表
建立链表的方法:
(1)用结构体变量表示a,b,c三个结点

(2)声明一个指向该种类型的结构体指针作为表头

(3)每一个节点中包含同种类型的指针变量用以存放下一个节点的地址

静态链表: a , b , c 先定义,在内存中开辟固定的且不一定连续的存储单元,不是临时开辟,不能用完释放
单链表
头插法
(1)判断链表是否有结点
(2)修改head指向的结点地址(使用&head)
(3)新结点的next指针指向插入点的后一个结点#include<stdio.h>
#include<stdlib.h>
struct Book
{
char title[128];
char author[40];
struct Book *next;
};
void getinput(struct Book *book)
{
printf("输入书名:");
scanf("%s",book->title);
printf("输入作者:");
scanf("%s",book->author);
}
void addBook(struct Book **library)//接收头指针的地址
{
struct Book *book,*temp; //*temp用来存放第一个节点的地址
book=(struct Book *)malloc(sizeof(struct Book)); //申请新结点
if(book==NULL)
{
printf("内存分配失败!\n");
exit(1);
}
getinput(book);
if(*library!=NULL)
{
temp=*library;
*library=book;
book->next=temp;
}
else
{
*library=book;
book->next=NULL;
}
}
void printlibrary(struct Book *library)
{
struct Book *book;
int count=0;
book=library;
while(book!=NULL)
{
printf("books%d:\n",count);
printf("书名:%s\n",book->title);
printf("作者:%s\n",book->author);
book=book->next;
count++;
}
}
void releaselibrary(struct Book *library)
{
while(library!=NULL)
{
library=library->next;
free(library);
}
}
int main()
{
struct Book *library=NULL; //头指针
char ch;
while(1)
{
printf("是否录入[y/n]?:");
do
{
ch =getchar();
}while(ch!='y'&& ch !='n');
if(ch=='y')
{
addBook(&library); //修改头指针指向的地址
}
else
{
break;
}
}
printf("是否打印[y/n]?:");
do
{
ch =getchar();
}while(ch!='y'&& ch !='n');
if(ch=='y')
{
printlibrary(library);
}
return 0;
}
尾插法
(1)修改最后一个结点的尾指针的指向,使其指向新结点
(2)新结点的尾指针指向NULL#include<stdio.h>
#include<stdlib.h>
struct Book
{
char title[128];
char author[40];
struct Book *next;
};
void getinput(struct Book *book)
{
printf("输入书名:");
scanf("%s",book->title);
printf("输入作者:");
scanf("%s",book->author);
}
void addBook(struct Book **library)//接收头指针的地址
{
struct Book *book,*temp; //*temp用来存放第一个节点的地址
book=(struct Book *)malloc(sizeof(struct Book)); //申请新结点
if(book==NULL)
{
printf("内存分配失败!\n");
exit(1);
}
getinput(book);
if(*library!=NULL)
{
temp=*library;
//定位链表尾部位置
while(temp->next!=NULL)
{
temp=temp->next;
}
//插入数据
temp->next=book;
book->next=NULL;
}
else
{
*library=book;
book->next=NULL;
}
}
void printlibrary(struct Book *library)
{
struct Book *book;
int count=0;
book=library;
while(book!=NULL)
{
printf("books%d:\n",count);
printf("书名:%s\n",book->title);
printf("作者:%s\n",book->author);
book=book->next;
count++;
}
}
void releaselibrary(struct Book **library)
{
struct Book *temp;
while(*library!=NULL)
{
temp=*library;
*library=(*library)->next;
free(temp);
}
}
int main()
{
struct Book *library=NULL; //头指针
char ch;
while(1)
{
printf("是否录入[y/n]?:");
do
{
ch =getchar();
}while(ch!='y'&& ch !='n');
if(ch=='y')
{
addBook(&library); //修改头指针指向的地址
}
else
{
break;
}
}
printf("是否打印[y/n]?:");
do
{
ch =getchar();
}while(ch!='y'&& ch !='n');
if(ch=='y')
{
printlibrary(library);
}
return 0;
}
void addBook(struct Book **library)//接收头指针的地址
{
static struct Book *tail; //记录链表尾部位置
struct Book *book,*temp; //*temp用来存放第一个节点的地址
book=(struct Book *)malloc(sizeof(struct Book)); //申请新结点
if(book==NULL)
{
printf("内存分配失败!\n");
exit(1);
}
getinput(book);
if(*library!=NULL) //不需要每次插入遍历单链表
{
tail->next=book;
book->next=NULL;
}
else
{
*library=book;
book->next=NULL;
}
tail=book;
}搜索单链表
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct Book
{
char title[128];
char author[40];
struct Book *next;
};
void getinput(struct Book *book);
void addBook(struct Book **library);
void printlibrary(struct Book *library);
void releaselibrary(struct Book **library);
struct Book *searchBook(struct Book *library,char *target);
void printBook(struct Book *book);
void getinput(struct Book *book)
{
printf("输入书名:");
scanf("%s",book->title);
printf("输入作者:");
scanf("%s",book->author);
}
void addBook(struct Book **library)//接收头指针的地址
{
static struct Book *tail; //记录链表尾部位置
struct Book *book,*temp; //*temp用来存放第一个节点的地址
book=(struct Book *)malloc(sizeof(struct Book)); //申请新结点
if(book==NULL)
{
printf("内存分配失败!\n");
exit(1);
}
getinput(book);
if(*library!=NULL) //不需要每次插入遍历单链表
{
tail->next=book;
book->next=NULL;
}
else
{
*library=book;
book->next=NULL;
}
tail=book;
}
void printlibrary(struct Book *library)
{
struct Book *book;
int count=0;
book=library;
while(book!=NULL)
{
printf("books%d:\n",count);
printf("书名:%s\n",book->title);
printf("作者:%s\n",book->author);
book=book->next;
count++;
}
}
void releaselibrary(struct Book **library)
{
struct Book *temp;
while(*library!=NULL)
{
temp=*library;
*library=(*library)->next;
free(temp);
}
}
struct Book *searchBook(struct Book *library,char *target)
{
struct Book *book;
book=library;
while(book!=NULL)
{
if(!strcmp(book->title,target) || !strcmp(book->author,target))
{
break;
}
book=book->next;
}
return book;
}
void printBook(struct Book *book)
{
printf("书名:%s\n",book->title);
printf("作者:%s\n",book->author);
}
int main()
{
struct Book *library=NULL; //头指针
char ch;
while(1)
{
printf("是否录入[y/n]?:");
do
{
ch =getchar();
}while(ch!='y'&& ch !='n');
if(ch=='y')
{
addBook(&library); //修改头指针指向的地址
}
else
{
break;
}
}
printf("是否打印[y/n]?:");
do
{
ch =getchar();
}while(ch!='y'&& ch !='n');
if(ch=='y')
{
printlibrary(library);
}
char input[128];
struct Book *book;
printf("输入要搜索的书名或作者:");
scanf("%s",input);
book=searchBook(library,input);
if(book==NULL)
printf("no find\n");
else
{
do
{
printf("已找到---\n");
printBook(book);
}while((book=searchBook(book->next,input))!=NULL);
}
releaselibrary(&library);
return 0;
}单链表插入
向单链表插入元素,从小到大排序
#include<stdio.h>
#include<stdlib.h>
struct Node
{
int value;
struct Node *next;
};
void insertNode(struct Node **head,int value)
{
struct Node *previous;
struct Node *current;
struct Node *new;
current =*head;
previous=NULL;
while(current!=NULL && current->value <value)
{
previous=current;
current=current->next;
}
new=(struct Node *)malloc(sizeof(struct Node));
if(new==NULL)
{
printf("内存分配失败\n");
exit(1);
}
new->value=value;
new->next=current;
if(previous==NULL)
{
*head=new;
}
else
{
previous->next=new;
}
}
void printNode(struct Node *head)
{
struct Node *current;
current=head;
while(current!=NULL)
{
printf("%d ",current->value);
current=current->next;
}
}
int main()
{
struct Node *head=NULL;
int input;
while(1)
{
printf("\n输入一个整数(-1结束):");
scanf("%d",&input);
if(input==-1)
{
break;
}
insertNode(&head,input);
printNode(head);
}
}
建立动态链表
#include<stdlib.h>
malloc(size);
struct student *p = (struct student *)malloc(sizeof(struct student));//申请存放结构体数据的内存空间,并让p指向该空间
free(p);//释放malloc()分配的内存链表操作(增,删,改,查)
#include<stdio.h>
#include<stdlib.h>
struct Node
{
int sum;
struct Node *next;
};
int main()
{
struct Node *head,*p,*q;
p=(struct Node *)malloc(sizeof(struct Node));
p->sum=10;
head=p;
q=(struct Node *)malloc(sizeof(struct Node));
q->sum=20;
q->next=NULL;
p->next=q;
while(head!=NULL)
{
printf("%d\t-\n",head->sum);
head=head->next;
}
return 0;
}先连后断
建立单项链表的操作步骤:
- 读取数据
- 生成新结点
- 将数据存入结点的成员变量中
- 将新结点插入到链表中
重复上述操作
【例9.1】写一个函数,建立3名学生分数的单向动态链表,每一个学生的数据块(结点)定义如下:
struct node
{
float score;
struct node *next;
};
#include<stdio.h>
#include<malloc.h>
//#define NULL 0
#define LEN sizeof(struct node)
struct node
{
float score;
struct node *next;
};
int n;
struct node *creat()
{
struct node *head,*p1,*p2;
n=0; //n为链表中结点个数,初值为0
p1=p2=(struct node *)malloc(LEN); //生成新结点
printf("输入结点%d数据(实型)(-1终止):",n);
scanf("%f",&p1->score);
head=NULL; //链表中无结点,即链表为空时,head的值
while(p1->score!=-1) //没有读到数据结束标志时进入循环
{
n++;
if(n==1) //若输入的是第一个节点数据,p1所指向的新开辟的结点成为链表中第一个结点
{
head=p1;
}
else
{
p2->next=p1; //新结点链接到链尾
}
p2=p1; //p2指向当前表尾
p1=(struct node *)malloc(LEN); //开辟新的结点空间
printf("输入结点%d数据(实型)(-1终止):",n); //读入数据
scanf("%f",&p1->score);
}
p2->next=NULL; //置链表结束标志
return(head); //返回表头指针
}
int main(void)
{
struct node *head;
head=creat();
}输出链表
【例9.2】写一个函数,输出上面建立的单向动态链表的结点数据
void print(struct node *p)
{
while(p!=NULL)
{
printf("%.1f\t\n",p->score);
p=p->next;
}
}
主函数调用-->print(head);
删除一个结点
找到要删除结点 p1 的前趋结点 p2,然后将此前趋结点的指针域指向删除节点的后续结点
p2->next=p1->next;
【例9.3】写一个函数,删除动态链表中指定的结点
struct node *del(struct node *head,float score)
{
struct node *p1,*p2;
if(head==NULL)
{
printf("\n此链表为空\n"); //若为空表
return(head);
}
p1=head; //从头结点开始
while(score!=p1->score && p1->next!=NULL) //p1所指向结点不是要找的结点,其后面还有结点
{
p2=p1; //p2指向刚才检查过的结点
p1=p1->next;
} //p1后移一个结点
if(score==p1->score) //找到删除结点
{
if(p1==head)
{
head=p1->next; //若p1指向首地址,把第二个结点地址赋给head
}
else
{
p2->next=p1->next; //将结点的下一个结点的地址,赋给前一个结点的地址
}
free(p1);
printf("delete:%.1f\n",score);
n--; //结点数减1
}
else
{
printf("%.lf未找到\n",score);
}
return(head);
}
主函数调用
float score;
printf("输入要删除结点的score:");
scanf("%f",&score);
del(head,score);
printf("删除后的链表信息-->");
print(head);
插入一个结点
首先确定插入的位置,插入点在指针p1所值的结点之前-->"前插",插入点再指针p1所指的结点之后-->"后插"
【例9.4】在分数值为score的结点前,插入分数值为sco的结点。若链表非空,分数值为score的结点存在,新结点插在该结点之前;分数值为score的结点不存在,则插在表尾。若链表为空,则新结点插在表头,作为第一个结点
struct node *insert(struct node *head,float score,float sco)
{
struct node *p,*p1,*p2;
p=(struct node *)malloc(LEN); //生成新结点
p->score=sco; //新结点中存入sco值
p1=head; //使p1指向第一个结点
if(head==NULL) //若原来是空表
{
head=p;
p->next=NULL; //使p指向的结点作为头结点
}
else
{
while((p1!=NULL) && (p1->score!=score)) //表非空且未到表尾,查找score的位置
{
p2=p1; //p2指向p1的前驱结点
p1=p1->next; //p1后移一个结点
}
p->next=p1;
p2->next=p; //score的位置存在,插在该结点之前,score的位置不存在,插在表尾
}
n++;
return(head);
}
主函数调用-->
float sert,sco;
printf("输入分数值score值:");
scanf("%f",&sert);
printf("输入插入结点sro的信息:");
scanf("%f",&sco);
insert(head,sert,sco);
printf("修改后的链表-->");
print(head);
清空链表和销毁链表
销毁:释放包括头的所有节点;链表不能再使用
清空:保留头,后面的结点都释放(head = NULL);链表还可以继续使用#include<stdio.h>
#include<stdlib.h>
struct age
{
int ages;
struct age *next; //链接结点的指针
};
struct age *addnode();
int delnode(struct age *head);
int cleanernode(struct age *head);
int main()
{
struct age *head,*p;
struct age *a,*b,*c;
a=addnode();
b=addnode();
c=addnode();
a->ages=21;
b->ages=20;
c->ages=17;
head=a;
a->next=b;
b->next=c;
c->next=NULL;
int i=1;
printf("输出节点信息---\n");
while(head!=NULL)
{
printf("-结点%d->%d\n",i,head->ages);
i++;
head=head->next;
}
// delnode(head); //销毁
cleanernode(head); //清空
}
struct age *addnode()
{
struct age *r;
r=(struct age *)malloc(sizeof(struct age));
return r;
}
int delnode(struct age *head)//销毁链表
{
struct age *del;
if(head==NULL)
{
return 0;
}
while(head)
{
del=head->next;
free(head);
head=del;
}
return 1;
}
int cleanernode(struct age *head)//清空链表
{
struct age *temp,*del;
if(head==NULL)
{
return 0;
}
del=head->next;
while(del)
{
temp=del->next;
free(del);
del=temp;
}
head->next=NULL;
return 1;
}共用体
共用体(联合):是几个不同类型的变量共占用一段内存的结构(对同一段内存单元的数据按不同类型来处理)
- 共用体类型变量的定义

共用体类型的变量占用内存空间的大小等于成员分量中最长的分量所占用内存的长度
- 共用体变量的引用方式
先定义,后使用
注意:只能引用共用体变量中的成员,不能整体引用共用体变量

- 共用体类型数据的特点
(1)系统采用覆盖技术,共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用
(2)共用体变量的地址和它的各成员的地址都是同一地址
(3)不能引用变量名来得到一个值,不能对共用体变量名赋值,不能在定义共用体变量时对它整体初始化
(4)共用体类型的变量可作为函数实参进行传递
(5)共用体类型可出现在结构体类型中,结构体类型也可存在于共用体类型中 【例6.7】分析程序运行结果
#include<stdio.h>
union node
{
long i;
int k;
char ii;
char s[4];
}mix;
int main()
{
mix.i=0x12345678;
printf("mix.i=%lx\n",mix.i);
printf("mix.k=%x\n",mix.k);
printf("mix.ii=%x\n",mix.ii);
printf("mix.s[0]=%x\tmix.s[1]=%x\tmix.s[2]=%x\tmix.s[3]=%x\n",mix.s[0],mix.s[1],mix.s[2],mix.s[3]);
}
枚举类型
枚举:将变量的值一一列举出来,只有几个可能的值时可定义枚举类型
枚举类型的定义形式:

image-20220512202429962 枚举变量的定义
(1)间接定义

(2)直接定义

枚举变量中colour1、colour2的值只可能是Red、Blue、Yellow、White中的一个
说明:
(1)对枚举变量按常量处理,故称为枚举变量;编译时按定义时的顺序置为0,1,2...
(2)枚举变量是常量,不能赋值,只能在定义时由程序指定

typedef定义数据类型
别名

说明:
(1)typedef不能定义变量,但可以声明各种类型名
(2)typedef没有创造新的类型,只是对已经存在的类型增加一个别名
位运算
提供将标志状态从标志字节中分离出来的位运算功能
- 位运算符和位运算
| 运算符 | 含义 | 优先级(从高到低) |
|---|---|---|
| ~ | 取反 | 1 |
| << | 左移 | 2 |
| >> | 右移 | 3 |
| & | 按位与 | 4 |
| ^ | 按位异或 | 5 |
| | | 按位或 | 6 |
说明:
(1)运算量只能是整型或字符型的数据,不能为实型数据
(2)除“~”外,都是双目运算符
(3)各双目运算符与赋值运算符可以组成扩展的赋值运算符
<< = > >> = &= ^= |=
(4)a<<2或a&0x00ff不能改变a的值
- 按位与(&)
格式:x&y
规则:参与运算的两数对应的二进制位相与,只有对应的两个二进制位均为1时,结果位才为1,否则为0
参与运算的数据都以补码方式出现


- 按位或(|)
格式:x|y
对应位置的值均为0时才为0,否则为1

- 异或(^)【XOR运算符】
格式:x^y
规则:参加运算的两个运算数中相对应的二进制位上两数相同,则该位的结果为0(假);若两数不同,则该位的结果为1(真)

- 取反(~)
唯一的一个单目运算符
格式:~x
对二进制数按位取反
- 左移(<<)
运算符左边是移位对象,右边是整型表达式,表示左移的位数
左移时,右端补0,高位左移时溢出,舍弃

- 右移(>>)

- 不同长度的数据进行位运算时的规则
(1)先将两个运算数按右端对齐
(2)再将运算数短的一个运算数往高位扩充,即无符号数和正整数左侧补0;负数左侧补1
(3)最后按位进行位运算
第七章 函数
函数概述
说明:
1. 一个c语言程序是由一个或多个程序模块组成,即由若干个源文件组成,方便编译编写
2. c语言程序的执行总是由main函数开始至结束
3. 函数可相互调用,mian函数只能由系统调用,其他函数不能调用
4. 从用户来说:可分为标准函数(库函数,系统提供)和自定义函数
5. 从函数的形式:无参函数和有参函数(主调函数调用被调函数,通过参数向被调函数传递数据)函数的定义
自定义函数的定义形式:


函数的一般调用

函数参数的传递方式
形参和实参
形参出现在函数定义中,实参出现在主调函数中
特点:
(1)形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元
只在函数内部有效,可与其他函数中的变量同名
(2)实参在进行函数调用时,必须具有确定的值,以便传给形参
(3)实参和形参在数量、类型和顺序上应严格一致
(4)“值传递”函数调用中发生的数据传递是单向的,只能把实参的值传送给形参,在函数调用的过程中,形参的值发生改变时,不会改变实参的值
实现交换两个变量值的功能:指针传递
数组元素作为实参
【1】数组元素(下标变量)作为实参使用
【2】数组名作为函数的形参和实参使用

数组名作函数参数
用数组名作为函数参数,形参和相对应的实参都必须是类型相同的数组或指针变量
数组名作函数参数时所进行的传送是地址的传送
能够接受并存放地址值的只能是指针变量,c语言编译系统都是将形参数组名作为指针变量来处理的

函数的形参为数组名时,数组名后的“[]”不能省略


函数的嵌套与递归调用
函数的嵌套调用:在一个函数体内又出现了对另一个函数的调用
递归调用:被调函数调用函数自身
函数不允许嵌套定义,但可嵌套调用



函数与指针(主函数传参)
int *p(int); | 返回 指针的函数 |
|---|---|
int (*p)(int); | 指向 函数的指针,函数返回值为int型 变量 |
int *(*p)(int); | 指向 函数的指针,函数返回值为int型 指针 |
int (*p[3])(int) | 函数指针数组,函数返回值为int型 变量 |
int *(*p[3])(int) | 函数指针数组,函数返回值为int型 指针 |

数组逆序
#include<stdio.h>
void inv(int *x,int n)
{
int *p,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;
}
}
int main()
{
int i,a[10]={3,7,9,11,0,6,7,5,4,2};
printf("Before\n");
for(i=0;i<10;i++)
{
printf("%5d",a[i]);
}
inv(a,10);
printf("\nAfter\n");
for(i=0;i<10;i++)
{
printf("%5d",a[i]);
}
printf("\n");
}
分别求数组中奇数元素与偶数元素的和
#include<stdio.h>
void arr_sum(int *p,int n,int s[])
{
int i;
for(i=0;i<n;i++)
{
if(p[i]%2==0)
{
s[0]=s[0]+p[i];
}
else
{
s[1]=s[1]+p[i];
}
}
}
int main()
{
int i,a[10]={3,7,9,11,0,6,7,5,4,2},b[2]={0};
for(i=0;i<10;i++)
{
printf("%5d",a[i]);
}
printf("\n");
arr_sum(a,10,b);
printf("偶数和为%5d,奇数和为%5d\n",b[0],b[1]);
}
要使用被调函数向主调函数返回多个值,可通过数组名作形参来实现 说明: (1)通过函数名只能向主调函数返回一个值,多值返回只能使用数组名或指针作形参实现
(2)地址调用与传值调用的最大区别在于形参的改变会影响实参
编写程序,通过函数给数组输入若干个大于0的整数,用负数作为输入结束标志,并调用函数输出数组中的元素
#include<stdio.h>
#define M 100
void arrout(int *,int );
int arrin(int *);
int main()
{
int s[M],k;
k=arrin(s);
arrout(s,k);
}
arrin(int *a) //s[M]的首地址
{
int i=0,x;
printf("请输入大于0的整数,负数作为输入结束的标志:\n");
scanf("%d",&x);
while(x>=0)
{
*(a+i)=x; //指针
i++;
scanf("%d",&x);
}
return i; //数组元素个数
}
void arrout(int *a,int n)
{
int i;
printf("输入的整数数列为:");
for(i=0;i<n;i++)
{
printf("%d,",*(a+i));
}
printf("\n");
}
实参为指针变量,形参为数组名
从10个数中找出其中最大值和最小值
#include<stdio.h>
void max_min_value(int array[],int n,int m[2])
{
int *p,*array_end;
array_end=array+n;
m[0]=m[1]=*array;
for(p=array+1;p<array_end;p++)
{
if(*p>m[0])
{
m[0]=*p;
}
else if(*p<m[1])
{
m[1]=*p;
}
}
}
int main()
{
int i,number[10],*pn,m[2];
pn=number;
printf("输入10个数:\n");
for(i=0;i<10;i++)
{
scanf("%d",&number[i]);
}
max_min_value(pn,10,m);
printf("max=%d,min=%d\n",m[0],m[1]);
}
用选择法对10个整数按降序排序
所谓选择法就是先将10个数中最小的数与a[0]对换,再将a[1]到a[9]中最小的数与a[1]对换,......,,每比较一轮,找出一个未经排序的数中的最小的一个。共比较9轮。
下面以5个数为例说明选择法的步骤
3 6 1 9 4 未排序
1 6 3 9 4 将5个数中最小的数1与a[0]对换
1 3 6 9 4 将余下的4个数中最小的数3与a[1]对换
1 3 4 9 6 将余下的3个数中最小的数4与a[2]对换
1 3 4 6 9 将余下的2个数中最小的数6与a[3]对换#include<stdio.h>
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[k];
array[k]=array[i];
array[i]=t;
}
}
}
int main()
{
int *p,i,a[10];
p=a;
printf("输入array[10]:");
for(i=0;i<10;i++)
{
scanf("%d",p+i);
}
p=a;
sort(p,10);
printf("输入的array[10]:");
for(p=a,i=0;i<10;i++)
{
printf("%d ",*p++);
}
printf("\n");
}
使用main函数调用swap函数交换主函数中变量a、b的内容
#include<stdio.h>
void swap(int *x,int *y)
{
int temp;
temp=*x;
*x=*y;
*y=temp;
}
int main()
{
int a=3,b=8,*p,*q;
p=&a;
q=&b;
printf("交换前:%d,%d\n",a,b);
swap(p,q);
printf("交换后:%d,%d\n",a,b);
}
实参维数组名,形参为数组
指针数组作形参
当指针数组作为形参时,对应的形参应当是一个指向指针的指针
main()
{
double a[5][3],*p[5];
...
for(i=0;i<5;i++)
{
p[i]=a[i];
func(p);
...
}
}
则fanc函数的首部可以是以下三种形式:
【1】fun(double *a[5]);
【2】fun(double *a[]);
【3】fun(double **a);
因为传送的是一维指针数组返回指针值的函数(不要返回局部变量的指针)
函数的返回值如果是指针,则称为返回指针值的函数,简称指针函数
定义指针函数的一般形式:
类型说明符 *函数名(形参表)
{
。。。函数体
}
声明格式:
类型说明符 *函数名(形参表);
调用格式:
指针变量=函数名(实参表); 指针函数应用
#include <stdio.h>
char *getword(char);
int main (void)
{
char input;
printf("输入一个字符:");
scanf("%c",&input);
printf("%s\n",getword(input));
return 0 ;
}
char *getword(char c)
{
switch(c)
{
case 'A':return "Apple";
case 'B':return "Banana";
case 'C':return "Cat";
case 'D':return "Dog";
default:return "None";
}
}

注意:
函数指针变量和指针函数两者的在写法和意义上的区别:
int (*p)()-->是一个变量定义,指明p是一个指向函数入口的指针变量,该函数的返回值是整型,(*p)两边的括号不能少
int *p()-->说明p为函数名,函数的返回值类型为指针 有若干个学生的成绩,找出其中至少有一项成绩不合格者,用指针函数实现
#include<stdio.h>
int *seek(int (*p_row)[3])
{
int j=0,*p_col;
p_col=*(p_row+1);
for(;j<3;j++)
{
if(*(*p_row+j)<60)
{
p_col=*p_row;
break;
}
}
return(p_col);
}
void main()
{
int gr[3][3]={{55,65,75},{45,75,85},{75,80,90}};
int *p,i,j;
for(i=0;i<3;i++)
{
p=seek(gr+i);
if(p==*(gr+i))
{
printf("编号.%d 成绩:",i+1);
for(j=0;j<3;j++)
{
printf("%d ",*(p+j));
}
printf("\n");
}
}
}
函数指针和指向函数的指针变量
一个函数在编译时总是被分配一个入口地址,这个入口地址就称为函数的指针
函数指针变量定义的一般形式:
类型说明符 (*指针变量名)([形式参数列表]);
通过指向函数的指针变量调用函数的一般形式:
(*指针变量名) (实参表);
函数指针作为参数

函数指针作为返回值
#include<stdio.h>
int add(int,int);
int sub(int,int);
int calc(int (*)(int,int),int,int);
int (*select(char))(int,int);
int add(int num1,int num2)
{
return num1+num2;
}
int sub(int num1,int num2)
{
return num1-num2;
}
int calc(int (*fp)(int,int),int num1,int num2)
{
return (*fp)(num1,num2);
}
int (*select(char op))(int,int)
{
switch(op)
{
case '+':return add;
case '-':return sub;
}
}
int main()
{
int num1,num2;
char op;
int (*fp)(int,int);
printf("输入一个式子(如1+3):");
scanf("%d%c%d",&num1,&op,&num2);
fp=select(op);
printf("%d %c %d =%d\n",num1,op,num2,calc(fp,num1,num2));
}
求a和b中较大者,用指针形式实现对函数调用的方法
#include<stdio.h>
int max(int a,int b)
{
if(a>b)
{
return a;
}
else
{
return b;
}
}
void main()
{
int max(int a,int b);
int (*pmax)(int,int);
int x,y,z;
pmax=max;
printf("输入两个数:\n");
scanf("%d%d",&x,&y);
z=(*pmax)(x,y);
printf("最大值为:%d\n",z);
}
"pmax=max"表示把被调函数的入口地址(函数名)赋予函数指针变量
使用函数指针变量应注意:
(1)函数的调用既可以通过函数名调用,也可通过指向函数的指针变量调用
(2)函数指针变量不能进行算术运算
(3)“ * ”不应该理解为求值运算,此处它只是一种表示符号主函数mian的参数
C语言规定main函数的参数只能有两个,可由用户自己命名,习惯上写成argc和argv
第一个形参argc必须是整型变量
第二个形参argv必须是字符型指针数组
main(int argc,char *argv[])
argc的值是在输入命令行时由系统按实际参数的个数自动赋予的
argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址argv
二维数组与一维数组的关系
argv[0] | ---> | Myfile\0 |
|---|---|---|
argv[1] | ---> | OK!\0 |
argv[2] | ---> | Good\0 |
argv[3] | ---> | Morning\0 |
指针函数总结
返回值-->指针
(1)全局变量的地址
(2)static变量的地址
(3)字符串常量的地址
(4)堆的地址(动态内存,malloc,free)递归函数和函数指针总结
递归函数
(1)递推阶段
(2)回归阶段
---------------------------------------
函数指针
存放函数的地址(函数的入口地址)
int (*p)(int,int);
p=add;
(*p)(m,n);
-----------------------------------
函数指针数组
int (*p)[3](int,int); //声明
p[0]=add; //函数地址传给函数指针
(*p)[0](m,n); //调用可变参数
#include<stdarg.h>
-va_list
-va_start
-va_arg
-va_end
#include <stdio.h>
#include<stdarg.h>
int sum(int n,...);
int sum(int n,...) //参数个数不确定
{
int i,sum=0;
va_list vap; //定义参数列表
va_start(vap,n); //传入宏
for(i=0;i<n;i++)
{
sum+=va_arg(vap,int); //获取参数值
}
va_end(vap); //关闭参数列表
return sum;
}
int main (void)
{
int result;
result=sum(1,2,3,4);
printf("%d\n",result);
result=sum(5,6,7,8);
printf("%d\n",result);
result=sum(1,1,1,1);
printf("%d\n",result);
return 0 ;
}函数与结构体
将某一结构体变量的值传递给另一个函数,3种方法:
(1)在函数之间传递结构体成员的值与结构体成员的地址
(2)在函数之间传递结构体变量
(3)向函数传递结构体变量(或数组)的地址(指针)用结构体变量做参数
有一个结构体变量stu,内含学生的学号、姓名和三门课程的成绩,要求用结构体变量作为函数的参数,在main函数中赋予值,在另一个函数输出
#include<stdio.h>
#include<string.h>
struct student
{
int num;
char name[9];
float score[3];
};
void main()
{
void print(struct student stu);
struct student stu;
stu.num=10101;
strcpy(stu.name,"李红");
stu.score[0]=60;
stu.score[1]=70;
stu.score[2]=80;
print(stu);
}
void print(struct student stu)
{
printf("%d\n%s\n%4.1f\n%4.1f\n",stu.num,stu.name,stu.score[0],stu.score[1],stu.score[2]);
}
利用结构变量求解两个复数之积:(3+4i)*(5+6i)
#include<stdio.h>
struct complex
{
int real,im;//"real"实部,"im"虚部
};
struct complex cmult(struct complex za,struct complex zb)
{
struct complex w;
w.real=za.real*zb.real-za.im*zb.im;
w.im=za.real*zb.im+za.im*zb.real;
return(w);
}
void main()
{
struct complex a={3,5},b={4,6},c;
c=cmult(a,b);
printf("{%d+%di}\n",c.real,c.im);
}
用指向结构体变量的指针做参数
用指向结构体变量(或数组)的指针作实参,实参向形参传递的就是一个结构体变量的指针值
输入10本书的名称和单价,按照单价从低到高排序,使用插入排序算法
插入排序的基本思想:在数组中,有n个已经从小到大排序好的元素,要加入1个新的元素时,可以从数组的第一个元素开始,一次与新元素比较;
当数组中首次出现第i个元素的值大于新元素时,则新的元素就应当插在原来数组中的第i-1个元素与第i个元素之间
此时可以将数组中第i个元素之后(包括第i个元素)的所有元素向后移动1个位置,将新元素插入,使它成为第i个元素
这样就可以得到已经排序好的n+1个元素#include<stdio.h>
#define NUM 10
struct book
{
char name[20];
float price;
};
sortbook(struct book term,struct book *pbook,int count)
{
int i;
struct book *q,*pend=pbook;
for(i=0;i<count;i++,pend++);
for(;pbook<pend;pbook++)
{
if(pbook->price>term.price)
{
break;
}
}
for(q=pend-1;q>=pbook;q--)
{
*(q+1)=*q;
}
*pbook=term;
}
printbook(struct book *pbook)
{
printf("%-20s%6.2f\n",pbook->name,pbook->price);
}
int main()
{
struct book term,books[NUM];
int count;
printf("请输入书名和价格:\n");
for(count=0;count<NUM;)
{
printf("编号.%d 信息:",count+1);
scanf("%s%f",term.name,&term.price);
sortbook(term,books,count++);
}
printf("--------书籍列表--------");
for(count=0;count<NUM;count++)
{
printbook(&books[count]);
}
}
手机中有一个简单的通讯录工具,用于管理联系人的基本信息,其基本功能包括新建联系人、查询联系人等。编写程序实现
#include<stdio.h>
#include<string.h>
struct friends_list
{
char name[10];
char sex;
int age;
char telephone[13];
char email[30];
char address[40];
};
int count=0;
void new_friend(struct friends_list friends[]);
void search_friend(struct friends_list friends[],char *name);
void main()
{
struct friends_list f1,friends[50];
int n;
char name[10];
do
{
printf("手机通讯录功能选项:0->退出 1->新建 2->查询\n");
printf("请选择功能:");
scanf("%d",&n);
switch(n)
{
case 0:break;
case 1:new_friend(friends);break;
case 2:printf("输入要查找的联系人姓名:");scanf("%s",name);search_friend(friends,name);break;
}
} while (n!=0);
printf("\n感谢使用通讯录功能!\n");
}
void new_friend(struct friends_list friends[])
{
struct friends_list f;
if(count==50)
{
printf("通讯录已满!\n");
return;
}
printf("输入联系人姓名:");
scanf("%s",f.name);
getchar();
printf("输入联系人性别:");
scanf("%c",&f.sex);
getchar();
printf("输入联系人年龄:");
scanf("%d",&f.age);
getchar();
printf("输入联系人联系电话:");
scanf("%s",&f.telephone);
getchar();
printf("输入联系人电子邮箱:");
scanf("%s",&f.email);
getchar();
printf("输入联系人地址:");
scanf("%s",&f.address);
friends[count]=f;
count++;
}
void search_friend(struct friends_list friends[],char *name)
{
int i,flag;
if(count==0)
{
printf("通讯录是空的!");
return;
}
for(i=0;i<count;i++)
{
if(strcmp(name,friends[i].name)==0)
{
flag=1;
break;
}
}
if(flag!=1)
{
printf("无此联系人!");
}
else
{
printf("%s\n%c\n%d\n%s\n%s\n%s\n",friends[i].name,friends[i].sex,friends[i].age,friends[i].telephone,friends[i].email,friends[i].address);
}
}
变量的作用域
每个变量和函数有两个属性:数据类型和数据的存储类别数据类型
从变量的作用域的角度考虑:可分为全局变量和局部变量 从变量生存期的角度考虑:可分为静态变量和动态存储类型
静态存储方式:在程序运行期间分配固定的存储空间的方式 动态存储方式:在程序运行期间根据需要进行动态分配存储空间的方式
用户存储空间可分为3部分:程序区、静态存储区、动态存储区
所有的数据分布存放在静态存储区和动态存储区中
全局变量全部存放在静态区,程序执行时,为全局变量分配存储区(占据固定的存储单元),程序执行完毕即释放;
动态存储区存放:函数的形式参数、自动变量和函数调用时的现场保护和返回地址(统称为自动变量);
如果一个程序两次调用同一个函数,分配自动变量的存储单元可能是不同的
局部变量,可说明为自动类型或静态类型 全局变量,只能是静态类型
存储类别
auto(自动)
register(寄存器)
static(静态)
extern(外部)通常与类型名一起出现,可放在类型名的左边和右边
局部变量

全局变量

自动变量

寄存器变量

静态变量

内部函数和外部函数

预处理命令
预处理命令以符号“#”开头,功能主要有以下3种:
(1)宏定义
(2)文件包含
(3)条件编译宏定义
用标识符代表一个字符串,即给字符串取一个名字;
“宏”分为不带参数的宏(无参宏)和带参数的宏(有参宏)
带参数的宏定义

文件包含

条件编译
第一种形式
#ifdef 标识符
程序段1
#else
程序段2
#endif
功能:如果标识符已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译
第二种形式
#ifndef 标识符
程序段1
#else
程序段2
#endif
功能:如果标识符未被#define命令定义则对程序段1进行编译,否则对程序段2进行编译
第三种形式
#if 表达式
程序段1
#else
程序段2
#endif
如果表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译模块结构程序设计
结构化程序设计方法
(1)按自顶向下方法对问题进行分析、设计
(2)模块化设计每个功能
(3)结构化编码模块化程序设计
(1)一个模块只完成一个zhidingg一个mo
(2)模块之间只存在参数调用的关系
(3)函数内尽量使用自动变量
(4)一个模块只有一个入口和一个出口
模块中包含的语句一般不要超过50行结构化程序编写
(1)符号的命名:应根据其作用、含义来命令
(2)程序的注解
(3)语句结构:简单、直接;清晰第一,效率第二
(4)良好的交互特性:提示,缺省值设置模块结构程序的工程创建于调试
(1)用文件包含的方法,在主文件模块中用#include把其他文件包含进去
(2)利用开发工具创建程序工程第八章 文件
C文件概述
c语言把文件看作是一个字符(字节)的序列,即由一个字符顺序组成
根据数据组织的形式: (1)ASCII(文本)文件 (2)二进制文件
ASCII文件的每一个字节存放一个ASCII代码,代表一个字符
【一个字节代表一个字符,占用存储空间较多】
二进制文件是把内存中的数据按其在内存中的存储形式原样输出到外部介质上存放
【一个字节不对应一个字符】
不能将二进制数据直接输出到显示器,也不能直接从键盘输入二进制数据
C语言对文件的存取是以字符(字节)为单位的
输入输出数据流的开始和结束仅受程序控制而不受物理符号(如换行符)控制
即在输出时不会自动增加换行符以作为记录结束的标志,输入时不以换行符作为记录的间隔--->流式文件
c语言允许对文件存取一个字符
ANSI C语言 采用缓冲文件系统处理文本文件和二进制文件
缓冲文件系统:自动地在内存区为每一个正在使用的文件名开辟一个缓冲区;
从内存向外部存储介质输出数据必须先送到 -> 内存的缓冲区,装满缓冲区后 -> 一起送到外部存储介质中;
如果从外部存储介质向内存读入数据,则一次从文件中将批量数据输入 -> 到内存缓冲区(充满缓冲区)
缓冲区的大小有各个具体c语言版本确定,一般为512字节
用缓冲文件系统进行的输入输出-->高级磁盘输入输出
UNIX系统,用缓冲文件系统处理文本文件,用非缓冲文件系统处理二进制文件
非缓冲文件系统:系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区
非文件缓冲系统进行的输入输出-->低级输入输出系统
文件类型指针
定义文件类型指针的一般形式:FILE *指针变量名
FILE *fp-->指向结构体变量的指针文件的打开与关闭
文件打开
提供库函数fopen用以打开文件
fopen函数的一般调用形式:fopen(文件名,文件使用方式);
函数返回一个FILE类型结构体的指针

若打开时发生错误,fopen函数将返回NULL
if((fp=fopen("file1","r"))==NULL)
{
printf("Cannot open this File!\n");
exit(0);
}当开始运行一个c语言程序时,系统将负责自动打开三个文件,
(1)标准输入文件 <-- stdin(文件指针) --连接键盘
(2)标准输出文件 <-- stdout(文件指针) --连接显示器
(3)标准出错文件 <-- stderr(文件指针) --连接显示器
三个指针为常量指针,不能重新赋值文件关闭
关闭文件通过对调用库函数fclose
形式如下:fclose(文件指针);
完成文件操作后,应关闭文件,否则文件缓冲区中的剩余数据将会丢失(数据未充满缓冲区就结束程序)
先把缓冲区中数据输出到磁盘文件,然后才释放文件指针变量
fclose函数有一个返回值,当成功执行了关闭操作后,函数返回 0,否则返回 EOF
EOF是stdio.h文件中定义的符号常量 --> 文件结束符,值为 -1
文件的读写
fputc(putc)函数和fgetc(getc)函数
fputc/fgetc --> 函数实现
putc/getc --> 宏的实现fputc(putc)函数

【例8.1】从键盘输入的文本按原样输出到file1.dat文件中,用字符*作为键盘输入结束标志
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
FILE *fp1;
char ch;
if((fp1=fopen("file1.dat","w"))==NULL)
{
printf("file error!\n");
exit(0);
}
printf("输入要输出的文件内容:\n");
while((ch=getchar())!='*')
{
fputc(ch,fp1);
}
fclose(fp1);
return 0;
}
fgetc(getc)函数

【例8.2】如果把一个已存在磁盘上的file1.dat文本文件中的内容,原样在屏幕上显示出来
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
FILE *fp2;
char ch;
if((fp2=fopen("file1.dat","r"))==NULL)
{
printf("file error\n");
exit(0);
}
while((ch=fgetc(fp2))!=EOF)
{
putchar(ch);
}
putchar('\n');
fclose(fp2);
return 0;
}

【例8.3】将一个磁盘文件file1.dat中的内容复制到另一个磁盘文件file2.dat中
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
FILE *fp1,*fp2;
if((fp1=fopen("file1.dat","r"))==NULL)
{
printf("file open error\n");
exit(0);
}
if((fp2=fopen("file2.dat","w"))==NULL)
{
printf("file open error\n");
exit(0);
}
while(!feof(fp1))
{
fputc(fgetc(fp1),fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}fgets函数和fputs函数

fread函数和fwrite函数

【例8.4】从键盘输入5个学生的有关数据,然后把它们转存到磁盘文件上去
#include<stdio.h>
#define SIZE 5
struct student
{
char name[8];
int num;
int age;
}stu[SIZE];
int main()
{
FILE *fp;
int i;
printf("输入%d个学生的姓名,学号,年龄(用空格隔开):\n",SIZE);
for(i=0;i<SIZE;i++)
{
scanf("%s%d%d",stu[i].name,&stu[i].num,&stu[i].age);
}
if((fp=fopen("student","wb"))==NULL)
{
printf("file open error\n");
return;
}
for(i=0;i<SIZE;i++)
{
if(fwrite(&stu[i],sizeof(struct student),1,fp)!=1)
{
printf("file write error\n");
}
}
fclose(fp);
//读入数据
FILE *fs;
fs=fopen("student","rb");
for(i=0;i<SIZE;i++)
{
fread(&stu[i],sizeof(struct student),1,fp);
printf("%10s%8d%4d\n",stu[i].name,stu[i].num,stu[i].age);
}
fclose(fs);
}
fread和fwrite函数一般用于二进制文件的输入输出
它们是按数据块的长度来处理输入输出的,在字符发生转换时,可能出错
当输入数据有空格时,fread函数会根据sizeof的字节数一次性读入,空格不作为数据间的分隔符
fprintf函数和fscanf函数

文件的定位
文件中有一个文件位置指针,文件位置指针用来表示当前或写的数据在文件中的位置
fopen打开文件时,文件位置指针总是指向文件的开头
rewind函数
功能:使文件位置指针总是指向文件的开头
调用形式:rewind(fp); ->fp 文件指针 此函数没有返回值
【例8.5】有一个磁盘文件,第一次把他复制到另一个文件上,第二次将它的内容显示在屏幕上
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp1,*fp2;
fp1=fopen("file1.dat","r");
fp2=fopen("file2.dat","w");
while(!feof(fp1))
{
putc(getc(fp1),fp2);
}
fclose(fp2);
rewind(fp1);
while(!feof(fp1))
{
putchar(getc(fp1));
}
putchar('\n');
fclose(fp1);
}fseek函数

fseek(fp,100L,0); //将位置指针移到离文件头100个字节处
fseek(fp,50L,1); //将位置指针移到离当前位置50个字节处
fseek(fp,-10L,2); //将位置指针从文件末尾处向后退10个字节一般用于二进制文件,防止文本文件要进行字符转换时,出错
【例8.6】在磁盘文件中存有5个学生的数据,要求将1,3,5个学生数据输入计算机,并在屏幕上输出
#include<stdio.h>
#include<stdlib.h>
#define SIZE 5
struct student
{
char name[8];
int num;
int age;
}stu[SIZE];
int main()
{
int i;
FILE *fp;
if((fp=fopen("student","rb"))==NULL)
{
printf("file open error\n");
exit(0);
}
for(i=0;i<SIZE;i+=2)
{
fseek(fp,i*sizeof(struct student),0);
fread(&stu[i],sizeof(struct student),1,fp);
printf("%10s%8d%4d\n",stu[i].name,stu[i].num,stu[i].age);
}
fclose(fp);
}
ftell函数
功能:获取当前文件当前位置指针的位置,用相对于文件开头的位移量来表示
ftell函数返回值为-1L,表示出错
通过下列语句可求文件中包含结构体为单位的数据块的个数
fseek(fp,0L,SEEK_END);
i=ftell(fp);
n=i/sizeof(struct stu);文件检测函数
feof函数(文件结束检测函数)
调用格式:feof(文件指针);
-->文件结束,返回 1
-->未结束,返回 0ferror函数(读写文件出错检测函数)
功能:检查文件在各种输入输出函数进行读写时是否出错
调用格式:ferror(文件指针);
-->未出错,返回 0
-->出错,返回 1clearerr函数(文件出错标志和文件结束标志置0函数)
功能:用于清除出错标志和文件结束标志,使它们的值为0
调用格式:clearerr(文件指针);perror函数
可以直观地打印出错误原因
perror("字符串");
【输出在字符串后面】strerror函数
直接返回错误码对应的错误信息
头文件<errno.h>
fprintf(stderr,"字符串:%s\n",strerror(errno));贡献者
更新日志
fb8bc-更新为vuepress于






































