C 语言详细教程

目录

第一章 C语言基础知识

第二章 数据类型、运算符和表达式

第三章 结构化程序设计

第四章 数组

第五章 函数

第六章 指针

第七章 结构体类型和自定义类型

第八章 编译预处理

第九章 文件


说明:本教程中的代码除一二三个之外,都在https://lightly.teamcode.com/dashboard   (C99) 或 visual studio 2022上测试过,visual studio 2022需要做如下配置:改预定义处理:右击项目,点击“属性”,打开属性页后,点击“预处理器”,在预处理器定义中添加“_CRT_SECURE_NO_DEPRECATE ”,然后点击确定,再点击应用,最后再确定。

第一章 C语言基础知识

第一节 C语言的发展和特点

一、C语言的发展

ALGOL60 -> CPL -> BCPC -> B ->C ->标准C

目前在微机上使用的C编译程序有: Turboc、Microsoft C、Quick C。

二、C语言特点

1.结构化语言

C语言具有良好的结构化(顺序,选择和循环)语句。

C语言的函数是程序的基本模块,便于实现程序的模块化。

2.运算能力强大

C语言提供了三十多种运算符,表达式的种类多样。

3.数据类型丰富

包括整型、实型、字符型、数组型、结构体类型、指针类型、空类型等。

4.具有预处理能力

5.可移植性好

6.程序执行效率高 由C源程序生成目标代码的效率一般仅比汇编语言低10% ·20%

7.程序设计自由度大

C语言的语法限制不严格,允许程序员具有较大的自由度。

C语言允许直接对计算机硬件进行操作,即具有高级语言的功能,又具有代级语言的功能,所以也被称为“中间语言”,可用来编定系统软件。

第二节 C语言基本词法

一、字符集

C语言的字符集就是ASCII字符集,主要包含下列四类:

1.大小写英文字母A~Z, a~z(52个).

2.数字0、1 .. 9

3.非字母、非数字的可显示字符(33个), 即键盘可输入的字符

4.转义字符

二、关键字

在C语言中有特殊含义的单词称为"保留字", 也称“关键字”, 主要用于构成语句,共32个。

根据关建字的作用,可以将32个关键字分为四类

数据类型关键字(12)

控制语句关键字

存储类型关键字

其他关键字

注意:所有的保留字均由小写字母组成的,一旦有一个字母大写,其不再是保留字原来的含义。

三、标识符

标识符是用户自定义的字符序列,比如符号常量名、变量名、数组名、函数名等。

C语言规定,标识符是由字母或下划线开头的字母、数字、下划线组成的字符序列。

C语言标识符中的字母是区分大小写的。

标识符的命名应遵循简洁明了和顾名思义,以增加程序的可读性。

用户自定义的标识符既不能是C语言的关键字,也不能与用户已编写的函数或C语言的库函数重名。

四、词汇分类

C语言的词类主要分为下列六类:

1.常量: C程序运行中其值不发生变化的量,

2.标识符: C 程序中使用的变量名、函数名和标号等统称为标识符

3.关键字:由C语言规定的具有特定意义的字符串

4.运算符:C语言中用于计算的符号

5.注释符

注释符有两种: 一种是以"/" 开头,并以"/"结尾的; 多行注释:是以“//”开头的单行注释。

注释可以出现在程序的任何位置,可有多行,但 不可嵌套,注释的作用是为用户提示或解释程序的意义。编译源程序时,不对注释进行任何处理。

在调度程序时对暂不使用的语句也可用注释符括起来,待调试结束后再去掉注释符。

6.分隔符: C 语言的分隔符包括逗号和空格两种。

第三节 C语言基本语句分类

按照语句功能或构成的不同,将C语言的语句分成八类。

1.数据定义语句:用来定义程序中使用的各种能存放数据的对象的名称和特性。

2.赋值语句:形如"变量=表达式"的语句,功能是计算表达式的值并赋予变量。

3.函数调用语句:形如"函数名(实际参数表)"的语句,功能是调用指定函数。

4.表达式语句: 仅由任何表达式组成的语句。

5.流程控制语句:用来控制程序执行过程的语句。

选择控制语句: if () ~else~ switch

循环控制语句: for()~、while()~、do~while()

中止语句:break

继续循环语句: continue

返回语句: return

无条件转移语句:goto

6.复合语句: 用花括号括住的一组任意语句

main()

{

{.....} /*复和语句

}

7.空语句:无任何操作的语句,即只由分号组成的语句,一般在程序中用作空循环体。

注意: while(getchar()!='\n'); 和上面程序段功能一样。

8.其他语句: 包括编译预处理命令、用户自定义类型语句等。

例:#include "stdio.h"

第四节 C程序基本组成

例1:从键盘输入两个整数,计算其乘积

#include <stdio.h>     /*编译预处理命令*/
int main(void)   /*主函数*/
{
    int n1,n2, result;     /*定义保存两个整数及其乘积的变量 */
    printf("please input the two numbers: \n");   /*屏幕显示提示信息*/
    scanf("%d%d", &n1, &n2);  /*从键盘输入两个整数并分别保存到变量n1和n2*/
    result = n1 * n2;   /*计算整数n1和n2的乘积并保存到就变量result*/
    printf("the result is:%d\n", result);    /*输出变量 result 中的两个整数的乘积*/
    return 0;   /*返回*/
} 
#include <stdio.h>
int main(void)
{
    int n1, n2, result;     /*定义保存两个整数及其乘积的变量 */
    printf("please input the two numbers: \n");   /*屏幕显示提示信息*/
    scanf_s("%d%d", &n1, &n2);  /*从键盘输入两个整数并分别保存到变量n1和n2*/
    result = n1 * n2;   /*计算整数n1和n2的乘积并保存到就变量result*/
    printf("the result is:%d\n", result);    /*输出变量 result 中的两个整数的乘积*/
    return 0;   /*返回*/
}

例2:从键盘输入两个整数,计算其乘积(使用用户自定义函数实现)

#include <stdio.h>     /*编译预处理命令*/
​
int mul(int a, int b)    /*定义一个函数mul*/
{
    int c; /*定义一个整型变量c*/
        c = a * b;  /*计算两个整数的乘积并保存到变量中*/
    return (c); /*返回变量c中的两个整数的乘积*/
}
#include <stdio.h>     /*编译预处理命令*/
int main(void)   /*主函数*/
{
    int n1,n2, result;     /*定义保存两个整数及其乘积的变量 */
    printf("please input the two numbers: \n");   /*屏幕显示提示信息*/
    scanf("%d%d", &n1, &n2);  /*从键盘输入两个整数并分别保存到变量n1和n2*/
    result = mul(n1, n2);   /*调用函数mul计算整数n1和n2乘积并赋值给变量result*/
    printf("the result is:%d\n", result);    /*输出变量 result 中的两个整数的乘积*/
    return 0;   /*返回*/
}

二、C程序基本组成的特点

1.函数是C程序的基本单元。每个C程序是由一个或多个函数构成的。每个C程序有且仅有一个主函数,该主函数的函数名规定为main.

2.每个函数(包括主函数)的定义分为两个部分:函数首部和函数体。

函数首部:返回值的类型 函数名(类型 形式参数名, ....)

函数体: {

数据定义部分

实现函数功能 的语句串

}

注意:当函数没有形式参数时写void

3.C程序中的每个语句以“分号”作为语句的结束,“分号”是C语句的组成部分,不可以省略。但预处理令和函数首部之后不能加分号.

4.C 语言本身没有输入/输出语句,而是通过调用库函数scanf()、printf()、getchar()、putchar()实现的。

5.C程序的任意位置都可以加注释,注释不编译。

/* ... */ 单行或多行注释,注意不可嵌套

//... 单行注释

6.C程序的书写格式灵活,一行内可书写多条语句,一条语句也可以写在多行上,可以任意插入空格和回车。

但为了程序清单层次分明、便于阅读,通常都采用缩格并对齐的书写方法。

一个说明或一条语句点一行。

使用空格或TAB缩进。

{}对齐

有足够的注释

有合适的空行

7.C程序中主函数和其他函数的位置是任意的,但程序的执行总是从主函数开始,并在主函数结束。其他函数总是通过函数调用语句被执行。

8.主函数可以调用任何其它函数;任何其它函数都可以相互调用,但不能调用主函数。

9.C程序中可以有预处理命令(例include命令),通常预处理命令应放在程序的最前面。

10.C语言编译系统区分字母大小写。

第五节 C程序开发环境

一、C程序的运行步骤

二、C程序的运行

第二章 数据类型、运算符和表达式

第一节 数据类型

一、C语言的数据类型及其关系图

二、C语言数据类型的说明

  1. C 语言规定在使用一个数据之前,必须定义数据的类型,因为数据类型决定了数据占用内存的字节数、 取值范围以及在其上可以进行的操作等。

  2. C语言的数据有常量和变量之分,它们分别属于上述的类型。

  3. 数据类型丰富,包括整型、实型、字符型、数组型、结构休类型、指针型和空类型等。

第二节 常量

常量的基本概念

常量以称为常数,是在程序运行过程中其值不能改变的数据。

C语言规定常量包括: 整型常量,实型常量,字符型常量、字符串常量和符号常量。

使用常量时,不需要事先定义,只需在程序中直接写出。常量的类型系统可通过书写形式自动识别,无须定义。

一、整型常量

在C语言中,整型常量有三种书写形式:

1.十进制整数:由数字0 - 9和正负号表示。如: 0、-111、+15、21等.

2.八进制数(以0开头)。例如00、-0111、+015、021

3.十六进制整数(以0x开关)。 例如0x0、 -0x111、 +0xff、0x2d

说明:(1) 正整数前面的" +" 号可以省略。

(2)整型常量在16位计算机中占用2个字节,数值范围为-2^15 ~ 2^15 -1, 即-32768 ~ 32767.

(3)长整型常量(在整数的末尾字母“L”);例如0L(十进制)、 0111L(八进制)、0x15L(十六进制)长整型常量在计算机中占用4个字符。其取值范围可达 -2^31 ~ 2^31 -1.

注意: 10 和 10L是不同的 整型常量,虽然它们有相同的数值,但它们在内存中占用不同数量的字节。

二、实型常量

实型常量只使用十进制,它的书写形式有两种:

1.十进制数形式:(必须有小数点)如: 0.123, .123, 123.0, 0.0, -.123

2.指数形式:(e或E之前必须有数字; 指数必须为整数)

如:12.3e3, 123E2,1.23e4, 错误: e-5,1.2E3.5

说明: (1)实型常在计算机中占用4个字节,数值范围都是 -10^38 ~ 10^38

(2) 实型常量有效数字是7位,例如, 1.23456789和1.234567是相同的。

三、字符常量

字符常量: 用一对单引号前后括住的单个字符。例如: 'a'、'1'、'%'、'A'。

转义字符: 一种特殊形式的字符常量,以反斜杠""开头,后跟单个字符或若干个字符组成。

四、字符串常量

字符串常量简称为“字符串”。字符串就是用两个双引号(“)前后括住的一串字符。例如, "abc"、"12345"、"a"、"A"、"\101\n\x43"都是字符串。

空字符串:没有字符的字符串称为"空字符串". 例如:"";

字符串的长度: 一个字符串中所有字符的个数称为该字符串的长度。

例如:""的长度为0; "ab12c"的长度为5; "\101\n\x43ab"的长度为5.

说明:

1.空串与空格串是不同的。空串不含任何字符,长度为0;空格串是包含若干空格字符的字符串,其长度为所含空格字符的个数。

2.每个字符串在内存中占用的字节数等于字符串的长度+1,其中最后一个字节存放的字符为“空字符”,其值为0, 书写时常用转义字符"\0"来表示,称为字符串结束标记。例如:"China",表示“China\0”

五、符号常量

符号常量的定义方法如下: #define 符号常量 常量

例:

1) #define PI 3.14159 /*定义了符号常量PI, 其值等于实型常量3.14159 */

2) #define R 10 /* 定义了符号常量R, 其值等于整型常量10*/

3) #define A 'A' /*定义了符号常量A, 其值等于字符常量'A' */

说明:

1.符号常量按标识符的规则构成,建议用大写英文字母组成。其中的常量可以是任何类型的常量。

2.符号常量的定义一般放在程序的开头,每个定义必须独占一行。其后不跟分号。

3.在程序中使用符号常量有两点好处:

提高程序的可读性, 便于程序修改。

第三节 变量

变量的基本概念

1.变量:在程序运行过程中,其值可以被惊变的量称为变量.

2.变量的三个要素

(1)变量名:每个变量都必须有一个名字----变量名, 变量命名遵循标识符命名规则。

(2)变量值:变量中所保存的数据称为该变量的值,在程序运行过程中,变量值存储在内存中。

(3)变量类型:变量类型可以是任何一种数据类型。

一、变量的数据类型

二、变量的定义和初始化

1.变量的定义

数据类型符 变量名1 [, 变量名2 ...]

例: int r, s; /* 定义2个有符号基本整型变量 */

short m,n; /* 定义2 个有符号短整型变量*/

long p, q; /* 定义2个有符号长整型变量 */

unsigned i, j, k; /* 定义3个无符号基本整型变量 */

float r, s; /* 定义2 个单精度实型变量 */

double f1, f2; /* 定义2个双精度实型变量 */

char c1, c2; /* 定义2 个字符型变量 */

说明:(1) 对变量的定义可以放在函数之外,也可以放在函数体中或复合语句中。如果是放在函数体或复合语句中,刚必须集中放在最前面。

例:

int a=10;  /*定义在函数之外的变量 */
void main()
{
   int b=2;   /* 定义在函数之内的变量 */ 
   {
     int c;   /**定义在复合语句之内的变量 */
     c = a + b;
   }
}

(2) 值在-128 ~ 127 之间的int、 short、long的变量 与 字符型的变量 是通用。

(3) 值在0 ~ 255之间的unsigned、unsigned short、unsigned long的变量 与 字符型的变量 通用.

(4) 变量定义后,系统自动为其分配连续的内存单元,所占用的内存字节数取决于变量的数据类型。

例: 1) int r; /* 整型变量r在内存中占2个字节 */

2) short m; /* 短整型变量m在内存中占2个字节 */

3) long p; /* 长整型变量p在内存中占4个字节 */

4) float s; /* 单精度实型变量s在内存中占4个字节 */

5) double f1; /* 双精度实型变量f1在内存中占8个字节 */

6)char c1; /* 字符型变量c1在内存中占1个字节 */

2.变量的初始化: 定义变量的同时给变量赋初值.

数据类型符 变量名1=初值1[, 变量名2=初值2, ...]

例: int r=2, s; /* 定义变量r的同时为其赋初值2 */

long p=123L;

float r=2.5, s=0.0;

double f1=5.6e2, f2=1.25;

char c1='Y', c2='N';

三、有名常量的定义

C语言中,如果定义了变量并赋予其初值,但不希望在程序中对其值进行修改,则可以将该变量定义为有名常量。

有名常量定义语句的一般格式如下: const 数据类型符 变量名1=初值1. .....;

例: const int j=1; /* 定义值为1整型有名常量 */ *

const float f1=3.5, f2=6.8; /* 定义2个单精度实型有名常量,f1的值是3.5, f2的值是6.8 */

第四节 运算符和表达式

一、算术运算符和算术表达式

1.算术运算符(7种)

算术运算符的优先级:

注意:

(1)两个整数相除,其商为整数,小数部分被舍弃。

例如: 5 / 2 =2, 1 / 2 = 0, -5 / 3 = -1;

(2) 一个或两个运算对象是实型, 则运算结果是实型。

例如:5.0 / 2 = 2.5, 5 / 2.0 = 2.5, 5.0 / 2.0 = 2.5

(3)模运算: 要求两侧的运算对象必须为整型,否则出错。运算结果(即余数)的符号与被除数的符号相同。

例如: 4 % -3 = 1 4 % 3 = 1

2.自增、自减运算符(2种)

 说明:

(1)自增、自减运算,常用于循环语句中,使循环控制变量加(或减)1, 以及指针变量中,使指针指向下(或上)一个地址。

(2)自增、自减运算符的对象是变量,不能是常量或表达式。

例如: 5++、--(a +2)、----1、++1++等都是非法的。

(3)自增和自减运算符的优先级

自增和自减运算符优先于双目算术运算符自增、自减运算符和单目算术运算符(+、-)的优先级相同。

(4)当出现若干个+或-组成运算符串时,C语言规定自左向右取尽可能多的符号组成运算符。

【例】 i+++j 应理解成(i++)+j, 而不是i+(++j)

【例】设int i = 2, j =2; char c1 = 'd' c2 = 'D' /* c1和c2的值分别是100和68 */

1) ++i 表达式的值是3, 变量i的值是3。

2) j-- 表达式的值是2,变量j的值是1。

3) ++c1表达式的值是'e'(值为101), 变量c1的值是'e'

4) c2-- 表达式的值是'D'(可看成68), 变量c2的值是'c' (值为67)

A 值为 65;a 值为 97 ;差32

B 值为66; b值为98;

5) j--+1的值是3, j的值是1。

6) c2--+1 的值是'E'(可看成69), c2的值是'C'(可看成67).

7) (i++)+(i++) 的值是4, i 的值是4

8) (j--)-(j--)的值是0, j的值是0。

9) i+++j 应理解成 (i++)+j, 值为4,i为3, j不变。

10) i+++j++ 应理解成 (i++)+(j++), 值为4, i为3,j为3.

3.算术表达式: 表达式中的运算符都是算术运算符。

设c中为大写字母,求对应的小写字母 c+32

设c中为小写字母,求对应的大写字母 c-32

二、关系运算符和关系表达式

1.关系运算符

 说明:

(1) 关系统活动运算符的优先级如下:

算术运算符 优先于 关系运算符

<、<=、>、>= 优先于 ==、 !=

<、<=、>、>= 优行级相同,结合性是自左向右的

==、!= 的优先级是相同的,结合性是自左向右

(2)C语言中“等于”这一关系运算符是双等号“==”, 而不是单等号"=" (赋值运算符)

2.关系表达式: 由关系运算符连接表达式构成

【例】 疫 int i=3, j =4, k=5; 计算下列表达式的值。

(1) i < k 即 3 < 5, 值为1

(2) i-j>=k-i 即 (3-4)>=(5-3) 即 -1>=2 值为 0

(3) j<i<k 即 (4<3) < 5 即 0<5 值为 1

(4) i==j!=k>j>=i 即 (3==4)!=((5>4)>=3) 即 (0)!=(1>=3) 即 0 != 0, 值为 0

(5) i < j < k !=i==i 即(((i<j)<k)!=i)==i, 即(((3<4)<5)!=3)==3 即((1<5)!=3)==3 即(1!=3)==3 即1==3 值为0

【例】 设float f1=1.0,f2=2.1; char c1='a', c2='d'; 计算下列关系表达式的值.

1) f1 < f2 即 1.0 < 2.1 值为1

2) c1 >= c2 即 97 >= 100 值为 0

3) c2 <= f1 即 100 <= 1.0 值为0

三、逻辑运算符和逻辑表达式

1.逻辑运算符

  说明:

(1) 逻辑运算的结果是整型数0或1。

(2)逻辑运算符的优先级如下: !优先于 双目算术运算符 优先于 关系运算符 优先于 && 优先于 ||

(3)用&&对两个表达式进行计算时,若第一个表达式的值为“假”, 则运算结果与第二个表达式的值无关,结果肯定为“假”,所以C语言规定此时第二个表达式将不再计算。

例如:int a=5, 则 0&&(a++) 的值为0, a值还是5.

(4) 用|| 对两个表达式进行计算时,若第一个表达式的值为“值”, 则运算结果与第二个表达式的值无关。结果肯定为“真”,所以C语言规定此时第二个表达式也不再计算。

例如: int a = 5, 则1||(a++)的值为1, a值还是5。

(5)逻辑运算符两侧的运算对象可以是任何类型的数据,如整型、实型、字符型等。系统一律当作逻辑值进行解释,即“非0”为逻辑真; "0"为逻辑假。

2.逻辑表达式: 由逻辑运算符连接表达式构成。

【例】 设 int x = 3, y=100; float f1=1.0, f2=2.1; char c='d'; 计算下表达式的值.

1) f1 && f2 && c 即1.0 && 2.1 && 100, 值为1

2) f1 > f2 || !c 即(1.0 > 2.1) || (!100), 即 0 || 0, 值为 0;

3) x + 97 == y && y ==c 即((3+97) == 100) && (100 == 100) 即1&&1, 值为1

【例】逻辑表达式实际应用的例子:

 四、赋值运算符和赋值表达式

1.基本赋值运算符

 说明:赋值运算符的优先级如下:

算术运算符 优先于 关系运算符 优先于 双目逻辑运算符 优先于 赋值运算符

2.复合赋值运算符(算术部分)

 说明:

(1) 算术复合赋值运算符的优先级如下:

算术运算符 优先于 关系运算符 优先于 双目逻辑运算符 优先于 算术复合赋值运算符

算术复合赋值运算符和赋值运算符的优先级是相同的,结合性都是自右向左的。

(2)算术复合赋值运算符右边的表达式是自动加括号的。例如 "c%=a-3" 不能理解为 "c=c%a-3", 应理解为"c=c%(a-3)"

3.赋值表达式: 由赋值运算符或复合赋值运算符连接变量和表达式构成。

【例】 疫 int i=1,j; float f=2.0; 则下列表达式均为赋值表达式。

1) f+=i-1 即f=2.0+(1-1), 表达式的值是2.0, f的值2.0

2)i=i>=f 即i=(1>=2.0), 表达式的值是0, i的值0

3)j=!(i==1) 即j=!(1==1), 表达式的值是0, i的值0

4)i=j=2 表达式的值是2, i的值2,j的值2

【例】设变量定义如下:

int m1=10, m2=10, m3=10, m4=10;

求表达式 m1+=m2-=m3*=m4/=2 运算后m1、m2的值.

求解过程:运算顺序相当于 m1 +=(m2-=(m3*=(m4/=2)))

m4=m4/2, m4=10/2,m4=5

m3=m3*5, m3=10*5, m3=50

m2=m2-50, m2=10-50, m2=-40

m1=m1+(-40), m1=10-40,m1=-30 表达式的值为 -30

【例】设变量定义如下: int m=10; 执行语句 m+=m-=m*m后,变量m的值是多少?

求解过程: m=m-(m*m)=10-(10*10)=-90 此时m的值为-90

m=(m+(-90))=-90-90=-180 最后m的值为 -180

五、逗号运算符和逗号表达式

1.逗号运算符

 说明: 任何运算符都优先于逗号运算符地。

2.逗号表达式: 用逗号运算符把两个或多个表达式连接起来构成逗号表达式,构成规则如下:

表达式1, 表达式2, ......

【例】逗号表达式的例子。

设 int i=1, j; float f=2.0; char ch='b';

1) i+1, ch+1 即1+1, 98+1 表达式的值是99

2) i》ch, i>=f 即1》98, 1>=2.0 表达式的值是0

3) i=j=2, j=3 逗号表达式的值是3

【例】设变量定义如下: int a=2, b=2, c,d,e,f;

!) b=a+3, c=b+4运算后,a 不变, b为5, c为9,表达式的值为9(c=b+4的值)

2) d=a--, e=d--, f=--e运算过程如下:

① 执行d=a--后, d的值为2, a的值为1;

② 执行e=d--后,e的值为2, d的值为1,

③ 执行f=--e后,f的值为1, e的值为1

最后,a,d,f,e均为1, 表达式值为1

六、条件运算符和条件表达式

1.条件运算符:(C语言唯一的一个三目运算符)

 说明:

其它运算符 优先于 条件运算符 优先于 赋值、算术复合赋值运算符 优先于 逗号运算符

2.条件表达式: 由条件运算符构成的表达式

【例】 设变量定义如下: int a=2, b=2, c=2,d=2;

1)表达式 a==b?(c=1):(d=0)运算后,a、b、c、d的值,表达式的值?

a、b、c的值不变, c为1, 表达式的值为1.

2)表达式 a==b?(c=0):a>b?(c=1):(c=-1) 运算后,a、b、c、d的值,表达式的值?

a、b不变, c为0, 表达式的值为 0。

七、位运算符和位运算表达式

(补) 计算机中各种数制的转换

1) 十进制转换为二进制

方法: 先将十进制分解为2的不同整数首乌次幂的和,然后把这些数分别喝转化为二进制,最后将这些二制数相加,就得到结果 。

例如:将十进制数 81 转换为二进制数.

81 = 64 + 16 + 1 = 2^6 + 2^4 + 2º=(1000000)₂ + (10000)₂+ (1)₂=(1010001)₂

2) 二进制转换为十进制

方法:采用乘幂相加法 例如: 将二进制数为 11010 转换为相应的十进制数.

1×2^4+1×2³+1×2¹=16+8+2=26

3)二进制转换为八进制

方法:将二进制右向左每三位分为一组,最左端不够三位补零,最后将每小组转换为一位八进制数。

例如: 将二进制数为 11010 转换为相应的八进制数.

分组: 011 010, 转换为八进制数32, 表示为032

4)八进制数转换为二进制

方法:从右向左每位八进制数转换为三位二进制数,然后去掉最左端的零。

例如: 将八进制为 32 转换为相应的二进制数。

32 按位转换 011 010 取掉最左端的零得 11 010

5) 二进制转换为十六进制

方法: 将二进制从右向左每四位分为一组,最左端不足四位补零,最后将每小组转换为一位十六进制数。

例如: 将二进制为 11010 转换为相应的十六进制数。

分组: 0001 1010, 转换为十六进制数 1A, 表示为 0x1A

6)十六进制转换为二进制

方法:从右向左每位十六进制数转换为四位二进制数,然后去掉最左端的零。

例如:将十六进制数为1A转换为相应的二进制数。

1A 按位转换 0001 1010 取掉最左端的零香 11 010

1.计算机中数值的表示、

(1)二进制位与字节: 字节是内存的基本单位,1字节由8个二进制位(bit)组成,每个二进制位的值为0或1。1字节中各二进制位的编号如下图所示:

(2) 数值的原码表示: 将最高位作为符号位(0表示正, 1表示负数), 其余各位代表数据本身的绝对值(以二进制形式表示)的表示形式。

例如: +9的原码是 00001001 -9的原码是 10001001

(3) 数值的反码表示: 正数的反码与原码相同,负数的反码的符号为1,其余各位为该数绝对值的原码按位取反。

例如: +9的反码是 00001001 -9的反码是 11110110

(4) 数值的补码表示: 正数的补码与原码相同,负数的补码的符号位为1,其余各位为该数绝对值的原码按位取反;然后在末位加1.

例如: +9 的补码是 00001001 -9 的原码是 10001001 ---> 除符号位外其余各位按位取反11110110 ---> 末位加1得到-9补码 11110111.

已知一个数的补码求原码:

(1) 如果补码的符号位为“0”, 表示是一个正数,所以补码就是该数的原码。

(2)如果补码的符号位为“1”, 表示是一个负数,求原码的操作可以是符号位不变,其余各位取反,然后再在末位加1.

例如,已知一个初码为11111001, 因为符号位为“1”,表示是一个负数,符号位不变,其余7位 1111 001取反后为0000110;再在末位加1,得到原码是10000111(-7)。

注意: 计算机系统中整型数一律采用补码表示(存储),原因在于使用补码,可以将符号位和其他位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进拉,则进位被舍弃。

2.位逻辑运算符

 

 说明:

(1) 运算对象只能是整型或字符型数据。除按位非为单目运算符外,其余均为双目运算符。

(2)参与位逻辑运算时,运算对象以二进制形式进行相应的按位运算。

(3) ~ 优先于 双目算术运算符 优先于 关系运算符 优先于 & 优先于 ^ 优先于 | 优先于 双目逻辑运算符。

(4) ~ 与单目逻辑运算符、自增、自减、单目算术运算符、长度运算符的优先级相同,结合性是从右至左。

【例】 求表达式 3 & 9 的结果。

其计算过程如下:

【例】 求表达式3^9的结果:

其计算过程如下:

【例】 设 unsigned short a=0123, b=0xA2; 求表达式 a^b的十六进制值.

a为无符号八进制整数,对应的二进制数是 0000000001010011

b为无符号十六进制整数,对应的二进制数是 0000000010100010

注意: 运算后,变量a和b的值保持不变。

注意: 位逻辑运算符用于对数据中的二进制位进行测试、置位。

(1)按位与的主要作用是提取(或保留)一个数的某(些)位,其余各位置0.

例如:将二进制数是 00010010 高四位不变,低四位置0。

方法: 00010010 & 11110000 结果: 00010000

(2)按位或的主要作用是将一个数的某(些)位置1,其余各位不变。

例如: 将二进制数是00010010高四位不变,低四位置1

方法:00010010|00001111 结果: 00011111

(3)按位异或的主要作用是使一个数的某(些)位翻转(即原来为1的变为0, 为0的变为1),其余各位不变。

例如:将二进制数是 00010010高四位不变,低四位置反转。

方法: 00010010^00001111 结果: 00011101

(4)按位非的主要作用是间接地构造一个数,以增强程序的可移植性。

3.位移位运算符

 位移规则:

无符号数左移: 左舍右补0

无符号数右移: 左补0右舍

有符号数左移:左舍有补0

有符号数右移:左补符号位右舍

说明:

(1)运算对象只能是整型或字符型数据。

(2)参与位移位运算时,运算对象以二进制形式进行对应的按位运算。

(3)算术运算符 优先于 位移位运算符 优先于 关系运算符

(4) 位移位运算符的优先级相同,结合性一致

【例】 设变量定义如下: unsigned short a=0111; a对应二进制数为 0000000001001001.

1) 表达式 a << 3 运算结果二进制数为0000001001001000,对应八进制数为01110.a不变。

2)表达式 a>>4运算结果二进制数为 00000000000000100, 对应八进制数为04, a 不变。

【例】设变量定义如下 short b=-4; b为带符号十进制数-4, 对应二进制数补码为 1111 1111 1111 1100 。

1) 表达式 b << 3 的运算结果的二进制数为 1111 1111 1110 0000 对应十进制数为-32, b不变

2) 表达式 b >> 4 的运算结果的二进制数为 1111 1111 1111 1111 对应十进制数为 -1, b不变

【例】从键盘输入一个正整数保存到int型变量num, 输出由 8 - 11 位构成的数(从低位、0号开始编号)

算法分析:

1)使变量num右移8位,将原来的8~11位移到低4位上

2)构造一个低4位为1、其余各位为0的整数。

3)与变量num进行控位与运算。

程序代码如下:

#include <stdio.h>
​
int main(void)
{
    int num, mask;
    printf("Input a integer number:");
    scanf("%d", &num);
    num = num >> 8; /*右移使 8 ~ 11 位移到低 4 位*/
    mask = ~(~0 << 4);   /*构造一个低4位为1、其余各位为0的整数*/
    printf("result=0x%x\n", num & mask);
    return 0;
}

4。位复合赋值运算符

 八、长度运算符

长度运算符是单目运算符,长度运算符的使用形式如下: sizeof(数据类型符) 或 sizeof(变量)

说明:

1.与单目算术运算符(+, -)、单目逻辑运算符(~)、自增和自减运算符(++、--)的优先级相同.

2.上述优先级相同的运算符的结合性都是从右至左.

【例】 设int i; short s; unsigned long t; float f; char c;

1) 表达式sizeof(i)运算后的值是4。

2)表达式sizeof(s)运算后的值是2。

3)表达式sizeof(t)运算后的值是4。

4)表达式sizeof(f)运算后的值是4。

5)表达式sizeof(c)运算后的值是1。

6)表达式sizeof(long)运算后的值是4。

7)表达式sizeof(unsigned int)运算后的值是4。

8)表达式sizeof(unsigned short)运算后的值是2。

第五节 数据类型转换

一、自动类型转换

自动类型转换:参与运算的各个数据都转换成数据长度较长的数据类型,然后计算,结果的类型就是数据长度较长的数据类型。即“就高不就低”或“就长不就短”的原则。

横向向左的箭头,表示必须的转换。char型或short型必须转换成int型,float型必须转换成double型; 纵向向上的箭头,表示不同类型一起运算的时候转换方向。

【例】设int i; float f; double d; long e;

则表达式 10 + 'a' + f * i - d/e 计算时的转换过程;

二、赋值类型转换

赋值类型转换: 先将运算结果的数据类型自动转换为左边变量的数据类型,然后再赋予该变量。本质上是“就左不就右”的转换规则。

【例】 设 int a = 1; float b = 2.1; char c = 'a';

则表达式 a = b + c 的计算过程;

1) 将变量c的值是97转换为97.0;

2) 计算 b+c 即97.0 + 2.1, 结果为99.1;

3) 由于变量a是整型,99赋值给变量a。

三、强制类型转换

强制类型转换: 强制改变表达式计算结果的数据类型.

命令格式如下: (数据类型符) (表达式)

例如:

1) (double)a 等价于 (double)(a) /* 将变量a的值转换成double型 */

2) (int)(x+y) /*将x+y的结果转换成int型*/

3)(float) 5/2 等价于 (float)(5)/2 /*将5转换成实型, 再除以2,为2.5*/

4) (float)(5/2) /* 将5整除2的结果2转换成实型2.0 */

第三章 结构化程序设计

第一节 结构化程序设计方法

结构化程序设计: 是以模块功能和处理过程设计为主的详细设计的基本原则。

结构化程序设计方法:

1.自项向下,逐步求精

2.模块化设计: 遵循模块独立性原则,每个模块的功能独立而且明确。C语言中,一个模块对应一个函数。

3.结构化编码:任何程序都由顺序、选择和循环三种基本结构组成,清晰地表示程序的逻辑结构。

第二节 结构化程序三种基本结构

一、顺序结构

顺序结构是程序的一个基本结构,这是按照书写顺序依次执行操作。

二、选择结构

选择结构又称分支结构,是根据某个或某些条件,从若干个操作中选择某个操作执行的一种控制结构。

选择结构分为单分支选择结构,双分支选择结构和多分支选择结构三种。

三、循环结构

循环结构是由循环控制条件控制循环体是否重复执行的一种控制结构。

循环结构分为当型循环结构、直到型循环结构和次数型循环结构三种。

第三节 顺序结构程序设计

概述

顺序结构的程序包括以下几部分:

1.程序开头的编译预处理命令

2.变量类型的说明

3.提供数据的语句

4.运算部分

5.输出部分

本节介绍几种顺序执行的语句: 赋值语句、函数调用语句、表达式语句、复合语句。

一、赋值语句

【格式1】 变量 赋值运算符 表达式;

【格式2】 变量 复合赋值运算符 表达式;

【例】 a=2; s=a+1; s+=2;

二、函数调用语句

1.调用系统函数: 必须在程序清单的开头写上下列命令

#include <头文件名.h>

2.函数调用语句的格式和功能:

【格式】函数名 (参数1,参数2, ……)

【例】 编程序,调用系统函数计算9的平方根、-10的绝对值、sin(3)的值。

#include <math.h>    /*程序中调用了数学函数*/
#include <stdio.h>   /*程序中调用了输入输出函数*/
​
void main(){
    double sqrt1, abs1, sin1;
    sqrt1 = sqrt(9.0);   /*调用系统函数*/
    abs1=fabs(-10.0);
    sin1=sin(3.0);
​
    printf("%f, %f, %f\n", sqrt1, abs1, sin1);
}

三、表达式语句

【格式】 表达式;

【功能】计算表达式的值.

注意: 任何表达式都能构成语句.

四、复合语句

【格式】 {

语句1;

语句2;

.......

语句n;

}

【功能】 依次执行语句1、语句2、... 、语句n。

说明:(1) 复合语句中虽然含有多条语句,但是,整体上应看成一条语句。

(2) 复合语句中的语句若有数据定义语句,要放在复合语名中其它语句的前面。

【例】 编程序计算半径为10的圆周长,圆面积、球表面积、球体积。

#define PI 3.14159   /*定义符号常量PI*/
#define R 10.0       /*定义符号常量R*/
#include <stdio.h>

void circle_ball() {
	double l, s;   /*数据定义语句*/
	l = 2.0 * PI * R;
	s = PI * R * R;
	{
		double v_s, v;   /*复合语句*/
		v_s = 4 * s;
		v = 4.0 * PI * R * R * R / 3.0;
		printf("%f, %f, %f, %f\n", l, s, v_s, v);
	}
}

五、字符输入/输出函数

1.字符输出函数

【格式】 putchar(ch)

【参数】"ch" 可以是一个字符型变量、字符型常量、整型变量或整型常量,也可以是一个转义字符或整型表达式,但不能是字符串。

【功能】将参数ch对应的字符输出到显示器上。

【说明】

(1) putchar(ch) 函数只能用于单个字符的输出,一次输出一个字符。

(2) 若程序中用到putchar(ch), 则在程序的开头加 #include <stdio.h>。

【例】 putchar()函数的调用格式和使用的例子。

#include <stdio.h>
int demo_putchar(void) {
​
    char ch1 = 'N', ch2 = 'E', ch3='W';
        putchar(ch1); putchar(ch2);
    putchar(ch3); putchar('\n');
    putchar(65);
    putchar('B');
    putchar('B' + 1);
    putchar('\n');
    return 0;
}

2.字符输入函数getchar()

【格式】getchar()

【参数】无参数

【功能】从键盘输入一个字符。

通常采用的语句: 变量 = getchar();

说明:

(1) getchar() 函数只能手于单个字符的输入, 一次输入一个字符。输入的数字也是按字符处理。当输入多于一个字符时,只接收第一个字符。

(2)getchar()函数输入的字符可以赋给一个字符变量或整型变量,也可以作为表达式的一部分。

(3)程序中用到getchar(ch), 要在程序的开头加 #include <stdio.h>

【例】编程序输入单个字符,判断是否数字字符,是则输出字符Y,否则输出字符N。

#include <stdio.h>
​
void demo_getchar() {
    char ch, yn;
    ch = getchar();   /*输入一个字符存入变量ch*/
    yn = (ch >= '0' && ch <= '9') ? 'Y' : 'N';   /*判断结果保存到变量yn*/
    putchar(yn); /*输出判断的结果*/
}

六、格式输入/输出函数

1.格式输出函数printf()

【格式】printf(格式控制字符串, 输出项表)

【参数】"格式控制字符串"是由控制输出格式的字符组成的字符串。"输出项表"是用逗号分隔的若干个表达式。

【功能】按照用户指定的格式把指定的数据输出到显示器。

【说明】 “格式控制字符串” 作用是将输出项表中的数据从内存的二进制形式转换成指定的格式输出。格式控制字符串由格式说明符、附加格式说明字符、转义字符和普通字符组成。

【例】格式字符的使用例子。

int fromatPrint(void) {
    int n1 = 248;
    printf("n1=%d, n1=%5d\n", n1, n1);
    return 0;
}

函数常用的格式字符及其含义:

附加格式说明字符:

常用的输出格式:

【例】格式字符d的使用例子

int demao_d(void) {
    int n1 = 123;
    long n2 = 123456;
    printf("n1=%-5d, n1=%2d\n", n1, n1);
    printf("n2=%ld,n2=%8ld\n", n2, n2);
        return 0;
}

【例】格式字符f的使用例子

int demo_f(void) {
    float f = 123.456;
    printf("start new test\n\n");
    printf("%f, %12f, %12.2f\n", f,f,f);
    printf("%-12.2f, %.2f\n", f, f);
    return 0;
}

【例】格式字符c的使用例子

#include <stdio.h>
​
int demo_c(void) {
    char c = 'A';
    int i = 65;
    printf("c=%c, %5c, %d\n", c, c, c);
    printf("i=%d, %c\n", i, i);
    return 0;
}

【例】格式字符s的使用例子

int demo_s(void) {
    printf("%s, %5s, %-10s\n", "Internet", "Internet", "Internet");
    printf("% 10.5s, % -10.s, % 4.5s\n", "Internet", "Internet", "Internet");
    return 0;
}

注意:

(1)printf()中格式说明符与输出项一一对应,若格式说明符的个数少于输出项的个数时,则多余的输出项不输出,若格式说明符的个数多于输出项的个数时,则对缺少的项输出不确定值。

(2)格式字符x、e、g可以用小写字母,也可以用大写字母。若使用大写字母,则输出数据中包含的字母也大写。除了格式字符x、e、g外,其他格式字符必须采用小写字母。例如"%f"不能写成"%F"。

2.格式输入函数

【格式】scanf(格式控制字符串, 输入项首地址表)

【功能】从键盘按照“格式控制字符串”中规定的格式读取若干个数据,按"输入变量地址表"中变量的顺序,依次存入对应的变量。

【参数】 "s格式控制字符串" 是由控制输入格式的字符组成的字符串。“输入项首地址表”由用逗号分隔的若干个输入首地址组成。

【说明】 scanf() 函数的格式说明符的一般形式如下: %[*][m][h|l]格式字符

scanf() 函数常用的格式字符及其含义

scanf() 函数常用的附加格式说明字符及其含义:

注意:

(1)如果相邻两个字符之间,没有指定数据分隔符(如逗号、冒号等),则相应的两个输入数据之间,至少用一空格分开,或者用(Tab)键分开,或者输入一个数据后按回车,然后再输入下一个数据。

例如, int num1, num2;

scanf (“%d%d”, &num1, num2);

正确的输入格式: 12 36 或者 12 <回车> 36 <回车> 或者 12 <tab> 36

(2) "格式控制字符串" 中出现的普通字符(包括转义字符), 务必原样输入。

例如: int num1, num2; scanf("%d, %d", &num1, &num2);

正确的输入格式: 12,36<回车>

scanf("num1=%d,num2=%d", &num1, &num2);

正确的输入格式:num1=12, num2=36 <回车>

(3)在以%d、%f、%lf、%e输入数值型数据时,遇到以下情况,系经认为数据输入结束。

1)遇到空格或回车或<Tab>键时输入结束,可用它们作为数据之间的分隔符。

例如: int num1, num2;

scanf("%d%d", &num1, &num2);

正确的输入格式: 12<空格>36 或 12 <Tab>36 或 12 <回车> 36<回车>

2)遇到宽度结束。例如"%3d"只取3列,即系统自动按域宽截取所需数据。

3)遇到输入数据与格式说明符类型不一致,则输入结束。

例如: int a; char c;

scanf("%d%c", &a, &c);

若从键盘输入12a, 就是a=12, c=‘a’。

4)遇到非法输入结束。例如在输入数值型数据时,遇到字母等非数值符号。

(4) 以"%c"输入单个字符时,空格、回车和转义字符均做为有效字符输入,因此输入的数据之间不需要分隔符。

例如: char ch1,ch2,ch3;

scanf("%c%c%c", &ch1,&ch2,&ch3);

若从键盘输入 A<空格>B<空格>C<回车>

结果:ch1='A', ch2=' ', ch3='B'

注意:

若从键盘输入ABC<回车> 结果: ch1=‘A’, ch2=‘B’, ch3=‘C’

(5)输入实数时不能规定精度。

例如:float x; scanf("%7.2f", &x); 是错误的。

(6) 赋值抑制字符 *。赋值抑制字符表示本输入项对应的数据读入后,不赋予相应的变量

例如: int num1, num2; scanf("%2d%*2d%3d", &num1, &num2);

输入 123456789<回车> 结果: num1=12, num2=567

常用的输入格式:

【例】输入一个十进制整数,输出该整数对应的八进制数和十六进制数。

int input_test(void) {
    int n;
    scanf_s("%d", &n); //输入一个十进制数
    printf("n_O=%o\n", n); //输出n的八进制数
    printf("n_x=%x\n", n); //输出n的十六进制数
    return 0;
}

【例】从键盘输入小写字母转换成大写字母并输出.

int covert_letter(void) {
    char c1, c2;
    printf("Input a lowercase  letter:");
    c1 = getchar();  //从键盘输入一个字符
    putchar(c1);
    printf(", %d\n", c1);
    c2 = c1 - 32; //小写字母转换成大写字母
    printf("%c, %d\n", c2, c2);
    return 0;
}

ASCII 数字对应: '0' ----> 48 '1'---->49

第四节 选择结构的程序设计

一、单分支选择语句

【格式】 if (表达式) 语句;

【功参】 计算表达式的值,如果为真(非0),则执行语句;否则不执行语句。

【说明】 (1) 表达式可以是任何类型是, 常用的是关系表达式或逻辑表达式。

(2) 语句可以是任何语句(不能是数据定义语句), 当然也可以是另一个if语句(称嵌套if语句)。

【例】从键盘输入一个整数,求其绝对值并输出.

int main(void) {
    int n;
    printf("input a number: ");
    scanf_s("%d", &n);
    if (n < 0) n = -n;
    printf("|n| = %d\n", n);
    return 0;
}

二、双分支选择语句

【格式】 if (表达式) 语句1; else 语句2;

【功能】 计算表达式的值, 如果为真(非零) 则执行语句1,否则执行语句2.

【例】从键盘输入一个整数,判断其奇偶。

#include <stdio.h>
​
int geteven(void) {
    int n;
    scanf_s("%d", &n);
    if (n % 2 == 0) {
        printf("this number is even\n");
    }
    else
    {
        printf("this number is odd\n");
    }
    return 0;
}

【例】 从键盘输入一个年份year(4位十进制整数),判断其是否是闰年(闰年的判断条件是能被4整除,但是不能被100整除,或者能被400整除)。

#include<stdio.h>
int leapyear(void) {
    int year;
    printf("Please input the year: ");
    scanf_s("%d", &year); 
    if ((year % 4 == 0 && year % 100 == 0) || (year % 400 == 0)){
        printf("%d is a leap year.\n", year);
    }
    else {
        printf("%d is not a leap year.\n", year);
    }
    return 0;
}

三、分支的嵌套

1.if语句的嵌套

if (表达式1)

if (表达式2) 语句1

else 语句2

else

if(表达式3) 语句3

else 语句4

2.if - else - if 语句的嵌套

if (表达式1) 语句1 ;

else if (表达式2) 语句2;

else if (表达式3) 语句3;

......

else If (表达式n) 语句n;

else 语句 n+1;

【例】 求一元二次方程 ax²+bx + c = 0的解;

判断一个实数是否小于等于0用: fabs(0) <= 1e-6 不能用 a==0

#include <stdio.h>
#include <math.h>
​
int quadratic(void) {
    double a, b, c, d, x1, x2, p, q;
    printf("input a, b, c = ?");
    scanf_s("%f, %f, %f", &a, &b, &c);
    if (fabs(a) <= 1e-6) printf("is not quadratic");  /*判断实数a等于0的方法,不能用 a== 0*/
    else {
        d = b * b - 4 * a * c;
        if (fabs(d) < 1e-6)  /*有两个相等实根*/
            printf("has two equal roots: %8.4f\n", b / (2 * a));
        else 
            if (d > 1e-6)   /*有两个不相等实根*/
            {
            x1 = (-b + sqrt(d)) / (2 * a);
            x2 = (-b - sqrt(d)) / (2 * a);
            printf("x1=%8.4f, x2=%8.4f\n", x1, x2); 
            }
            else {
                p = b / (2 * a);
                q = sqrt(-d) / (2 * a);    /*实部和虚部*/
                printf("x1=%8.4f+%8.4f\n", p, q);
                printf("x2=%8.4f-%8.4f\n", p, q);
            }
    }
    return 0;
}

注意:在设计嵌套的“if语句”和"if-else语句"时,要特别注意else是和前面出的的哪个if配对.

程序段中的else是和前面最近的没有配对的if配对。 复合语中的if-else独立处理.

四、多分支选择语句

【格式】 switch (表达式)

{

case 常量表达式1: 语句组1; break;

case 常量表达式1: 语句组2; break;

.........

case 常量表达式1: 语句组n; break;

default: 语句组 n + 1; [break]

}

【说明】

(1)switch后面的表达式常用的是字符型或整型表达式。

(2)常量表达式是由常量或符号常量组成的表达式。所有常量表达式值必须互不相同。

(3)break在switch语句中的作用是: 执行完某个语句组后,将退出该switch语句。如果省略了break语句,则执行完某个语句组后,将连续执行其后的所有语句组,直到遇到break或多分支语句结束。

(4) default及其后的语句组可以省略,省略时,若表达式的值和n个常量表达式值均不相同,则该语句什么也不做。

【例】从那般输入年和月,输出该月的天数。

#include <stdio.h>
int getdays(void) {
    int year, month, days=0;
    printf("input year, month?\n");
    scanf_s("%d, %d", &year, &month);
    switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12: days = 31; break;
    case 4:
    case 6:
    case 9:
    case 11: days = 30; break;
    case 2: if ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0))
        days = 29;   /*闰年*/
          else days = 28; 
        break;
    default: printf("month is error\n");
    }
    printf("year=%d, month=%d, days=%d\n", year, month, days);
    return 0;
}

第五节 循环结构程序设计

一、while语句

while语句用来实现当型循环结构

【格式】 while (表达式)

语句:

【功能】计算表达式值,为真(非0)则执行语句;反复执行上述操作,直到表达式值为假(0)时止。

【说明】 (1) 表达式称为"循环控制条件",可以是任何类型的表达式,常用关系或逻辑表达式。

(2) 先判断后执行循环体。即表达式不成立时,循环体最小执行0次。

(3) 循环控件条件永远成立时,是死循环。如 while(1)

【例】 利用while语句实现计算1+2+……+100之和。

#include <stdio.h>
​
int getsum(void) {
    int n = 1, sum = 0;
​
    while (n <= 100) {
        sum += n;
        n++;
    }
    printf("sum=%d\n", sum);
    return 0;
}

二、do-while语句

do-while用来实现直到型循环结构

【格式】 do 语句;

while (表达式);

【功能】先执行语句,然后计算表达式的值; 若表达式的值为假(0)则退出循环,表达式的值为真(非0)继续循环。

【说明】先执行循环体,再判断循环控制条件。适用于无论循环控制条件是否成立,先执行一次循环体的情形。循环体最少执行1次。

【例】利用do-while语句实现计算1+2+...+100之和

#include <stdio.h>
int sumdemo(void) {
    int n = 1, sum = 0;
​
    do {
        sum += n;
        n++;
    } while (n <= 100);  /*该句末尾必做有分号*/
​
    printf("sum=%d\n", sum);
    return 0;
}

【例】 将一个正整数的各位数字逆序输出。

臬法分析: 提取某一正整数的最末一位数字,采用取模10的余数获得,依此类推即可。

#include <stdio.h>
int reverse(void) {
    int i, r;
    printf("Input an integer\n");
    scanf_s("%d", &i);
    do {
        r = i % 10;
        printf("%d", r);
    } while ((i/=10)!=0);
​
    printf("\n");
    return 0;
}

三、fpr语句

for语句主要用来实现次数型循环结构。

【格式】 for (表达式1;表达式2;表达式3)

语句;

【功能】

(1) 计算表达式1, 实现变量赋初值

(2) 计算表达式2, 如果其值非0,则执行(3); 否则转至退出for循环.

(3) 执行语句,并计算表达式3,然后转至(2).

【说明 】

(1) 表达式1可以是任何类型的。注意表达式1后面有一个分号。表达式1可以省略。

(2) 表达式2称为“控制循环的条件”,可以是任何类型,常用关系或逻辑表达式。注意表达式2后面有一个分号。表达式2可以省略,省略时,是“死循环”。

(3)表达式2可以是任何类型的。注意表达式3后面有分号。表达式3可以省略。

(4)表达式1和3都省略, for 循环相当于while循环。

【例】计算n的阶乘n!=1×2×...×n。

int step(void) {
    int n;
    long fact = 1;
​
    printf("please input a number:");
    scanf_s("%d", &n);
​
    for (int i = 1; i <= n; i++) {
        fact = fact * i;
    }
​
    printf("fact=%d\n", fact);
    return 0;
​
}

【例】 从键盘上输入若干个整数,求其中最大数和最小数。直到输入的数为0时结束。

#include <stdio.h>
​
int getmaxandminnumber(void) {
​
    int currennumber, max, min;
    printf("please input some number, until 0 end\n");
    scanf_s("%d", &currennumber);
    max = min = currennumber;
    for (; currennumber != 0; )
    {
        if (currennumber > max) {
            max = currennumber;
        }
​
        if (currennumber < min) {
            min = currennumber;
        }
        scanf_s("%d", &currennumber);
    }
​
    printf("max=%d, min=%d\n", max, min);
    return 0;
}

四、break语句和continue语句

1.break语句

【格式】 break;

【功能】强制结束本层的循环,不再执行循环体中break后面的语句;

【说明】

(1) break语句只能用在三条循环语句的循环体中或switch语句中。

(2)通常break语句是和if语句配合使用,从而构成第二个结束循环的条件。

2.continue语句

【格式】continue;

【功能】跳过continue之后的语句,重新判断循环控制条件,决定是否继续循环。

【说明】

(1) continue语句只能用在三条循环语句的循环体中。

(2)通常continue语句是和if语句配合使用,从而构成其后的部分循环体是否执行的单分支结构。

【例】从键盘上输入不多于10个实数,求这些数的总和及其中正数的总和。若不足10个数,则以输入0作为结束。

int sumdemoten(void) {
    float sum, psum, x;
    int i;
    for (sum = psum = 0.0, i = 0; i < 10; i++) {
        scanf_s("%f", &x);
        if (fabs(x) < 1e-6) {   /*若输入实数为0*/
            break;
        }
        sum += x;            /*所以数的和*/
        if (x < 0) {         /*输入数是负数*/
            continue;
        }
        psum = psum + x;     /*正数的和*/
    }
​
    printf("sum=%f, psum=%f\n", sum, psum);
}

五、循环的嵌套

循环的嵌套:循环语句的循环体内又包含另一个完整的循环结构。

循环嵌套的层数是任意的。

内层循环必须完整地包含在外层循环的循环中,不能出现内、外层循环体交叉的情况。

for语句、while语句和do-while语句都允许循环嵌套。

【例】用100元钱买100只鸡,其中公鸡5元一只,母鸡3元一只,小鸡1元3只,问公鸡、母鸡和小鸡各买多少只?

【算法分析】看到题目后,大家很容易想到列方程。设公鸡、母鸡和小鸡分别买x、y、z只,则有:

x + y + z = 100

5x + 3y + z/3 = 100

3个末知数,只能列出两个方程,显然不能解出答案。通过C语言编程可很容易求出结果。

#include <stdio.h>
int buy(void) {
    int x, y, z;
    for (x = 0; x < 20; x++) {
        for (y = 0; y < 33; y++) {
            z = 100 - x - y;
            if (z % 3 == 0 && 5 * x + 3 * y + z / 3 == 100) {
                printf("x=%d, y=%d, z=%d\n", x, y, z);
            }
        }
    }
    return 0;
}

【例】求Fibonacci数列的前40个数。

【算法分析】 Fibonacci数列的生成公式是

即从第三个数开始,每个数等于前两个数之和。

#include <stdio.h>
​
int fibonacci(void) {
    long int f1 = 1, f2 = 1;
    int i = 1;
​
    for (; i <= 20; i++) {
        printf("%15ld%15ld", f1, f2);
        if (i % 2 == 0) {
            printf("\n");
        }
        f1 += f2; f2 += f1;
    }
​
    return 0;
}

【例】计算1! + 2! +.... + n!

#include <stdio.h>
int multiply(void) {
    int n = 0, i = 0, j = 0;
    long result = 0;
    printf("Entern n:");
    scanf_s("%d", &n);
    for (i = 1; i <= n; i++) {
        int tem = 1;
        for (j = i; j > 0; j--) {
            tem = tem * j;
        }
​
        result = result + tem;
    }
​
    printf("the result is %ld\n", result);
    return 0;
}

第四章 数组

概述

第一节 一维数组

一、一维数组的定义

【格式】 数据类型符 数组名1[长度1], 数组名2[长度2], ...;

例如: int a[6], b[5];

【说明】

1.数据类型符: 指的是数据元素的类型,可以是基本数据类型,也可是构造数据类型j。

2.数组名定名规则和变量名相同,遵循标识符定名规则

3.数组名后是用方括弧括起来的常量表达式,不能用圆括弧,下面用法是错的: int a(6).

4.长度即数组长度,是一个“整型常量表达式”,通常是一个整型常量,表示元素的个数。

例如,在a[6]中,a数组有6个元素,下标从0开始,这6个元素是: a[0]...a[5].注意不能使用数组使用数组元素a[6]

5.常量表达式中可以包括常量和符号常量,不能包含变量。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。

例如,设int n =6, s[n]; 是错语的,因为n是变量。

6.一个数组定义语句中可以只定义一个数组,也可以定义多个数组,还可以同时定义数组和变量。

例如, int a[10], b[20], m, n;

二、一维数组元素的引用

一维数组元素的引用方法如下: 数组名[下标]

【说明】

1."下标"可以是一个整型常量、整型变量或整型表达式,其合法值的范畴是0~长度-1。

特别强调:在运行C语言程序过程中,系统并不自动检验数组元素的下标是否越界。因此在编写程序时,保证数组下标不越界十分重要的。

例: short y[2]; /* 数组y合法的下标为0、1*/

y[2]=10; /* 将会破坏y[1]后面2个字节的原有数据*/

2.一个数组元素,本质上就是一个变量。数组元素参与表达式运算,则必须已被赋值。

3.在C语言中,数组作为一个整体,不能参加数据运算,只能对单个的元素进行处理。

4.当定义了某个一维数组,则要给其所有的元素分配内存单元。单元数目=数组长度 * 每个元素占用的字节数,而且,这此内存单元是连续的,并按照元素顺序依次排列。

5.C语言还规定,数组名是数组的首地址。

例如, int a[3]; 则一组数组a所占用的内存字节数= 3 * 2 = 6字节。

三、 一维数组的初始化

一维数组初始化的一般格式如下:

数据类型符 数组名[长度] = {初始表}, ... ;

【说明】

1.对数组所有元素赋初值,此时数组定义中数组长度可以省略。

例如: int a[5] = {1, 2, 3, 4, 5}; 或 int a[] = {1, 2, 3, 4, 5};

2.对数组部分元素赋初值,此时数组长度不能省略。

例如: int a[5] = {1,2};

a[0]=1, a[1]=2, 其余元素为编译系统指定的默认值0。

例如: char c[5] = {'a', 'b'};

c[0]='a', c[1]='b', 其余元素为编译系统指定的默认值'\0'

3.对数组的所有元素赋初值0。

例如: int a[5]={0};

注意: 如果不进行初始化, 如定义int a[5];那么数组元素的值是随机的,不要指望编译系统为你设置为默认值0.

4.static int a[5] 不进行初始化,编译系统为你将所有元素设置为默认值0;

static chars[5]不进行初始化,编译系统为你将所有元素设置为默认值‘\0’.

四、一维数组的程序设计实例

【例】 从键盘上输入10个字符,然后按相反次序输出。

#include <stdio.h>
int output(void) {
    char a[10];
    int i;
    for (i = 0; i < 10; i++) {  /*输入字符,存入数组a中*/
        scanf_s("%c\n", &a[i]);
    }
​
    for (i = 9; i >= 0; i--) { /*按相反次序输出*/
        printf("%c, ", a[i]);
    }
​
    printf("\n");
}

【例】求斐波拉契数列的前20项值并输出。斐波拉契数列是如下的整数序列: 1, 1,2,3,5,8,13,21...

斐波拉契数列中数的定义如下: f(0)=1, f(1)=1, f(n)=f(n-2) + f(n-1)

【算法分析】 用长度为20的整型数组f存放斐波拉契数列的前20项的值。并给f[0]、f[1]赋初值为1。然后用次数型循环求斐波拉契数列后面的18项值并存入数组元素f[2]~f[19].控制循环的变量n依次等于2、3、... 19并作为数组元素的下标。

#include <stdio.h>
​
int fibo2(void) {
    int n, f[20] = {1,1}; /*前两项赋初值为1, 1*/
    for (n = 2; n < 20; n++) {   /*计算第3-第20项的值*/
        f[n] = f[n - 2] + f[n - 1];
    }
​
    for (n = 0; n < 20; n++) {    /*依次输出前20项的值*/
    
        if (n % 5 == 0) {
            printf("\n");          /*每行输出5个*/
        }
        printf("%10d ", f[n]);   /*宽度10,右对齐*/
    }
    return 0;
}

【例】用"冒泡排序法"对一维数组中前n个整数按从小到大进行排序。

【算法分析】 假定要对13、10、12、6和8共5个数据按从小到大顺序重新排列.冒泡排序法的排序过程如下图所示。

void b_sort(void) {
    int n, i, j, k, temp, a[10] = {91,25,46,81,54,66,35,15,5,10};
    scanf_s("%d", &n);                /*输入要求排序数的个数*/
    printf("please input %d numbers:\n", n);
    for (k = 0; k < n; k++)             /*输入n个要求排序的数*/
    {
        scanf_s("%d\n", &a[k]);
    }
​
    for (i = 0; i < n - 1; i++) {
        for (j = n - 1; j > i; j--) {
            if (a[j] < a[j - 1]) {
                temp = a[j];
                a[j] = a[j-1];
                a[j-1] = temp;
            }
        }
    }
​
    for (k = 0; k < n; k++) {
        printf("%d, ", a[k]);
    }
    printf("\n");
}

【例】利用"选择排序法" 对一维数组的10个整数从小到大排序。

【算法分析】 选择排序的过程:

第1趟排序:在第1~n个数中找出最小数,然后和第1个数交换,前1个数排好序。

第2趟排序,在第2~n个数中找出最小数,然后和第2个数交换,前2个数排好序。……

第n-1趟排序,在第n-1~n个数中找出最小数,然后和第n-1个数交换,排序结序。 select sort

#include <stdio.h>
​
int select(void) {
    int i, j, temp, k, n, a[100];
    scanf_s("%d\n", &n);
    for (i = 0; i < n; i++) { /*输入特排序的数据*/
        scanf_s("%d\n", &a[i]);
    }
​
    for (i = 0; i < n - 1; i++) {  /*控制n-1轮排序*/
        k = i;
        for (j = i + 1; j < n; j++) {   /*每轮进行比较的次数*/
            if (a[j] < a[k])
            {
                k = j;                  /*k每轮比较中的最小者的下标*/
            }
        }
​
        if (k != i) {
            temp = a[i];
            a[i] = a[k];
            a[k] = temp;
        } /*将第i轮的最小者与a[i]交换*/
    }
​
    printf("the sorted numbers:\n");
    for (i = 0; i < n; i++) {
        printf("%d, ",a[i]);   /*输出结果*/
    }
    return 0;
}

第二节 二维数组

一、二维数组的定义

【格式】数据类型 数组名 [行长度] [列长度],...; 例如: int a3;

【说明】

1.二维数组的存放规律是“按行排列”,即先顺序存放第一行的元素,再存放第二行,以此类推

2.一个二维数组可以看成若干个一维数组。二维数组a[3][4]理解为:有三个元素a[0]、a[1]、a[2]每一个元素是一个包含4个元素的一维数组。

二、二维数组元素的引用

【格式】 数组名【行下标】【列下标】

【说明】

1.行下标和列下标取值范围分别是: 0~(行长度-1)、0~(列长度-1).

下标越界会造成运行结果不可预料的问题。

2.二维数组元素可以看作一个变量来使用。

三、二维数组的初始化

1.分行给二维数组所有元素赋初值。

例如:int a[2][3]={{1,2,3},{4,5,6}};

2.不分行给多维数组所有元素赋初值。

例如: int a[2][3]={1,2,3,4,5,6};

3.只对每行的前若干个元素赋初值, 此时所有末赋值的数组元素均获得零值(对整型数组是0;对实型数组是0.0;对字符型数组是‘\0’).

例如: int a[2][3] = {{1},{4,5}};

4.只对前若干行的前若干个元素赋初值,此时所胡末赋初值的数组元素均获得零值。

例如: char a[2][3] = {{'1', '2'}};

5.若给所胡元素赋初值,行长度可以省略。

例如: float a[][3]={{1,2,3}, {4,5,6}}; float a[][3]={1,2,3,4,5,6};

自动认为数组a[][3]的行长度是2。

注意:允许给出的初值不是列长度的整数倍。此时,行长度 = 初值个数整除列长度后再加1。

例如: float a[][3]={1,2,3,4,5,6,7}; 则省略的行长度= 7/3 + 1 = 2 + 1 = 3。

6.若分行给所有行的前若干个元素赋初值,行长度可以省略。

例如: float a[][3]={{1}, {4,5}}; 系统自动认为数组a[][3]的行长度是2。

【例】 从键盘输入一个3行4列的矩阵,将其转置后形成4行3列矩阵输出。矩阵的转置是将原矩阵的行和列互换。

【算法分析】 将矩阵X存放在3行4列的二维数组中,矩阵Y存放在4行3列的二维数组中,利用双层循环完成矩阵的转置。

#include <stdio.h>
​
int matrix(void) {
​
    int i, j, x[3][4], y[4][3];
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            scanf_s("%d\n", &x[i][j]);  /*输入二维数组x*/
        }
    }
​
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            y[j][i] = x[i][j];   /*矩阵转置*/
        }
    }
​
    for (i = 0; i < 4; i++) {  /*输出数组x转置后的二维数组y*/
        for (j = 0; j < 3; j++) {
            printf("%d ", y[i][j]);
        }
        printf("\n");
    }
    return 0;
}

【例】已知M个学生的N门课程的成绩,分别计算每位学生的平均成绩和每门课程的平均成绩并输出。

#include <stdio.h>
#define M 5 /*定义符号常量,学生人数为5*/
#define N 4 /*定义符号常量,课程为4门*/
​
int score(void) {
    int i, j;
    float score[M][N] = { {78,85,83,65},{88,91,89,93},{72,65,54,75},{86,88,75,60},{69,60,50,72}};
    float stu_ave[M] = { 0 };     /*存放每位学生的平均成线*/
    float cla_ave[N] = { 0 };    /*存放每门课程的平均成绩*/
    for (i = 0; i < M; i++) {
        for (j = 0; j < N; j++) {
            stu_ave[i] += score[i][j];    /*计算第i个学生N门课程的总成绩*/
            cla_ave[j] += score[i][j];    /*计算第j门课的总成绩*/
        }
        stu_ave[i] /= N; /*计算第i个学生的平均成绩*/
    }
}#include <stdio.h>
#define M 5 /*定义符号常量,学生人数为5*/
#define N 4 /*定义符号常量,课程为4门*/
​
int score(void) {
    int i, j;
    float score[M][N] = { {78,85,83,65},{88,91,89,93},{72,65,54,75},{86,88,75,60},{69,60,50,72}};
    float stu_ave[M] = { 0 };     /*存放每位学生的平均成线*/
    float cla_ave[N] = { 0 };    /*存放每门课程的平均成绩*/
    for (i = 0; i < M; i++) {
        for (j = 0; j < N; j++) {
            stu_ave[i] += score[i][j];    /*计算第i个学生N门课程的总成绩*/
            cla_ave[j] += score[i][j];    /*计算第j门课的总成绩*/
        }
        stu_ave[i] /= N; /*计算第i个学生的平均成绩*/
    }
​
    for (j = 0; j < N; j++) {
        cla_ave[j] /= M;
    }
    printf("学生编号  课程1  课程2   课程3  课程4  个人平均\n");
    for (i = 0; i < M; i++) {
        printf("Student%d\t", i + 1); /*输出学生编号*/
        for (j = 0; j < N; j++) {
            printf("%.1f   ", score[i][j]);   /*输出四门课程成绩*/
        }
        printf("%.1f\n", stu_ave[i]);     /*输出个人平均成绩*/
    }
    printf("\n课程平均");
    for (j = 0; j < N; j++) {
        printf("%.1f  ", cla_ave[j]);
    }
    printf("\n");
    return 0;
}

第三节 字符数组与字符串

概述

字符数组是存放字符型数据的数组,每个数组元素存放一个字符。

字符串是由若干个字符组成的,其最后一个字符是字符串的结束标志符“\0”。

C语言中没有字符串变量,一个字符串可以用一个一维字符数组存放;多个字符串用多个一维字符数组存放,也可以用一个二维字符数组存放,每行存入一个字符串。

一、字符数组

1.字符数组的定义和初始化

(1) 一维字符数组定义: char 数组名 [数组长度];

【例】 char c[10];

(2) 一维字符数组初始化: char 数组名[数组长度]=[初值表];

①用字符初始化字符数组。 【例】 char str[5]={'C', 'h', 'i', 'n','a'}; 【例】char str[8] ={'p','r','o','g','r','a','m','\0'};

②用字符串初始化字符数组。 【例】 char str[8]={"program"}; 或 char str[8]="program";

③用ASCII码初始化字符数组。 【例】 char str[8] = {112, 114,111,103,114,97,109,0};

④初始化时如果只提供了部分元素的值,未提供初值的元素自动赋值为'\0'。

【例】char str[8] ={'p','r','o','g','r','a','m','\0'}; 【例】 char str3[10]={"program"};

注意:上面的字符数组str3,其数组长度是10,但里面存放的字符串的长度是7。

⑤所有元素均赋初值可省略数组长度。

【例】 char str[]={'p','r','o','g','r','a','m'}; 数组长度为7

【例】char str[] = {"program"}; 数组长度为8

(3) 二维字符数组定义

char 数组名[行长度][列长度]; 【例】char s[5][6];

(4)二维字符数组的初始化

①用字符初始化二维字符数组 【例】 char s[2][10] = {{’c','o','m','p','u','t','e','r‘}, {'s','c','i','e','n','c','e'}};

②用字符串初始化二维字符数组。 char s[2][10] = {"computer", "science"};

【例】 从键盘输入一串字符,以'\n'结束,将该字符串原样输出并统计其字符个数。

#include <stdio.h>
​
int outputchar(void) {
    int i, j = 0;
    char str[80] = {'\0'};
​
    for (i = 0; i < 90; i++) {
        str[i] = getchar();     /*依次输入字符到数组str*/
        if (str[i] == '\n') {
            break;              /*若输入'\n'则终止循环*/
        }
        j++;    /*统计字符的个数*/
    }
​
    i = 0;
    while (str[i] != '\0') {
        printf("%c", str[i++]);    /*依次输出数组str的元素*/
    }
​
    printf("%d\n", j);   /*输出字符个数*/
    return 0;
}

2.字符数组的输入和输出

(1)一维字符数组的输入

①使用scanf("%c", 一维字符数组元素) 逐个字符输入。 【例】 int i; char s[5];

for(i=0; i < 5; i++) scanf("%c", &s[i]);

②使用scanf("%s", 一维字符数组名) 整个字符串输入 scanf("%s", &s);

③使用gets(一维字符数组名)整个字符串输入gets(s);

(2)一维字符数组的输出

①作用printf("%c", 一维字符数组元素)逐个字符输出。 【例】 int i; char s[5]; for(i=0; i < 5; i++) printf("%c", s[i]);

②使用printf("%s", 一维字符数组名) 整个字符串输出 printf("%s", s);

③使用puts(一维字符数组名)整个字符串输出 puts(s);

(3) 二维字符数组的输入

二维字符数组的每一行可以看作一个一维字符数组

① 使用scanf("%s", 一维字符数组名)逐个字符串输入 【例】 int i; char s[3][5]; for(i=0; i<3;i++) scanf("%s", s[i]);

②使用gets(一维字符数组名)逐个字符串输入 for(i=0; i < 3; i++) gets(s[i]);

(4)二维字符数组的输出

①使用printf("%c", 数组元素)逐个字符输出

【例】 int i, j; char s[3][5];

for(i=0; i<3;i++)

{ for(j=0; j < 5;j++){

printf("%c", s[i][j]);

}

printf("\n");

}

②使用printf("%s", 一维数组数组名) 逐个字符串输出

for(i = 0; i < 3; i++) printf(s[i]);

③使用puts(一维字符数组名)逐个字符串输出 for (i = 0; i < 3; i++) puts(s[i]);

二、字符串

1.字符串的定义和初始化

字符串是指若干有效字符的序列,可以包括字母、数字、专用字符和转义字符等。

字符串的初始化:

【例】 char str[8] = {'p','r','o','g','r','a','m','\0'};

char str[8] = {"program"};

char str[8] = ""program;

char *str = "program";

2.字符串的输入和输出

和前面讲解的字符数组的输入方法一样。

【说明】 (1) 使用scanf("%s", 一维字符数组名) 输入字符时, 系统会自动在字符串末尾加一个'\0'。

(2) 使用scanf('%s', 一维字符数组名)输入字符串时,遇到空格或回车则认为字符串输入结束。

例如: char s1[10], s2[10]; scanf("%s%s", s1, s2);

从键盘输入C Language<回车>, 则"C"后面添加一个'\0'后存入字符数组s1, “Language”后面添加一个'\0'后存入字符数组s2.

(3)使用printf("%s", 一维数组数组名)输出字符串时,一旦遇到'\0'则结束输出('\0'不输出),其后字符不再输出。

【例】 char str[10] = "123\0ab\0ef"; print("%s\n", str);

则执行后输出结果是123。

三、字符串处理的常用库函数

1.字符串输入函数

【调用格式】gets(字符数组名)

【参数】 字符数组名是已定义的字符数组名。

【功能】从键盘接受一个字符串(仅以回车换行符为结束标记),存入指定的字符数组中。

【返回值】字符数组的首地址。

【说明】 对于gets()函数,只有"回车换行符"才看成输入字符串的结束, “空格符”看成字符串的一部分。对于scanf(“%s”)函数, “回车换行符”或“空格符”都看成输入符串的结尾。

【例】使用gets()函数和scanf()函数输入字符串的不同之处举例。 (需要验证)

#include <stdio.h>
void getsdemo() {
    char s[4][100];
    gets(s[0]);
    gets(s[1]);
    scanf_s("%s%s",s[2], s[3]);
    printf("%s\n%s\n%s\n%s\n", s[0], s[1], s[2], s[3]);
}

2.字符串输出函数

【调用格式】 puts(字符数组名)

【参数】 字符数组名已存放字符串的字符数组名。

【功能】输出字符数组中存放的字符串,其中结束标记转换成回车换行符输出。

【说明】 puts()函数和使用"%s"格式的printf()函数都可以输出字符串,但输出时有区别:对于printf()函数,“字符串结束标记符”不输出;对于puts()函数,“字符串结束标记符”转换成“回车换行符”输出。

【例】使用puts()函数和printf()函数输出字符串的不同之外举例。

#include <stdio.h>
void pustsdemo() {
    char s1[100] = "123", s2[100] = "xyza";
    puts(s1);
    puts(s2);
    printf("%s%s", s1, s2);
}

输出如下:

123 ‘\0’转换成“回车换行符”输出

xyza

123xyza "字符串结束标记符"不输出

3.字符串比较函数

【调用格式】 strcmp(字符串1, 字符串2)

【参数】字符串1和这字符串是字符串常量或已存放字符串的字符数组名。

【功能】若字符串1等于字符串2,返回值为0;若字符串1小于字符串2,返回值为负整数;若字符串1大于字符串2,返回值为正整数。

【说明】不能使用关系运算符“==”比较两个字符串,只能调用strcmp()函数进行处理。

【例】printf("%d,%d,%d", strcmp("net", "Net"), strcmp("net", "net"), strcmp("ne", "ntw"));

则语句执行后输出结果是1, 0 , -1.

注意: if (str1 > str2) 是错误的,正确做法是:

if (strcmp(str1, str2) > 0) if(strcmp(str1, str2) < 0) if(strcmp(str1, str2) == 0)

4.字符串复制函数

【调用格式】strcpy(字符数组名, 字符串, 整型表达式)

【参数】字符数组名已定义的字符数组名;字符串是字符串常量或已存放字符串的字符数组名,整型表达式是任何整型表达式。本参数可以省略。

【功能】将“字符串”的前"整型表达式"个字符组成新的字符串存“字符数组”中。若省略“整型表达式”,则将整个“字符串”存入字符数组中。

【返回值】字符数组的首地址。

【说明】

(1) 字符数组的长度要足够大。以便容纳复制过来的字符串。复制时,连同字符串结束标志'\0'一起复制。

(2)不能用赋值运算符"="将一个字符串直接赋值给一个字符数组,只能调用strcpy()函数thgj。

【例】 char str1[10]={"internet"}, str2[]={"net"}; strcpy(str1, str2);

5.字符串连接函数strcat()

【调用格式】 strcat(字符数组名, 字符串)

【参数】“字符数组名”是已经定义的存入字符串的字符数组名。“字符串”可以是字符串常量,也可以是已经存入字符串的字符数组名。

【功能】把"字符串"连接到“字符数组”中字符串的尾端(最后一个有效字符的后面),组成新的字符串并存储到"字符数组"。“字符数组”中原来的结束标志,被“字符串”的第一个字符覆盖,而“字符串”在操作中未被修改。

【返回值】字符数组的首地址。

【说明】

(1)注意保证字符数组长度足够大,以便容纳连接后的新字符串。

(2)连接前两个字符串都有结束标志‘\0’, 连接后字符数组中存储的字符串结束标志‘\0’被覆盖,只在新字符串的最后保留一个'\0'。

【例】 char str1[10] = {"Inter"}, str2[]={"net"}; strcat(str1, str2);

6.测试字符串长度函数

【调用格式】 strlen(字符串)

【参数】字符串 字符串常量或已存放字符串的字符数组名。

【功能】测试指定字符串的长度(除字符串结束标记外的所有字符的个数)。

【返回值】字符串的长度。

【例】 对于以下字符串,strlen(s)的值为;

(1) char s[10] = {'A', '\0', 'B', 'C', '\0', 'D'} 1

(2)char s[] = "\t\v\\0will\n"; 3

(3)char s[]="\x69\082\n"; 1

7.字符串大写字母转换成小写函数strlwr()

【调用格式】 strlwr(字符串)

【参数】“字符串” 可以是字符串常,也可以是已经存放字符串的字符数组名。

【功能】将字符串中的大写字母转换成小写字母,其他字符(包括小写字母和非字母字符)不转换。

8.字符串小写字母转换成大写函数strupr()

【调用格式】 strupr(字符串)

【参数】 “字符串”可以是字符串常量,也可以是存放字符串的字符数组名。

【功能】将字符串中的小写字母转换成大写字母,其它字符(包括大写字母和非字母字符)不转换。

例: char str1[10] = {"Internet"}, str2[10]={"Internet"};

strlwr(str1); strupr(str2);

printf("%s\n%s\n", str1, str2);

则执行后输出结果是: internet ITERNET

【例】输入一个仅由数字和英文字母组成的字符串,依次取出字符串中所有英文字母形成新的字符串,并取代原字符串。

#include <stdio.h>
​
int arraydemo(void) {
    char str[80];
    int i = 0, j = 0;
    printf("Enter a string:");
    scanf_s("%c", str);   /*从键盘输入一字符串*/
​
    for (i = 0; str[i] != '\0'; i++) {
        if ((str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z'))    /*判断是否是英文字母*/
        {
            str[j++] = str[i];   /*是英文字母,前移*/
        }
    }
    str[j] = '\0';
    printf("the string fo changing is %s\n", str); /*输出新的字符串*/
    return 0;
} 

【例】统计子串在字符串中出现的次数。

#include <stdio.h>
int substring(void) {
    char str[80], substr[10];
    int i = 0, j = 0, num = 0;
    printf("Enter a string:");
    gets(str);   /*输入主字符串*/
    printf("Enter the substring:");
    gets(substr);
    for (i = 0; str[i] != '\0'; i++) {
        int k = 0;
        for (j = i; str[j] == substr[k] && str[j] != '\0'; k++, j++) /*内层循环, 逐个比较字符串中的字符与子串中的字符*/
        {
            if (substr[k + 1] == '\0') {
                num++;
                break;
            }
        }
        
    }
    printf("The substring appears %d time%s.\n", num, num > 1 ? "s" : "");
    return 0;
}

第五章 函数

第一节 函数的概念和模块化程序设计

一、函数的概念

函数是一个可以反复执行的程序段。从其它的程序段中均可以通过函数调用语句来执行这段程序,完成既定的功能。

主调函数:调用其它函数的函数。

被调函数:被调用的函数

任何函数都可以是被调函数,也可以是主调函数。而主调函数只能是主调函数。

1.C语言函数的特点

(1)一个c程序由一个或多个函数组成,其中必须有且只能有一个main函数(称为主函数)。

(2)C程序的执行从主函数开始,并在主函数中结束整个程序的运行。

(3)主函数可以调用其他函数,但其他函数不能调用主函数。主函数由操作系统调用,其他函数之间可以互相调用。

(4)函数之间没有从属关系,互相独立,不能嵌套定义。

2.C语言函数的分类

(1)从函数定义的角度分:库函数和用户函数

(2)从函数有无参数的角度分:有参函数和无参函数。

(3)从函数有无返回值的角度分:有返回值函数和无返回值函数

(4)从函数作用范围的角度分:外部函数和内部函数,外部函数是可以被任何编译单元调用的; 内部函数只能在本编译单元调用。

C语言程序的结构如下图,图中的每一个源程序文件就是一个编译单位。

二、函数的定义

1.函数的一般定义格式

数据类型符 函数名(形式参数表) #函数首部

{数据定义语句序列;

执行语句序列; #函数体

}

【说明】

(1) 函数首部

1)数据类型符 规定了本函数返回值的数据类型,如果函数无返回值,数据类型符应使用void

2)函数名 是一个标识符,在同一个编译单位中函数是不能重名的。

3) 形式参数表 格式: 数据类型符 形式参数1,数据类型符 形式参数2,...

(2)函数体

①数据定义语句序列 用来定义本函数中使用的变量、数组、指针变量等。

②执行语句序列 由函数中完成函数功能的程序段组成。

三、函数的调用

1.函数调用的一般格式: 函数名([实际参数表]);

说明:

(1)[]中的部分可以省略,为无实参函数调用。

(2)实参的个数、类型和顺序,应该与被调函数所要求的形参个数、类型和顺序一致。

(3)当有多个实参时,实参之间用逗号分隔。如: add(2.0, 4.3);

2.函数调用的方式

(1)函数调用作为表达式的一项,出现在表达式中,以函数的返回值参与表达式的运算。 例如: i = 2*add(x, y)

(2)函数调用作为一条独立的语句。 例如: printf("hello\n");

(3)函数调用作为另一个函数调用的实参出现。 例如: s = 2 * add(x, add(y,z));

3.函数的形式参数和实际参数

形参是在函数定义时设置的,用来接收从主调函数传来的对应的实参数据。

实参是调用函数时的实际参数,实参可以是常量、变量或表达式,也可以是函数的返回值,无论哪种形式必须有确定的值。

实参的个数、类型和顺序,应该与被调函数所要求的形参个数、类型和顺序一致。

4.函数的返回

返回语句形式 : return(表达式); 或 return 表达式; 或 return;

功能:使程序控制从被调用函数返回到主调用函数中,同时把返回值带给调用函数。

【说明】 (1) 函数中可有多个return语句,但每调用一次该函数,只能执行到一个return语句。

(2)若return语句中不含表达式,这时必须定义函数为void类型,它的作用只是使流程返回到调用函数,并没有确定的函数值。

(3)若函数体内没有return语句,这时也必须定义函数为void类型,程序执行到函数的最后一个}时,自动返回主调用函数,没有确定的函数值。

(4)若函数类型与return语句中表达式值的类型不一致,按前者准,自动转换------函数调用转换。

5.函数调用的过程

(1)暂停执行函数调用所在的语句,转向执行被调函数。

(2)为函数的所有形参分配内存,再将所有实参的值计算出来,依次赋予对应的形参(注意:若形参是数组则不给形参分配内存)。若是无参函数,则不执行这一操作。

(3)进入函数体,先执行数据定义语句部分,为函数体中定义的变量、数组等分配内存。

(4)执行函数体中的执行语句部分。

(5)收回分配给函数体中定义的变量、数组、形参等的内存单元。返回到主调函数继续执行。

【例】从键盘输入两个整数并求出较大者。

#include <stdio.h>
​
int max(int x, int y) {
    int z;
    z = x > y ? x : y;
    return (z);
}
​
int composedoublenumber(void) {
    int a, b, c;
    printf("input integers a, b:");
    scanf_s("%d %d", &a, &b);
    c = max(a, b);   /*调用函数 max*/
    printf("Max is %d\n", c);
    return 0;
}

四、模块化程序设计方法

一个大型C程序由许多源程序文件(又称为程序模块)组成,一个源程序文件的结构如下:

包含文件: 例如, #include <stdio.h>

宏定义, 例如, #define PI 3.14159

条件编译, 例如, #if...#else...#endif

函数声明, 例如, 定义函数的原型

全局变量说明, 例如, 函数外定义的变量

函数定义

模块化程序设计的基本思想:将一个大的程序按功能分割成一此程序模块,特点:

1.各模块相对独立、功能单一、结构清晰、接口简单;

2.控制了程序设计的复杂性;

3.提高元件的可靠性

4.缩短开始周期

5.避免程序开发的重复劳动。

6.易于维护和功能扩充

开发方法:自上向下,逐步分解, 分而治之。

第二节 函数声明

函数声明又称为函数原型,其一般格式如下:

[存储类型][数据类型符] 函数名 (形参类型1 [形参名1], 形参类型2 [形参名2]);

【说明】 1.存储类型j(两种)

exten 定义的函数叫做外部函数, 它可以被其他编译单位中的函数调用。

static 定义的函数叫做内部函数,它只能被本编译单位中的函数调用

存储类型说明符可以省略,省略时,默认为extern。

2.函数声明告知编译程序函数返回的数据类型、函数所要接收的参数个数、类型和顺序,编译程序利用函数声明校验函数调用是否正确。

3.函数声明中可以只说明形参类型和形参个数,无须说明形参名。

例如:对函数 int max (int x, int y) {...} 的声明以下两种形式均可:

int max(int, int); int max(int x, int y);

4.函数声明可以在主调函数中,也可以在所有函数的外部(如文件声明处).这种情况下,在该声明之后定义的所有函数均可以调用该函数,而不必再声明。

5.函数声明、函数定义、函数调用要保持一致。

6.被调函数书写在主调函数之前,声明可以省略。

第三节 函数的参数和数据传递方式

概述

C语言中,调用函数和被调用函数之间的数据可以通过四种方式进行传递:

1.值传递-----实参向形参传送具体数值;

2.地址传递-----实参向形参传送具体地址值;

3.通过return语句把函数值返回调用函数;

4.通过全局变量,这不是一种好的方式。通常 不提倡使用。

一、值传递方式

值传递方式的特点:

函数调用时,为形参分配单元,并将实参的值复制到形参中;调用结束,形参单元被释放,实参单元仍保留并维持原值---单向传递。

形参与实参占用不同的内存单元,即使形参与实参名字相同,也占用的是不同的内存单元。

【说明】

1.实参必须有确定的值;

2.形参必须指定类型;

3.形参与实参类型一致,个数相同;

4.若形参与实参类型不一致,自动按形参类型转换----函数调用转换。

5.形参在函数被调用前不占内存;函数调用时为形参分配内存;调用结束,形参所占内存释放。

【例】下面程序运行后的输出结果是( 1, 2 )

#include <stdio.h>
void change(int x, int y) 
{ int z;
  z = x; x = y; y = z;
  return ;
}
​
int main(void) 
{
  int a= 1; b = 2;
  change(a, b);
  printf("%d, %d\n", a, b);
  return 0;
}

【例】 下面程序运行后的输出结果是( 12 )

#include <stdio.h>
fun(int a, int b)
{
  if(a > b) return (a);
  else return (b);
}
​
int main(void) 
{
 int x=3, y = 8, z= 6, r;
 r = fun(fun(x, y), z*z);
 printf("%d\n",r);
}

二、数组作为函数参数的数据传递方式

数组作为函数参数有两种形式:

1.数组元素作为函数的实参使用;

2.数组名作为函数的形参和实参使用。

1.数组元素作为函数实参

数组元素的值传递给形参,实现单向的值传递方式。

注意:

(1) 数组元素的处理是按变通变量对待的。

(2)数组元素只参做实参,不能做形参。

【例】分析下列程序的功能。

#include <stdio.h>
#include <string.h>
int isalp(char c)                     /*定义函数isalp(), 形参是变量c */
{ if (c>'a' && c < 'z' || c > 'A' && c < 'Z')
     return 1;
  else return 0;
}
​
int main(void) 
{ int i, num=0;
  char str[255];
  printf("Input a string:");
  gets(str);
  for(i=0;str[i]!='\0';i++) 
  {
     if (isalp(str[i])) num++;    /*调用函数isalp() */
  }
  puts(str);
  printf("num=%d\n", num);
  return 0;
}

【答案】统计输入的一串字符中字母的个数

2.数组名作为函数参数-----地址传递

数组名代表数组首地址,数组名作为函数的实参,是将数组首地址传给形参,形参也必须是同类型的数组名或相应指针变量。

本质: 实参数组和形参数组共享同一段内存空间。

【例】编一个程序,调用的函数change(),将整型数组中前10个数中的最大数与第1个数交换。整型数组在主函数中由键盘输入。

#include <stdio.h>
void main(void) 
{  void change(int b[], int n);     /*对被调函数的声明*/
   int a[10], i;
   for (i=0; i < 10; i++)       /*输入整型数组*/
   {
     scanf("%d", &a[i]);
   }
   change(a, 10);             /*调用函数*/
   for(i=0; i< 10;i++)        /*输出*/
   {
      printf("%d,",a[i]);
   }
}
void change(int b[], int n)       /*函数头*/
{int max, max_i, i;
  max = b[0],max_i=0;              /*设第一个元素是当前最大数*/
  for(i=1;i<n;i++)                 /*求数组b中最大数及其下标*/  
  {
     if(max  <b[i])
     {
        max=b[i];
        max_i = i;
     }
  }
  max = b[0], b[0]=b[max_i], b[max_i]=max;   /*交换最大数与第1个数*/
  return;
}

第四节 变量的存储类型和作用域

一、变量的存储类型

含有存储类型符的变量定义语句格式:

存储类型符 数据类型符 变量名1,变量名2,...;

例: static int a, b;

变量的初始化和变量的存储类型有很大关系,进行初始化时要注意下列几点:

1.外部参照型(extern)变量不允许初始化。自动型(auto)变量、寄存器型(register)变量和静态型(static)变量都可以进行初始化。

2.自动型(auto)变量和寄存器型(register)变量如果进行初始化,则每次进入所定义的函数或复合语句都随着重新定义而重新初始化,变量值将不确定。因此,必须用其它赋值方式为其赋值后方能参与运算。

3.静态型(static)变量如果进行初始化,只有第一次执行定义语句时随着分配内存赋于初值,当退出所定义的函数或复合语句时,将保留分配给它的内存和其中的值。再次进入所定义的函数或复合语句时,不再重新分配内存,也不进行初始化。此时,该变量值是上次离开时的值。静态型(static)变量如果不进行初始化,C语言编译系统将自动为其赋予“零值”: 对数值型变量赋0,对字符型变量赋空字符‘、0’(空字符的ASCII代码值为0)

【例】 以下程序的输出结果是( 15 )

#include <stdio.h>
int fun(int x) 
{ static int t = 0;
  return (t ==x);
}
​
main()
{ int s, i;
  for(i=1; i <=5; i++)
  {
     s=fun(i);
  }
  printf("%d\n", s);
}

二、变量的生存期和作用域

1.变量的生存期:从系统为变量分配内存单单元(或寄存器)开始到系统收回内存单元(或寄存器)的 变量的生存期。

2.变量的作用域:变量有效的范围称为变量的作用域。

3.内部变量与外部变量(从变量的作用域不同分类)

(1)内部变量:在函数(或某个复合语句)内定义的变量;只在本函数内(或复合语句内)有效。

(2)外部变量:在函数外定义的变量。它的有效范围是从定义它的地方开始,到整个程序结束的任何地方。

【说明】

① 主函数中定义的内部变量,只能在主函数中使用,其他函数不能使用。同时,主函数中也不能使用其它函数定义的内部变量。

②形参变量也是内部变量,仅限于函数内使用。

③允许在不同函数中使用重名的变量,它们代表不同的对象,分配不同的内存单元。

④复合语句中也可定义变量,所定义的变量是内部变量,其作用域只在该复合语句范围内。

⑤ 在同一源程序文件中,允许外部变量和内部变量重名。如果外部变量和内部变量重名,则在内部变量的作用域内,外部变量将被屏蔽而不起作用。

⑥外部变量的作用域是从定义点到本文件结束。如果定义点之前的函数需要使用这些外部变量时,需要在函数内对被使用的外部变量进行声明,其声明的一般形式为: extern 数据类型 外部变量1, 外部变量2,...;

4.全局变量和局部变量(根据变量生存期的不同分类)

(1)全局变量:生存期覆盖了定义点到整个程序结束的变量称为全局变量。

(2)局部变量:生存期只覆盖了某个函数(或复合语句)的变量称为局部变量。

【说明】

①外部变量只能定义成无存储类型或静态型(static),不能被定义成自动型(auto)或寄存器型(register)。内部变量可以说明 为自动型、寄存器型或静态型。

②外部变量总是全局变量,在整个程序运行期都不释放;它的作用域是从定义点到整个程序的结束,即在定义后的任何一个函数中都可以使用。

③被说明为auto或register存储型的内部变量是局部变量,只在所定义的函数或复合语句中存活,一旦离开了所定义的函数或复合语句就释放;它的作用域只是所定义的函数或复合语句,一旦离开了所定义的函数或复合语句就不能再使用了。如果再次进入所定义的函数或复合语句,则重新定义后再使用。

④被说明为static存储型的内部变量是全局变量,在整个程序运行期都不释放。它的作用域只是所定义的函数或复合语句,一旦离开了所定义的函数或复合语句就不能再使用,但该变量仍在生存期,所分配的内存并不释放仍保留退出时的值。如果再次进入所定义的函数或复合语句,将不再重新定义就可使用,并且具有上次退出时的值。

三、利用全局变量的数据传递方式

利用在主调函数和被调函数都有效的全局变量,在主调函数和被调函数之间共享数据。

【例】编写一个无返回值的函数,对存放在实型数组的前五个实数计算平均值。在主函数中依次从键盘输入五个实数并保存至数组,调用函数并输出平均值。

#include <stdio.h>
float x;           /*定义全局变量,用于传递数据*/
void fun(float b[])   /*进行函数声明*/
int main(void)
[ int i;
  float a[20];
  x = 0;
  for(i=0;i<5;i++) 
  {
    scanf("%f", &a[i]);  /*输入五个数组元素*/
  }
  fun(a);   /*调用函数fun, 数组名a做实参*/
  printf("aver=%f", x);
  return 0;
]
​
void fun(float b[])    /*形参是实型数组b*/
{ int i;
  for(i=0;i<5;i++)
  {
     x+=b[i];         /*求5个数组元素的和*/
  }
  x = x/5;
}

【例】请阅读下程序,写出运行该程序的输出结果

int a=1, b=2;   /*全局外部变量a和b*/
void f()
{
  int a = 3;             /*变量a重名,内部变量a优先*/
  printf("a=%d, b=%d\n", a,b);
}
void main()
{
  int b = 4;
  f();
  printf("a=%d, b=%d", a, b);
}

第五节 函数的嵌套调用和递归调用

一、函数的嵌套调用

C语言规定:函数定义不可嵌套,但可以嵌套调用函数。

【例】利用函数的嵌套调用计算s = 的值并输出。

#include <stdio.h>
#define K 4
#define N 5       /*符号常量*/
​
long f1(int n, int k)  /*定义函数f1(), 计算n的K次幂*/
{
    long power = n;
    int i;
​
    for (i = 1; i < k; i++)
    {
        power *= n;
    }
    return power;
}
long f2(int n, int k)  /*函数f2计算1到n的k次幂的累加*/
{
    long sum = 0;
    int i;
    for (i = 1; i <= n; i++) {
        sum += f1(i, k);            /*调用函数f1()*/
    }
    return sum;
}
​
int recursion(void) /*主函数*/
{
    printf("Sum of %d powers of integers from 1 to %d = ", K, N);
    printf("n%d\n", f2(N, K));   /*调用函数f2()*/
    return 0;
}

二、函数的递归调用

函数可直接或间接地自己调用自己称为函数递归调用。前者称为简单递归(直接)递归,后者称间接递归。

【例】编一个程序计算n!(n>1),采用递归调用函数。

【算法分析】n1=n*(n-1)(n-2)...2*1=n*(n-1)!,要计处n!,先要计算出(n-1)!。

计算n!的数学公式如下:

#include <stdio.h>
float f(long n)    /*定义递归函数计算n!*/
{
    if (n == 1L) {
        return (1.0);
    }else {
        return (n * f(n - 1));
    }
}
​
int main(void) {
    long x;
    x = f(4); /*调用f函数计算4!*/
    printf("n!=%ld\n", x);
    return 0;
}

递归过程必须解决两个问题:

一是递归计算的公式;

二是递归线束的条件和此时函数返回值。

对求阶乘的递归函数来说,这两个条件可以写成下列公式:

递归计算公式 p(n) = n * p(n-1)

递归结束条件 p(1) = 1

在程序设计实现:

if(递归结束条件) return (递归结束条件中的返回值);

else return (递归计算公式);

【例】不用递归调用方法,编一个计算n!(n>1)的函数,在主函数中依次调用该函数计算1!、2!、...、n!(n由键盘输入)。

#include <stdio.h>
long fn(long n)
{
    static long i = 1l;   /*定义局部静态变量i*/
    i = n * i;
    return (i);
}
int demo3(void) {
    long n, i, pn=0;
    scanf_s("%ld", &n);
    for (i = 1l; i <= n; i++) {
        pn = fn(i);   /*循环调用函数f(), 依次求i!*/
    }
​
    printf("%ld!=%ld\n", n, pn);
    return 0;
}

第六节 常用的库函数

一、常用的数学处理函数

1.计算整型绝对值函数

【函数首部】 int abs(int x)

【返回值】返回整数x的绝对值。

例如, abs(-5)结果是5。

2.计算长整型绝对值函数

【函数首部】 long labs(long x)

【返回值】返回长整型数x的绝对值。

例如,labs(-6)结果是6l.

3.计算实型绝对值函数

【函数首部】 double fabs(double x)

【返回值】返回双精度实数x的绝对值。

例如,fabs(-6.3)结果是6.3。

4.计算小于或等于x的最大整数函数

【函数首部】double floor(double x)

【返回值】返回小于或等于x的最大整数对应的双精度实数。

例如,floor(-2.3)结果是-3.0. floor(5.9)结果是5.0。

5.计算大于或等于x的最小整数函数

【函数首部】double ceil(double x)

【返回值】返回大于或 等于x的最小整数对应的双精度实数。

例如,ceil(-2.3)结果是-2.0.

ceil(5.9) 结果是6.0.

6.计算平方根函数

【函数首部】double sqrt(double x)

【返回值】返回x的平方根

【说明】x的值≥0.

7.计算常用对数函数

【函数首部】double log10(double x)

【返回值】返回常用对数log10(x)的值。

【说明】x的值≥0.

8.计算自然对数的函数

【函数首部】double log(double x)

【返回值】返回自然对数ln(x)的值

【说明】x的值≥0.

9.计算指数函数

【函数首部】 double exp(double x)

【返回值】返回ex的值。

10.计算10的x次幂函数

【函数首部】double pow10(int x)

【返回值】返回10x的值

例如,pow10(-2)结果是0.010000.

11.计算x的y次方函数

【函数首部】double pow(double x, double y)

【返回值】 返回xy的值。

【说明】不能出现x,y均<0;或x≤0,而y不是整数的情况。

例如, pow(x, -3)结果是0.125000, pow(0.2, 3)结果是0.008000。

12.计算正弦函数

【函数首部】 double sin(double x)

【返回值】返回正弦函数sin(x)的值

【说明】x以弧度为单位,如果是角度,则用x*3.1415926/180转换为弧度。

例如, sin(90*3.1415926/180)结果是1.000000。

13.计算余弦函数

【函数首部】double cos(double x)

【返回值】返回余弦函数cos(x)的值。

【说明】x弧度为单位。如果是角度,则用x*3.1415926转换为弧度。

例如,cos(90*3.1415926/180)结果是0.000000。

14.计算正切函数

【函数首部】double tan(double x)

【返回值】返回正切函数tan(x)的值。

【说明】x以弧度为单位。如果是角度,则用x*3.1415926/180转换为弧度。

例如, tan(45 * 3.1415926/180)结果是1.000000。

注意:以上常用的数学处理的库函数,它们均包含在头文件math.h中。

二、常用的类型转换函数

1.字符串转换成浮点数函数

【函数首部】double atof(char *x)

【返回值】返回x所指向的字符串转换成的实数

【说明】x所指向的字符串中存放的应当是一个实数形式。

例如,“32.1”、“0.321e2",则转换结果分别是32.1和32.1。如果不正确的实数形式,则转换结果将取前面若干个能组成实数的字符。例,”2S32.1“、”0.3b21e2“,则转换结果分别是2.0和3.0。

2.字符串转换成整数函数

【函数首部】 int atoi(char *x)

【返回值】返回x所指向的字符串转换成的整型数。

【说明】x所指向的字符串中存放的应当是一个整数形式。例如,"32", "321"则转换结果分别是32和321。如果不是正确的整数形式,则转换结果将取前面若干个能组成整数的字符对应的整数。例,”2S321D“、”45b21e2“,则转换结果分别是2和45.

注意:以上两个类型转换库函数,它们均包含在头文件stdlib.h中。

三、常用的字符处理函数

1.判断是否字母函数

【函数首部】 int isalpha(int x)

【返回值】若x中存放的字符是字母,则返回非0(真);否则,返回0(假)。

例如,isalpha(50)结果是0(50对应的字符是‘2’)。isaplha('w')结果是非0.

2.判断是否小写字母函数

【函数首部】int islower(int x)

【返回值】若x中存放的字符是小写字母,则返回非0(真),否则,返回0(假)。

例如, islower(98)结果是非0(98对应的字符是'b'). islower(‘G’)结果是0.

3.判断是否大写字母函数

【函数首部】int isupper(int x)

【返回值】若x中存放的字符是大写字母,则返回非0(真), 否则,返回0(假)。

例如,isupper(98)结果是0(98对应的字符是‘b’). isupper('G')结果是非0.

4判断是否数字字符函数

【函数首部】int isdigit(int x)

【近回值】若中存放的字符是数字字符,则返回非0(真),否则,返回0(假)。

例如,isdigit(50)结果是非0(50对应的字符是'2'), isdigit('b')结果是0。

5.将大写字母转换成为小写字母函数

【函数首部】 int tolower(int x)

【返回值】若x中存放的字符是大写字母,则返回值是对应的小写字母;若x中存放的字符不是大写字母 ,则返回值等于x的原值。

例如,tolower(50)结果是'2'(50对应的字符是'2'). tolower("B")结果是'b'。

注意: 以上常用的字符处理库函数,它们均包含在头文件ctype.h中。

四、其他的常用函数

1.随机数发生器初始化函数

【函数首部】 void randomize()

【功能】对随机发生器进行初始化。

【返回值】 无

2.随机数发生函数

【函数首部】int random(int x)

【功能】产生一个0 ~ x-1的随机整数。

【返回值】返回一个0 ~ x-1的随机整数。

注意:以上两个库函数所在的头文件均为stdlib.h。

第七节 函数的程序设计实例

【例】一维数组元素的值已由小到大排序,缩写函数删除数组中出现多次的元素值,使之只剩下一个,返回删除后数组元素的个数,

【算法分析】 int num[20] = {2,2,2,3,5,5,5,5,6,6,8,10,13,13,14,14,15,15,15};

#include <stdio.h>
int fun(int a[], int n)  /*定义函数fun,只要该数字是第一次出现,则存入数组的前部*/
{
    int k = 0, pos = 1;
    int cmp = a[0];
    for (k = 1; k < n; k++)   /*遍历数组中各元素*/
    {
      if(a[k] != cmp) {
          a[pos++] = a[k];
          cmp = a[k];
      }
    }
    return pos;
}
​
int repeat(void)
{
    int num[20] = { 2, 2, 2, 3, 5, 5, 5, 5, 6, 6, 8, 10, 13, 13, 14, 14, 15, 15, 15 };
    int i = 0, j = 0, n;
    printf("The original data is: ");
    for (i = 0; i < 20; i++) {
        printf("%d ", num[i]);
    }
    printf("\nThe data after changin is:");
    n = fun(num, 20); /*调用fbn(), 返回不两只元素个*/
    for (i = 0; i < n - 1; i++) {
        printf("%d ", num[i]);
    }
}

【例】输入一个只由数字和字母组成的字符串,将其中的小写字母转换为对应的大写字母,将大写字母转换为对应的小写字母,然后输出转换后的字符串。

#include <stdio.h>
#include <string.h>
#include <ctype.h>   /*字符处理函数*/
​
int conversion(void)
{
    char str[80]= "adfdUTSdfege0938kdIjdjXCD";;  /*存放字符串的数组*/
    int i = 0;
    printf("Enter a string:");
    //scanf_s("%s", str);    /*从键盘输入一字符串*/
    //str = 
    printf("The string of changing is:");
    for (i = 0; i < strlen(str); i++) {
        if (isalpha(str[i]))   /*判断是否是字母*/
        {
            if (isupper(str[i]))
            {
                str[i] += 32;
            }
            else
                str[i] -= 32;
        }
        printf("%c", str[i]);
    }
    return 0;
}

【例】以下程序的运行结果是( 6 )

#include <stdio.h>
int f(int y)
{
 int y;
 if(x==- || x==1) return (3);
 y = x*x -f(x-2);
 return y;
}
main()
{
  int z;
  z=f(3);
  printf("%d\n", z);

}

【例】编程序按下列公式计算并输出s值,其中的n由键盘输入。 s(n) = 1^2 + 2^2 + ... + n^2

【算法分析】 这个公式实际上是一递归计算的问题。

递归计算公式: s(n) = s(n-1) + n*n

递归结束条件: s(1) = 1*1=1 n=1

#include <stdio.h>
float s(int n)
{
    if (n == 1)
    {
        return (1.0);   /*递归结束条件成立*/
    }
    else {
        return (s(n - 1) + n * n); /*递归调用*/
    }
}
​
void test()
{
    int n;
    scanf_s("%d", &n);   /*输入n的值*/
    printf("s(%d)=%f\n", n, s(n)); /*调用递归函数计算的结果*/
}

第六章 指针

第一节 指针和指针变量

一、指针

指针是一种数据类型,也是C语言的一个重要特色和关键。指针是存放数据的内存单元地址,指针的作用主要体现在以下几个方面:

(1) 使程序简洁、紧凑、高效。

(2)有效地表示复杂的数据结构。

(3)动态分配内存。

(4)获得多于一个的函数返回值。

1.变量与地址

例如,在程序清单中有下列数据定义语句:char ch='A'; short s = 5; float f = 2.8;

2.数组与地址

例如:在程序表单中有下列数据定义语句: short a[3] = {1,2,3};

【说明】 (1) 一个数组占用的总字节数 = 数组长度 * 每个数组元素占用的字节数。

(2) 当一个数组元素只占用一个字节时,该字节的地址就是该数组元素的地址;当数组元素占用若干个连续的字节时,第一个字节的地址就是该数组元素的地址。

(3)一个数组的首地址,就是第1个元素(即下标为0的数组元素)的地址。数组名代表数组首地址。

(4)&a[i]=数组首地址 + i * 数组元素的数据类型所占用的字节数。

二、指针变量

指针变量也是变量,但只能存放地址类型的数据,可以称为“地址型”变量。比如,存放变量的地址、数组的地址、函数的地址等。

1.指针变量的定义和初始化

【格式】数据类型符 *指针变量名1[=初始地址值,] ....

【例】 int a = 10; int *p = &a;

【说明】

(1)指针变量名的构成原则是标识符,前面必须有"*"号。

(2)在一个定义语句中,可以同时定义普通变量、数组、指针变量。例: float f, *p = &f, s[10], *q1=s;

(3)定义成某种数据类型的指针变量,只能用来指向同种数据类型的变量或数组。

(4)"初始地址值"通常是“&变量名”“&数组元素”或"一维数组名",这里的变量或数组必须是已定义的。

(5)指针变量的初始化,除了可以是已定义变量的地址,也可以是已初始化的同类型的指针变量,也可以是NULL(空指针)。

例如: int x, y, *px = &x; int *py = &y, *pz = px; char *pc=NULL; /*指针变量pc初始化为NULL*/

【例】计算字符串的实际长度。

#include <stdio.h>
int getstringlength(void) {
	char s[256];
	char *p;  /*定义字符型指针变量p*/
	gets(s);
	p = s; /*指针变量p指向数组s的首地址*/
	while (*p != '\0')
	{
		p++;  /*指针p遍历数组*/
	}

	printf("The string length is %d\n", p - s); /*计算并输出字符串的实际长度*/
	return 0;
}

2.指针变量的一般使用

C程序中指针变量的使用有多种方式,常用的是以

(1) 给指针变量赋值

【格式】指针变量=地址型表达式

【例】 int i, *p_i, *p_s, *p_s2, s[10], *p; p_i=&i; p_s=s; *p_s2 = &s[2]; p=3000; /*想使p指向3000地址单元 错误的*/

(2)直接使用指针变量名

【格式】指针变量名

【例】int i, j , *p =&i, *q;

q = p; /*将指针变量p的值赋予指针变量q*/

scanf("%d, %d", q, &j);

(3)通过指针变量来引用它所指向的变量

【格式】 *指针变量名

【例】 int i =1, j =2, k, p=&i; k=p+j; /*由于p指向i,所以*p就代表i, 结果k等于3*/

【例】从键盘输入两个整数,按先大后小的顺序输出。

#include <stdio.h>
int orderoutput(void)
{
    int x, y, *px, *py, *p;
    scanf_s("%d%d", &x, &y);
    py = &y;
    px = &x;
    if (x < y) {
        p = px, px = py, py = p;
    }
    printf("x=%d, y=%d\n", x, y);
    printf("MAX=%d, MIN=%d\n", *px, *py);
    return 0;
}

3.指针的基本运算

【说明】取地址运算符、指针运行符和自增、自减等单目运算符的优先级相同。

【例】 int i=5, k, a[5], *p1, *p2, *p3;

p1 = &i; p2 = &a[1]; p3 =a; k=*p3; /*等价于p3 = &a[0]*/

【例】 int i =10, *p=&i;

1) *p+=1; /*该语句等价于i+=1;*/

2) ++*p; /*该语句等价于++i;*/

3)(*p)++; /*该语句等价于i++;*/

4) *p++ /*\该语句的作用是先取*p的值作为表达式的值,指针p加1;*/

【例】 设有数据定义语句: float a, *p = &a;

1) *&a是正确的。相当于*(&a), &a是变量工的地址,*(a地址代表变量a).

2)&*a是错误的。相当于&(*a), 因为a不是指针变量,所以*a不正确。

3)*&p是正确的。相当于*(&p), &p是p的地址, *(p地址)代表p。

4) &*p是正确的,相当于&(*p), *p代表变量a, &(变量a)代表a的地址。

第二节 指针和数组

一、指针和一维数组

指针变量指向一维数组的两种方法:

(1)在数据定义语句中用赋初值的方式 *指针变量 = 数组名

例:short a[10], *pa=a;

(2)在程序中用赋值方式 指针变量 = 数组名

例:short a[10], *pa; pa = a;

指针变量指向某一维数组元素的两种方法:

(1) 在数据定义语句中用赋初值的方式 *指针变量 = &数组名[下标] 例: short a[10], *pa = &a[3]

(2) 在程序中用赋值方式 指针变量 = &数组名[下标] 例: short a[10], *pa; pa=&a[3];

1.用指向一维数组的指针变量处理数组元素

【例】 short a[10], *p =a; a[i]﹤=﹥p[i]﹤=﹥*(p+i)﹤=﹥*(a+i)

可见short a[10], *p=a; 中

1) 引用数组a中下标为i的数组元素可以采用四种方法: ①a[i] ②a*(a+1) ③p[i] ④*(p+i)

2) 引用数组a中下标为i的数组元素的地址可以采用四种方法。 ①&a[i] ②a+i ③ &p[i] ④p+i

【单选题】 设 int a[] = {1,2,3,4,5}, *p = a; 则数组元素的正确表示是( A )。 A. a[2] B. a+1 C. a[5] D. p

【例】 利用多种方法实现从键盘输入N个学生的成绩。计算其平均成绩并输出。

方法一: 利用下标法直接访问数组元素。

#include <stdio.h>
#define N 10
int main(void)
{
    int s[N], i, sum=0;
    float av;
    printf("input scores:\n");
    for (i = 0; i < N;i++){
        scanf("%d", &s[i]);
    }
​
    for (i = 0; i < N;i++){
        sum += s[i];   /*计算N个学生的总成绩*/
    }
    av = (float)sum/N;   /*计算平均成绩*/
    printf("average scored is %5.2f\n", av);
    return 0;
}

方法二: 利用地址间接访问数组元素.

#include <stdio.h>
#define N 10
int main(void)
{
   int s[N], i, sum =0;
   float av;
   printf("input scores:\n");
   for (i=0; i<N; i++) {
   scanf("%d", s+i);
   }
​
  for(i=0; i<N;i++){
     sum += *(s+i);   /*计算N个学生的总成贯*/
  }
  av = (float)sum / N;   /*计算平均成绩*/
  printf("average score: %5.2f\n", av);
  return 0;
}

方法三:利用指针变量间接访问数组元素。

#include <stdio.h>
#define N 10
int demoavg3(void)
{
    int s[N], * p, sum = 0;
    float av;
    p = s;
    printf("input scores: \n");
​
    for (; p < s + N; p++)
    {
        scanf("%d", p);
    }
    p = &s[0];  /*指针p重新指向s[0]*/
    for (; p < s + N; p++)
    {
        sum += *p;   /*计算N个学生的总成绩*/
    }
    av = (float)sum / N;  /*计算平均成绩*/
    printf("average score is %5.2f\n", av);
    return 0;
}

二、指针变量的运算

当指针变量指向一维数组后,指针变量可以进行赋值运算、部分算术和关系运算。

1.赋值运算

【例】设int i, *p,*p1,*p2,*pi,*pj,*pz,array[10]; 则:

① pi=&i; 正确 ②i=pi; 错误 ③p = array; 正确 ④ p2=&array[i]; 正确 ⑤p1 =p2; 正确

⑥ pj=0; 正确, 等价于 pj = NULL ⑦pz=4000; 不能把整数值4000赋予指针变量pz.

2.算术运算

指针变量可以进行的算术运算包括以下四种:

① 指针变量±整数。

②指针变量++/++指针变量

③指针变量--/--指针变量

④指针变量1 - 指针变量2

注意: 不能进行 指针变量1 + 指针变量2

【例】指针变量与地址常量举例。

设有数据定义语句: float a[10], *pa=a; *pb=&a[6];

正确的表达式: ++pa、pa++、--pa、pa--、pa=pa+5、pa=pa-5、a+5、pb-pa.

错误的表达式: ++a、a++、--a、a--、a=a+5。 数组名代表数组首地址,是常量,不能进行上述运算。

3.关系运算

两个类型相同的指针变量可以运用关系运算符比较大小,表示两个指针变量所指向地址位置的前后关系,即前者为小,后者为大。

【例】设有short a[10], *p1=&a[2], *p2 = &a[3];

1) p1 < p2 结果为1(真) 2)p1 < a 结果为0(假) , a是数组首地址

3) p1 < &a[9] 结果为1(真) , &a[9]是地址常量 4)p2 <= a + 3 结果为1(真), a+3是地址型表达式,代表a[3]的地址。

指针变量的运算例题

【单选题】 设 int x [] = {1, 2, 3, 4, 5}, *p=x;, 则值为3的表达式是(A);

A. p+= 2, *p++ B p+=2, *++p C. p+=2,p++ D. p+=2, ++*p

第三节 指针与字符串

概述

C语言的字符串本质上是字符型一维数组,即可以利用字符串中的字符作为字符数组元素进行访问,也可以利用字符型指针变量进行访问。对字符串睥操作可以数组名作为一个整体进行,也可以逐个字符进行,即定义一个字符型指针变量既可以指向一个字符串,也可以指向一个字符变量,通过字符型指针变量访问整个字符串或字符串的单个字符。

一、指向字符串的指针变量

1.将指针变量指向字符串常量的方法

(1)在数据定义语句中用赋初值的方式; *指针变量 = 字符串常量 【例】 char *p = "abcd";

(2)在程序中用赋值的方式: 指针变量 = 字符串常量 【例】 char *p ; p = "abcd";

【说明】

(1) 这两种方法中并不是将字符串常量赋予指针变量,而是将存放字符串常量的连续岗内存单元的首地址赋予指针变量。

(2)不允许将字符串常量用赋值方式赋予字符型数组。 char a[10]; a="abcd"; /*这个语句出错*/

(3)使用一个字符型指针变量先后指向不同的字符串常量。

例: char *pa; pa="abcdefgh": /*pa指向字符串常量“abcdefgh”*/ ...

pa="123456"; /*pa指向字符串常"123456"*/

2.指向字符串常量的指针变量的使用

(1) 处理整个字符串 输出整个字符串 printf("%s", 指针变量); 输入整个字符串 scanf("%s", 指针变量);

(2)处理字符串中的单个字符 第i个字符的表示方法 *(指针变量+i)

【例】 设有下列数据定义语句:

char *p1 = "ABCD", *p2="1234";

printf("%s\n", p2); /*输出结果是: 1234*/

printf("%c, %c\n", *(p1+1), *(p1+3)); /*输出结果是: B, D*/

printf("%s\n", p2+2); /*输出结果是: 34*/

二、指向字符数组的指针变量

处理指向字符数组的指针变量方法和处理一维数组元素的方法完全相同,唯一需要注意的是,数组元素的类型是字符型。

【例】将字符串s1的内容复制到字符串s2.

#include <stdio.h>
#include <string.h>
int copystring(void)
{
    int i = 0;
    char s1[80], s2[80], * p1 = s1, * p2 = s2;

    gets(s1);   /*输入一个字符串存入字符数组s1*/

    while (*p1 != '\0') {
        *p2 = *p1;   /*把s1的一个字符复制到s2的对应位置*/
        p1++;
        p2++;
    }

    *p2 = '\0';             /*末尾添加一个结束标志符*/
    printf("%s\n%s\n", s1, s2);
    return 0;
}

三、字符指针和字符数组的区别

字符指针和字符数组的区别:

1.存储内容不同。字符数组是一段连续的存储单元存储字符串本身字符指针存储的是字符串所占用内存单元的首地址。

2.赋值方式不同。

【例】char *pointer, array[80];

pointer="This is a example."; /*正确*/

arry = "this is a example."; /* 错误的用法 */

3.字符指针变量在定义后应先赋值才能引用。

【例】 char *p, [10];

scanf("%s", p); /*错误的用法*/

p=str;

scanf("%s", p); /*正确的用法*/

4.指针变量的值是可以改变的,字符指针变量也不例外;而数组名代表数组的首地址,是一个常量,而常量是不能改变的。

【例】运行程序运行后的输出结果是(BCD\n CD\n D\n)

#include <stdio.h>
main()
{
   char s[] = "ABCD", *p;
   for (p=s+1; p<s+4;p++) 
   {
     printf("%s\n", P);
   }
}

第四节 指针和函数

一、指针变量作为函数参数

(1)指针变量既可以作为函数的形参,也可以作为函数的实参。

(2)指针变量作为函数参数,形参和实参之间的数据传递方式本质上是值传递,传的是地址值,这样使得形参变量和实参变量指向同一个变量。若被调函数中有对形参所指变量内容的改变,实际上是改变了实参所指变量的内容。

【例】从键盘输入两个整数,按从大到小的顺序输出。

#include <stdio.h>
void swap1(int x, int y)  /*普通变量作为函数形参*/
{
    int temp; 
    temp = x; x = y; y = x;
}
​
void swap2(int* px, int *py)  /*指针变量作为函数实参*/
    { int temp;
      temp = *px; *px = *py;
*py = temp;
    }    /*交换的是指针变量所指向变量的值*/
​
void swap3(int* px, int* py)  /*指针变量作为函数形参*/
{
    int* temp;
    temp = px, px = py, py = temp;      /*交换的是指针变量*/
}
​
int somposernumber(void) 
{
    int a = 9, b = 12;
    int* p1 = &a, * p2 = &b;
    if (a < b) {
        swap1(a, b);   /*普通变量作为函数实参*/
    }
    printf("%d, %d\n", a, b);
a = 9; b = 12;
if (a < b) {
    swap2(p1, p2);               /*指针变量作为函数实参*/
}
printf("%d, %d\n", a, b);
​
a = 9, b = 12;
if (a < b)
{
    swap3(&a, &b);  /*变量的地址作为函数实参*/
}
printf("%d, %d\n", a, b);
return 0;
}

【结论】

当指针变量作为函数形参时,可以得到如下结论:

(1)形参是指针变量,实参必须是地址表达式。

(2)当指针变量作业形参时,属于“值传递”方式。调用函数时,为形参(指针变量)分配内存空间,计算实参表达式的值(地址值)赋予形参(指针变量)。函数调用结束时,不把形参(指针变量)的地址带回给实参,并将收回分配给形参(指针变量)的内存单元,返回主调 函数。

(3)在被调函数中,由于形参对应的指针变量已经指向主调函数的某个变量(也可以是数组元素或数组),则可以使用作为形参的指针变量来引用或修改主调函数中的变量或数组元素的值。返回主调函数后可以直接利用变量或数组元素获得在被调函数中修改的值。

(4)当指针变量作为实参时,即将指针变量的值(地址)传递给被调函数的形参(必须是一个指针变量)。需要注意的是,被调函数不能改变实参指针变量的值,但可以改变实参指针变量所指向的变量的值。

2.数组名作为函数参数

(1)数组名作为函数形参时,接收实参数组的首地址;数组名作业函数实参时,将数组的首地址传递给形参数组。

(2)数组及指向数组的指针变量作为函数参数时,可有四种等价形式:

形参、实参均为数组名。

形参、实参均为指针变量名。

形参为指针变量、实参为数组名。

形参为数组名、实参为指针变量。

【例】 编写一个函数实现字符串的复制。

#include <stdio.h>
void string_copy(char *str_from, char *str_to)
{
    int i = 0; 
    for (; (*(str_to + i) = *(str_from + i)) != '\0'; i++)
    {
        /*for语句的循环体为空语句, '\0'也完成复制*/
    }
}
​
int stringdemo1(void)
{
    char array_str1[20] = "China", array_str2[20];
    string_copy(array_str1, array_str2); /*调用函数*/
    printf("array_str2 = %s\n", array_str2);
    return 0;
}

【例】计算两个班学生的平均成绩。

#include <stdio.h>
float average(float array[], int n) {
    int i;
    float aver, sum = array[0];
    for (i = 1; i < n; i++)
    {
        sum += array[i];    /*计算n个学生的总成绩*/
        
    }
    aver = sum / n;     /*计算n个学生的平均成绩*/
    return(aver);
}
int main(void)
{
    float* p1, * p2;    /*定义指针变量p1和p2*/
    static float score1[5] = {98,.597,91.5,60,55};
    static float score2[10] = {67.5,89.5,99,69.5,77,89,76.5,54,60,99.5};
    p1 = score1;
    p2 = score2;
    printf("the average of class A is %6.2f\n", average(p1, 5));
    printf("the average of class A is %6.2f\n", average(p2, 10));
    return 0;
}

二、指针型函数

1.指针型函数的定义

指针型函数是指函数的返回值是指针型。

【格式】 数据类型 *函数名(形参1, 形参2...);

2.指针型函数的调用

注意:只能使用指针变量或指针数组元素接收指针型函数的返回值,不能使用数组名接收指针型函数的返回值,因为函数名是地址常量。

【例】编程,在一个字符数组中找一个给定的字符,则输出从其开始的字符,否则输出"NO FOUND"

#include <stdio.h>
#include <string.h>

char* match(char c, char* s)                 /*定义指针型函数*/
{
	int count = 0;
	while (c != s[count] && s[count] != '\0')
	{
		count++;
	}

	if (c = s[count]) {
	
		return(&s[count]);   /*找到,返回其地址值*/
	}
	return NULL;
}

int findstr(void)
{
	char s[80], * p, ch;
		gets(s); 
	ch = getchar();
	p = match(ch, s) ;  /*也没调用函数*/
	if (p) printf("there is the string: %s\n", p); /*输出从给给定字符开始的字符串*/
	else {
		printf("NO FOUND");
	}
		return 0;

}

第五节 指针数组

1.指针数组定义

指针数组是数组中的元素均为指针变量,可用于处理二维数组或多个字符串。

【格式】数据类型 *指针数组名[长度[];

【例】 int *p[6];

定义了一个名为p、用来指向整型数据的指针型数组,有3个元素: p[0], p[1],p[2].

2.指针数组的初始化

【例】 char *ps[] = {"China", "America", "Russia", NULL};

定义指针数组ps, 其长度是4.

ps[0]指向字符串"China";

ps[1]指向字符串"America";

ps[0]指向字符串"Russia";

ps[4]存放的NULL,即0。

【例】 int a3={1,2,3,4,5,6,7,8,9};

int *p[3] = {a[0], a[1], a[2]};

定义了对指针数组p, 其元素p[0], p[1],p[2]指向了二维数组3各行的首地址。

3.指针数组元素的赋值

指针数组元素的赋值分为两种情况:

(1)将数组名赋予指针数组各元素

【例】 char s4 = {"China", "America", "Russia", NULL};

char *p[4];

p[0]=s[0]; /*p[0]指向"China"的首地址*/

(2)将字符串直接赋予指针数组元素

【例】 char *p[4]; p[0]="China";

4.指针数组元素的使用

指针数组元素的使用和指针变量的使用完全相同。

【例】输入三个国家的名称,按字母顺序排序后输出。

【算法分析】利用指针数组s存放三个国家的名称,用指针变量p作为中间变量以存放指针数组元素交换的字符串的首地址。

#include <stdio.h>
#include <string.h>
​
int main(void) {
    char* s[] = { "China", "America", "Russia" }, *p;
    int i, j, k = 3;
    for (i = 0; i < k - 1; i++) {
        for (j = 0; j < k - 1 - i; i++) {
            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;
}

第六节 指针的程序设计实例

【例1】输入两个长度均小于40的字符串s1和s2,将两个字符串连接形成新的字符串s3并输出。

#include <stdio.h>
#include <string.h>
​
void connect(char* a, char* b, char* c)
{
    for (; *a != '\0';) {
        *c = *a; c++; a++;  /*将a串的内容放入c串中*/
    }
​
    for (; *b != '\0';) {
        *c = *b, c++; b++;   /*将b串的内容放入c串中*/
    }
    *c = '\0';   /*在c串末尾添加字符串结束符'\0'*/
}
int main(void) {
    char s1[40], s2[40], s3[80];
    printf("Enter string s1: ");
    gets(s1);
    printf("Enter string s2: ");
    gets(s2);
    connect(s1, s2, s3);
    printf("The result of concaternating is %s", s3);
    return 0;
}

【例2】利用指针实现冒泡排序

#include <stdio.h>
void sort(int* a, int n) {
    int i = 0, j = 0, tem = 0;
    for (i = 0; i < n - 1; i++) /*需要进行n-1轮排序*/ {
        for (j = 0; j < n - 1 - i; j++) /*每一轮比较前n-1-i个*/ {
            if (*(a + j) > *(a + j + 1)) {
                {tem = *(a + j);
                *(a + j) = *(a + j + 1);
                *(a + j + 1) = tem;
                }
            }
        }
    }
}
​
int main(void) /*主函数*/
{
    int a[100], * p;
    int i = 0, num = 0;
    p = a;   /*p指向整型数组a*/
    scanf_s("%d", &num); /*输入待排序的数的个数*/
    for (i = 0; i < num; i++)
    {
        scanf_s("%d", p + i);
​
    }
​
    sort(p, num);  /*调用函数sort()*/
    for (i = 0; i < num; i++) {   /*输出排序后结果*/
        printf("%4d", *(p + i));
    }
    return 0;
}

第七章 结构体类型和自定义类型

第一节 结构体类型定义

结构体类型定义的一般形式如下:

struct 结构体类型名 /*struct是关键字*/

{

数据类型符成员名1,

数据类型符成员名2,

.....

数据类型符成员名n

}; /*此行分号民不能缺少*/

【例】为学生成绩登记定义结构型.

struct student

{

long number;

char name[10];

char sex;

short age;

float score;

}

【例】 定义嵌套结构型的例子。

struct date

{

int year, month, day;

};

struct person

{

char name[10];

char sex;

struct date birthday;

char address[30];

long zipcode;

};

【说明】

1.名为date的结构体类型的定义必须出现在结构体person的定义之前。

2.persion这个结构体占用的内存字节数为: 10 + 1 + (2 + 2 + 2) + 20 +4=41

sizeof(struct person) = 41

第二节 结构体类型变量

一、结构型变量的定义和初始化

定义结构型变量的方法有三种:

1.选定义结构型,后定义变量(罗常用的一种)

【例】 struct stu_info /*定义结构体类型*/

{ long number,

char name[10],

char sex;

float score[3];

};

......

struct stu_info a, b; /*定义结构型变量*/

在结构体类型变量定义时赋初值:

struct std_info a = {1, "ZhangYin", 'F', {80, 78,88}}, b={2, "LiYang", 'M', {89, 91, 85}};

2.在定义结构体类型的同时,定义结构体类型变量。

【例】 struct stu_info /*定义结构体类型*/

{ long number;

char name[10];

char sex;

float score[3];

}a, b; /*定义结构型变量*/

【例】 struct stu_info /*定义结构体类型*/

{ long number;

char name[10];

char sex;

float scire[3];

} a={1, "ZhangYin", 'F', {80,78,88}},

b={2, "LIYang", 'M', {89, 81, 85}};

3.省略结构体类型名, 直接定义结构体类型变量。

【例】 struct /*定义无名结构体类型*/

{ long number;

char name[10];

char sex;

float score[3];

}a, b; /*定义结构型变量*/

【例】 struct /*定义无名结构体类型*/

{ long number;

char name[10];

char sex;

float scire[3];

} a={1, "ZhangYin", 'F', {80,78,88}},

b={2, "LIYang", 'M', {89, 81, 85}};

注意:这种方法省略了结构体类型名,以后将无法再用这种结构体类型定义其他变量。

二、结构体类型变量成员的引用

1.结构型变量成员的引用方法

结构型变量名.成员名

【例】 结构体类型变量的输入、赋值和输出的例子。

#include <stdio.h>
#include <string.h>
struct std_info /*定义结构体类型*/
{
    int no;
    char name[10];
    char sex;
    int score[3];
};
​
int main(void) {
    struct std_info s1, s2; /*定义结构体变量s1,s2*/
    s1.no = 1;
    strcpy(s1.name, "LiWei");
    s1.sex = 'F';
    s1.score[0] = 82; /*变量s1的数组成员赋值*/
    s1.score[1] = 70;
    s1.score[2] = 92;
    printf("no=%d, name = %s, sex=%c, score[0]=%d, score[1]=%d, score[2]=%d\n",
           s1.no, s1.name, s1.sex, s1.score[0], s1.score[1], s1.score[2]);
    scanf("%d%s%c%d%d%d", &s2.no, &s2.name[10], &s2.sex, &s2.score[0], &s2.score[1],
          &s2.score[2]);
    s1 = s2; /*相同类型的结构体变量之间直接赋值*/
    printf("no=%d, name = %s, sex=%c, score[0]=%d, score[1]=%d, score[2]=%d\n",
           s1.no, s1.name, s1.sex, s1.score[0], s1.score[1], s1.score[2]);
}

嵌套型结构型变量的引用方法:

外层结构型变量,外层结构型成员名.层成员名

【例】struct date { int year, month, day; };

struct person { .... struct date birhtday; ....} s1, s2; s1.birthday.year = 2001;

2.结构体类型变量成员地址的引用方法

(1)成员是变量形式的变量地址: &结构型变量名.成员名

(2)成员是数组形式的数组首地址: 结构型变量名.成员名

(3)成员是数组,其数组元素的地址: &结构型变量名.成员数组[下标]

【例】结构体类型变量成员地址的引用例子。

#include <stdio.h>
#include <string.h>
struct std_info          /*定义结构体类型*/
{
	int no;
	char name[10];
};

int main(void)
{
	struct std_info student;
	int *p_no;   /*定义指针变量*/
	char *p_name;
	p_no = &student.no;  /*变量形式的变量地址*/
	p_name = student.name;   /*数组形式的数组首地址*/
	*p_no = 100;
	strcpy(p_name, "LiWei");
	printf("No: %d\n", *p_no);
	printf("Name: %s\n", p_name);
	return 0;
}

3.结构体类型变量地址的引用方法

&结构体类型变量名

第三节 结构体类型数组

一、结构体类型数组定义和初始化的三种方法:

方法一:先定义结构体类型,再定义结构体类型数组并初始化。

struct std_info /*先定义结构体类型*/

{int no;

char name[10];

char sex; };

....

struct std_info s[3] = {{100, "ZhangYi", 'f'}, {101, "WangHong", 'm'}, {102, "LiSan", 'f'}};

方法二: 定义结构体类型的同时定义结构体类型数组并初始化。

struct std_info /*先定义结构体类型*/

{int no;

char name[10];

char sex; } s[3] = {{100, "ZhangYi", 'f'}, {101, "WangHong", 'm'}, {102, "LiSan", 'f'}};

方法三:定义无名称的结构体类型的同时定义结构体型数组并初始化。

struct /*先定义无名结构体类型*/

{int no;

char name[10];

char sex; } s[3] = {{100, "ZhangYi", 'f'}, {101, "WangHong", 'm'}, {102, "LiSan", 'f'}};

二、结构体类型数组元素成员的引用

1.结构体类型数组元素成员的引用方法:

结构体类型数组名[下标].成员名

2.结构体类型数组元素成员地址引用方法:

&结构体类型数组名[下标].成员名

【例】结构体类型数组元素成员及其地址的引用例子

#include <stdio.h>
#define NAMESIZE 20
struct std_info
{
    char name[NAMESIZE];
    int age;
    char sex;
}s[10];
​
int main(void)
{
    int i = 0;
    printf("Please input student data\n");
    while (i < 3)       /*输入3个学生的信息*/
    {
        printf("name:      age:    sex:");
        scanf("%s", s[i].name);  /*结构体数组成员数组名*/
        scanf("%d", &s[i].age);  /*结构体数组成员变量名*/
        getchar();   /*接收空格符*/
        scanf("%c", &s[i].sex);
        i = i + 1;
    }
    i = 0; /*i重新置0*/
    printf("name            age    sex\n");
    while (i < 3)
    {
        printf("%-10s%5d%dc\n", s[i].name, s[i].age, s[i].sex);
         i = i + 1;
     }
    return 0;
}

第四节 结构体类型指针

一、指向结构体类型变量的指针变量

对结构体成员的引用3种方式:

①变量.成员名 ② (*指针变量).成员名 ③ 指针变量->成员名

【例】设 struct st { int a; float b; }s, *p = &s; /*p是指向结构体变量s的指针变量*/

对结构体成员a的引用3种方式:

① s.a ② (*p).a ③ p -> a

二、指向结构型数组的指针变量

1.指针变量指向数组元素,对结构体成员的引用3种

① 数组名[i].成员名 ② (*指针变量).成员名 ③指针变量->成员名

2.指针变量指向数组,对结构体成员的引用3种方式:

① 数组名[i].成员名 ② (*(指针变量+i)).成员 ③ (指针变量 + i) ->成员名

【例】引用结构体类型变量中的数据。

struct date [int year, month, day;];

struct student [

char name[10];

char sex;

struct date birthday;

float sc[4];

]std, pers[5], *ps = &std, *pe=pers;

1)对结构体变量成员的引用

① 引用结构体变量中的普通成员: std.sex 或 ps->sex 或 (*ps).sex

②引用结构体变量中的数组成员: std.sc[1]或 ps ->sc[1] 或 (*ps).sc[1]

注意: 不能写成std.sc, 因为sc是一个数组名,C语言不允许对数组整体访问(字符串除外),只能逐个引用其元素。

③引用作业字符串使用的字符型数组成员

std.name 或 ps -> name 或 (*ps).name

④引用结构体变量中内嵌结构体变量成员

std.birthday.year 或 ps -> birthday.year 或 (*ps).birthday.year

2)对结构体变量中的成员进行操作

①对相应变量中的name成员所进行的操作:

树荫地(“%s”, std.name); 或 gets(std.name); strcpy(std.name, "Li Ming");

参数亦可用 ps->name 或 (*ps).name;

注意: 不能直接赋值 std.name = "LI Ming"; 错误!

②对相应变量中的成员数组sc中的元素进行操作:

for(j=0;j<4;j++) scan("%f", &std.sc[i]); 参数亦可用 &ps->sc[j]或ps.sc[j]

for(j=0; j<4;j++) printf("%f", std.sc[j]); 参数亦可用ps->sc[j] 或 (*ps).sc[j]

【例】有以下说明和定义语句

struct student {int age; char num[8]; };

struct student stu[3]={{20,"200401"},{21,"200402"},{19,"200403"}};

struct student *p = stu;

以下选项中引用结构体变量成员的表达式错误的是( D ).

A. (p++) -> num B p-> num c (*p).num D stu[3].age

【例】以下程序的运行结果是( 20, 15 )

#include <stdio.h>
struct tt
{
    int x; 
    struct tt* y;
} *p;
struct tt a[4] = {20, a+1, 15, a+2,30,a+3,17, a};
demo8() {
    int i; p = a;
    for (i = 1; i <= 2; i++)
    {
        printf("%d,", p->x);
        p = p->y;
    }
​
}

【解析】该题考核的知识点是结构体成员的引用。题中指针p指向结构体数组a的首地址,即指向数组元素a[0]。执行for循环过程中,当i = 1时,首先输出p->x, 即a[0].x的值20, 然后将p->y,即a[0].y的值a+1赋给p,这样p就指向数组元素a[2]。所以程序的运行结果是20, 15.

三、结构体类型数据在函数间的传递

1.使用全局外部变量传递结构型数据

2.利用返回值传递结构体类型数据 return(表达式); 表达式是已定义的结构体类型。

3.利用形参和实参结合传递结构体类型数据

(1)地址传递方式

结构体变量的地址作为实参传递时,对应的形参应该是一个类型相同的结构体类型的指针变量。

【例】编写函数display().通过主函数调用实现将若干个学生信息打印在屏幕上。

#include <stdio.h>
struct date { int year, month, day; };
struct std_info {
    char no[10];
    char name[10];
    char sex;
    struct date birthday;
}student[3] = { {"000102", "WangHong", 'f', {2000, 5,20}},
    {"000105", "ZhangYing", 'f', {2000, 8,15}},
    {"000112", "HaoLin", 'm', {2000, 3,10}} };  /*全局结构体数组*/
void display(struct std_info* p)   /*形参是指向结构体类型变量的指针变量*/
{
    printf("%-7s%-10s%-5c", p->no, p -> name, p->sex);
    printf("%4d-%2d-%2d\n", p->birthday.year, p->birthday.month, p->birthday.day);
}
​
​
​
int demo9(void)
{
    int i = 0;
    printf("%-7s%-10s%-5s%s", "No", "Name", "Sex", "Birthday\n");   /*打印表头*/
    for (; i < 3; i++)
    {
        display(student + i);
        printf("\n");
    }
    return 0;
}
 

(2)值传递方式

值传递方式中,形参为结构体类型变量,对应的实参须是同一结构体类型的变量或数组元素。

【例】利用值传递方式传递结构体类型数据的例子。

#include <stdio.h>
struct data /*定义包含三个成员的结构体类型*/
{
    int a, b, c;
};
​
void func(struct data parm)  /*形参是结构体变量*/
{
    printf("parm.a=%d parm.b=%d parm.c=%d\n", parm.a, parm.b, parm.c);
    parm.a = 18; parm.b = 5;
    parm.c = parm.a + parm.b;
    printf("parm.a=%d parm.b=%d parm.c=%d\n", parm.a, parm.b, parm.c);
}
int main(void)
{
    struct data arg; /*定义结构体类型变量arg*/
    arg.a = 27; arg.b = 3;
    arg.c = arg.a + arg.b;
    printf("arg.a=%d arg.b=%d arg.c=%d\n", arg.a, arg.b, arg.c);
    func(arg);   /*调用函数func(), 实参是结构体变量*/
    printf("arg.a=%d arg.b=%d arg.c=%d\n", arg.a, arg.b, arg.c);
    return 0;
}

第五节 结构体类型的程序设计实例

【例】 定义结构体类型employee存储员工的工号、姓名和销售额。输入五名员工的信息并存储到结构体类型数组emp,找出销售额最多的员工并输出该员工的姓名、工号,最后输出所有员工的总销售额。

#include <stdio.h>
#include <string.h>
struct employee                        /*定义结构体类型employee*/
{
    char no[5];
    char name[20];
    int sales;    /*销售额*/
};
​
int three(void) 
{
    struct employee emp[5];     /*结构体数组emp*/
    int i = 0, sum = 0, max = 0;
    for (i = 0; i < 5; i++)    /*依次输入五名员工的信息*/
    {
        printf("Please input the ID of employee %d:\n", i+1);
        //scanf("%s", emp[i].no);  /*输入员工的工号*/
        gets(emp[i].no);
        printf("Please input the name of employee %d:\n", i+1);
        gets(emp[i].name);            /*输入员工的姓名*/
        printf("Please input the sales of employee %d:\n", i + 1);
        scanf_s("%d", &emp[i].sales);     /*输入员工的销售额*/
        getchar();   /*接收空格符*/
        if (emp[i].sales > emp[max].sales)
        {
            max = i;    /*销售额最多的员工*/
        }
        sum = sum + emp[i].sales;   /*计算销售额最多的*/    
    }
    printf("The employee with largest sales us %s, %s\n", emp[max].no, emp[max].name);
    printf("The total sales of 5 employe e is %d\n", sum);
    return 0;
}

第六节 自定义类型

用户自定义类型的方法

【格式】 typedef 原类型符 新类型符

【功能】将"原类型符"定义成用户自选的"新类型符",此后可用"新类型符"来定义相应数据类型的变量、数组、指针变量、结构型、函数。

1.基本数据类型符的自定义

【格式】 typedef 基本数据类型符 用户类型符

【功能】 将"基本数据类型符" 定义为用户自己的“用户类型符”。

【例】 typedef float REAL; /*定义单精度实型为REAL */

void main() { REAL f1=1.2; /*该语句相当于float f1=1.2; */ ....}

2.数组类型的自定义

【格式】typedef 基本数据类型符 用户类型符[数组长度];

【功能】 以后可以使用“用户类型符”来定义由"基本数据类型符"组成的数组, 其长度为定义时确定的“数组长度”。

【例】 typedef float F_ARRAY[20];

void main() {F_ARRAY f1 = {1, 2}}; /*相当于float f1[20]={1,2};*/

3.结构型的自定义

【格式】 typedef struct {

数据类型符 成员名1;

数据类型符 成员名2;

数据类型符 成员名n;

} 用户类型符;

【例】自定义结构型举例。

typedef struct {

long num;

char name[10];

char sex;

} STUDENT;

void main(){

STUDENT stu1, stu[10];

}

4.指针型的自定义

【格式】 typedef 基本数据类型符 * 用户类型符

【功能】 以后可以使用"用户类型符" 定义 "基本数据类型符" 类型的指针变量或数组等.

【例】 自定义 指针类型举例。

typedef int *POINT_I;

void main() { POINT_I p1, p2; /*该语句相当于 int *p1, *p2 */ ...... }

第八章 编译预处理

概述

编译预处理:是指对源程序进行编译之前,先对源程序中的各种预处理命令进行处理; 然后再将处理结果和源程序一起进行编译,以获得目标代码。

本章主要介绍宏定义、文件包含和条件编译三种编译预处理命令。

预处理命令的末尾不得用“;”号结束。

第一节 宏定义

无参宏定义的一般格式如下:

【格式】 #define 宏名 字符序列

【功能】 对源程序进行编译之前,预处理程序用"字符序列"替换程序中出现的“宏名”.

【说明】 1.“宏名” 是标识符的一种, 命名规则与标识符相同,通常采用大写字母;

2."字符序列"可以是常量、表达式及各种符号等。替换宏名。

3.宏定义不是C语言的语句,所以不能在行尾加分号。否则,宏展开时会将分号作为字符串的一个字符,用于替换宏名。

4.宏展开时,预处理程序公按宏定义简单地替换宏名,而不作任何检查。

5.对于双引号括起来的字符串内的字符,即使与宏名重名,也不进行宏展开.

6.宏定义命令#define出现在函数的外部,宏名的作用是从宏定义开始到本文件结束。

7.可以使用 #undef 标识符 提前终止宏定义的作用域

【例】 #define PI 3.14 main() { ... #under PI ....}

以上PI的作用域从#define PI3.14 命令行开始,到#undef PI 命令行结束。

8.宏定义时,可以引用已定义的宏名,宏展开是逐层替换的。

【例】

#include <stdio.h>

#define PI 3.1415926

#define R 3

#define L 2 * PI * R

#define s PI * R * R

int main(void)

{ printf("length = %.2f, area = %.2f\n", L, S); }

9.使用宏定义的优点

(1) 提高源程序的可读性;

(2)源程序的可修改性,修改宏定义中的"字符序列" 可以起到一改全改的作用。

二、有参宏定义

【格式】 #define 宏名(形参表) 带参数的字符序列

【说明】

1.宏名 和 “(” 必须紧挨着,它们之间不得留有空格。否则,编译程序将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。

【例】 设 # define s (a,b)a*b

程序中mul = S(x, y)宏展开结果是mul=(a, b)a*b(x,y)

2.有参的宏展开只是将实参作为字符串,简单地替换形参字符串,绝对不要按自己的理解加括号。最好为所有形参和整个字符串,均加一对圆括号,以保证任何情况下都把宏定义作为一个整体。

【例】 #define S(r) 3.1415926*r*r ..... area =S(a+b)

则宏展开结果为area=3.1415926a+ba+b;

若想得到的宏展开结果是: area=3.1415926*(a+b)*(a+b)

则正确的是在宏定义时给字符序列中的形参加上括号: #define S(r) 3.1415926*(r)*(r)

【例】 #define f(x) x*x main(){ int i; i = f(4+4)/f(2+2); printf("%d\n", i);} 执行后输出结果是( 28 )

【解析】 宏展开后 i = 4 + 4*4 + 4/2 + 2*2 + 2 = 28

有参宏和有参函数不同点:

1.调用有参函数时,是先计算实参表达式的值,然后再传给形参而展开有参宏,不计算实参表达式的值, 只是将实参简单地替换形参。

2.有参函数中,实参和形参需要定义类型,且类型一致;而有参宏中,形参是没有类型的,因此用干替换的实参,什么类型堵可以。

3.无论调用多少次有参函数。都不会使目标程序变长同,但每次调用都要占用系统时间进行调用现场的保护和恢复而有参宏,由于宏展开是在编译预处理时进行的,所以不占用运行时间,但是每使用一次,都会使目标程序增大。

4.有参函数在调用时进行值的处理,需要分配临时的内存单元,而用参宏展开是编译预处理时进行,展开时不分配内存单元,不进行值的传递处理。

第二节 文件包含命令

文件包含是指一个源文件A可以将另一个源文件B的全部内容包含进来。这样就可以使用源文件B的全部数据和函数。

文件包含命令的格式如下:

【格式一】 #include "文件名"

【格式二】 #include <文件名>

两种格式的区别:

1.格式一(使用双引号), 系统首先到当前源文件所在的目录查找被包含文件,如果没有找到,再到系统指定的“包含文件目录”(由用户在配置环境时设置)去查找,必要时在文件名前加上所在的路径。

2.格式二(使用尖括号), 直接到系统指定的"包含文件目录"支查找。一般情况下,如果包含C语言的标准头文件,则使用这种格式。一般建议采用格式二。

【说明】

  1. 编译预处理时,预处理程序将查找指定的被包含文件,并将其复制到命令#include出现的位置上。

  2. 文件头部的被包含文件称为“标题文件”或“头部文件”, 如: #include <stdio.h>

  3. 一条文件包含命令, 只能指定一个被包含文件。

    如: #include <stdio.h> #include <math.h>

4.文件包含命令允许嵌套。

5.若被包含文件的内容发生变化,则应该对包含的所有源文件重新进行编译。

第三节 条件编译命令

条件编译命令使得用户可以选择对源程序的一部分内容进行编译,即不同的编译条件产生不同的目标程序。

条件编译命令具有以下几种形式:

【格式一】 #ifdef 标识符

程序段 1

[#else

程序段2]

#endif

【功能】 当“标识符” 已经被#ifdef命令定义,则编译程序段1; 否则编译程序段2。

【格式二】 #ifndef 标识符

程序段 1

[#else

程序段2]

#endif

【功能】当“标识符”没有被#ifdef命令定义,则编译程序段1; 否则编译程序段2。

【格式三】 #if 表达式

程序段 1

[#else

程序段2]

#endif

【功能】当表达式为值(非0),则编译程序段1; 否则编译程序段2。

【例】输入一串字母,根据需要设置条件编译,使之其将字母全部改为大写输出,或全部改为小写输出,直输入#结束.

#include <stdio.h>
#define LETTER 1  /*LETTER的值是0或非零编译不同语句*/
​
int demo11(void)
{
    char ch;
    while (1)
    {
        ch = getchar();
        if (ch = '#') break;   /*输入#时结束循环*/
    
#if LETTER
        if (ch >= 'a' && ch <= 'z') ch = ch - 32;
#else
        if (ch >= 'A' && ch <= 'Z') ch = ch + 32;
#endif
        putchar(ch);
    }
    return 0;
​
}

第九章 文件

第一节 文件概述

一、基本概念

1.文件定义

文件是存储在外部存储介质上的数据集合。

2.文件命名

盘符:路径\文件主名.扩展名

【说明】1) 盘符表示文件所在的磁盘。

2) 路径是由目录序列组成的,目录间的分隔符号为“\”。C语言规定其中的符号""需要写成转义字符"\".

3)文件主名和扩展名是由字母开头的字母数字等字符组成的。

C语言中,常见的文件扩展名如下:

C:C源程序文件

obj: C源程序经过编译后生成的目标文件。

exe: 目标文件经过链接后生成的可执行文件。

数据文件的扩展名常用dat, 文本文件的扩展名常用txt。

3.文件分类

1)按照文件的内容划分

① 程序文件: 源文件 目标文件 可执行文件

②数据文件

2)按照文件的组织形式划分

①顺序存取文件

②随机存取文件

3)按照文件的存储形式划分

①文本文件: 当从内存输出到外在时,数据转换成对应的ASCII码来存放,一字符占一个字节 。当读入内存时,又把ASCII码转换为数据。便于对字符逐一进行处理。多花费存储空间和与二进制间转换时间。

②二进制文件:数据不经过任何转换、按计算机内的存储形式直接存放在磁盘上,占用空间少,存取速度快。

【例】 int a=10000; 其各种存储:

①在内存存储形式: 00100111 00010000 ②二进文件存储形式(外存) 00100111 00010000

③文本文件的形式存放----ASCII码(外存) 00110001 00110000 00110000 00110000 00110000

注意:数字0的ASCII码值对应的十进制数是 48

4)按照文件的存储介质划分

①普通文件即存储介质文件(磁盘、磁带等)。

②设备文件(键盘、显示器和打印机等)文件。

4.文件存取方式

1)顺序存取是自上而下逐一地存取文件的内容。保存数据时,将数据附加到文件的尾部。顺序存取方式常用于文本文件,被存取的文件则称为顺序文件。

2)随机存取是以一个完整的单位进行数据的读取和写入, 随机存取方式常用于二进制文件,被存取的文件则称为随机文件。

5.文件系统

1)缓冲文件系统: 高级文件系统, 系统自动为正在使用的文件开辟内存缓冲区。C语言使用的是缓冲文件系统。

2)非缓冲文件系统:低级文件系统, 由用户在程序中为每个文件设定缓冲区。

6.文件读写

读文件; 将磁盘文件中的数据传送到计算机内存的操作。

写文件:从计算机内存向磁盘文件传送数据的操作。

二、文件型指针

C语言规定文件类型是一种特殊的"结构型", 定义:

typedef struct       /*文件类型声明*/
{
    short level;     /*缓冲区满/空程度*/
    unsigned flags;  /*文件状态标志*/
    char fd;         /*文件描述符*/
    unsigned hold;   /*检查缓冲区状态*/
    short bsize;     /*缓冲构大小*/
    char* buffer;    /*文件缓冲区位置*/
    char* curp;      /*当前读写位置*/
    unsigned istemp; /*临时文件指示*/
    short token;     /*用于有效检查*/
} FILE;              /*结构体类型名FILE*/  

文件结构体类型已经由系统在头文件"stdio.h"中。使用文件型指针对文件进行访问。

文件型指针的定义方法: FILE *文件型指针名1, *文件型指针名2, ....;

第二节 文件的打开与关闭函数

一、文件打开函数

【格式】 FILE *fopen(“文件名”, “使用方式”)

【功能】以"使用方式", "文件名"对应的文件。同时自动给该文件分配一个内存缓冲区。

【返回值】 正常打开,返回指向文件结构体的指针;打开失败,返回NULL。

【例】 打开文件的常用程序段

#include <stdio.h>     /*FILE的头文件stdio.h*/
#include <stdlib.h>    /*exit(0)函数的头文件stdlib*/
FILE *fp;
if((fp=fopen("文件名", “使用方式”))==NULL)
{
  printf("Can not open file!\n");
  exit(1);        /*强制中止程序运行*/
}
........          /*文件正确打开,可对文件操作*/

文件使用方式: r:读 w:写 +: 读写 a:追加 b: 二进制

exit()函数:

【函数首部】 void exit(程序状态值)

【功能】 关闭已经打开的所有文件,结束程序运行返回操作系统,并将“程序状态值”为0时,表示程序正常退出; 当它为非0时,表示程序出错退出。

二、文件关闭函数

【格式】 fclose(FILE *fp)

【功能】关闭文件指针fp所指向的文件,同时自动释放分配给该文件的内存缓冲区。

【返回值】若正确关闭指定的文件,则返回0;否则返回1.

三、标准设备文件的打开和关闭

三种标准输入/输出设备的使用不必事先打开对应的设备文件,因为在系统启动后,已自动打开。使用后,也不必关闭。因为在退出系统时,将自动关闭。

第三节 文件的读/写函数

一、文件尾测试函数

【格式】 feof(fp)

【功能】判断fp所指向的文件是否到达文件尾。是文件尾返回非0,否则返回0.

【例】 常用语句

while (!feof(fp)) /*若不是文件尾则继续循环*/ {........}

二、字符读/写函数

1.写字符函数

【格式】 fputc(char ch, FILE *fp)

【功能】将ch中的字符写到fp所指向的文件的当前位置。同时将读/写位置指针后移1字节,即指向下一个读/写位置。

2.读字符函数fgetc()

【格式】fgetc(fp)

【功能】从fp所指向的文件当前位置读入单个字符。将读/写位置指针后移1字节,即指向下一个读/写位置。

【例】将一个磁盘文件的信息复制到另一个磁盘文件。

#include <stdio.h>
#include <stdlib.h>
​
int filecopy(void)
{
    char ch;
    char rfile[20], wfile[20];
    FILE* fpr, * fpw;   /*定义文件型指针*/
    printf("Please input the source filename\n");
    gets(rfile);         /*从键盘输入源文件名*/
    printf("Please input the destination filename\n");
    gets(wfile);       /*从键盘输入目标文件名*/
    if ((fpr = fopen(rfile, "r")) == NULL)    /*以只读方式打开源文件*/
    {
        printf("Cannot open the file\n");
        exit(1);
    }
    if ((fpw = fopen(wfile, "w")) == NULL)    /*以只写方式找开目标文件*/
    {
        printf("Cannot open the file\n");
        exit(1);
    }
​
    while ((ch = fgetc(fpr)) != EOF)
    {
        fputc(ch, fpw);    /*逐个字符复制*/
    }
​
    fclose(fpr);   /*关闭fpr所指向的文件*/
    fclose(fpw);   /*关闭fpw所指向的文件*/
    return 0;
}

三、字符串读/写函数

1.写字符串函数fputs()

【格式】fputs(str, fp)

【功能】将str指向的一个字符串,舍去结束标记符"\0"后写入fp所指向文件的当前位置。str也可以是一字符串常量,同时将读/写位置指针后移动strlen(字符串)字节.

2.字符串读函数fgets()

【格式】fgets(str, length, fp)

【功能】从fp所指向的文件当前位置读取length - 1 个字符,在其后补充一个字符串结束标记符'\0', 组成字符串并存入str指定的内存区。如果读取的前 length - 1个字符中有"回车符", 则只读到回车符为止, 补充结束标记符 '\0' 组成字符串(包括该回车符)。如果读取前 n-1 个字符中遇到文件尾,则将读取的字符后面补充结束标记符 '\0' 组成字符串。

【例】 从键盘读取3个字符串,写入d盘根目录下名为"string1.txt"的文本文件.

#include <stdio.h>
#include <stdlib.h>
​
void demo12()
{
    FILE* fp;
    int i;
    char s1[3][100];
    for (i = 0; i < 3; i++)  /*输入3个字符串存数组*/
    {
        gets(s1[i]);
    }
​
    if ((fp = fopen("d:\\string1.txt", "w")) == NULL)
    {
        printf("Can not open file!\n");
        exit(1);
    }
    for (i = 0; i < 3; i++)
    {
        fputs(s1[i], fp);  /*将数组s1中的3个字符串写入fp指向的文件*/
    }
    fclose(fp);  /*关闭fp所指向的文件*/
    return 0;
}

【例】从d盘根目录下名为"string1.txt"的文本文件中读取一个字符串或前20个字符组成字符串,并显示在屏幕上。

#include <stdio.h>
#include <stdio.h>
​
void demo14()
{
    FILE* fp;
    char s[21];
​
    if ((fp = fopen("d:\\string1.txt", "r")) == NULL)
    {
        printf("Can not open file!\n");
        exit(1);
    }
​
    fgets(s, 21, fp);  /*从fp指向的文件中读取一个字符串或前20个字符组成字符串存入数组s*/
    fputs(s, stdout);  /*输出字符数组s中的字符串到标准输出设备(显示器)*/
    fclose(fp);       /*关闭fp所指向的文件*/
    return 0;
}

注意:

四、数据块读/写函数

1.写数据块函数

【格式】fwrite(buffer, size, count, fp)

【功能】将buffer地址开始的count个数据(每个数据字节数为size)写入fp指向的文件。

2.读数据块函数

【格式】fread(stu, size, count, fp)

【功能】从fp所指向的文件当前位置读取count个数据,每个数据的字节数为size,组成count个长度为size个字节的数据存入以stu为首地址的内存区。

【例】从当前盘当前路径(C系统目录)下名为“intb1.dat”的二进制数据文件中读取10个整数, 输出到显示器上。(需要验证)

#include <stdio.h>
#include <stdlib.h>
​
void demo15() {
    FILE* fp;
    int i, a[10];
    if ((fp = fopen("intb1.dat", "rb")) == NULL)
    {
        printf("Can not open file!\n");
        exit(1);
    }
​
    fread(a, sizeof(int), 10, fp);   /*从fp指向文件中读取10个整数存入数组a*/
​
    for (i = 0; i < 10; i++)  /*输出数组a中10个整数*/
    {
        printf("  %d   ", a[i]);
    }
​
    fclose(fp);    /*关闭fp所指向的文件*/
}

【例】从键盘上读取10个整型数据, 写入当前盘当前路径(C系统目录)下名为"intb1.dat"的二进制数据文件中。(需要验证)

#include <stdio.h>
#include <stdlib.h>
​
void writefile() {
    FILE* fp;
    int a[10], * p = a;
    if ((fp = fopen("intb1.dat", "wb")) == NULL)
    {
        printf("Can not open file!\n");
        exit(1);
    }while (p < a + 10) {
        scanf("%d", p++);  /*从键盘读取10个整数存入数组a*/
    }
​
    fwrite(a, sizeof(int), 10, fp); /*将数组a中10个整数写到fp指向的文件*/
    fclose(fp);                /*关闭fp所指向的文件*/
    return 0;
}

五、格式化读/定函数

1.格式化写函数fprintf()

【格式】 fprintf(fp, 格式控制字符串,输出项表列);

【功能】按格式输出项表列的各个数据依次写入fp所指文件.

【例】fprintf(fp, "%d%d", x, y);

把x和y两个整型变量中的整数按%d格式输出到fp所指的文件中.

2.格式化读函数fscanf()

【格式】 fscanf(fp, 格式控制字符串, 地址表列);

【功能】从fp所指文件按照"格式控制字符串"的格式把读出的数据保存"输入项表"中的各变量。

【例】若文件指针fp已指向一个已打开的文本文件,a、b分别为整型变量,则以下语句从fp所指的文件中读入两个整数放入变量a和b中: fscanf(fp, "%d%d", &a, &b);

【例】阅读下列程序,写出程序运行的输出结果。 54321

#include <stdio.h>
int outputdemo1(void)
{
    FILE* fp;
    int i, x[5] = { 1, 2,3,4,5 };
    fp = fopen("text.dat", "w+");
    for (i = 0; i < 5; i++)
    {
        fprintf(fp, "%d\n", x[i]);
    }
    rewind(fp);
​
    for (i = 0; i < 5; i++)
    {
        fscanf(fp, "%d", &x[4 - i]);
    }
​
    fclose(fp);
    for (i = 0; i < 5; i++)
    {
        printf("%d", x[i]);
    }
    return 0;
}

第四节 文件的定位函数

一、文件指针复位函数

文件指针复位函数rewind()

【格式】 rewind(fp)

【功能】将fp所指向的文件的内部位置指针置于文件开头。即把文件的位置指针重新定位到fp所指向文件的起始位置。

【例】将d盘路径为fengyi\bkc下的文本文件chlx中内容显示并复制到同一路径下的另一文本文件ch2.c。

#include <stdio.h>
#include <stdlib.h>
​
int filecopydemo(void)
{
    FILE* fp1, * fp2;
    fp1 = fopen("D:\\study_code\\go_study\\src\\helloworld.go", "r");
    fp2 = fopen("D:\\study_code\\go_study\\helloworld.c", "w");
    while (!feof(fp1))   /*输出fp1所指向的文件内容*/
    {
        putchar(fgetc(fp1));
    }
    rewind(fp1);   /*将fp1所指向文件的位置指针指向文件头*/
    while (!feof(fp1))
    {
        fputc(fgetc(fp1), fp2);   /*fp1所指向的文件中读取 字符写入到fp2所指向的文件*/
    }
    fclose(fp1);
    fclose(fp2);
    return 0;
}

二、文件随机定位函数

【函数头】 int fseek(FILE *fp, long offset, int from)

【功能】将fp所指向的文件内部指针从from指定的起始位置移动offset个字节,指向新的位置。

【参数】offset 长整型表达式,表示从from为起始位置的偏移字节数。可以取正数(向文件尾方向移动)、0(不移动)、负数(向文件头方向移动)。若offset是表达式,用(long)(表达式)强制转换为长整型。

from 确定起始位置的参数。

可以选取下列整数或对应的符号常量:

【例】 文件随机定位函数的例子。

fseek(fp, -2L, 1); /*从当前位置前移2字节的位置*/

fseek(fp, 5L, SEEK_SET); /*从文件头向后移5字节的位置*/

fseek(fp, -6L, SEEK_END); /*从文件尾向前移6字节的位置*/

第五节 文件的程序设计实例

【例】以下程序执行后abc.dat文件的内容是( Chinang )

#include <stdio.h>
writedemo()
{
    FILE* pf;
    char* s1 = "China", * s2 = "Beijing";
    pf = fopen("abc.dat", "wb+");
    fwrite(s2, 7, 1, pf);
    rewind(pf);    /*文件位置指针回到文件开头*/
    fwrite(s1, 5, 1, pf);
    fclose(pf);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值