西安石油大学C语言期末重点知识点总结
大一学生一周十万字爆肝版C语言总结笔记
是我自己在学习完C语言的一次总结,尽管会有许多的瑕疵和不足,但也是自己对C语言的一次思考和探索,也让我开始有了写作博客的习惯和学习思考总结,争取等我将来变得更强的时候再去给它优化出来一版更好的,加油吧少年!💯
C语言必须通过编译连接才能执行
程序:就是一组能识别和执行的指令,每一条指令使计算机执行特定的操作
算法+数据结构=程序
软件:程序+相关文档
三种基本结构:顺序 选择 循环
sqrt》》求根函数
综述
C语言是一门面向过程的语言 C++是在C的基础上更进一步的提升 面向对象
相比较于面向过程的程序设计来说有更多的封装的函数可以使用,相比较来说会比较方便。但是如何去设计整个程序的思路也是需要一定的训练的。
C++是C语⾔的继承,它既可以进⾏C语⾔的过程化程序设计,⼜可以进⾏以抽象数据类型为特点的
基于对象的程序设计,还可以进⾏以继承和多态为特点的⾯向对象的程序设计。C++擅⻓⾯向对象程序设计的同时,还可以进⾏基于过程的程序设计,因⽽C++就适应的问题规模⽽论,⼤⼩由之。
C++不仅拥有计算机⾼效运⾏的实⽤性特征,同时还致⼒于提⾼⼤规模程序的编程质量与程序设计
语⾔的问题描述能⼒。
scanf小结:
scanf()函数是格式化输入函数,它从标准输入设备(键盘) 读取输入的信息。
其调用格式为: scanf("<格式化字符串>",<地址表>);
格式化字符串包括以下三类不同的字符;
1、 格式化说明符:
格式化说明符与printf()函数中的格式说明符基本相同。但和printf()函数中格式字符串的用法有一些小区别。我们来看下面这个表。
格式字符 说明
%d 从键盘输入十进制整数
%o 从键盘输入八进制整数
%x 从键盘输入十六进制整数
%c 从键盘输入一个字符
%s 从键盘输入一个字符串
%f 从键盘输入一个实数
%e 与%f的作用相同
附加格式说明字符表
字符 说明
L 输入"长"数据
H 输入"短"数据
M 指定输入数据所占宽度
* 空读一个数据
2、 空白字符: 空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符。
3、 非空白字符: 一个非空白字符会使scanf()函数在读入时剔除掉与这个非空白字符相同的字符。
————————————————
版权声明:本文为CSDN博主「时雨h」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shaozheng0503/article/details/128505294
数位及其表示、数制之间的转换
十进制 0 1 2 3 4 5 6 7 8 9
表示:0b 二进制 0 1
表示:0 八进制 0 1 2 3 4 5 6 7
表示:0x 十六进制 0 1 2 3 4 5 6 7 8 9 A B C D E F
C语言的编译过程是怎样的
1.预处理(Preprocessing), 2.编译(Compilation), 3.汇编(Assemble), 4.链接(Linking)。
算法
算法(algorithm)是解决一系列问题的清晰指令,也就是,能对一定规范的输入,在有限的时间内获得所要求的输出。 6是解决做什么和怎么做的问题
简单来说,算法就是解决一个问题的具体方法和步骤。算法是程序的灵魂。
程序:(1)对数据的描述。 在程序中要制定用到哪些数据,以及这些数据的组织形式。这也是大名鼎鼎的数据结构(我还在期待着马上能啃这块)
对操作的描述。 要求计算机进行操作的步骤。(算法)
第二章课后重点题目总结
- 什么是算法?试从日常生活中找3个例子,描述它们的算法
算法:简而言之就是求解问题的步骤,对特定问题求解步骤的一种描述。
比如生活中的例子:
考大学
首先填报志愿表、交报名费、拿到准考证、按时参加考试、收到录取通知书、按照日期到指定学校报到。
去北京听演唱会
首先在网上购票、然后按时坐车到北京,坐车到演唱会会场。
把大象放进冰箱
先打开冰箱门,然后将大象放进冰箱,关冰箱。
- 什么叫结构化的算法?为什么要提倡结构化的算法?
结构化算法:由一些顺序、选择、循环等基本结构按照顺序组成,流程的转移只存在于一个基本的范围之内。
结构化算法便于编写,可读性高,修改和维护起来简单,可以减少程序出错的机会,提高了程序的可靠性,保证了程序的质量,因此提倡结构化的算法。
- 试述3种基本结构的特点,请另外设计两种基本结构(要符合基类结构的特点)。
结构化程序设计方法主要由以下三种基本结构组成:
顺序结构:顺序结构是一种线性、有序的结构,它依次执行各语句模块
选择结构:选择结构是根据条件成立与否选择程序执行的通路。
循环结构:循环结构是重复执行一个或几个模块,直到满足某一条件位置
重新设计基本结构要满足以下几点:
只有一个入口
只有一个出口
结构内的每一部分都有机会执行到
结构内不存在死循环
求一元二次方程的根
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
输入参数a,b,c,求一元二次方程axx+b*x+c=0的根,结果保留2位小数。
输出使用以下语句:
printf(“参数都为零,方程无意义!\n”);
printf(“a和b为0,c不为0,方程不成立\n”);
printf(“x = %0.2f\n”, -c/b);
printf(“x1 = %0.2f\n”, (-b+sqrt(d))/(2*a));
printf(“x2 = %0.2f\n”, (-b-sqrt(d))/(2*a));
printf(“x1 = %0.2f+%0.2fi\n”, -b/(2a), sqrt(-d)/(2a));
printf(“x2 = %0.2f-%0.2fi\n”, -b/(2a), sqrt(-d)/(2a));
输入输出示例:括号内为说明
输入:
5 (repeat=5)
0 0 0 (a=0,b=0,c=0)
0 0 1 (a=0,b=0,c=1)
0 2 4 (a=0,b=2,c=4)
2.1 8.9 3.5 (a=2.1,b=8.9,c=3.5)
1 2 3 (a=1,b=2,c=3)
输出:
参数都为零,方程无意义!
a和b为0,c不为0,方程不成立
x = -2.00
x1 = -0.44
x2 = -3.80
x1 = -1.00+1.41i
x2 = -1.00-1.41i
#include <stdio.h>
#include <math.h>
int main(void)
{
int repeat, ri;
double a, b, c, d;
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
scanf("%lf%lf%lf", &a, &b, &c);
d=b*b-4*a*c;
if(a==0){
if(b==0){
if(c==0) printf("参数都为零,方程无意义!\n");
else printf("a和b为0,c不为0,方程不成立\n");
}
else printf("x = %0.2f\n", -c/b);
}
else{
if(d>=0){
printf("x1 = %0.2f\n", (-b+sqrt(d))/(2*a));
printf("x2 = %0.2f\n", (-b-sqrt(d))/(2*a));
}
else{
printf("x1 = %0.2f+%0.2fi\n", -b/(2*a), sqrt(-d)/(2*a));
printf("x2 = %0.2f-%0.2fi\n", -b/(2*a), sqrt(-d)/(2*a));
}
}
}
}
参考2
#include <stdio.h>
#include <math.h>
int main(void)
{
int repeat, ri;
double a, b, c, d;
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
scanf("%lf%lf%lf", &a, &b, &c);
d=b*b-4*a*c;
if(a==0&&b==0&&c==0) printf("参数都为零,方程无意义!\n");
if(a==0&&b==0&&c!=0) printf("a和b为0,c不为0,方程不成立\n");
if(a==0&&b!=0)
printf("x = %0.2f\n", -c/b);
if(a!=0&&d>=0){
printf("x1 = %0.2f\n", (-b+sqrt(d))/(2*a));
printf("x2 = %0.2f\n", (-b-sqrt(d))/(2*a));
}
if(a!=0&&d<0){
printf("x1 = %0.2f+%0.2fi\n", -b/(2*a), sqrt(-d)/(2*a));
printf("x2 = %0.2f-%0.2fi\n", -b/(2*a), sqrt(-d)/(2*a));
}
}
}
算术优先级:
语言的i运算符包括单目运算符、双目运算符、三目运算符,优先级如下:
第1优先级:各种括号,如()、[]等、成员运算符 . ;
第2优先级:所有单目运算符,如++、–、!、~等;
第3优先级:乘法运算符*、除法运算符/、求余运算符%;
第4优先级:加法运算符+、减法运算符-;
第5优先级:移位运算符<<、>>;
第6优先级:大于运算符>、大于等于运算符>=、小于运算符<、小于等于运算符<=;
第7优先级:等于运算符==、不等于运算符!=;
第8优先级:按位与运算符&;
第9优先级:按位异或运算符^;
第10优先级:按位或运算符|;
第11优先级:逻辑与运算符&&;
第12优先级:逻辑或运算符||;
第13优先级:三目条件运算符 ?: ;
第14优先级:各种赋值运算符,如=、+=、-=、*=、/= 等;
第15优先级:逗号运算, 。
书上第53页++ --多留意
注意赋值时是从右向左的
赋值运算符的左侧就是一个可修改值的“左值”(left value 简写为lvalue)
b=a B=5
整型数据间的赋值按存储单元中的存储形式直接传送,实型数据之间以及整型与实型之间的赋值,是先转换(类型)后赋值。
C语言%7.2d、%-7d、%7.2f、%0.2f的含义和区别
1.%d是输出整形格式,即int型数据
%-7d也是整形,但是输出的时候是左对齐,最少输出7位,不足7位的右端补空格。
%07d中的d代表:十进制有符号整数
7代表:输出的数字的最大宽度,小于这个宽度的数字前面就补空格,大于将按其实际长度输出
0代表:这里同上面的7一起作用,小于这个宽度的数字前面用0来补
1. %7.2f有,表示输出最少7位浮点数,其中小数占两位
%7.2f中的2表示小数位数为2位,7表示最少输出7位,不足的左端补空格,比如123.4567输出就是空格123.46
%0.2 f也可以写成 %.2f 指保留小数点后两位
if语句基本格式
//多分支
if (表达式1)
语句1;
else if (表达式2)3.
语句2;
else
语句3;
在if语句中又包含一个或多个if语句称为if语句的嵌套(nest)
else总是与它上面最近的未配对的if配对
如何避免不是与自己想要的if配对上了呢?
①此时{}限定了内嵌if语句的范围因此else与第一个if配对
If()
{
If()语句1
}
else 语句2
② 由于有外层的else相隔,内嵌的else不会被误认为和外层的if配对,而只能与内嵌的if配对
if(x<0)
y=-1;
else
if(x==0)y=0;
else y=1;
printf();
return 0;
算术符号优先次序:
由高到低为:!(非)>算术运算符>关系运算符>&&和||>赋值运算符
Switch语句的用法:
显示五级记分制成绩所对应的百分制成绩区间(使用switch)
输入一个正整数 repeat (0<repeat<10),做 repeat 次下列运算:
输入五级制成绩(A-E),输出相应的百分制成绩(0-100)区间,要求使用switch语句。
五级制成绩对应的百分制成绩区间为:A(90-100)、B(80-89)、C(70-79)、D(60-69)和E(0-59),如果输入不正确的成绩,显示"Invalid input"。
输出使用以下语句:
printf("90-100\n");
printf("80-89\n");
printf("70-79\n");
printf("60-69\n");
printf("0-59\n");
printf("Invalid input\n");
输入输出示例:括号内是说明
输入
6ABCDEj (repeat=6,输入的五级成绩分别为A、B、C、D、E和无效的字符j)
输出
90-100
80-89
70-79
60-69
0-59
Invalid input (输入数据不合法)
#include <stdio.h>
int main(void)
{
char ch;
int repeat, ri;
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
ch = getchar();
switch(ch){
case'A':
printf("90-100\n");
break;
case'B':
printf("80-89\n");
break;
case'C':
printf("70-79\n");
break;
case'D':
printf("60-69\n");
break;
case'E':
printf("0-59\n");
break;
default:
printf("Invalid input\n");
break;
}
}
return 0;
}
显示水果的价格(使用switch)
查询水果的单价。有4种水果,苹果(apples)、梨(pears)、桔子(oranges)和葡萄(grapes),单价分别是3.00元/公斤,2.50元/公斤,4.10元/公斤和10.20元/公斤。
在屏幕上显示以下菜单(编号和选项),用户可以连续查询水果的单价,当查询次数超过5次时,自动退出查询;不到5次时,用户可以选择退出。
当用户输入编号1~4,显示相应水果的单价(保留1位小数);输入0,退出查询;输入其他编号,显示价格为0。
输入输出示例:括号内是说明
输入
3 (oranges的编号)
0 (退出查询)
输出
[1] apples
[2] pears
[3] oranges
[4] grapes
[0] Exit
price = 4.1
[1] apples
[2] pears
[3] oranges
[4] grapes
[0] Exit
#include <stdio.h>
int main(void)
{
int choice, i;
double price;
for(i = 1; i <= 5; i++){
printf("[1] apples\n");
printf("[2] pears\n");
printf("[3] oranges\n");
printf("[4] grapes\n");
printf("[0] Exit\n");
scanf("%d", &choice);
if(choice == 0)
break;
else{
switch (choice){
case 1: price= 3.00; break;
case 2: price=2.50; break;
case 3: price=4.10; break;
case 4: price=10.20; break;
default: price=0; break;
}
printf("price = %0.1f\n", price);
}
}
}
————————————————
版权声明:本文为CSDN博主「时雨h」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shaozheng0503/article/details/128505294
选择结构典型例题:
有3个整数a, b, c,由键盘输入,输出其中最大的数。
解题思路: 每个数字两两与剩余两个数字进行比较,若比剩下的两个数大则最大,例如:a>b && a>c则a是最大的
答案:
#include <stdio.h>
int main()
{
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
if (a == b && a == c) {
printf("Three numbers are equal\n");
}else if (a == b && a > c) {
printf("a and b are the largest number\n", a);
}else if (a == c && a > b) {
printf("a and c are the largest number\n", a); //相等的情况不能忘!!!
}else if (b == c && b > a) {
printf("c and b are the largest number\n", a);
}else if (a > b && a > c) {
printf("a=%d is the largest number\n", a);
}else if (b > a && b > c) {
printf("b=%d is the largest number\n", b);
}else {
printf("c=%d is the largest number\n", c);
}
return 0;
}
————————————————
版权声明:本文为CSDN博主「时雨h」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shaozheng0503/article/details/128505294
给一个不多于5位的正整数,要求:①求出它是几位数;②分别输出每一位数字;③按逆序输出各位数字,例如原数为321,应输出123。
①求出它是几位数;
解题思路: 大于10000就是5位,否则大于1000就是四位,否则大于100是三位…
答案:
#include <stdio.h>
int main()
{
int num;
printf("enter num:");
scanf_s("%d", &num);
if (num > 99999 || num < 0) {
printf("请输入0~99999之间的正数\n");
return -1;
}
if (num >= 10000) {
printf("5\n");
}else if (num >= 1000) {
printf("4\n");
}else if (num >= 100) {
printf("3\n");
}else if (num >= 10) {
printf("2\n");
}else {
printf("1\n");
}
system("pause");
return 0;
}
②分别输出每一位数字;
解题思路: 99999除以10000则输出9;9999除以1000则输出9,…
答案:
#include <stdio.h>
int main()
{
int num;
printf("enter num:");
scanf_s("%d", &num);
if (num > 99999 || num < 0) {
printf("请输入0~99999之间的数字\n");
return -1;
}
if (num / 10000 > 0) {//取出万位数字
printf("%d ", num / 10000);
}
if (num%10000 >= 1000) {//取余10000则可以取出低四位的数据,除以1000则得到千位的数字
printf("%d ", (num % 10000) / 1000);
}
if (num%1000 >= 100) {//取余1000则可以取出低三位的数据,除以100则得到百位的数字
printf("%d ", (num % 1000) / 100);
}
if (num%100 >= 10) {//取余100则可以取出低两位的数据,除以10则得到十位的数字
printf("%d ", (num % 100) / 10);
}
if (num%10 >= 0) {//取余10则取出个位数字
printf("%d ", num % 10);
}
printf("\n");
system("pause");
return 0;
}
③按逆序输出各位数字,例如原数为321,应输出123。
解题思路: 思路与第二题相同,只不过将整个过程逆序即可
答案:
#include <stdio.h>
int main()
{
int num;
printf("enter num:");
scanf_s("%d", &num);
if (num > 99999 || num < 0) {
printf("请输入0~99999之间的数字\n");
return -1;
}
if (num % 10 >= 0) {
printf("%d ", num % 10);
}
if (num % 100 >= 10) {
printf("%d ", (num % 100) / 10);
}
if (num % 1000 >= 100) {
printf("%d ", (num % 1000) / 100);
}
if (num % 10000 >= 1000) {
printf("%d ", (num % 10000) / 1000);
}
if (num / 10000 > 0) {
printf("%d ", num / 10000);
}
printf("\n");
system("pause");
return 0;
}
给出一百分制成绩,要求输出成绩等级’A’、‘B’、‘C’、‘D’、‘E’。 90分以上为’A’,8089分为’B’,7079分为’C’ ,60~69分为’D’ ,60分以下为’E’。
解题思路: 根据不同的阶段成绩区间作为成绩的判断条件,属于哪个区间则输出对应等级即可
答案:
#include <stdio.h>
int main()
{
int score;
printf("enter score:");
scanf_s("%d", &score);
if (score >= 90) {
printf("A\n");
}else if (score >= 80 && score < 90) {
printf("B\n");
}else if (score >= 70 && score < 80) {
printf("C\n");
}else if (score >= 60 && score < 70) {
printf("D\n");
}else {
printf("E\n");
}
system("pause");
return 0;
}
课后第十题:
企业发放的奖金根据利润提成。利润I低于或等于100000元的,奖金可提成10%;利润高于100000元,低于200000元(100000<I≤200000)时,低于100000元的部分按10%提成,高于100000元的部分,可提成7. 5%;200000<I≤400000时,低于200000元的部分仍按上述办法提成(下同)。高于200000元的部分按5%提成;400000<<I≤600000元时,高于400000元的部分按3%提成;600000<1≤1000000时,高于600000元的部分按1.5%提成;I>1000000时,超过1000000元的部分按1%提成。从键盘输入当月利润I,求应发奖金总数。要求:(1) 使用if语句编写程序。(2) 使用switch语句编写程序。
(1) 使用if语句编写程序。
解题思路: 先将每一档的最大奖金算出来,在某一个区间时,则那小于这一档的奖金加上多出部分的奖金即可,例如:
先列出100000档的奖金是10000,则180000就是10000 + (180000-100000) * 0.075;列出200000档的奖金是第一档加上多出100000部分的7.5%得到17500,则300000就是17500 + (300000-200000)*0.05;
答案:
#include <stdio.h>
int main()
{
double I, salary = 0;
printf("enter performance:");
scanf_s("%lf", &I);
if (I < 0) {
printf("请输入一个正数\n");
system("pause");
return -1;
}
double salary1 = 100000 * 0.1;//10万的奖金
double salary2 = (200000 - 100000) * 0.075 + salary1;//20万的奖金
double salary3 = (400000 - 200000) * 0.05 + salary2;//40万的奖金
double salary4 = (600000 - 400000) * 0.03 + salary3;//60万的奖金
double salary5 = (1000000 - 600000) * 0.015 + salary4;//100万的奖金
if (I <= 100000) {
salary = I * 0.1;//小于100000按10%提成
}else if (I > 100000 && I <= 200000) {
salary = salary1 + (I - 100000) * 0.075;//多出10万的按比例计算,加上10w的奖金
}else if (I > 200000 && I <= 400000) {
salary = salary2 + (I - 200000) * 0.05;//多出20万的按比例计算,加上20w的奖金
}else if (I > 400000 && I <= 600000) {
salary = salary3 + (I - 400000) * 0.03;//多出40万的按比例计算,加上40w的奖金
}else if (I > 600000 && I <= 1000000) {
salary = salary4 + (I - 600000) * 0.015;//多出60万的按比例计算,加上60w的奖金
}else if (I > 1000000){
salary = salary5 + (I - 1000000) * 0.01;//多出100万的按比例计算,加上100w的奖金
}
printf("salary:%f\n", salary);
system("pause");
return 0;
}
(2) 使用switch语句编写程序。
解题思路: 与第一题思路没有太大差别,区别在于switch语句的case子句中需要是一个常量整数,并且switch中若子句中没有break将循序向下执行,直到遇到break才会跳出switch语句,如果这时候将利润除以10w,则得到09的数字,其中0表示小于10w,1表示介于1020w,2、3表示介于2040w,4、5表示介于4060w,6、7、8、9表示介于60~100w,否则就是大于100w
答案:
#include <stdio.h>
int main()
{
double I, salary = 0;
printf("enter performance:");
scanf_s("%lf", &I);
if (I < 0) {
printf("请输入一个正数\n");
system("pause");
return -1;
}
double salary1 = 100000 * 0.1;//大于100000时0~100000的奖金
double salary2 = (200000 - 100000) * 0.075 + salary1;//大于200000时0~20万的奖金
double salary3 = (400000 - 200000) * 0.05 + salary2;//大于400000时0~40万的奖金
double salary4 = (600000 - 400000) * 0.03 + salary3;//大于600000时0~60万的奖金
double salary5 = (1000000 - 600000) * 0.015 + salary4;//大于1000000时0~100万的奖金
int grade = I / 100000;
switch(grade) {
case 0:
salary = I * 0.1; break;
case 1:
salary = salary1 + (I - 100000) * 0.075; break;
case 2://会顺序执行到下一个break处
case 3:
salary = salary2 + (I - 200000) * 0.05; break;
case 4:
case 5:
salary = salary3 + (I - 400000) * 0.03; break;
case 6:
case 7:
case 8:
case 9:
salary = salary4 + (I - 600000) * 0.015; break;
default:
salary = salary5 + (I - 1000000) * 0.01; break;
}
printf("salary:%f\n", salary);
system("pause");
return 0;
}
while
Do可以简单的使用英语翻译进行理解,就是做
Do{ 做
Printf(“2 “); 输出2 (这个行为)
}while(i<=5) 当i<=5时
而while则是
While(i<=5) 当i<=5时
{
Printf(“2 “); 输出2
}
从这两个例子就可以看到,do- while循环比while多了一个“做”的命令,而这就可以理解为:不管怎么样,你先给我运行一次再说。
求整数的位数以及各位数字之和
程序填空,不要改变与输入输出有关的语句。
输入一个正整数 repeat (0<repeat<10),做 repeat 次下列运算:
输入一个整数 in,求它的位数以及各位数字之和。例如 123 的各位数字之和是 6,位数是 3。
输入输出示例:括号内是说明
输入
4 (repeat=4)
0 (in=0)
23456 (in=23456)
-100 (in=-100)
-1 (in=-1)
输出
count = 1, sum = 0 (0的位数是1, 各位数字之和是0)
count = 5, sum = 20 (23456的位数是5, 各位数字之和是20)
count = 3, sum = 1 (-100的位数是3, 各位数字之和是1)
count = 1, sum = 1 (-1的位数是1, 各位数字之和是1)
#include <stdio.h>
int main(void)
{
int count, in, sum;
int repeat, ri;
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
scanf("%d", &in);
count=0; sum=0;
if(in<0) in=-in;
do{
count++;
sum+=in%10;
in=in/10;
} while (in!=0);
printf("count = %d, sum = %d\n", count, sum);
}
return 0;
}
用for语句实现循环
for(表达式1;表达式2;表达式3;)
for(循环变量赋初值;循环条件;循环变量增值)
小tips:for语句会比while语句功能强,除了可以给出循环条件外,还可以赋初值,使循环变量自动增值。
求2/1+3/2+5/3+8/5+...
输入一个正整数 repeat (0<repeat<10),做 repeat 次下列运算:
输入一个正整数 n,输出 2/1+3/2+5/3+8/5+……前n项之和,保留2位小数。(该序列从第2项起,每一项的分子是前一项分子与分母的和,分母是前一项的分子)
输入输出示例:括号内是说明
输入
3 (repeat=3)
1 (n=1)
5 (n=5)
20 (n=20)
输出
sum = 2.00 (第1项是2.00)
sum = 8.39 (前5项的和是8.39)
sum = 32.66 (前20项的和是32.66)
#include <stdio.h>
int main(void)
{
int i, n;
int repeat, ri;
double denominator, numerator, sum, temp;
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
scanf("%d", &n);
sum=0;
denominator=1;
numerator=2;
for(i=1;i<=n;i++){
sum += numerator/denominator;
temp = denominator; /* 为求下一项分子,保留当前项分母 */
denominator=numerator;
numerator=numerator+temp;
}
printf("sum = %.2f\n",sum);
}
}
改变循环执行的状态
用break语句提前终止循环
验证歌德巴赫猜想(选作)
程序填空,不要改变与输入输出有关的语句。
验证哥德巴赫猜想:任何一个大于6的偶数均可表示为两个素数之和。例如6=3+3,8=3+5,…,18=7+11。
输入两个正整数 m 和 n(6<=m, n<=100),将 m 到 n 之间的偶数表示成两个素数之和,打印时一行打印5组。
输出使用语句:printf("%d=%d+%d ", number, i, number - i);
输入输出示例:括号内为说明
输入:
89 100 (m=90, n=100)
输出:
90=7+83 92=3+89 94=5+89 96=7+89 98=19+79
100=3+97
#include "stdio.h"
#include "math.h"
int main(void)
{
int count, i, j, k, m, n, number;
scanf("%d%d", &m, &n);
if(m % 2 != 0) m = m + 1;
if(m >= 6){
/*---------*/
}
}
#include <stdio.h>
int prime(int m)
{
int i, ifPrime=0;
if(m==1) return 0;
for(i=2;i<=m/2;i++) {
if(m%i==0)
break;
}
if(i>m/2)
ifPrime=1;
return ifPrime;
}
int main(void)
{
int count, i, m, n, number;
scanf("%d%d", &m, &n);
if(m % 2 != 0) m = m + 1;
if(m >= 6) {
count=0;
for(number=m;number<=n;number=number+2) {
for(i=1;i<=number/2;i++) {
if( prime(i) && prime(number-i) ) {
printf("%d=%d+%d ", number, i, number-i);
count++;
if(count%5==0)
printf("\n");
break;
}
}
}
}
}
注:此题标准答案是输出每个数的第一对满足条件的素数之和,但是:
90=7+83 90=11+79 90=17+73 90=19+71 90=23+67 ...90=83+7
也都是满足条件的, 而机器只对输出第一种为正确解
用continue语句提前结束本次循环
一定要记住当执行continue时只是结束本次循环而不是终止整个循环的执行。而break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。
continue语句应该这样理解:当执行continue语句时,流程跳转到表示循环体结束的右花括号的前面(注意不是右花括号的后面) 也可以理解为continue后的不执行,然后重新进入下一次新的循环当中
# include <stdio.h>
int main(void)
{
int val; //variable的缩写, “变量”的意思
printf("请输入您想去的楼层:");
while (1)
{
scanf("%d", &val);
switch (val)
{
case 1:
printf("1层开!\n");
break; //跳出switch
case 2:
printf("2层开!\n");
break; //跳出switch
case 3:
printf("3层开!\n");
break; //跳出switch
default:
printf("该层不存在, 请重新输入:");
continue; //结束本次while循环
}
break; //跳出while
}
return 0;
}
continue和break的区别
continue 语句和 break 语句的区别是,continue 语句只结束本次循环,而不是终止整个循环。break 语句则是结束整个循环过程,不再判断执行循环的条件是否成立。而且,continue 只能在循环语句中使用,即只能在 for、while 和 do…while 中使用,除此之外 continue 不能在任何语句中使用。
所以,再次强调:continue 不能在 switch 中使用,除非 switch 在循环体中。此时 continue 表示的也是结束循环体的本次循环,跟 switch 也没有关系。
循环结构典型例题
3.输人两个正整数m和n,求其最大公约数和最小公倍数
答案解析:
该题题目直接使用“辗转相除法”来求解最大公约数,以除数和余数反复做除法运算,当余数为 0 时,就取得当前算式除数为最大公约数。
最大公约数和最小公倍数之间的性质:两个自然数的乘积等于这两个自然数的最大公约数和最小公倍数的乘积。所以,当我们求出最大公约数,就可以很轻松的求出最小公倍数。
代码示例:
#include <stdio.h>
int main()
{
int p, r, n, m, temp;
printf("请输入两个正整数n,m:");
scanf("%d%d,", &n, &m);
//调整n保存较大的值
if (n < m)
{
temp = n;
n = m;
m = temp;
}
p = n * m;
while (m != 0)
{
r = n % m;
n = m;
m = r;
}
printf("它们的最大公约数为:%d\n", n);
printf("它们的最小公倍数为:%d\n", p / n);
return 0;
}
6.求∑ n = 1 20 n ! \sum\limits_{n=1}^{20}n!
n=1
∑
20
n! (即求1!+2!+3!+4!+…+20!)。
答案解析:
该题需要从1循环到20,依次求出每一个数字阶乘的结果。所以在代码当中需要有两个循环,大循环从1到20,保证1到20个数字都被循环到,小循环里计算N阶乘,累加求和。注意:对于20的阶乘已经超出了int类型能过表示的数字范围,所以在代码当中使用double类型
代码示例:
#include<stdio.h>
int main()
{
double total_sum = 0;
for(int i = 1; i <= 20; i++)
{
double single_sum = 1;
for (int j = i; j > 0; j--)
{
single_sum *= j;
}
total_sum += single_sum;
}
printf("1~20每个数字阶乘总和为:%lf\n",total_sum);
return 0;
}
8.输出所有的“水仙花数”,所谓“水仙花数”是指一个3位数,其各位数字立方和等于该数本身。例如,153是水仙花数,因为153=1*+5*+3。
答案解析:
从题目当中得到”水仙花数“为一个3位数,则范围确定为[100, 999]。另外需要获取该数字的百位数字,十位数字,个位数字相加起来等于该数本身,则我们需要使用到%除的方式,来获取每一个位权的数字。
代码示例:
#include <stdio.h>
int main()
{
//a表示百位数字,b表示十位数字,c表示各位数字
int a, b, c;
for (int i = 100; i <= 999; i++)
{
a = i / 100;
b = (i / 10) % 10;
c = i % 10;
if (a * a * a + b * b * b + c * c * c == i)
{
printf("%d\n", i);
}
}
return 0;
}
利用数组处理批量数据
数组的定义及其理解:用来表示同一性质的数据(比如说一个班的30名同学的成绩)
数组是一组有序数据的集合。数组中各数据的排列是有一定规律的,小标代表数据在数组中的序号。
用一个数组名(如s)和下标(如15)来唯一地确定数组中的元素,如s15来唯一地确定数组中的元素,如s15 就代表第十五个学生的成绩。
数组中的每一个元素都属于同一个数据类型。不能把不同类型的数据(如学生的成绩和学生的性别)放在同一个数组中。
在计算机中只能输入有限的单个字符而无法表示上下标,C语言中就规定用方括号中的数字来表示下标,如用s[15]表示s15 ,即第15个学生的成绩。
将数组与循环相结合起来可以有效的处理大批量的数据,大大提高工作效率。
怎样定义和引用一维数组
int a[10];
表示定义了一个整型数组,数组名为a,此数组中包含了10个整型元素。
注意a[10]中是从a[0]开始到a[9]结束 没有a[10]!!!
常量表达式中可以包含常量和符号常量,如inta[3+5]是合法的,不能包含变量,如int a[n];是不合法的,也就是说,C语言不允许对数组的大小作动态定义,即数据的大小不依赖于程序运行过程中变量的值。
例题:简化的插入排序
输入一个正整数 repeat (0<repeat<10),做 repeat 次下列运算:
输入一个正整数 n(0<n<=9)和一组(n个)有序的整数,再输入一个整数 x,把 x 插入到这组数据中,使该组数据仍然有序。
输入输出示例:括号内为说明
输入:
4 (repeat=4)
5 (数据的个数n=5)
1 2 4 5 7 (5个有序整数)
3 (待插入整数x=3)
4 (数据的个数n=4)
1 2 5 7 (4个有序整数)
-10 (待插入整数x=-10)
3 (数据的个数n=3)
1 2 4 (3个有序整数)
100 (待插入整数x=100)
5 (数据的个数n=5)
1 2 4 5 7 (5个有序整数)
4 (待插入整数x=4)
输出:
1 2 3 4 5 7
-10 1 2 5 7
1 2 4 100
1 2 4 4 5 7
#include <stdio.h>
int main(void)
{
int i, j, n, x;
int repeat, ri;
int a[10];
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
scanf("%d", &n);
for(i = 0; i < n; i++)
scanf("%d", &a[i]);
scanf("%d", &x);
if (x >= a[n-1]) a[n] = x; /* 特殊情况:若x比所有的元素都要大 */
else
for(i = 0; i < n; i++) {
if(x > a[i]) continue; /* 将x 插入到合适的位置*/
j = n - 1; /* 从当前要插入的位置往后移位 */
while(j >= i){
a[j+1] = a[j];
j--;
}
a[i] = x; /* 将x查到当前的位置 */
break;
}
for(i = 0; i < n + 1; i++)
printf("%d ", a[i]);
putchar('\n');
}
}
一维数组的初始化
int a[10]={0,1,2,3,}
int a[10]={0}未赋值的部分自动设定为0
用数组来求fibonacci数列问题:
#include<stdio.h>
int main()
int i;
int f[20]={1,1};
for(i=2;i<20;i++)
{
f[i]=f[i-2]+f[i-1];
for(i=2;i<20;i++)
{
if(i%5==0) printf(“\n”);
printf(“%12d”,f[i]);
}
printf(“\n”);
return 0;
}
#include <stdio.h>
void function(char a[],int);//尤其注意,此处的函数声明必须是char a[],因为这里穿的是地址,不能仅仅使用char
int main()
{
int i;
char a[10]={'i','l','o','v','e','y','o','u','y','x'};//十个数的无序字符数列
printf("此程序使用冒泡排序法排列无序数列!\n");
function(a,10);//调用冒泡排序
printf("排列好的字符组是:\n");
//输出排列好得吃数列
for(i=0;i<10;i++)
{
printf("%c ",a[i]);
}
return 0;
}
void function(char a[],int m)
{
//冒泡排序
int i,j;
char t;
for(i=0;i<m-1;i++)//n个数的数列总共扫描n-1次
{
for(j=0;j<m-i-1;j++)//每一趟扫描到a[n-i-2]与a[n-i-1]比较为止结束
{
if(a[j]>a[j+1])//后一位数比前一位数小的话,就交换两个数的位置(升序)
{
t=a[j+1];
a[j+1]=a[j];
a[j]=t;
}
}
}
return 0;
}
怎样定义和引用二维数组
二维数组常称为矩阵。把二维数组写成行和列的排列形式,可以有助于形象化地理解二维数组的逻辑结构。
float pay[3][6];
3*6(3行6列)
多维数组在内存中的排列顺序为:第一维的下标变化最慢,最右边的下标变化最快。例如:
a[0][0][0]>>a[0][0][1]>>a[0][0][2]>>a[0][1][0]>>a[0][1][1]>>a[0][1][2]>>a[1][0][0]
交换行和列
#include<stdio.h>
int main()
{
int n,m,i,j,k;
int num1,num2;//要交换的两行或两列
char ch;//决定进行行变换还是列变换
int rek;//进行几次操作
int tmp;
scanf("%d %d",&n,&m);
int arr[n][m];
//输入矩阵
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
scanf("%d",&arr[i][j]);
}
}
scanf("%d",&k);
for(rek=0;rek<k;rek++)
{
scanf(" %c %d %d",&ch,&num1,&num2);
if(ch=='r')//进行行变换
{
for(j=0;j<m;j++)
{
tmp=arr[num1-1][j];//进行行变换的时候约定矩阵的行不变循环递归列然后交换即可
arr[num1-1][j]=arr[num2-1][j];
arr[num2-1][j]=tmp;
}
}
else if(ch=='c')//进行列变换
{
for(i=0;i<n;i++)
{
tmp=arr[i][num1-1];//同理进行列变换的时候约定列为需要交换的两列不变后,循环递归循环行即可。
arr[i][num1-1]=arr[i][num2-1];
arr[i][num2-1]=tmp;
}
}
}
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}
6.3字符数组
字符型数据是以字符的ASCII代码存储在存储单元中的,一般占1字节
C语言中没有字符串类型,也没有字符串变量,字符串是存放在字符串数组中的。
6.3.1怎样定义字符数组
char c[10]
由于字符型数据是以整数形式(ASCII代码)存放的,因此也可以用整型数组来存放字符数据,例如:
int c[10]; //合法,但浪费存储空间
c[0]=‘a’
6.3.4字符串和字符串结束标志
C语言规定了一个“字符串结束标志”,以字符串‘\0’作为结束标志。如果字符数组中存放有若干字符,前面9个字符都不是空字符‘\0’,而第十个字符是‘\0’,则认为数组中有一个字符串,其有效字符为9个。也就是说,在遇到字符‘\0’时,表示字符串结束,把它前面的字符组成一个字符串。
注意:C系统在用字符数组存储字符串常量时会自动加一个‘\0’作为结束符。例如“C program”共有9个字符。字符串是存放在一维数组中的,在数组中占10个字节,最后有一个字节‘\0’是系统自动加上的。
说明:‘\0’代表ASCII码为0的字符,从ASCII码表中可以查到,ASCII码为0的字符不是一个可以显示的字符,而是一个“空操作符”,即它什么也不做。用它来作为字符串结束标志不会产生附加的操作或增加有效字符,只起一个供辨别的标志。
对字符串的说明:
①不能用运算符对字符串做运算
②通过数组的方式可以遍历字符串
6.3.5字符数组的输入输出
字符串的结尾有‘\0’
Char string[8];
Scanf (“%s”,string); //前面不允许加&(因为在C语言中数组名代笔该数组第一个元素的地址或者也可以说是数组的起始地址)
Printf (“%s”,string);
安全的输入:
Char string[8];
Scanf (“%7s”,string);
这个7代表:最多允许读入的字符数量,应比数组数组的大小小1
6.3.6使用字符串处理函数
注意:在使用后字符串处理函数时,应当在程序文件的开头用
#include <string.h>
puts函数——输出字符串的函数
Puts(str);
gets函数——输入字符串的函数
Gets(str);
Strcat函数———字符串连接函数
Strcat(字符数组1,字符数组2)
#include <stdio.h>
#include <string.h>
int main(void) {
char str1[6] = "hello";
char str2[6] = "world";
strcat(str1,str2);
printf("str1 = %s\n",str1);
printf("str2 = %s\n",str2);
int len = strlen(str1);
printf("len的长度:%d\n",len);
return 0;
}
说明:
字符数组1必须足够大,以便容纳连接后的新字符串
连接两个字符串的后面都有‘\0’,连接时将字符串1后面的‘\0’取消,只在最后保留‘\0’。
Strcpy和strncpy——字符串复制函数
Strcpy(字符数组1,字符串2)
Char strl[10],str2[]“china”;
Strcpy(str1;str2);
说明:
字符数组1必须足够大,以便容纳连接后的新字符串
“字符数组1”必须写出数组名形式如(str1),“字符串2”可以是字符数组名,也可以是一个字符串常量。
Strcmp——字符串比较函数
Strcmp(字符串1,字符串2)
字符串比较的规则是:将两个字符串自左向右逐个字符相比(按ASCII码值大小比较)直到出现不同的字符或者是遇到‘\0’为止。
如全部字符相同,则认为两个字符串相等;
若出现并不相同的字符,则以第1对不相同的字符的比较结果为准。
比较的结果由函数值带回:
如果字符串1与字符串2相同,则函数值为0。
如果字符串1>字符串2,则函数值为一个正整数
如果字符串1<字符串2,则函数值为一个负整数
这样使用if(strcmp(str1,str2)>0)
Printf(“yes”)
Strlen函数——测字符串长度的函数
是string length(字符串的长度)的缩写。
Char str[10]=“CHINA”
Printf(“%d”,strlen(str));
输出的结果不是10,也不是6,而是5。
Strlwr函数——转换为小写的函数
Strlwr(字符串)
是string lowercase的缩写
Strupr函数——转换为大写的函数
Strupr(字符串)
6.3.7字符数组应用举例
70013 将数组中的数逆序存放
输入一个正整数 repeat (0<repeat<10),做 repeat 次下列运算:
输入一个正整数 n (1<n<=10),再输入 n 个整数,存入数组a中,先将数组a中的这n个数逆序存放,再按顺序输出数组中的n个元素。
输入输出示例:括号内为说明
输入
2 (repeat=2)
4 (n=4)
10 8 1 2
5 (n=5)
1 2 5 4 0
输出
2 1 8 10
0 4 5 2 1
#include <stdio.h>
int main(void)
{
int i, n, temp;
int repeat, ri;
int a[10];
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
scanf("%d", &n);
for(i = 0; i < n; i++)
scanf("%d", &a[i]);
i=0;
for(i=0;i<n/2;i++){
temp=a[i];
a[i]=a[n-1-i];
a[n-1-i]=temp;
}
for(i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
}
}
70014 求最大值及其下标
输入一个正整数 repeat (0<repeat<10),做 repeat 次下列运算:
输入一个正整数 n (1<n<=10),再输入n个整数,输出最大值及其对应的最小下标,下标从0开始。
输入输出示例:括号内为说明
输入
3 (repeat=3)
3 (n=3)
1 6 4
4 (n=4)
10 8 1 9
5 (n=5)
1 2 0 4 5
输出
max = 6, index = 1 (最大值6的下标是1)
max = 10, index = 0 (最大值10的下标是0)
max = 5, index = 4 (最大值5的下标是4)
#include <stdio.h>
int main(void)
{
int i, index, n;
int ri, repeat;
int a[10];
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++){
scanf("%d", &n);
for(i = 0; i < n; i++)
scanf("%d", &a[i]);
index=0;
a[index]=a[0];
for(i = 1; i < n; i++){
if(a[index]<a[i]){
a[index]=a[i];
index=i;
}
}
printf("max = %d, index = %d\n", a[index], index);
}
}
3. 求一个3 X 3的整形矩阵对角线元素之和
【答案解析】
矩阵:即二维数组,矩阵行和列相等的二维数组称为方阵。
1 2 3
4 5 6
7 8 9
左上角到右下角对角线上数字:行下标和列下标相加
右上角到左下角对角线上数字:列下标减1 行下标加一
通过两个循环来取到对角线上的元素,并对其求和即可。
【代码实现】
#include<stdio.h>
int main()
{
int array[3][3];
int sumLT2RB = 0; // 标记左上角到右下角对角线元素之和
int sumRT2LB = 0; // 标记右上角到左下角对角线元素之和
printf("请输入3行3列的矩阵:\n");
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
scanf("%d", &array[i][j]);
}
// 左上角到右下角对角线
for (int i = 0; i < 3; ++i)
sumLT2RB += array[i][i];
for (int i = 0, j = 2; i < 3; ++i, j--)
sumRT2LB += array[i][j];
printf("左上角到右下角对角线元素之和: %d\n", sumLT2RB);
printf("右上角到左下角对角线元素之和: %d\n", sumRT2LB);
return 0;
}
5. 将一个数组中的值按逆序重新存放。例如:原来顺序为8,6,5,4,1。要求改为1,4,5,6,8。
【答案解析】
该题为数组的逆置,具体处理方式如下:
如果begin < end时,则循环进行一下操作
给定两个下标begin和end,begin放在数组起始的位置,end放在数组最后一个元素的位置
交换begin和end位置的元素
begin往后移动,end往前移动
【代码实现】
#include<stdio.h>
int main()
{
int array[5] = {8,6,5,4,1};
int begin = 0, end = 4;
printf("逆序之前数组为:");
for (int i = 0; i < 5; ++i)
printf("%d ", array[i]);
printf("\n");
// 逆序:begin在数组最左侧,end在数组最右侧
// 只要begin < end,将begin和end位置元素进行交换
// 然后begin往后移动一步,end往前移动一步
while (begin < end)
{
int temp = array[begin];
array[begin] = array[end];
array[end] = temp;
begin++;
end--;
}
printf("逆置之后数组为:");
for (int i = 0; i < 5; ++i)
printf("%d ", array[i]);
printf("\n");
return 0;
}
冒泡排序
冒泡排序例题:
(算法入门,算法的重要性!)
http://t.csdn.cn/ERGm9
数字的排序:
#include <stdio.h>
#define SIZE 10
int main()
{
int a[SIZE]={12 ,43,9,13,67,98,101,89,3,35};//十个数的无序数列
int i,j,t;
printf("此程序使用冒泡排序法排列无序数列!\n");
//冒泡排序
for(i=0;i<10-1;i++)//n个数的数列总共扫描n-1次
{
for(j=0;j<10-i-1;j++)//每一趟扫描到a[n-i-2]与a[n-i-1]比较为止结束
{
if(a[j]>a[j+1])//后一位数比前一位数小的话,就交换两个数的位置(升序)
{
t=a[j+1];
a[j+1]=a[j]; //t就是一个中间量用来交换2个数的值的
a[j]=t;
}
}
}
printf("排列好的数列是:\n");
//输出排列好得数列
for(i=0;i<10;i++)
{
printf("%d ",a[i]);
}
return 0;
}
函数
为了更好的实现模块化程序设计 函数(function)也有功能的意思
7.2怎样定义函数
为什么要定义函数?
事先编辑好的功能,编译系统按照定义的功能去执行
定义函数应包括以下内容:
指定函数的名字,以便以后按名调用
指定函数的类型,即函数返回值的类型
指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项
指定函数应该完成什么操作,也就是函数是做什么工作的,即函数的功能
定义函数的方法:
定义无参函数的方法:
函数名()
{
函数体
}
或者
函数名(void)
{
函数体
}
函数体内包含声明部分和语句部分
计算函数P(n,x) (函数递归)
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
输入一个整数n (n>=0)和一个双精度浮点数x,输出函数p(n,x)的值(保留2位小数)。
[1 (n=0)
p(n, x) = [x (n=1)
[((2*n-1)*p(n-1,x)-(n-1)*p(n-2,x))/n (n>1)
例:括号内是说明
输入
3 (repeat=3)
0 0.9 (n=0,x=0.9)
1 -9.8 (n=1,x=-9.8)
10 1.7 (n=10,x=1.7)
输出
p(0, 0.90)=1.00
p(1, -9.80)=-9.80
p(10, 1.70)=3.05
#include <stdio.h>
double p(int n, double x); //对函数的声明
int main(void)
{
int repeat, ri;
int n;
double x, result;
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++)
{
scanf("%d%lf", &n, &x);
result = p(n, x);
printf("p(%d, %.2lf)=%.2lf\n", n, x, result);
}
}
double p(int n, double x) //对函数的定义
{
double t;
if(n==0) t=1;
else if(n==1)
t=x;
else
t=((2*n-1)*p(n-1,x)-(n-1)*p(n-2,x))/n;
return t;
}
函数的“定义”和‘声明’并不是同一回事
函数的定义是指对函数功能的确立,包括指定函数名,函数值类型,形参及其类型以及函数体等,它是一个完整的,独立的函数单位。
而函数的声明的作用则是把函数名,函数值类型,参数个数,参数类型和参数顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如:函数名,函数值类型,参数个数,参数类型和参数顺序),他不包含函数体。
函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。
C语言函数递归调用_外太空程序员的博客-CSDN博客_c语言函数的递归调用
范例:计算5的阶乘,其实就是计算 12345,
我们并不知道5的阶乘是多少,但是我们知道一点,4的阶乘 * 5他就等于5的阶乘。
我们并不知道4的阶乘是多少,但是我们知道一点,3的阶乘 * 4他就等于4的阶乘。
我们并不知道3的阶乘是多少,但是我们知道一点,2的阶乘 * 3他就等于3的阶乘。
所以递归调用的出口肯定是在1的阶乘,因为1的阶乘是1,可以作为出口,我们能够求出2的阶乘,也就是1*2;以此类推。
#include<stdio.h>
// 函数声明
int dg_jiecheng(int n);
int main() //主函数
{
int result = dg_jiecheng(5);
printf("result=%d\n",result); // 结果为120
}
// 函数定义
int dg_jiecheng(int n)
{
int result; // 局部变量,保存阶乘结果
if(n==1) // 1的阶乘就是1
{
return 1; // 这里就是该递归调用的出口
}
else
{
// 第一次是 result = dg_jiecheng(4)*5,然后进入到了 dg_jiecheng(4),这行代码就被暂存了;
第二次是 result = dg_jiecheng(3)*4,然后进入到了 dg_jiecheng(3),这行代码就被暂存了;
第三次是 result = dg_jiecheng(2)*3,然后进入到了 dg_jiecheng(2),这行代码就被暂存了;
第四次是 result = dg_jiecheng(1)*2,然后进入到了 dg_jiecheng(1),这行代码就被暂存了;
此时,dg_jiecheng(1)的出口条件成立了,终于,能够执行return 1,这可是 return 语句第一次捞着执行。
第一次:return 1,返回的是1,返回到dg_jiecheng(2)这里:
return =1*2 并且也执行return result;,返回1*2=2;
返回到dg_jiecheng(3)这里:
return =2 并且也执行return result;,返回2*3=6;
返回到dg_jiecheng(4)这里:
return =6 并且也执行return result;,返回6*4=24;
返回到dg_jiecheng(5)这里:
return =24 并且也执行return result;,返回24*5=120;
result = dg_jiecheng(n-1)* n;
}
return result;
}
递归的优缺点
优点:
代码少,代码看起来简洁,精妙。
缺点:
虽然代码简洁,精妙,但是不好理解。
如果调用的层次太深,调用栈(内存),可能会溢出,如果真出现这种情况,那么说明不能用递归解决该问题。
效率和性能都不高,这深层次的调用,要保存的东西很多,所以效率和性能肯定高不起来。有些问题用不用递归都行,有些问题可能必须用递归解决。汉诺塔
递归函数的直接和间接调用:
递归函数的直接调用:调用递归函数f的过程中,f函数有调用自己,这就是直接调用。
递归函数的间接调用:调用函数f1的过程中要调用f2函数,然后f2函数又调用f1函数,这就是间接调用。
数组作为函数参数
编程题(指针数组)
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
编写程序,输入一个月份,输出对应的英文名称,要求用指针数组表示12个月的英文名称。
若输入月份错误,输出提示信息。
输入输出示例:括号内为说明
输入:
3 (repeat=3)
5
9
14
输出:
May
September
Wrong input!
#include<stdio.h>
void main()
{
int ri,repeat;
int month;
char *month_name[]={"","January","February","March","April","May","June","July","August","September","October","November","December"};
scanf("%d",&repeat);
for(ri=1;ri<=repeat;ri++){
scanf("%d",&month);
if((month>=1)&&(month<=12))
puts(month_name[month]);
else
printf("Wrong input!");
}
}
编程题 (指针数组,查找相同的字符串)
程序填空,不要改变与输入输出有关的语句。
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
定义一个指针数组将下表的星期信息组织起来,输入一个字符串,在表中查找,若存在,输出该字符串在表中的序号,否则输出-1。
(表格详见实验教材P99)
输入输出示例:括号内为说明
输入:
3 (repeat=3)
Tuesday
Wednesday
year
输出:
3
4
-1
#include<stdio.h>
#include<string.h>
void main()
{
int i,ri,repeat,dateNum;
char *date[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
char str[80];
scanf("%d",&repeat);
getchar();
for(ri=1;ri<=repeat;ri ++){
scanf("%s",str);
/*---------*/
dateNum=sizeof(date)/sizeof(char *);
for (i = 0; i<dateNum; i++){
if (strcmp(date[i],str) == 0) {
printf("%d\n",i+1); break;
}
}
if (i==dateNum) printf("%d\n",-1);
}
}
计算最长的字符串长度
编写一个函数int max_len(char *s[ ], int n),用于计算有n(n<10)个元素的指针数组s中最长的字符串的长度,并编写主程序验证。
例:(括号内为说明)
输入
4 (n=4)
blue
yellow
red
green
输出
length=6
#include <stdio.h>
#include <string.h>
int max_len(char *s[],int n);
void main()
{
int i,n;
char s[10][80],*p[10];
scanf("%d",&n);
for(i=0;i<n;i++){
scanf("%s",s[i]);
p[i]=s[i];
}
printf("length=%d\n",max_len(p,n));
}
int max_len(char *s[],int n)
{
int max,i;
max=strlen(s[0]);
for(i=1;i<n;i++)
if(max<strlen(s[i]))
max=strlen(s[i]);
return max;
}
静态局部变量的值(static局部变量):
有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次调用该函数时,该变量已有值(就是上一次函数调用结束时的值),这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。
自动变量(auto变量)
函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储去中,函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。
实际上,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”
int b,c=3 ========== auto int b,c=3
## 第七章好题
1、写两个函数,分别求两个整数的最大公约数和最小公倍数,用主函数调用这两个函数,并输出结果。两个整数由键盘输人。
题目解析:
该题直接使用“辗转相除法”来求解最大公约数和最小公倍数
最大公约数找出两数中的最小值,然后直接相模,当能够同时被两数整除时,则为最大公约数。
最小公倍数找出两数中的最大值,然后直接进入死循环,直到找到一个数能够同时被两数整除时,则为最小公倍数
【注】此题还有一些比较高级的解法,比如求最大公约数的相减法、欧几里德辗转相除法等,有兴趣的同学可以查询相关资料
代码示例:
#include<stdio.h>
//最大公约数
size_t GCD(size_t a, size_t b)
{
size_t gcd;
gcd = a > b ? b : a;
while(gcd > 1)
{
if((a % gcd == 0) && (b % gcd == 0))
return gcd;
gcd--;
}
return gcd;
}
//最小公倍数
size_t LCM(size_t a, size_t b)
{
size_t lcm;
lcm = a > b ? a : b;
while(1)
{
if((lcm % a==0) && (lcm % b==0))
break;
lcm++;
}
return lcm;
}
int main()
{
size_t a, b, result;
printf("请输入两个整数:>");
scanf("%d %d", &a, &b);
result = GCD(a, b);
printf("%d和%d的最大公约数为:%d\n", a, b, result);
result = LCM(a, b);
printf("%d和%d的最小公倍数为:%d\n", a, b, result);
return 0;
}
3、写一个判素数的函数,在主函数输人一个整数,输出是否为素数的信息。
题目解析:
素数是一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做素数
该题可以使用概念直接判断法求解,不过不需要判断所有的数据,只需要判断数据的一半即可,因为偶数不可能为素数(除了2),所以只需判断该数的一半即可的到答案
代码示例:
#include<stdio.h>
#include<stdbool.h>
bool IsPrime(int value)
{
for(int i=2; i<value/2; ++i)
{
if(value % i == 0) //说明除了1和本身之外,还能被其他数整除
return false;
}
return true;
}
int main()
{
int value;
bool flag;
printf("请输入 value :>");
scanf("%d", &value);
flag = IsPrime(value);
if(flag)
printf("%d 是素数.\n", value);
else
printf("%d 不是素数.\n", value);
return 0;
}
第8章善于利用指针
指针?什么是指针?从根本上看,指针(pointer)是一个值为内存地址
的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是
整数,指针变量的值是地址。
在C语言中,指针有许多用法。本章将介绍如
何把指针作为函数参数使用,以及为何要这样用。
假设一个指针变量名是ptr,可以编写如下语句:
ptr = &pooh; // 把pooh的地址赋给ptr
对于这条语句,我们说ptr“指向”pooh。ptr和&pooh的区别是ptr是变量,
而&pooh是常量。或者,ptr是可修改的左值,而&pooh是右值。还可以把ptr
指向别处:
ptr = &bah; // 把ptr指向bah,而不是pooh
现在ptr的值是bah的地址。
数组和指针
数组形式和指针形式有何不同?以上面的声明为例,数组形式(
ar1[])
在计算机的内存中分配为一个内含29个元素的数组(每个元素对应一个字
符,还加上一个末尾的空字符’\0’),每个元素被初始化为字符串字面量对
应的字符。通常,字符串都作为可执行文件的一部分储存在数据段中。当把
程序载入内存时,也载入了程序中的字符串。字符串储存在静态存储区 (
static memory)中。但是,程序在开始运行时才会为该数组分配内存。此 时,才将字符串拷贝到数组中(第 12 章将详细讲解)。注意,此时字符串
有两个副本。一个是在静态内存中的字符串字面量,另一个是储存在ar1数
组中的字符串。
此后,编译器便把数组名ar1识别为该数组首元素地址(&ar1[0])的别
名。这里关键要理解,在数组形式中,ar1是地址常量。不能更改ar1,如果
改变了ar1,则意味着改变了数组的存储位置(即地址)。可以进行类似
ar1+1这样的操作,标识数组的下一个元素。但是不允许进行++ar1这样的操
作。递增运算符只能用于变量名前(或概括地说,只能用于可修改的左
值),不能用于常量。
指针形式(
*pt1)也使得编译器为字符串在静态存储区预留29个元素的
空间。另外,一旦开始执行程序,它会为指针变量pt1留出一个储存位置,
并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,
但是它的值可以改变。因此,可以使用递增运算符。例如,++pt1将指向第
2 个字符(
o)。
字符串字面量被视为const数据。由于pt1指向这个const数据,所以应该
把pt1声明为指向const数据的指针。这意味着不能用pt1改变它所指向的数
据,但是仍然可以改变pt1的值(即,pt1指向的位置)。如果把一个字符串
字面量拷贝给一个数组,就可以随意改变数据,除非把数组声明为const。
总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针
只把字符串的地址拷贝给指针。程序清单11.3演示了这一点
C语言*p、p以及&p的区别,*p和**p的区别_14skyang的博客-CSDN博客_c语言*p
int p; //这是一个普通的整型变量
int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
指针的四方面的内容:
指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。
1.指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
2.指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
3.指针的值----或者叫指针所指向的内存区或地址
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
4 指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。
指针的算术运算
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。
例二:
char a[20];
int *ptr=(int *)a; //强制类型转换并不会改变a 的类型
ptr++;
在上例中,指针ptr 的类型是int*,它指向的类型是int,它被初始化为指向整型变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr 的值加上了sizeof(int),在32 位程序中,是被加上了4,因为在32 位程序中,int 占4 个字节。由于地址是用字节做单位的,故ptr 所指向的地址由原来的变量a 的地址向高地址方向增加了4 个字节。由于char 类型的长度是一个字节,所以,原来ptr 是指向数组a 的第0 号单元开始的四个字节,此时指向了数组a 中从第4 号单元开始的四个字节。
我们可以用一个指针和一个循环来遍历一个数组,看例子:
例三:
int array[20]={0};
int *ptr=array;
for(i=0;i<20;i++)
{
(*ptr)++;
ptr++;
}
这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1 个单元,所以每次循环都能访问数组的下一个单元。
再看例子:
例四:
char a[20]="You_are_a_girl";
int *ptr=(int *)a;
ptr+=5;
在这个例子中,ptr 被加上了5,编译器是这样处理的:将指针ptr 的值加上5 乘sizeof(int),在32 位程序中就是加上了5 乘4=20。由于地址的单位是字节,故现在的ptr 所指向的地址比起加5 后的ptr 所指向的地址来说,向高地址方向移动了20 个字节。
在这个例子中,没加5 前的ptr 指向数组a 的第0 号单元开始的四个字节,加5 后,ptr 已经指向了数组a 的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。如果上例中,ptr 是被减去5,那么处理过程大同小异,只不过ptr 的值是被减去5 乘sizeof(int),新的ptr 指向的地址将比原来的ptr 所指向的地址向低地址方向移动了20 个字节。
下面请允许我再举一个例子:(一个误区)
例五:
#include<stdio.h>
int main()
{
char a[20]=" You_are_a_girl";
char *p=a;
char **ptr=&p;
//printf("p=%d\n",p);
//printf("ptr=%d\n",ptr);
//printf("*ptr=%d\n",*ptr);
printf("**ptr=%c\n",**ptr);
ptr++;
//printf("ptr=%d\n",ptr);
//printf("*ptr=%d\n",*ptr);
printf("**ptr=%c\n",**ptr);
}
误区一、输出答案为Y 和o
误解:ptr 是一个char 的二级指针,当执行ptr++;时,会使指针加一个sizeof(char),所以输出如上结果,这个可能只是少部分人的结果.
误区二、输出答案为Y 和a误解:ptr 指向的是一个char *类型,当执行ptr++;时,会使指针加一个sizeof(char *)(有可能会有人认为这个值为1,那就会得到误区一的答案,这个值应该是4,参考前面内容), 即&p+4; 那进行一次取值运算不就指向数组中的第五个元素了吗?那输出的结果不就是数组中第五个元素了吗?答案是否定的.
正解: ptr 的类型是char **,指向的类型是一个char *类型,该指向的地址就是p的地址(&p),当执行ptr++;时,会使指针加一个sizeof(char*),即&p+4;那*(&p+4)指向哪呢,这个你去问上帝吧,或者他会告诉你在哪?所以最后的输出会是一个随机的值,或许是一个非法操作.
总结一下:
一个指针ptrold 加(减)一个整数n 后,结果是一个新的指针ptrnew,ptrnew 的类型和ptrold 的类型相同,ptrnew 所指向的类型和ptrold所指向的类型也相同。ptrnew 的值将比ptrold 的值增加(减少)了n 乘sizeof(ptrold 所指向的类型)个字节。就是说,ptrnew 所指向的内存区将比ptrold 所指向的内存区向高(低)地址方向移动了n 乘sizeof(ptrold 所指向的类型)个字节。指针和指针进行加减:两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。两个指针可以进行减法操作,但必须类型相同,一般用在数组方面,不多说了。
运算符&和*
&是取地址运算符,*是间接运算符。
&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
*p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
int a=12; int b; int *p; int **ptr;
分析:
p=&a; //&a 的结果是一个指针,类型是int*,指向的类型int,指向的地址是a 的地址。
*p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是p 所指向的地址,显然,*p 就是变量a。
ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*,在这里是int **。该指针所指向的类型是p 的类型,这里是int*。该指针所指向的地址就是指针p 自己的地址。
*ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b 来给*ptr 赋值就是毫无问题的了。
**ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果是一个int 类型的变量。
指针表达式
一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。在例七中,&a 不是一个左值,因为它还没有占据明确的内存。*ptr 是一个左值,因为*ptr 这个指针已经占据了内存,其实*ptr 就是指针pa,既然pa 已经在内存中有了自己的位置,那么*ptr 当然也有了自己的位置。
数组和指针的关系
数组的数组名其实可以看作一个指针。看下例:
例九:
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0]; //也可写成:value=*array;
value=array[3]; //也可写成:value=*(array+3);
value=array[4]; //也可写成:value=*(array+4);
上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此*array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以*(array+3)等于3。其它依此类推。
例十:
char *str[3]={
"Hello,thisisasample!",
"Hi,goodmorning.",
"Helloworld"
};
char s[80];
strcpy(s,str[0]); //也可写成strcpy(s,*str);
strcpy(s,str[1]); //也可写成strcpy(s,*(str+1));
strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));
上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **,它指向的类型是char *。
*str 也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char**,它指向的类型是char*。
*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'
下面总结一下数组的数组名(数组中储存的也是数组)的问题:
声明了一个数组TYPE array[n],则数组名称array 就有了两重含义:
第一,它代表整个数组,它的类型是TYPE[n];
第二,它是一个常量指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0 号单元,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
在表达式*array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。
表达式array+n(其中n=0,1,2,.....)中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE *,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4
例十一:
int array[10];
int (*ptr)[10];
ptr=&array;:
上例中ptr 是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array 代表数组本身。
本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?
答案是前者。例如:
int(*ptr)[10];
则在32 位程序中,有:
sizeof(int(*)[10])==4
sizeof(int[10])==40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
指针和结构类型的关系
可以声明一个指向结构类型对象的指针。
例十二:
struct MyStruct
{
int a;
int b;
int c;
};
struct MyStruct ss={20,30,40};
//声明了结构对象ss,并把ss 的成员初始化为20,30 和40。
struct MyStruct *ptr=&ss;
//声明了一个指向结构对象ss 的指针。它的类型是
//MyStruct *,它指向的类型是MyStruct。
int *pstr=(int*)&ss;
//声明了一个指向结构对象ss 的指针。但是pstr 和
//它被指向的类型ptr 是不同的。
请问怎样通过指针ptr 来访问ss 的三个成员变量?
答案:
ptr->a; //指向运算符,或者可以这们(*ptr).a,建议使用前者
ptr->b;
ptr->c;
又请问怎样通过指针pstr 来访问ss 的三个成员变量?
答案:
*pstr; //访问了ss 的成员a。
*(pstr+1); //访问了ss 的成员b。
*(pstr+2) //访问了ss 的成员c。
虽然我在我的MSVC++6.0 上调式过上述代码,但是要知道,这样使用pstr 来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元: (将结构体换成数组)
例十三:
int array[3]={35,56,37};
int *pa=array;
//通过指针pa 访问数组array 的三个单元的方法是:
*pa; //访问了第0 号单元
*(pa+1); //访问了第1 号单元
*(pa+2); //访问了第2 号单元
从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。
所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。
所以,在例十二中,即使*pstr 访问到了结构对象ss 的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a 和成员b 之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。
不过指针访问结构成员的正确方法应该是象例十二中使用指针ptr 的方法。
指针和函数的关系
可以把一个指针声明成为一个指向函数的指针。
int fun1(char *,int);
int (*pfun1)(char *,int);
pfun1=fun1;
int a=(*pfun1)("abcdefg",7); //通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
例十四:
int fun(char *);
inta;
char str[]="abcdefghijklmn";
a=fun(str);
int fun(char *s)
{
int num=0;
for(int i=0;;)
{
num+=*s;s++;
}
return num;
}
这个例子中的函数fun 统计一个字符串中各个字符的ASCII 码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s 后,实际是把str 的值传递给了s,s 所指向的地址就和str 所指向的地址一致,但是str 和s 各自占用各自的存储空间。在函数体内对s 进行自加1 运算,并不意味着同时对str 进行了自加1 运算。
指针类型转换
当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
例十五:
float f=12.3;
float *fptr=&f;
int *p;
在上面的例子中,假如我们想让指针p 指向实数f,应该怎么办?
是用下面的语句吗?
p=&f;
不对。因为指针p 的类型是int *,它指向的类型是int。表达式&f 的结果是一个指针,指针的类型是float *,它指向的类型是float。
两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0 上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":
p=(int*)&f;
如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP *TYPE, 那么语法格式是: (TYPE *)p;
这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE *,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。
而原来的指针p 的一切属性都没有被修改。(切记)
一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,必须保证类型一致,否则需要强制转换
例十六:
void fun(char*);
int a=125,b;
fun((char*)&a);
void fun(char*s)
{
charc;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
注意这是一个32 位程序,故int 类型占了四个字节,char 类型占一个字节。函数fun 的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a 的结果是一个指针,它的类型是int *,它指向的类型是int。形参这个指针的类型是char *,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int *类型到char *类型的转换。
结合这个例子,我们可以这样来
想象编译器进行转换的过程:编译器先构造一个临时指针char *temp,然后执行temp=(char *)&a,最后再把temp 的值传递给s。所以最后的结果是:s 的类型是char *,它指向的类型是char,它指向的地址就是a 的首地址。
我们已经知道,指针的值就是指针指向的地址,在32 位程序中,指针的值其实是一个32 位整数。
那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:
unsigned int a;
TYPE *ptr; //TYPE 是int,char 或结构类型等等类型。
a=20345686;
ptr=20345686; //我们的目的是要使指针ptr 指向地址20345686
ptr=a; //我们的目的是要使指针ptr 指向地址20345686
//编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:
unsigned int a;
TYPE *ptr; //TYPE 是int,char 或结构类型等等类型。
a=N //N 必须代表一个合法的地址;
ptr=(TYPE*)a; //呵呵,这就可以了。
严格说来这里的(TYPE *)和指针类型转换中的(TYPE *)还不一样。这里的(TYPE*)的意思是把无符号整数a 的值当作一个地址来看待。上面强调了a 的值必须代表一个合法的地址,否则的话,在你使用ptr 的时候,就会出现非法操作错误。想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:
例十七:
int a=123,b;
int *ptr=&a;
char *str;
b=(int)ptr; //把指针ptr 的值当作一个整数取出来。
str=(char*)b; //把这个整数的值当作一个地址赋给指针str。
现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。
指针的安全问题
看下面的例子:
例十八:
char s='a';
int *ptr;
ptr=(int *)&s;
*ptr=1298;
指针ptr 是一个int *类型的指针,它指向的类型是int。它指向的地址就是s 的首地址。在32 位程序中,s 占一个字节,int 类型占四个字节。最后一条语句不但改变了s 所占的一个字节,还把和s 相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。
让我们再来看一例:
例十九:
char a;
int *ptr=&a;
ptr++;
*ptr=115;
该例子完全可以通过编译,并能执行。但是看到没有?第3 句对指针ptr 进行自加1 运算后,ptr 指向了和整形变量a 相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。
而第4 句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。
在指针的强制类型转换:ptr1=(TYPE *)ptr2 中,如果sizeof(ptr2的类型)大于sizeof(ptr1 的类型),那么在使用指针ptr1 来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2 的类型) 小于sizeof(ptr1 的类型),那么在使用指针ptr1 来访问ptr2 所指向的存储区时是不安全的。至于为什么,读者结合例十八来想一想,应该会明白的。
第八章典型例题:
调试示例error11_1.cpp (指针数组、内存动态分配)
程序填空,不要改变与输入输出有关的语句。
输入若干有关颜色的英文单词,以#作为输入结束标志,对这些单词升序排列后输出。其中颜色的英文单词数数小于20个,颜色的英文单词长度均不超过10个字符。
输入输出示例:括号内为说明
输入:
red
blue
yellow
green
purple
#
输出:
blue green purple red yellow
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
void main()
{
int i,j, index, n = 0;
char *color[20], str[10], *temp;
scanf("%s", str);
while(str[0] != '#') {
color[n] = (char *)malloc(sizeof(char)*(strlen(str)+1));
strcpy(color[n], str);
n++;
scanf("%s", str);
}
/*---------*/
for (i =0; i<n-1; i++) {
index = i;
for (j = i+1; j<n; j++)
if (strcmp(color[j],color[index]) < 0) index = j;
strcpy(str, color[i]);
strcpy(color[i], color[index]);
strcpy(color[index], str);
}
for(i = 0; i < n; i++)
printf("%s ", color[i]);
printf("\n");
}
10022 编程题(指针数组)
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
编写程序,输入一个月份,输出对应的英文名称,要求用指针数组表示12个月的英文名称。
若输入月份错误,输出提示信息。
输入输出示例:括号内为说明
输入:
3 (repeat=3)
5
9
14
输出:
May
September
Wrong input!
#include<stdio.h>
void main()
{
int ri,repeat;
int month;
char *month_name[]={"","January","February","March","April","May","June","July","August","September","October","November","December"};
scanf("%d",&repeat);
for(ri=1;ri<=repeat;ri++){
scanf("%d",&month);
if((month>=1)&&(month<=12))
puts(month_name[month]);
else
printf("Wrong input!");
}
}
10023 编程题 (指针数组,查找相同的字符串)
程序填空,不要改变与输入输出有关的语句。
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
定义一个指针数组将下表的星期信息组织起来,输入一个字符串,在表中查找,若存在,输出该字符串在表中的序号,否则输出-1。
(表格详见实验教材P99)
输入输出示例:括号内为说明
输入:
3 (repeat=3)
Tuesday
Wednesday
year
输出:
3
4
-1
#include<stdio.h>
#include<string.h>
void main()
{
int i,ri,repeat,dateNum;
char *date[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
char str[80];
scanf("%d",&repeat);
getchar();
for(ri=1;ri<=repeat;ri ++){
scanf("%s",str);
/*---------*/
dateNum=sizeof(date)/sizeof(char *);
for (i = 0; i<dateNum; i++){
if (strcmp(date[i],str) == 0) {
printf("%d\n",i+1); break;
}
}
if (i==dateNum) printf("%d\n",-1);
}
}
第9章用户自己建立数据类型
书上典型例题总结:
如何去定义一个结构体数组:
#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;i<3;i++)
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;
}
指向结构体变量的指针:
#include<stdio.h>
#include<string.h>
int main()
{
struct Student
{
long num;
char name[20];
char sex;
float score;
};
struct Student_stu_1;
struct Student*p;
p=stu&_1; //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)表示p指向的结构体变量,(*p).num表示p所指向的结构体变量中的成员num。注意*两侧的括号不可省,因为成员运算符“.”优先于“*”运算符
return 0;
}
如果p指向一个结构体变量stu,以下三种用法等价
(1)stu.成员名(如stu.num);
(2)(*p).成员名(如(*p).num);
(3)p->成员名(如p->num)。
306页例9.7:
学生成绩管理系统
#include<stdio.h>
#include<stdlib.h>
#include<stdlib.h>
#include<string.h>
struct Student{ //每个学生对应一个结构体
char ID[20];//学号
char Name[10];//姓名
float Mark1;//语文成绩
float Mark2;//数学成绩 //四个变量
float Mark3;//英语成绩
float Mark4;//计算机成绩
float All; //总分
float Average;//平均成绩
}students[1000];
int num=0; //计数器
void Copy(struct Student *arr,int i,int j)
{
strcpy(arr[i].ID,arr[j].ID); //strcpy()函数的简介:
是将一个字符串复制到另一块空间地址中 的函数,‘\0’是停止拷贝的终止条件,
也复制到目标空间。下面是库中的strcpy()函数声明:
strcpy(arr[i].Name,arr[j].Name);
arr[i].Mark1 = arr[j].Mark1;
arr[i].Mark2 = arr[j].Mark2;
arr[i].Mark3 = arr[j].Mark3;
arr[i].Mark4 = arr[j].Mark4;
arr[i].All = arr[j].All;
arr[i].Average = arr[j].Average;
}
int Student_SearchByName(char name[])//通过姓名来检索学生
{
int i;
for (i=0;i<num;i++)
{
if (strcmp(students[i].Name,name)==0) //通过strcmp函数来对比学生姓名,找到返回在数组的位置
{
return i;
}
}
return -1; //未找到返回 -1
}
int Student_SearchByIndex(char id[])//通过学号来检索学生信息
{
int i;
for (i=0;i<num;i++)
{
if (strcmp(students[i].ID,id)==0) //通过strcmp函数来对比学生id,找到返回位置
{
return i;
}
}
return -1; //未找到返回 -1
}
void Student_DisplaySingle(int index)//输出表头
{
printf("%10s%10s%8s%8s%8s%10s\n","学号","姓名","语文","数学","英语","计算机","总成绩","平均成绩");
printf("-------------------------------------------------------------\n");
printf("%10s%10s%8.2f%8.2f%8.2f%8.2f%10.2f%10.2f\n",students[index].ID,students[index].Name,
students[index].Mark1,students[index].Mark2,students[index].Mark3,students[index].Mark4,students[index].All,students[index].Average);
}
void inputt()//利用循环录入学生信息
{
while(1)
{
printf("请输入学号:");
scanf("%s",&students[num].ID);
getchar();
printf("请输入姓名:");
scanf("%s",&students[num].Name);
getchar();
printf("请输入成绩:");
scanf("%f",&students[num].Mark1);
getchar();
printf("请输入成绩:");
scanf("%f",&students[num].Mark2);
getchar();
printf("请输入成绩:");
scanf("%f",&students[num].Mark3);
getchar();
printf("请输入成绩:");
scanf("%f",&students[num].Mark4); //依次输入各项数据
getchar();
students[num].All=students[num].Mark1+students[num].Mark2+students[num].Mark3+students[num].Mark4; //输完数据后自动计算总成绩与平均成绩
students[num].Average=(students[num].Mark1+students[num].Mark2+students[num].Mark3+students[num].Mark4)/4;
if(Student_SearchByIndex(students[num].ID) == -1)
{
num++; //移向下一个位置
}
else
{
printf("学号重复,输入数据无效 !!!\n");
}
printf("是否继续?(y/n)");
if (getchar()=='n')
{
break;
}
}
}
void modify()//修改成绩
{
while(1)
{
char id[20];
int index;
printf("请输入要修改的学生的学号:");
scanf("%s",&id);
getchar();
index=Student_SearchByIndex(id); //调用搜查id函数,根据其返回值确定位置
if (index==-1)
{
printf("学生不存在!\n");
}
else
{
printf("你要修改的学生信息为:\n");
Student_DisplaySingle(index);
printf("-- 请输入新值--\n");
printf("请输入学号:");
scanf("%s",&students[index].ID);
getchar();
printf("请输入姓名:");
scanf("%s",&students[index].Name);
getchar();
printf("请输入语文成绩:");
scanf("%f",&students[index].Mark1);
getchar();
printf("请输入数学成绩:");
scanf("%f",&students[index].Mark2);
getchar();
printf("请输入英语成绩:");
scanf("%f",&students[index].Mark3);
getchar();
printf("请输入计算机成绩:");
scanf("%f",&students[index].Mark4); //重新录入一套新的数据替代
getchar();
students[index].All=students[index].Mark1+students[index].Mark2+students[index].Mark3+students[index].Mark4;
students[index].Average=(students[index].Mark1+students[index].Mark2+students[index].Mark3+students[index].Mark4)/4;
}
printf("是否继续?(y/n)");
if (getchar()=='n')
{
break;
}
}
}
void deletee()//删除学生信息
{
int i;
while(1)
{
char id[20];
int index;
printf("请输入要删除的学生的学号:");
scanf("%s",&id);
getchar();
index=Student_SearchByIndex(id); //调用搜查id函数,根据其返回值确定位置
if (index==-1)
{
printf("学生不存在!\n");
}
else
{
printf("你要删除的学生信息为:\n");
Student_DisplaySingle(index);
printf("是否真的要删除?(y/n)");
if (getchar()=='y')
{
for (i=index;i<num-1;i++)
{
Copy(students,i,i+1);
//students[i]=students[i+1]; //把后边的对象都向前移动
}
num--;
}
getchar();
}
printf("是否继续?(y/n)");
if (getchar()=='n')
{
break;
}
}
}
void display()//打印已录入的学生信息
{
int a;
printf("%10s%10s%8s%8s%8s%8s%10s%10s\n","学号","姓名","语文","数学","英语","计算机","总成绩","平均成绩");
printf("-------------------------------------------------------------\n");
for (a=0;a<num;a++)
{
printf("%10s%10s%8.2f%8.2f%8.2f%8.2f%10.2f%10.2f\n",students[a].ID,students[a].Name,
students[a].Mark1,students[a].Mark2,students[a].Mark3,students[a].Mark4,students[a].All,students[a].Average);
}
}
void insert()//指定位置插入学生信息
{
int a,b,c;
printf("请输入你要插入的位置");
scanf("%d",&a);
if(a>num) {
printf("输入的位置有误,请重新输入,当前共%d条数据\n",num);
scanf("%d",&a);}
b=num-1;
for(;b>=a-1;b--)
{
//strcpy(students[b+1].ID,students[b].ID);
//strcpy(students[b+1].Name,students[b].Name);
//students[b+1].Mark1=students[b].Mark1;
//students[b+1].Mark2=students[b].Mark2;
//students[b+1].Mark3=students[b].Mark3;
//students[b+1].Mark4=students[b].Mark4;
//students[b+1].All=students[b].All;
//students[b+1].Average=students[b].Average;
Copy(students,b+1,b); //根据其输入的位置,将其及以后的数据向后移动一个位置
}
num++;
printf("请输入学号:");
scanf("%s",&students[a-1].ID);
getchar();
printf("请输入姓名:");
scanf("%s",&students[a-1].Name);
getchar();
printf("请输入语文成绩:");
scanf("%f",&students[a-1].Mark1);
getchar();
printf("请输入数学成绩:");
scanf("%f",&students[a-1].Mark2);
getchar();
printf("请输入英语成绩:");
scanf("%f",&students[a-1].Mark3);
getchar();
printf("请输入计算机成绩:");
scanf("%f",&students[a-1].Mark4); //输入新数据
getchar();
students[a-1].All=students[a-1].Mark1+students[a-1].Mark2+students[a-1].Mark3+students[a-1].Mark4;
students[a-1].Average=(students[a-1].Mark1+students[a-1].Mark2+students[a-1].Mark3+students[a-1].Mark4)/4;
}
void search()//查询学生信息
{
while(1)
{
char name[20];
int index;
printf("请输入要查询的学生的姓名:");
scanf("%s",&name);
getchar();
index=Student_SearchByName(name); //调用搜查name函数,根据其返回值确定位置
if (index==-1)
{
printf("学生不存在!\n");
}
else
{
printf("你要查询的学生信息为:\n");
Student_DisplaySingle(index);
}
printf("是否继续?(y/n)");
if (getchar()=='n')
{
break;
}
}
}
voidsort()//根据平均分排序
(此时注意按照题目要求应该排序两个)
{
int i,j;
//struct students tmp;
for (i=0;i<num;i++)
{
students[i].Average=(students[i].Mark1+students[i].Mark2+students[i].Mark3+students[i].Mark4)/4;
}
for (i=0;i<num;i++)
{
for (j=1;j<num-i;j++)
{
if (students[j-1].Average<students[j].Average)
{
Copy(students,num,j-1);
Copy(students,j-1,j);
Copy(students,j,num);
//tmp=students[j-1];
//students[j-1]=students[j];
//students[j]=tmp; //冒泡排序
}
}
}
int a;
printf("%10s%10s%8s%8s%8s%10s\n","学号","姓名","语文","数学","英语","计算机","总成绩","平均成绩");
printf("-------------------------------------------------------------\n");
for (a=0;a<num;a++)
{
printf("%10s%10s%8.2f%8.2f%8.2f%8.2f%10.2f%10.2f\n",students[a].ID,students[a].Name,
students[a].Mark1,students[a].Mark2,students[a].Mark3,students[a].Mark4,students[a].All,students[a].Average);
}
}
void SearchLow()//搜索不及格的并输出
{
int a;
printf(" 语文不及格的有%10s%10s%8s\n","学号","姓名","语文");
for(a=0;a<num;a++)
{
if(students[a].Mark1<60)
printf("%10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark1); //从头搜索到尾,若小于60就输出
}
printf(" 数学不及格的有%10s%10s%8s\n","学号","姓名","数学");
for(a=0;a<num;a++)
{
if(students[a].Mark2<60)
printf("%10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark2);
}
printf(" 英语不及格的有%10s%10s%8s\n","学号","姓名","英语");
for(a=0;a<num;a++)
{
if(students[a].Mark3<60)
printf("%10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark3);
}
printf(" 计算机不及格的有%10s%10s%8s\n","学号","姓名","计算机");
for(a=0;a<num;a++)
{
if(students[a].Mark4<60)
printf("%10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark4);
}
system("pause"); //这个好像没作用
}
void SearchHigh()//搜索成绩最高者输出
{
int a;
int max ;
printf(" 语文最高分为%10s%10s%8s\n","学号","姓名","语文");
max=students[0].Mark1;
for(a=1;a<num;a++)
{
if(students[a].Mark1>max)
max=students[a].Mark1;
}
for(a=0;a<num;a++)
{
if(max==students[a].Mark1)
printf("%10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark1);
}
printf(" 数学最高分为%10s%10s%8s\n","学号","姓名","数学");
max=students[0].Mark2;
for(a=1;a<num;a++)
{
if(students[a].Mark2>max)
max=students[a].Mark2;
}
for(a=0;a<num;a++)
{
if(max==students[a].Mark2)
printf("%10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark2);
}
printf(" 英语最高分为%10s%10s%8s\n","学号","姓名","英语");
max=students[0].Mark3;
for(a=1;a<num;a++)
{
if(students[a].Mark3>max)
max=students[a].Mark3;
}
for(a=0;a<num;a++)
{
if(max==students[a].Mark3)
printf(" %10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark3);
}
printf(" 计算机最高分为%10s%10s%8s\n","学号","姓名","计算机");
max=students[0].Mark4;
for(a=1;a<num;a++)
{
if(students[a].Mark4>max)
max=students[a].Mark4;
}
for(a=0;a<num;a++)
{
if(max==students[a].Mark4)
printf(" %10s%10s%8.2f\n",students[a].Name,students[a].ID,students[a].Mark4);
}
system("pause");
}
void Save()
{
FILE*fp = fopen("temp.txt","w+");
fprintf(fp,"%d\n",num);
for(int i = 0 ; i< num ;i++)
{
fprintf(fp,"%s %s %f %f %f %f %f %f\n",students[i].ID,students[i].Name,students[i].Mark1,students[i].Mark2,students[i].Mark3,students[i].Mark4,students[i].All,students[i].Average);
}
fclose(fp);
}
void Load()
{
FILE*fp = fopen("temp.txt","r");
fscanf(fp,"%d",&num);
for(int i = 0 ; i< num ;i++)
{
fscanf(fp,"%s %s %f %f %f %f %f %f\n",students[i].ID,students[i].Name,&students[i].Mark1,&students[i].Mark2,&students[i].Mark3,&students[i].Mark4,&students[i].All,&students[i].Average);
}
fclose(fp);
}
/*主程序*/
int main(){
int i;
while(1){
Load();
printf("\t\t\t\t\t-------- 学生成绩管理系统-------\n\n\n\n"); //菜单
printf("\t\t\t\t\t1. 增加学生记录\n\n");
printf("\t\t\t\t\t2. 修改学生记录\n\n");
printf("\t\t\t\t\t3. 删除学生记录\n\n");
printf("\t\t\t\t\t4. 插入学生记录\n\n");
printf("\t\t\t\t\t5. 显示所有记录\n\n");
printf("\t\t\t\t\t6. 查询学生记录\n\n");
printf("\t\t\t\t\t7. 按平均成绩排序\n\n");
printf("\t\t\t\t\t8. 输出各科目不及格学生\n\n");
printf("\t\t\t\t\t9. 输出各科目最高分\n\n");
printf("\t\t\t\t\t0. 退出\n\n\n");
printf("请选择(0-9):");
scanf("%d",&i);
switch(i){
case 1:inputt();break;
case 2:modify();break;
case 3:deletee();break;
case 4:insert();break;
case 5:display();break;
case 6:search();break;
case 7:sort();break;
case 8:SearchLow();break;
case 9:SearchHigh();break;
case 0:exit(0);
default: ;
}
Save();
}
return 0;
}
void Save() {
FILE *fp = fopen("temp.txt", "w+");
fprintf(fp, "%d\n", num);
for (int i = 0 ; i < num ; i++) {
fprintf(fp, "%s %s %f %f %f %f %f %f\n", students[i].ID, students[i].Name, students[i].Mark1, students[i].Mark2,
students[i].Mark3, students[i].Average1);
}
fclose(fp);
}
void Load() {
FILE *fp = fopen("temp.txt", "r");
fscanf(fp, "%d", &num);
for (int i = 0 ; i < num ; i++) {
fscanf(fp, "%s %s %f %f %f %f %f %f\n", students[i].ID, students[i].Name, &students[i].Mark1, &students[i].Mark2,
&students[i].Mark3, &students[i].Average1);
}
fclose(fp);
示例问题:创建图书目录
Gwen Glenn要打印一份图书目录。她想打印每本书的各种信息:书名、
作者、出版社、版权日期、页数、册数和价格。其中的一些项目(如,书
名)可以储存在字符数组中,其他项目需要一个int数组或float数组。用 7 个
不同的数组分别记录每一项比较繁琐,尤其是 Gwen 还想创建多份列表:一
份按书名排序、一份按作者排序、一份按价格排序等。如果能把图书目录的
信息都包含在一个数组里更好,其中每个元素包含一本书的相关信息。
因此,Gwen需要一种即能包含字符串又能包含数字的数据形式,而且
还要保持各信息的独立。C结构就满足这种情况下的需求。我们通过一个示
例演示如何创建和使用数组。但是,示例进行了一些限制。第一,该程序示
例演示的书目只包含书名、作者和价格。第二,只有一本书的数目。当然,
别忘了这只是进行了限制,我们在后面将扩展该程序。请看程序清单14.1及
其输出,然后阅读后面的一些要点。
程序清单14.1 book.c程序
//* book.c -- 一本书的图书目录 */
#include <stdio.h>
#include <string.h>
char * s_gets(char * st, int n);
#define MAXTITL41 /* 书名的最大长度 + 1 */
#define MAXAUTL31 /* 作者姓名的最大长度 + 1*/
struct book { /* 结构模版:标记是 book */
char title[MAXTITL];
1007char author[MAXAUTL];
float value;
}; /* 结构模版结束 */
int main(void)
{
struct book library; /* 把 library 声明为一个 book 类型的变量 */
printf("Please enter the book title.\n");
s_gets(library.title, MAXTITL); /* 访问title部分*/
printf("Now enter the author.\n");
s_gets(library.author, MAXAUTL);
printf("Now enter the value.\n");
scanf("%f", &library.value);
printf("%s by %s: $%.2f\n", library.title,
library.author, library.value);
printf("%s: \"%s\" ($%.2f)\n", library.author,
library.title, library.value);
printf("Done.\n");
return 0;
}
1008char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是 NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
continue; //处理输入行中剩余的字符
}
return ret_val;
}
我们使用前面章节中介绍的s_gets()函数去掉fgets()储存在字符串中的换
行符。下面是该例的一个运行示例:
Please enter the book title.
1009Chicken of the Andes
Now enter the author.
Disma Lapoult
Now enter the value.
29.99
Chicken of the Andes by Disma Lapoult: $29.99
Disma Lapoult: "Chicken of the Andes" ($29.99)
Done.
程序清单14.1中创建的结构有3部分,每个部分都称为成员(member)
或字段(
field)。这3部分中,一部分储存书名,一部分储存作者名,一部
分储存价格。下面是必须掌握的3个技巧:
为结构建立一个格式或样式;
声明一个适合该样式的变量;
访问结构变量的各个部分
建立结构声明
结构声明(
structure declaration)描述了一个结构的组织布局。声明类
似下面这样:
struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
该声明描述了一个由两个字符数组和一个float类型变量组成的结构。该
声明并未创建实际的数据对象,只描述了该对象由什么组成。〔有时,我们
把结构声明称为模板,因为它勾勒出结构是如何储存数据的。如果读者知道
C++的模板,此模板非彼模板,C++中的模板更为强大。〕我们来分析一些
细节。首先是关键字 struct,它表明跟在其后的是一个结构,后面是一个可
选的标记(该例中是 book),稍后程序中可以使用该标记引用该结构。所
以,我们在后面的程序中可以这样声明:
struct book library;
这把library声明为一个使用book结构布局的结构变量。
在结构声明中,用一对花括号括起来的是结构成员列表。每个成员都用
自己的声明来描述。例如,title部分是一个内含MAXTITL个元素的char类型
数组。成员可以是任意一种C的数据类型,甚至可以是其他结构!右花括号
后面的分号是声明所必需的,表示结构布局定义结束。可以把这个声明放在
所有函数的外部(如本例所示),也可以放在一个函数定义的内部。如果把
结构声明置于一个函数的内部,它的标记就只限于该函数内部使用。如果把
结构声明置于函数的外部,那么该声明之后的所有函数都能使用它的标记。
1011例如,在程序的另一个函数中,可以这样声明:
struct book dickens;
这样,该函数便创建了一个结构变量dickens,该变量的结构布局是
book。
结构的标记名是可选的。但是以程序示例中的方式建立结构时(在一处
定义结构布局,在另一处定义实际的结构变量),必须使用标记。我们学完
如何定义结构变量后,再来看这一点。
访问结构成员
结构类似于一个“超级数组”,这个超级数组中,可以是一个元素为char
类型,下一个元素为forat类型,下一个元素为int数组。可以通过数组下标单
独访问数组中的各元素,那么,如何访问结构中的成员?使用结构成员运算
符——点(
.)访问结构中的成员。例如,library.value即访问library的value
部分。可以像使用任何float类型变量那样使用library.value。与此类似,可以
像使用字符数组那样使用 library.title。因此,程序清单 14.1 中的程序中有
s_gets(library.title, MAXTITL);和scanf("%f", &library.value);这样的代码。
本质上,.title、.author和.value的作用相当于book结构的下标。
注意,虽然library是一个结构,但是library.value是一个float类型的变
量,可以像使用其他 float 类型变量那样使用它。例如,scanf("%f",...)需要一
个 float 类型变量的地址,而&library.float正好符合要求。.比&的优先级高,
因此这个表达式和&(library.float)一样。
指向结构的指针
喜欢使用指针的人一定很高兴能使用指向结构的指针。至少有 4 个理由
可以解释为何要使用指向结构的指针。第一,就像指向数组的指针比数组本
身更容易操控(如,排序问题)一样,指向结构的指针通常比结构本身更容
易操控。第二,在一些早期的C实现中,结构不能作为参数传递给函数,但
是可以传递指向结构的指针。第三,即使能传递一个结构,传递指针通常更
有效率。第四,一些用于表示数据的结构中包含指向其他结构的指针。
下面的程序(程序清单14.4)演示了如何定义指向结构的指针和如何用
这样的指针访问结构的成员。
程序清单14.4 friends.c程序
/* friends.c -- 使用指向结构的指针 */
#include <stdio.h>
#define LEN 20
struct names {
char first[LEN];
char last[LEN];
};
struct guy {
struct names handle;
char favfood[LEN];
char job[LEN];
1031float income;
};
int main(void)
{
struct guy fellow[2] = {
{ { "Ewen", "Villard" },
"grilled salmon",
"personality coach",
68112.00
},
{ { "Rodney", "Swillbelly" },
"tripe",
"tabloid editor",
432400.00
}
};
struct guy * him; /* 这是一个指向结构的指针 */
printf("address #1: %p #2: %p\n", &fellow[0], &fellow[1]);
him = &fellow[0]; /* 告诉编译器该指针指向何处 */
1032printf("pointer #1: %p #2: %p\n", him, him + 1);
printf("him->income is $%.2f: (*him).income is $%.2f\n",
him->income, (*him).income);
him++; /* 指向下一个结构 */
printf("him->favfood is %s: him->handle.last is %s\n",
him->favfood, him->handle.last);
return 0;
}
该程序的输出如下:
address #1: 0x7fff5fbff820 #2: 0x7fff5fbff874
pointer #1: 0x7fff5fbff820 #2: 0x7fff5fbff874
him->income is $68112.00: (*him).income is $68112.00
him->favfood is tripe: him->handle.last is Swillbelly
关键概念
我们在编程中要表示的信息通常不只是一个数字或一些列数字。程序可
能要处理具有多种属性的实体。例如,通过姓名、地址、电话号码和其他信
息表示一名客户;或者,通过电影名、发行人、播放时长、售价等表示一部
电影DVD。C结构可以把这些信息都放在一个单元内。在组织程序时这很重
要,因为这样可以把相关的信息都储存在一处,而不是分散储存在多个变量
中。
设计结构时,开发一个与之配套的函数包通常很有用。例如,写一个以
结构(或结构的地址)为参数的函数打印结构内容,比用一堆printf()语句强
得多。因为只需要一个参数就能打印结构中的所有信息。如果把信息放到零
散的变量中,每个部分都需要一个参数。另外,如果要在结构中增加一个成
员,只需重写函数,不必改写函数调用。这在修改结构时很方便。
联合声明与结构声明类似。但是,联合的成员共享相同的存储空间,而
且在联合中同一时间内只能有一个成员。实质上,可以在联合变量中储存一
个类型不唯一的值。
enum 工具提供一种定义符号常量的方法,typedef 工具提供一种为基本
或派生类型创建新标识符的方法。
指向函数的指针提供一种告诉函数应使用哪一个函数的方法。
本章小结
C 结构提供在相同的数据对象中储存多个不同类型数据项的方法。可以
使用标记来标识一个具体的结构模板,并声明该类型的变量。通过成员点运
算符(.)可以使用结构模版中的标签来访问结构的各个成员。
如果有一个指向结构的指针,可以用该指针和间接成员运算符(->)代
替结构名和点运算符来访问结构的各成员。和数组不同,结构名不是结构的
地址,要在结构名前使用&运算符才能获得结构的地址。
一贯以来,与结构相关的函数都使用指向结构的指针作为参数。现在的
C允许把结构作为参数传递,作为返回值和同类型结构之间赋值。然而,传
递结构的地址通常更有效。
联合使用与结构相同的语法。然而,联合的成员共享一个共同的存储空
间。联合同一时间内只能储存一个单独的数据项,不像结构那样同时储存多
种数据类型。也就是说,结构可以同时储存一个int类型数据、一个double类
型数据和一个char类型数据,而相应的联合只能保存一个int类型数据,或者
一个double类型数据,或者一个char类型数据。
通过枚举可以创建一系列代表整型常量(枚举常量)的符号和定义相关
联的枚举类型。
typedef工具可用于建立C标准类型的别名或缩写。
函数名代表函数的地址,可以把函数的地址作为参数传递给其他函数,
然后这些函数就可以使用被指向的函数。如果把特定函数的地址赋给一个名
为pf的函数指针,可以通过以下两种方式调用该函数:
#include <math.h> /* 提供sin()函数的原型:double sin(double) */
...
double (*pdf)(double);
1121double x;
pdf = sin;
x = (*pdf)(1.2); // 调用sin(1.2)
x = pdf(1.2); // 同样调用 sin(1.2)
在 C 语言中*号有三个用途,
分别是:
- 乘号,用做乘法运算,例如 5*6。
- 申明一个指针,在定义指针变量时使用,例如 int *p;。
- 间接运算符,取得指针所指向的内存中的值,例如 printf(“%d”,*p)
第10章对文件的输入输出
C 文件的有关基本知识
1.1 什么是文件
文件有不同的类型,在程序设计时,主要用到两种文件:
(1)程序文件。包括源程序文件(后缀为.c)、目标文件(后缀.obj)、可执
行文件(后缀.exe)等。这种文件的内容是程序代码。
(2)数据文件。文件的内容不是程序,而是供程序运行时读写的数据。
1.2 文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件标识包括 3 部分:(1)文件路径
(2)文件名主干
(3)文件后缀
文件路径表示文件在外部存储设备中的位置。
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
137
扫码领答案 精讲系统课
1.3 文件的分类
根据数据的组织形式,数据文件可分为 ASCII 文件和二进制文件。
二进制文件:数据在内存中是以二进制形式存储的,如果不加转换地输出到
外存,就是二进制文件。
ASCII 文件:如果要求在外存上以 ASCII 代码形式存储,则需要在存储前
进行转换,就是 ASCII 文件,ASCII 文件又称文本文件。
1.4 文件类型指针
每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文
件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是存
放在一个结构体变量中的。该结构体类型是由系统声明的,取名为 FILE。
一般不对 FILE 类型变量命名,也就是不通过变量的名字来引用这些变
量,而是设置一个指向 FILE 类型变量的指针变量,然后通过它来引用这些
FILE 类型变量。
FILE *fp;
题 1.C 语言中系统默认的文件类型有文本文件和二进制文件。(判断题)
答案:正确
2.打开与关闭文件
2.1 用 fopen 函数打开数据文件
fopen 函数的调用方式为
fopen(文件名,使用文件方式);
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
138
扫码领答案 精讲系统课
表 1 使用文件方式
文件使用方式 含义 文件不存在
“r”(只读) 为了输入数据,打开一个已存在的文本文件 出错
“w”(只读) 为了输出数据,打开一个文本文件 建立新文件
“a”(追加) 向文本文件尾添加数据 出错
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立新文件
“ab”(追加) 向二进制文件尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,建立一个新的文本文件 建立新文件
“a+”(读写) 为了读和写,打开一个文本文件 出错
“rb+”(读写) 为了读和写,打开一个二进制文件 出错
“wb+”(读写) 为了读和写,建立一新的二进制文件 建立新文件
“ab+”(读写) 为读写打开一个二进制文件 出错
如果不能实现“打开”的任务,fopen 函数将会带回一个出错信息。此时
fopen 函数将带回一个空指针值 NULL。
常用下面的方法打开一个文件:
if((fp=fopen(“file1”,“r”))==NULL)
{
printf(“cannot open this file\n”);
exit(0);
}
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
139
扫码领答案 精讲系统课
2.2 用 fclose 函数关闭数据文件
“关闭”就是撤销文件信息区和文件缓冲区,使文件指针变量不再指向该
文件,也就是文件指针变量与文件“脱钩”,此后不能再通过该指针对原来与
其相联系的文件进行读写操作。
关闭文件用 fclose 函数。fclose 函数调用的一般形式为
fclose(文件指针);
fclose 函数也带回一个值,当成功地执行了关闭操作,则返回值为 0;否
则返回 EOF(-1)。
题 1.定义 FILE *fp;以写方式打开文件 C:\aa.dat 的正确语句是( )。
A.fp=fopen(“C:\aa.dat”,“w”);
B.fp=fopen(“C:\aa.dat”,“r”);
C.fp=fopen(“C:\\aa.dat”,“w”);
D.fp=fopen(“C:\\aa.dat”,“r”);
答案:C
题 2.若 fp 已正确定义为一个文件指针,d.txt 为 C 盘根目录下的文本文件。
请填空,以便为“读”而打开此文件:
fp=fopen(“ ”,“ ”);
答案:C:\\d.txt,r
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
140
扫码领答案 精讲系统课
3.顺序读写数据文件
3.1 向文件读写一个字符
表 2 读写一个字符的函数
函数名 调用形式 功能 返回值
fgetc fgetc(fp)
从 fp 指向的文件读
入一个字符
读成功,带回所读的字符,失
败则返回文件结束标志EOF(即
-1)
fputc fputc(ch,fp)
把字符 ch 写到文件
指针变量 fp 所指向
的文件中
输出成功,返回值就是输出的
字符;输出失败,则返回EOF(即
-1)
3.2 向文件读写一个字符串
表 3 读写一个字符串的函数
函数名 调用形式 功能 返回值
fgets fgets(str,n,fp)
从 fp 指向的文件读入一个
长度为(n-1)的字符串,存
放到字符数组 str 中。
读成功,返回地址
str,失败则返回
NULL
fputs fputs(str,fp)
把 str 所指向的字符串写到
文件指针变量 fp 所指向的
文件中
输出成功,返回 0;
输出失败,否则返
回非 0 值
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
141
扫码领答案 精讲系统课
3.3 用格式化的方式读写文件
一般的调用方式为:
fprintf(文件指针,格式字符串,输出列表);
fscanf(文件指针,格式字符串,输入列表);
3.4 用二进制方式向文件读写一组数据
C 语言允许用 fread 函数从文件中读一个数据块,用 fwrite 函数向文件写
一个数据块。在读写时是以二进制形式进行的。再想磁盘写数据时,直接将内
存中一组数据原封不动、不加转换地复制到磁盘文件上,在读入也是将磁盘文
件中若干字节的内容一批读入内存。
它们的一般调用形式为
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
题 1.C 语言中,调用 函数来检测文件位置指针有没有到文件尾。
答案:feof
题 2.以下叙述中错误的是( )。
A.gets 函数用于从键盘输入字符串。
B.getchar 函数用于从文件读入字符。
C.fputs 函数用于把字符串输出到文件。
D.fwrite 函数用于以二进制形式输出数据到文件。
答案:B
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
142
扫码领答案 精讲系统课
题 3.将整型数组 a 中各元素存入文件 data.txt 中。(改错题)
#include<stdio.h>
int main()
{
int a[]={1,2,3,4,5,6,7,8};
FILE *fp;
fp=fopen(“E:\data.txt”,w);
if(fp!=NULL)
{
for(i=0;i<8;i++)
fprintf(“%d”,a[i]);
}
fclose(fp);
}
答案:fp=fopen(“E:\\data.txt”,”w”);
fwrite(&a[i],sizeof(int),1,fp);
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
143
扫码领答案 精讲系统课
课时十二 练习题
1.C 语言中的文件类型有( )。
A.索引文件和文本文件两种
B.二进制文件一种
C.文本文件一种
D.ASCII 文件和二进制文件两种
2.若要打开名为 abc.txt 的文本文件进行读、写操作,下面符合此要求的函数
调用的是( )。
A.fopen(“abc.txt”,“r”);
B.fopen(“abc.txt”,“r+”);
C.fopen(“abc.txt”,“rb”);
D.fopen(“abc.txt”,“w”);
3.若执行 fopen()函数时发生错误,则函数的返回值是( )。
A.地址值 B.NULL
C.1 D.EOF
4.fscanf 函数的正确调用形式是( )。
A.fscanf(fp,格式控制符,输出列表);
B.fscanf(格式控制符,输出列表,fp);
C.fscanf(格式控制符,文件指针,输出列表);
D.fscanf(文件指针,格式控制符,输入列表);
5.在向文件写时,下列哪个函数名是写单字符函数( )。
A.fread B.fgetc C.fputc D.fwrite
蜂考系统课 官方公众号:蜂考官微 学习交流加 QQ 群:975047395
144
扫码领答案 精讲系统课
6.在 C 语言中,对文件操作的一般步骤是( )。
A.打开文件,读文件,写文件,关闭文件
B.定义文件指针,读文件,写文件,关闭文件
C.定义文件指针,打开文件,读写文件,关闭文件
D.操作文件,定义文件指针,修改文件,关闭文件
7.以下程序的功能是,从键盘输入一组字符,将字符本身及字符 ASCII 码写入
D 盘中 test.txt 文本文件保存,输入字符以#结束。
#include<stdio.h>
#include<stdlib.h>
int main()
{
;
char ch;
fp=fopen(“D:\\test.txt”,“ ”);
while((ch=getchar())!=‘#’)
fprintf(fp,“%c %d”, );
fclose(fp);
return 0;
}
与文件进行通信
有时,需要程序从文件中读取信息或把信息写入文件。这种程序与文件
交互的形式就是文件重定向。这种方法很简单,但是有一定限制。例如,假设要编写一个交互程序,询问用户书名并把完整的书名列表保存在文件中。如果使用重定向,应该类似于:
books > bklist
用户的输入被重定向到 bklist 中。这样做不仅会把不符合要求的文本写
入 bklist,而且用户也看不到要回答什么问题。
C提供了更强大的文件通信方法,可以在程序中打开文件,然后使用特
殊的I/O函数读取文件中的信息或把信息写入文件。在研究这些方法之前,
先简要介绍一下文件的性质。
文件是什么
文件(file)通常是在磁盘或固态硬盘上的一段已命名的存储区。对我
们而言,stdio.h就是一个文件的名称,该文件中包含一些有用的信息。然
而,对操作系统而言,文件更复杂一些。例如,大型文件会被分开储存,或
者包含一些额外的数据,方便操作系统确定文件的种类。然而,这都是操作
系统所关心的,程序员关心的是C程序如何处理文件(除非你正在编写操作
系统)。
C把文件看作是一系列连续的字节,每个字节都能被单独读取。这与UNIX环境中(C的发源地)的文件结构相对应。由于其他环境中可能无法完
全对应这个模型,C提供两种文件模式:文本模式和二进制模式。
文本模式和二进制模式
首先,要区分文本内容和二进制内容、文本文件格式和二进制文件格
式,以及文件的文本模式和二进制模式。
948所有文件的内容都以二进制形式(0或1)储存。但是,如果文件最初使
用二进制编码的字符(例如, ASCII或Unicode)表示文本(就像C字符串那
样),该文件就是文本文件,其中包含文本内容。如果文件中的二进制值代
表机器语言代码或数值数据(使用相同的内部表示,假设,用于long或
double类型的值)或图片或音乐编码,该文件就是二进制文件,其中包含二
进制内容。
UNIX用同一种文件格式处理文本文件和二进制文件的内容。不奇怪,
鉴于C是作为开发UNIX的工具而创建的,C和UNIX在文本中都使用\n(换行
符)表示换行。UNIX目录中有一个统计文件大小的计数,程序可使用该计
数确定是否读到文件结尾。然而,其他系统在此之前已经有其他方法处理文
件,专门用于保存文本。也就是说,其他系统已经有一种与UNIX模型不同
的格式处理文本文件。例如,以前的OS X Macintosh文件用\r (回车符)表
示新的一行。早期的MS-DOS文件用\r\n组合表示新的一行,用嵌入的Ctrl+Z
字符表示文件结尾,即使实际文件用添加空字符的方法使其总大小是256的
倍数(在Windows中,Notepad仍然生成MS-DOS格式的文本文件,但是新的
编辑器可能使用类UNIX格式居多)。其他系统可能保持文本文件中的每一
行长度相同,如有必要,用空字符填充每一行,使其长度保持一致。或者,
系统可能在每行的开始标出每行的长度。
为了规范文本文件的处理,C 提供两种访问文件的途径:二进制模式和
文本模式。在二进制模式中,程序可以访问文件的每个字节。而在文本模式
中,程序所见的内容和文件的实际内容不同。程序以文本模式读取文件时,
把本地环境表示的行末尾或文件结尾映射为C模式。例如,C程序在旧式
Macintosh中以文本模式读取文件时,把文件中的\r转换成\n;以文本模式写
入文件时,把\n转换成\r。或者,C文本模式程序在MS-DOS平台读取文件
时,把\r\n转换成\n;写入文件时,把\n转换成\r\n。在其他环境中编写的文本
模式程序也会做类似的转换。
除了以文本模式读写文本文件,还能以二进制模式读写文本文件。如果
读写一个旧式MS-DOS文本文件,程序会看到文件中的\r 和\n 字符,不会发
生映射(图 13.1 演示了一些文本)。如果要编写旧式 Mac格式、MS-DOS格
949式或UNIX/Linux格式的文件模式程序,应该使用二进制模式,这样程序才能
确定实际的文件内容并执行相应的动作。
虽然C提供了二进制模式和文本模式,但是这两种模式的实现可以相
同。前面提到过,因为UNIX使用一种文件格式,这两种模式对于UNIX实现
而言完全相同。Linux也是如此
关键概念
C程序把输入看作是字节流,输入流来源于文件、输入设备(如键
盘),或者甚至是另一个程序的输出。类似地,C程序把输出也看作是字节
流,输出流的目的地可以是文件、视频显示等。
C 如何解释输入流或输出流取决于所使用的输入/输出函数。程序可以
不做任何改动地读取和存储字节,或者把字节依次解释成字符,随后可以把
这些字符解释成普通文本以用文本表示数字。类似地,对于输出,所使用的
函数决定了二进制值是被原样转移,还是被转换成文本或以文本表示数字。
如果要在不损失精度的前提下保存或恢复数值数据,请使用二进制模式以及
fread()和fwrite()函数。如果打算保存文本信息并创建能在普通文本编辑器查
看的文本,请使用文本模式和函数(如getc()和fprintf())。
要访问文件,必须创建文件指针(类型是FILE *)并把指针与特定文件
名相关联。随后的代码就可以使用这个指针(而不是文件名)来处理该文
件。
要重点理解C如何处理文件结尾。通常,用于读取文件的程序使用一个
循环读取输入,直至到达文件结尾。C 输入函数在读过文件结尾后才会检测
到文件结尾,这意味着应该在尝试读取之后立即判断是否是文件结尾。可以
使用13.2.4节中“设计范例”中的双文件输入模式。
本章小结
对于大多数C程序而言,写入文件和读取文件必不可少。为此,绝大对
数C实现都提供底层I/O和标准高级I/O。因为ANSI C库考虑到可移植性,包
含了标准I/O包,但是未提供底层I/O。
标准 I/O 包自动创建输入和输出缓冲区以加快数据传输。fopen()函数为
标准 I/O 打开一个文件,并创建一个用于存储文件和缓冲区信息的结构。
fopen()函数返回指向该结构的指针,其他函数可以使用该指针指定待处理的
文件。feof()和ferror()函数报告I/O操作失败的原因。
C把输入视为字节流。如果使用fread()函数,C把输入看作是二进制值
并将其储存在指定存储位置。如果使用fscanf()、getc()、fgets()或其他相关函
数,C则将每个字节看作是字符码。然后fscanf()和scanf()函数尝试把字符码
翻译成转换说明指定的其他类型。例如,输入一个值23,%f转换说明会把
23翻译成一个浮点值,%d转换说明会把23翻译成一个整数值,%s转换说明
则会把23储存为字符串。getc()和 fgetc()系列函数把输入作为字符码储存,
将其作为单独的字符保存在字符变量中或作为字符串储存在字符数组中。类
似地,fwrite()将二进制数据直接放入输出流,而其他输出函数把非字符数
据转换成用字符表示后才将其放入输出流。
ANSI C提供两种文件打开模式:二进制和文本。以二进制模式打开文
件时,可以逐字节读取文件;以文本模式打开文件时,会把文件内容从文本
的系统表示法映射为C表示法。对于UNIX和Linux系统,这两种模式完全相
同。
通常,输入函数getc()、fgets()、fscanf()和fread()都从文件开始处按顺序
读取文件。然而, fseek()和ftell()函数让程序可以随机访问文件中的任意位
置。fgetpos()和fsetpos()把类似的功能扩展至更大的文件。与文本模式相
比,二进制模式更容易进行随机访问。
(拓展)一些比较有意思的东西
不使用临时变量交换两个整数的值
(其实交换两个指针变量的值也可以)
#include <stdio.h>
int main() {
int a, b;
a = 11;
b = 99;
printf("交换之前 - \n a = %d, b = %d \n\n", a, b);
a = a + b; // ( 11 + 99 = 110) 此时 a 的变量为两数之和,b 未改变
b = a - b; // ( 110 - 99 = 11)
a = a - b; // ( 110 - 11 = 99)
printf("交换后 - \n a = %d, b = %d \n", a, b);
}
奇偶数判断(利用二进制)
奇偶数判断其实有个更简单高效的办法,我们的整数,在计算机中存储的都是二进制奇数的最后一位必是1,所以我们可以这样写:
#include <stdio.h>
int main()
{
int number;
printf("请输入一个整数: ");
scanf("%d", &number);
// 判断这个数最后一位是1这为奇数
if(number&1)
printf("%d 是奇数。", number);
else
printf("%d 是偶数。", number);
return 0;
}
接条件判断闰年
(四年一闰,百年不闰) || 四百年在闰年
#include <stdio.h>
int main()
{
int year;
printf("输入年份: ");
scanf("%d",&year);
// year = 400;
// (四年一闰,百年不闰) || 四百年在闰年
if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
{
printf("y\n");
}
else
{
printf("n\n");
}
return 0;
}
判断素数
质数(prime number)又称素数,有无限个。质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数,这样的数称为质数
(1)
#include <stdio.h>
int main()
{
int n, i, flag = 0;
printf("输入一个正整数: ");
scanf("%d",&n);
for(i=2; i<=n/2; ++i)
{
// 符合该条件不是素数
if(n%i==0)
{
flag=1;
break;
}
}
if (flag==0)
printf("%d 是素数",n);
else
printf("%d 不是素数",n);
return 0;
}
输出结果:
输入一个正整数: 29
29 是素数
(2)判断两个数之间的素数
#include <stdio.h>
int main()
{
int low, high, i, flag;
printf("输入两个整数: ");
scanf("%d %d", &low, &high);
printf("%d 与 %d 之间的素数为: ", low, high);
while (low < high)
{
flag = 0;
for(i = 2; i <= low/2; ++i)
{
if(low % i == 0)
{
flag = 1;
break;
}
}
if (flag == 0)
printf("%d ", low);
++low;
}
return 0;
}
输出结果:
输入两个整数: 100 200
100 与 200 之间的素数为: 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199