C语言
1.第一章 程序设计和C语言
1.1 编译/执行 C 程序
#include <stdio.h> //这是编译预处理指令
int main() //定义主函数
{ //函数开始的标志
/* 我的第一个 C 程序 */
printf("Hello, World! \n"); //输出所指定的一行信息
return 0; //函数执行完毕时返回函数值0
} //函数结束标志
实例解析:
- 所有的 C 语言程序都需要包含 main() 函数。 代码从 main() 函数开始执行。
- /* … */ 用于注释说明。
- printf() 用于格式化输出到屏幕。printf() 函数在 “stdio.h” 头文件中声明。
- stdio.h 是一个头文件 (标准输入输出头文件) , #include 是一个预处理命令,用来引入头文件。 当编译器遇到 printf() 函数时,如果没有找到 stdio.h 头文件,会发生编译错误。
- return 0; 语句用于表示退出程序。
1.2什么是计算机语言?
计算机语言是人和计算机都能识别的语言
机器语言:计算机工作基于二进制,从根本上说。计算机只能接受0和1组成的指令。
计算机能直接识别和接受的二进制代码称为机器指令,机器指令的集合就是该计算机的机器语言。
什么是程序?
程序就是一组计算机能识别和执行的指令
高级语言
计算机不能直接识别高级语言,需要翻译,用一种称为编译程序的软件把高级语言写的源程序转换为机器指令的程序,然后让计算机执行机器指令程序,最后得到结果。
高级语言不同的发展阶段
- 非结构化的语言
- 结构化语言
- 面向对象的语言
1.3 C语言的发展及其特点
C语言发明1972年,1973年被改写,然后到1978年就风靡全球
C语言的特点
- 语言简洁,紧凑使用方便,灵活。
- 运算符丰富
- 数据类型丰富
- 具有结构化的控制语句
- 语法限制不太严格,程序设计自由度大
- C语言允许直接访问物理地址,能进行位(bit)操作,能实现汇编语言的大部分功能,可以直接对硬件进行操作。
- C语言编写的程序可移植性好
- 生成目标代码质量高,程序执行效率高
1.4 C语言程序结构
1.一个程序由一个或多个源程序文件组成
- 预处理指令如#include <stdio.h>,对#include <stdio.h>指令来说就是,将stdio.h头文件的内容读进来,取代#include <stdio.h>。由预处理得到的结果与程序其他部分一起,组成一个完整的。可以用来编译的最后的源程序,然后由编译程序对该源程序正式进行编译,才得到目标程序。
- 全局声明:即在函数之外进行的数据声明。
- 函数定义:要指定每个函数的功能
2.函数是C语言程序的主要组成部分
1.一个C语言程序是由一个或多个函数组成的,其中必须包含一个main函数。
2.一个小程序只包含一个源程序文件,在一个源程序文件中包含若干个函数(其中有一个main函数)。
3.一个函数包括两个部分
-
1.函数首部
-
int max (int x, int y) | | | | | | v v v v v v 函数类型 函数名 函数参数类型 函数参数名 函数参数类型 函数参数名
-
2.函数体
-
即函数首部下面的花括号内的部分。如果在一个函数中包括有多层花括号,则最外层的一对花括号是函数体的范围
-
声明部分:定义在本函数中所用到的变量。
-
执行部分:由若干个语句组成,指定在函数中所进行的操作。
4.程序总是从main函数开始执行。而不论main函数在整个程序中的位置如何。
5.程序中要求计算机完成的操作是由函数中的C语句完成的。
6.在每个数据声明和语句的最后必须有一个分号。
7.C语言本身不提供输入输出语句。
8.程序应当包含注释
2.第二章 算法——程序的灵魂
2.1 程序 = 算法+数据结构
1.对数据的,描述。在程序中要指定用到那些数据,以及这些数据的类型和数据的组织形式。这就是数据结构。
2.对操作的描述。要求计算机进行操作的步骤,也就是算法。
计算机算法分为两大类:数值运算算法和非数值运算算法。
算法的特性
(1)有穷性:一个算法应包含有限的操作步骤,而不能是无限的。
(2)确定性:算法中的每一个步骤都应当是确定的,而不是应当是含糊的,模棱两可的。
(3)有零个或多个输入:所谓输入是指在执行算法时需要从外界取得必要的信息。
(4)有一个或多个输出:算法的目的是为了求解,“解”就是输出。
(5)有效性:算法中的每一个步骤都应当能有效地执行,并得到确定的结果。
三种基本结构和改进的流程图
(1)顺序结构
(2)选择结构
(3)循环结构
1.当型(while型)循环结构
2.直到型(until型)循环结构
3.最简单的C程序设计———顺序程序设计
3.1 顺序程序设计举例
例如温度转换公式是 c=5/9(f-32)
用N-S图来表示是
用代码表示是:
#include<stdio.h>
int main(){
float f,c;
f=64.0;
c=(5.0/9)*(f-32);
printf("f=%f\nc=%f\n",f,c);
return 0;
}
3.2 数据的表现形式及其运算
1.常量和变量
1.常量
-
整型常量:没有小数点的整数
-
实型常量:有两种表示,
1.十进制小数形式由数字和小数点组成。
2.指数形式,如12.34e3(代表12.34x10x10x10)
-
字符常量。
普通字符:用单撇号括起来的字符,如‘A’
转义字符:以字符"\n"开头的字符序列
-
字符串常量
如"BOY",用双撇号把若干个字符括起来,字符串常量是双撇号中的全部字符不包含双撇号本身
-
符号常量
用#define指令,指定用一个符号名称代表一个常量,如:#define PI 3.14
使用字符常量的好处:
- 含义清楚。看程序PI就大致可以知道他代表圆周率。
- 在需要改变程序中多处用到的同一个常量时,能做到一改全改
注意:要区分符号常量和变量,不要把符号常量误认为变量。符号常量不占用内存,只是一个临时符号,代表一个值,在预编译后这个符号就不存在了,所以不能对符号常量赋新的值。为与变量相区别,习惯上符号常量用大写来表示
2.变量
变量代表一个有名字的具有特定属性的一个存储单元。它是用来存放数据,也就是存放变量的值。在程序运行期间,变量的值是可以改变的
变量必须先定义后使用。在定义时指定该变量的名字和类型。一个变量应该有一个名字以便被引用。注意区分变量名和变量值的概念
从变量中取值,实际上是通过变量名找到相应的内存地址,从该存储单元中读取数据
3.常变量
C99中允许使用常变量,方法是在定义变量时,前面加一个关键字const,
const int a=3;
**常变量与变量的异同:**常变量具有变量的基本属性;有类型,占存储单元,只是不允许改变其值。可以说常变量是有名字的不变量,而常量是没有名字的不变量。有名字就便于在程序中被引用。
4.标识符
#合法
sum,average,_total,Class;
#不合法
M.D.John
在计算机高级语言中,用来对变量,符号常量名,函数,数组,类型等命名的有效字符序列称为标识符。简单来说标识符就是一个对象的名字。
C语言规定标识符只能由字母数字和下划线三种字符组成,且第一个字符必须为字母或下划线。
2.数据类型
|- 基本整型(int)
|- 短整型(short int)
|- 长整型(long int)
|————整数类型|
| |- 双长整型(long long int)
| |- 字符型(char)
| |- 布尔型(bool)
|————基本类型|
| |
| |
| | |-单精度浮点型(float)
| |————浮点类型|-双精度浮点型(double)
| |-复数浮点型(float_complex,double_complex,long_complex)
|
|
|
|
数据类型————————————枚举类型(enum)和空类型(void)
|
|
|
|
| |-指针类型(*)
| |-数组类型([])
|————派生类型-|-结构体类型(struct)
|-共用体类型(union)
|-函数类型
3.整型数据
-
整形数据的分类
(1)基本整型(int型):长度四个字节
(2)短整型(short int):长度两个字节
(3)长整型(long int):长度四个字节
(4)双长整型(long long int):长度八个字节
-
字符数据
(1)字符型(char):长度一个字节
(2)char定义的是单个字符,当定义数组时才为字符串。
-
浮点数据
(1.)单精度浮点型(float):长度为四个字节
(2)双精度浮点型(double):长度八个字节
(3)长双精度浮点型(long double):长度十六个字节
-
怎么确定常量的类型
(1)整型常量:不带小数点的数值是整型常量,但应注意其有效范围。
(2)浮点型常量:凡是一小数点或指数形式出现的实数均是浮点型常量,在内存中都以指数形式储存。
3.3 运算符和表达式
-
C运算符
(1)算术运算符 (+,-,*,/,++,--) (2)关系运算符 (>,<,==,>=,<=,!=) (3)逻辑运算符 (!,&&,||) (4)位运算符 (<<,>>,~,\,^,&) (5)赋值运算符 (=及其拓展赋值运算符) (6)条件运算符 (?:) (7)逗号运算符 (,) (8)指针运算符 (*和&) (9)求字节数运算符 (sizeof) (10)强制转换运算符 ((类型)) (11)成员运算符 (.,->) (12)下标运算符 ([]) (13)其他 (如函数调用运算符())
-
自增(++),自减(–)运算符
++i,--i, (在使用i之前,先使i的值加(减)1) i++,i--, (在使用i之后,先使i的值加(减)1)
-
算数表达式和运算符的优先级与结合性
用算术运算符和括号将运算对象连接起来的,符合C语法规则的式子称为C算术表达式
算术运算符是自左向右(左结合性),赋值运算符号是自右至左(左结合性)
-
不同类型数据间的混合运算
(1)+,-,*,/运算的两个数中有一个数为float或double型,结果是double型,因为系统将所有float型数据都先转换为都变为double型再运算。
(2)如果int型与float或double型数据进行运算,先把int型和float型转换为double型,然后进行运算,结束输出double型数据。
(3)字符(char)型数据与整数数据进行运算,就是把字符ASCII代码与整型数据进行运算。
-
强制类型转换运算符
一般形式: (类型名)(表达式) (double)a (将a转换为double型数据) (int)(x+y) (将x+y的值强制转换为int型数据) (float)(5%3) (将5%3的值强制转换成float型数据)
在强制类型转换时,得到一个所需类型中间数据,而原来变量的类型未发生变化
在C语言中有两种类型转换:
(1)一种是在运算时不必用户干预,系统自动进行的类型转换
(2)另一种就是强制转换
3.4 c语句
一个函数包含声明部分和执行部分,执行部分是由于语句组成的,语句的作用是向计算机系统发送操作指令,要求执行相应的操作。
-
控制语句
if()...else... (条件语句) for()... (循环语句) while()... (循环语句) do...while() (循环语句) continue (结束本次循环语句) break (中止执行switch或循环语句) continue (结束本次循环) switch (多分支选择语句) return (从函数返回语句) goto (转向语句,在结构化程序中基本不用goto)
-
函数调用语句
函数调用语句由一个函数调用加一个分号构成 printf("hello world"); 调用格式化输出函数printf()
-
表达式语句
一个表达式的最后加一个分号就成了一个语句,一个语句必须在最后有一个分号,分号是语句中不可缺少的组成部分,而不是两个语句间的分隔符号。
-
空语句:指只有一个分号的一行代码。
-
复合语句
可以用{}把一些语句和声明括起来成为复合语句(又称语句块)
3.5 语句的输入输出
1.输入输出例子
#include<stdio.h>
#include<math.h>
int main(){
double a,b,c,disc,x1,x2,p,q;
scanf("%1f%1f%1f",&a,&b,&c);
disc=b*b-4*a*c;
p=-b/(2.0*a);
q=sqrt(disc)/(2.0*a);
x1=p+q;
x2=p-q;
printf("x1=%7.2f\nx2=%7.2f\n",x1,x2);
return 0;
}
2.有关数据输入输出的概念
-
输入输出是以计算机为主体而言,输入是指人通过输入设备(键盘,鼠标)向计算机输入数据,输出是指计算机通过输出设备(显示屏)向人输入数据。
-
C语言本身不提供输入输出语句,输入输出操作是由C标准函数库中的函数来实现的。
例如:putchar(输出字符),getchar(输入字符),printf(格式化输出),scanf(格式输入),puts(输出字符串),gets(输入字符串)
-
要在程序文件的开头用预处理指令#include把有关文件放在本程序中
3.用printf函数输出数据
-
printf函数的一般格式
printf函数的一般形式 printf(格式控制,输出表列); (1)"格式控制"是用双撇号括起来的字符串,称为格式控制字符串,简称格式字符串, 1.格式声明:格式声明由"%"和格式字符组成,作用是将输出的数据转换未指定的格式后输出。 2.普通字符:普通字符即需要在输出时原样输出字符。 (2)输出表列:输出表列是程序需要输出的一些数据,可以是常量,变量或表达式。 printf(参数1,参数2,参数3,参数4,.....参数n);
-
格式字符
d格式符:用来输出一个有符号的十进制整数 c格式符:用来输出一个字符 s格式符:用来输出一个字符串 f格式符:用来输出实数(包括单,双精度,长双精度),以小数形式输出,整数部分全输出,小数部分输出六位 指定数据宽度和小数位,用%m.nf,m为宽度,n为小数位。 e格式符:用格式声明%e指定以指数形式输出实数 i格式符:作用与d格式符相同,按十进制整型数据的实际长度输出。 o格式符:以八进制整数形式输出。 u格式符:用来输出无符号型数据,以十进制整数形式输出 g格式符:用来输出浮点数,系统自动选f格式或e格式输出,选择其中长度较短的格式,不输出无意义的0;
4.用scanf函数输入数据
-
scanf函数的一般形式:scanf(格式控制,地址表列)
-
scanf函数中的格式声明:以%开始,以一个格式字符结束,中间可以插入附加的字符。
-
使用scanf函数应注意的问题
- scanf函数中的格式控制后面应当是变量地址,而不是变量名。
- 如果在格式控制字符串中除了格式声明以外还有其他字符,则在输入数据时在对应的位置上应输入与这些字符相同的字符。
- 在用“%c”格式声明输入字符时,空格字符和“转义字符”中的字符都作为有效字符输入。
- 在输入数值数据时,如输入空格,回车,tab键或遇到非法字符。
-
字符输入输出函数
putchar()函数
#include<stdio.h> int main(){ char a='b'; char b='o'; char c='y'; putchar(a); putchar(b); putchar(c); return 0; }
getchar()函数
#include<stdio.h> int main(){ a=getchar(); b=getchar(); c=getchar(); putchar(a); putchar(b); putchar(c); return 0; }
4.选择结构程序设计
4.1 选择结构和条件判断
- 选择结构有两种if语句和switch语句
- 选择结构是依据判断来进行选择,先判断条件再进行选择,选择后再执行
4.2 用if语句实现选择结构
-
用if语句处理选择结构举例
//输入两个实数,按小到大的顺序输出这两个数 #include<stdio.h> int main(){ int a,b,c; printf("请输入两个数进行比较大小:"); scanf("%d%d",&a,&b); if(a>b){ c=a; a=b; b=c; } printf("a=%d,b=%d",a,b); return 0; }
-
if语句的一般形式
一般形式: if(表达式) 语句1 [else 语句 2]
根据if语句的一般形式,if语句可以写成不同的形式
(1)if(表达式) 语句1
(2)if(表达式)
语句1
else
语句2
(3)if(表达式1) 语句1
else if(表达式2) 语句2
else if(表达式3) 语句3
. .
. .
. .
else if(表达式n) 语句n
else 语句n+1
//整个if语句可以写在多行也可以写在一行
//if语句无论写在几行上,都是一个整体,属于同一个语句。
//在if语句中要对给定的条件进行检查,判定所给的条件是否成立,判断的结果是一个逻辑值“是”或“否”。
### 4.3 关系运算符和关系表达式
**关系运算符**:在C语言中,比较符(或称比较运算符)称为关系运算符。所谓“关系运算”就是“比较运算”,将两个数值进行比较,判断其比较的结果是否符合给定的条件。
1. 关系运算符及其优先次序
C语言提供六种关系运算符:
1.< (小于)
2.<= (小于等于)
3.> (大于)
4.>= (大于等于)
5.== (等于)
6.!= (不等于)
//1,2,3,4的优先级相同;5,6优先级相同且低于1,2,3,4,
关系运算符的优先级低于算术运算符
关系运算符的优先级高于赋值运算符
2. 关系表达式:用关系运算符将两个数值或数值表达式连接起来的式子,称为关系表达式。
### 4.4 逻辑运算符和逻辑表达式
**逻辑表达式**:用逻辑运算符将关系表达式或其他逻辑量连接起来的式子就是逻辑表达式
1. 逻辑运算符号及其优先次序
//有三种逻辑运算符:与(AND),或(OR),非(NOT)
运算符 | 含义 | 举例 | 解释
&& |逻辑与(AND) | a&&b |如果a和b都为真,则结果为真,否则为假
|| |逻辑或(OR) | a||b |如果a和b中有一个为真。则结果为真,二者都为假,结果为假
! |逻辑非(NOT) | !a |如果a为假则!a为真,如果a为真则!a为假,
!(非) (高)
算术运算符 |
关系运算符 ^
&&和|| |
赋值运算符 (低)
2. 逻辑表达式
```c
//逻辑表达式的值应该是一个逻辑量"真"或"假",由逻辑运算符和数据以及算术运算符构成。
//在逻辑表达式中作为参加逻辑运算的运算对象可以是0("假")或任何非0的数值(按"真"对待)
a和b值分别为1和2,a||b的值为真
a和b值分别为1和2,a&&b的值为真
a和b值分别为1和2,!a&&!b的值为真
4.5 条件运算符和条件表达式
条件运算符:条件运算符由两个符号(?和:)组成,必须一起使用。要求有三个操作对象,称为三目运算符号。
条件运算符的一般形式为
表达式1?表达式2:表达式3;
先判断表达式1的真假,为真则执行表达式2,为假则执行表达式3。
有点像if语句
4.6 选择结构的嵌套
在if语句中又包含一个或多个if语句称为if语句的嵌套
if语句的嵌套的一般形式
if()
if() 语句1
else 语句2
else
if() 语句3
else 语句4
//if与else的配对关系。else总是与它上面的最近的未配对的if配对。
4.7 用switch语句实现多分支选择结构
switch语句:是多分支选择语句,可以代替if语句解决程序冗长的代码
switch()
{
case 常量1:语句1
case 常量2:语句2
case 常量3:语句3
. . .
. . .
. . .
case 常量n:语句n
default: 语句n+1
}
举例
#include<stdio.h>
int main(){
void action1(int,int),action2(int,int);
char ch;
int a=15,b=20;
ch=getchar();
switch(ch){
case 'a':
case 'A':action1(a,b);break;
case 'b':
case 'B':action2(a,b);break;
default:putchar('\a');
}
return 0;
}
void action1(int x,int y){
printf("x+y=%d\n",x+y);
}
void action2(int x,int y){
printf("x*y=%d\n",x*y);
}
5.循环结构程序设计
5.1 为什么需要循环结构
循环结构可以帮我们处理需要重复重复的问题,它能帮我们节约时间
5.2 用while语句实现循环
while语句:
//一般形式
while(表达式) 语句
//while语句称为循环条件表达式
while语句:只有当循环条件表达式为真(即给定条件成立),就执行循环体语句
它的执行特点:先判断条件表达式,后执行循环体语句
例子
#include<stdio.h>
int main(){
int i=1,sum=0;
while(i<=100){
sum=sum+i;
i++;
}
printf("sum=%d\n",sum);
return 0;
}
5.3 用do…while实现循环
**do…while语句:**先无条件地执行循环体,然后判断循环条件是否成立
对同一个问题可以用while语句处理,也可以用do…while语句处理。do…while语句结构可以转换成while语句结构,二者完全等阶。如果while后面的表达式一开始就为假(0值)时,两种循环的结果是不同的。
当while后面的表达式的第一次的值为”真“时,两种循环得到的结果相同;否则,两者结果不相同(指而二着具有相同的循环体的情况)
一般形式:
do
语句
while(表达式);
例子
#include<stdio.h>
int main(){
int i=1,sum=0;
do
{
sum=sum+i;
i++;
}while(i<=100);
printf("sum=%d\n",sum);
return 0;
}
5.4 用for语句实现循环
for循环:
//for语句的一般形式为
for(表达式1;表达式2;表达式3)
语句
类似于
表达式1;
while 表达式2
{
语句;
表达式3;
}
//表达式1:设置初始条件,只执行yic
//表达式2:是循环条件表达式,用来判断是否继续循环.在每次执行循环体前先执行此表达式,决定是否继续执行循环。
//表达式3:作为循环的调整,例如使循环变量增值
//常用的for语句形式是:
for(循环变量赋初值;循环条件;循环变量增值)
语句
//在执行完循环体后循环变量的值“超过”循环终值,循环结束。
5.5 循环的嵌套
循环嵌套的形式:
(1)
while(){
while(){
}
}
(2)
do{
do{
}while();
}while();
(3)
for(;;){
for(;;){
}
}
(4)
while(){
do{
}while();
}
(5)
for(;;){
while(){
}
}
(6)
do{
for(;;){
}
}while();
5.6 几种循环的比较
- 三种循环都可以来处理同一问题,一般情况下它们可以互相替代。
- 在while循环和do…while循环中。只在while后面的括号内指定循环条件,因此为了使循环能正常结束,应在循环中包含使循环趋于结束的语句(如i++,或i=i+1等)。for循环可以在表达式3中包含使循环趋于结束的操作,甚至可以将循环体中的操作全部放到表达式3中,因此for语句的功能更强,凡用while循环能完成的,用for循环都能实现。
- 用while和do…while循环时,循环变量初始化的操作应在while和do…while语句之前完成。而for语句可以在表达式1中实现循环变量的初始化
- while循环,do…while循环,和for循环都可以用break语句跳出循环,用continue语句结束本次循环。
5.7 改变循环执行的状态
break语句
用break语句可以使流程跳出switch结构,继续执行switch语句下面的一个语句。实际上break语句还可以来从循环体内跳出循环体,即提前结束循环,接着执行下面的语句。
break语句只能用于循环语句和switch语句之中,而不能单独使用。
#include<stdio.h>
#define SUM 100000
int main(){
float amount,aver,total;
int i;
for(i=1,total=0;i<=1000;i++){
printf("请输入数据:");
scanf("%f",&amount);
total = total+amount;
if(total>=SUM) break;
}
aver=total/i;
printf("num=%d\naver=%10.2f\n",i,aver);
return 0;
}
continue语句
不中止整个循环的操作,而只希望提前结束本次循环,而接着执行下次循环。
#include<stdio.h>
int main(){
int n;
for(n=100;n<=200;n++){
if(n%3==0)
continue;
printf("%d",n);
}
printf("\n");
return 0;
}
break和continue的区别
continue语句只结束本次循环,而不是中止整个循环的执行。而break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。
6.利用数组处理批量数据
6.0数组
**数组:**一批具有同名的同属性的数据就组成一个数组
- 数组是一组有序数据的集合。数组中各数据的排列是具有一定规律,下标代表数据在数组中的序号,
- 数组中的每一个元素都属于同一种数据类型
6.1 定义和引用一位数组
定义一维数组的一般形式
类型说明符 数组名[常量表达式];
引用数组元素的表示方式为
数组名[下标]
一维数组的初始化
-
在定义数组时对全部数组元素赋予初值。
int a[10]={1,2,3,4,5,6,7,8,9,0};
-
可以只给数组中的部分元素赋值
int a[10]={0,1,2,3,4};
-
如果想一个数组中的所有元素为0
int a[10]={0,0,0,0,0,0,0,0,0,0}; 或 int a[10]={0};
-
在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组长度
int a[5]={1,2,3,4,5}; 或 int a[]={1,2,3,4,5};
程序举例
用数组求斐波那契数列前二十个
#include<stdio.h>
int main(){
int j;
int f[20]={1,1};
for(j=2;j<20;j++){
f[j]=f[j-2]+f[j-1];
}
for(j=0;j<20;j++){
if(j%5==0){
printf("\n");
}
printf("%12d",f[j]);
}
printf("\n");
return 0;
}
冒泡排序
#include<stdio.h>
int main(){
int a[10];
int j,i,swap;
printf("请输入十个数:\n");
for(i=0;i<10;i++){
scanf("%d\n",&a[i]);
}
printf("\n");
for(j=0;j<9;j++){
for(i=0;i<9-j;i++){
if(a[i]>a[i+1]){
swap = a[i];
a[i]=a[i+1];
a[i+1]=swap;
}
}
}
printf("排序后:");
for(i=0;i<10;i++){
printf("%d\n",a[i]);
}
printf("\n");
return 0;
}
比较法
#include<stdio.h>
int main(){
printf("请输入十个数:");
int a[10];
int i,j,temp;
for(i=0;i<10;i++){
scanf("%d\n",&a[i]);
}
printf("排序前:");
for(i=0;i<10;i++){
printf("%4d",a[i]);
}
printf("\n排序后:");
for(i=0;i<9;i++)
for(j=i+1;j<10;j++){
if(a[i]>a[j]){
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
for(i=0;i<10;i++){
printf("%3d",a[i]);
}
}
选择法排序
#include<stdio.h>
void main(){
printf("请输入十个数:");
int a[10];
int i,j,temp,min;
for(i=0;i<10;i++){
scanf("%d\n",&a[i]);
}
printf("排序前:");
for(i=0;i<10;i++){
printf("%4d",a[i]);
}
for(i=0;i<9;i++){
min = i;
for(j=i+1;j<10;j++){
if(a[min]>a[j]){
min = j;
}
if(min!=i){
temp = a[i];
a[i] = a[min];
a[min]= temp;
}
}
}
printf("\n排序后:");
for(i=0;i<10;i++){
printf("%4d",a[i]);
}
}
6.2 定义和引用二维数组
定义二维数组
类型说明符 数组名[常量表达式][常量表达式]
//例如:
int a[3][4] //定义一个三行四列的整型数组
float b[4][5]//定义一个四行五列的浮点型数组
引用二维数组
数组名 [下标][下标]
//例如
int a[2][2];
//这个数组中有9个位置,分别是a[0][0],a[0][1],a[0][2],
// a[1][0],a[1][1],a[1][2],
// a[2][0],a[2][1],a[2][2]
//当想直接引用其中一个位置时,可以直接选择一个位置进行赋值
a[0][0] = 100
a[0][1] = 90
//也可以对方括号内的数值进行运算
a[0+1][0+1] = 70
a[0*1][0+2]=110
二维数组的初始化
-
分行对二维数组进行赋值
int c[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}
-
可以将所有数据写在一个花括号内,按数组元素内存中的排序顺序对各个元素赋值
int c[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}
-
可以对部分元素赋值
int a[3][4] = {1,2,4,5,7,};
二维数组程序举例
将一个三行四列的二维数组变成一个四行三列的二维数组
#include<stdio.h>
int main(){
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int b[4][3];
int i,j;
printf("改造前:\n");
for(i=0;i<3;i++){
for(j=0;j<4;j++){
printf("%3d",a[i][j]);
}
printf("\n");
}
for(i=0;i<3;i++){
for(j=0;j<4;j++){
b[j][i]=a[i][j];
}
printf("\n");
}
printf("改造后:\n");
for(i=0;i<4;i++){
for(j=0;j<3;j++){
printf("%3d",b[i][j]);
}
printf("\n");
}
}
比较一个数组中的最大值,并将数组中最大值的位置报出
#include<stdio.h>
int main(){
int a[3][3],i,j,min;
int hang,lie;
printf("请为三行三列数组输入初值:");
for(i=0;i<3;i++){
for(j=0;j<3;j++){
scanf("%d\n",&a[i][j]);
}
}
min = a[0][0];
printf("\n");
printf("开始时:\n");
for(i=0;i<3;i++){
for(j=0;j<3;j++){
printf("%4d",a[i][j]);
}
printf("\n");
}
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if(a[i][j]<min){
min = a[i][j];
hang = i;
lie = j;
}
}
}
printf("最小值=%d\thang=%d\tlie=%d\n",min,hang,lie);
}
6.3 字符数组
字符数组的定义
//定义一个一维字符数组
char a[10] = {'h','e','l','l','o','w','o','r','l','d'};
//定义一个二维字符数组
char a[2][5]={'h','e','l','l','o','w','o','r','l','d'};
字符数组的初始化 C语言在字符存储字符串常量时会自动加一个’\0’作为结束符
//有限制字符数组
char a[10] = {'h','e','l','l','o','w','o','r','l','d'};
//无限制字符数组(以花括号内的字符个数来界定数组的大小)
char a[] = {'h','e','l','l','o','w','o','r','l','d'};
使用字符串处理函数
-
puts函数
//它的作用是将一个字符串输出到终端,可以包含转义字符,遇到'\0';就结束输出; char str[] = {"hfd\0sdfj"}; //输出结果是hfd
//输出时只输出第一次赋值的字符串,无法输出再次赋值的变量。只能接收一个变量 char str[] = {"hfd\0sdfj"}; char str[] = {"fbgfbgf"}; puts(str);
-
gets函数–字符数组
gets(str); //从键盘接受一个字符串 只能接收一个变量
-
strcat函数–字符串连接函数
char str1[] = {"hello"}; char str2[] = {"world"}; printf("%s",strcat(str1,str2)); //结果是:helloworld //函数将两个字符串连接在一起 //连接的过程是将前一个字符串末尾的'\0'去除再将两个字符串连接在一起,后一个字符串'\0'未被去除
-
strcpy和strncpy函数 —字符串复制函数
char str1[30]; char str3[30]; char str2[20] = "hello,world"; strcpy(str3,str2); printf("%s",str3); //strcpy函数将str2字符串复制到str3,要保证str3的空间要大于str2 strncpy(str1,str2,3); printf("%s",str1); //strncpy函数将str2字符串中的前三个字符复制到str1;n代表的是选择复制的个数
-
strcmp函数–字符串比较函数
//strcmp(str1,str2); //比较两个字符串str1和str2长度 //如果str1>str2返回值为1 //如果str1=str2返回值为0 //如果str1<str2返回值为负数 //strcmp函数的返回值可以用int定义的变量接收过来输出查看 char str1[30]= "hello,world"; char str3[30]= "hello,word"; if(strcmp(str1,str3)>0){ printf("str1>str3"); } else if(strcmp(str1,str3)==0){ printf("str1=str3"); }else{ printf("str1<str3"); }
-
strlen函数–测试字符串长度的函数
char str[100] = "hello,world"; printf("%d",strlen(str)); //字符串的长度的足够长,如果放数组的字符串的长度不够长,那么函数只能返回放置数组的最大值,可能导致错误
-
strlwr函数和strupr–将字母转换为小写和大写
char str[100] = "hHellO,woOOrld"; printf("%s",strlwr(str)); //结果是:hhello,wooorld printf("%s",strupr(str)); //结果是:HHELLO,WOOORLD //strlwr函数和strupr是将一个字符串中的所有英文字母全部转换为大写或小写
7.用函数实现模块化程序设计
7.1 为什么要用函数
-
为什么要用函数:当一个程序的功能比较多并且规模比较大的时候,把所有程序代码写在主函数下会使得主函数冗余混乱,也不利于阅读和维护,所以就诞生了函数。
-
模块化程序设计思路,就是事先编好一批常用的函数来实现不同功能的函数,函数就是功能。每一个函数用来实现一个特定的功能。函数的名字反映了其代表的功能。
-
一个C语言程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。这样便于分别编写和编译,提高调试效率。一个源文件可以为多个C程序共用。
-
一个源程序文件由一个或多个函数构成,一个源程序文件就是一个编译单位,在编译时是以源程序文件为单位编译的,而不是以函数单位进行编译的。
-
C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回main函数,在main函数结束整个程序的运行。
-
所有函数都是平行的,在定义函数时是分别进行的是相互独立的。函数之间没有从属关系,函数间可以相互调用,但不能调用main函数。main函数只能由系统操作。
-
从用户角度来看,函数分为自定义函数和库函数
库函数:系统提供,用户不必自己定义,可以直接使用。
自定义函数:你想怎么定义你就怎么用
-
从函数角度看,函数分为无参函数和有参函数
//无参函数 void zf(){ printf("--------------"); } //有参函数 void swap(int a,int b){ int swap; if(a>b){ swap = a; a = b; b = swap; } }
7.2 怎样定义函数
函数定义要求
- 指定函数的名字,以便以后按名调用
- 指定函数的类型,即函数返回值的类型
- 指定函数的参数的名字和类型,以便在调用函数时向它们传递数据,无参传值函数不用指定。
- 指定函数的功能,函数可以做什么。
定义函数
-
定义无参函数
类型名 函数名() { 函数体 } 或 类型名 函数名(void) { 函数体 }
-
定义有参数
类型名 函数名(形式参数表列) { 函数体 } //例子 int ww(int x,int y){ int z; z=x>y?x:y; return (z); }
-
定义空函数
类型名 函数名() { }
7.3 调用函数
-
函数调用的形式
函数调用的一般形式:函数名(实参表列)
- 函数调用语句:把函数用作一个单独语句去执行,不要求待会返回值。
- 函数表达式:函数调用传出现在另一个表达式中,属于赋值表达式的一部分时,这时要求函数带回返回值
- 函数参数:函数调用作为另一个函数调用的实参。
-
函数调用时的数据传递
-
形式参数和实际参数
在调用有参函数时,主调函数和被调函数之间有数据传递关系,在定义函数时函数名后面括号中的变量称为形式参数或虚拟参数。
在主调函数中调用一个函数时,函数名后面括号中的参数称为实际参数,实际参数可以是常量,变量或表达式。
-
-
函数调用的过程
- 在定义函数指定的形参,再未出现函数调用时,它们并不占内存中的存储单元。在发生函数调用时函数的形参才被临时分配内存单元。
- 将实参的值传递给对应形参。
- 在执行函数期间,由于形参已经有值了,就可以利用形参进行有关的运算。
- 通过return语句将函数的值带回到主调函数,如果函数不需要返回值,则不需要return语句。这时函数的类型应定义为void类型。
- 调用结束,形参单元被释放。实参单元仍然保留并维持原值没有改变。
-
函数的返回值
- 函数的返回值是通过函数中的return语句获得的。
- 函数的类型:函数的返回值应当在定义函数时指定函数值的类型。
- 在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致。如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据可以自动进行类型转换。即函数类型决定返回值类型
- 对于不带回值的函数,应当定义函数为“void类型”。这样,系统就保证不使用函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。此时在函数中不得出现return语句。
7.4 对被调用函数的声明和函数原型
在一个函数调用另一个函数需要满足以下条件。
- 首先被调用的函数必须是已经定义的函数。
- 使用库函数得在代码开始就用#include指令引入
- 如果使用用户自己定义的函数,而该函数的位置在调用它的函数的后面,应该在主调函数中对被调函数做声明。声明是为了让主调函数能找到被调函数,从而能处理程序。
7.5 函数的嵌套调用
函数的定义是互相平行独立的,一个函数内不能再定义另一个函数,可以嵌套调用,不可以嵌套定义
mian函数 a函数 b函数
| > | > |
1 | 2 / | 3 / 4|
V / V / |
调用a函数 调用b函数 |5
| < | < |
9| \ 7| \ |
V 8\ V 6\ |
结束 \ \ |
-
执行main函数开头部分
-
遇函数调用语句,调用函数a,流程转去a函数。
-
执行a函数的开头部分
-
遇函数调用语句,调用函数b,流程转去b
-
执行b函数,如果再无其他嵌套函数,则完成b函数的全部操作
-
返回到a函数中调用b函数的位置
-
继续执行a函数中尚未执行的部分,直到a函数结束
-
返回main函数中调用a函数的位置
-
继续执行main函数的剩余部分直到结束
例子
#include<stdio.h> int main(){ int max4(int a,int b,int c,int d); int a,b,c,d,max; printf("请输入四个数:"); scanf("%d %d %d %d",&a,&b,&c,&d); max=max4(a,b,c,d); printf("max=%d",max); return 0; } int max4(int a,int b,int c,int d){ int max2(int a,int b); int m; m=max2(a,b); m=max2(m,c); m=max2(m,d); return(m); } int max2(int a,int b){ if(a>=b){ return a; } else{ return b; } }
7.6 函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身称为函数的递归调用
用递归方法解题的条件:
- 所求解的问题能转化为同一方法解决的子问题
- 子问题的规模比原问题的规模小
- 必须要有递归结束条件,停止递归,否则形成无穷递归,系统无法实现
用递归方法求n!
#include<stdio.h>
int main(){
int fac(int n);
int n;
int y;
printf("请输入你想求的数:");
scanf("%d",&n);
y = fac(n);
printf("%d!=%d\n",n,y);
return 0;
}
int fac(int n){
int f;
if(n<0){
printf("n<0,数据错误");
}
else if(n == 0||n == 1){
f = 1;
}
else{
f = fac(n-1)*n;
}
return(f);
}
7.7 数组作为函数参数
-
数组元素作函数实参
数组元素可以用作函数实参,但是不能用作形参。因为形参是在函数被调用时临时分配存储单元的,不可能作为一个数组元素单独分配存储单元。在用数组元素作函数实参时,把实参的值传给形参,是值传递方式。数据传递的方向是 从实参传到形参,单向传递
输入十个数,要求输出最大的元素位置和值。
#include<stdio.h> int main(){ int max(int x,int y); printf("请输入十个数:"); int m,n; int a[10],i; for(i=0;i<10;i++){ scanf("%d",&a[i]); } for(i=0,m=a[0],n=0;i<10;i++){ if(max(m,a[i])>m){ m=max(m,a[i]); n=i; } } printf("最大值=%d 所在位置=%d",m,n+1); } int max(int x,int y){ return(x>y? x:y); }
-
一维数组名作函数参数
可以用数组元素做为函数参数,也可以用数组名作为函数参数(包括形参和实参)
#include<stdio.h> int main(){ float average(float array[10]); float score[10],aver; int i; printf("请输入十个数:"); for(i=0;i<10;i++){ scanf("%f",&score[i]); } printf("\n"); aver=average(score); printf("平均分是:%5.2f\n",aver); return 0; } float average(float array[10]) { int i; float aver,sum=array[0]; for(i=1;i<10;i++){ sum=sum+array[i]; } aver=sum/10; return(aver); }
-
多维数组名作函数参数
#include<stdio.h>
int main(){
int max_value(int arrray[][4]);
int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}};
printf(“最大值是%d\n”,max_value(a));
return 0;
}
int max_value(int arryay[][4]){
int i,j,max;
max=array[0][0];
for(i=0;i<3;i++){
for(j=0;j<4;j++){
if(array[i][j]>max){
max=array[i][j];
}
}
}
return(max);
}
//有错误
### 7.8 局部变量和全局变量
1. 局部变量:(1) 在函数的开头定义;
(2) 在函数内的复合语句内定义;
(3)在函数的外部定义;
在一个函数内部定义的变量只在本函数范围内有效,也就是说只有在本函数内才能引用它们,在此函数以外是不能使用这些变量的。在复合语句内定义的变量只在本复合语句范围内有效,只有在本复合语句内才能使用它们。在该复合语句以外是不能使用这些变量。以上称为”局部变量“。
1. 主函数中定义的变量也只在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效,主函数也不能使用其他函数中定义的变量
2. 不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。
3. 形式参数也是局部变量。
4. 在一个函数內部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为**分程序** 或**程序块**。
2. 全局变量:在函数内定义的变量是局部变量,而在函数之外定义的的变量称为外部变量,外部变量是全局变量。全局变量可以为本文件中其他函数所共用。它的有效范围从定义变量的位置开始到本源文件结束。**在函数内定义的变量是局部变量,在函数外定义的变量是全局变量。**
**在一个函数中既可以使用本函数中的局部变量,也可以使用有效的全局变量**
(1)全局变量在函数的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
(2)它使函数的通用性降低了,因为如果在函数中引用了全局变量,那么执行情况会受到有关外部变量的影响,如果将一个函数移到另一个文件中,还要考虑把有关的外部变量及其值一起移过去。但是若该外部变量与其他文件的变量同名时,就会出现问题。这就降低了程序的可靠性和通用性。
(3)使用全局变量过多,会降低程序的清晰性,人们往往难以清楚的判断出每个瞬时各个外部变量的值。由于在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。
(4)如果在同一个源文件中,全局变量与局部变量同名,局部变量有效,全局变量被屏蔽,即它不起作用。
```c
#include<stdio.h>
int a=3,b=5;
int main(){
int max(int a,int b);
int a=8;
printf("最大值:\n",max(a,b));
return 0;
}
int max(int a,int b){
int c;
c=a>b?a:b;
return(c);
}
返回值是:8
7.9 变量的存储方式和生存期
-
动态存储方式与静态存储方式
有的变量在程序运行的整个过程都是存在的,而有的变量则是在调用其所在的函数时才临时分配存储单元,而在函数调用结束后该存储单元就马上释放了,变量就不存在了。
变量的存储方式有两种:静态存储方式和动态存储方式
静态存储方式是指在程序运行期间由系统分配固定的存储空间方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间方式。
内存中的存储空间可分为三部分 1.程序区 2.静态存储区 3.动态存储区 数据分别存放在静态存储区和动态存储区。全局变量全部存放在静态存储区,在程序开始执行时给全局给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的内存单元,而不是动态地进行分配和释放。 动态存储区存放以下数据: 1.函数形式参数。在调用函数时给形参分配存储空间 2.函数中定义的没有用关键字static声明的变量,即自动变量 3.函数调用时的现场保护和返回地址
在C语言中,每一个变量和函数都有两个属性:数据类型和存储类别
数据类型:就是常见的整型和浮点型
存储类别:就是动态存储和静态存储
C语言的存储类别有:自动的(auto),静态的(static),寄存器(register),外部的(extern)。
-
局部变量的存储类别
-
自动变量(auto)
函数中的局部变量,如果没有专门声明为静态的(static)存储类别,就都是动态的分配存储空间,数据存储在动态存储区中,函数中的形参和在函数中定义的局部变量都是自动变量,在调用函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间,这类存储变量就是自动变量。
一般在写代码时,关键字auto被省略,不写auto则隐含指定为“自动存储类别”
-
静态存储变量(static局部变量)
-
静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量属于动态存储类别,分配在动态存储区空间而不在静态存储空间,函数调用结束后即释放。
-
对静态局部变量是在编译是赋初值,即只赋一次初值,在运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行的,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
-
如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0或空字符’\0‘。而对自动变量来说,它的值是一个不确定的值,这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的内容是不可知的。
-
虽然静态局部变量在函数调用结束后仍然存在,但在其他函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用。
#include<stdio.h> int main(){ int fac(int n); int i; for(i=0;i<5;i++){ printf("%d!=%d\n",i,fanc(i)); } } int fac(int n){ static int f=1; f=n*f; return f; }
用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可以先后为多个变量使用,节约内存),而且降低了程序的可读性,当调用次数多时往往弄不清静态局部变量的当前值是什么。
-
-
寄存器变量(register变量)
一般情况下,变量的值是存放在内存中的。当程序中用到哪一个变量的值时由控制器发出指令将内存中该变量的值送到运算器中。
C语言中允许将局部变量的值放在CPU中的寄存器中,需要用时直接从寄存器取出参数参加运算,不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样的变量叫做寄存器变量。
-
-
全局变量的存储类别
-
在一个文件内拓展外部变量的作用域
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在该引用之前用关键字extern对该变量做“外部变量声明”,表示把该外部变量的作用域拓展到此位置。
#include<stdio.h>
int main(){
extern int A,B,C;
printf(“请输入三个数字:”);
scanf(“%d%d%d\n”,&A,&B,&C);
printf(“最大值:\n”,max());
return 0;
}
int A,B,C;
int max()
{
int m;
m=A>B?A:B;
if(C>m){
m=C;
}
return m;
}2. 将外部变量的作用域拓展到其他文件 如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件中各自定义一个外部变量Num,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任意一个文件中定义外部变量Num,而在另一个文件中用extern对Num作“外部变量声明”。在编译和连接时,系统会由此知道Num“外部链接”,可以从别处找到已定义的外部变量Num,并将在另一文件中定义的外部变量Num的作用域拓展到本文件,在本文件中可以合法地引用外部变量Num。 ```c #include<stdio.h> int main(){ int power(int); int b=3,c,d,m; printf("enter the number a and its power m:\n"); scanf("%d,%d",&A,&m); c=A*b; printf("%d*%d=%d\n",A,b,c); d=power(m); printf("%d**%d=%d\n",A,m,d); return 0; } exyern A; int power(int n){ int i,y=1; for(i=1;i<=n;i++){ y*=A; } return y; }
-
将外部变量的作用域限制在本文件中
有时候在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这个时候可以在定义外部变量时加一个static声明。
file1.c file2.c static int A; extern A; int main(){ void fun(int n) . { . . . . } . A=A*n; . . } 这种写法会出现报错。
在file1.c中定义了一个全局变量A,但他用了static声明,把变量A的作用域限制在本文件范围内,虽然在
file2.c中用了“extern A;”,但仍然不能使用file1.c中的全局变量A。这种加上了static声明,只能用于本文件的外部变量称为静态外部变量。
**说明:**不要误以为对外部变量加static声明后才采取静态存储方式(存放在静态存储区中),而不加static的是采取动态存储(存放在动态存储区)。声明局部变量的存储类型和声明全局变量的存储类型的含义是不同的。对局部变量来说,声明存储类型的作用是指定变量存储的区域(静态存储区或动态存储区)以及由此产生的生存期的问题,而对于全局变量来说,由于都是在编译时分配内存的,都存放在静态存储区,声明存储类型的作用是变量作用域的拓展问题。
用static声明一个变量的作用是:
(1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
(2)对全局变量使用static声明,则该变量的作用域只限于本文件模块(即声明的文件中)
-
存储类别小结
对一个数据的定义,需要指定两种属性:数据类型和存储类别
-
从作用域角度分,有局部变量和全局变量。它们采取的存储类别如下:
-自动变量,即动态局部变量(离开函数,值就消失) |静态局部变量(离开函数,值仍保留) -局部变量-----| | |寄存器变量(离开函数,值就消失) | -(形式函数可定义为自动变量或寄存器变量) 按作用域角度分 --- | -静态外部变量(只限本文件引用) -全局变量-----| -外部变量(即静态外部变量,允许其他文件引用)
-
从变量存在时间(生存期)来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。
|-自动变量(本函数内有效) -动态存储-寄存器变量(本函数内有效) | |-形式参数(本函数内有效) 按变量生存期分- | |-静态局部变量(函数内有效) -静态存储-静态外部变量(本文件内有效) |-外部变量(用extern声明后,其他文件可引用)
-
从变量值存放位置来区分
- 静态局部变量 | 静态外部变量 (函数外部静态变量) -内存中静态存储区- | - 外部变量(可为其他文件引用) 按变量值存放的位置分- | - 内存中动态存储区 | - CPU中的寄存器:寄存器变量
-
关于作用域和生存期的概念。
(1)如果一个变量在某个文件或函数范围内是有效的,就称该范围为该变量的作用域,在此作用域内可以引用该变量,在该作用域内该变量可见,这种性质称为变量的可见性。
(2)如果一个变量值在某一时刻是存在的,则认为这一时刻属于该变量的生存期,这时体现该变量的存在性。
-
static对局部变量和全局变量的作用不同。对局部变量来说,它使变量由动态存储方式改变为静态存储方式。而对全局变量来说,它使变量局部化(局部于本文件),但仍为静态存储方式。从作用域角度来看,凡有static声明的,其作用域都是局限的,或者局限于本函数内(静态局部变量),或者局限于本文件内(静态外部变量).
-
-
7.10 关于变量的声明和定义
一个函数一般由两部分组成:声明部分和执行部分。声明部分的作用是对有关的标识符的属性进行声明。对函数而言声明和定义的区别是明显的,函数的声明是函数的原型,而函数的定义是对函数功能的定义。对被调函数的声明部分中的,而函数的定义显然不在声明部分的范围内,它是一个独立的模块。
在声明部分出现的变量有两种情况:一种是需要建立存储空间的(变量的定义),另一种是不需要建立存储空间的(变量的声明)。前者称为定义性声明,后者称为引用性声明。一般为了叙述方便,把建立存储空间的声明称定义,而把不需要建立存储空间的声明称声明。
7.11 内部函数和外部函数
根据函数是否能被其他源文件调用,将函数区分为内部函数和外部函数。
-
内部函数
如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,
static 类型名 函数名(形参表);
内部函数又称静态函数,因为它是用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件。这样,在不同的文件中即使有同名的内部函数,也互不干扰,不用担心所用函数是否会与其他文件模块中的函数同名。
-
外部函数
如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用。
extern int fun (int a,int b); //这样fun函数可以被其他函数调用了
在需要调用此函数的其他文件中,需要对此函数作声明(不要忘记,即使在本文件中调用一个函数,也要用函数原型进行声明)。在对此函数作声明时,要加关键字extern,表示该函数“是在其他文件中定义的外部函数”。
-
文件1
#include<stdio.h> int main(){ extern void enter_string(char str[]); extern void delete_string(char str[],char ch); extern void print_string(char str[]); char c,str[80]; enter_string(str); scanf("%c",&c); delete_string(str,c); print_string(str); return 0; }
-
文件2
void enter_string(char str[80]){ gets(str); }
-
文件3
void delete_string(char str[],char ch){ int i,j; for(i=j=0;str[i]!='\0';i++){ if(str[i]!=ch){ str[j++]=str[i]; } str[j]='\0'; } }
-
文件4
void print_string(char str[]){ printf("%s\n",str); }
-
8.善于利用指针
8.1 指针是什么
地址指向该变量单元:地址的大小是有限的,变量单元指给变量分配到存储空间大小,例如int型四个字节,char一个字节,将变量的类型作为变量的单元大小,而地址则是确定这一个变量的单元位置准确的值。
地址形象化地称为“指针”:指针是用来存放地址的,指针变量的值就是地址
直接访问:直接按变量名进行的访问(例如:定义一个变量直接通过输出函数输出的过程)
间接访问:从被定义的变量地址获取或接收的访问(例如:定义一个变量a后,将a的地址给另一个b变量,然后通过输出函数输出b却访问到a的过程)
指针:通过地址能找到所需的变量单元
指针和指针变量是两个概念,指针是一个地址,指针变量是存放地址的变量
8.2 指针变量
-
使用指针变量的例子:通过指针变量访问整型变量
#include<stdio.h> int main(){ int a=100,b=10; int *p1,*p2; p1=&a; p2=&b; printf("a=%d,b=%d\n",a,b); printf("p1=%d,p2=%d\n",*p1,*p2"); }
-
怎样定义指针变量
类型名 *指针变量名; int *p1; //四个字节大小 char *p2; //一个字节大小 float *p3; //四个字节大小 double *p4; //八个字节大小 //指针变量前面的*代表的是它是指针变量,这是与定义为整型变量或实型变量的的形式不同。 //在指定指针变量时必须指定基类型 //指向一个整型变量和指向一个实型变量,其物理意义上的含义是不同的。 //指向整型数据的指针类型表示为"int*",读作"指向int的指针"或简称"int指针" //指针变量中只能存放地址,不要将一个整数赋给一个指针变量。
一个比变量的指针的含义包括两方面,一是以存储单元编号表示的纯地址,一是它指向的存储单元的 数据类型(如 int,char,float等)
-
怎样引用指针变量
引用指针变量的三种情况:
1.给指针变量赋值: p=&a 2.引用指针变量指向的变量 p=&a printf("%d",*p) 3.引用指针变量的值 printf("%o",p)
**例题:**输入a和b两个整数,按先大后小的顺序输出a和b
#include<stdio.h> int main(){ int a,b,*p,*p1,*p2; scanf("%d,%d",&a,&b); p1=&a; p2=&b; if(a<b){ p=p1; p1=p2; p2=p; } printf("a=%d,b=%d\n",a,b); printf("p1=%d,p2=%d\n",*p1,*p2"); return 0; }
-
指针变量作为函数参数
函数的参数不仅可以是整型,浮点型,字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一函数中。
#include<stdio.h> int main(){ void swap(int *p1,int *p2); int a,b; int *p1,*p2; printf("请输入两个数:\n"); scanf("%d,%d\n",&a,&b); p1=&a; p2=&b; if(a<b){ swap(p1,p2); } printf("最大值是:%d,最小值是:%d",a,b); return 0; } void swap(int *p1,int *p2){ int temp; temp = *p1; *p1 = *p2; *p2 = temp; }
不能企图通过改变指针形参的值而使指针实参的值改变
函数调用可以得到返回值,而使用指针变量作参数,可以得到多个变化的值,如果不用指针变量是难以做到这一点的。
8.3 通过指针引用数组
-
数组元素的指针
一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素。所谓数组元素的指针就是数组元素的地址
引用数组元素可以用下标法,也可以用指针法,即通过指向数组元素的指针找到所需元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。
-
在引用数组元素时指针的运算
在指针已指向一个数组元素的时,可以对指针进行以下运算:
加一个整数(用+或+=),如p+1;
减一个整数(用-或-=),如p-1;
自加运算,如p++,++p;
自减运算,如p–,–p。
如果指针变量p已指向数组中的一个元素,则p+1指向同一个数组中的下一个元素,p-1指向同一个数组中的上一个元素。
[ ]实际上是变址运算符,即将a[i]按a+i计算地址,然后找出此地址单元中的值。
注意:两个地址不能相加,如p1+p2是无实际意义的。
-
通过指针引用数组元素
(1)下标法,如a[i]形式;
#include<stdio.h> int main(){ int a[10]; int i; printf("请输入十个数字:"); for(i=0;i<10;i++){ scanf("%d",&a[i]); } for(i=0;i<10;i++){ printf("%d",a[i]); } printf("\n"); return 0; }
(2)指针法,如*(a+i)或(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值p=a;
#include<stdio.h> int main(){ int a[10]; int i; printf("请输入十个数字:"); for(i=0;i<10;i++){ scanf("%d",&a[i]); } for(i=0;i<10;i++){ printf("%d",*(a+i)); } printf("\n"); return 0; }
(3)用指针变量指向数组元素
#include<stdio.h> int main(){ int a[10]; int *p,i; printf("请输入十个数字:"); for(i=0;i<10;i++){ scanf("%d",&a[i]); } for(p=a;p<(a+10);p++){ printf("%d",*p); } printf("\n"); return 0; }
第三种方法比第一和第二种方法快,用指针变量直接指向元素,不必每次都重新计算地址,这种有规律的改变地址值能大大提高执行效率。
可以通过改变指针变量的值指向不同元素
#include<stdio.h> int main(){ int a[10]; int *p,i; printf("请输入十个数字:"); for(i=0;i<10;i++){ scanf("%d",&a[i]); } for(i=0;i<10;i++,p++){ printf("%d",*p); } printf("\n"); return 0; }
-
用数组名作函数参数
以变量名和数组名作为函数参数的比较
实参类型 变量名 数组名 要求形参的类型 变量名 数组名或指针变量 传递的信息 变量的值 实参数组首元素的地址 通过函数调用能否改变实参的值 不能改变实参变量的值 能改变实参数组的值
**说明:**C语言调用函数时虚实结合的方法都是采用值传递方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按指针变量处理。
例题1
将数组a中n个整数按相反顺序存放。
#include<stdio.h> int main(){ void inv(int x[],int n); int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("数组开始顺序:"); for(i=0;i<10;i++){ printf("%d",a[i]); } printf("\n"); inv(a,10); printf("数组反序后顺序:"); for(i=0;i<10;i++){ printf("%d",a[i]); } printf("\n"); return 0; } //未定义指针在函数内部 void inv(int x[],int n){ int temp,i,j,m=(n-1)/2; for(i=0;i<m;i++){ j=n-1-i; temp=x[i]; x[i]=x[j]; x[j]=temp; } return; } //定义指针在函数内部 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; } return; }
例题2
用指针方法对十个整数按从大到小顺序排序
#include<stdio.h> int main(){ void sort(int x[],int n); int i,*p,a[10]; p=a; printf("请输入十个数字"); for(i=0;i<10;i++){ scanf("%d",p++); } p=a; sort(p,10); for(p=a,i=0;i<10;i++){ printf("%d",*p); p++; } printf("\n"); return 0; } //没有使用指针 void sort(int x[],int n){ int i,j,k,t; for(i=0;i<n-1;i++){ k=i; for(j=i+1;j<n;j++){ if(x[j]>x[k]){ k=j; } if(k!=i){ t=x[i]; x[i]=x[k]; x[k]=t; } } } } //使用指针 void sort(int *x,int n){ int i,j,k,t; for(i=0;i<n-1;i++){ k=i; for(j=i+1;j<n;j++){ if(*(x+j)>*(x+k)){ k=j; } if(k!=i){ t=*(x+i); *(x+i)=*(x+k); *(x+k)=t; } } } }
-
通过指针引用多维数组
-
多维数组元素的地址
不要把&a[i]简单地理解为a[i]元素的存储单元的地址,因为并不存在a[i]这样一个实际的数据存储单元。它只是一种地址的计算方法,能得到第i行的起始地址,&a[i]和a[i]的值是一样的,但他们的基类型是不同的。
#include<stdio.h> int main(){ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; printf("%d,%d\n",a,*a); printf("%d,%d\n",a[0],*(a+0)); printf("%d,%d\n",&a[0],&a[0][0]); printf("%d,%d\n",a[1],a+1); printf("%d,%d\n",&a[1][0],*(a+1)+0); printf("%d,%d\n",a[2],*(a+2)); printf("%d,%d\n",&a[2],a+2); printf("%d,%d\n",a[1][0],*(*(a+1)+0)); printf("%d,%d\n",*a[2],*(*(a+2)+0)); return 0; }
-
指向多维数组元素的指针变量
(1)指向数组元素的指针变量
例题1
有一个3X4的二维数组,要求用指向元素的指针变量输出二维数组各个元素的值
#include<stdio.h> int main(){ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int *p; for(p=a[0];p<a[0]+12;p++){ if((p-a[0])%4==0){ printf("\n"); } printf("%4d",*p); } printf("\n"); return 0; }
(2)指向由m个元素组成的一维数组的指针变量
例题2
输出二维数组任一行任一列元素的值
#include<stdio.h> int main(){ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int (*p)[4],i,j; p=a; printf("请输入任一行任一列:"); scanf("%d,%d",&i,&j); printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j)); return 0;
说明
要注意指针变量的类型,从int (*p)[4]可以看出,p的类型不是int *类型,而是int( * )[4]型,p被定义为指向一维整型数组的指针变量,一维数组有四个元素,因此p的基类型是一维数组,其长度是十六个字节
-
用指向数组的指针做函数参数
一维数组可以作为函数参数,多维数组名也可以做函数参数。用指针变量做形参,以接受实参数组名传递来的地址。可以有两种方法:(1)用指向变量的指针变量;(2)用指向一维数组的指针变量
#include<stdio.h> int main(){ void average(float *p,int n); void search(float (*p)[4],int n); float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}}; average(*score,12); search(score,2); return 0; } void average(float *p,int n); { float *p_end; float sum=0,aver; p_end=p+n-1; for(;p<=p_end;p++){ sum=sum+(*p); } aver=sum/n; printf("average=%5.2f\n",aver); } void search(float (*p)[4],int n){ int i; printf("The score of No.%d are:\n",n); for(i=0;i<4;i++){ printf("%6.2f",*(*(p+n)+i)); } printf("\n"); }
-
8.4 通过指针引用字符串
-
字符串的引用方式
例题1
定义一个字符数组,在其中存放字符串“I Love China!”,输出该字符串和第八个字符。
#include<stdio.h> int main(){ char string[]="I Love China!"; printf("%s\n",string); printf("%c\n",string[7]); return 0; }
例题2
通过字符指针变量输出一个字符串
#include<stdio.h> int main(){ char *string = "I Love China!"; printf("%s\n",string); return 0; }
有人误认为string是一个字符串变量,以为在定义时把“I Love China!”这几个字符赋值给该字符串变量,这是不对的。在C语言中只有字符变量没有字符串变量。
-
字符指针做函数参数
如果想把一个字符串从一个函数“传递”到另一个函数,可以通过地址传递的方法,即用字符数组作为参数,也可以用字符指针变量做参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以引用改变后的字符串。
例题1
用函数调用实现字符串的复制
(1)用字符数组名作为函数参数
#include<stdio.h> int main(){ void copy_string(char from[],char to[]); char a[]="I am a teacher."; char b[]="You are a student."; printf("string a=%s\nstring b=%s\n",a,b); printf("复制字符串a到b\n"); copy_string(a,b); printf("string a=%s\nstring b=%s\n",a,b); return 0; } void copy_string(char from[],char to[]){ int i=0; while(from[i]!='\0'){ to[i]=from[i]; i++; } to[i]='\0'; }
(2)用字符型指针变量作实参
#include<stdio.h> int main(){ void copy_string(char from[],char to[]); char a[]="I am a teacher."; char b[]="You are a student."; char *from=a,*to=b; printf("string a=%s\nstring b=%s\n",a,b); printf("复制字符串a到b\n"); copy_string(a,b); printf("string a=%s\nstring b=%s\n",a,b); return 0; } void copy_string(char from[],char to[]){ int i=0; while(from[i]!='\0'){ to[i]=from[i]; i++; } to[i]='\0'; }
调用函数时实参与形参的对应关系
-
使用字符指针变量和字符数组的比较
字符数组和字符指针变量都能实现字符串的存储和运算,但他们二者之间是有区别的,不应该混为一谈,主要有以下几点。
(1)字符数组有若干个元素组成,每一个元素放一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址),绝不是将字符串放到字符指针 变量中。
(2)赋值方式。可以对字符指针变量赋值但不能对数组名赋值。
可以采用以下办法对字符指针变量赋值:
char *a; a="I LOVE China";
不能采用以下办法对字符数组名赋值:
char str[14]; str[0]='I'; str="I LOVE China"; //数组名是地址,是常量,不能被赋值,非法
(3)初始化的含义。对字符指针变量 赋初值:
char *a="I LOVE China"; #等价于 char *a; a="I LOVE China";
而对数组的初始化:
char str[14]="I LOVE China"; #不等价于 char str[14]; str[]="I LOVE China"; //企图把字符串赋给数组中各个元素,错误
数组可以在定义时对各元素赋初值,但不能用赋值语句对字符数组中全部元素整体赋值。
(4)存储单元的内容。编译时为字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元。
(5)指针变量的下值是可以改变的,而字符数组名代表一个固定的值(数组首元素的地址),不能改变
#include<stdio.h> int main(){ char *a="I LOVE China"; a=a+7; printf("%s\n",a); return 0; }
(6)字符数组中各元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串常量中的内容是不可以被取代的。
char a[]="House"; //字符数组a的初始化 char *b="House"; //字符指针变量b指向字符串常量的第一个字符 a[2]='r'; //合法,r取代a数组元素a[2]原始值u b[2]='r'; //非法,字符串常量不能改变
8.5 指向函数的指针
-
什么是函数的指针
如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段内存空间有一个起始地址,也称为函数的入口地址。每次调用函数时都从该地址入口开始执行此段函数代码。函数名代表函数的起始地址。调用函数时,从函数名得到函数的起始地址,并执行函数代码。
函数名就是函数的指针,它代表函数的起始地址。
-
用函数指针变量调用函数
如果想调用一个函数,除了可以通过函数名调用以外,还可通过指向函数的指针变量来调用该函数。
题目:用函数求整数a和b中的大者
(1)通过函数名调用函数
#include<stdio.h> int main(){ int max(int,int); int a,b,c; printf("请输入a和b\n"); scanf("%d,%d",&a,&b); c=max(a,b); printf("a=%d\nb=%d\nmax=%d\n",a,b,c); return 0; } int max(int x,int y){ int z; if(x>y) z=x; else z=y; return(z); }
(2)通过指针变量调用它所指向的函数
#include<stdio.h> int main(){ int max(int,int); int (*p)(int,int); int a,b,c; p=max; printf("请输入a和b\n"); scanf("%d,%d",&a,&b); c=(*p)(a,b); printf("a=%d\nb=%d\nmax=%d\n",a,b,c); return 0; } int max(int x,int y){ int z; if(x>y) z=x; else z=y; return(z); }
-
怎样定义和使用指向函数的指针变量
(1)定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。如”int (*p)(int ,int);"表示指针变量p可以指向函数返回值为整型且有两个整型的参数的函数。在程序中把哪一个函数(该函数的值是整型的且有两个整型参数)的地址赋给它,它就想指向哪一个函数。在一个程序中,一个指针变量可以先指向同类型的不同函数。
(2)如果要用指针调用函数,必须先使指针变量指向该函数。如:
p=max;
这就把max函数的入口地址付给了指针变量p。
(3)在给函数指针变量赋值时,只需给出函数名而不必给出参数,例如:
p=max;
因为是将函数入口地址赋给p,而不牵涉实参与形参的结合问题。如果写成
p=max(a,b);
就错了。p=max(a,b)的作用是将调用max函数所得到的函数值赋给p,而不是将函数入口地址赋给p。
(4)用函数指针变量调用函数时,只将(*p)代替函数名即可(p为指针变量名),在( *p)之后的括号中根据需要写上实参,例如:
c=(*p)(int,int);
表示"调用由p指向的函数,实参为a,b。得到函数值赋给c".
请注意函数返回值的类型。从指针变量p的定义中可以知道,函数的返回值应是整型的,因此将其值赋给整型变量c是合法的。
(5)对指向函数的指针变量不能进行算数运算,如p+n,p++,p–等运算是无意义的。
(6)用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。
#include<stdio.h> int main(){ int max(int,int); int min(int,int); int (*p)(int,int); int a,b,c,n; p=max; printf("请输入a和b\n"); scanf("%d,%d",&a,&b); printf("选择1或2:\n"); scanf("%d\n",&n); if(n==1) p=max; else if(n==2) p=min; c=(*p)(a,b); printf("a=%d\nb=%d\nmax=%d\n",a,b,c); return 0; } int max(int x,int y){ int z; if(x>y) z=x; else z=y; return(z); } int min(int x,int y){ int z; if(x<y) z=x; else z=y; return(z); }
-
用指向函数的指针做函数参数
指向函数的指针变量的一个重要的用途是把函数的入口地址作为参数传递到其他函数。
指向函数的指针可以作为函数参数,把函数的入口地址传递给形参,这样就能够在被调用的函数中使用实参函数。
8.6 返回指针的函数
一个函数可以返回一个整型值,字符值,实型值,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型。
例题:有a个学生,每个学生有b门课程的成绩。要求在输入学生序号以后能输出该学生的全部成绩。用指针函数来实现
//有错误
#include<stdio.h>
int main(){
float *search(float (*pointer)[4],int n);
float score[][4]={{70,80,90,95},{70,75,68,59},{56,34,76,48}};
float *p;
int i,k;
printf("输入学生的序号:\n");
scanf("%d",&k);
printf("这是%d学生的序号:\n",k);
p=search(score,k);
for(i=0;i<4;i++){
printf("%5.2f\t",*(p+i));
}
printf("\n");
return 0;
}
float *search(float (*printer)[4],int n){
float pt;
pt=*(pointer+n);
return(pt);
}
8.7 指针数组和多重指针
-
什么是指针数组
一个数组,若其元素均为指针类型的数据,称为指针数组,也就是说,指针数组中的每一个元素都存放指针,相当于一个指针变量。下面定义一个指针数组:
//一维数组定义一般形式:类型名 *数组名[数组长度]; int *p[4];
将若干个字符按字母顺序从小到大输出
#include"stdio.h" #include"string.h" int main(){ void sort(char *name[],int n); void print(char *name[],int n); char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"}; int n=5; sort(name,n); print(name,n); return 0; } void sort(char *name[],int n){ char *temp; int i,j,k; for(i=0;i<n-1;i++){ k=i; for(j=i+1;j<n;j++){ if(strcmp(name[k],name[j])>0) k=j; if(k!=j) { temp=name[i]; name[i]=name[k]; name[k]=temp; } } } } void print(char *name[],int n){ int i; for(i=0;i<n;i++){ printf("%s\n",name[i]); } }
-
指向指针数据的指针变量
指向指针数据的指针变量,简称为指向指针的指针,指针数组的每一个元素是一个指针型的变量,其值为地址,当再定义一个指针变量指向指针数组,就出现了多重指针。
例如:使用指向指针数据的指针变量
#include<stdio.h> int main(){ char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"}; char **p; int i; for(i=0;i<5;i++){ p=name+i; printf("%s\n",*p); } return 0; }
-
指针数组作main函数的形参
指针数组一个重要的应用是作为main函数的形参。
一般情况下写法为
int main(){ } 或 int main(void)
括号中要么为空或有“void”,表示main函数没有参数,调用main函数时不必给出实参。这是一般程序常采用的方式。实际上在某些情况下,main函数可以有参数,即
int main(int argc,char *argv[])
其中,argc和argv就是函数的形参,它们是程序的“命令行参数”。argc(argument count的缩写,意思是参数的个数),argv(argument vector 缩写,意思是参数向量),它是一个*char指针数组,数组中每一个元素(其值为指针)指向命令行中的第一个字符串的首字符。
注意:如果使用带参数的main函数,其第一个形参必须是int型,用来接收形参个数,第二个必须是字符型指针数组,用来接收从操作系统的命令行传来的字符串中首字符的地址。
8.8 动态内存分配与指向它的指针变量
-
什么是内存的动态分配
全局变量是分配在内存中的静态存储区,非静态的局部变量是分配在内存中的动态存储区,这个存储区是一个称为栈的区域。C语言还允许建立内存动态分配区域,以存放一些临时用到数据,这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放,而是需要时随时开辟,不需要时随时释放。这些数据是临时存放在一个特别的自由存储区,称为堆区。可以根据需要向系统申请所需要大小的空间。由于未在声明部分定义它们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用。
-
怎样建立内存的动态分配
-
用malloc函数开辟动态存储区
其函数原型为
void * malloc(unsigned int size);
其作用是在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型定为无符号整型(不允许为负数)。此函数的值(即“返回值”)是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的第一个字节。
malloc(100); //开辟100字节的临时分配域,函数值为其第一个字节的地址
注意指针的基类型为void,即不指向任何类型的数据,只提供一个纯地址。如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL).
-
用calloc函数开辟动态存储区
其函数原型为
void *calloc(unsigned n,unsigned size);
其作用是在内存的动态存储区中分配n个长度的为size的连续空间,这个空间一般比较大,足以保存一个数组。
用calloc函数可以为为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的第一个字节的指针;如果分配不成功,返回NULL.
p=calloc(50,4) //开辟50X4个字节的临时分配域,把首地址赋给指针变量p
-
用realloc函数重新分配动态存储区
其函数原型为
void *realloc(void *p,unsigned int size);
如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用recalloc函数重分配。
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重新分配不成功,返回NULL。
realloc(p,50) //将p所指向的已分配的动态空间改为50字节
-
用free函数释放动态存储空间
其函数原型为
void free(void *p)
其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。
p应是最近一次调用calloc或malloc函数时得到的函数返回值。
free(p) //释放指针变量p所指向的已分配的动态存储空间
free函数没有返回值。
当在使用这些函数的时候请添加头文件****
-
-
void指针类型
void 指针是一种特殊的指针,表示为“无类型指针”,
void 指针没有特定的类型,因此它可以指向任何类型的数据。也就是说,任何类型的指针都可以直接赋值给 void 指针,而无需进行其他相关的强制类型转换.
void *p1; int *p2; … p1 = p2;
8.9 有关指针的小结
-
首先要准准确理解指针的概念。指针是一个C语言中一个形象化的名词形象的表示“指向”的关系,其在物理上的实现是通过地址来完成的。
- &a是变量a的地址,也可称为变量a的指针。
- 指针变量是存放地址的变量
- 指针变量的值是一个地址
- 指针变量也可以称为地址变量,它的值是地址
- &是取地址运算符,&a是a的地址,也可以说,&是取指针运算符。&a是变量a的指针(即指向变量a的指针)。
- 数组名是一个地址,是数组首元素的地址,也就是说,数组名是一个指针,是首元素的指针
- 函数名是一个指针(指向函数代码区的首字节),也可以说函数名是一个地址(函数代码区首字节的地址)
- 函数的实参如果是数组名,传递给形参的是一个地址,也可以说,传递给形参的是一个指针。
-
在C语言中函数的所有数据都是有类型的,对地址而言,也是同样的,它由指针类型数据的存储方式存储。指针型存储单元是专门用来存放地址的,指针型数据的存储形式就是地址的存储形式。它不是一个简单的纯地址,还有一个指向问题,也就是说它指向的是那种类型的数据。
一个地址型的数据包含3个信息:
1.表示内存编号的纯地址 2.它本身的类型,即指针类型 3.以它为标识的存储的的单元中存放的是什么类型的数据,即基类型。
-
要区别指针指针变量。指针就是地址,而指针变量是用来存放地址的变量。类型是没有值的,只有变量才有值,正确的说法是指针变量的值是一个地址。
-
什么叫“指向”?地址就意味着指向,因为通过地址能找到去油该地址的对象,对于指针变量来说,把谁的地址存放在指针变量中,就说此指针变量指向谁。并不是任何类型数据的地址都可以存放在同一个指针变量中,只有指针变量的基类型相同的数据才能放在相应的指针变量中。
void *指针是一种特殊的指针,不指向任何类型的数据。如果需要用此地址指向某类型的数据,应先对地址进行类型转换。
-
要深入掌握在对数组的操作中正确地使用指针,要搞清楚指针的指向。
-
有关指针变量的归纳比较
变量定义 类型表示 含义 int i int 定义整型变量i int *p int * 定义p为指向整型数据的指针变量 int a[5] int [5] 定义整型数组a,它有五个元素 int *p[4] int*[4] 定义指针数组p,它由四个指向整型数据的指针元素组成 int (*)[4] int(*)[4] p为指向包含四个元素的一维数组的指针变量 int f() int () f为返回整型函数值的函数 int *p() int*() p为返回一个指针的函数,该指针指向整型数据 int (*)() int(*)() p为指向函数的指针,该函数返回一个整型值 int **p int ** p是一个指针变量,它指向一个指向整型数据的指针变量 void *p void * p是一个指针变量,基类型为void(空类型),不指向具体的对象 -
指针运算
-
指针变量加减一个整数。
p++,p--,p+1,p-1,p+=i,p-=i等均是指针变量加减一个整数。 将该指针变量的原值(是一个地址)和它指向的变量所占用的存储单元的字节数相加(减)
-
指针变量赋值
将一个变量地址赋给一个指针变量。 p=&a; (将变量a的地址赋给p) p=array; (将数组array首元素地址赋给p) p=&array[i]; (将数组array第i个元素的地址赋给p) p=max; (max为定义的函数,将max的入口地址赋给p) p1=p2; (p1和p2是基类型相同指针变量,将p2的值赋给p1)
-
两个指针变量可以相减:如果两个指针变量指向同一个数组中的元素,则两个指针变量值之差是两个指针之间的元素个数。
-
两个指针变量比较
若两个指针指向同一个数组的元素,则可以进行比较。指向前面的元素的指针变量“小于”指向后面元素的指针变量。如果两个指针不是指向同一个数组比较将没有意义。
-
-
指针可以有空值,即该指针变量不指向任何变量
9.用户自己建立数据类型
9.1 定义和使用结构体变量
-
自己建立结构体类型
C语言允许用户建立由不同类型数据组成的组合型的数据结构,它称为结构体。在其他一些高级语言中称为“记录”(record)
建立结构类型:
struct Student{ int num; //学号为整型 char name[20]; //姓名为字符串 int age; //年龄为整型 char sex; //性别为字符型 float score; //分数为浮点型 char addr[30]; //地址为字符串 }; //注意最后有一个分号
上面的代码是由程序开发者指定了一个数据结构struct Student(struct是声明结构体类型必须使用的一个关键字,不能省去)
声明结构体的一般形式:
struct 结构体名 {类型名 成员名;};
结构体类型的名字是由一个关键字struct和结构体名组合而成(例如:struct Student),结构体名是由用户指定的,又称为“结构体标记”,以区别其他的结构体类型。上面的结构体声明中Student就是结构体名(结构体标记)
结构体类型并非只有一种,而是可以设计出许多种结构类型。
-
定义结构体类型变量
-
先声明结构体类型,再定义该类型的变量
struct Student student1,student2{ int num; //学号为整型 char name[20]; //姓名为字符串 int age; //年龄为整型 char sex; //性别为字符型 float score; //分数为浮点型 char addr[30]; //地址为字符串 }; //注意最后有一个 //struct Student(结构体类型名) //student1,student2(结构体变量名)
-
在声明类型的同时定义变量
例如:
struct Student{ int num; //学号为整型 char name[20]; //姓名为字符串 int age; //年龄为整型 char sex; //性别为字符型 float score; //分数为浮点型 char addr[30]; //地址为字符串 } student1,student2; //注意最后有一个分号
它的作用与第一种方法相同,但是在定义struct Student类型的同时定义两个struct Student类型的变量student1和student2。这种定义方法的一般形式为
struct 结构体名 { 成员列表 }变量名表列;
声明类型和定义变量放在一起进行,能直接看到结构体的结构,比较直观,在写小程序时用此方式比较方便,但写大程序时,往往要求对类型的声明和对变量的定义分别放在不同的地方,以使程序结构清晰,便于维护,所以一般不多用这种方式。
-
不指定类型而直接定义结构体类型变量
其一般形式为
struct { 成员表列 }变量名表列;
指定了一个无名的结构体类型,它没有名字(不出现结构体名)。显然不能再以此结构体类型去定义其他变量。这种方式用得不多。
说明
(1)结构体类型与结构体变量是不同的概念,不要混淆。只能对变量赋值,存取或运算,而不能对一个类型赋值,存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
(2)结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象。程序中可以另定义一个变量num,它与struct Student 中的num 是两回事,互不干扰。
(3)对结构体中变量中的成员(即“域”),可以单独使用,它的作用与地位相当于普通变量。
-
-
结构体变量的初始化和引用
例子
#include<stdio.h> int main(){ struct Student{ long int num; char name[20]; char sex; char addr[20]; }a={10010,"LiHua",'M',"123 HuBei"}; printf("NO.:%1d\nname:%s\nsex:%c\naddress:%s\n",a.num,a.name,a.sex,a.addr); return 0; }
可以引用结构体变量中成员的值,引用方式为
结构体变量名.成员变量
如果成员变量本身又属于一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员。只能对最低级的成员进行赋值或存取以及运算。
对结果体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)
同类型的结构体变量可以相互赋值
可以引用结构体变量成员的地址,也可以引用结构体变量的地址。结构体变量的 地址主要用作函数参数,传递结构体变量的地址
**例如:**输入两个学生的学号,姓名和成绩,输出成绩较高的学生的学号,姓名和成绩。
#include<stdio.h> int main(){ struct Student{ int num; char name[20]; float score; }student1,student2; scanf("%d%s%f",&student1.num,&student1.name,&student1.score); scanf("%d%s%f",&student2.num,&student2.name,&student2.score); printf("最高的分数是:\n"); if(student1.score>student2.score){ printf("%d %s %6.2f\n",student1.num,student1.name,student1.score); } else if(student1.score<student2.score){ printf("%d %s %6.2f\n",student2.num,student2.name,student2.score); } else{ printf("%d %s %6.2f\n",student1.num,student1.name,student1.score); printf("%d %s %6.2f\n",student2.num,student2.name,student2.score); } return 0; }
9.2 使用结构体数组
-
定义结构体数组
用一个简单的例子来说明怎么定义和引用结构体数组。
**例题:**有三个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。
#include<stdio.h> #include<string.h> struct Person{ char name[20]; int count; }leader[3]={"Li",0,"Zhang",0,"Sun",0}; int main(){ int i,j; char leader_name[20]; for(i=1;i<=10;i++){ scanf("%s",leader_name); for(j=0;j<3;j++){ if(strcmp(leader_name,leader[j].name)==0) leader[j].count++; } } printf("\nResult:\n"); for(i=0;i<3;i++){ printf("%5s:%d\n",leader[i].name,leader[i].count); } return 0; }
说明:
(1)定义结构体数组一般形式是: 1.struct 结构体名 {成员列表} 数组名[数组长度]; 2.先声明一个结构体类型,然后再使用此类型定义结构体数组; 结构体类型 数组名[数组长度]; (2)对结构体数组初始化的形式是在定义数组的后面加上: ={初值表列}; 例如:struct Preson leader[3]={"li",0,"zhang",0,"suan",0};
-
结构体数组的应用举例
**例题:**有n个学生的信息(包括学号,姓名,成绩),要求按照成绩的高低顺序输出各学生的信息
#include<stdio.h> struct Student { int num; char name[20]; float score; }; int main(){ struct Student stu[5]={{101,"wang",90},{102,"zhang",100},{103,"deng",80},{104,"qian",60},{105,"li",70}}; struct Student temp; const int n=5; int i,j,k; printf("The order is:\n"); for(i=0;i<n-1;i++){ k=i; for(j=i+1;j<n;j++){ if(stu[j].score>stu[k].score){ k=j; temp=stu[k]; stu[k]=stu[i]; stu[i]=temp; } } } for(i=0;i<n;i++){ printf("%6d %s %6.2f\n",stu[i].num,stu[i].name,stu[i].score); } printf("\n"); return 0; }
9.3 结构体指针
-
指向结构体变量的指针
指向结构体对象的指针变量既可以指向结构体变量,也可以指向结构体数组中的元素。指针变量的基类型必须与结构体变量的类型相同。
**例子:**通过结构体变量的指针变量的指针输出结构体变量中成员信息
#include<stdio.h> #include<string.h> int main(){ struct Student{ //声明结构体类型struct Student long num; char name[20]; char sex; float score; }; struct Student stu_1; //定义struct Student类型变量stu_1 struct Student *p; //定义指向struct Student类型数据的指针变量p p=&stu_1; //p指向stu_1 stu_1.num=10101; //对结构体变量的成员赋值 strcpy(stu_1.name,"Li Ling"); //用字符串复制函数给stu_1.name赋值 stu_1.sex='M'; stu_1.score=89.5; printf("No.:%1d\nname:%s\nsex:%c\nscore:%5.1f\n",stu_1.num,stu_1.name,stu_1.sex,stu_1.score); //输出结果 printf("No.:%1d\nname:%s\nsex:%c\nscore:%5.1f\n",(*p).num,(*p).name,(*p).sex,(*p).score); return 0; }
**说明:**为了使用方便和直观,C语言允许把(*p).num用p->num代替,"->“代表一个箭头,p->num表示p所指向的结构体变量中的num成员。同样,(*p).name等价于p->name.”->"称为指向运算符。
如果p指向一个结构体变量stu,以下3种用法等价:
- stu.成员名(如stu.num);
- (* p).成员名(如(* p).num);
- p->成员名(如p->num).
-
指向结构体数组的指针
**例子:**有三个学生的信息,放在结构体数组中,要求输出全部学生的信息
#include<stdio.h> struct Student{ int num; char name[20]; char sex; int age; }; struct Student stu[3]={{101,"王",'M',19},{101,"张",'W',20},{101,"李",'W',19}}; int main(){ struct Student *p; printf("学号 姓 性 年龄\n"); for(p=stu;p<stu+3;p++){ printf("%d %6s %6c %8d\n",p->num,p->name,p->sex,p->age); } return 0; }
**说明:**如果p的初值为stu,即指向stu的序号为0的元素,p加1后,p就指向下一个元素。
例如:
(++p)->num //先使p自加1,然后得到p指向的元素中的num元素中的成员值(101) (p++)->bun //先求得p->num的值(101),然后再使p自加1,指向stu[1] //请注意以上二者的不同
-
用结构体变量和结构体变量的指针作函数参数
将一个结构体变量的值传递给另一个函数,有三个方法:
-
用结构体变量的成员做参数。例如,用stu[1].num或stu[2].num作函数实参,将实参值传给形参。用法和普通变量作实参是一样的,属于“值传递”方式。应当注意实参与形参的类型保持一致。
-
用结构体变量作实参。用结构体变量作实参时,采取的也是“值传递”方式,将结构体变量所占的内存单元的内容全部按顺序传递给形参,形参也必须是同类型的结构体变量。在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,开销是很可观的。此外,由于采用值传递方式,如果在执行被调用函数期间改变了形参(也是结构体变量)的值,该值不能返回主调函数,这往往造成使用上的不便,因此一般较少用这种方法。
-
用结构体变量 (或数组元素)的指针作实参,将结构体变量(或数组元素)的地址传给形参。
**例题:**有n个结构体变量,内含学生学号,姓名和3门课程的成绩。要求输出平均成绩最高的学生的信息(包括学号,姓名,3门课程成绩和平均成绩)
#include<stdio.h> #define N 3 struct Student{ int num; char name[20]; float score[3]; float aver; }; int main(){ void input(struct Student stu[]); struct Student max(struct Student stu[]); void print(struct Student stu); struct Student stu[N],*p=stu; input(p); print(max(p)); return 0; } void input(struct Student stu[]){ int i; printf("请输入各学生的信息:学号,姓名,3门课成绩:\n"); for(i=0;i<N;i++){ scanf("%d %s %f %f %f",&stu[i].num,&stu[i].name,&stu[i].score[0],&stu[i].score[0],&stu[i].score[1],&stu[i].score[2]); stu[i].aver=(stu[i].score[0]+stu[i].score[1]+stu[i].score[2])/3.0; } } struct Student max(struct Student stu[]){ int i,m=0; for(i=0;i<N;i++){ if(stu[i].aver>stu[m].aver){ m=i; } return stu[m]; } } void print(struct Student stud){ printf("\n成绩最高的学生是:\n"); printf("学号:%d\n姓名:%s\n三门成绩:%5.1f,%5.1f,%5.1f\n平均成绩:%6.2f\n",stud.num,stud.name,stud.score[0],stud.score[1],stud.score[2],stud.aver); }
-
9.4 用指针处理链表
-
什么是链表
链表是一种常见的数据结构。它是动态地进行存储分配的一种数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
-
建立简单的静态链表
**例题:**建立一个简单链表,它由3个学生数据的结点组成,要求输出各节点中的数据。
#include<stdio.h> struct Student{ //声明结构体类型struct Student int num; float score; struct Student *next; }; int main(){ struct Student a,b,c,*head,*p; //定义三个结构体变量a,b,c作为链表的节点 a.num=10101; a.score=89.5; b.num=10102; b.score=99.5; c.num=10103; c.score=79.5; //对三个节点赋值 head=&a; //将节点a的起始地址赋给指针head a.next=&b; //将节点b的起始地址赋给a节点的next成员 b.next=&c; //将节点c的起始地址赋给b节点的next成员 c.next=NULL; //c节点的next成员不存放其他节点地址 p=head; do{ printf("%1d %5.1f\n",p->num,p->score); p=p->next; }while(p!=NULL); return 0; }
-
建立动态链表
建立链表的函数
#include<stdio.h> #include<stdlib.h> #define LEN sizeof(struct Student) struct Student { long num; float score; struct Student *next; }; int n; struct Student *creat(void){ struct Student *head; struct Studnet *p1,*p2; n=0; p1=p2=(struct Student*)malloc(LEN); scanf("%1d,%f",&p1->num,&p1->score); head=NULL; while(p1->num!=0){ n=n+1; if(n==1) head=p1; else p2->next=p1; p2=p1; p1=(struct Student*)mallco(LEN); scanf("%1d,%f",&p1->num,&p1->score); p2->next=NULL; return (head); } } int main(){ struct Student *pt; pt=creat(); printf("\nnum:%1d\nscore:%5.1f\n",pt->um,pt->score); return 0; }
-
输出链表
include<stdio.h> #include<stdlib.h> #define LEN sizeof(struct Student) struct Student { long num; float score; struct Student *next; }; int n; void print(struct Student *head){ struct Student *p; printf("\nNow,These%drecords are:\n",n); p=head; if(head!=NULL){ do { printf("%1d %5.1f\n",p->num,p->score); p=p->next; }while(p!=NULL); } }
9.5 共用体类型
-
什么是共用体类型
使几个不同的变量共享同一段内存结构,称为共用体结构。
union 共用体名 { 成员列表 }变量表列;
与结构体变量不同的是,结构体变量所占内存长度是各成员占的内存长度之和。
而共用体变量所占的内存长度等于最长的成员的长度。
-
引用共用体变量的方式
只有先定义了共用体变量才能引用它,但注意,不能引用共用体变量,而只能引用共用体变量中的成员。
当你引用引用共用体变量,而存储共用体是用不同的类型存放数据,有不同的长度,仅写共用体变量名,系统无法知道究竟应该输出哪一个成员的值。
-
共用体类型数据的特点
9.6 使用枚举类型
9.7 用typedef声明型类型名
10.对文件的输入输出
10.1 C文件的有关基本知识
-
什么是文件
(1)程序文件:包括源程序文件(后缀为.c),目标文件(后缀为.obj),可执行文件(后缀为.exe)等。这种文件的内容是程序代码。
(2)**数据文件:**文件的内容不是程序,而是供程序运行时读写的数据,如在程序运行过程中输出到磁盘(或其他外部设备)的数据,或在程序运行过程中供读入的数据。
为了简化用户对输入输出设备的操作,使用户不必区分各种输入输出设备之间的区别,操作系统把各种设备都统一作为文件来处理。从操作系统的角度来看,每一个与主机相连的输入输出设备都看作一个文件。
文件是程序设计中一个重要的概念。所谓文件一般指存储在外部介质上数据的集合。操作系统是以文件为单位对数据进行管理的,也就是说,如果想找存放在外部介质上的数据,必须先按文件名找到所指定的文件,然后再从该文件中读取数据。
输入输出是数据传送的过程,数据如流水一样从一处刘向另一处,因此常将输入输出形象地称为流,即数据流。流表示了信息从源到目的端的流动。文件是由操作系统进行统一管理的,无论是用Word打开或保存文件,还是C程序的输入输出都是通过操作系统进行管理。
C语言把文件看作一个字符(或字节)的序列,即由一个一个字符(或字节)的数据顺序组成。一个输入输出流就是一个字符流或字节流。
-
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。文件标识包括三部分:(1)文件路径;(2)文件名主干;(3)文件后缀
D:\CC\template \file. c | | | | | | V V V 文件路径 文件名主干 文件后缀
-
文件的分类
根据数据的组织形式,数据文件可以分为ASCII文件和二进制文件。数据是在内存中是以二进制形式存储的,如果不加转换的输出到外存,就是二进制文件,可以认为它就是存储在内存的数据的映像,所以也称之为映像文件。如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称文本文件,每一个字节存放一个字符的ASCII代码。
-
文件缓冲区
ANSI C标准采用”缓冲文件系统“处理数据文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。
每一个文件在内存中只有一个缓冲区,在向文件输出数据时,它就作为输出缓冲区,在文件输入数据时,它就作为输入缓冲区。
-
文件类型指针
缓冲文件系统中,关键的概念是”文件类型指针“,简称”文件指针“。每一个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息。