要求:了解C语言的特点和C程序的组成,理解程序设计的环境
例1.1 (P6)
#include //编译预处理指令
int main() //定义主函数
{
printf("This is a C program.\n");
return 0; //函数执行完毕时返回函数值0
}
注意:1.一定要包含编译预处理指令(P10),否则输入输出函数等无法使用
2.每个C语言程序都必须有一个main函数,并且只能有一个main函数(选择题)
例1.2 (P8)
#include
int main()
{
int a,b,sum; //定义a,b,sum为整型变量
a = 123;
b = 456;
sum = a + b; //= 表示将a和b相加的值579赋值给sum sum = 579
printf("sum is %d\n", sum); //%d表示十进制数
return 0;
}
%d 就相当于一个占位符,帮sum占个位置。
总结:编程时一定要写的三行代码:#include
、int main()
、return 0;
例1.3
#include
int main()
{
int max(int x,int y); //对被调用函数max的声明
int a,b,c;
scanf("%d,%d",&a,&b);
c = max(a,b);
printf("max = %d\n",c);
return 0;
}
int max(int x,int y)
{
int z;
if(x > y)
z = x;
else
z = y;
return (z); //将z的值作为max函数值,返回到调用max函数的位置
}
注意:1. 函数的返回类型和函数的声明类型是一样的
2. 如果main函数写在被调用函数之前,那么一定要先声明被调用函数,main函数在其之后,可不用声明。
一个程序由一个或多个源程序文件组成
源程序文件中又包含: 1.预处理指令(预处理命令以符号“#”开头)
2.全局声明
3.函数定义
函数是C程序的主要组成部分
一个C语言程序是由一个或多个函数组成的
(记住:C语言程序是由函数组成的!)
一个函数包括 函数首部 和 函数体 两个部分 (填空题)
main函数要点总结(笔记):
1.
2.
3.
要求:了解算法的概念、特性、掌握用自然语言、流程图和伪代码表示算法。理解结构化程序设计方法
例2.1 求 1×2×3×4×5 (求5!) 。
#include
int main()
{
int num = 2;
int s = 1;
while(num <= 5)
{
s = s * num; // s = 2
num += 2;
}
printf("s = %d\n",s);
return 0;
}
例2.2 给出一个大于或等于3的正整数,判断它是不是一个素数。 (错了)
提示:素数是除了1和该数本身之外,不能被其他任何整数整除的数
#include
int main()
{
int num = 13;
for(int i=2;i < num;i++)
{
if( num % i == 0)
{
printf("%d不是素数",num);
break;
}
printf("%d是素数",num);
break;
}
return 0;
}
例2.3 判定2000-2500年中的每一年是否为闰年,并将结果输出。(思考)
提示(真题):1. 能被4整除,但不能被100整除的年份都是闰年
2. 能被400整除的年份是闰年
(y % 4 == 0 )&&(y % 100 != 0)||(y % 400 == 0)
例2.4 求
1 − 1 2 + 1 3 − 1 4 + . . . + 1 99 − 1 100 ( 思 考 ) 1-\frac12+\frac13-\frac14+...+\frac1{99}-\frac1{100}(思考) 1−21+31−41+...+991−1001(思考)
要求:掌握C的简单数据类型,基本运算和各种表达式的含义
掌握各种类型的数据在内存的存储方式
熟练掌握数据的输入/出函数,灵活运用各种数据格式符号
例3.1 有人用温度计测量出用华式法表示的温度,今要求把它转换为以摄氏法表示的温度。
c = 5 9 ( f − 32 ) c\;=\;\frac59(\;f\;-32) c=95(f−32)
#include
int main()
{
float c,f;
f = 64.0; //64也可以,并不强求一定要加小数点
c = (5.0/9) * (f - 32);
printf("f = %f\nc = %f\n",f,c);
return 0;
}
运行结果:
f = 64.000000
c = 17.777778
思考: c = (5/9)*(f-32) 可以吗? 5/9 = 0 c= 0
写一段代码测试一下float与double保留的有效位数
#include
int main()
{
float f = 192.0020123;
double e = 123.9123932810938;
printf("f = %f\ne = %f", f, e);
return 0;
}
运行结果:
以上结果表明: %f 只能保留6位小数
注意: 在用%f输出时要注意数据本身能提供的有效数字,如float型数据的存储单元只能保证 6 位有效数字,即从左面开始的第7
位数字以后的数字并不保证是绝对正确的,double型数据能保证15位有效数字。(选择题)
在程序运行过程中,其值不能被改变的量称为常量,分为以下几类:
(1)整型常量。如1000,12345,0,-345等
(2)实型常量。(float double)
a. 十进制小数形式,由数字和小数点组成。如123.456,-56.79等
b. 指数形式,如12.34e3(代表12.34×103)0.145E-25(代表0.145×10^(-25))等 。规定以字母e或E代表以10为底的指数。
注意:e或E之前必须有数字,且e或E后面必须为整数!(选择题)
判断一下这样的书写方式对不对? e4 , 12e2.5, 23E12,-1e1
(3)字符常量 (选择题)
a. 普通字符。用单撇号括起来的一个字符,如:‘a’,‘Z’,‘3’,‘?’… (选择题)
那可以这样表示吗? ‘ab’, ‘123’
里边的字符是以ASCⅡ码(美国信息交换标准代码)存储的,如‘a’ 为97,‘A’ 为65。
b. 转义字符。常用的有 \n,\t 。
解释(重要):‘\101’ :表示八进制数 101 的ASCⅡ字符哦
‘\012’ :表示八进制数 12 的ASCⅡ字符
‘\x41’ :表示十六进制数 41 的ASCⅡ字符
‘\0’ 、‘\000’ :表示ASCⅡ码为0的控制字符
‘\033’ 、‘\x1B’ :表示ASCⅡ代码为27(十进制)的字符
(4)字符串常量
如“boy” ,“123” ,双撇号。
(5)符号常量。用#define指令,指定用一个符号名称代表一个常量。
#define PI 3.1416 //注意行末没有分号(选择题)
注意:要区分符号常量和变量。符号常量不占内存,只是一个临时符号,在预编译后这个符号就不存在了,对符号常量的名字是不分配存储单元的,所以不能对符号常量赋新值。
#define S(a,b) a*b
area = S(3,2)
问: area = ? 6
#define S(r) r*r
area = S(a + b)
问:area = ? (a+b)*(a+b) a+b*a+b S(r) (r)*(r)
变量必须先定义后使用
int a;
a = 10;
或者
int a = 10 ;
不能直接 a = 10 一定要指定数据类型
变量名实际上是以一个名字代表的一个存储地址
从变量中取值,实际上是通过变量名找到相应的内存地址,从该存储单元中读取数据。
格式 : const int a = 3;
表示a被定义为一个整型变量,指定其值为3,而且在变量存在期间其值不能改变。
问1:常变量与变量的区别?
常变量具有变量的基本属性:有类型,占存储单元,只是不许改变其值。
问2:常变量与符号常量的区别?
一个是常量,一个是变量,区别很大。
C语言规定标识符只能由字母、数字、和下划线3种字符组成。且第一个字符必须为字母或下划线。(选择题)
判断下列标识符不合法的原因
M.D.John,¥123,#33,3D64,a>b
标识符区别大小写
int ,short int(short) ,long int(long),char,bool,float,double
#include
int main()
{
printf("%d",sizeof(int));
return 0;
}
字节:存储单元的长度
各数据类型的字节数:int 4 float 4 double 8 char 1 *p 4
(p44)
求负数的补码
有符号数 第一位为0代表为整数 第一位为1代表为负数
如果给整型变量分配2个字节,最大值0111111111111111 ,数值为(2^15 -1)怎么算的?
(p46)
说明:1、只有整型(包括字符型)数据可以加signed或unsigned修饰符,实型数据(浮点型)不能加。
2、对无符号整型数据用“%u”格式输出。%u表示用无符号十进制数的格式输出。
#include
int main()
{
unsigned short price = -1;
printf("%d\n", price);
}
字符是以整数形式(字符的ASCⅡ代码)存放在内存单元中的。
字符’1’ 与整数1 是不同的概念,前者占1个字节,后者占4个字节。
#include
int main()
{
char c = '?';
printf("%c %d\n",c,c);
return 0;
}
用%d的格式输出十进制整数63,用%c格式输出字符‘?’。
如果将一个负整数赋给有符号字符型变量是合法的,但它不代表一个字符,而作为一字节整型变量
#include
int main()
{
char c = 255;
printf("%d\n",c);
return 0;
}
如果输出为 255 则该系统默认为无符号 如果输出为-1 则认为是有符号
浮点型数据是用来表示具有小数点的实数。
规范化的指数形式:小数部分中小数点前的数字为0,小数点后的第1位数字不为0
3.14159的规范化 0.314159e001
整型常量
在一个整数的末尾加大写字母L或小写字母l,表示它是长整型(long int),例如123L,234l 。
long a = 123L ;
乘号 *
除号 /
取余 %
注意:1.两个实数相除的结果是双精度实数,两个整数相除的结果为整数。
5/3 = ?1 -5/3 = ?(默认)
2.% 两侧的数据均为 整型量
3.5 % 2 不合法的
++i --i
i++ i–
int i = 1;
printf("i= %d\n",i++);
printf("i= %d",--i);
结果为多少? i = 1
i = 1
注意:++ 和 – 只能用于变量,而不能用于常量或表达式
如 5 ++ 或(a+b)++ 都是不合法的。
优先级 遵守 数学运算规则
结合性 : 左结合 右结合
(a+b)*c
类型不同时,会自动进行类型转换
运算中 有int,float和double 结果是 double
有char 和 int 结果为int
有char 和 实型 结果为 double
#include
int main()
{
float i = 1.980980970970;
printf("%f\n", i);
printf("%f", i + 'A');
return 0;
}
输出: 1.980980
问:诸如%f %d %c 可以随便用吗?
int a = 12
%f
计算
int i = 10;
float f = 4;
double d = 7.5;
'c' + i / f - d * 3 + 1 = ?
printf("%f",'c' + i / f - d * 3 + 1);
输出结果为:
(double)a
(int)(x + y)
(float)(5 % 3)
(int)x + y
a = (int)x
1、运算优先级:
比如 c = a + b && ! 2 * 7
2、逗号运算符(,)
其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。
例1:
int a1,a2,b=2,c=7,d=5;
a1 = (++b, c--, d+3);
a2 = ++b, c--, d+3;
a1 = ? 8
a2 = ? 4
例2:
int a[2],x=2,y=5;
a[0]=(x+3,y++,x++);
a[1]=(x++,x+3,x+7);
a[0] = ? 2
a[1] = ? 11
x = ? 4
y = 6
例3:
int i=24;
int n = (i++,i++,i++,i++);
i = ? 27
3、条件运算符 ? :
c = a > b ? a : b 如果a > b c = a ,否则 c = b
if()
{
}else
{
}
i = i + 1 (是表达式,不是语句)
任何表达式加上分号 成为语句
i = i + 1 ;
if(表达式)
{
}
1、sqrt(x) 求根公式
abs(x) 求绝对值
pow(x,n) 求x的n次方 (x、n及函数的返回值是double类型 , 所以要用%f输出)
C语言程序要使用这些数学方法,必须引入 #include
2、左值:它可以出现在赋值运算符的左侧,值可以改变。表达式,常量不能作为左值。
a = (b = 3 * 4)
(a = b) = 3 * 4
a + = a - = a * a a = 12
3、概括:浮点型 —> 整型
int i = 3.9;
printf("i = %d", i);
输出 i = 3
整型 —> 浮点型
double —> float(精度发生变化 15 —> 6) %f
float —> double(精度发生变化 6 —> 15)
char —> int
4、变量赋初值
int a = 10;
float b = 1.23;
int a,b,c = 10;
int a = 12,b = 11,c = 10;
int a = b = c = 3;
但是 a = b = c = 3 是合法的赋值表达式
scanf("%lf%lf%lf",&a,&b,&c);
printf("x1 = %7.2f",x1);
指定数据占7列,小数占2列
(7表示输出数据在终端设备上占用7个字符宽度右对齐,实际数据位数(包括小数点)小于7时左边用空格补齐,大于7时按实际位数向右扩展输出。)
double x1 = 1332.1231234;
double x2 = 31.233492;
printf("x1 = %7.2f\nx2 = %7.2f", x1, x2);
输出结果:
x1 = 1332.12
x2 = 31.23
#include
#include "stdio.h"
写法都可以
区别:调用自己写的头文件 必须使用“ ”来引入
printf("%5d\n%5d\n",12,-345); 向右靠齐,左边补空格
输出结果:
12
-345
char ch = 'a';
printf("%c\n",ch);
printf("%5c",ch);
输出结果:
a
a
printf("%s","CHINA");
输出字符串 CHINA
%f 只能输出6位小数
% m.nf
double a = 1.0;
printf("%20.15f\n",a/3);
运行结果: 0.333333333333333
% -m.nf
float a = 10000 / 3.0;
printf("%-25.15f,%25.15f\n",a,a);
运行结果:3333.333251953125000 , 3333.333251953125000
printf("%e",123.456);
printf("%13.2e",123.456);
运行结果:1.234560e+02 书上 小数部分6位,指数部分5位
运行结果: 1.23e+02
double a = 12345678954321;
printf("%f\t%e\t%G\n",a,a,a);
输出结果:12345678954321.000000 1.234568e+13 1.23457E+13
表3.6
int n = 27; //p40
printf("%o\n", n); //033
printf("%x %X\n", n, n);
printf("%e %E %g", 123.456, 123.456, 123.456);
运行结果:
33
1b 1B
1.234560e+02 1.234560E+02 123.456
m:数据最小宽度(之前解释过)
注意:除X,E,G外,其他格式字符必须用小写字母。
1、表3.9
scanf("%d%*c", &n)
%*c表示忽略掉一个字符型的输入项,&n表示对象n在内存中的地址。
整体表示输入两个数,并且忽略掉第二个字符型的输入项,然后将输入的对象存入n。
int n;
scanf("%d%*c", &n);
printf("%d\n", n);
2、使用scanf函数时应注意的问题
“格式控制” 中的格式什么样就以该格式输入数据
注意:输入数值时,scanf("%d%d%d",&a,&b,&c)
这种数值输入时就要加空格,以区分是输入三个数据
%c 空格作为一个有效字符会被读取 1 2 3
int a;
char b;
float c;
scanf("%d%c%f", &a,&b,&c);
printf("%d %c %f", a, b, c);
putchar函数是输出字符的函数,所以它输出的是字符。
getchar函数没有参数,从终端输入一个字符。只能接收一个字符。
#include
int main()
{
char a,b,c;
a = getchar();
b = getchar();
c = getchar();
putchar(a);
putchar(b);
putchar(c);
putchar('\n');
return 0;
}
如果输入的是 B
O
程序如何运行?
要求:理解程序中的选择结构和条件判断,正确表达各种条件,熟悉if,switch语句使用
C语言有两种选择结构:if 语句 (实现双分支 )
switch 语句 (实现多分支)
例 4.1
重点: 在用scanf 函数输入双精度实型数据时,不能用“%f”格式声明,而应当用 “%lf” 格式声明
输出时不作要求。
例4.2 输入两个实数,按代数值由小到大的顺序输出这两个数
#include
int main()
{
float a,b,max;
scanf("%f,%f",&a,&b);
if( a > b)
{
max = a;
a = b;
b = max;
}
printf("%5.2f,%5.2f",a,b);
return 0;
}
例4.3 输入3个数a,b,c,要求按由小到大的顺序输出
#include
int main()
{
float a,b,c,max;
scanf("%f,%f,%f",&a,&b,&c);
if(a > b)
{
max = a;
a = b;
b = max;
}
if(a > c)
{
max = a;
a = c;
c = max;
}
if(b > c)
{
max = b;
b = c;
c = max;
}
printf("%5.2f,%5.2f,%5.2f",a,b,c);
return 0;
}
1. if()
{
...
}
2. if()
{
...
}
else //else 不能作为语句单独使用,必须与if配套使用
{
...
}
3. if()
{
...
}
else if()
{
...
}
...
else
{
...
}
关系运算符:大于 小于 的优先级 高于 == !=
算术运算符 > 关系运算符 > 赋值运算符
计算顺序 ?
假设 a = 3, b = 2 ,c = 1,表达式的值?
c > (a+b) 0
(a > b) == c 1
a == (b < c) 0
a = (b > c) 1
d = (a > b > c) 1
运算符 | 含义 | 举例 | 说明 |
---|---|---|---|
&& | 逻辑与 | a && b | a,b都为真,则结果为真,否则为假 |
|| | 逻辑或 | a || b | 如果a,b其中有一个以上为真,则结果为真,二者都为假时,结果为假 |
! | 逻辑非 | ! a | 如果a为假,则结果为真,a为真,结果为假 |
((!a) && b) || ((x > y) && c)
运算顺序:
以 1 表示为“真”,以 0 表示为“假”
1.填空题
若 a = 4 ,则a为: 真 ,那么 !a 就为 : 假 。
若 a = 4, b = 5 ,则 a && b 为: 1 ,a || b为: 1 ,a || !b 为: 1 。
4 && 0 || 2.5
为: 1 。
!a && b || x > y && c
中假设 :a = -3, b = 4, x = 3, y = 1, c = 6
运算结果:1
5 > 3 && 8 < 4 - !0
运算结果:0
运算依据:运算对象为0即为假,所以非零的数(整数,浮点型,字符型等)皆为真
P95
在逻辑表达式的求解中,并不是所有的逻辑运算符都会被执行。
判断闰年(year)的条件:
1 . _Bool
float score;
printf("Please input your grade:");
scanf("%f",&score);
_Bool a,b;
a = score >= 60;
b = score <= 69;
if(a && b) printf("The grade is C\n");
2. bool
#include
#include
int main()
{
float score;
printf("Please input your grade:");
scanf("%f",&score);
bool a,b;
a = score >= 60;
b = score <= 69;
if(a && b) printf("The grade is C\n");
return 0;
}
条件运算符 ? :
条件表达式 (表达式1)?(表达式2) :(表达式3)
条件运算符的优先级别比关系运算符和算术运算符都低
max = a > b ? a : b
max = a > b ? a : (b + 1)
为什么这个表达式2和表达式3两侧有括号?
a > b ? ( max = a ) : ( max = b)
条件运算符的优先级比赋值运算符高
a > b ? max = a : max = b
执行上述式子就会:
错误 “=”: 左操作数必须为左值
变量能为左值,表达式、常量不能为左值
switch(表达式)
{
case 常量1 :语句1
case 常量2 :语句2
case 常量3 :语句3
case 常量n :语句n
default:语句n+1
}
表达式:整数类型(包括字符型)
如果没有与switch相匹配的case,就默认执行default后的语句
可以没有default语句
case 标号出现次序不影响执行结果
每一个case常量必须互不相同
break测试
switch(grade)
{
case 'a':
case 'A':printf("成绩优异"); break;
case 'b':
case 'B':printf("成绩良好");
...
default:printf("不合法成绩");
}
grade 为 a 输出:成绩优秀
grade 为 B 输出:不合法成绩
要求:掌握循环结构和各种循环语句的语法,正确使用循环语句
while(表达式) 语句
while语句:当循环条件表达式为真,就执行循环体语句
特点:先判断条件表达式,后执行循环体语句
例 5.1 求1 +2 +3 +... +100
#include
int main()
{
int n = 1, s = 0;
while(n <= 100)
{
s = s + n;
n++;
}
printf("sum = %d",s);
return 0;
}
do
语句
while(表达式) ;
特点:至少会执行一次循环体
int i = 1;
do
{
printf("%d\n",i++); i = 1 /2 i = 2 /3
}
while(i<=2);
输出结果:
1
2
例 5.1 求1 +2 +3 +... +100
#include
int main()
{
return 0;
}
for(表达式1;表达式2;表达式3)
语句
表达式1:只执行1次
表达式2:循环条件
表达式3:执行完循环体后运行
for(循环变量赋初值;循环条件;循环变量增值)
语句
例 5.1 求1 +2 +3 +... +100
#include
int main()
{
return 0;
}
省略for循环中的某一表达式,会怎样运行呢? (P 121)
for( ; ;
三种循环都可以互相嵌套,根据自己的喜好和习惯安排!
例5.4 在全系1000学生中,征集慈善募捐,当总数达到10万元时就结束,统计此时捐款的人数,以及平均每人捐款的数目
#include
#define SUM 100000
int main()
{
float amount,aver,total;
int i;
for(i = 0,total = 0;i < 1000;i++)
{
printf("please enter amount:");
scanf("%f",&amount);
total = total + amount;
if(total>= SUM) break;
}
aver = total / i;
printf("人数为%d\n平均数为%10.2f",i,aver);
return 0;
}
注意:break语句只能用于循环语句和switch语句之中,而不能单独使用。(选择题)
例5.5 要求输出100~200之间的不能被3整除的数
#include
int main()
{
for(int i=100;i<=200;i++)
{
if(i % 3 != 0)
{
printf("%d ",i);
}
}
return 0;
}
使用continue:结束本次循环
for(int i=100;i<=200;i++)
{
if(i % 3 == 0)
continue;
printf("%d ",i);
}
例5.6 输出以下4*5的矩阵
#include
int main()
{
int i,j;
for(i=1;i<=4;i++)
{
for(j=1;j<=5;j++) i = 1 j = 1
{
printf("%d\t",i * j);
}
printf("\n");
}
return 0;
}
很明显,这是一个双重循环,思考一下在内循环中使用break,continue,出现的效果是怎样的呢?
1.是提前终止内循环还是提前终止整个循环?
测试一下!
#include
int main()
{
int i,j;
for(i=1;i<=4;i++)
{
for(j=1;j<=5;j++)
{
if(i == 3 && j == 1) break;
printf("%d\t",i * j);
}
printf("\n");
}
return 0;
}
说明:
那么continue呢?
测试一下!
#include
int main()
{
int i,j;
for(i=1;i<=4;i++)
{
for(j=1;j<=5;j++)
{
if(i == 3 && j == 1) continue;
printf("%d\t",i * j);
}
printf("\n");
}
return 0;
}
要求:理解数组是类型相同且用同一个标识符标识的一组数
熟悉定义和使用数组,掌握字符数组的定义和处理
一维数组的命名形式:
类型符 数组名[常量表达式];
比如 int a[10];
注意:
1、数组名的命名规则和变量名相同,遵循标识符命名规则。
2、a[10],10个元素,下标从0开始,不存在数组元素a[10]。
3、常量表达式中可以包括常量和符号常量,如“int a[3+5]” 是合法的。
那么int a[n] 可以吗? 为什么?
int n;
scanf("%d",&n); //企图在程序中临时输入数组的大小
int a[n];
但是
void func(int n)
{
int a[2 * n]; // 合法,n的值从实参传来
...
}
但是,如果指定数组为静态(static)存储方式,则不能用“可变长数组”,如
static int a[2 * n]; //不合法,a数组指定为static存储方式
复习一下:
各数据类型的字节数:int float double char *p
那么 数组的字节呢?
int a[12] : 12*4 = 48
double b[4] : 32
引用: a[元素标号]
例6.1 对10个数组元素一次赋值为0,1,2,3,4,5,6,7,8,9,要求按逆序输出。
#include
int main()
{
// 定义含有10个元素的数组a 想好数据类型
int a[10] ;
// 赋值
for(int i = 0 ;i<10 ; i++)
{
a[i] = i;
}
// 输出
for(int i=9 ;i>=0 ; i--)
{
prinf("%d\n",a[i]);
}
return 0;
}
在定义数组时对全部数组元素赋予初值
int a[10] = {0,1,2,3,4,5,6,7,8,9};
可以只给数组中的一部分元素赋值
int a[10] = {1,2,3,4,5};
int a[10] = {0};
对全部数组赋值时,如果数据的个数确定可以不指定数组长度
int a[] = {1,2,3,4,5};
等价于int a[5] = {1,2,3,4,5};
如果数组长度与提供初值的个数不相同,则方括号中的数组长度不能省略。
说明:定义 int double float 数组,未指定初始化的元素,默认为0 ;
char型 ,默认为 ‘\0’ ;
指针类型 ,默认为 NULL 。
例6.2 用数组来处理斐波那契数列问题。
分析:斐波那契数列规律
1 1 2 3 5 8 13 21 ...
a[2] = a[0]+a[1];
a[3] = a[1]+a[2];
#include
int main()
{
int a[20] = {1,1};
for(int i = 2;i<20;i++)
{
a[i] = a[i-1]+a[i-2];
}
return 0;
}
重点介绍
冒泡排序法:每次将相邻两个数比较,将小的调到前头,打的放在后边
9 8 5 4 2 0
第一次从左往右对比:六个数对比,对比5次,第一次将数列中最大的数排列在最右端
8 5 4 2 0 9
第二次循环:五个数对比,对比4次,第二次将数列中第二大的数排列在右侧
5 4 2 0 8 9
第三次循环:四个数对比,对比3次,将5排至右侧
4 2 0 5 8 9
第四次循环:三个数对比,对比2次,将4排至右侧
2 0 4 5 8 9
第五次循环:两个数对比,对比1次,将2排至右侧
0 2 4 5 8 9
代码实现上述:
for(int i = 0;i<5;i++)
{
for(int j=0;j<5-i;j++)
{
if(a[j] > a[j+1])
{
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
例6.3 有10个地区的面积,要求对它们按由小到大(升序)的顺序排列。
#include
int main()
{
return 0;
}
for(int i = 0 ; i < n-1 ; i++)
{
for(int j = 0; j < n-1-i ; j++)
{
if(a[j] > a[j+1])
{
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
float a[3][4], b[5][10];
二维数组中 a[0] 代表第一行 a [0] [1] 代表第一行第二列
注意:数据排列形式上是矩阵的样子,在内存中,各元素时连续存放的,是线性的。
同样,在引用数组元素时,下标值应在已定义的数组大小的范围内。
int a[3][4];
...
a[3][4] = 3;
有问题吗? 为什么?
1、分行给二维数组赋初值。
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
2、int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
也可以,系统会自动识别行列的。
3、对部分元素赋初值
int a[3][4] = {{1},{0,6},{0,0,9}};
问:1,6,9实际在第几行第几列?
4、(重要) 如果对全部元素都赋初值,则定义数组时对第1维的长度可以不指定,但第2维的长度不能省。
int a[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
5、在定义时也可以只对部分元素赋初值而省略第1维的长度,但应分行赋初值。
{}{}{}
例6.4 将一个二维数组行和列的元素互换,存到另一个二维数组中。
[ 1 2 3 4 5 6 ] [ 1 4 2 5 3 6 ] \begin{bmatrix}1&2&3\\4&5&6\end{bmatrix} \begin{bmatrix}1&4\\2&5\\3&6\end{bmatrix} [142536]⎣⎡123456⎦⎤
#include
int main()
{
//初始化矩阵a,并且定义一个二维数组b接收转置后的a数组
int a[2][3] = {{1,2,3},{4,5,6}};
int b[3][2];
int i,j;
//找二者之间的规律,并输出二维矩阵b
for(i = 0;i<3;i++)
{
for(j=0;j<2;j++)
{
b[i][j] = a[j][i];
prinf("%5d",b[i][j]);
}
}
return 0;
}
C语言中没有字符串类型,字符串是存放在字符型数组中。
char c[10];
// 字符数组中的一个元素存放一个字符
c[0]='C';c[1]='H';c[2]='I';c[3]='N';c[4]='A';
由于字符型数据是以整型形式(ASCⅡ码) 存放的,因此也可以用整型数组来存放字符数据。
int c[10];
c[0] = 'a'; // 合法,但浪费存储空间
char c[10] = {'I','','a','m','','h','a','p','p','y'};
1、如果在定义字符数组时不进行初始化,则数组中各元素的值是不可预料的。
char c[10]; //没有初始化
printf("%d%c", c[1],c[3]);
char c[10] = {'h'};
printf("%c%d\n", c[1],c[3]);
printf("%d%c", c[1], c[3]);
输出结果:
0
0
2、提供的初值个数与预定的数组长度相同,在定义时可以省略数组长度。
这样更方便。
3、二维字符数组 同 二维数值数组一样
引用其实和之前讲过的数值类型的二维数组一样,就是输出时 格式 发生变化 %c
为了测定字符串的实际长度,C语言规定了一个“字符串结束标志”,以字符‘\0’作为结束标志。
printf("How do you do?\n");
在向内存中存储时,系统自动在最后一个字符‘\n’的后面加一个’\0’,作为字符串结束标志。
那么,存储字符串常量还有其他的写法:
char c[] = {"I am happy"};
或者
char c[] = "I am happy";
注意:此时数组c的长度不是10,而是11,因为字符串常量之后加上了一个’\0’。
所以它等价于 char c[] = {'I','','a','m','','h','a','p','p','y','\0'};
而不等价于 char c[] = {'I','','a','m','','h','a','p','p','y'};
char c[10] = {"China"};
| C | h | i | n | a | \0 | \0 | \0 | \0 | \0 |
1. 不加'\0'
#include
int main()
{
char c[] = { "C program." };
printf("%s\n", c);
for(int i=0;i<5;i++)
{
scanf("%c",&c[i]);
}
printf("%s\n", c);
return 0;
}
2.加'\0'
#include
int main()
{
char c[] = { "C program." };
printf("%s\n", c);
scanf("%s",c);
printf("%s\n", c);
return 0;
}
或者 更为通俗易懂一点
#include
int main()
{
char c[] = { "C program." };
char d[] = { 'H','e','l','l','o'};
printf("%s\n", c);
for (int i = 0; i < sizeof(d); i++)
{
c[i] = d[i];
}
printf("%s\n", c);
return 0;
}
替换 char d[] = { 'H','e','l','l','o','\0' };
如果利用一个scanf函数输入多个字符串,则应在输入时以空格分隔,例如
char str1[5],str2[5],str3[5];
scanf("%s%s%s",str1,str2,str3);
scanf函数中的输入项没有加地址符&,因为在C语言中 数组名代表该数组的起始地址。
char str[13];
scanf("%s",str);
如果输入 How are you?
输出结果:
(有特定的"服务"对象): 接下来介绍的所有函数只针对字符型数组!并且使用这些函数需要引入 #include
1、puts 函数
puts(字符数组)
char str[] = {"China\nBeijing"}; //用puts函数输出的字符串中可以包含转义字符
puts(str);
输出结果:
puts();
换行
注意:
2、gets 函数
gets(字符数组)
char str[];
gets(str);
等价于 scanf("%s",str);
字符串的形式输入,送给数组的字符要加上'\0',所以若是输入Computer,则送个数组的共有9个字符。
注意:
用puts 和 gets 函数只能输出或输入一个字符串,不能写成 puts(str1,str2)
或 gets(str1,str2)
3、strcat函数 —— 字符串连接函数
strcat(字符数组1,字符数组2)
作用:把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用得到一个函数值——字符数组1的地址。
char str1[30] = {"People's Republic of "};
char str2[] = {"China"};
printf("%s",strcat(str1,str2));
若定义时改用 char str1[] = {"People's Republic of "};
则会报错。为什么?
4、strcpy 和 strncpy 函数 ——字符串复制函数
strcpy(字符数组1,字符串2)
作用:将字符串2复制到字符数组1
1.
char str1[10],str2[]="China";
strcpy(str1,str2);
2.
strcpy(str1,"China"); //字符数组1 必须写成数组名形式 字符串2可以是数组名也可以是字符串常量
3.
若复制前未对str1数组初始化,则str1最后4个字符并一定是'\0'
4.
要给字符型数组赋值必须使用strcpy函数,不能直接赋值。
str1 = "China";
str1 = str2;
赋值语句只能将一个字符赋给一个字符型变量或字符数组元素
strncpy(str1,str2,n)
: 将字符串中的前面n个字符复制到字符数组1中去,取代str1中原有的最前面的n个字符。
n < = str1中的有效字符个数
5、strcmp 函数 —— 字符串比较函数
strcmp(字符串1,字符串2)
比较的规则:两个字符串自左向右比较ASCⅡ码值大小
比较的结果:
if(strcmp(str1,str2) == 0)
printf("str1 = str2");
if(strcmp(str1,str2) > 0)
printf("str1 > str2");
if(strcmp(str1,str2) < 0)
printf("str1 < str2");
6、strlen 函数 —— 测字符串长度的函数
strlen(字符数组)
char str1[10] = "China";
printf("%d",strlen(str));
结果为:5
strlen(str1) 不等价于 str1数组的长度
strlen(str1) = str1 - 1
要求:理解函数的用途,掌握定义和调用函数
理解递归函数的实现和数组作为函数参数
试卷上:大题会有要求要用函数调用完成代码题
一些概念会在选择题涉及
函数就是“功能”,每一个函数用来实现一个特定的功能。函数的名字应反映其代表的功能。
一个C程序可由一个主函数和若干个其他函数构成。所以,C语言程序是由函数组成的!
例7.1 想输出以下的结果,用函数调用实现。
******************
How do you do!
******************
******************
******************
#include
int main()
{ // 2.声明函数
// 3.调用函数
return 0;
}
// 1.思考需要几个函数
函数声明的作用:将有关函数的信息(函数名、函数类型、函数参数的个数与类型)通知编译系统,以便编译系统对程序进行编译时,在进行到main函数调用函数时,知道它们是函数而不是变量或其他对象。
1.以下说法正确的是(C)
A、C语言程序总是从第一个函数开始执行
B、在C语言程序中,要调用函数必须在main()函数中定义
C、C语言程序总是从main()函数开始
D、C语言程序中的main()函数必须放在程序的开始部分
说明提取要点:1. 一个C程序由若干个源文件组成,一个源程序文件可以为多个C程序共用
2.一个源程序文件由一个或多个函数以及其他有关内容组成
C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回main函数,在main函数中 结束整个程序的运行。
函数不能嵌套定义(一个函数内不能再定义一个函数),函数间可以互相调用,但不能调用main函数。main函数是被操作系统调用
在程序中用到的所有函数,必须“先定义,后使用”。
如果不定义,编译系统不知道正在执行的函数是什么,要实现什么功能。
对于C编译系统提供的库函数,无需定义,用#include
定义无参函数
类型名 函数名()
{
函数体
}
函数体包括 声明部分 和 语句部分
函数包括 函数首部 和 函数体
定义有参函数
类型名 函数名(形式参数表列)
{
函数体
}
int max(int x,int y)
{
int z; //声明部分
z = x > y ? x : y; //执行语句部分
return (z);
}
print_star(); //调用无参函数
c = max(a,b); //调用有参函数
调用的函数可单独作为一个语句,可作为赋值表达式的一部分,也可作为另一个函数调用时的实参
m = max(a,max(b,c));
1.实参和形参
#include
int main()
{
int max(int x,int y);
int a,b,c;
scanf("%d,%d",&a,&b);
c = max(a,b);
printf("max is %d\n",c);
return 0;
}
int max(int x,int y)
{
int z; //声明部分
z = x > y ? x : y; //执行语句部分
return (z);
}
上述程序,a、b是实参,x、y是形参。
实参可以是 变量、常量或表达式 (重要)。
2.实参和形参间的数据传递
实参与形参的类型应相同或赋值兼容。
如果实参为int ,而形参为float,则先将实参中的值转换为float,再送到形参。
若上述程序 ,float a;
输入3.5,而形参x为int型,则在传递时先将实数3.5转换成整数3,然后送到形参x。
char型与int型可以互相通用。
在定义函数中指定的形参,在未出现函数调用时,不占内存,在发生函数调用时,形参被临时分配内存单元。
调用结束,形参单元被释放。
注意: 在执行一个被调用的函数时,形参的值发生改变,不会改变主调函数的实参的值。
这是因为实参和形参是两个不同的存储单元。
返回值的类型与函数类型一致。
实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。
如果函数值的类型和return 语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。
#include
int main()
{
int max(float ,float );
float a,b;
int c;
scanf("%f,%f",&a,&b);
c = max(a,b);
printf("max is %f\n",c);
return 0;
}
int max(float x,float y)
{
float z; //声明部分
z = x > y ? x : y; //执行语句部分
return (z);
}
int min()
{
max();
}
输入: 1.5,2.6
void max()
{
}
1.函数的声明其实就是函数的首部(函数原型),外加一个;
2.函数声明中的形参名可以省写,只写形参的类型。声明的形参名可以与定义的形参名不同。
写在所有函数前面的外部声明在整个文件范围中有效。
例题7.5
main 函数调用 max4 函数
max4 函数调用 max2 函数
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。
int f(int x)
{
int y,z;
z = f(y);
return (2 * z);
}
例7.6
age(5) = age(4) + 2
age(4) = age(3) + 2
age(3) = age(2) + 2
age(2) = age(1) + 2
age(1) = 10
求第5个同学的年龄
int main()
{
int c = age(5);
}
int age(int n)
{
if(n == 1)
return 10;
else
return age(n-1)+2; //age(4)+2
//age(3)+2+2
//age(2)+2+2+2
//age(1)+2+2+2+2
} //10+2+2+2+2
例7.7 求n! 1*2*3*4*5
1. 递推法
#include
int main()
{
int s=1,i,n;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
s = s * i;
}
return 0;
}
2.递归法
5! 5*4*3*2*1 (5!= 5 * 4! 4!= 4 * 3!... 2! = 2 * 1! 1! = 1 * 0! )
int f(int n)
{
if(n==1 || n==0 )
return 1;
else
return n*f(n-1); // 5 *f(4)
// 5*4*f(3)
// 5*4*3*f(2)
// 5*4*3*2*f(1) 1! = 1
// 5*4*3*2*1*f(0) 0! = 1
}
数组元素可以用作函数实参,不能用作形参。
因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元。
值传递,从实参传到形参,单向传递。
例7.9 输入10个数,要求输出其中值最大的元素和该数是第几个数。
#include
int main()
{
int max(int a, int b);
int a[10];
int m = a[0], n = 0;
for (int i = 0; i < 10; i++)
{
scanf("%d", &a[i]);
}
for (int i = 1; i < 10; i++)
{
if (max(m, a[i]) > m)
{
m = max(m, a[i]);
n = i; //下标
}
}
printf("max = %d\nn = %d", m, n+1);
return 0;
}
int max(int a, int b)
{
return (a > b ? a : b);
}
数组名可以作实参也可作形参,传递的是数组第一个元素的地址,所以形参数组中个元素的值会影响实参数组。
例7.10 有一个一维数组score,内放10个学生成绩,求平均成绩。
#include
int main()
{
float average(float array[10]);
float score[10],aver;
int i;
for(i = 0;i<10;i++)
{
scanf("%f",&score[i]);
}
aver = average(score);
printf("aver = %5.2f",aver);
return 0;
}
float average(float array[]) // 形参数组可以不指定大小
{
sum = 0;
for(int i=0;i<10;i++)
{
sum = sum + array[i];
}
aver = sum / 10;
return aver;
}
选择法:若对n个数从小到大排序,比较n-1次,第a次循环,都会在剩余n-a+1个的元素中产生一个最小的数排在数列左侧
第a次循环,就把array[a-1]这个位置的元素与之后的每一个元素进行比较
例 7.12 用选择法对数组中10个整数按由小到大排序
for(int i = 0;i<10;i++)
{
min = i;
for(int j = i+1; j<10;j++)
{
if(array[j] < array[min])
{
min = j; //整个内循环遍历完,k中保存了当前循环中最小元素的下标
}
}
int t = array[min];
array[min] = array[i];
array[i] = t;
}
int main()
{
int max(int array[][4]);
int array[3][4] = {{1,2,3},{0,1},{12,0,2,1}};
int m = max(array);
return 0;
}
int max(int array[][4])
{
int m;
...
return m;
}
在一个函数中定义的变量,在其他函数中能否被引用?在不同位置定义的变量,在什么范围内有效?
那么这就涉及一个 作用域 的问题
定义变量有3种情况
在函数的开头定义
在函数内的复合语句内定义
在函数的外部定义
在函数内定义的称为局部变量
//在函数的开头定义
float f1(int a)
{
int b,c;
...
}
char f2(int x,int y)
{
int b,c;
...
}
int main()
{
int m,n;
for(int i = 0;i<10;i++) // 在复合语句内定义
{
int c = m + n;
...
}
...
return 0;
}
在函数外定义的变量称为外部变量,也就是全局变量。
int p = 1,q = 5;
float f1(int a)
{
int b,c;
...
}
char c1,c2;
char f2(int x,int y)
{
int i,j;
...
}
int main()
{
int m,n;
...
return 0;
}
这里 p,q,c1,c2 都是函数外定义的,所以它们是全局变量,那他们的作用域相同吗?
由于函数的调用只能带回一个函数返回值,所以有时可以使用全局变量,得到一个以上的值。(例题7.14)
例7.15 若外部变量与局部变量同名,分析结果。
#include
int a = 3,b = 5; // a为全局变量
int main()
{
int max(int a, int b);
int a = 8; // a为局部变量
printf("max = %d\n",max(a,b));
return 0;
}
int max(int a,int b)
{
int c;
c = a > b ? a : b;
return c;
}
(这种变量设定会出现在读程序题)
结论:在局部变量的作用范围内,局部变量有效,全局变量被“屏蔽”,即它不起作用。
从变量的作用域来分可分为 和 。
另一个角度,从变量值的**存在的时间(生存期)**来分,可分为 静态存储方式 和 动态存储方式 。
动态存储区:函数调用开始时分配动态存储空间,函数结束时释放这些空间。
所以,每一个变量和函数都有两个属性,一个是数据类型,一个是数据的存储类别。
那么,在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别(如果用户不指定,系统会隐含地指定为某一种存储类别)。
C的存储类别4种:自动的(auto),静态的(static),寄存器的(register),外部的(extern)。
函数中的形参和函数中定义的局部变量(包括在复合语句中定义的局部变量),都是auto变量。
问:函数中的变量若不定义存储类别,默认为下列哪种方式(A)?
A.auto B.static C.register D.extern
因为有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占有的存储单元不释放。
在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。
例7.16 考察静态局部变量的值
#include
int main()
{
int f(int);
int a = 2, i;
for(i = 0; i<3; i++)
printf("%d\n",f(a));
return 0;
}
int f(int a)
{
auto int b = 0;
static c = 3;
b = b + 1;
c = c + 1;
return (a + b +c);
}
输出结果:
#include
int fun(int x,int y)
{
static int m = 0, i = 2;
i+=m+1;
m=i+x+y;
return m;
}
int main()
{
int j = 1,m = 1, k;
k = fun(j,m);printf("%3d",k);
k = fun(j,m);printf("%3d",k);
return 0;
}
输出结果:**5*11
例7.17 输出1到5的阶乘值。
#include
int main()
{
int fac(int n);
int i;
for(i = 1; i <= 5; i++)
{
printf("%d! = %d",fac(i));
}
return 0;
}
int fac(int n)
{
static int f = 1;
f = f*n;
return f;
}
如果有一些变量使用频繁(例如,在一个函数中执行10000次循环,每次循环中都要引用某局部变量),则为存取变量的值要花费不少时间。为提高执行效率,允许将局部变量的值放在CPU总的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取
register int f;
自动变量存储在动态存储区,静态局部变量存储在静态存储区,寄存器存储在CPU中的寄存器中。
例 7.18调用函数,求3个整数中的大者
#include
int main()
{
int max();
extern int A,B,C; //有了此声明,就可以从声明处起,合法使用该外部变量
printf("Please enter three integer numbers:");
scanf("%d %d %d",&A,&B,&C);
printf("max is %d\n",max());
return 0;
}
static int A,B,C;
int max()
{
int m;
m = A > B? A : B;
if( C > m ) m = C;
return (m);
}
如果程序由多个源程序文件组成,那么在一个文件中想引用另一个文件中已定义的外部变量,有什么办法呢?
例 7.19给定b的值,输入a和m,求a*b和a^m的值
文件 file1.c:
#include
int A;
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;
}
文件 file2.c:
extern A; // 把在file1文件中已定义的外部变量的作用域扩展到本文件中
int power(int m)
{
int i,y = 1;
for(i = 1; i <= m; i++)
{
y *= A;
}
return y;
}
p211 最后一段话
有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。
这时可以在定义外部变量时加一个static声明。
file1.c
static int A; //此时的全局变量A只能用于file1文件
int main()
{
...
}
file2.c
extern A;
void fun(int n)
{
...
A = A * n; //出错
...
}
如果我在例 7.18 int A,B,C;
前加 static ,程序可以运行吗?
看书 P212
int main()
{
extern A; //是声明,不是定义
...
return 0;
}
int A; //是定义,定义A为整型外部变量
函数本质上使全局的,但是也可以指定某些函数不能被其他文件调用。
根据函数能否被其他源文件调用,将函数区分为 内部函数 和 外部函数
如果一个函数只能被本文件中其他函数调用,称为内部函数(静态函数)。
static 类型名 函数名(形参表)
extern int fun(int a,int b)
这样,函数fun就可以为其他文件调用,其实不写extern,也默认为外部函数。
例7.20 有一个字符串,内有若干个字符,现输入一个字符,要求程序将字符串中该字符删去。用外部函数实现
file1.c(文件1)
#include
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;
}
file2.c(文件2)
void enter_string(char str[80])
{
gets(str);
}
file3.c(文件3)
void delete_string(char str[80],char ch)
{
int i,j;
for(i = j = 0;str[i] != '\0'; i++)
{
if(str[i] != ch)
str[j] = str[i];
j++;
}
str[j] = '\0';
}
file4.c(文件4)
void print_string(char str[])
{
printf("%s\n",str);
}
要求:了解通过指针间接对数据进行操作的方式
熟悉运用各种类型的指针对数据或函数进行操作
在程序中中定义一个变量,在对程序进行编译时,系统会给这个变量分配内存单元,然后,编译系统又根据定义的变量类型,
分配相对应长度的空间。内存区的每一个字节都有一个编号,这就是变量的“地址”。
由于通过地址能找到所需的变量单元,可以说,地址指向改变量单元。
比如,一个房间的房间号为2008,那么2008就是这个房间的指针,或者说,2008“指向”这个房间。
我们将地址形象化地称为“指针”。
指针 就是 地址
三个变量 i = 3(2000~2003), j = 6(2004~2007) , k = 9(2008~2011), 分别占4个字节
printf("%d\n",i);
scanf("%d",&i);
k = i + j;
这种直接按变量名进行的访问,称为“直接访问”方式。
间接访问
i_pointer = &i; // 将i的地址存放到i_pointer中
此时 i_pointer = 2000
*i_pointer //*i_pointer表示i_pointer指向的对象
所以 *i_pointer = 3
一个变量的地址称为该变量的“指针”。
如果有一个变量专门用来存放另一变量的地址(即指针),则它称为“指针变量” ,
比如,i_pointer就是指针变量,它存放了变量i的地址2000。
区分“指针”和“指针变量”:
变量i的指针是 2000,i的指针变量i_pointer。
例8.1 通过指针变量访问整型变量
#include
int main()
{
int a = 100, b = 10;
float i = 9.0, j = 1.0;
int * pointer_1, * pointer_2;
pointer_1 = &a;
pointer_2 = &b;
printf("a = %d,b = %d\n",a,b);
printf("* pointer_1 = %d,* pointer_2 = %d\n",* pointer_1,* pointer_2);
return 0;
}
类型名 * 指针变量名;
int * pointer_1, * pointer_2;
int 是在定义指针变量时必须指定的“基类型”。指针变量的基类型用来指定 此指针变量可以指向的变量的类型。
float * pointer_3;
char * pointer_4;
在定义指针变量时要注意:
指针变量名是pointer_1 和 pointer_2 ,而不是 *pointer_1 和 *pointer_2 。
“*” 表示该变量类型为指针型变量。
但是,如下可以这样写吗?
* pointer_1 = &a;
* pointer_2 = &b;
在定义指针变量时必须指定基类型。之后对指针进行操作时,不同的基类型会有不同的结果。
一个变量的指针的含义:a. 以存储单元编号表示的地址
b. 该指针指向的存储单元的数据类型
3.指向整型数据的指针类型表示为"int *",读作“指向int的指针”或简称“int指针”。
4.指针变量中只能存放地址(指针),不能将一个整数赋给一个指针变量。
* pointer_1 = 100; //pointer_1是指针变量,100是整数,不合法
引用指针变量 ,3种情况:
给指针变量赋值,如:
p = &a; //将a的地址赋给指针变量p
引用指针变量指向的变量。
printf("%d", *p);
那如果此时 我令 * p = 1
printf("%o",p);
例8.2 输入a和b两个整数,按先大后小的顺序输出a和b。
#include
int main()
{
int *p1,*p2,a,b;
p1 = &a;
p2 = &b;
scanf("%d,%d",&a,&b);
if(a < b)
{
int *p;
p = p1;
p1 = p2;
p2 = p;
}
printf("%d,%d",*p1,*p2);
}
若函数参数是指针类型,它的作用是讲一个变量的地址传送到另一个函数中。
例8.3 用函数处理例8.2,而且用指针类型的数据作函数参数
#include
int main()
{
void swap(int *p1, int *p2);
int *pointer_1, *pointer_2;
int a, b;
scanf("%d,%d", &a, &b);
pointer_1 = &a;
pointer_2 = &b;
if (a < b)
swap(pointer_1, pointer_2);
printf("max = %d,min = %d",*pointer_1,*pointer_2);
system("pause");
return 0;
}
void swap(int *p1, int *p2)
{
int *p;
p = p1;
p1 = p2; //值传递 址传递
p2 = p;
}
那么问题出在哪了呢?为什么这样不行呢?
swap()函数中,指针变量的值的确是互换了,但是呢,C语言中实参和形参之间的数据传递时单向的“值传递”方式,用指针变量作函数参数同样要遵循这一规则。简单来说,p1 和 p2 的值互换了,但是pointer_1和pointer_2并没有。
不可能通过执行调用函数来改变实参指针变量的值(不要企图通过改变指针形参的值而使指针实参的值改变),
但是可以改变实参指针变量所指变量的值。
怎么改?
#include
int main()
{
void swap(int *p1,int *p2);
int *pointer_1, *pointer_2;
int a,b;
scanf("%d,%d",&a,&b);
pointer_1 = &a;
pointer_2 = &b;
if(a < b)
swap(pointer_1,pointer_2);
printf("max = %d,min = %d",*pointer_1,*pointer_2);
return 0;
}
void swap(int *p1,int *p2)
{
int t;
t = *p1;
*p1 = *p2;
*p2 = t;
}
指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。
数组元素的指针就是数组元素的地址。
int a[10] = {1,2,3,4,2,5,1,5,2,22};
int *p;
p = &a[0];
等价于 int *p = &a[0];
C语言中数组名代表数组中首元素的地址。
p = &a[0];
p = a;
两个语句是等价的
所以也可以写成 int *p = a;
在什么情况下需要而且可以对指针进行加和减的运算呢?
当指针指向数组元素的时候,比如指针变量p指向数组元素a[0],那么使用p+1就可以指向下一个元素a[1]。
p + 1
p - 1
p++ , ++p
p-- , --p
p1 - p2 (只有p1和p2都指向同一数组中的元素才有意义)
说明:
1、执行p+1时并不是将p的值(地址)简单地加1,而是加上一个数组元素所占用的字节数。
那么系统如何知道将1转换为元素占用的字节数呢? 基类型
2、
int *p = &a[0];
p + i ? a[i]的地址
a + i ?
*(p + i) 和 *(a + i) ? a[i]
例如 *(p + 5) = *(a + 5) = a[5] &a[0] scanf("%d",&a[i])
3、p2 - p1
,结果是两个地址之差除以数组元素的长度
比如:p2 --> a[5] p2 = 2020 , p1 --> a[3] p1 = 2012
那么:p2 - p1 = (2020 - 2012) / 4
= 2
表示p2所指元素与p1所指元素之间差2个元素
注意: 两个地址不能相加,p1 + p2 是无实际意义的。
1、下标法,如 a[i]
2、指针法,如*(a + i), * (p + i)
例8.6 有一个整型数组a,有10个元素,要求输出数组中的全部元素。
(1)下标法
#include
int main()
{
int a[10];
int i;
for(i = 0; i < 10; i++)
{
scanf("%d",&a[i]);
}
for(i = 0; i < 10; i++)
{
printf("%d ",a[i]);
}
return 0;
}
(2)通过数组名计算数组元素地址,找出元素的值
#include
int main()
{
int a[10];
int i;
for(i = 0; i < 10; i++)
{
scanf("%d",&a[i]); //也可以改用 a + i
}
for(i = 0; i < 10; i++)
{
printf("%d ",*(a + i));
}
return 0;
}
(3)用指针变量指向数组元素
#include
int main()
{
int a[10];
int i;
int *p = a;
for(;p<(a+10);p++)
{
scanf("%d",p);
}
p = a;
for(;p<(a+10);p++)
{
printf("%d",*p)
}
return 0;
}
for(p = a; a < (p+10); a++)
printf("%d", *a);
例8.7 通过指针变量输出整型数值a的10个元素。
#include
int main()
{
int *p,i,a[10];
p = a;
for(i = 0; i < 10; i++)
scanf("%d",p++);
for(i = 0; i < 10; i++,p++)
printf("%d ",*p);
printf("\n");
return 0;
}
#include
int main()
{
int *p,i,a[10];
p = a;
for(i = 0; i < 10;)
scanf("%d",p++);
i++;
for(p = a,i = 0; i < 10; i++,p++)
printf("%d ",*p);
printf("\n");
return 0;
}
指向数组的指针变量也可以带下标,如p[i]。
当指针变量指向数组元素时,指针变量可以带下标。因为在程序编译时,对下标的处理方法是转换为地址的,
对p[i]处理成 *(p+i) ,如果p是指向一个整型数组元素a[0],则p[i]代表a[i]。
但是,必须清楚p当前值是什么,如果p–>a[3],则p[2]并不代表a[2],而是a[3+2],即a[5]。
int *p = a;
(1) p++;
*p;
(2) *p++;
(3) *(p++) *(++p) 作用相同吗?
(4) ++(*p) (*p)++
例如想输出a数组的100个元素
p = a;
while(p < a+100)
printf("%d",*p++);
fun(int arr[],int n);
fun(int *arr,int n);
例8.8 将数组a中n个整数按相反顺序存放
说明:实参用数组名a,形参可用数组名,也可用指针变量名
#include
int main()
{
void inv(int x[],int n);
int i,a[10] = {3,7,9,11,0,6,7,5,4,2};
printf("The original array:\n");
for(i = 0;i<10;i++)
printf("%d",a[i]);
printf("\n");
inv(a,10);
printf("The array has been inverted:\n");
for(i = 0;i<10;i++)
printf("%d",a[i]);
printf("\n");
return 0;
}
void inv(int x[],int n)
{
int temp,i,j,m = (n-1)/2; n=9,m=4
for(i = 0; i <= m; i++) i=4;j=4
{
j = n - 1 - i;
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
}
(2) 形参为指针变量
void inv(int *x,int n)
{
int *p,*i,*j,temp;
int m = (n-1)/2;
i = x;
j = x + n - 1;
p = x + m ;
for(;i <= p;i++,j--)
{
temp = *i;
*i = *j;
*j = temp;
}
}
例8.10 选择排序法
(1)
for(int i = 0;i<10;i++)
{
min = i;
for(int j = i+1; j<10;j++)
{
if(array[j] < array[min])
{
min = j; //整个内循环遍历完,k中保存了当前循环中最小元素的下标
}
}
int t = array[min];
array[min] = array[i];
array[i] = t;
}
(2)
for(int i = 0;i< n-1;i++)
{
min = i;
for(int j = i+1; j< n;j++)
{
if(a[j] < a[min])
{
min = j; //每一次对比,就进行交换
if(min != i)
{
int t = a[min];
a[min] = a[i];
a[i] = t;
}
}
}
}
设有一个二维数组a,它有3行4列
int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
前已述及,*a[i] 与 (a+i) 等价 (硬记) p232 (3)
二维数组中,a[0] 与 *(a+0) 等价 ,a[0] 是什么?
我们知道 a[0] + 1 是什么?那么它对应的元素值怎样表示? a[0] [1] *(a[0] + 1) * ( *(a+0) + 1) *( *a + 1)
我们又知道 a + 1 是多少 ? *(a+1) 是多少? a[1]
很奇怪,为什么两者的值是相等的呀?
表示形式 | 含义 | 结果 |
---|---|---|
a | ||
a[0] | ||
*a | ||
&a[1],a+1 | ||
a[1] | ||
*(a+1) | ||
a[1] + 2 | ||
a[1] [2] | ||
*( *(a+1) + 2) | ||
&a[1] [2] | ||
*(a[1] + 2) |
在指向行的指针前面加一个,就转换为指向列的指针。反之,在指向列的指针前面加&,就成为指向行的指针。*
(1) 指向数组元素的指针变量
例8.12 有一个3*4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值。
#include
int main()
{
int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
int *p ;
p =
a 是一维数组 int *p = a;
for(p = a[0];p < a[0] + 12;p++)
{
if(p - a[0] % 4 == 0) printf("\n");
printf("%d ",*p);
}
return 0;
}
a[i] [j] 在数组中对a[0] [0]的相对位移量为 i * n + j ,其中n是列数。
若开始时指针变量p指向a[0] [0],a[i] [j]的地址为多少?
#include
int main()
{
void print1(int a[][4]);
void print2(int a[][4]);
int a[3][4] = { { 1,3,5,7 },{ 9,11,13,15 },{ 17,19,21,23 } };
print1(a);
print2(a);
system("pause");
return 0;
}
void print1(int a[][4])
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", *(&a[0][0] + (i * 4 + j)));
}
}
printf("\n");
}
void print2(int a[][4])
{
int *p = a[0];
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", *(p + (i * 4 + j)));
}
}
printf("\n");
}
(2)指向由m个元素组成的一维数组的指针变量
令p 指向 a[0],即p = &a[0],p+1 不是指向a[0] [1],而是指向a[1],p的增值以一维数组的长度为单位。
令p指向a[0],ji
例8.13 输出二维数组任一行任一列元素的值
#include
int main()
{
int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
int (*p)[4],i,j; //指针变量p指向包含4个整型元素的一维数组
p = a;
printf("please enter row and colum");
scanf("%d,%d",&i,&j);
printf("a[%d][%d] = %d\n",*(*(p + i) + j))
}
考点:(*p)[4]的含义
若有语句:char (*line)[5];,以下叙述中正确的是()
A、定义line是一个数组,每个数组元素是基类型为char的指针变量
B、定义line是一个指针变量,该变量可以指向一个长度为5的字符型数组
C、定义line是一个指针数组,语句中的*号称为间址运算符
D、定义line是一个指向字符型函数的指针
p所指对象是有4个整型元素的数组
分析一下小程序:
#include
int main()
{
int a[4] = {1,2,3,4};
int (*p)[4];
p = &a; //分析点 为什么不能写成 p = a;
printf("%d\n",(*p)[3]);
return 0;
}
分析:
用指针变量作形参,以接受实参数组名传递来的地址。
1️⃣ 用指向变量的指针变量
2️⃣ 用指向一维数组的指针变量
例8.14 有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。
#include
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); // 填空1
search( score ,2); // 填空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;
for(i = 0; i < 4 ; i++)
printf("%5.2f", *(*(p + n)+ i) ); // 填空3 a[2][0] a[2][1] a[2][2] a[2][3]
printf("\n");
}
(1) 用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明"%s"输出该字符串。
例8.16 定义一个字符数组,在其中存放字符串“I love China!”,输出该字符串和第8个字符。
#include
int main()
{
char string[] = "I love China!";
printf("%s\n",string);
printf("%c\n",string[7]);
return 0;
}
复习一下:char string[] = “I love China!” 的长度为多少?
(2) 用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
例8.17 通过字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
#include
int main()
{
char *string = "I love China!"; //string 指向‘I’
printf("%s\n",string);
return 0;
}
注意:在C语言中只有字符变量,没有字符串变量。
指针变量只能指向一个字符类型数据,而不能同时指向多个字符数据,更不是把“I love China”这些字符存放到string中
(指针变量只能存放地址),也不是把字符串赋给* string(将 *string看作变量名)。
只是把“I love China”的第一个字符的地址赋给指针变量string。
考点:用%s可以对一个字符串进行整体的输入输出。 (p257说明)
通过字符数组名或字符指针变量可以输出一个字符串,而对一个数值型数组,是不能企图用数组名输出它的全部元素的。
int a[10];
...
printf("%d\n",a);
输出结果:
对于数值型数组的元素值只能逐个输出。
例8.18 将字符串a复制为字符串b,然后输出字符串b。
#include
int main()
{
char a[] = "I am a student.",b[20];
int i;
for(i = 0; *(a+i)!='\0';i++) //a[i]
{
*(b+i) = *(a+i); // a[i] = a[j];
}
*(b+i) = '\0'; //不能漏写
return 0;
}
用指针变量的方式改写:
#include
int main()
{
char a[] = "I am a student.",b[20];
char *p = a,*p1 = b;
for(;*p != '/0';p++,p1++)
{
*p1 = *p;
}
*p1 = '/0';
p1 = a;
printf("%s",p1);
return 0;
}
例8.20 用函数调用实现字符串的复制。
(1) 用字符数组名作为函数参数
#include
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\nstringb = %s\n",a,b);
printf("copy string a to string b:\n");
copy_string(a,b);
printf("string a = %s\nstringb = %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) 用字符型指针变量作实参 //在(1)的基础上进行修改
char *p1 = "";
char *p2 = "";
copy_string(p1,p2);
(3) 用字符型指针变量作实参和形参
#include
int main()
{
void copy_string( char *p1, char *p2 );
copy_string(p1,p2);
return 0;
}
void copy_string( char *p1, char *p2 )
{
for(;*p1 = '\0';p1++,p2++) //*p++ *(p++) (*p)++ 5
{
}
}
(1)字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址)。
(2)赋值方式。可以对字符指针变量赋值,但不能对数组名赋值。
char str[14];
str[0] = 'I';
str = "I love China!"; //数组名是地址,是常量,不能被赋值,非法
char *a;
a = "I love China!"; //把字符串第一个元素的地址赋给a,合法
对数组的初始化:
char str[14] = "I love China!";
不等价于
char str[14];
str[] = "I love China!"; //企图把字符串赋给数组中各元素,错误
(3)存储单元的内容。编译时为字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元(4个字节)。
char *a = " I LLSKFL ";
scanf("%s",a);
可以直接这样使用吗?
(4)指针变量的值是可以改变的,而数组名代表一个固定的值(数组首元素的地址),不能改变。
#include
int main()
{
char *a = "I love China!";
a = a + 7;
printf("%s\n",a);
return 0;
}
输出结果:
(5)字符数组中各元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对它们再赋值)。
char a[] = "House";
char *b = "House";
a[2] = 'r';
b[2] = 'r'; //非法,字符串常量不能改变
(6)引用数组元素。若字符指针变量p指向字符串常量,就可以用指针变量带下标的形式引用所指的字符串中的字符。
(7)用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。
char *format;
format = "a = %d,b = %f\n";
printf(format,a,b);
总结:使用字符数组时,若不想逐个对元素赋值,则需在定义时便赋初值。
在引用指针变量之前,必须先指定其指向。
可以定义p是一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。
int (*p)(int,int); int (*p)[4];
如果想调用一个函数,除了可以通过函数名调用之外,还可以通过指向函数的指针变量来调用该函数。
例8.22 用函数求整数a和b中的大者。
//通过指针变量访问它所指向的函数
#include
int main()
{
int max(int,int);
int (*p)(int,int); //
int a,b,c;
p = max; //将函数max的入口地址赋给指针变量p,即 使p指向max函数
printf("please enter a and b:");
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;
}
若 int (*p)(int,int);
变为 int *p(int,int);
后一个的p代表什么?
类型名 (*指针变量名)(函数参数表列)
int (*p)(int,int);
此处的int代表函数返回值的类型为整型。
说明:
1.int (*p)(int,int);
定义指向函数的指针变量p, 表示指针变量p只能指向函数返回值为整型且有两个整型参数的函数。在一个程序中,一个指针变量可以先后指向同类型的不同函数。
2.如果要用指针调用函数,必须先使指针变量指向该函数。 p = max ;
3.如果写成 p = max(a,b) , p的含义也就不对了。
4.那怎样用函数指针变量调用函数呢?
int c = (*p)(a,b);
表示“调用由p指向的函数,实参为a,b。得到的函数值赋给c”。
5.对指向函数的指针变量不能进行算术运算。无意义。
例8.23 输入两个整数,然后让用户选择1或2,选1时调用max函数,输出二者中的大数,选2时调用min函数,输出二者中的小数。
#include
int main()
{
int max(int,int);
int min(int,int);
int (*p)(int,int);
int a,b,n,c;
printf("please enter a and b:");
scanf("%d,%d",&a,&b);
printf("please choose 1 or 2:");
scanf("%d",&n);
if(n == 1) p = max ; //填空
else p = min ; //填空
// 提示:调用
c = (*p)(a,b);
printf("%d",c);
return 0;
}
int max(int,int)
{
...
}
int min(int,int)
{
...
}
指向函数的指针变量的一个重要用途是把函数的地址作为参数传递到其他函数。
指针函数的指针可以作为函数参数,把函数的入口地址传递给形参,这样就能够在被调用的函数中使用实参函数。
void fun(int (*x1)(int),int (*x2)(int,int))
{
int a,b,i = 3,j = 5;
a = (*x1)(i);
b = (*x2)(i,j);
}
例8.24 有两个整数a和b,由用户输入1,2或3。如输入1,程序就给出a和b中大者,输入2,就给出a和b中小者,输入3,则求a与b之和。
#include
int main()
{
int fun(int x,int y, int (*p)(int,int));
int max(int,int);
int min(int,int);
int add(int,int);
int a = 34,b = -21,n;
printf("please choose 1,2 or 3:");
scanf("%d",&n);
if(n == 1) fun(a,b,max);
else if(n == 2) fun(a,b,min);
else if(n == 3) fun(a,b,add);
return 0;
}
void fun(int x,int y,int (*p)(int,int))
{
int result;
result = (*p)(x,y);
printf("%d\n",result);
}
int max(int x,int y)
{
...
}
int min(int x,int y)
{
...
}
int add(int x,int y)
{
...
}
形式:
类型名 *函数名(参数表列)
int *a(int x,int y);
int(*a)(int x,int y)
返回值是整型数据的地址。
例8.25 有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数实现。
#include
int main()
{
float score[][4] = {{60,70,80,90},{56,89,67,88},{34,78,90,66}};
float *search(float (*pointer)[4],int k);
float *p;
int i,k;
printf("enter the number of student:");
scanf("%d",&k);
printf("The scores of No. %d are:\n",k);
p = search( score , k ); //填空1
for(i = 0;i < 4;i++) printf("%5.2f\t", *(p + i) ) //填空2 打印成绩
printf("\n");
return 0;
}
float *search(float (*pointer)[4],int k)
{
float *pt;
pt = *(pointer + k) //填空3
return (pt); //第k行0列
}
pt = (*pointer + k);
输出结果是什么? score[0] [1] k = 1
一个数组,若其元素均为指针类型数据,称为指针数组。也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
int *p[4];
p是一个包含了4个指向整型的指针变量的指针数组。
那么什么情况下会用到指针数组呢?
答:指针数组比较适合用来指向若干个字符串
例8.27 将若干字符串按字母顺序(由小到大)输出。
#include
#include
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 != i)
{
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]);
}
if(*name[k] > *name[j]) k = j
将比大小的语句换成这句,可以吗? strcmp()
指向指针数据的指针变量。
char **p;
可以分为两个部分看 char * (*p)
*p 表示p是指针变量,char * 表示p指向的是char *的数据
也就是说:p指向一个字符指针变量(这个字符指针变量指向一个字符型数据)
p = name + 2;
printf("%d\n",*p);
printf("%s\n",*p);
例8.28 使用指向指针数据的指针变量。
#include
int main()
{
char *name[] = {"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};
char **p;
for(int i = 0; i < 5; i++)
{
p = name + i;
printf("%s",*p);
}
return 0;
}
指针数组的元素也可以指向整型数据或实型数据。
int a[5] = {1,3,5,7,9};
int *num[5],i;
int **p;
for(i = 0;i<5;i++)
{
num[i] = &a[i];
}
例8.29 有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组个元素的值。
#include
int main()
{
int a[5] = {1,3,5,7,9};
int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
int **p,i;
p = num;
for(i = 0;i<5;i++)
{
printf("%d",**p);
p++;
}
printf("\n");
return 0;
}
不要将错写为 int *num[5] = {"",3,5,7,9};
指针数组的元素只能存放地址,不能存放整数。
int main(int argc,char *argv[])
(重点背一下格式)
argc 代表什么? 参数的个数
argv 代表什么? 存放字符串
main函数是操作系统调用的,实参只能由操作系统给出。
如果有一个名为file1的文件,它包含以下的main函数:
int main(int argc,char *argv[])
{
while(argc > 1)
{
++argv;
printf("%s\n",argv);
--argc;
}
return 0;
}
输入: file1 China Beijing
输出: China Beijing
不需要定义,在需要时随时开辟,不需要时随时释放。
因为我们并没有定义它为什么类型的变量或数组,因此只能通过指针调用。
malloc,calloc,free,realloc
void * 型变量,不指向任何类型的数据。
#include
#include
int main()
{
void check(int *p); //声明函数
int *p1,i;
p1 = (int *)malloc(5 * sizeof(int));
for(i = 0;i<5;i++)
scanf("%d",p1+i);
check(p);
return 0;
}
void check(int *p)
{
int i;
printf("They are fail:");
for(i = 0;i<5;i++)
if(p[i] < 60) printf("%d",p[i]);
printf("\n");
}
表达式 | 含义 |
---|---|
int *p | |
int *p(int x, int y) | |
int (*p)[n] | |
int (*p)(int x, int y) | |
int *p[4] | 指针数组 |
要求:掌握使用结构体类型的数据,了解共用体和枚举类型
1、结构体是什么?
用户自己建立由不同类型数据组成的组合型的数据结构称为结构体。
2、为什么要使用结构体?
3、怎样建立结构体?
struct Student //struct 是声明结构体类型时的关键字 Student 就是结构体名
{
int num; // 从num开始定义的变量或数组都是结构体成员
char name[20];
char sex;
int age;
float score;
char addr[30];
}; //分号不能省略
struct School
{
struct Student student; //成员student属于 struct Student类型
int perSum;
}
1、先声明结构体类型,再定义该类型的变量
struct Student
{
int num; 4
char name[20]; 20
char sex; 1
int age; 4
float score; 4
char addr[30]; 30
};
struct Student student1,student2;
(重点)计算结构体的内存所占字节
所以 student1所占字节为 多少?
2、在声明类型的同时定义变量
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}student1,student2; //此处定义变量,这种方式一般不多用
3、不指定类型名而直接定义结构体类型变量
struct
{
成员表列
}变量名表列;
指定了一个无名的结构体类型,就不能再以此结构体类型去定义其他变量。
说明:
1.结构体类型和结构体变量是两回事,只能对变量赋值,存取,运算,而不能对一个类型进行操作。
在编译时,不对类型分配空间,只对变量分配空间。
2.结构体类型中的成员名可以和程序中的变量名相同,但二者不代表同一个对象。
(1)在定义结构体变量时,可以对它初始化。
例9.1 把一个学生的信息(包括学号、姓名、性别、住址)放在一个结构体变量中,然后输出这个学生的信息。
#include
int main()
{
struct Student
{
long int num;
char name[20];
char sex;
char addr[20];
}a = {10101,"Li Lin",'M',"123 Beijing Road"}; //定义结构体变量a并初始化
printf("%ld %s %c %s\n",a.num,a.name,a.sex,a.addr) //引用成员变量
return 0;
}
允许对某一成员初始化
struct Student b = {.name = "Zhang Fang"}; //在成员名前有成员运算符
其他成员默认初始化。
(2)可以引用结构体变量中成员的值
student1.num = 10010;
student1.name = "ZHANG";
“.”是成员运算符,在所有的运算符中优先级最高。
printf("%s\n",student1);
注意:不能企图输出结构体变量名来输出结构体变量所以成员的值,和数组不一样。
(3)
student1.num
student1.birthday.month
student1.birthday //错误,只能对最低级的成员进行赋值或其他操作
(4)结构体变量的成员可以像普通变量一样进行各种运算。
(5)同类的结构体变量可以互相赋值。
(6)可以引用结构体变量成员的地址,也可以引用结构体变量的地址。
scanf("%d",&student1.num);
printf("%o",&student1); //结构体变量的地址主要用作函数参数
例9.2 输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩。
#include
int main()
{
struct Student
{
int num;
char name[20];
float score;
}student1,student2;
scanf("%d%s%f",&student1.num,student1.name,&student1.score);
...
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;
}
之前定义的是结构体变量,简单来说,由结构体类型的数据组合成的数组就是结构体数组。
struct 结构体名
{
成员表列
}数组名[数组长度];
或者
结构体类型 数组名[数组长度];
数组名[数组长度] = {初值表列};
如:struct Person leader[3] = {"Li",0,"Zhang",0,"Sun",0};
例9.3 有3个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。
写题目之前思考一下:
1. 这个结构体要怎样设计?
2. 怎样去统计每个候选人的选票数?
#include
//定义结构体并初始化
struct Student
{
char name[20];
int count;
}student[3] = {"Li",0,"Zhang",0,"Sun",0};
int main()
{
char name[20];
//假设我们有10个选民
for(int i = 0; i < 10 ;i++)
{
scanf("%s", name); //Li i = 0
for(int k = 0;k < 3;k++)
{
if(strcmp(name[i],student[k].name) == 0) student[k].count ++;
}
}
//将各人得票结果输出
for(int i =0; i < 3;i++)
{
printf("%s %d",student[i].name,student[i],count);
}
return 0;
}
例9.4 有n个学生的信息(包括学号、姓名、成绩),要求按照成绩的高低顺序输出各学生的信息。
#include
struct Student
{
int num;
char name[20];
float score;
};
int main()
{
//第二种初始化结构体数组的方式
struct Student stu[5] = {{10101,"Zhang",78},{10103,"Wang",98.5},{10106,"Li",86},{10108,"Ling",73.5}, {10110,"Sun",100}};
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%8s%6.2f\n",stu[i].num,stu[i].name,stu[i].score);
printf("\n");
return 0;
}
所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。
如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。
指向结构体对象的指针变量既可以指向结构体变量,也可指向结构体数组中的元素。指针变量的基类型必须和结构体变量的类型相同。
struct Student *pt;
例9.5 通过指向结构体变量的指针变量输出结构体变量中的成员的信息。
#include
#include
int main()
{
struct Student
{
long num;
char name[20];
char sex;
float score;
};
struct Student stu_1; //
struct Student *p //
p = &stu_1; //
stu_1.num = 10101; //
strcpy(stu_1.name,"Li Lin");
stu_1.sex = 'M';
stu_1.score = 89.5;
printf("No.:%ld\nname:%s\nsex:%c\nscore:%5.1f\n",stu_1.num,stu_1.name,stu_1.sex,stu_1.score);
// 如何通过指针变量输出结构体变量中的成员
printf("No.:%ld\nname:%s\nsex:%c\nscore:%5.1f\n",(*p).num,(*p).name,(*p).sex,(*p).score); //
}
说明:
如果p指向一个结构体变量stu,以下3种用法等价:
stu.成员名(如stu.num);
(*p).成员名(如(*p).num);
p->成员名(如p->num);
可以用指针变量指向结构体数组的元素。
例9.6 有3个学生的信息,放在结构体数组中,要求输出全部学生的信息(学号,姓名,性别,年龄)。
{{10101,"Zhang",'M',18},{10102,"Wang",'F',20},{10106,"Li",'M',22}};
#include
//定义结构体 并初始化结构体数组
struct Student
{
int num;
char name[20];
char sex;
int age;
}student[3]={{10101,"Zhang",'M',18},{10102,"Wang",'F',20},{10106,"Li",'M',22}};;
int main()
{
//定义指向结构体变量的指针变量
struct Student *p;
//使用指针变量输出学生信息
for(p = student ; p < student+3 ; p++ )
{
printf("%d%s%c%d\n",(*p).num ,(*p).name ,(*p).sex ,(*p).age );
}
return 0;
}
注意:
1.(++p)->num
(p++)->num
2.struct Student *p;
p = stu[0].name;
p可以指向这个name元素吗?
p = (struct Student*)stu[0].name; //类型强转
printf("%s",p);
printf("%s",p+1);
数组与链表
头指针,存放指向下一个元素的地址
链表中的每一个元素称为 结点
链表中各元素内存中的地址是可以不连续的,数组中各元素内存的地址是连续的。
这样的数据结构是需要利用指针变量才能实现,即一个结点中应包含一个指针变量,存放下一个结点的地址。
struct Student
{
int num;
float score;
struct Student *next; //指向结构体变量
}
例9.8 建立如上图所示的简单链表,它由3个学生数据的结点组成,要求输出各结点中的数据。
#include
struct Student
{
int num;
float score;
struct Student *next;
};
int main()
{
struct Student a={},b={},c={},*head,*p;
a.num = 10101;a.score = 89.5;
b.num = 10103;b.score = 90;
c.num = 10107;c.score = 85;
head = &a;
a.next = &b;
b.next = &c;
c.next = NULL; //
p = head;
do
{
printf("%ld%5.1f\n",p->num,p->score);
p = p->next; // p++
}while(p!=NULL);
return 0;
}
所谓动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,并建立起前后相链的关系。
目的:建立一个有3名学生数据的单向动态链表(单链表)
例9.9 写一函数建立一个有3名学生数据的单向动态链表。
#include
#include
#define LEN sizeof(struct Student) //符号常量 没有分号
struct Student
{
long num;
float score;
struct Student *next;
};
stuct Student *creat(void)
{
struct Student *head;
struct Student *p1,*p2;
n = 0;
p1 = p2 = (struct Student*)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
head = NULL;
while(p1->num != 0)
{
n = n+1;
if(n==1) head = p1;
else p2->next = p1;
p2 = p1;
p1 = (struct Student*)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
}
p2 -> next = NULL;
return (head);
}
int main()
{
struct Student *pt;
pt = creat(); //函数返回链表第一个结点的地址
printf("\nnum:%ld\nscore:%5.1f\n",pt->num,pt->score);
return 0;
}
例9.10 编写一个输出链表的函数print
void print(struct Student head)
{
struct Student *p;
p = head;
if(head != NULL)
do{
printf("ld%5.1f\n",p->num,p->score);
p = p -> next;
}while(p->next != NULL);
}
简单来说就是,使几个不同的变量共享同一段内存的结构,称为“共用体”类型的结构。
union 共用体名
{
成员表列
}变量表列;
例如:
union Data
{
int i; //表示不同类型的变量i,ch,f可以存放到同一段存储单元中
char ch;
float f;
}a,b,c; //在声明类型同时定义变量
也可以分开定义
union Data a,b,c;
union
{
int i;
char ch;
float f;
}a,b,c;
定义形式和“结构体”类似
注意:结构体变量所占内存长度是各成员占的内存长度之和
共用体变量所占内存长度等于最长的成员的长度(重要)
a.i a.ch a.f
同一个内存段的确可以用来存放几种不同类型的成员,但在每一瞬间只能存放其中一个成员,而不是同时存放几个。
union Date
{
int i;
char ch;
float f;
}a;
a.i = 97;
a.ch = b;
printf("%d",a.i); 97 98
printf("%c",a.ch); a b
printf("%f",a.f); 0.000000
97在内存中以补码形式存放,是00000000 00000000 00000000 01100001 ;float型是按阶码+尾数原码形式存放的,最高位是符号位,紧跟着8位是阶码,剩下的是尾数。把00000000 00000000 00000000 01100001按此意义解释是0表示正、00000000为阶码(阶码用所谓移码表示,float型要减127,所以全0阶码表示尾数的小数点要向左移动127位)、剩下的尾数表示0.0000000 00000000 01100001,小数点向左移127位后尾数已经非常非常接近0了……甚至,97这个数再大一些,结果仍然是0!
可以对共用体变量初始化,但初始化表中只能有一个常量。
union Date
{
int i;
char ch;
float f;
}a={1,'a',1.5}; //错误
union Data a = {16}; //正确
union Data a = {.ch = 'k'}; //正确,对指定的一个成员初始化
共用体变量中起作用的成员是最后一次被赋值的成员,之前的赋值的成员会被覆盖(重要)。
共用体变量的地址和它的各成员的地址都是同一地址。
不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
P321 看书上代码
如果一个变量只有几种可能的值,则可以定义为枚举类型
enum Weekday{sun,mon,tue,wed,thu,fri,sat}; //声明枚举类型
enum Weekday a,b; //定义枚举变量
枚举变量 workday和weekend 只能是sun到sat之一
weekday = monday; //错误
enum{sun,mon,tue,wed,thu,fri,sat}workday,weekend;
说明:
1.sun = 0;mon = 1 //错误,不能对枚举元素赋值
2.每一个枚举元素都代表一个整数,默认从0开始递增(重要)
上面的定义中,sun的值为0,mon的值为1…sat的值为6
workday = mon; 相当于 workday = 1;
printf("%d",workday); 1
enum Weekday{sun=7,mon=1,tue,wed,thu,fri,sat}workday,week_end; (重要)
可以用typedef指定新的类型名来代替已有的类型名
1.简单地用一个新的类型名代替原有的类型名
typedef int Integer; //指定用Integer为类型名,作用与int相同
typedef float Real;
定义之后
int i,j; float a,b;
等价于
Integer i,j; Real a,b;
2.命名一个简单的类型名代替复杂的类型表示方法
typedef struct
{
int month;
int day;
int year;
}Date; //struct DATE birthday
Date birthday; //定义结构体变量 struct 类型名 变量名
Date *p; //定义结构体指针
typedef int Num[100];
Num a; //a此时为一个包含100个元素的整型数组
... 作个了解就好
了解文件的作用,掌握文件操作
源文件(后缀为.c) 目标文件(后缀为.obj) 可执行文件(后缀为.exe)(选择题)
C的数据文件由一连串的字符(或字节)组成,对文件的存取是以字符(字节)为单位的。
文件名
D:\CC\temp\file1.dat
文件的分类
ASCⅡ文件(文本文件) 和 二进制文件 (重点 : 填空题)
文件类型指针
FILE *fp; //FILE是系统声明的一个结构体类型
全部大写FILE file
用fopen函数打开数据文件
fopen(文件名,使用文件方式);
FILE *fp;
fp = fopen("al","w");
(重点) 背表10.1 binary “rb”
if((fp=fopen("file1","r")) == NULL)
{
printf("cannot open this file\n");
exit(0);
}
当成数学公式背
用fclose函数关闭数据文件
fclose(文件指针);
fclose(fp);
fclose函数也带回一个值,当成功地执行了关闭操作,则返回值为0,否则返回EOF(-1)。
记忆 表10.2
例10.2
(重点)feof(文件指针)
函数
可以检查到文件读写位置标记是否移到文件的末尾,即磁盘文件是否结束。
如果结束,则函数值为1(真),否则为0。
题目:若文件指针为fp,则判断文件是否结束的表达式是?
if(!feof(fp))
记忆表10.3
fprintf(文件指针,格式字符串,输出表列);
fscanf(文件指针,格式字符串,输出表列);
fprintf(fp,"%d,%6.2f",i,f);
fread(buffer,size,cout,fp);
fwrite(buffer,size,count,fp);
fread(f,4,10,fp);
rewind()
:使文件位置标记重新返回文件的开头。
fseek()
: 记忆表10.4
ftell()
:
ferror()
: 返回值为0(假),表示未出错,返回非零值,表示出错。
clearerr()
: 使文件错误标志和文件结束标志置为0.