《C语言从入门到项目实战》:基础篇 1-9 章

C语言入门基础学习

C语言概述:(7.6)

每天两小时学习,首先这本书是基于一个控制台文本界面操作函数库mycon,该库是由书籍团队设计完成的。

功能:在字符界面下设置窗体长和宽,设置窗口标题,定位光标,显示光标,隐藏光标,设置字符
的前景色和背景色,延时,清屏,键盘接收,播放声音共10个函数。有了这些能干什么?

作用:借此平台能做到,轻松愉快地开发有声有色地字符界面游戏,动画和实用的小软件(本书已经实现了字符跑马灯,贪吃蛇游戏,空战游戏,赌马游戏,推箱子游戏,读者在此基础上可以设计实现更多的字符界面游戏,如俄罗斯方块,走迷宫,打字练习,各种字符动画广告等。)

C语言程序员主要做的事有两件:写自己的函数使用别人的函数

学会如何熟练地使用标准函数库(这个是跨越Windows和UNIX/Linux等操作系统平台的)

根据开发需要,学会下载安装配置并使用第三方函数库,如图形库,网络库,多线程库等,来提高自己的开发效率,进行各种企业级的实用化开发。

学习程序设计时最有效的方法不是对什么都刨根问底,把遇到的每一点都弄明白,而是应该先不求甚解,努力实践,把它做出来,然后琢磨为什么这么做。

组成C语言程序的若干个函数的代码在整个程序中的位置十分灵活,谁在前,谁在后均可以。

C语言严格区分大小写

程序的本质是模拟客观世界

编写C程序的过程中,推荐大量使用C函数库中的函数来完成需要的功能

例1-4 计算两个双精度浮点数的乘法运算

C语言本身并没有提供输入/输出的语句,它是通过相应的库函数来实现输入/输出的。最常用的输入库函数为scanf,输出库函数为printf 。f 是format的缩写,意思是格式化,对应就是格式化输入和输出。

scanf("格式控制字符串",接收输入的地址列表)printf("格式控制字符串",输出项列表)

例1-4

#include <stdio.h>
int main(void)
{
    double x1;
    double x2;
    printf("请输入第一个数:");
    scanf("%lf",&x1);  
    printf("请输入第二个数:");
    scanf("%lf",&x2);
    printf("%f * %f = %.2f\n",x1,x2,x1 * x2);
    return 0;
}

%lf 用于接收 double型
%.2f 用于控制输出项以十进制有符号实数的形式输出,前两位小数,不是保留两位小数。

例1-8 动画效果的HelloWorld程序

#include <stdio.h>
#include <windows.h>
int main(void)
{
	while(1){
	printf("H");
	Sleep(500);
	printf("e");
	Sleep(500);
	printf("l");
	Sleep(500);
	printf("l");
	Sleep(500);
	printf("o");
	Sleep(500);
	printf(",");
	Sleep(500);
	printf("W");
	Sleep(500);
	printf("o");
	Sleep(500);
	printf("r");
	Sleep(500);
	printf("l");
	Sleep(500);
	printf("d");
	Sleep(500);
	printf("!");
	Sleep(500);
	printf("\n");}
	return 0;
} 

有点类似flash动画逐帧出现的感觉,我挺喜欢这类动态的程序。
这一例子添加了 <windows.h> 头文件,调用了其中的库函数Sleep;对应百度百科链接: link.
在Linux下,sleep中的“s”不大写,sleep()单位为秒,usleep()里面的单位是微秒。
上述windows中使用的Sleep(500)时间理论500ms,实际用时超过500ms。

例1-9 动画效果的HelloWorld程序2

\b 退格键,键盘上对应键,叫做Backspace,概念是转义字符,对应百度百科链接链接: link.

输出这两个字符,就会让光标回到本行当前位置的前两个字符位置处,如果此时用printf函数输出一个字符串,那么新的字符串的其实输出位置就是刚才重新定位的光标位置。写出下面程序:

#include <stdio.h>
#include <windows.h>
int main(void)
{
	while(1){
	printf("H");//1
	Sleep(500);
	printf("e");
	Sleep(500);
	printf("l");
	Sleep(500);
	printf("l");
	Sleep(500);
	printf("o");
	Sleep(500);
	printf(",");
	Sleep(500);
	printf("W");
	Sleep(500);
	printf("o");
	Sleep(500);
	printf("r");
	Sleep(500);
	printf("l");
	Sleep(500);
	printf("d");
	Sleep(500);
	printf("!");//12
	Sleep(500);
	//下面加入退格键后
	printf("\b ");	//1 
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b ");
	Sleep(500);
	printf("\b\b \b");//12
	Sleep(500);
	}
	return 0;
} 

在源代码基础上添加一个while循环,让它一直循环。
//1 只有一个\b和一个空格,缺一不可。这是书上原文,那我不去想为什么,我直接去掉空格看现象。
在这里插入图片描述

去掉空格后从 l 开始了, 原因是格式化打印printf,按“ ”内的顺序执行,
第一个printf执行\b移动到 !后,第二个printf执行\b\b,往后继续移动二到 r 和 l之间,然后输出空格覆盖l,l消失
之所以后续都进行了\b\b两次退格,是因为打印一次空格覆盖内容光标向前移动一格。
基本原理如上,其实是通过 退格 输出空格覆盖 这样的逻辑来完成撤销操作。

课后练习

第一章 第7题 编写程序,让一只小鸟煽动翅膀从左到右飞过(小鸟可以交替用字符C或^表示,即可呈现小鸟煽动翅膀的效果)

首先我们看书籍作者给出的代码。

/**
 * 飞翔的小鸟 2019年5月5日
 */
#include <stdio.h>
#include <windows.h>

int main(void)
{
	printf("飞翔的小鸟:\n");

	printf("V\r");
	Sleep(100);

	printf(" ^\r");
	Sleep(100);

	printf("  V\r");
	Sleep(100);

	printf("   ^\r");
	Sleep(100);

	printf("    V\r");
	Sleep(100);

	printf("     ^\r");
	Sleep(100);

	printf("      V\r");
	Sleep(100);
 
	printf("       ^\r");
	Sleep(100);
	
	printf("        V\r");
	Sleep(100);

	printf("         ^\r");
	Sleep(100);

	printf("           V\r");
	Sleep(100);
 
	printf("            ^\r");
	Sleep(100);
	
	printf("             V\r");

	printf("\n");
}

从我的角度按道理应该是可以对此代码进行升级的,首先抓出一次显示飞翔操作。

	printf("V\r");
	Sleep(100);
	printf(" ^\r ");
	Sleep(100);

然后利用二维坐标轴的思想,让“小鸟”在平面有规律的移动。程序代码如下。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h> 
int main()
{
	int i,j;
	int x = 0;
	int y = 5;
	
	int velocity_x = 1;
	int velocity_y = 1;
	int left = 0;
	int right = 20;
	int top = 0;
	int bottom = 10;
	
	while (1)
	{
		x = x + velocity_x;
		y = y + velocity_y;
		
		system("cls");   // 清屏函数
		// 输出小鸟前的空行
		for(i=0;i<x;i++)
			printf("\n");
		for (j=0;j<y;j++)
			printf(" ");
			
		printf("V");//输出小鸟煽动翅膀 
		Sleep(100);
		printf("\b^");
		Sleep(100);
		printf("\n"); 
		
		Sleep(50);  // 等待若干毫秒
		
		if ((x==top)||(x==bottom))
			velocity_x = -velocity_x;
		if ((y==left)||(y==right))
			velocity_y = -velocity_y;		
	}
	return 0;
}

第一章 第8题 A 编写程序,实现两种不同风格的安装进度表示:一种是百分比进度指示(从10%到100%,每次增加10%,如果每次增加1%,则代码太长了);一种是温度计字符进度指示(■■■□□)

/**
 * 模拟安装进度条
 */
#include <stdio.h>
#include <windows.h>

int main(void)
{
	printf("00%%\r");
	Sleep(100);

	printf("10%%\r");
	Sleep(100);

	printf("20%%\r");
	Sleep(100);

	printf("30%%\r");
	Sleep(100);

	printf("40%%\r");
	Sleep(100);

	printf("50%%\r");
	Sleep(100);

	printf("60%%\r");
	Sleep(100);

	printf("70%%\r");
	Sleep(100);

	printf("80%%\r");
	Sleep(100);

	printf("90%%\r");
	Sleep(100);

	printf("100%%\r");
	Sleep(100);

	printf("\n");
	
	return 0;
}

第一章 第8题 B

/**
 * 模拟安装进度条
 */
#include <stdio.h>
#include <windows.h>

int main(void)
{
	printf("模拟安装进度条:\n");

	printf("□□□□□□□□□□0%%\r");

	printf("■□□□□□□□□□10%%\r");
	Sleep(200);

	printf("■■□□□□□□□□20%%\r");
	Sleep(200);

	printf("■■■□□□□□□□30%%\r");
	Sleep(200);

	printf("■■■■□□□□□□40%%\r");
	Sleep(200);

	printf("■■■■■□□□□□50%%\r");
	Sleep(200);

	printf("■■■■■■□□□□60%%\r");
	Sleep(200);

	printf("■■■■■■■□□□70%%\r");
	Sleep(200);

	printf("■■■■■■■■□□80%%\r");
	Sleep(200);

	printf("■■■■■■■■■□90%%\r");
	Sleep(200);

	printf("■■■■■■■■■■100%%\r");
	Sleep(200);

	printf("\n");
	
	return 0;
}

第一章 第9题 编写程序,实现10s倒计时。

/**
 * 模拟倒计时
 */
#include <stdio.h>
#include <windows.h>

int main(void)
{
	printf("10\r");
	Sleep(1000);

	printf("9 \r");
	Sleep(1000);

	printf("8 \r");
	Sleep(1000);

	printf("7 \r");
	Sleep(1000);

	printf("6 \r");
	Sleep(1000);

	printf("5 \r");
	Sleep(1000);

	printf("4 \r");
	Sleep(1000);

	printf("3 \r");
	Sleep(1000);

	printf("2 \r");
	Sleep(1000);

	printf("1 \r");
	Sleep(1000);

	printf("0 \r");
	Sleep(1000);
	
	return 0;
}

第一章 第10题 编写一个程序,查看当前计算机是否打开了135端口(请查询netstat命令和find命令的帮助,理解netstat-an|find“135“的含义,并且要特别注意:在字符串,即”和“间的若干字符中如果要使用”字符本身,则必须使用转义字符\“(斜杠加上一个”)来表示一个双引号本身。所谓的端口就是0-65535之间的一个整数,其实代表的是内存中某个网络程序的收发网络数据包的队列,也就是间接地代表了某个网络程序是否打开)。

/**
 * 编程查看是否打开135端口
 */
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	system("netstat -an | find \"135\"");

	system("pause>nul");
	
	return 0;
}

数据存储与运算:(7.7)

每天两小时学习,从我个人的角度来学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 理解数据的二进制存储。
  • 掌握C语言的基本数据类型。
  • 掌握变量与常量。
  • 掌握C语言的运算符及表达式。

洗脑内容

  • 计算机能且只能做两种事情:执行计算与保存计算结果。

章节内容

  • 定义变量使用内存,使用运算符进行计算
  • 本章只介绍基本类型,枚举类型和void类型。

基础知识

  • 数据在计算机内存中是以二进制的形式存储的,1个二进制数称为1位,也就是说最小单位:位;也称作 bit 比特
  • 用 位 作单位又嫌弃太小,又采用字节(Byte)作为计算机存储单位,1个字节等于8个比特
  • const定义符号常量,有类型,编译时也可以进行类型检查;对应百度百科链接: link.
  • 一个数据的类型决定了这个数据在内存中所占用空间的大小,以及它的值所采用的编码方式。例如带符号和不带符号的二进制数。
  • 程序中使用的变量名,函数名,标号等统称为标识符。
  • 标识符命名规则:第一个字符必须是字母或下划线,且只能由英文字母,数字,和下划线“_”组成。而且不能是C的关键字和保留标识符。
  • 标识符最好采用英文单词或其组合,切忌使用汉语拼音来命名,且言简意赅
  • C语言严格区分大小写
  • 有符号整型数据在内存中以补码的形式存储。
  • sizeof 运算符可测试数据占几个字节
  • 十进制输出整数 %d,八进制输出 %o,十六进制输出 %x 或 %X,如果要显示进制前缀,类似 %#x,%#X,添加 # 号即可。
  • int类型是最适应计算机系统架构的整数类型。原因:它具有和CPU寄存器相对应的空间大小和位格式。(这句话我在别的书也看过)
  • char 类型实际存储的是整数而不是字符。对应ASCII编码;百度百科链接: link.
  • 对于ascii码,通常记忆是 32 为空格’ ‘; 32 / 2 * 3=48 数字’0’;32 * 2 + 1=65 ‘A’ 32 * 3 + 1 = 97 ‘a’
  • float 精度 6位,表示小数点后6位,而不是保留到小数点后六位
  • 枚举类型可以定义一种新的数据类型,并列举所有的可能值。 百度百科链接: link.
  • 通常不去记运算符优先级,而是利用 () 达到你想要的顺序。 百度百科链接链接: link.
  • 算术运算符中 只有 % 运算符需要整数操作数,其它数据类型不设限。
  • 除法运算,除数为 0 ,程序崩溃,别瞎搞。
  • 逻辑表达式按照从左到右得顺序执行。
  • 显示强制类型转换运算符,目前除了申请空间之外很少用到,不过我还是记录了一道例题。

补充:整数类型可以使用一个或多个类型修饰符进行修饰。

  • signed
  • unsigned
  • short
  • long

补充:转义序列 百度百科链接: link.

例2-5 5种有符号整数类型所占字节数及数据范围

书籍配套源码基础上调整了打印格式后的代码如下:

#include <stdio.h>
#include <limits.h>  //该文件包含了CHAR_MIN、INT_MIN等宏
int main(void)
{   
    printf("\nsigned char所占字节数:%d,数据范围:[%d,%d]\n\n"
			,sizeof(signed char),SCHAR_MIN,SCHAR_MAX);	
    printf("short所占字节数:%d,数据范围:[%d,%d]\n\n"
			,sizeof(short),SHRT_MIN,SHRT_MAX); 
	printf("int所占字节数:%d,数据范围:[%d,%d]\n\n"
	        ,sizeof(int),INT_MIN,INT_MAX);
	printf("long所占字节数:%d,数据范围:[%ld,%ld]\n\n"
	        ,sizeof(long),LONG_MIN,LONG_MAX);
	printf("long long所占字节数:%d,数据范围:[%lld,%lld]\n\n"
	        ,sizeof(long long),LLONG_MIN,LLONG_MAX);	
	return 0;
}

输出结果比较正常。
在这里插入图片描述

例2-7 数据溢出示例

溢出行为是未定义的行为,C语言标准并未定义有符号类型的溢出规则。
所以面对这类问题,做到不溢出。

#include <stdio.h> 
#include <limits.h>
int main(void) 
{ 
	int a,b;       // 定义变量
	a =  INT_MAX ; // 给整型变量a赋一个最大值 (INT_MAX)
	b = a + 1;     // 最大值加上1后的结果赋给整型变量b	 
	printf("a = %d\nb = %d\n",a,b); 
	system("pause");
	return 0; 
}

结果很真实。
在这里插入图片描述

例2-8 浮点类型定义,初始化及输出

我对这个指数表示有点迷茫,感觉这样表示不够 real

#include <stdio.h> 
int main(void) 
{ 
	float x = 320.0f;
	double y = 2.14e9;
	long double z = 8.8L;
	
	printf("%f也可以写成%e\n",x,x);
	printf("y的值是 %e\n",y);
	printf("z的值是 %Lf\n",z);
	return 0; 
}

以前整天都是%d输出十进制整数,现在我的确应该多看看实数输出的例子。
在这里插入图片描述

例2-12 自增运算符编程实例

#include <stdio.h> 
int main(void) 
{ 	
    int a,b,c,d;
	a = 7;b = 7;
	c = a++;    //后缀形式:先返回a的值,再增1 
	d = ++b;    //前缀形式:先增1,再返回b值 
	printf("a=%d,b=%d,c=%d,d=%d\n",a,b,c,d);	
	return 0; 
}

个人理解:++a(前),运算前先+1;a++(后),先使用最后才+1;
出现这种运算符目的是为了减少运算指令,提高运算效率。
在这里插入图片描述

例2-16 sizeof运算符获取int空间大小的三种方式

个人角度第二种方式比较常用

#include <stdio.h>
int main(void)
{
	int a;
	printf("%d\n",sizeof(int));       //第1种方式
	printf("%d\n",sizeof(a));         //第2种方式
	printf("%d\n",sizeof a);           //第3种方式
   	return 0;
}

结果很正常。
在这里插入图片描述

例2-17 显式强制类型转换与自动类型转换示例

#include <stdio.h>
int main(void)
{
	double f = 9.14;      //f是双精度浮点类型变量 
    int n = (int) f ;         //把f显式强制转换成整数 
    f = n / 2 ;                            //整除结果是整数4,类型转换自动转换成双精度类型 
    printf("f = %f n = %d\n",f,n);   
   	f = (double) n / 2 ;               //把n显式强制转换成双精度类型 
    printf("f = %f n = %d\n",f,n);   
    return 0;
}

有趣的数据。
在这里插入图片描述

例2-18 条件运算符示例

条件运算符使用率极高,很喜欢用

#include <stdio.h>
int main(void)
{
	int x = 5,y = 7;
	int distance = x<y ? y-x : x-y;
	printf("distance = %d",distance);	
	return 0;
}

表达式1 ?表达式2 : 表达式3
表达式1 ?(真,就执行) :(假,执行这条)
在这里插入图片描述

课后练习

1.在C语言中为一个特定的数据分配内存时,程序员必须( A )。
A.定义一个数据类型的变量 B.定义一个值
C.声明一个特定数据类型的指针 D.以上都不是

2.若有以下定义:
char w; int x; float y;double z;
则表达式w*x+z-y结果为( D )类型。
A.float B.char C.int D.double

3.下列变量名中,哪些是合法的?哪些是不合法的?为什么?
This_little_pig 2_for_1_special latest things
The_$12_method number correct?
MineMineMine _this_is_ok _foo
答:
合法的变量名有:
This_little_pig number MineMineMine _this_is_ok _foo

4.为下面的变量指定最佳的变量类型。
(1)以米为单位的房间面积。
(2)一小时内通过学校门口的车辆数量。
答:
(1)double 或float
(2)int

5.设x = 3、y=4、z=6,表达式!(x>y)+(y!=z)||(x+y)&&(y-z)的结果是( B )。
A.0 B.1 C.-1 D.6

6.若s是int型变量,且s=6,则s%2+(s+1)%2表达式的值为( 1 )。若a是int型变量,则“(a=45,a2),a+6”表达式的值为( 26 )。若x和a均是int型变量,则执行表达式x = (a=4,6*2)后的x的值为( 12 )。

7.若已知a=10,b=15,c=1,则表达式 a*b&&c 的运算结果是( 1 )。

8.若已定义 int a=25,b=14,c=19,则三元运算符(a>b? c:b)所构成语句的执行结果是( 19 )。

9.设a、b、c为整型数,且a=2、b=3、c=4,则执行语句 a=16+(b++)-(++c);后,a的值是(28)*

10.设 a=5,b=6,c=7,d=8,m=2,n=2,执行(m=a>d) && (n=c>b)后n的值为( 2 )。

11.初始化值是 0.968 的双精度变量 a 的定义形式为( double a = 0.968; )。

12.“200<x≤800”的C语言表达式为( x>200 && x<=800 )。

13.若x为 int 类型变量,则执行以下程序段后的 x 值是( -60 )。
x=6; x+=x-=x*x;

14.设 x 为 int 型变量,判断 x 是偶数的表达式为( x % 2 == 0 )。

15.读程序写运行结果。

#include <stdio.h>

int main(void)
{
	int i = 5,s = 10;
	s += s-i;
	printf("i=%d,s=%d\n",i,s);
	
	return 0;
}

程序运行结果为:
i=5,s=15

16.读程序写运行结果。

#include <stdio.h>

int main(void)
{
	int a = 4,b = 5,c = 0,d;
	d = !a&&!b--||!c;
	printf("b = %d d = %d\n",b,d);
	
	return 0;
}

程序运行结果为:
b = 5 d = 1

17.一年大约有3.156×107s。编写一个程序,提示用户输入年龄,然后显示该年龄对应的秒数。
参考代码如下:

#include <stdio.h>
int main(void)
{
    const double year = 3.156e7; 
	
    int age;
    printf("请输入你的年龄:"); 
    scanf("%d",&age);
	
    printf("从你出生现现在已经过去了:%.2e秒\n",year*age); 

    return 0;
}

18. 编程实现以下任务:
(1)定义int类型变量x和y,并且x的初始值为10,y的初始值为20。
(2)定义char类型变量ch并初始化为’B’。
(3)将int类型变量x的值加5,从而更新其值。
(4)定义double类型变量z并初始化为50.3。
(5)将int类型变量x和y的值互换。
(6)输出变量x和表达式2*x+5-y的值。

参考代码如下:

#include <stdio.h>
int main(void) 
{
    int x = 10,y = 20;          //(1)
    char ch = 'B';              //(2)
    x += 5;                     //(3)
    double z = 50.3;            //(4)
    int t = x;                  //(5)
    x = y;
    y = t;
    printf("x = %d 2*x+5-y = %d\n",x,2*x+5-y);     //(6)
	
    return 0;
}

简单程序设计:(7.8)

每天两小时学习,从我个人的角度来学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 了解算法的概念及算法的描述方法。
  • 掌握几种简单语句的使用
  • 理解分支结构的含义
  • 掌握if语句和switch语句的使用
  • 能够灵活设计顺序结构,分支结构程序。

洗脑内容

  • 沃斯提出:数据结构+算法=程序;实际上,数据结构+算法+程序设计方法+语言工具;
  • 通常判断非0 为真 是0为假

math.h

  • double fabs(double x)求绝对值函数
  • double sqrt(double x)求平方根函数

开关语句

  • switch-case是专用于实现多支结构的选择语句,特点是各分支清晰而且直观。
switch
{
	case 常量: 语句; break;
	......
	case 常量; 语句; break; 
	(default: 语句;)
}
  • case后面的常量表达式的值,必须是整型,字符型或枚举类型。

课后练习

1.C语言中,逻辑“真”等价于( C )。
A.大于零的数 B.大于零的整数 C.非零的数 D.非零的整数

2.对如下程序,若用户输入为A,则输出结果为( B )。

int main(void)
{ 
    char ch;
    scanf("%c",&ch);
    ch=(ch>='A'&&ch<='Z')?(ch+32):ch;
    printf("%c\n",ch);
    return 0;
}

A.A B.32 C.a D.空格

3.下列表达式中能表示a 在0 到100 之间的是( B )。
A.a>0&a<100 B.!(a<0||a>100)
C.0<a<100 D. !(a>0&&a<100))

4.有如下程序,其输出结果是( C )。

int main(void)
{   int a=2,b=-1,c=2;
    if(a<b)
    if(b<0) c=0;
    else c++;
    printf("%d\n",c);
    return 0;
}

A.0 B.1 C.2 D.3

5.有如下程序段:

int main(void)
{
    int x =1, y=1 ;
    int m , n;
    m=n=1;
    switch (m)
    {   
        case 0 : x=x*2;
        case 1: {
            switch (n)
            {   case 1 : x=x*2;
                case 2 : y=y*2;break;
                case 3 : x++;

            }
          }
        case 2 : x++;y++;
        case 3 : x*=2;y*=2;break;
        default:x++;y++;
      }
    return 0;
}

执行完成后,x 和y 的值分别为( A )。
A.x=6 y=6 B.x=2 y=1 C.x=2 y=2 D.x=7 y=7

6.C语言的switch语句中,case后为( B )。
A.只能为常量
B.只能为常量或常量表达式
C.可为常量及表达式或有确定值的变量及表达式
D.可为任何量或表达式

7.若执行以下程序时从键盘上输入9,则输出结果是( B )。

int main(void)
{   
	int n;
	scanf("%d",&n);
	if(n++<10)   printf("%d\n",n);
	else         printf("%d\n",n--);
	return 0;
}

A.11 B.10 C.9 D.8

8.if语句的基本形式是if(表达式)语句,以下关于“表达式”值的叙述中正确的是( D )。
A.必须是逻辑值 B.必须是整数值
C.必须是正数 D.可以是任意合法的数值

9.编程实现少量数排序问题:输入4个任意整数,按从小到大的顺序输出。
参考代码如下:

#include <stdio.h>
int main(void)
{
	int a,b,c,d;
	
	scanf("%d %d %d %d",&a,&b,&c,&d);	
	if(a>b)
	{
		int t;
		t = a;
		a = b;
		b = t;
	}
	if(b>c)
	{
		int t;
		t = b;
		b = c;
		c = t;
	}
	if(c>d)
	{
		int t;
		t = c;
		c = d;
		d = t;
	}
	if(a>b)
	{
		int t;
		t = a;
		a = b;
		b = t;
	}
	if(b>c)
	{
		int t;
		t = b;
		b = c;
		c = t;
	}
	if(a>b)
	{
		int t;
		t = a;
		a = b;
		b = t;
	}
	printf("%d %d %d %d \n",a,b,c,d);
	return 0;
}

10.计算邮资:根据邮件的重量和用户是否选择加急计算邮费。计算规则:重量在1000克以内(包括1000克),基本费8元;超过1000克的部分,每500克加收超重费4元,不足500克部分按500克计算;如果用户选择加急,多收5元。
输入一行,包含整数和一个字符,以一个空格分开,分别表示重量(单位为克)和是否加急。如果字符是y,说明选择加急;如果字符是n,说明不加急。
输出一行,包含一个整数,表示邮费。
参考代码如下:

#include <stdio.h>
int main()
{
	int x,y;
	char yn;
	scanf("%d %c",&x,&yn);
	
	if(x<=1000) y=8;
	else 
	{
		y=8+(x-1000)/500*4;
		if((x-1000)%500!=0) y+=4;
	}
	if(yn=='y') y+=5;
	printf("%d",y);
	return 0;
}

11.从键盘接收一个字符,如果是字母,输出其对应的ASCII码;如果是数字,按原样输出,否则给出提示信息:输入错误!。
参考代码如下:

#include <stdio.h>
int main(void)
{
	char ch;
	
	scanf("%c",&ch);
	if(ch>='a'&&ch<='z' || ch>='A'&&ch<='Z')
		printf("%d\n",ch);
	else if(ch>='0'&&ch<='9')
		printf("%c\n",ch);
	else 
		printf("输入错误!\n");
	return 0;
}

12.输入一个字符,判断它是否是小写字母,如果是小写字母,则将它转换成大写字母;如果不是,则不转换,然后输出所得到的字符。
参考代码如下:

#include <stdio.h>
int main(void)
{
	char ch;
	
	scanf("%c",&ch);
	if(ch>='a'&&ch<='z')
		ch = ch-'a'+'A'; //ch = ch-32;
	printf("%c\n",ch);
	
	return 0;
}

13.判断输入的正整数是否既是5的倍数又是7的倍数,若是,则输出yes,否则输出no。
参考代码如下:

#include <stdio.h>
int main(void)
{
	int m;
	
	scanf("%d",&m);
	if(m%5==0 && m%7==0)
		printf("yes\n");
	else 
		printf("no\n");	
	return 0;
}

循环:(7.11)

每天两小时学习,从我个人的角度来学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 掌握循环结构的本质。
  • 掌握while,do…while及for循环语句的语法格式,执行流程及用法。
  • 掌握循环中断及结束本次循环的方法。
  • 理解循环嵌套结构。
  • 能够灵活设计循环结构程序。

内容

  • goto语句;(汇编学过,没想到C也可以用)百度百科链接 link.
  • 数值计算时,有必要对结果的位数进行推算。变量就不容易出现溢出错误。所以的确有必要对变量的取值范围进行一个记忆,简单记忆那便是要对变量所占内存字节数进行记忆。
  • 枚举法:尝试每一种情况;思想:将问题的所有可能答案一一列举,然后根据条件判断此答案是否符合条件,保留合适的,丢弃不合适的。特点:算法简单,容易理解,但运算量较大。
  • 递推问题:迭代是循环,重复执行一系列运算步骤。

洗脑内容

  • break语句只能用于循环语句和switch语句中。
  • continue语句能够提前终止本次循环,继续下次循环。

课后练习

1.下面的for语句是( C )。
for(x=0,y=10;(y>0)&&(x<4);x++,y-- );
A.无限循环 B.循环次数不定 C.循环执行4次 D.循环执行3次

2.已知int i=1; 执行语句while (i++<4);后,变量i的值为( C )。
A.3 B.4 C.5 D.6

3.求取满足式 12+22+32+ … +n2 ≤1000的n,正确的语句是( A )。
A.for(i=1,s=0;(s=s+ii)<=1000;n=i++);
B.for(i=1,s=0;(s=s+i
i)<=1000;n=++i);
C.for(i=1,s=0;(s=s+i*++i)<=1000;n=i);
D.for(i=1,s=0;(s=s+i*i++)<=1000;n=i);

4.下列循环语句中有语法错误的是( D )。
A.while(x=y) 5; B.while(0) ;
C.do 2; while(xb); D.do x++ while(x10) ;

5.从键盘上输入:ABCdef<回车>,程序的执行结果为( acbDEF )。

#include <stdio.h>
int main (void)
{ 
	char  ch;
	while ((ch=getchar())!='\n')
	{
		if(ch>='A' && ch<='Z')	  
			ch = ch + 32;
		else if(ch>='a' && ch<='z')
			ch = ch-32;
		printf("%c",ch);
	}
	printf("\n");
	return 0;
}

6.下面程序的执行结果为( )。

#include <stdio.h>
int main(void)
{
	int  x = 1,y = 0;
	switch  (x)
	{
		case 1:
		switch (y)
		{ 	
			case 0: printf("**1**\n"); break;
			case 1: printf("**2**\n"); break;
		}
		case 2: printf("**3**\n");
	} 
	return 0;
}

运行结果为:
1
3
7.写出以下程序输入2,3<回车>时的运行结果,并给出程序的功能。

#include <stdio.h>
int main(void)
{
	long term=0,sum=0;
  	int a,i,n;
  	printf("Input a,n:");
  	scanf("%d,%d",&a,&n);
  	for(i=1;i<=n;i++)
  	{ 
	    term=term*10+a;
    	    sum += term;
  	}
    printf("sum=%ld\n",sum);
    return 0;
}
程序的功能的为:求a+aa+...+a...a(最后一项为n位)的和。

8.最高的分数:孙老师讲授的《计算导论》这门课期中考试刚刚结束,他想知道最高的分数是多少。因为人数比较多,他觉得这件事情交给计算机来做比较方便。你能帮孙老师解决这个问题吗?
输入两行,第一行为整数n(1≤n < 100),表示参加这次考试的人数,第二行是这n个学生的成绩,相邻两个数之间用单个空格隔开。所有成绩均为0到100之间的整数。输出一个整数,即最高的成绩。
参考代码如下:

#include <stdio.h>

int main(void)
{
	int n,x,max;	
	
	scanf("%d",&n);
	max=-1;
	while(n--)
	{
		cin>>x;
		if(x>max) max=x;
	}	
	printf("%d\n",max);
	return 0;
}

9.满足条件的数累加:将正整数 m 和 n 之间(包括 m 和 n)能被 17 整除的数累加。其中,0 < m < n < 1000。
输入一行,包含两个整数m和n,中间以一个空格间隔。输出一行,包含一个整数,表示累加的结果。
参考代码如下:

#include <stdio.h>

int main(void)
{
	int m,n,s;	
	
	scanf("%d %d",&m,&n);
	s=0;
	int i=m;
	while(i<=n)
	{
		if(i%17==0)	s+=i;
		i++; 	
	}
	printf("%d\n",s);
	return 0;
}

10.满足条件的3位数:编写程序,按从小到大的顺序寻找同时符合条件1和2的所有3位数,条件为:(1)该数为完全平方数;(2)该数至少有两位数字相同。如,100同时满足上面两个条件。
输入一个数n,n的大小不超过实际满足条件的3位数的个数。输出第n个满足条件的3位数(升序)。
参考代码如下:

int a[1000];
int main(void)
{
	int n,k=0,j=10,i;
	
	for(i=j*j;i<1000;j++,i=j*j)
	{
		if(i%10==i/10%10||i%10==i/100||i/100==i/10%10)
		{
			a[++k]=i;
		} 		
			
	}
	scanf("%d",&n);
	printf("%d\n",a[n]);
	
	return 0;
}

11.人口增长问题:我国现有x亿人口,按照每年0.1%的增长速度,n年后将有多少人?
输入一行,包含两个整数x和n,分别是人口基数和年数,以单个空格分隔。输出最后的人口数,以亿为单位,保留到小数点后四位(1≤x≤100, 1≤n≤100)。
参考代码如下:

#include <stdio.h>

int main(void)
{
	int x,n;
	double s;	
	
	scanf("%d %d",&x,&n);
	s=x;		
	while(n--)
	{
		s=s*(1+0.1/100);		
	}
	
	printf("%.4f\n",s);
	return 0;
}

12.银行利息:农夫约翰在去年赚了一大笔钱!他想要把这些钱用于投资,并对自己能得到多少收益感到好奇。已知投资的复合年利率为R(0到20之间的整数)。约翰现有总值为M的钱(100到1,000,000之间的整数)。他清楚地知道自己要投资Y年(范围0到400)。请帮助他计算最终他会有多少钱,并输出它的整数部分。数据保证输出结果在32位有符号整数范围内。
输入一行包含三个整数R,M,Y,相邻两个整数之间用单个空格隔开。输出一个整数,即约翰最终拥有多少钱(整数部分)。
参考代码如下:

#include <stdio.h>
int main(void)
{
	int R,M,Y;
	double s;	
	
	scanf("%d %d %d",&R,&M,&Y);
	s=M;		
	while(Y--)
	{
		s=s*(1+1.0*R/100);		
	}	
	printf("%d\n",(int)s);
	return 0;
}

13.买房子:某程序员开始工作,年薪N万,他希望在中关村公馆买一套60平米的房子,现在价格是200万,假设房子价格以每年百分之K增长,并且该程序员未来年薪不变,且不吃不喝,不用缴税,每年所得N万全都积攒起来,问第几年能够买下这套房子(第一年年薪N万,房价200万)?
输入一行,包含两个正整数N(10 ≤ N ≤ 50), K(1≤ K ≤ 20),中间用单个空格隔开。输出如果在第20年或者之前就能买下这套房子,则输出一个整数M,表示最早需要在第M年能买下,否则输出Impossible。
参考代码如下:

#include <stdio.h>
int main(void)
{
	int N,k;
	scanf("%d %d",&N,&k);
	
	int i=1;
	double s,hprice=200;
	while(i<=20&&s<hprice)
	{
		i++;
		s=N*i;
		hprice*=(1+1.0*k/100);
		
	}
	if(i<=20) 
		printf("%d",i);
	else 
		printf("Impossible");
	return 0;
}

14.运动场有4位篮球运动员正在进行篮球训练,他们分别来自火箭队,湖人队,森林狼队,凯尔特人队。一位记者问:“你们都来自那个队伍?”
B说:“C球员是凯尔特人队。”
C说:“D球员不是森林狼队。”
A说:“B球员不是火箭队的。”
D说:“他们三个人中,只有凯尔特人队的队员说了实话。”
D的话是可信的,那么他们分别来自那个球队呢?

#include <stdio.h>
void printterm(int n);
int main(void)
{
	// hj,hr,sl,ke分别用1,2,3,4代表 
	int A,B,C,D;
	int la,lb,lc,ld;
	
	for(A=1;A<=4;A++)
	{
	    for(B=1;B<=4;B++)
	    {	
		for(C=1;C<=4;C++)
		{
	             for(D=1;D<=4;D++)
		    {	
		        if(B!=A&&C!=A&&C!=B&&D!=A && D!=B && D!=C)
			{
		             lb=(C==4);
			    lc=(D!=3);
			    la=(B!=1);
			   if((A==4&&la&&!lb&&!lc) + (B==4&&lb&&!la&&!lc) + C==4&&lc&&!la&&!lb)==1)
			    {
			        	printf("A来自"); printterm(A);
			        	printf("B来自"); printterm(B);
			       	printf("C来自"); printterm(C);
			        	printf("D来自"); printterm(D);		            	        		   
 }
			} 	
		    }
		}
	    }
	} 
}
void printterm(int n)
{
	switch(n)
	{
		case 1: printf("火箭队\n"); break;
		case 2: printf("湖人队\n"); break;
		case 3: printf("森林狼队\n"); break;
		case 4: printf("凯尔特人队\n"); break;
	}
}

函数:(7.11)

每天两小时学习,个人的角度学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 掌握函数的定义,声明,调用及返回
  • 掌握函数形参与实参
  • 掌握函数的嵌套调用与递归调用
  • 理解变量的生存期与作用域
  • 掌握编译预处理及模块化编译链接

内容

  • 递归:程序调用自身的编程技巧;
  • 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算;百度百科link.
  • 变量的生存期是指变量在什么时间段内存在,变量的作用域是指变量在哪些代码块中能够访问。
  • #ifdef 和 #ifndef;常见的头文件包含如下代码:
#ifndef _XXX_H_
#define _XXX_H_
头文件内容
#endif

含义为:如果已经包含了头文件XXX.h的内容,则不再包含该头文件,否则包含该头文件的内容

  • 特殊符号处理,这里笔者只记录了一个。
    __FILE__:包含当前程序文件名的字符串。 (这个我尝试过,挺有用的)
  • 模块化编译链接;简述就是自己写库函数头文件,再调用。

洗脑内容

  • 对于一个不加return语句的“函数”,编译器会自动加上。

  • 程序中有些变量,在程序加载到内存中时并不分配存储空间,而是到定义它的函数被调用执行时才会临时分配存储空间,并且一旦该函数执行完毕返回到被调用处,这些变量在内存中分配的存储空间也将被回收,即它们将不复存在。这种变量称为非静态局部变量或自动变量,用关键字auto修饰。一个局部变量,如果没有用static修饰,则它自动为auto的。无论是否用auto修饰,自动变量或非静态局部变量的生存期为函数调用时到函数返回时的这个时间段,在此之前和之后它们都不存在。

  • 全局变量,生存期为整个程序的生存期,作用域一般为定义处到它所在的文件结束。

  • 将一些没必要设置成全局变量的变量,实际上浪费空间。

  • 如果全局变量加以static关键字修饰,则为静态全局变量,作用域局限在定义它的文件内;
    好处是:1.不会被其他文件访问和修改;2/其他文件中可以使用相同名称的变量,不会发生冲突。

  • static关键字修饰函数也是如此,加上后只能限定在定义它的文件内被调用
    好处是:1.不会被其他文件访问; 2.其他文件中可以使用相同名称的函数,不会发生冲突。

  • 上文总结:(函数内的变量而言)
    局部变量生存期最短,用时活,用完死。
    静态局部变量到数第二,生存期为整个程序的生命周期,但限制了作用域;

  • 全局变量和静态局部变量如果没有被初始化,编译器自动将其初始化为0;

  • 非静态局部变量如果没有被初始化,则它的值是随机的。

  • 允许全局变量和局部变量同名,但在局部变量的作用域内访问的是局部变量,此时全局变量被屏蔽。

  • C语言由源代码生成可执行文件的各阶段如下:C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件。通常将C源程序->编译预处理->编译->优化程序->汇编程序称为编译阶段;而把链接程序生成可执行文件这一阶段称为链接阶段。

  • 伪指令(以#开头的指令)

  • 预处理过程会删除程序中的注释和多余的空白字符

  • <>尖括号括起来的头文件,这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。

  • “ ”双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。

  • 宏定义, #define 宏标识符 宏标识符代表的特定的字符串;使用宏由如下好处:
    1.使用方便 2.可读性强 3.容易修改
    所以宏标识符要尽可能取得见名知义,增强可读性;宏定义尽量集中到源程序得最前面,便于修改。

  • 宏定义可以嵌套,但长表达式必须加括号,不然难以保证宏和参数得完整性。

错误例子
#define CUBE(x) x * x * x    

正确例子
#define CUBE(x) ((x) * (x) * (x)) 

这是因为带参宏的定义使得后续必须如此定义,否则使用不当,会出现一些无法预估的错误
  • 宏调用比函数调用的执行效率高,但由于带参数的宏很容易发生副作用,所以并不推荐带参数的宏大量使用。
  • #undef取消宏定义,取消后不能再调用,否则出错。

例5-22 条件编译指令的用法

  • 先看现象

程序运行时,定义了DEBUG符号,所以处于自定义的调试状态;
在循环中会输出每一步的 i 和 sum 的值,暂停并提示按任意键继续;
这样就可以清楚地看到每一步循环地执行状态。达到调试程序的目的。
因为最后会学习八种常见排序,所以笔者会在演示排序中利用该指令。
百度百科链接:link.
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

#define DEBUG

int main(void)
{
	int i;
	int sum = 0;

	//#undef DEBUG
	for (i = 1; i <= 10; i++)
	{
	    #ifdef DEBUG
	    printf("i = %d, sum = %d", i, sum);
	    system("pause");
	    #endif
        sum = sum + i;
    }
	printf("Sum is : %d\n", sum);

	return 0;
}

课后练习

5-11.
利用递归从前往后输出整数的各位

#include <stdio.h>

void p(int);

int main(void)
{
	int n;

	printf("请输入一个整数:");
	scanf("%d", &n);

	p(n);
}

void p(int n)
{
	if (n / 10 == 0)
	{
		printf("%d ", n);
		return;
	}
	p(n / 10);
	printf("%d ", n % 10);
}

5-12.
利用非递归和递归进行因式分解

#include <stdio.h>

void factor(int);
void factorr(int);

int main(void)
{
	int n;

	printf("请输入待分解的整数:");
	scanf("%d", &n);
	
	printf("\n非递归分解:%d = ", n);
	factor(n);

	printf("\n递归分解:%d = ", n);
	factorr(n);

	return 0;
}

//非递归分解方法

void factor(int n)
{
	int i = 2;

	if (n < 0)
	{
		n = -n;
	}

	while (n >= 2)
	{
		for (i = 2; i <= n; i++)
		{
			if (n % i == 0)
			{
				if (n != i)
				{
					printf("%d * ", i);
				}
				else
				{
					printf("%d", i);
				}
				n = n / i;
				i = 2;
				break;
			}
		}
	}
}

//递归分解方法

void factorr(int n)
{
	int i;

	if (n < 0)
	{
		n = -n;
	}

	for (i = 2; i <= n / 2; i++)
	{
		if (n % i == 0)
		{
			printf("%d * ", i);
			factorr(n / i);
			return;
		}
	}
	printf("%d", n);
}

数组:(7.12)

基础知识

每天两小时学习,个人的角度学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 理解数组的概念
  • 初步掌握C语言中数组元素的存储特点
  • 初步掌握一维,二维数组的定义,初始化,引用及与函数的关系。
  • 初步掌握字符数组处理字符串的方法
  • 初步领悟数组作为顺序存储数据的工具。

主要内容
一维数组和二维数组的定义,初始化,元素的引用,简单介绍数组与函数的关系。
初步了解数组的存储;并将字符串的输入/输出,常见操作单独展开了简单的介绍。
介绍了选择排序,顺序查找的算法。

------------------------------------------数组的定义-----------------------------------------------

  • 定义一个数组,需要指定元素的类型和元素的数量,如下为一般格式:
元素类型名 数组名[数组长度];
注意1[]中必须是大于0的整数常量,不能用变量指定。(gcc编译能通过,但实际少用)
注意2:在定义处的[]中存放表示数组大小的 常量 表达式;
注意3:在非定义处[]中存放表示元素下标的值。(可以是常量或变量,或整型表达式)
注意4:定义后,数组名是常量,值 为第一个元素的首地址,通常称为数组的基地址。
注意5:一维数组的总字节数=sizeof(元素类型名) * 数组长度
注意6:数组下标从 0 开始,毋庸置疑。
注意7:无论测试还是书籍中,C99中可以存在int n=3;int a[n];这也是注意1后括号笔者所述内容;
接上7:但仍然建议用常量作为数组长度。(笔者认为C语言还是相信经验丰富的前辈比较好!)
  • 当定义了int array[10];后,0-9 是有效下标,而a[10]无效,但编译器不会帮你检查下标越界问题。
  • 查看linux系统位数 uname -i
  • 系统为数组申请 数组长度 * sizeof(元素类型名) 个连续的内存单元,用来存储数组元素;
    内存的大小取决于数组的类型及数组长度。
    如果第一个元素所占的存储单元首地址为addr,
    则第二个元素所占的存储单元首地址为 addr+sizeof(元素类型名)
    同理,下标为 i 的元素的首地址应该为 addr+ i * sizeof(元素类型名)

------------------------------------------初始化一维数组-----------------------------------------------

  • 如果数组在定义时元素未初始化(全局数组除外,全局变量未初始化编译器自动补值为0),或之后也没有给元素赋值,则这些元素的值就是垃圾值。
1.可以逐个元素赋值(循环语句),效率低下
2.完全初始化: int a[5] = {12345};
3.省略长度的初始化:int a[]={1,2,3,4,5};(与完全初始化等阶)
4.不完全初始化:int a[5] = {1,2,3}; (未被初始化元素的值为0), int a[5]={0};(数组所有元素值均为0)

注意上述初始化 2 和 3, 括号内容(与完全初始化等阶),记住这句话,因为在sizeof(a[])的时候时按照初始化的元素个数*类型来计算的。而存入该数组元素的上限也与初始化相关。
举例: int a[]={0},只占4个字节, 且存入数值时只有a[0]有效, (已经测试过)

  • 完全初始化后,可以利用 sizeof(a) / sizeof(int) 计算数组中元素个数。
  • 错误1:int a[5]={0},b[5]; b = a; 不可以把数组a 的值全部赋值给数组b
    数组名代表的是数组第一个元素的首地址,是常量,即数组名不能作为左值(不能出现在赋值号左边)

------------------------------------------通过函数访问一维数组-----------------------------------------------

  • 如下三种无返回值方式,这三种方式结果一样;
    因为每种方式都告诉编译器要接收一个数组的首地址。
    对应的实参均可以为 (数组名,元素个数)
1.形参时一个已定义大小的数组的函数原型:
void MyFunction(int arr[10],int size);

2.形参时一个未定义大小的数组的函数原型:
void MyFunction(int arr[],int size); (初始化数组时说到,省略长度的初始化与完全初始化等阶)

3.形参是一个指针的函数原型
void MyFunction(int *arr,int size);

------------------------------------------一维数组的简单应用-----------------------------------------------

  • 一维数组可用于描述线性事物,可以处理一个行向量或一个列向量的数据。(排序和查找 常用)
    1.求最大数及位置 (对于一组没有给定取值范围的数据,不能初始假设最大值为0.)
    2.排序
    3.查找 (最常用的是“顺序查找”,但有更高效的)
    (如上内容举例暂时省略,先记住用途)

------------------------------------------二维数组定义-----------------------------------------------

  • 二维数组常用来表示一个矩阵;
  • 定义格式:类型说明符 数组名[常量表达式1][常量表达式2]
    常量表达式1表示 第一维的长度(行数),常量表达式2表示第二维的长度(列数)。(行下标,列下标)
    元素数 = 常量表达式1 * 常量表达式2所占内存 = 元素数 * 元素类型
    比如 int a[3][4];12个元素,48字节;
    接上例: a[3][4]分解为三个一维数组,其数组名为a[0],a[1],a[2]; 每个一维数组都有4个元素
    一维数组 a[0]的元素为:a[0][0], a[0][1], a[0][2], a[0][3] (其它两个类似)
  • 数组的元素在内存中是连续存放的。而C语言的二维数组是按行优先存储的。先a[0],再a[1]…
  • 若二维数组是 int a[M][N],即 M 行 N 列,则元素a[ i ][ j ]的内存单元的首地址为 a + (i * N + j) * 4

------------------------------------------二维数组引用-----------------------------------------------

  • 与一维元素的引用方式相同,元素只能单个引用不能整体引用,引用时只需再数组名后加上其下标。
  • 对于二维数组元素的输入及输出通常使用双重循环实现,外层循环控制行下标,内层循环控制列下标。

------------------------------------------二维数组初始化-----------------------------------------------

  • 二维数组可按行分段赋值,也可按行连续赋值。
1.按行分段赋值
int a[2][3] = {{80,76,92},{61,65,71}};

2.按行连续赋值可写成:
int a[2][3] = {90,76,92,61,65,71};

如上两种赋初值的结果是完全相同的

3.可以只对部分元素赋初值,未赋初值的元素自动取0值。(数组的初始化讲过了)

4.如果对全部元素赋初值,则第一维的长度可以不给出。
例如:int a[3][3]={1,2,3,4,5,6,7,8,9};
替换:int a[ ][3]={1,2,3,4,5,6,7,8,9};
但是不能写成:int a[3][]; 笔者认为这个错误是,编译器无法识别数组的边界导致的。

------------------------------------------二维数组应用-----------------------------------------------

  • 二维数组作为函数的形参定义形式为:
    类型标识符 数组名[一维长度][二维长度] 或 类型标识符 数组名[ ][二维长度]

------------------------------------------字 符 串-----------------------------------------------

  • 只有以’\0’(空字符)结尾的字符数组才是C字符串,否则只是一般的C字符数组。
    而’\0’是 ASCII 值为 0 的不显示字符。
  • 字符串的初始化可以使用“=”进行初始化,如下:
char str1[] = "abcde";
char* str3 = "abcdefg"; 
错误案例如下:
str2[20];   str2 = "xyz"; 错因:非定义处不能利用 “ = ” 对C字符串进行赋值,因为数组名是常量。
  • 若事先知道字符串最多有 N 个字符,则存放字符串的数组长度至少为 N+1;
  • C中字符串 常用的几个简单函数:
int strlen(const char * str)  返回字符串str长度
char* strcat(char* dest,const char* src) 将字符串src附加到dest后,返回dest;
char* strcpy(char* dest,const char* src) 将字符串src复制到dest中,返回dest;
int strcmp(const char* str1,const char* str2) 字符串比较函数, 相等返回0,前大返回正,后大返回负;

洗脑内容

  • 一维数组可以模拟线性事物
  • 二维数组可以模拟平面事物
  • 三维数组可模拟立体事物,利用数组可以模拟现实世界。(之前知识匮乏,只能在想象中感受这点。)

例6-2 将一组整数逆置:输入n(n<=10)及n个整数,将这组整数逆置并输出。

源代码给出的思路是,正常存入,再逆置;
如果只是完成需求,可以逆置存入,最后打印;

#include <stdio.h>

const int N = 10;

int main(void)
{
	int a[N];              				//定义有N个整数元素的数组a
	int n;
	int i; 
	scanf("%d", &n);       				//输入整数个数
	for(i=0; i < n; i++)   		//输入数组a的n个元素 
		scanf("%d", &a[i]);  			//输入元素a[i], 注意它是a的第i+1个元素 
	for(i=0;i < n/2;i++)  			//逆置,距离首(a[0])和尾(a[n-1])相等的元素交换其值
	{
        int t;
        t = a[i];  a[i] = a[n-1-i];  a[n-1-i] = t;	//交换a[i]与a[n-1-i]的值 
	}
	for(i = 0;i < n;i++)    			//输出a的n个元素 
	    printf("%d  ",a[i]);                   
	
	return 0;
}

现象:
(笔者认为可以在存入第n个数据时提醒)
在这里插入图片描述

例6-3

题目:已知N个学生(N<=10)的年龄,输出平均年龄以及大于平均年龄的学生数/

书籍本身给出的代码是.cpp文件,是C++的逻辑自下而上,面向对象。
而C语言的逻辑自顶而下,面向过程。
不管顺序如何,并不影响笔者学习编程的思想。

分析:
1.N个学生年龄已知,构建数组存储N个学生年龄。(实际这部分内容是输入获取的)
2.求平均值。(double类型精度最高)
3.输出大于平均年龄的学生数量 。(比较)

#include <stdio.h>

int Input(int arr[]);                  		//输入数组arr的元素,返回元素个数 
double GetAverage(int arr[], int size);   	//返回数组中size个元素的平均值 
int Count(int arr[], int size, double ave);  	//返回数组中size个元素中大于ave的元素个数 
const int N=10;

int main (void)
{
    int age[N], n, k;     					//分别存储年龄、实际学生数,以及大于平均年龄的学生数
    double ave;                				//存放学生的平均年龄	

    n=Input(age);              				//输入学生年龄,并求学生数n
    ave = GetAverage(age, n);    			//求n个学生平均年龄
    k=Count(age, n, ave);        				//求大于平均年龄的学生数
    printf("平均年龄是:%.1f\n大于平均年龄的学生数是:%d\n", ave, k); 

    return 0;
}
int Input(int arr[])
{
    int n = 0, a;               				//存储学生个数及学生的年龄
	
    while(1)
    {
	    scanf("%d", &a);
	    if(a <= 0 ) break;   				//年龄不合法,结束输入 
	    arr[n++] = a;     					//将a赋值给下标为n的元素,同时学生数加1
    }

    return n;	
} 
double GetAverage(int arr[], int size)
{
    int  i;
    double ave, sum = 0;
		
    for (i = 0; i < size; ++i)
    {
	    sum += arr[i];
    }	
    ave = sum / size;
	
    return ave;
}
int Count(int arr[], int size, double ave)
{
	int k = 0, i;	            				//存储值大于ave的元素个数
	for(i = 0; i < size; i++)
	{
		if(arr[i] > ave)     k++;      		//元素值大于ave时,k增加1 
	}

	return k;                      			//返回k
}

例6-8 字符串的简单加密

  • gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,以回车结束读取;
    gets函数 百度百科链接link.
  • 常用的scanf 是遇到空格结束读取。
#include <stdio.h>
#include <string.h>

int main(void)
{
	char text[80];
	int len;              					//存放串text的长度 	
	printf("输入要加密的字符串:");
	gets(text);           					//输入可能含有空格的字符串
	len = strlen(text);     					//求字符串的长度
	int i;
	for(i = 0; i < len; i++)
	{//加密处理 
		if(text[i] >= 'a'&&text[i] < 'z' || text[i] >= 'A' && text[i] < 'Z') 
			text[i]++;
		else if(text[i] == 'z') 
			text[i] = 'a';
		else if(text[i] == 'Z') 
			text[i] = 'A';
	} 	
	printf("加密后字符串为:    %s\n", text);
	
	return 0;	 
}

例6-9 系统登陆验证

这代码笔者也写过类似的,但水平太低就不放出来了

分析:假设用户id由不超过30个任意字符组成,密码由不超过20个任意字符组成,目前系统只有一个用户id是wyp,对应密码是123456,使用gets(str)函数,输入id和密码;然后使用函数strcmp判断id和密码是否相同。输入三次均没有输入正确则退出登陆界面。

#include <stdio.h>
#include <string.h>

int main(void)
{//正确的id号和密码分别为:"wyp", "123456" 
	char id[31], psw[21];  			//存放用户输出的id和密码
	int n=3, f=0;  					//分别存放输入id、密码的次数,以及是否输入正确,0表示不正确 
	do
	{
		n--;
		printf("输入你的id:");
		gets(id);
		printf("输入你的密码:");
		gets(psw);
		if(strcmp(id,"wyp") == 0 && strcmp(psw,"123456") == 0) 
		{ //验证id和psw与系统设定的用户和密码是否完全一致 
				printf("welcome!\n");
			f = 1;          				//标志输入正确 
			break;
		}
		else
		{
			if(n > 0)
			   printf("你还有%d机会输入id和密码哟!\n", n);
		}
					
	}while(n > 0);
	if(f == 0)              			//三次输入均不正确
		printf("sorry! bye bye!\n");	
	
	return 0;
}

课后练习

1.C语言中,引用数组元素时,其数组下标的数据不正确的是( C )。
A.整型常量 B.整型表达式
C.整型常量或整型表达式 D.任何类型的表达式

2.以下对一维整型数组a正确定义的是( D )。
A.int a(10); B.int n=10,a[n];
C.int n; scanf("%d",&n);int a[n]; D.#define SIZE 10 int a[SIZE];

3.若有语句: int a[10];则对a数组元素的正确引用是( D )。
A.a[10] B.a[3.5] C.a(5) D.a[10-10]

4.若有语句:int a[3][4];则对a数组元素的正确引用是( C )。
A.a[2][4] B.a[1,3] C.a[1+1][0] D.a(2)(1)

5.若有语句:int a[3][4];则对a数组元素的非法引用是( D )。
A.a[0][2*1] B.a[1][3] C.a[4-2][0] D.a[0][4]

6.以下能对二维数组a正确初始化的语句是( BD )。
A.int a[2][ ]={{1,0,1},{5,2,3}}; B.int a[ ][3]={{1,2,3},{4,5,6}};
C.int a[2][4]={{1,2,3},{4,5},{6}}; D.int a[ ][3]={{1,0,1},{ },{1,1}};

7.下面程序以每行4个数据的形式输出a数组,请填空。

#include<stdio.h>
#define N 20
    int main(void)
    { 
       int a[N],i;
       for{i=0;i<N;i++}  scanf("%d",   &a[i]      );
       for{i=0;i<N;i++}
       { if(   (i+1)%4==0   ) ;
           printf("%3d ",a[i]);
       }
     printf("\n");
     return 0;
}

8.下面程序的运行结果是( )。

#include<stdio.h>
int main(void)
{    
    char str[ ]="SSSWLIA",c;
    int k;
    for(k=2;(c=str[k])!='\0';k++)
    {
       switch(c)
       {  case'I':++k;break;
          case'L':continue;
          default:putchar(c);continue;
       }
       putchar('*');
    }
    return 0;
}
//运行结果为: SW*

9.数组可以用来保存很多数据,但在一些情况下,并不需要把数据保存下来。下面哪些题目可不借助数组?哪些必须借助数据?均编程实现。
(1)输入一些整数,统计个数。
(2)输入一些整数,求最大值、最小值及平均数。
(3)输入一些整数,判断哪两个数最接近。
(4)输入一些整数,求第二大的值。
(5)输入一些整数,求它们的方差。
(6)输入一些整数,统计不超过平均数的个数。
参考代码如下:

1)可不借助数组
#include <stdio.h>

int main(void)
{
	int x,n = 0;
	
	printf("输入正整数,-1输入结束\n");
	while(1)
	{
		scanf("%d",&x);
		if(x<0) break;
		n++;
	}
	printf("%d\n",n);
	
	return 0;	
}

2)可不借借助数组
#include <stdio.h>

int main(void)
{
	int x,max,min,n = 0;
	double sum = 0;
	
	printf("输入正整数,-1输入结束\n");
	scanf("%d",&x);
	max = min = x;
	while(1)
	{
		scanf("%d",&x);
		if(x<0) break;
		sum += x; 
		if(x>max) max =x;
		if(x<min) min = x; 
		n++;
	}
	printf("最大数为:%d\n",max);
	printf("最小数为:%d\n",min);
	printf("平均数为:%.2f\n",sum/n);
	
	return 0;	
}
3)借助数组
#include <stdio.h>
#include <stdlib.h>
int cmp(const void *a,const void *b)
{
	return *(int *)a -*(int *)b;
}
int mindiff(int *a,int n);
int main(void)
{//5 100 78 3 77 -1
	int x,n = 0;
	int a[100];	
	
	printf("输入正整数,-1输入结束\n");
	
	while(1)
	{
		scanf("%d",&x);
		if(x<0)  break;
	
		a[n++] = x;	
	}
	qsort(a,n,sizeof(a[0]),cmp);	
	int k = mindiff(a,n);
	printf("相差最小的两个元素为:%d %d\n",a[k],a[k+1]);
	
	return 0;	
}
int mindiff(int *a,int n)
{
	int min = 32767,k;
	for(int i=0;i<n-1;i++)
	{
		if(fabs(a[i]-a[i+1])<min)
		{
			min = fabs(a[i]-a[i+1]);
			k = i;
		} 			
	}	
return k;
}

4)借助数组
#include <stdio.h>
#include <stdlib.h>
int cmp(const void *a,const void *b)
{
	return *(int *)a -*(int *)b;
}

int main(void)
{//5 100 78 3 77 -1
	int x,n = 0;
	int a[100];	
	
	printf("输入正整数,-1输入结束\n");	
	while(1)
	{
		scanf("%d",&x);
		if(x<0) break;
		a[n++] = x;	
	}
	qsort(a,n,sizeof(a[0]),cmp);	
	printf("第二大的数为:%d\n",a[n-2]);
	
	return 0;	
}
5)借助数组
#include <stdio.h>
#include <stdlib.h>
int cmp(const void *a,const void *b)
{
	return *(int *)a -*(int *)b;
}

int main(void)
{
	int x,n = 0;
	int a[100];	
	
	printf("输入正整数,-1输入结束\n");	
	while(1)
	{
		scanf("%d",&x);
		if(x<0) break;
		a[n++] = x;	
	}
	qsort(a,n,sizeof(a[0]),cmp);	
	printf("第二大的数为:%d\n",a[n-2]);
	
	return 0;	
}
6)借助数组
#include <stdio.h>
#define N 100
int main(void)
{ 
	int age; 
	int sage[N];           
	double sum = 0;    
	int n = 0;         
	while(1)  
	{
		scanf("%d",&age);   
		if(age <= 0) break;    
		sum += age;
		sage[n]=age;          
		n++;
	}
	int k = 0;
	if(n > 0)
	{
		printf("%.2f\n",sum/n);
		for(int i=0;i<n; i++)
		    if(sage[i]<=sum/n)   
		        k++;
		printf("%d\n" , k);
	}	        
		
	return 0;	
}

10.年龄与疾病
描述:某医院想统计一下某项疾病的获得与否与年龄是否有关,需要对以前的诊断记录进行整理,按照018、1935、36~60、61以上(含61)四个年龄段统计患病人数占总患病人数的比例。
输入:共2行,第一行为以往病人的数目n(0<n≤100),第二行为每个病人患病时的年龄。
输出:按照018、1935、36~60、61以上(含61)四个年龄段输出该段患病人数占总患病人数的比例,以百分比的形式输出,精确到小数点后两位。每个年龄段占一行,共四行。
样例输入:
10
1 11 21 31 41 51 61 71 81 91
样例输出:
20.00%
20.00%
20.00%
40.00%
参考代码如下:

//0-18、19-35、36-60、61
#include <stdio.h>
int main(void)
{
	int age,n,num[4]={0};
	
	scanf("%d",&n);
	for(int i=0;i<n;i++) 
	{
		scanf("%d",&age);
		if(age>=61) num[3]++;
		else if(age>=36) num[2]++;
		else if(age>=19) num[1]++;
		else num[0]++;
	}
	for(int i=0;i<4;i++)
	{
		printf("%.2f%%\n",100.0*num[i]/n);
	}
	return 0;
} 

11.人民币支付
描述:从键盘输入一个指定金额(以元为单位,如345),然后输出支付该金额的各种面额的人民币数量,显示100元、50元、20元、10元、5元、1元各多少张,要求尽量使用大面额的钞票。
输入:一个小于1000的正整数。
输出:输出分行,每行显示一个整数,从上到下分别表示100元、50元、20元、10元、5元、1元人民币的张数。
样例输入:
735
样例输出:
7
0
1
1
1
0
参考代码如下:

#include <stdio.h>
int main()
{//从上到下分别表示100元,50元,20元,10元,5元,1元人民币的张数
	int a[]={100,50,20,10,5,1};
	int y,n;
	
	scanf("%d",&y);
	n=sizeof(a)/sizeof(int);
	for(int i = 0;i < n;i++)
	{
		printf("%d\n",y / a[i]);
		y%=a[i];
	}	
	return 0;
} 

12.最简真分数
描述:给出n个正整数,任取两个数分别作为分子和分母组成最简真分数,编程求共有几个这样的组合。
输入:第一行是一个正整数n(n≤600)。第二行是n个不同的整数,相邻两个整数之间用单个空格隔开。整数大于1且小于等于1000。
输出:一个整数,即最简真分数组合的个数。
样例输入:
7
3 5 7 9 11 13 15
样例输出:
17

参考代码如下:

#include <stdio.h>
#include <stdlib.h>

int zhenfen(int m,int n);//m和n能构成真分数返回1,否则返回0 
int main()
{
	int n,*a,i,j,k;
	
	scanf("%d",&n);
	a=(int *)malloc(sizeof(int)*n);
	for(i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	k = 0;
	for(i=0;i<n-1;i++)
	{
		for(j=i+1;j<n;j++)
		{
			if(zhenfen(a[i],a[j])) k++;
		}
	}
	printf("%d\n",k);
	free(a);
	
	return 0;	
}
int zhenfen(int m,int n)
{
	int r;
	while(m%n)
	{
		r = m%n;
		m = n;
		n = r;
	}
	if( n == 1) return 1;
	return 0;
}

指针:(7.13)

基础知识

每天两小时学习,从我个人的角度来学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 理解地址与指针的概念。
  • 熟练掌握指针的定义和使用。
  • 掌握指针与数组的关系。
  • 掌握指针与函数的关系。
  • 掌握动态内存分配的概念及相关库函数。
  • 掌握命令行参数的概念。

内容

  • 指针就是内存地址,指针变量就是存储地址的变量。
  • 使用指针可以提高程序的编译效率和执行速度,使程序更加简洁;
  • C语言提供两种指针运算符 “*” 和 “&”

------------------------------------------指针变量定义-----------------------------------------------

1.数据类型不是指针变量的数据类型,而实其所指目标对象的数据类型。
数据类型 * 变量名 [=初值];
例如:int *p;告诉编译器,p变量只能存储整型空间的地址,不能存储其他类型空间的地址。
  • 取地址运算符 & :例如: p = &a; 表面得到变量a的地址,并把该地址存入指针变量p中。
  • 间接运算符 *。星号(*)如果不是再指针变量定义时出现,而是某条语句某个指针变量前出现,则是间接运算符,表示 取指针所指向变量的值。
  • 指针变量空间的大小。定义指针变量后,系统会为该指针变量分配存放一个地址值存储单元,存储单元的大小与该指针所指向内存空间的类型无关一般情况下,32位系统分配4个字节,64位系统分配8个字节,可以用sizeof运算符计算出指针变量所占空间大小。

------------------------------------------空指针-----------------------------------------------

  • 空指针常量是一个值为0的整数常量表达式,在头文件stdlib.h以及其他头文件中,宏NULL被定义为空指针常量。
int *ptr = NULL; 定义一个空指针

------------------------------------------void 指针-----------------------------------------------

  • 指向void的指针,简称void指针。
  • void指针是类型为void* 的指针,表示未确定类型的指针,也称作万能指针
  • 若想声明一个可以接收任何类型指针参数的函数,可以将所需的参数设定为void* 指针。

------------------------------------------void 指针 与 memset 函数-----------------------------------------------

  • 函数 memset() ,声明在string.h 中,原型:void* memset(void *s, int c, size)
  • 函数memset()将 c 的值赋值到 a 数组的每个字节,实参 a 具有int * 类型;
  • 函数被调用时,实参被转换成形参void*,这个类型转换是隐式的。
  • memset()函数常被用来对数组元素清0,如下:
int a[10];
memset(a,0,10 * sizeof(int));

------------------------------------------void 指针 与 malloc 函数-----------------------------------------------

  • malloc 全称 memory allocation ,中文叫动态内存分配。
  • 用于申请一块连续的指定大小的内存块区域,以void* 类型返回分配的内存区域地址
  • 当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态分配的内存。
  • void* 类型可以通过类型转换强制转换为任何其他类型的指针。
  • malloc函数的原型:void* malloc(size_t size);
  • 如果分配成功则返回指向被分配内存的地址(此存储区中的初始值不确定),否则返回空指针NULL。
  • 如果内存不再使用时,应使用 free() 函数将内存块释放;原型 void free(void* ptr)
  • 结合如上两个函数就可以使用动态数组,也就是在程序运行过程中,根据需要动态申请数组空间,不需要时再释放数组空间。
  • 查看例 7-3 学习如何使用。

------------------------------------------const 指针常量-----------------------------------------------

  • const修饰“*” 被称作 常量指针,这样不能通过该指针变量修改指向的内容。
					常量指针改指针。意思是可以修改指针,但不能修改指针指向的变量。
int x = 5,y=6;
const int *ptr = &x;
*ptr = 10; 			错误,不能通过常量指针修改所指内容。
x = 10;    			正确。
ptr = &y;  			正确,因为ptr本身是常量,可以指向其他整型变量。

------------------------------------------常量指针变量-----------------------------------------------

  • 用const修饰指针变量名,称为常量指针变量。
int x=5,y=6;
int * const ptr = &x;
*ptr = 10;				正确,可以通过ptr修改指向的数据。
ptr = &y;				错误,不能修改ptr的值,因为ptr本身是常量。

------------------------------------------指针常量-----------------------------------------------

  • 指针常量既是常量指针,又是常量指针变量。
int x = 5,y = 6;
const int * const ptr = &x;
*ptr = 10;					错误,不能通过常量指针修改所指内容。
ptr = &y;					错误,不能修改ptr的值,因为ptr本身是常量。

------------------------------------------指针与数组-----------------------------------------------

  • 利用数组的存储方式,计算机只需知道第一个数组元素的地址,就可以访问整个数组。例 7-6
  • 函数形参是指针类型的,编程实例 7-7

------------------------------------------数组指针-----------------------------------------------

  • 指向数组的指针被 简称为 数组指针
  • 一维数组指针的定义形式为: 类型名( * 标识符)[数组长度];
int (*p)[10];
P是一个指针变量,指向一个长度为10,数组元素为int类型的一维数组;

int a[10];
int (*p)[10] = &a;
a 是有10个单元的整型数组,p指向了这个数组。 为什么不是 P = a 呢?
因为 a 是 a[0] 单元的地址,是一个整型空间的地址,与 p 类型不匹配。

int a[11];
int (*p)[10] = &a; 错误,p 只能是10个整型空间的地址,不能是其它类型的地址。
  • 数组指针的主要用途是指向二维数组的某一行。 如例 7-8

------------------------------------------指针数组-----------------------------------------------

  • 指针数组也就是元素为指针类型的数组。一般情况,数组中的指针会指向动态分配的内存区域。
  • 一维指针数组的定义形式为:类型名 *标识符[数组长度];编程实例 例7-9
int *ptr[10]; 
ptr 是数组名,10个元素的类型都是 int *
  • 指针数组通常也适用指向若干字符串,这样使得字符串处理更加灵活方便;
    (其实笔者更推荐枚举类型)
    ------------------------------------------指向函数的指针-----------------------------------------------

  • 函数包括一系列的指令,当它经过编译后,在内存中会占据一块内存空间,该空间有一块内存空间,该空间有一个首地址,指针变量可以存储这个地址。
    存储这个地址的变量就是函数指针(或称为指向函数的指针)
    也可以在数组中存储函数指针,然后使用数组的索引来调用这些函数。

  • 函数指针的定义格式如:数据类型 ( * 指针名)(参数列表);
    例如:double (*funcPtr)(double,double);(定义函数指针括号不能省略,否则变成了函数声明)
    funcPtr就是函数指针,而该指针只能指向有两个double形参且返回值为double的函数。

  • 举例说明 例 7-10

  • 函数指针还可以作为函数的形参,最典型的是 stdlib.h 中有一个qsort() 函数,可以对数组进行排序。

  • qsort 函数百度百科链接

void qsort(void* base,size_t num,size_t size,int (*compar)(const void*const void*));
函数原型看着很复杂,一共四个参数
1.base是void指针,可以指向待排序数组的起始位置
2.num是排序的元素个数
3.size是每个元素占的字节数
4.compar则是函数指针,它指向的函数可以确定元素的顺序。
举例说明,请看下文中的例 7-12

------------------------------------------命令行参数-----------------------------------------------

洗脑内容

  • 指针变量定义时,如果没有初始化,指针变量的值是未定义的。(一般情况下,定义指针应该初始化)
  • 常量指针改指针
  • 右左法则,const最近的 则不能修改。
  • 难以判断类型的请 参考 右左法则 ;百度百科链接:link.
  • 当一个指针指向普通数据变量时称为一级指针,指向一级指针的指针是二级指针,指向二级指针的指针是三级指针。多级指针常与多维数组一起使用。
  • 指针内容核心指针就是地址,指针变量就是存储地址的变量

例7-3 随机生成指定长度的字符串。

  • 通过随机生成指定长度字符串,介绍 malloc 函数和 free 函数具体怎么使用
  • buffer 是动态字符数组,用于存储字符串
  • 例 7-4 也是动态数组的例子
#include <stdio.h>      
#include <stdlib.h>     // malloc, free, rand,srand 
#include <time.h>       // time(0)

int main ()
{
 	int len;
  	char * buffer = NULL;     //声明指针变量并初始化为空指针
  	srand(time(0)); // 设置随机数种子 
  	printf ("你想要多长的串? ");
 	scanf ("%d", &len);

  	buffer = (char*) malloc (len+1);     //动态分配 len+1个字节空间 
  	if (buffer == NULL) exit (1);          //分配失败 

 	for (int i = 0; i < len; i++)               //产生随机串 
    	      buffer[i] = rand()%26 + 'a';
         buffer[len]='\0';                               //放字符串结束标志 
 
  	printf ("随机串: %s\n",buffer);
  	free (buffer);  //释放空间 

  	return 0;
}

例7-4 与指定数字相同的数的个数

  • 一维动态数组
  • 一般情况下,结束程序运行前,要使用free()函数释放空间,因为动态分配的内存空间,操作系统不能自动收回。
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
    int n;
    int *a = NULL;       //定义指针变量并初始化为空指针
    scanf("%d",&n);     
    a = (int*)malloc( n*sizeof(int) );  //动态分配 n个整型空间
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
    int x,sum = 0;
    scanf("%d",&x);
    for(int i=0;i<n;i++)
        if(x == a[i])   sum++;
    printf("%d\n",sum);
    free(a);                        //释放a指针指向的n个整型空间 
    
    return 0;
}

例7-6 通过指针操作数组。

  • 格式控制符“%p”中的 p 是pointer(指针)的缩写。指针的值是C语言实现(编译程序)相关的,但几乎所有实现中,指针的值都是一个表示地址空间中某个存储器单元的整数。printf函数中对于%p一般以十六进制整数方式输出指针的值,附加前缀0x。
  • %p是打印地址的, %x是以十六进制形式打印, 完全不同!另外在64位下结果会不一样, 所以打印指针老老实实用%p .
    在这里插入图片描述
#include <stdio.h>

int main()
{
	int a[5] = {1,3,5,7,9};
	int i = 0 ;
	int *p = a;
	p[2] = 10;  
	printf("a数组的首地址是:%p\n",a); 
    
	for(i = 0;i < 5;i++)   //使用指针变量p输出数组a每个单元的地址及数组元素值 
	    printf("a[%d]的地址是:%p a[%d]的值是:%d\n",i,p+i,i,*(p+i));
	double x;
	double *q =  &x;

	printf("%p  %p\n",q,q+1);	
	return 0;
}

例7-7 在数组中查找元素

  • search 中 for 循环里 的 if 判断条件 相当于 p = a;这时就可以用 p 作为数组名来操作数组,但是这的前提是你已只给指针赋值的地址是数组首地址,如果不是,操作失败。
#include <stdio.h>

int search(const int *p, int n,int x);//函数声明  

int main()
{
	int a[6] = {1,5,2,3,9,7};
	int x;
	printf("输出要查找的元素:");
	scanf("%d",&x);
	int k = search(a,6,x);
	if(k == -1)
	printf("未找到!\n");
	else printf("找到了,是数组中的第%d个元素:",k+1);
	
	return 0;
}

int search(const int *p, int n,int x)
{//在数组中找元素值为x的元素
 //找到返回在数组中的下标,找不到返回-1 
	int pos = -1;
	int i = 0; 
	for(;i < n;i++)
	{
	    if(p[i] == x) 
	    {
		pos = i;
		break;
	       }
	}
	return pos;
}

例7-8 数组指针访问二维数组

#include <stdio.h>

int main(void)
{
	int a[5][3] = { {1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}, {13,14,15} };
	int (*p)[3] = a;     // 指向第一行
	int sum = 0;
	for(int i = 0;i<5;i++)
	{
	    for(int j=0;j<3;j++)
            sum+= *(*p+j) ;
	    p++;               //指向下一行
	}
	
	printf("sum = %d\n",sum);

	return 0;
}
  • 数组指针更多地应用在函数形参,二维数组名作为实参。上述程序改写为如下程序:
#include <stdio.h>
int sum(int (*p)[3],int n);
int main(void)
{
	int a[5][3] = { {1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}, {13,14,15} };
	printf( "sum = %d\n", sum(a,5) );       //函数调用,二维数组名作为实参

	return 0;
}
int sum(int (*p)[3],int n)
{
	int s = 0;
	for(int i = 0;i<5;i++)
	{
	    for(int j=0;j<3;j++)
            s += *(*p+j) ;
	    p++;               //指向下一行
	}
	return s;
}

例7-9 指针数组编程示例

  • 个人感觉指针数组不是很友好,得初始化,得申请空间,正常得话还得加个失败判断。
  • 代码中p[0]是一维动态数组(7个整型单元)的起使地址
  • p[1]是一维动态数组(5个整型单元)的起使地址
  • p[2]是整型变量x的地址
    在这里插入图片描述
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int x = 5;
	int *p[3] = {NULL,NULL,NULL};
	p[0] = (int *)malloc(7*sizeof(int));
	p[1] = (int *)malloc(5*sizeof(int));
	p[2] = &x;
    for(int i = 0;i < 3;i++)
    {
    	printf("%#X\n",p[i]);
    }
		
	return 0;
}

例7-10 函数指针示例

在这里,函数名单独拿出来的用法和数组名类似

#include <stdio.h>

double Add(double x,double y)
{
    return x + y;
} 
int main (void)
{
    double (*funcPtr)(double ,double );   //函数指针声明

    funcPtr = Add;                                   // funcPtr指向函数Add
    double result = funcPtr(6.0,7.0);       //通过函数指针调用函数
    printf("%f",result);
    
    return 0;
}
  • 函数指针变量还可以指向其他同类型的函数,以增加程序设计的灵活性,如下例7-11

例7-11 对用户输入的两个数字,进行加减乘除计算

  • 这个例子就是一个简单的计算器。
  • 表达式funcTable[i](x,y) 会调用地址保存在 funcTable[i] 中的函数,结果如下:
    在这里插入图片描述
#include <stdio.h>

double Add(double x,double y);
double Sub(double x,double y);
double Mul(double x,double y);
double Div(double x,double y); 

double (*funcTable[4])(double,double) = {Add,Sub,Mul,Div};      //函数指针数组
char *msgTable[4] = {"sum","Difference","Product","Quotient"};  //字符指针数组
int main(void)
{
	double x = 0, y = 0;
	printf("输入两个运算数:\n");
	scanf( "%lf %lf",&x,&y) ;
    
	for(int i =0;i < 4;i++)   //遍历函数指针数组,完成加减乘除运算
	    printf("%10s: %6.2f\n",msgTable[i],funcTable[i](x,y));
	  
	return 0;  
}

double Add(double x,double y)
{
	return x + y;
}
double Sub(double x,double y)
{
	return x - y;
}
double Mul(double x,double y)
{
	return x * y;
}
double Div(double x,double y)
{
	return x / y;
}

例7-12 使用qsort()函数完成数组排序

  • 该程序是对整型数组排序.如果对其他类型的数组排序,还需要定义一个相应类型的两个数的比较函数。
  • qsort() 函数的形参base是void指针,而compare是函数指针,才让qsort() 函数具有通用性,可以对任意类型的数组进行升序或降序排序。
#include <stdio.h>
#include <stdlib.h>     //qsort

int compare(const void*a,const void*b)
{
	return ( *(int *)a - *(int *)b ); //先把void*强制转换成int*,再取指针所指空间的值
}
int main (void)
{
 	int values[]={40,10,100,60,70,20};
    
    qsort(values,6,sizeof(int),compare);
    
    for(int i=0 ;i<6;i++)    //遍历数组
       	printf("%d ",values[i]);

  	return 0;
}

课后练习

1.字符指针、浮点指针以及函数指针这三种类型的变量哪个占用的内存最大?为什么?
答:指针变量也占用内存单元,而且所有类型的指针变量占用的内存大小都是相同的。也就是说,不管是指向何种对象的指针变量,它们占用的内存空间的字节数都是一样的,通常是一个机器字长。一般情况下32位系统占4个字节,64位系统占8个字节,可以用sizeof运算符测试指针变量所占字节数。

2.以下对C语言的“指针”的描述不正确的是( D )。
A.32位系统下任何类型的指针的长度都是4个字节
B.指针数据类型声明的是指针实际指向内容的数据类型
C.野指针是指向未分配或者已释放的内存地址
D.当使用free释放掉一个指针内容后,指针变量的值被置为NULL

3.有如下定义,则下列符号中均正确地代表x的地址的选项是( D )。
int x , *p;
p = &x;
A.&x、p、&x B.&、x、p C.&p、*p、x D.&x、&*p、p

4.有基本类型相同的指针p1、p2,则下列运算不合理的是( A )。
A.p1 /= 5 B.p1 - p2 C.p1 = p2 D.p1 == p2

5.以下程序的输出是( C )。
int *p = 0;
p += 6;
printf("%d\n",p)
A.12 B.72 C.24 D.0
E.6 F.任意数

6.有以下定义:
int a = 248,b = 4;
const int *d = &a;
int *const e = &b;
int const *f = &a;
则下列表达式中( BDE )是有问题的。
A.d = &b; B.*d = 8; C.*e = 34; D.e = &a;
E.f = 0x321f;

7.以下代码执行后p1+5和p2+5分别是多少?
unsigned char p1;
unsigned long p2;
p1 = (unsigned char
) 0x801000;
p2 = (unsigned long
) 0x810000;
答:
p1+5是0x801005
p2+5是0x810014

8.请问以下代码有什么问题?你会怎么改写?
char a;
char *str = &a;
strcpy(str,“hello”);
printf("%s\n",str);
答:str是一个字符变量的a地址,strcpy()函数拷贝一个串到str所指空间,类型不匹配,所以可以做如下修改:
char a;
char str100]; //用字符数组存字符串
strcpy(str,“hello”);
printf("%s\n",str);

9.以下这段程序中有什么错误?你会怎么改写?
void swap(int *p1,int *p2)
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = p;
}
答:在swap函数中p是一个野指针,并没有分配指向的空间,当执行
p = *p1时会导致程序执行崩溃。可以做如下修改:
void swap(int *p1,int *p2)
{
int *p = (int *)malloc(sizeof(int)); //动态分配一个整型空间
*p = *p1;
*p1 = *p2;
*p2 = *p;
free§; //释放空间
}

10.给出以下程序的输出结果。

#include <stdio.h>
#include <stdlib.h> 

int main (void)
{
    	int *p,*q,**pp;
    	p = (int*) malloc( 3*sizeof(int) );
    	for(int i = 0;i < 3;i++)
    	{
	    *(p+i) = i+1;
    	}
    	q = (int*) malloc( 4*sizeof(int) );
    	for(int i = 0;i < 3;i++)
    	{
	    *(q+i) = i+5;
    	}
    
    	pp = (int **) malloc(2*sizeof(int*));
    	*pp++ = p;
    	*pp = q;
    	printf("%d,%d\n",*(*(pp+1)+1),**(--pp));
    	free(p);free(q);free(pp);
	 
  	return 0;
}
    答:程序的输出结果如下:
    6,1

结构,联合与位字段:(7.14)

每天两小时学习,个人的角度学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 掌握结构类型声明的方法
  • 掌握结构变量的定义,初始化及引用
  • 掌握结构数组,结构体指针的使用
  • 理解链表的概念及基本操作
  • 掌握联合类型和位字段

主要内容

  • 结构数据类型可以把 基本数据类型 和 派生类型 组合起来。结构类型也是派生类型。
  • 联合与结构类似,但不同的是,一个联合内的所有成员都是从同一个地址开始的。
    因此,当想使用同一地址来记录不同类型的对象时,可以使用联合。
  • 结构和联合的成员除了基本类型和派生类型外,也可以包含位字段

----------------------------------------------结构-----------------------------------------------

  • 结构(struct)类型是在程序中定义的类型,以指定记录格式,包括成员名称和类型,以及成员在内存中的存储次序。
  • 声明结构类型
struct [结构类型名]
{
	成员声明列表
}
[变量列表名];

如下举个例子:
struct coord
{
	int x;
	int y;
};
声明一个名为coord的结构类型,包含两个整型成员 x和y。这只是类型声明;
可以理解成创建了一个自定义的数据类型,然后用这个数据类型定义变量,就像用基本数据类型定义变量一样。

----------------------------------------------定义结构类型-----------------------------------------------

  • 定义结构类型的方式有两种。一种是在结构声明后带一个或多个变量名列表:
1.声明一个名为coord的结构类型,并定义了两个coord类型的结构变量 first 和 second
struct coord
{
	int x;
	int y;
}first,second;

2.把coord结构类型声明和变量定义分离。 大部分情况下,编写程序使用第二种方法。
struct coord
{
	int x;
	int y;
};
struct coord first,second;

与这种方式等同的是
typedef struct coord Coord;
解释:就是给struct coord结构类型定义一个同义词Coord,以后就可以使用
Coord first,second; 
这种是比较常见的。
  • 初始化
struct coord origin = {00};
  • C99C11 为结构提供了指定初始化器。结构的指定初始化器使用点(成员)运算符和成员名,如下:
struct coord first = {.x = 0};
顺序任意,但笔者感觉这种方式不如上面的初始化。
struct coord first = {.y = 0, .x = 1};
  • 注意
    1.结构类型声明语句必须以分号结尾,可以放在函数内部,也可以放在函数外部,作用域与变量相似
    2.包含多个源文件的工程中,如果使用相同的结构类型,需要在这些源文件中都声明相同的结构类型。
    3.结构类型声明描述了该结构类型的数据组织形式。但声明系统不为所动,只有定义系统才会为其分配内存空间。

----------------------------------------------访问结构成员-----------------------------------------------

  • C语言中,使用结构成员运算符(.)来访问结构成员。结构成员运算符也称为点运算符。
first.x = 50;
first.y = 100;
  • 可以通过简单赋值表达式语句在相同类型的结构间赋值信息
second = first
  • 可以定义结构类型的指针,通过指针访问结构类型变量的成员,使用“->”运算符。
struct book mybook,*ptbook; 结构变量和指向结构变量的指针
ptbook = &mybook; 			  ptbook存储的是mybook变量的地址
  • 下面的代码是通过指针变量ptbook 和 ->运算符访问mybook变量的成员,完成变量成员的赋值
strcpy(ptbook->title,“The C programming language”);
strcpy(ptbook->author, "Brian W.Ker");
ptbook->price=30.0;
ptbook->pages=258;

----------------------------------------------结构变量的内存分配-----------------------------------------------

  • 结构变量的内存空间大小为所有成员空间大小之和但需要考虑内存对齐问题
  • 内存地址对齐是计算机语言自动进行的,也是编译器的工作。
    所以如果能遵循某些规则,可以让编译器做的更好。
  • 可以通过sizeof运算符计算结构变量内存空间大小。
  • 一般情况下,结构类型变量的内存空间大小为结构成员中所占内存空间字节数最大值的整数倍
  • 这个可以出题考核,一般遇见就是改变char数组的元素来增加字节数,它增加则最大加一个类型的字节数就是结构体所占内存大小。

----------------------------------------------结构数组-----------------------------------------------

  • 结构数组与基本类型数组的定义,初始化及引用规则是相同的,区别在于结构数组中的元素为结构类型。
struct book library[30];	可以在定义时初始化
  • 针对结构数组本来有一个例子说明的,我个人觉得太冗长了没必要,之后自己写一篇博客说明。
这里用typedef给结构类型struct student定义了同义词 Student。
所以后面代码可以直接定义一个结构数组。
编程时建议使用typedef给结构类型定义这种一个词表示的类型名。
typedef struct student
{
	声明成员列表
}Student;
Student stu[100];
  • 结构数组元素的输入输出只能对单个成员进行,
    不能把结构数组元素作为一个整体直接进行输入和输出。

----------------------------------------------链表-----------------------------------------------

  • 使用数组可以保存一系列相同类型的数据。
    但如果数据元素的数量不确定,使用数组来存放就会非常麻烦。
    经常会遇到存储空间浪费或空间不够用的情况。
  • 学习了结构类型就可以创建新的数据组织形式——链式结构。
  • 利用指针成员可以实现多种形式的数据组织,如线性链表二叉链表邻接表等。
  • 下面介绍单链表。

----------------------------------------------单链表-----------------------------------------------

  • 一个结点两部分:存储数据元素的数据域 和 存储下一个结点地址的指针域。
  • C语言中,数组空间的大小是不能改变的,因此一个数组总有存满的时候。
  • 单链表的长度不受限制地增减,只在系统内存不足时才会提示内存满。
  • 后续自己单独写一篇博客说明类容。

----------------------------------------------联合-----------------------------------------------

  • 联合也称为共用体,声明和变量定义 与 结构 十分相似。
  • 联合成员共享一个内存位置。达到节省空间的目的。
  • 联合所以成员都是从相同的内存地址开始。
  • 可以声明一个拥有许多成员的联合,但是同一时刻只能有一个成员允许使用。
联合的声明方式与结构是一样的,只是把关键字struct改成union:
union[联合体名]
{
	成员声明列表
}[变量名列表];
  • 实际编程看 例 8-5

----------------------------------------------联合与结构主要区别-----------------------------------------------

  • 1.联合和结构都是由多个不同的数据类型成员组成,但在任何同一始课,联合只存放了一个被选中的成员,而结构的所有成员都存在。
  • 2.对于联合的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于结构的不同成员赋值是互不影响的。

----------------------------------------------位字段-----------------------------------------------
位字段百度百科

8-5 联合union编程实例

  • 原则:联合 同一时刻只能有一个成员允许使用,所以联合的成员使用会覆盖前成员内容。
  • 以下为程序中4个printf的内容
    1.union 占 16个字节空间
    2.前八个字节空间内容
    3.var.i = 0 (初始 i = 5,但执行 x = 1.25赋值语句后,i 的值为0 了。)
    4.输出每个字节的内容

在这里插入图片描述

#include <stdio.h> 
#include <string.h>

union Data         //联合类型定义
{
	int i;         //整型成员
	double x;     //浮点成员
	char str[16];   //字符串成员
};
int main(void) 
{ 
	union Data var;
	printf("%d\n",sizeof(var)); //用sizeof运算符计算union data类型变量空间大小 
	
	var.i = 5;  
	var.x = 1.25;
	int i = sizeof(double)-1;
	for(;i >= 0;i--)  //输出前8个字节空间内容(地址由高到低) 
	    printf("%02X ",(unsigned char) var.str[i]);
	printf("\n");
	printf("%d\n",var.i) ; //前4个字节解析成整数输出 
	strcpy(var.str,"hello");
	i = 15;
	for(;i >= 0;i--)
	    printf("%02X ",(unsigned char) var.str[i]);
	printf("\n");    
	
	return 0; 
}

课后练习

1.若有以下结构体声明,则( D )是正确的引用或定义。
struct example
{
int x,y;
}v1;
A.example.x = 10; B.example v2.x = 10;
C.struct v2; v2.x = 10; D.struct example v2 = {10};

2.以下C程序在64位处理器上运行后sz的值是( C )。
struct Student
{
char *p;
int i;
char a;
};
int sz = sizeof(struct Student);
A.24 B.20 C.16 D.14
E.13 F.12

3.有以下结构体指针变量定义:
struct Student
{
char name;
int id;
}ptr;
若ptr中的地址值为0x100000,则ptr+100为多少?
答:
如果是32位系统,name指针变量占4个字节,id占4个字节,即sizeof(struct Student)是8个字节,所以ptr+100=0x100000 + 100
8 = 0x100320;
如果是64位系统,name指针变量占8个字节,id占4个字节,即sizeof(struct Student)是16个字节,所以ptr+100=0x100000 + 100
16 = 0x100640;

4.有以下声明和定义语句,在32位系统中执行语句printf("%d",sizeof(struct data)+sizeof(max));的输出结果是多少?
typedef union
{
long i;
int k[5];
char c;
}DATE;
struct data
{
int cat;
DATE cow;
double dog;
}too;
DATE max;
答:
32位系统中,DATE是一个联合类型,其中空间分配最大的是成员k,占20个字节,所以sizeof(DATE) = 20,struct data是结构类型,是内存对齐的,所以sizeof(struct data) = 32,所以printf(“%d”,sizeof(struct data)+sizeof(max));的输出结果是52。

5.分析以下代码打印输出结果。
#include <stdio.h>
typedef struct
{
int a:2;
int b:2;
int c:1;
}test;
int main (void)
{
test t;
t.a = 1;
t.b = 3;
t.c = 1;
printf("%d,",t.a);
printf("%d,",t.b);
printf("%d,\n",t.c);

return 0;

}
答:程序运行结果为:
1,-1,-1,

6.在32位环境下给定如下结构体类型A:
struct A
{
char t:4;
char k:4;
unsigned short i:8;
unsigned long m;
};
则sizeof(A)是( C )。
A.7 B.6 C.8 D.以上答案都不对

文件:(7.16)

每天两小时学习,个人的角度学习基础知识,就是反复的对自己进行洗脑。

学习目标

  • 了解文件的相关概念
  • 掌握C语言中打开和关闭文件的操作
  • 掌握对文件的读写以及文件的定位等操作
  • 了解文件的一般应用

洗脑内容

  • 在编写关于文件操作程序的过程中,应区分 文件指针 和 文件位置指针这两个概念。
  • 对于文件状态所使用的字符,一定要注意其具体意思,如果涉及到文件指针的操作,可能会利用rewind(fp)文件定位函数 和 feof(fp)文件结束判断函数。
  • 对于文件读写函数,可以根据实际需要灵活进行选择,从功能上看,fread 函数 和 fwrite 函数可以完成文件的各种读写操作。一般来说,读写函数的选择原则时根据读写的数据单位来确定。
    (1)读写一个字符数据:选用 fgetc 和 fputc 函数
    (2)读写一个字符串:选用 fgets 和 fputs 函数
    (3)读写多个不含格式的数据块:选用 fread 和 fwrite 函数
    (4)读写多个含格式的数据:选用 fscanf 和 fprintf 函数
    (5)fwrite是将数据不经转换直接以二进制的形式写入文件,而fprintf是将数据转换为字符后再写入文件。
  • C语言中,除了通过读写操作来实现文件的复制,合并等功能,
    还可对文件进行创建,查找,删除等操作。
    与之对应的函数也是在不同的头文件里定义的。
    例如,
    创建文件函数 createio.h 中定义,
    删除文件函数 removestdio.h 中定义,
    查找文件函数 searchpathdir.h 中定义。

主要内容

  • 一个程序包括数据的输入,处理和输出三部分。

----------------------------------------------文件概述-----------------------------------------------

  • “文件”是指存储在外部介质上的相关数据的集合。

  • C语言中,对文件的读取和写入操作是采用流方式处理的,即把外部介质上文件中的数据读取到当前程序中,称为输入流; 而在程序中,把数据写到文件里,称为输出流,所以流的 输入/输出 是对于程序而言。

  • C语言的文件处理功能依据系统是否设置“缓冲区”分为两种:设置缓冲区和不设置缓冲区。

  • 因为不设置缓冲区的文件处理方式,必须使用较低级的 I/O 函数直接对磁盘存取,存取速度慢,并且因为不是C的标准函数,跨平台操作时容易出问题,所以C语言通常采用带缓冲区的文件处理方式。

  • 使用标准 I/O 函数(包含在头文件stdio.h中)时,系统会自动设置缓冲区,并通过数据流来读写文件。

  • 进行文件读取时不会直接对磁盘进行读取;
    1.先打开数据流,将磁盘上的文件信息复制到缓冲区内;
    2.然后程序再从缓冲区中读取所需要的数据;

  • 进行写入文件时并不会马上写入磁盘中
    1.先写入缓冲区;
    2.只有在缓冲区已满或“关闭文件”时,才会将数据写入磁盘。

  • 对于每个正在使用的文件,系统会自动在内存中为其开辟一个文件缓冲区,以便对文件进行操作。

  • 输入文件缓冲区 -> 程序数据区 -> 输出文件缓冲区 -> 磁盘文件 -> 输入文件缓冲区 (循环,周而复始)

----------------------------------------------文件分类-----------------------------------------------

  • 从用户角度, 文件可分为:普通文件 和 设备文件。
  • 从文件编码方式上可分为:文本文件 和 二进制文件。(C语言默认的文件类型是文本文件)
    二进制文件优点:存取速度快,占用空间小,可随机存取数据。
    缺点是 如果以记事本打开,会看到一堆乱码。
    所以一般大量数据存取会选择二进制文件,前提是不会直接打开文件查看内容。

----------------------------------------------文件存取方式-----------------------------------------------

  • 顺序存取,随机存取 两种方式
  • 顺序读取是指从上向下,依次读取文件的内容,保存数据时,将数据附加在文件的末尾,这种存取方式常用于文本文件。
  • 随机存取多半以二进制文件为主,它会以一个完整的单位进行数据的读取和写入,通常以结构为单位。

----------------------------------------------文件名,目录与路径-----------------------------------------------

  • 每个文件都用文件名来标识,文件名由“文件.扩展名”来表示,扩展名是操作系统用来标志文件格式的一种机制,指明该文件的类型。
  • 路径分 绝对路径相对路径,个人感悟如下:
    绝对路径: 从上往下,起点到终点, linux 中输入 pwd 也是绝对路径。
    相对路径: 从这里去往哪里需要怎么做,就是相对而言的路径。
  • windows操作系统中,路径分隔符用“\”,UNIX操作系统中路径分隔符是 " / "
    windos系统: win+R cmd 查看, UNIX系统中 打开终端就能查看。

----------------------------------------------文件指针-----------------------------------------------

  • 带缓冲区的文件处理系统中,当对一个具体的文件进行操作时,需要先定义一个FILE类型的指针变量指向该文件。
  • FILE 类型是在头文件stdio.h 中定义的一个结构体类型,该结构体中含有文件名,文件状态和文件当前位置等信息。
  • 编写程序时,不必关心FILE结构的具体细节,只需知道通过文件指针指向要操作的文件,然后进行访问即可。
  • 定义文件指针的一般格式为:
FILE *指针变量名
例如
FILE *fp;
fp 是指向 FILE 结构的指针变量,通过 fp 即可找到存放文件信息的结构变量
然后按结构变量提供的信息找到该文件,即可对文件操作。
  • C语言提供了3个标准设备文件的文件指针,直接使用即可。如下:
    1.stdin:指向标准输入文件(键盘)
    2.stdout:指向标准输出文件(显示器)
    3.stderr: 指向标准错误输出文件(显示器)
  • 设计程序时,使用文件的步骤如下:
    1.打开文件:将文件指针指向文件,为其开辟文件缓冲区。
    2.操作文件:对文件进行读,写,追加和定位操作。
    3.关闭文件:断开文件指针和文件的关联,释放文件缓冲区。
  • 如上操作,均通过 stdio.h 中提供的标准 I/O 库函数实现。

----------------------------------------------文件位置指针-----------------------------------------------

  • 两个指针的概念完全不同。文件中有一个位置指针,用于指向当前文件读写的位置。
  • 如果顺序读写,每次读写完一个数据后,该位置指针会自动指向下一个数据的位置。
  • 如果想随机读写一个文件,就必须根据需要改变文件的位置指针。
    这就要用到后面介绍的文件定位函数。

----------------------------------------------文件打开与关闭-----------------------------------------------

  • 文件操作通常分三步:打开文件,对文件进行读写等操作,关闭文件。
  • C语言中,文件操作都是由库函数完成的,这些库函数均在头文件 stdio.h 中。
  • 文件的打开,打开文件需要调用文件打开函数 fopen()
除了指定要打开文件的文件名,还需要知名对文件的具体使用方式,表明对文件进行何种操作, (,写或追加)

FILE * fopen(const char * path,const char * mode);
1.参数path 打开的路径名 或者 文件名; 通常是字符串常量或字符数组;
2.参数mode字符串指明要打开文件的类型, (文本文件或二进制文件) 和 操作方式,(读,写或追加)
  • 举个例子: 从当前源文件所在目录下的文本文件 file1.txt中读入数据,则语句为:
FILE *fp = fopen("file1.txt","r");

如果文件file1.txt 为二进制文件,且文件在D盘根目录下,则打开文件语句应为:
FILE *fp=fopen(”d:\\file1.txt“,"rb");
  • 文件的关闭,fclose(文件指针)
    若文件关闭成功,返回值为NULL;若文件关闭失败,则返回非NULL值;

----------------------------------------------文件操作方式及意义-----------------------------------------------

  • 常见文件操作方式及意义
  • 注意 w+ 或 wb+,若文件存在,则文件清空,即该文件内容会消失;
    若文件不存在,则建立该文件。从头读文件。
  • 注意 r+ 或 rb+ 方式打开文件时,写入数据时,新数据只覆盖所占的空间,后面数据不变。
    读写时,可以由位置函数(rewind等)设置读和写的起始位置。 (该文件必须存在)
  • 在打开一个文件时,如果出错,fopen()将返回一个空指针值NULL。
if(fp == NULL)
{
	printf("file1.txt can't open!\n");
	return 0;
}

----------------------------------------------文件的读写操作-----------------------------------------------

  • 顺序读写文件时,文件的位置指针由系统自动控制,每次读写操作后,系统都会将位置指针移到下一个数据的位置,这种方式不改变位置指针。
  • 但实际应用中,经常需要从文件中某一特定的位置来读写相应的内容,这就需要将文件内部的位置指针移动到该位置,再进行读写操作,这种读写方式称为随机读写方式。

----------------------------------------------文件的顺序读写-----------------------------------------------

  • 根据对文件读写数据单位的不同分为字符字符串格式化数据块读写函数

  • 字符读写函数:fgetc 从文件中读取一个字符,fput 将一个字符写到文件中。

正常时返回读取字符的ASCII码,非正常时返回EOF(-1)(表示文件结尾或文件打开方式不含 r 或 + )
举例 例9-2
文件打开,系统给文件分配一个位置指针,用于指向文件当前的读写位置。
每读取一个字符,文件的位置指针便会自动向下移动一个字节,不需人为控制。
许多读写函数中都是如此。
  • 字符串读写函数:fgets 从文件种读取一个字符串,fputs 将一个字符串写到文件中。
读 函数原型
char *fgets(char *str,int n,FILE *fp)
从由fp指定的文件中读取 n-1 个字符,并把它们存储到由str所指的内存地址作为开始地址的内存中去
最后加上结束符‘\0’

疑问:为什么读取的是 n-1 个字符?
file get string,读取的是字符串,缓冲区长度n只能存放n-1个字符,最后那个要被填写成 0 以便结束字符串
字符串常量:以 NUL 字节结尾的 0 个或多个字符组成的序列。即使字符串为空也仍有一个‘\0’空字符

写 函数原型
int fputs(char *str,FILE *fp)
功能:把由 str 所指的字符串写入 fp 所指的文件中。
正常时返回 写入文件的字符个数,即 字符串长度; 非正常返回 NULL 值

编程举例 9-4
  • 格式化读写函数:fscanf 从文件种按格式化方式读取数据,fprintf 将数据按格式化方式写到文件中。
存储比较规范的数据文件,利用格式化读写。
格式读函数 fscanf
int fscanf(FILE *fp,char *format,char *arg_list)
fscanf遇到空格和换行结束。 这与 fgets 有区别,fgets 遇到空格不结束。
成功,返回读入参数的个数;  失败时,返回EOF(-1);

格式写函数 fprintf
int fprintf(FILE *fp,char *format,char *arg_list)
写入成功时,返回写入文件的字符个数;写入失败时,返回一个负值。

编程实例 查看 例 9-6
  • 数据块读写函数:fread 以二进制形式读取文件中数据块,fwrite 以二进制形式将数据块写道文件中。
    前面给出的字符和字符串读写函数一般用于处理数据量小的文件。(相对而言)
    在程序设计中,经常需要处理大量的数据,C语言提供了用于二进制文件的数据块读写函数
    freadfwrite ,可用来读写一组数据。
函数原型
int fread(void *buffer,unsigned size, unsigned count, FILE *fp);
功能:
1.从由 fp 指定的文件中,按二进制形式将 size*count 个字节的数据读到由 buffer 指定的数据区中
2.fwrite 的功能时按二进制形式,
将由 buffer 指定的数据缓冲区内的size*count 个字节的数据(一块内存区域中的数据) 
写入由fp指定的文件中区。
参数说明:
buffer时一个指针,存放输入/输出数据的首地址;size指出一个数据块的字节数,即一个数据块的大小;
count 是一次读写数据块的个数;fp为文件指针,指向要读写的文件。
返回值:实际读写数据块的数量;若文件结束或出错,则返回0;

int fwrite(void *buffer, unsigned size, unsigned count, FILE *fp);

举例 9-7

----------------------------------------------文件的随机读写-----------------------------------------------

  • 实现随机读写的关键是要按要求移动文件位置指针,称为文件的定位。
  • C语言提供了 rewind,fseek 和 ftell 函数实现了文件的随机定位操作,
    可以根据需要直接读取文件任意位置的数据,而不必按顺序从前往后读取。
  • 1.文件定位
移动文件位置指针的函数主要有两个:rewind()fseek()1)文件定位到文件首的函数rewind 原型:void rewind(FILE *fp)
功能:将文件指针fp指定的文件位置指针重新指向文件的首部位置。

(2)随机定位函数fseek 原型为:int fseek(FILE *fp, long offset, int base);
功能:将文件指针 fp 移到基于 base 的相对位置 offset 处,可以根据需要将文件位置指针移到任意位置。

参数说明:
fp为文件指针;
offset为相对base的字节位移量,这是个长整数,用以支持大于64KB的文件;
base 为文件位置指针移动的基准位置,是计算文件位置指针位移的基点;
ANSI C定义了base的可能取值,以及这些取值的符号常量,如下所示:

符号常量     取值    表示的起始点
SEEK_SET    0      文件首
SEEK_CUR 	1	   当前位置
SEEK_END    2 	   文件末尾

返回值:正常时返回当前指针位置,异常时返回 -1,表示定位操作出错。

例如:
fseek(fp,100L,0);   表示把位置指针移动到距离文件首100个字节处。
fseek(fp,-20,1);	把位置指针从当前位置沿文件首方向移动20个字节。

(3)文件指针的当前位置函数ftell 函数原型 long ftell(FILE *fp);
功能:获取fp指定文件的当前读/写位置,该位置值用相对于文件开头的位移量来表示
参数说明:fp为文件指针
返回值:正常时返回位移量(这个是长整数),异常时返回-1,表示出错。
  • 2.文件的随机读写
    在移动位置指针之后,用前面介绍的任一种读写函数进行读写。
    由于一般是读写一个数据块,因此常用 fread 和 fwrite 函数。
  • 编程实例:9-8

----------------------------------------------文件检测函数-----------------------------------------------

  • C语言中常用的文件检测函数包括feof,ferror 和 clearerr
1.feof 函数用于 判断文件位置指针是否到达文件末尾,可用于二进制文件,也可用于文本文件
功能:文件结束,返回 1 , 否则返回 0
如:
while(!feof(fp))			当文件未结束
	putchar(fgetc(fp));		从文件中读取字符并显示

2.读写文件出错检测函数 ferror
使用输入/输出函数进行读写操作时,如果出现错误,可以用ferror函数来检测读写时是否出错。
功能:返回值为 0,没错;否则表示有错。

3.文件出错标志和文件结束标志置 0 函数 clearerr
功能:本函数用于清楚出错标志和文件结束标志,使它们为0值。

例9-2 从指定文件中读取数据,并在屏幕上输出。

  • 如何判断文件是否读完?
    1.循环中用 feof 函数判断文件是否结束 作为循环条件,若结束 feof 返回值为1,否则为0;
    2.通过判断每次读取的字符是否是文件结束符EOF来判断是否为文件尾,EOF是再头文件stdio.h中定义的常量,用户可直接使用。

  • 例9-3 是对文件的写入操作。记得两个一起看。getchar和putchar的操作。

#include <stdio.h>

int main(void)
{ 
	FILE *fp;
	char inputfile[20] ;	
  	printf("请输入要打开文件的名字:");
  	scanf("%s", inputfile);      	//输入文件名 
  	fp=fopen(inputfile, "r");   	//以只读方式打开文本文件  
	if(fp == NULL)         	//文件打开失败,直接退出程序 
	{
	    printf("\n%s打开失败!\n", inputfile);
	    return 0;   
	}
	printf("%s 文件内容:\n", inputfile);  
	while(!feof(fp))        	//当文件未结束时 
	    putchar(fgetc(fp));  	//从文件中读取字符并显示 
   	printf("\n");
   	fclose(fp);           	//关闭文件 
	
	return 0;
}

例9-3 从键盘输入字符,并将其写入一个文本文件,直到遇到“#”结束输入,然后再从该文件读取文件内容,并在屏幕上输出。

  • (1)输入文件名,打开该文件

  • (2)用循环语句向文件里写入字符数据,直到输入“#”为止。

  • (3)从文件里读字符,并显示。

  • (4)关闭文件。

  • 调用了rewind(fp) 文件定位函数,用于将文件位置指针移动到文件首,如果不重新定位,文件位置指针会处于文件尾巴,下次读文件内容时会导致读取错误。

  • 适用于处理数据较少的情况,如果读写的文件数据量较大,按字符读写数据会比较麻烦,而且效率也低,C语言提供了字符串和数据块处理函数来解决文件较长的读写操作。

#include <stdio.h>
void WriteFile(FILE *fp);	//向文件里写内容
void ReadFile(FILE *fp);          	//从文件里读内容  

int main(void)
{   
	FILE *fp;
    char inputfile[30] ;
    
    printf("请输入文件名: ");
    scanf("%s", inputfile);              	//输入要打开的文件名     
    fp=fopen(inputfile, "w+");          
    if(fp == NULL)
    {
		printf("打开文件失败");
       	return 0; 
    }
    else
    {
        printf("%s 打开成功\n", inputfile);    
        WriteFile(fp);  	//调用函数把字符写入文件    
        ReadFile(fp);  	//调用函数读文件内容并显示   
        fclose(fp);  	//关闭文件
    	}
	return 0;
}
void WriteFile(FILE *fp)	//向文件里写内容 
{    
	char ch;
	
    printf("请输入文件内容,以#结束!\n");    
	while ((ch = getchar()) != '#')     	//从键盘读入字符,直到读入#循环结束 
	{
		fputc(ch, fp);  	//向fp所指文件里写字符ch		
	}
    rewind(fp);                	//将文件指针定位于文件开头 
}

void ReadFile(FILE *fp)        	//从文件里读内容 
{   
	char ch;
	printf("读取文件内容:\n");
	ch = fgetc(fp);           	//从文件读取字符 
	while(ch != EOF)   
	{
		putchar(ch);  	//显示字符 
		ch = fgetc(fp);  	//从文件读取下一个字符 
	}
	printf("\n文件读取结束");
}

例9-4 编程实现将一个字符串追加到指定文件中,并将文件的内容在屏幕上输出(使用字符串读写函数实现)。

  • 要在文件末尾追加字符串,需要以追加读写文本文件的方式打开文件。
    输入字符串,用 fputs 函数把该字符串写入文件末尾。
    然后循环 fgets ,从文件中读取字符串,并输出到屏幕上,最后关闭文件。
  • 紧接着看下一个例子 例9-5
#include <stdio.h>

int main(void)
{
    FILE *fp;		
    char str[100] ;
    printf("请输入一个字符串:\n") ;
    gets(str) ;	//输入要写到文件中的字符串	
    char fileName[20]; 	//定义存放文件名的字符数组
    printf("请输入文件所在路径及名称:\n") ;
    scanf("%s", fileName);	//输入文件所在路径及名称
    if ((fp = fopen(fileName, "a")) == NULL)	//以追加方式打开指定文件     
    {
        printf("打开失败!");
        return  0;
    }    
    fputs(str, fp); 	//把字符数组str中的字符串写到fp指向的文件
    fclose(fp); 	 
    if ((fp = fopen(fileName, "r")) != NULL)
    {   
	   printf("%s 文件内容:\n",fileName);
        while (fgets(str, sizeof(str), fp)) 	//从fp所指的文件中读取字符串存入str中 
             printf("%s", str); 	//将字符串输出 
        fclose(fp);              	//关闭文件
    }

    return 0;
}

例9-5 编程实现文件复制 (使用字符串读写函数实现)

  • 以读方式打开源文件,以写方式打开目标文件;
    使用循环,从源文件中一行一行读取数据,写到目标文件中,直到文件结束;
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE  *in, *out;	//定义两个FILE类型的指针变量
    char infile[50], outfile[50];     	//分别存放源文件和目标文件名 
    char s[256]; 				
    printf("请输入源文件名:");
    scanf("%s", infile);	//输入源文件所在路径及名称
    printf("请输入目标文件名:");
    scanf("%s", outfile);	//输入目标文件所在路径及名称
    if ((in = fopen(infile, "r")) == NULL)	//以只读方式打开指定文件
    {
        printf("打开文件%s失败\n", infile);
        exit(0);                               	//退出程序
    }
    if ((out = fopen(outfile, "w")) == NULL) 	//以只写方式打开指定文件
    {
        printf("不能建立%s文件\n", outfile);
        exit(0);                                	//退出程序
    }    
    while (fgets(s, 256, in)) 	//将in指向的文件的内容复制到out所指向的文件中
        fputs(s, out);	  
    printf("文件复制完成\n");
        //关闭文件
	fclose(in);    
	fclose(out);  

	return 0;
}

例9-6 用 fscanf 和 fprintf 函数实现将商品信息输入到文件,将文件内容及这些商品的总价输出到显示器上。

  • 要实现将输入的每条商品信息按格式化方式写到指定的文件中,
    然后按格式化方式读取文件中的每条商品信息并输出。具体步骤如下:
    (1) 定义一个结构体类型表示商品信息。
    (2) 以读写方式打开指定的文件。
    (3) 将输入的每条商品信息存放在一个结构体变量中,用格式化写函数 fprintf 写到文件中。
    (4) 用格式化读函数 fscanf 从文件中读出每条信息存到一个结构体变量中,并显示出来,同时统计这些商品的总价。
    这里定义了函数WriteFile 用于实现将键盘输入的商品信息以格式化形式写入文件中,ReadFile函数将文件中的商品信息按格式化形式读出并显示。
#include<stdio.h>

typedef struct Rec	//定义结构体类型,使用typedef定义Rec为结构体类型名 
{ 
    char id[10];
    char name[20];
    float price;
    int  count;
 }Rec;
void WriteFile(FILE *fp, int n);  	//将键盘输入的商品信息写到文件
void ReadFile(FILE *fp, int n);  	//从文件读出商品信息 

int main(void)
{   
    char filename[20]; 
    int n;
    FILE *fp;    
    printf("请输入目标文件:\n");      
	scanf("%s", filename);	
	fp=fopen(filename, "w+");  	//以文本读写方式打开文件	
	if(fp == NULL)
	{ 
		printf("打开文件失败");
		return 0;
	}	
	printf("请输入商品数量:\n");
	scanf("%d", &n);  	//从键盘输入
	WriteFile(fp, n); 
	ReadFile(fp, n);	
	fclose(fp); 	//关闭文件 
	
	return 0;
}
void WriteFile(FILE *fp, int n)      	//将键盘输入的商品信息写到文件 
{ 
	int i; 
	Rec record;	
	printf("***********请输入商品数据***********\n");
	for(i = 1; i <= n; i++)            	//从键盘输入商品信息 
	{
	    printf("请输入序号:");
	    scanf("%s", record.id);		 
	    printf("请输入名称:");
	    scanf("%s", record.name);	    
	    printf("请输入价格:");
	    scanf("%f", &record.price);	    
	    printf("请输入数量:");
	    scanf("%d", &record.count);
		 printf("\n");
	    //写入文件
	    fprintf(fp,"%s %s %5.2f %d\n", record.id, record.name, record.price, record.count); 
	}
}
void ReadFile(FILE *fp, int n)        	//从文件读出商品信息 
{ 
   	Rec record;
	double total = 0;	
    rewind(fp);              	//把文件内部的位置指针移到文件首
    while(fscanf(fp, "%s %s %f %d\n", record.id, record.name, &record.price, &record.count)!=EOF)
    {//输出数据
        printf("序号:%s 名称:%s 价格:%5.2f 数量:%d \n", record.id, record.name,
 record.price, record.count);  			 
	   total = total + record.price * record.count;
	}
	printf("合计:%5.2f\n", total);
}   

例9-7 数据块读写函数的使用:使用数据块写函数实现将商品信息写到指定的二进制文件中,然后将文件内容及这些商品的总价输出到显示器上。

  • 使用fread 和 fwrite进行读写文件时,文件的打开方式应该时打开二进制文件,即使用文本文件,系统也会按二进制文件读写。
#include<stdio.h>
#include <stdlib.h>

typedef struct Rec	//定义结构体类型,使用typedef定义Rec为结构体类型名 
{ 
    char id[10];
    char name[20];
    float price;
    int  count;
 }Rec;
void WriteFile(FILE *fp, int n);  	//将键盘输入的商品信息写到文件
void ReadFile(FILE *fp, int n);  	//从文件读出商品信息 

int main(void)
{   
    char filename[20]; 
    int n;
    FILE *fp;    
    printf("请输入目标文件:\n");      
	scanf("%s", filename);	
	fp=fopen(filename, "wb+");  	//以二进制读写方式打开文件	
	if(fp == NULL)
	{ 
		printf("打开文件失败");
		exit(1);
	}	
	printf("请输入商品数量:\n");
	scanf("%d", &n); 	//从键盘输入
	WriteFile(fp, n); 
	ReadFile(fp, n);	
	fclose(fp); 	//关闭文件 
	
	return 0;
}
void WriteFile(FILE *fp, int n)      	//将键盘输入的商品信息写到文件 
{ 
	int i; 
	Rec record;
	
	printf("***********请输入商品数据***********\n");
	for(i = 1; i <= n; i++)                    	//从键盘输入商品信息 
	{
	    printf("请输入序号:");
	    scanf("%s", record.id);		 
	    printf("请输入名称:");
	    scanf("%s", record.name);	    
	    printf("请输入价格:");
	    scanf("%f", &record.price);	    
	    printf("请输入数量:");
	    scanf("%d", &record.count);
	    printf("\n");
	    fwrite(&record, sizeof(Rec), 1, fp); 	//以块的方式写入文件
	}

}
void ReadFile(FILE *fp, int n)        	//从文件读出商品信息 
{ 
   	Rec record;
	double total = 0 ;    
	
   	rewind(fp);                	//把文件内部的位置指针移到文件首    
    while(fread(&record, sizeof(Rec), 1, fp)) 	//以块的方式读取文件中的数据
    {
        printf("序号:%s 名称:%s 价格:%5.2f 数量:%d \n", record.id,  record.name,
       record.price, record.count);  			//输出数据 
	    total = total+record.price*record.count;
	}
	printf("合计:%5.2f\n", total);
}  

例9-8 输出第m个商品信息:将商品信息文件中所有商品信息输出后,再根据输入的商品记录号m,输出第m个商品信息。

1.商品信息文件名使用 scanf 函数读入,以rb方式打开商品信息文件;
2.使用 例9-7 读取商品信息并输出的方法将文件中信息输出,
3.然后使用fseek将文件指针移至第m个记录处,使用fread读出数据并输出到屏幕上;
4.最后关闭文件。

  • 该程序用随机读取的方法读出第m个商品的信息;
  • 使用函数 fseek(fp,(m-1)*sizeof(struct Rec),0) 将位置指针移到第 m-1 条记录的末尾
  • 然后用 fread(&record,sizeof(struct Rec),1,fp) 读取第m条记录信息,并显示输出。
#include<stdio.h>
#include <stdlib.h>

typedef struct Rec	//定义结构体类型,使用typedef定义Rec为结构体类型名 
{ 
    char id[10];
    char name[20];
    float price;
    int  count;
}Rec;

int main(void)
{   
    char filename[20]; 	//存放商品信息文件名    
    FILE *fp;
    int m;    	//存放要读取的商品记录号 
    Rec record;
    
    printf("请输入商品信息文件:\n");      
	scanf("%s", filename);	
	fp=fopen(filename, "rb"); 	//以文本读写方式打开文件	
	if(fp == NULL)
	{ 
		printf("打开文件失败");
		exit(1);
	}
    while(fread(&record, sizeof(Rec), 1, fp))
	{		
	  printf("%s 名称:%s 价格:%5.2f 数量:%d \n", record.id, record.name,
		       record.price, record.count);
	}		
	printf("请输入要读取的商品记录号:\n");
	scanf("%d", &m); 	//从键盘输入
	fseek(fp, (m-1)*sizeof(Rec), 0); 	//将文件位置指针移到第m个商品信息位置
	fread(&record, sizeof(Rec), 1, fp);
	printf("第%d条记录\n序号:%s 名称:%s 价格:%5.2f 数量:%d \n", m, record.id,
          record.name, record.price, record.count);	
	fclose(fp); 	//关闭文件 
	
	return 0;
}

课后练习

1.系统的标准输入文件是指( A )。
A.键盘 B.显示器 C.软盘 D.硬盘

2.若执行fopen函数时发生错误,则函数的返回值是( B )。
A.地址值 B.0 C.1 D.EOF

  1. 若要用fopen函数打开一个新的二进制文件,该文件要既能读也能写,则文件方式字符串应是( B )。
    A.“ab+” B.“wb+” C.“rb+” D.“ab”
    4.fscanf函数的正确调用形式是( D )。
    A.fscanf(fp,格式字符串,输出表列);
    B.fscanf(格式字符串,输出表列,fp);
    C.fscanf(格式字符串,文件指针,输出表列);
    D.fscanf(文件指针,格式字符串,输入表列);

5.fgetc函数的作用是从指定文件读入一个字符,该文件的打开方式必须是( )。
A.只写 B.追加 C.读或读写 D.答案B和C都正确

6.函数调用语句:fseek(fp,-20L,2);的含义是( C )。(这个函数能改变文件位置指针,可实现随机读写,其中第二个参数表示以起始点为基准移动的字节数,正数表示向前移动,负数表示向后退。第三个参数表示位移量的起始点,0表示文件首;1表示文件当前位置;2表示文件末尾)
A.将文件位置指针移到距离文件首20个字节处
B.将文件位置指针从当前位置向后移动20个字节
C.将文件位置指针从文件末尾处后退20个字节
D.将文件位置指针移到离当前位置20个字节处

7.利用fseek函数可实现的操作( B )。
A.fseek(文件类型指针,起始点,位移量);
B.fseek(fp,位移量,起始点);
C.fseek(位移量,起始点,fp);
D.fseek(起始点,位移量,文件类型指针);

8.利用fread(buffer,size,count,fp)函数可实现的操作有( B )。
A.从fp指向的文件中,将count个字节的数据读到由buffer指定的数据区中
B.从fp指向的文件中,将size*count个字节的数据读到由buffer指定的数据区中
C.以二进制形式读取文件中的数据,返回值是实际从文件读取数据块的个数count
D.若文件操作出现异常,则返回实际从文件读取数据块的个数

9.设FILE *fp,则语句fp=fopen(“m”, “ab”);执行的是( D )。
A.打开一个文本文件m进行读写操作
B.打开一个文本文件,并在其尾部追加数据
C.打开一个二进制文件m进行读写操作
D.打开一个二进制文件m,并在其尾部追加数据

10.C语言中根据数据的组织形式,把文件分为( 顺序文件 )和( 索引文件 )两种;C语言中标准输入文件stdin是指( 键盘 ),标准输出文件是指( 显示器 );文件可以用( 顺序 )方式存取,也可以用( 随机 )方式存取。

11.C语言中,使用( fopen )函数打开文件,使用( fclose )函数关闭文件。

12.C语言中,文件位置指针设置函数是( fseek );文件指针位置检测函数是( fteel );函数rewind()的作用是( 使文件位置指针重新指向文件的开头位置 )。

13.函数feof()用来判断文件是否结束。若遇到文件结束,函数值是( 1 ),否则为( 0 )。

14.一条学生的记录包括学号、姓名和成绩信息,要求:
(1)格式化输入多个学生记录。
(2)利用fwrite将学生信息按二进制方式写到文件中。
(3)利用fread从文件中读出成绩并求平均值。
(4)对文件中按成绩排序,将成绩单写入文本文件中。
参考代码如下:

#include <stdio.h>
typedef struct Student
{
	char sno[15];
	char name[20];
	int score;
}Student;

int main(void)
{
	int n;
	char filename[20];
	FILE *fp;
	
	printf("请输入要写入和读出数据的文件名:");
	scanf("%s",filename);
	fp = fopen(filename,"wb+");
	printf("请输入学生数:");
	scanf("%d",&n);
	Student s;
	printf("输入%d个学生的信息:学号 姓名 成绩:\n",n);
	for(int i = 1;i<=n;i++)
	{
		scanf("%s %s %d",s.sno,s.name,&s.score);
		fwrite(&s,sizeof(Student),1,fp);
	}
	double sum = 0;
	int k = 0;
	rewind(fp);
	while(fread(&s,sizeof(Student),1,fp))
	{
		sum += s.score;
		k++;
	}
	printf("平均成绩为:%.2f\n",sum/k);
	return 0;
}

15.编写函数实现单词的查找,对于已打开的文本文件,统计其中含某单词的个数。
参考代码如下:

#include <stdio.h>
#include <string.h>
char word[100],r[100];
int wordNum(const char* infilename) 
{	
    FILE* inp = fopen(infilename, "r+");
	fseek(inp, 0L, 0);  //将文件位置指针指向文件头
	int i = 0,num = 0,n = 30;
	while (!feof(inp)) 
	{
	    char ch=getc(inp);
	    if(ch>='a'&&ch<='z'||ch>='A'&&ch<='Z')
		{
			    r[i++]=ch;
		}
		else
		{
			r[i]='\0';
			i=0;
			if(!strcmp(word,r)) num++;
		}
	}
	fclose(inp);
	return num;
}
int main(void) 
{
	char filename[30];
	
	printf("请输入文件名:");
	scanf("%s",filename);
	printf("请输入要查找的单词:"); 
	scanf("%s",word);
	printf("%d",wordNum(filename));
	
	return 0;
}

16.青年歌手大赛记分程序,要求:
(1)使用结构记录选手的相关信息。
(2)使用链表或结构数组。
(3)对选手成绩进行排序并输出结果。
(4)利用文件记录初赛结果,在复赛时将其从文件中读出,累加到复赛成绩中,并将比赛的最终结果写入文件中。
参考代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Singer
{
	char name[20];
	int score;
}singer;
int cmp(const void *a,const void *b)
{
	return ((singer*)b)->score-((singer*)a)->score;
}
int main(void)
{
	struct Singer num[10];
	char filename1[30],filename2[30];
	int n,i;
	FILE* fp1;
	
	printf("输入歌手初赛文件:");
	scanf("%s",filename1);
	fp1 = fopen(filename1, "r+");
	if(fp1==NULL)
	{
		printf("%s文件打开不成功",filename1);
		return 0;
	}
	printf("输入参赛人数\n");
	scanf("%d",&n);
	printf("输入初赛选手及成绩\n");

	for(i=0;i<n;i++)
	{
		scanf("%s%d",num[i].name,&num[i].score);
	}
	qsort(num,n,sizeof(num[0]),cmp);
	for(i=0;i<n;i++)
	{
		fprintf(fp1, "%s %d\n", num[i].name, num[i].score);
	}
	printf("输入复赛赛选手及成绩\n");
	for(i=0;i<n;i++)
	{
		scanf("%s%d",num[i].name,&num[i].score);
	}
	fseek(fp1, 0L, 0);
	char last[20];
	while (!feof(fp1)) 
	{
		char oname[20];
		int oscore;
		fscanf(fp1, "%s%d", oname, &oscore);
		if(strcmp(last,oname)==0) continue;		
		for(i=0;i<n;i++)
		{
			    if(!strcmp(num[i].name,oname))
			    {
			        num[i].score+=oscore;
			        break;
		  	}
		}
		strcpy(last,oname);
	}
	fclose(fp1);
	printf("输入歌手复赛文件:");
	scanf("%s",filename2);
	FILE* fp2 = fopen(filename2, "r+");
	if(fp2==NULL)
	{
	    printf("%s文件打开不成功",filename2);
	    return 0;
	}
	fseek(fp2, 0L, 0);
	qsort(num,n,sizeof(num[0]),cmp);
	for(i=0;i<n;i++)
	{
	    fprintf(fp2, "%s %d\n", num[i].name, num[i].score);
	}
	fclose(fp2);	
	return 0;
}

17.编程实现用户注册及登录操作。要求:用户的注册信息包括用户名、密码、Email等。注册时,输入新用户名和密码,验证文件中该用户名是否存在,如存在,提示重新输入用户名及密码;否则,将该用户输入的注册信息写入文件。登录时,输入用户名和密码,从文件中核对用户名和密码是否正确,若正确,显示“登录成功”;否则,提示重新输入;输入错误三次以上,结束程序。
参考代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
bool search(char id[], char pass[]) 
{//验证账号密码
	FILE *fp;
	char tid[10], tpass[10];
	fp = fopen("text.txt", "r");
	while (!feof(fp)) {
		fscanf(fp, "%s%s", tid, tpass);
		if (strcmp(tid, id) == 0 && strcmp(tpass, pass) == 0)
		{
		    fclose(fp);
		    return true;
		}
	}
	fclose(fp);
	return false;
}
bool search2(char id[]) 
{//验证账号密码
	FILE *fp;
	char tid[10],pass[18];
	fp = fopen("text.txt", "r");
	while (!feof(fp)) 
	{
		fscanf(fp, "%s%s", tid,pass);
		//printf("%s %s %d\n",tid,id,strcmp(tid, id));
		if (strcmp(tid, id) == 0 )
		{
		    fclose(fp);
		    return false;
		}
	}
	fclose(fp);
	return true;
}
bool login() 
{//登录
	char id[10], pass[10];
	printf("Login\nPress the id: ");
	scanf("%s", id);
	printf("Press the password: ");
	scanf("%s", pass);
	printf("-----------------------\n");
	if (search(id, pass))
		return true;
	else
		return false;
}
void _add(char id[], char pass[]) 
{//写入id和密码
	FILE* fp;
	fp = fopen("text.txt", "a");
	fprintf(fp, "%s %s\n", id, pass);
	fclose(fp);
}
void regis() 
{//注册
	char id[10], pass[10], tpass[10];
	printf("Register\nPress the id: ");
	scanf("%s", id);
	if(search2(id)==false)
	{
		printf("Id already exists!\n");
		return;
	}
	while (true) 
	{
		printf("Press the password: ");
		scanf("%s", pass);
		printf("Press the password again: ");
		scanf("%s", tpass);
		if (strcmp(pass, tpass) != 0)
		    printf("The passwords you pressed are notthe same!\n");
		else
		    break;
	}
	_add(id, pass);
	printf("Register successfully!\n");
}
int main(void) 
{
	int command;
	while (true)
	{
		printf("(1.Login   2.Register  3.Exit)\n");
		scanf("%d", &command);
		printf("\n");
		if (command == 3)
		    break;
		else if (command == 1) 
		{
		    int num=3;
		    while (num)
		    {
		        if (!login())
		        {
		            printf("ID is not exist or password iswrong!\n");
		            printf("Again!\n");
		        }
		        else
		        {
		             printf("Login successfully!\n");
		             break;
		         }
		        num--;
		       }
		        if(!num) 
		        {
		             printf("Error 3 times! Exit!\n");
		             break;
		         }
		    }
		    else
		         regis();
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/GameStrategist/article/details/107161632