C语言程序设计教程(Ver 2.0)Ver.王敬华 讲授:刘宗香
第一章 C语言程序设计的预备知识
第二章 C语言程序设计基础
原码反码和补码
1、负数的补码(符号位不变其与按位取反)等于反码加一。正数三码相同
数制转换
十进制转二进制
整数转换:
1.首先用2整除一个十进制整数,得到一个商和余数
2.然后再用2去除得到的商,又会得到一个商和余数
3.重复操作,一直到商为小于1时为止
4.然后将得到的所有余数全部排列起来,再将它反过来(逆序排列),切记一定要反过来!
小数转换
采用"乘2取整,顺序排列"法:
1.用2乘十进制小数,可以得到积,将积的整数部分取出
2.再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出
3.重复操作,直到积中的小数部分为零,此时0或1为二进制的最后一位,或者达到所要求的精度为止
例如将0.125转换为二进制:
0.125 * 2 = 0.25 ------0
0.25 * 2 = 0.5 ------0
0.5 * 2 = 1.0 ------1
当小数部分为0就可以停止乘2了,然后正序排序就构成了二进制的小数部分:0.001
如果小数的整数部分有大于0的整数时,将整数部分和小数部分先单独转为二进制,
再合在一起就可以了,例如:
假设要将8.125 转换为二进制
现将8转为二进制:得到1000
再将0.125转为二进制:得到0.001
合并后为1000.001
关键 ASCII 码总结
0000 0000 |
0 |
0 |
0 |
NUL(null) |
空字符 |
0011 0000 |
60 |
48 |
30 |
0 |
字符0 |
0100 0001 |
101 |
65 |
41 |
A |
大写字母A |
0110 0001 |
141 |
97 |
61 |
a |
小写字母a |
常见ASCII码的大小规则:0~9<A~Z<a~z。
1)数字比字母要小。如 “7”<“F”;
2)数字0比数字9要小,并按0到9顺序递增。如 “3”<“8” ;
3)字母A比字母Z要小,并按A到Z顺序递增。如“A”<“Z” ;
4)同个字母的大写字母比小写字母要小32。如“A”<“a” 。
几个常见字母的ASCII码大小: “A”为65;“a”为97;“0”为 48 。
二进制位运算
一,位运算基础
位运算(包括与,或,取反,异或,左移,右移等)是程序设计中的一个重要的领域。
1,与运算:&
与运算的操作符为&。2个数进行与运算时,就是将这2个数的二进制进行与操作, 只有当2个数对应的位都为1,该位运算结果为1,否则运算结果为0。即:1&1=1;1&0=0;0&0=0.
比如计算15&10,首先15的二进制为:1111,10的二进制为1010,所以15&10为:
2,或运算:|
或运算的操作符为|。2个数进行或运算时,就是将这2个数的二进制进行或操作, 只要2个数对应的位有一个为1,该位运算结果为1,否则运算结果为0。即:1|1=1;1|0=1;0|0=0.
比如计算15&10,首先15的二进制为:1111,10的二进制为1010,所以15|10为:
所以15|10=15。
1、参与运算的两位只要有一
位为1,结果就为1;
2、只有两位同时为0,结果才
为0。
3,异或运算:^
异或运算的操作符为^。2个数进行异或运算时,就是将这2个数的二进制进行异或操作, 只要2个数对应的位相同,该位运算结果为0,否则运算结果为1。即:1^1=0;1^0=1;0^0=0.
比如计算15^10,首先15的二进制为:1111,10的二进制为1010,所以15^10为:
所以15^10=5。
4、“非”运算(~)
按位取反
计算机使用补码的形式来表示一个数。因为采用补码时符号位能和数据位一样参加运算,这便于运算器的设计和实现。
第二章:C程序特点
C程序是由多个函数构成的。
每个C程序中有且只有一个main函数。
main函数是程序的入口和出口。
不使用行号,无程序行的概念。
程序中可使用空行和空格。
引用C语言标准库函数(见附录B),一般要用文件包含预处理命令将其头文件包含进来。
用户自定义的函数,必须先定义后使用。
变量必须先定义后使用。
变量名、函数名必须是合法的标识符,标识符习惯用小写字母,大小写敏感。
不能用关键字来命名变量和函数。
函数包含两个部分:声明部分和执行部分,在C程序中,声明部分在前,执行部分在后,这两部分的顺序不能颠倒,也不能有交叉。
C语言的语句都是以分号结尾。
计算机程序设计语言的发展,经历了从机器语言,汇编语言,高级语言的发展过程;
机器唯一可识别的语言是机器语言;
C语言属于高级语言;
C语言程序可以在不同的操作系统运行,反映其良好的可移植性;
一个C程序是由许许多多函数组成的;而main函数位置可以任意;但程序的入口总是main
C源程序的最小单位是字符
编制C语言程序的基本步骤
编 辑:程序代码的录入,生成源程序*.c
|
编 译:语法分析查错,翻译生成目标程序*.obj
|
链 接:与其它目标程序或库链接装配,生成可执行程序*.exe
|
运 行
第三章:基本数据类型、运算符与表达式
1. 标识符
定义:用来标识变量、常量、函数等的字符序列
组成:
只能由字母、数字、下划线组成,且第一个字母必须是字母或下划线
C语言的关键字不能用作变量名
大小写敏感
2. 常量
定义:程序运行时其值不能改变的量(即常数)
常量的分类 :
值常量
整型常量: 10、15、-10、-30
实型常量: 12.5、 30.0、-1.5
字符常量: 'A'、'b'、'c'
字符串常量: "sum"、"A"、"123"
符号常量
用标识符来代表常量。其定义格式为:
#define 符号常量 常量
3. 变量
定义:其值可以被改变的量
变量的两要素 :变量名 、变量值 int a, b = 2; float data;
整型变量
基本型
类型说明符为int,在内存中占4个字节,其取值为基本整常数。4个字节,范围:0 — 2^32-1(32bit system)
短整型
类型说明符为short int。占2个字节。short int -32768~32767(sign)-2^15 — 2^15-1(0 include)
整型
类型说明符为long int或long ,在内存中占4个字节,其取值为长整常数。在任何的编译系统中,长整型都是占4个字节。在一般情况下,其所占的字节数和取值范围与基本型相同。
无符号型
类型说明符为unsigned。在编译系统中,系统会区分有符号数和无符号数,区分的根据是如何解释字节中的最高位,如果最高位被解释为数据位,则整型数据则表示为无符号数。
实型变量
单精度实型(float)
float f = 3.14, g; /*占4个字节(32位)*/
双精度实型(double)
double x, y; /*占8个字节(64位)*/
字符型常量
定义:用单引号括起来的单个普通字符或转义字符.
字符常量的值:该字符的ASCII码值
字符型变量char
占1个字节(8位)
通常用于存放字符ASCII码
可与int数据进行算术运算
存在有符号和无符号之分。默认情况下为有符号
字符串常量
定义:用双引号("")括起来的字符序列
存储:每个字符串尾自动加一个 '\0' 作为字符串结束标志
字符常量与字符串常量不同
关于printf()函数
%d:用于有符号整型数据,如int、short型数据;
%f:用于实型数据,如float、float型数据;
%c:用于字符型数据,如char型数据;
%s:用于字符串数据。
C之运算符
运算符的分类:
单目运算符:只带一个操作数的运算符。如:~运算符。
双目运算符:带两个操作数的运算符。如:*、/运算符。
三目运算符:带三个操作数的运算符。
自增、自减运算符++ --
作用:使变量值加1或减1。(只能用于变量)
种类:
前置 ++i, --i (先执行i=i+1或i=i-1,再使用i值)
后置 i++,i-- (先使用i值,再执行i=i+1或i=i-1)
位运算符:按位与(&)、按位或(|)、按位取反(~)、按位异或(^)、左移(<<)、右移(>>)六种。
左移(<<)
将变量对应的二进制数往左移位,溢出的最高位被丢掉,空出的低位用零填补。
a << n 变量a向左移动n位
右移同理
位运算符实例
将short类型数据的高、低位字节互换
#include <stdio.h>
void main ( )
{
short a = 0xf245 , b, c;
b = a << 8 ; //将a的低8位移到高8位赋值给b,b的值为0x4500
c = a >> 8 ; //将a的高8位移到低8位赋值给c,c的值为0xfff2
c = c & 0x00ff; //将c的高8位清0后赋值给c,c的值为0x00f2
a = b + c; //将b和c的值相加赋值给a,a的值为0x45f2
printf ("a = %x", a);
}
习题选
第四章 基本输入、输出和 顺序程序设计
格式化输出printf
有符号整数的输出 %[-] [+] [0] [width] [l] [h] d
[ ]:表示可选项。
-:左对齐(缺,右对齐)。
+ :输出正数,数前加+号(缺,不加)
0 :右对齐时,数据宽度小于width,左边补0(缺,补空格)。
width:数据显示的最小宽度。若实际宽度超出,按实际宽度输出。
(缺,按数据实际宽度输出)
字母l:d前加字母l(long), 按长整型格式输出数据。
字母h:d前加字母h(short),按短整型输出数据。
scanf函数的格式控制符
%[width] [l | h] Type
[ ]:表示可选项,可缺省。|表示互斥关系。
width:指定输入数据的域宽,遇空格或不可转换字符则结束。
Type:各种格式转换符(参照printf)。
l:用于d、u、o、x|X前,指定输入为long型整数;用于e|E、f前,指定输入为double型实数。
h:用于d、u、o、x|X前,指定输入为short型整数。
程序的控制结构
程序 = 数据结构 + 算法。
算法:就是解决问题的方法与步骤。
程序:算法的具体实现。
学习C语言,不仅要熟练掌握数据结构、语法规则,更重要的要掌握分析问题、解决问题的方法。
算法是程序的灵魂。
伪码是一种计算机不能识别、人能看懂的代码。该代码易于转换为计算机程序。
顺序程序设计举例
【例1】任意从键盘输入一个三位整数,要求正确地分离出它的个位、十位和百位数,并分别在屏幕上输出。
程序设计的分析:
设计算法前,先要确定分离个位、十位和百位数的方法。
可用求余法(假设a=456)。
个位:b0=a%10; (a=456, b0=6)
十位:a=a/10; b1=a%10; (a=45, b1=5)
百位:a=a/10; b2=a%10; (a=4, b2=4)
根据以上的分析,这个程序应这样设计:
(1) 定义一个整型变量x,用于存放用户输入的一个三位整数;再定义三个整型变量b0、b1、b2,用于存放计算后个位、十位和百位数。
(2) 调用scanf函数输入一个三位整数。
(3) 利用上述计算方法计算该数的个位、十位和百位数。
(4) 输出计算后的结果。
#include <stdio.h>
void main ( )
{
int x, b0, b1, b2; //变量定义
printf ("输入一个三位整数 : "); //提示用户输入一个整数
scanf ("%d", &x); //输入一个整数
b0 = x % 10; //用求余数法计算最低位
x=x/10; b1=x%10;
x=x/10; b2=x%10;
printf ("bit2 = %d, bit1 = %d, bit0 = %d\n", b2, b1, b0); //输出结果
}
第五章 选择结构程序设计
C程序中语句的分类
表达式语句
由表达式加上分号“;”组成。其一般形式为:表达式;
函数调用语句
由函数名、实际参数加上分号“;”组成。其一般形式为:函数名(实际参数表);
空语句
只有分号“;”组成的语句称为空语句。空语句是什么也不执行的语句。在程序中空语句可用来作空循环体。
复合语句
用 {…}括起来的一组语句。 一般形式为:
{ [数据说明部分;]
执行语句部分;
}
控制语句
用来实现一定的控制功能的语句称为控制语句 。
C语言用控制语句来实现选择结构和循环结构。
C语言有九种控制语句。可分成以下三类:
关系运算符、逻辑运算符、条件运算符
关系运算符
> (大于) >= (大于或等于)
< (小于) <= (小于或等于) 等优先级,比下面的优先级高 左结合性
== (等于) != (不等于) 等优先级,比上面的优先级低
关系表达式
用关系运算符连接起来的式子称为关系表达式。
例:a + b > c – d
x > 3 / 2
使用关系表达式注意:
(1)关系表达式的值不是0就是1,0表示假,1表示真。
(2)关系运算符的优先级相对较高,位于移位之后。
(3)关系表达式本身是表达式,因此,下达表达式合法
a <= x <= b 5 > 2 > 7 > 8
(左结合性,分部判断,但前半判断后即为表达式之结果,即0或者1,再予后者进行判断)
逻辑运算符和逻辑表达式
用逻辑运算符连接起来的式子称为逻辑表达式。
(1) 逻辑表达式表达式的值只能是0和1。
(2) 逻辑表达式求解时,并非所有的逻辑运算符都被执行。【节省算力】
a && b && c //只在a为真时,才判别b的值;//只在a、b都为真时,才判别 c的值
a || b || c //只在a为假时,才判别b的值;只在a、b都为假时,才判别 c的值
C控制语句:分支和跳转(选择结构程序设计)
If语句
If语句可嵌套
C语言规定,在缺省{ }时,else总是和它上面离它最近的未配对的if配对
Switch语句
第6章 循环结构程序设计
C控制语句:循环
紧记着循环控制变量需要一定要更新
while语句 (不确定循环次数未定,为假即结束)
while (表达式) 先判断表达式,再执行循环体
循环体语句;
(1) 如果while后的表达式的值一开始就为假,循环体将一次也不执行。
(2) 循环体中的语句可为任意类型的C语句。
(3) 遇到下列情况,退出while循环:
表达式为假(为0)。
循环体内遇到break、return。
do_while语句(出口条件循环,每次循环后检查条件,符合即跳出)
do 特点:先执行循环体,再判断表达式
循环体语句; while最后面的分号;不能省
while(表达式);
(1) 如果do-while后的表达式的值一开始就为假,循环体还是要执行一次。
(2)在if语句、while语句中,表达式后面都不能加分号,而在do-while语句的表达式后面则必须加分号,否则将产生语法错误。
其它同while
For语句(计数循环,即循环次数随条件语句确定)
for (表达式1;表达式2;表达式3)
循环体语句
for语句很好地体现了正确表达循环结构应注意的三个问题:
1、控制变量的初始化。
2、循环的条件。
3、循环控制变量的更新。
表达式1:一般为赋值表达式,给控制变量赋初值。
表达式2:关系表达式或逻辑表达式,循环控制条件。
表达式3:一般为赋值表达式,给控制变量增量或减量。
for语句注意事项:
(1) 表达式1、表达式2、和表达式3可以是任何类型的表达式。比方说,这三个表达式都可以是逗号表达式,即每个表达式都可由多个表达式组成。
(2) 表达式1、表达式2、和表达式3都是任选项,可以省掉其中的一个、两个或全部,但其用于间隔的分号是一个也不能省的。
(3) 表达式2如果为空则相当于表达式2的值是真 。【进入循环的条件】
(4) 循环体中的语句可为任意类型的C语句。
(5) for语句也可以组成多重循环,而且也可以和while语句和do-while语句相互嵌套。
(6) 循环体可以是空语句。
例:计算用户输入的字符数(当输入是回车符时统计结束)。
#include <stdio.h>
void main ( )
{
int n = 0;
printf ("input a string:\n");
for ( ; getchar( ) != '\n'; n++) ;
printf ("%d",n);
}
如何选择循环?
首先,判断需要用循环实现的目标,达成条件是什么?需要达到达成条件的工作次数确定吗?不确定,我们可以用什么来判断它达成目标?
然后,根据目标,我们可以选择入口式循环(for,while),入口式两种可互相替代,而出口式循环更注重结果,即达成条件,不注重循环次数,较为简洁。循环涉及到变量初始化,变量更新时,for会更合适
break与continue语句
break语句
在循环语句和switch语句中,终止并跳出循环体或开关体
(1) break仅用于循环语句和switch语句中。
(2) break只能终止并跳出最近一层的结构。
在嵌套循环的情况下,如何让break语句跳出最外层的的循环体?
for (…)
{
while (…)
{
……
if (…) break;
…
}
while循环后的第一条语句
}
实现方法:通过设置一标志变量tag,然后在每层循环后加上一条语句:if (tag) break; 其值为1表示跳出循环体,为0则不跳出。
continue语句
结束本次循环,跳过循环体中continue后尚未执行的语句,进行下一次循环。
(1) 仅用于循环语句中。
(2) 在嵌套循环的情况下,continue语句只对包含它的最内层的循环体语句起作用。
单元实验:水仙花数
(第三个程序)输出所有水仙花数。
所谓“水仙花数”是指一个三位数,其各位数字立方和等于该数本身。例如,153是一个水仙花数,因为153=13+53+33。
Resource Code
int main()
{
int a,b,c,i;
for(i=100;i<=999;i++)
{
a=i%10;
b=(i/10)%10;
c=(i/100)%10;
if(i==a*a*a+b*b*b+c*c*c)
printf("%d\n",i);
}
}
第七章 数组
用const定义数组需要在定义时初始化且,在后续使用中对这个数组是“只读”
一维数组
定义方式:
数据类型符 数组变量名[整型常量];
- 数组定义时,必须指定数组的大小(长度),大小必须是整型常量,不能是变量或变量表达式。
- 数组定义后,系统将给其分配一定大小的内存单元。数组所占内存单元= 数组大小 × sizeof(元素类型)short int a[20]; 则数组a所占内存单元的大小为:20 * sizeof(short) = 20 * 2 = 40(字节)。
- 占用内存中连续的存储单元,其中第一个数组元素的地址是数组的首地址。
一维数组的引用
数组变量名[下标];记得需要传地址操作时,数组整体可以用首项地址/数组名
而需要对数组特定元素或者从特定元素开始传址操作时,记得加取地址符号&或者使用首地址指针加减
(2) 只能逐个引用数组元素,不能一次引用整个数组!!
(3) 数组中的一个元素相当于一个变量,数组元素也称下标变量。对变量的一切操作适合于数组元素。
(4) 数组引用要注意越界问题。
一维数组的赋值
1、直接赋值
(4) 如果表达式的个数小于数组的大小,则未指定值的数组元素被赋值为0;
在定义数组时,如果不给数组变量赋初值,数组大小不能省略
2、通过循环来赋值
排序(冒泡、选择,)
冒泡排序法:
排序过程:(实现从小到大排序)
(1) 第一趟冒泡排序,将n个数中最大的数被安置在最后一个元素位置上;
方法:比较第一个数与第二个数,若为逆序a[0]>a[1],则交换;然后比较第二个数与第三个数;依次类推,直至第n-1个数和第n个数比较为止。结果最大的数被安置在最后一个元素位置上。依次比较,可用循环。
(2) 对前n-1个数进行第二趟冒泡排序,结果使次大的数被安置在第n-1个元素位置;
(3) 重复上述过程,共经过n-1趟冒泡排序后,排序结束【每一个元素都与其之前的数遍历,而因为它之后的数已经与元素比较然后在合适的位置上,因此实际上元素与全集进行了比较】
for (i = 1; i < NUM; i++) //趟数,共NUM-1趟
for (j = 0; j < NUM - i; j++) //实现一次冒泡操作
if (a[j] > a[j+1]) //交换a[j]和a[j+1]
{
t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
选择排序法:
排序过程:
(1) 首先通过n-1次比较,从n个数中找出最小的(最小数的下标), 将它与第一个数交换—第一趟选择排序,结果最小的数被安置在第一个元素位置上;
(2) 再通过n-2次比较,从剩余的n-1个数中找出次小的(
次小数的下标),将它与第二个数交换—第二趟选择排序;
(3) 重复上述过程,共经过n-1趟排序后,排序结束。
【找出下标,而没有交换数组的元素】
【例4】 用数组求Fibonacci数列 前20个数
#include <stdio.h>
void main( )
{
int i;
int f[20] = {1, 1};
for (i = 2; i < 20; i++)
f[i] = f[i-2] + f[i-1];
for (i = 0; i < 20; i++)
{
if (i % 5 == 0)
printf ("\n");
printf ("%12d", f[i]);
}
}
二维数组及多维数组
1、 二维数组的定义
定义方式:
数据类型 数组名[常量表达式1][常量表达式2];
2、二维数组元素的引用
形式: 数组名[下标1][下标2]
3、二维数组元素的初始化
分行初始化:也可以通过循环初始化/赋值;
字符串与数组
1、字符串的本质
字符串是一种以’\0’结尾的字符数组。
字符及字符串操作的常用函数
I (input) 输入
1、gets()函数
char str[80];
gets (str);
当输入:I□love□china!↙(□表示空格,↙表示回车)时,str中的字符串将是:“I love china!”
‘□’是字符串中的一部分【可输入空格,即,更像是输入一句话“sentence”】
缺点:gets函数的缺点,正是源自于它的优点,gets较于scanf函数的繁琐步骤【转换说明符】以及局限性【读词不读句】,gets无需复杂的转换说明,因为它是一个针对性极强的函数,而且gets读句,无需如scanf读句【本质上是读多个连在一起的词】要使用复数多个说明符,它简单易用,但就是因为gets函数对读取的字符串(字符数组)的元素没有限制,导致其有可能因为元素过多导致栈溢出【缓存区溢出(buffer overflow),栈溢出的一种】,从而威胁到数据安全,因此许多IDE都会针对可能导致栈溢出的函数进行警告报错甚至在库中删除/禁用该类函数。Write By Menou 2021.6.30
替代品:fgets(),gets_s()等,前者应用广,后者为C11可选拓展
通常情况下,现阶段fgets是最佳选择
2、scanf()函数
char str[80];
scanf ("%s", str);
当输入:I□love□china↙时,str将是:"I" ,
‘□’被当作分隔符【不可输入空格,即,更像是输入一个单词“word”】
当然也可以在转换标识符处添加读取字符串长度的最长限制或者最小限制(少则空格补位)
O(output)输出
1、puts函数
格式:puts(字符串地址) /*头文件为stdio.h*/
功能:向显示器输出字符串(输出完,换行)
例:char str[ ] = "I love china! “;
puts (str);
2、 printf函数
格式:printf("%s", 字符串地址) /*头文件为stdio.h */
功能:依次输出字符串中的每个字符直到遇到字符’\
例:char name[ ] = "John Smith";
printf ("%s\n", name);
printf ("%s\n", &name[5]);
配套fputs()//针对文件,输出屏幕-fputs(name,stdout)【标准输出】
字符串的长度
strlen函数
格式:strlen(字符串地址) /*文头件为string.h */
功能:计算字符串长度
字符串的复制:
strcpy (字符串1, 字符串2)【只是简单的copy,无法检测第一个数组能否容纳第二个数组】
char str1[20], str2[20];
scanf ("%s", str2);
strcpy (str1, str2); /*将str2复制到str1中*/
换而言之,strcpy也是一个可能导致栈溢出的函数
更谨慎的选择:
Strncpy函数,该函数前两个参数与上一个函数相同,新增的第三个参数指定了最大可拷贝字符数。
字符串比较:
strcmp (字符串1, 字符串2)
功能:比较两字符串的大小
返回值:a. 若字符串1< 字符串2, 返回负整数
b. 若字符串1> 字符串2, 返回正整数
c. 若字符串1== 字符串2, 返回零
同样的,strncmp是一个可以指定比较前n个字符的字符数组比较用函数
字符串的连接:
strcat (字符数组1, 字符数组2)【只是简单的拼接,无法检测第一个数组能否容纳第二个数组】
功能:把字符数组2连到字符数组1后面,形成新字符串
例:char str1[20] = "12345", str2[ ] = "6789";
strcat (str1, str2);
printf ("%s", str1);
换而言之,strcat也是一个可能导致栈溢出的函数
对应的解决方案:
Strncat函数,该函数前两个参数与上一个函数相同,新增的第三个参数指定了最大添加字符数。
字符串数组
是一个字符型的二维数组,该数组的每一行存放一个字符串
初始化:可以用二维数组初始化的方式初始化;char city[][10] = {{'B', 'e', 'i', 'J', 'i', 'n', 'g', '\0'},
{'S', 'h', 'a', 'n', 'g', 'H', 'a', 'i', '\0' }};
采用下列方法初始化;
char city[][10] = { "BeiJing", "ShangHai"};
第八章 函数
模块化编程
函数的概念与分类
函数其实就是一段可以重复调用的、功能相对独立完整的程序段。
具体分为:
无参数无返回值 void name (void)
无参数有返回值 int name (void) [int 可替换]
有参数无返回值 void name ()
有参数返回值 int name () [int 可替换]
函数参数的传递方式
根据实参传递给形参值的不同,通常有值传递方式和地址传递方式两种。
1、传值
函数调用时, 实参的值复制到形参中,存放于形参自已的单元中;调用结束,形参单元被释放,实参单元中的值不变。
特点:
① 形参与实参占用不同的内存单元
② 单向传递:调用者能向被调用者传数,被调用者不能向调用者传数
2、传址
方式:
函数调用时,将数据的存储地址作为参数传递给形参
特点:
① 形参与实参占用同样的存储单元(实参单元)
② 双向传递:调用者可向被调用者传数,被调用者也能向调用者传数
方法:
① 数组作为函数的参数
② 指针作为函数的参数(第9章)
用数组名作为函数参数时注意:
形参数组和实参数组的类型必须一致。
形参数组和实参数组的长度可以不相同,因传送的只是首地地址。
多维数组作为函数的参数时,第一维长度可省
例: 将任意两个字符串连接成一个字符串
#include <stdio.h>
void mergestr (char s1[ ], char s2[ ]);
void main ( )
{
char str1[40] = {"Hello "}; /*str1定义的要足够大*/
char str2[ ] = {"china!"};
mergestr (str1, str2);
printf ("%s\n", str1);
}
void mergestr (char s1[ ], char s2[ ])
{
int i, j;
for (i = 0; s1[i] != '\0'; i++) /*找到s1的结束标志'\0'; */
;
for (j = 0; s2[j] != '\0'; j++) /*将s2复制到s1的后边*/
s1[i+j] = s2[j];
s1[i+j] = '\0'; /*置字符串结束标志*/
}
变量的作用域和生存期
1、作用域和生存期的基本概念
*变量的作用域
即变量的作用范围(或有效范围,有效空间)。
按其作用域范围可分为两种:即局部变量和全局变量
*变量的生存期
即变量占用内存的时间。
按其生存期可分为两种:即动态变量和静态变量
动态变量和静态变量之区别
1)静态存储变量通常是在变量定义时就分定存储单元并一直保持不变,直至整个程序结束。静态变量,全局动态变量都是静态存储
2)动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放
3)静态存储变量是一直存在的,而动态存储变量则时而存在时而消失。通常把由于变量存储方式不同而产生的特性称为变量的生存期
4)静态存储只会初始化一次
函数的嵌套与递归调用
函数可以嵌套调用函数
函数递归调用:
函数直接或间接的调用自身叫函数的递归调用
【例1】递归的执行情况分析
#include <stdio.h>
void print (int w);
void main ( )
{
print ( 3 );
} 图-8-1 解释递归原理流程图
void print (int w) /*递归函数*/
{
int i;
if ( w != 0) /*递归结束条件, 若W = 0,递归结束*/
{
print (w-1);
for (i = 1; i <= w; ++i)
printf ("%d ", w);
printf ("\n");
}
}
为何程序执行结果是这样呢?而不是倒过来的三角形呢?
这涉及到递归计算的问题,如程序,当w=3时,只要w不等于0,if条件语句就会一直进入,假如我们把print ( 3 )看作是对这个函数的一级递归调用,那么在进入if后,立即就会产生对此函数的二级递归调用print ( 2),依次类推,而print(0)作为第四级调用其实也产生了,只不过值为void?【for循环进入条件不满足】,而运算优先级是从*递归到最低级递归,第三级递归就是print(1)w1=1,同理w2=2,w3=3,而根据for循环决定个数。
递归优秀案例:猴子吃桃问题
/*循环求第一天桃子*/
#include <stdio.h>
int peach_n( ) ;
void main ( )
{
printf ("%d",peach_n( ));
}
int peach_n( )
{
int i, a0=1;
for(i=9; i>=1; i--)
a0=2*(a0+1);
return a0;
}
Tower of Hanoi问题(汉诺塔问题)
问题描述:有A,B,C三个塔座,A上套有n个直径不同的圆盘,按直径从小到大叠放,形如宝塔,编号1,2,3……n。要求将n个圆盘从A移到C,叠放顺序不变,移动过程中遵循下列原则:
1、每次只能移一个圆盘
2、圆盘可在三个塔座上任意移动
3、任何时刻,每个塔座上不能将大盘压到小盘上
#include <stdio.h>
void move(char getone, char putone)
{
printf("%c--->%c\n", getone, putone);
}
void hanoi(int n,char A,char B, C)
{
if(n==1) /*递归结束条件*/
move(A,C);
else
{
hanoi(n-1, A, C, B); /*从A移到B,C为中介*/
move(A, C);
hanoi(n-1, B, A, C); /*从B移到C,A为中介*/
}
}
第八章编程题精选
// homework1 编写函数Mystrcopy(char str1[],char str2[])将字符串str2复制到str1中(不允许使用strcpy)
//20:23--21:16 done
void stringcopy(char str1[],char str2[])
{//自定义字符串拷贝函数
int i=0;
while(str2[i]!='\0')
{
str1[i]=str2[i];
i++;//21:12 bug fixed
}
//str1[i]='\0'; used for what?
}
int main()
{
char str1[20],str2[20];
//gets(str1);//输入字符串1
scanf("%s",&str1);
//gets(str2);//输入字符串2
scanf("%s",&str2);
stringcopy(str2,str1);//将字符串1拷贝到字符串2
printf("%s\n",str2);
printf("%s",str1);
//puts(str2);//输出结果
}
//homework2 编写一函数count(int n)计算13 + 23 + …+ n3 ,函数返回值为计算结果.
//5.18 Thu. 21:24 begin---21:30 first try---done!
int count(int n);//声明函数
int main()
{
int n;
printf("enter the number");
scanf("%d",&n);
count(n);
printf("%d",count(n));//输出模块
}
int count(int n)//定义函数
{
int i,all=0;
for(i=1;i<n+1;i++)
{
all+=i*i*i;
}
return(all);
}
//homework 3 编一函数Mystrcmp(char s1[],char s2[]),不使用strcmp函数但完成与strcmp类似的功能。
// 若s1<s2,返回-1;
// 若s1=s2,返回0;
// 若s1>s2,返回1。
//21:45 begin
//21:56 done-->bug 13
//22:11--->fix bug--->fail error
//22:33 first can run!--->fail!-->无法进入循环
//22:46 second run but fail-->仅仅比较首字母,循环失效原因不明
//23:11 finally done ! 原因查明,是return 0会跳出for循环
#include <string.h>
int Mystrcmp(char s1[],char s2[]);//声明函数
int main()
{
char s1[100],s2[100];
int l;
printf("please enter 2 string:\n");
scanf("%s",s1);
scanf("%s",s2);
l=strlen(s1)>strlen(s2)? strlen(s2): strlen(s1);
printf("%d\n",l);
printf("%d\n",Mystrcmp(s1,s2));
}
int Mystrcmp(char s1[],char s2[])
{
int i;
int l;
l=strlen(s1)>strlen(s2)? strlen(s2): strlen(s1);
for(i=0;i<l+1;i++)
{
if(s1[i]<s2[i])
{
return -1;
}
else if(s1[i]>s2[i])
{
return 1;
}
}
return 0;
}
2021.5.19 19:18 homework 4
//4、编写一函数 CheckNum(int m),用来判断一个数是否为素数,
// 是返回值为1,不是返回值为0。
//19:29 first try--->done!;19:34 improve the code;
//补充:
// 思路1):因此判断一个整数m是否是素数,只需把 m 被 2 ~ m-1 之间的每一个整数去除,
// 如果都不能被整除,那么 m 就是一个素数。
// 思路2):m 不必被 2 ~ m-1 之间的每一个整数去除,只需被 2 ~ 之间的每一个整数去除就可以了。
// 如果 m 不能被 2 ~ 间任一整数整除,m 必定是素数。
// 例如判别 17 是是否为素数,只需使 17 被 2~4 之间的每一个整数去除,由于都不能整除,可以判定 17 是素数。
// 原因:因为如果 m 能被 2 ~ m-1 之间任一整数整除,
// 其二个因子必定有一个小于或等于 ,另一个大于或等于 。
// 例如 16 能被 2、4、8 整除,16=2*8,2 小于 4,8 大于 4,16=4*4,4=√16,
// 因此只需判定在 2~4 之间有无因子即可。
/*
int CheckNum(int m);//函数声明
int main()
{
int n;
printf("This is a program used to judge one digital whether it's an prime number\n");
printf("now please input the number:");
scanf("%d",&n);
CheckNum(n);
printf("now press any key to check the result\n");
getchar();
getchar();
if(CheckNum(n)==1)
printf("this number is an primer number!\n");
else
printf("this number isn't an primer number!\n");
return 0;
}
int CheckNum(int m)//函数定义
{
int j;
for(j=2;j<m;j++)
{
if(m%j==0) break;//此处没有必要将运算单独写出,直接在if判断内写出运算即可
}//若m最终为素数,则j会在最后一次运算后=m,从而跳出循环,来到下一个if判断中
if(j==m)//素数判断,输出
return 1;
else
return 0;
}
*/
//homework 5 begin at 19:45
//20:11 first generation--->error
//20:56 唐琪鑫版本
//5,编写一函数sort(int n, int a[]) ,对数组a的前n个元素从大到小排序。
/*
#define SIZE 20
void sort(int n, int a[]);//函数声明
int main()
{
int i,n,a[SIZE];
printf("now input the digital into the shuzu!\n");
for(i=0;i<SIZE;i++)
{
scanf("%d",&a[i]);
}
printf("now decide how many number should be permutation:");
scanf("%d",&n);
sort(n,a[]);
for(i=0;i<SIZE;i++)
{
printf("%d",a[i]);
printf(" ");
}
return 0;
}
void sort(int n, int a[])//函数定义
{
int i;
for(i=0;i<n;i++)//冒泡排序选择,之后再编写选择排序
{
int j;
for(j=i+1;j<n+1;j++)
{
int t=0;
if(a[i]<a[j])
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
}
}
*/
//Tang Qixin edition
#include <stdio.h>
#define SIZE 20
void sort(int n, int a[]);//函数声明
int main()
{
int i, n,point;
int a[SIZE];
printf("now input the digital into the shuzu!\n");
for(i = 0; i < SIZE; i++)
{
scanf("%d", &a[i]);
}
printf("now decide how many number should be permutation:");
scanf("%d", &n);
sort(n, &a[0]);
//sort(n, a);//这两种都是等效的,对于含有数组的函数而言,调用的是数组的首个元素的地址
//point=a[0];//这一种似乎也是可行的,但是调试过程中,发现若输入n=13,则程序只会排序7项?
//sort(n, &point);//这一种先不用,此处尝试证明了函数是调用数组第一个元素的地址这一事实。
for(i = 0; i < SIZE ; i++)
{
printf("%d ", a[i]);
}
return 0;
}
void sort(int n, int a[])//函数定义
{
int i, j;
int temp = 0;
for(i = 0; i < n; i++)//冒泡排序选择,之后再编写选择排序
{
for(j=i+1;j < n; j++)
{
if(a[i] < a[j])
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
第九章 指针
指针与指针变量
变量地址
在定义变量时,系统要为其分配内存单元。内存单元的首地址即为该变量的地址。
指针与指针变量
指针:一个抽象概念,是指变量的地址
指针变量:一个变量类型,专门存放地址的变量 地址是指针变量i_pointer的内容(是i的地址)
&与*运算符
&后跟变量名,取变量的地址;
*后跟指针变量,指针所指单元的内容;/(单独解引用:*运算符给出指针指向的地址上贮存之值)
如:short int i=10, j, *i_pointer;/*(取I之地址赋值予i_pointer) */
i_pointer = &i; /*指针i_pointer所指单元的内容赋给j*/
j = *i_pointer; //(解引用) /*地址i_pointer所存的数据赋给j */
i = *i_pointer = *(&i)
指针变量的定义和引用
变量值的存取方法(访问方法)
直接访问:按变量名来存取变量值
例 int i;
i = 3; ——直接访问
间接访问:通过变量地址(指针变量)去访问变量
int i, *i_pointer;
i_pointer = &i;
*i_pointer = 20; -----间接访问
指针变量的定义 :[存储类型] +数据类型符 +*变量名;
int *p1, *p2;
float *q;
指针变量的赋值:
1、定义时赋值——[存储类型] 数据类型 *指针名 = 初始地址值;
例 int i;
int *p = &I;(记得指针变量是储存地址,要加取地址符)
int *q = p;——>用已赋值的指针变量作赋值
2、赋值语句赋值
int a;
int *p;
p = &a;
指针变量赋值的几种错误方法:
错误一,不知道/未声明变量及变量类型的情况下直接对指针变量赋值
int *p; (为什么不能直接对pointer赋值?直接赋予一个正确的地址也不行吗?)
p = 2000; (可以!!但是方式不对!!2000是一个数字不是地址,应该
写成(*int)2000这才是一个地址,另外,指针变量是变量,具有变量的性质)
错误二:不理解*单独放出是解引用的意思
int a;
int *p;
*p = &a; (*p 对应的是 p 这个地址里存放的值,不是地址)
应改成:p=&a;
错误三:不同的指针变量类型没有转换直接相互赋值
int a;
int *pi = &a;——————————————>
char *pc = &a; 最后一句应改成
注意:虽然经过强制类型转换后,指针变量的类型和值都改变了,但是这种改变不会影响原地址内存放的数据的类型和内容,只不过,如果原来是整型(int)的指针变量若转换成 char 型的变量指针(即从 4 个 bit 的地址转换成 1 个 bit 的地址),它所能指向的值就只剩下原来的 1 个 bit 的内容.(待考证)
零指针与空类型指针
零指针:(空指针)
-定义: 指针变量值为零
-表示: int * p = 0;
void *类型指针
表示: void *p;
使用时要进行强制类型转换
(待cpp补充)
指针和地址运算
指针变量的加、减运算
指针可以参与加法和减法运算,但其加、减的含义绝对不同于一般数值的加减运算。如果指针p是这样定义的:
ptype *p;并且p当前的值是ADDR,那么: p ± n 的值 = ADDR ± n * sizeof(ptype)
这和数据在内存中存放的方式以及数据本身类型有关系
这就是指针的运算之一:指针与整数的运算,只要加的是个整数,整数就会和指针所指向类型的大小(以sizeof显示的字节大小)相乘,无需自己再费劲苦心的考虑地址加上整数乘大小,实在很方便【通过实机测试 Clion ver.01.2021】如果相加的结果超出了初始指针指向的数组范围,计算结果则是不定义的,除非刚好等于数组最后一个元素之位置,C保证该指针有效。
指针与数组
数组的指针
数组的指针:数组在内存中的起始地址, 或数组变 量名, 或数组第一个元素在内存中的地址。
指向数组的指针变量
如果将数组的起始地址赋给某个指针变量,那么该指针变量就是指向数组的指针变量。
下面是对数组元素赋值的几种方法,它们从功能上是等价的
1、 char str[10];
int k;
for (k = 0; k < 10; k++)
*(str+k) = 'A' + k
2、 char str[10];
int k;
char *p;
p = str;
for (k = 0; k < 10; k++)
*(p+k) = 'A' + k
3、 char str[10];
int k;
char *p;
p = str;
for (k = 0; k < 10; k++)
*p++ = 'A' + k; /*相当于 *p = 'A' + k; p++;*/
注意:数组名是地址常量,切不可对其赋值,也不可做++或--运算。例如:int a[10];如果在程序中出现a++或a--则是错误的。
元素为指针的数组____指针数组
指针数组:用于存储数据地址的数组 数据类型符 *变量名[常量表达式];
例:
char c[3] = {'a', 'b', 'c'};
char *p[3];
p[0] = &c[0];
p[1] = &c[1];
p[2] = &c[2];
【例】利用指针数组对键盘输入的5个整数进行从小到大排序。
#include <stdio.h>
void main ( )
{
int i, j, t;
int a, b, c, d, e;
int *p[5] = {&a, &b, &c, &d, &e};
/*指针变量使用时必须先给其赋地址*/
scanf ("%d,%d,%d,%d,%d", p[0], p[1], p[2], p[3], p[4]);
for (i = 0; i < 4; i++) /*利用比较法排序*/
for (j = i + 1; j < 5; j++)
if (*p[i] > *p[j]) /*交换p[i]、p[j]所指向的变量值*/
{
t = *p[i];
*p[i] = *p[j];
指针与字符串
字符串表示形式—>用字符数组实现
例:
void main ( )
{
char string[] = "I love China! ";
printf ("%s\n", string);
printf ("%s\n", &string [7]);
}
用字符指针实现
例:
void main ( )
{
char *string;
string = "I love China! ";
printf ("%s\n", string);
printf ("%s\n", string+7);
//数组名就是一个指针
//这里是指针和地址的思想
}
字符指针变量与字符数组之比较
char *cp; 与 char str[20];
str是地址常量,不可赋值;cp是地址变量,可赋值。
char str[20]; str="I love China! "; (X)
char *cp; cp="I love China! "; (V)
cp接受键入字符串时,必须先开辟存储空间(先赋地址)
字符指针除了可赋值外,其它方面等同于字符数组
【例】 利用字符指针实现字符串的倒序排列
程序设计思想:
使用两个指针p和q。p指向字符串首地址,q指向字
符串未地址。
如果p<q,交换两指针指向单元的内容,交换后p增1,
指向下一个单元,q减1,指向上一个单元的内容,直到p
大于等于q为止。未地址与首地址间的关系为q=p+strlen(str)-1;
注意:无法使用strlen求字符数组的长度
#include <stdio.h>
void main ( )
{
char str[]="I Love China!", ch;
int i=0;
char *p, *q;
p = str; //p指向字符串的首
while(str[i]) i++;
q = p + i - 1;//q指向字符串的末
while (p < q) { /*交换p和q
ch = *p;
*p = *q;
*q = ch;
p++; q--;
}
printf ("%s\n", str);
}
右边的做法与左边一致,但是采用了
Sizeof求长度从而求末尾之做法,此处有个很有趣的现象,就是左边是p+n-1;而右边则需要-2,其原因待考证;其次,strlen只能用于字符串之求长度,至于为何无法用于同类的字符数组,带考究。
指针与动态内存分配
静态内存分配
系统根据变量或数组定义分配内存单元,这种内存分配方式称为静态内存分配 。
short int k; /*系统将给变量k分配2个字节内存单元*/
char ch[10]; /*系统将给数组ch分配10个字节的内存单元,首地址是数组名ch*/
静态内存分配一般是在知道数据量大小的情况下使用
如果事先并不知道数据的具体数量,编写程序时,数量由用户输入,然后根据数量输入变量之值。那该如何处理呢?
解决的方法有两种:
1、定义大尺寸的数组,如:int score[100]; 问题:内存浪费
2、动态分配内存
根据实际需要来分配一块大小合适的连续的内存单元。
动态内存分配可用函数malloc :
void *malloc( unsigned int size );
功能:分配一个大小为size字节的内存空间,返回该内存空间的首地址。
应用实例:
char i, *p;
p = (char *) malloc(4 * sizeof(char));
for (i = 0; i < 4; i++)
*(p + i) = i + 1; /*等同于p[I]=i+1;*/
3、动态内存释放:分配的内存空间用完要释放
void free (void *block); free( pscore );
(否则造成空间被长期占据,浪费内存资源)
编写程序先输入学生人数,然后输入学生成绩,最后输出学生的平均成绩
#include <stdio.h>
#include <malloc.h>
void main ( )
{
int i, num, *pscore;
float sumscore=0, averscore;
scanf ("%d", &num);
pscore = (int *) malloc(num * sizeof(int));
if ( pscore == NULL)
return;
for (i = 0; i < num; i++)
scanf ("%d", pscore + i);
for (i = 0; i < num; i++)
sumscore = sumscore + pscore[i];
averscore = (float)sumscore / num;
printf ("平均成绩是 %.1f\n", averscore);
free (pscore); /*释放动态分配的内存*/
}
指针作为函数的参数
参数传递方式:传值调用和传址调用
第10章 预处理命令
预处理命令:C源程序中以#开头、以换行符结尾的行
源程序生成执行文件的过程:
C语言源程序 编译 目标程序 链接 执行程序
.c或.cpp。 预处理 .obj .exe
种类:
宏定义 #define、#undef
文件包含 #include
格式:
“#”开头
占单独书写行
语句尾不加分号
宏定义
宏定义分为两种:不带参数的宏定义和带参数的宏定义。
不带参数的宏定义一般形式 ——>用标识符(宏名)表示单词串(宏体)
#define 标识符 单词串
注意:宏替换时仅仅是将源程序中与宏名相同的标识符替换成宏的内容文本,并不对宏的内容文本做任何处理。
宏定义注意事项
1.C程序员通常用大写字母来定义宏名,以便与变量名区别
2.宏定义可嵌套定义【这点容易导致后边混淆,要时刻牢记预处理只是替换】
3.程序中字符串常量即双引号中的字符,不作为宏进行宏替换操作
#define XYZ this is a test
printf(“XYZ"); 输出:XYZ,而不是:this is a test。
4. 宏定义一般以换行结束,不是以分号结束
5.在定义宏时,如果宏是一个表达式,那么一定要将这个表达式用()括起来,否则可能会引起非预期的结果。
6.#undef终止宏名作用域【可通过undefine使一个预处理命令变为局部命令】
带参数的宏定义
#define 标识符(参数列表) 单词串【标识符(参数列表)之间不能有空格】
宏展开:形参用实参换
宏体及各形参外一般应加括号()
例 #define POWER(x) x*x
z=POWER(x+y); z=x+y*x+y;
#define POWER(x) ((x)*(x))
z=POWER(x+y); z=((x+y)*(x+y));
文件包含
功能
一个源文件可将另一个源文件的内容全部包含进来
一般形式
#include <包含文件名> 或 #include “包含文件名”,其中
< >: 直接到系统指定的“文件包含目录”去查找被包含的文件 ;
" “:系统首先到当前目录下查找被包含文件,如果没找到,再到系统指定的“文件包含目录”去查找;
第十一章 复杂数据类型
结构体(结构)
设计程序时,最重要的步骤之一是选择表示数据的方法。在许多情况 下,简单变量甚至是数组还不够。C提供了结构变量(structure variable)提高你表示数据的能力,它能让你创造新的形式。此节将详细介绍C结构。我们先从结构(结构体)的定义开始
结构体是一种构造数据类型(用户自定义数据类型)
用途:把不同类型的数据组合成一个整体
结构声明(structure declaration)描述了一个结构的组织布局。声明类 似下面这样:
struct book
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
};以分号;结尾
其中:
struct是关键字,不能省略
类似于book是合法标识符,我们称book这类为结构布局,则上边的结构体称为具有book结构布局的结构体,它像是一个规范,规定了拥有book标识的结构体的元素构造,它告诉编译器如何表示数据,但是还没有为相应的数据分配空间,它是可省的,然而,如果打算多次使用结构模板,就要使用带标记的形式;省略则为无名结构体
间接定义法:先定义结构类型,再定义结构变量
struct 结构体类型名
{
数据类型名1 成员名1;
… …
数据类型名n 成员名n;
};
struct 结构体类型名 变量名列表;
直接定义法:定义结构体类型的同时定义结构体变量
struct Student_Info
{
char no[9]; //学号
char name[20]; //姓名
char sex; //性别
unsigned int age; //年龄
unsigned int classno; //班级
float grade; //成绩
} student1, student2;
结构体变量的引用
结构类似于一个“超级数组”,这个超级数组中,可以是一个元素为char 类型,下一个元素为forat类型,下一个元素为int数组。可以通过数组下标单独访问数组中的各元素,那么,如何访问结构中的成员?使用结构成员运算 符——点(.)访问结构中的成员。
结构体变量不能整体引用,只能引用变量成员
结构体变量的赋值
结构体变量初始化赋值
先定义结构体类型,再定义结构体变量时赋初值
struct 结构体类型名 初值表
{ … … };
struct 结构体类型名 变量名 = {成员1的值, …, 成员n的值};
定义结构体类型的同时,定义结构体变量并赋初值
struct [结构体类型名]
{ 初值表
… …
} 变量名 = {成员1的值,成员2的值, …, 成员n的值};
struct Student_Info
{
char no[9]; /*学号*/
char name[20]; /*姓名*/
char sex; /*性别*/
unsigned int age; /*年龄*/
unsigned int classno; /*班级*/
float grade; /*成绩*/
} student = {"20020306", "ZhangMing", 'M', 18, 1, 90};
结构体变量的赋值
对其成员逐一赋值,或者用已赋值的结构体变量对它赋值
【例】 计算学生5门课的平均成绩
结构体数组
结构体数组的引用
引用格式:结构体数组名[下标].成员名;
第12章 文 件
文件(file)通常是在磁盘或固态硬盘上的一段已命名的存储区。对我 们而言,stdio.h就是一个文件的名称,该文件中包含一些有用的信息。C把文件看作是一系列连续的字节,每个字节都能被单独读取。C提供两种文件模式:文本模式和二进制模式。
文件分类
文本文件: ASCII文件,每个字节存放一个字符的ASCII码
文本文件特点:存储量大、速度慢、便于对字符操作
二进制文件:数据按其在内存中的存储形式原样存放
二进制文件特点:存储量小、速度快、便于存放中间结果
文件操作
读文件:将磁盘文件中的数据传送到计算机内存的操作。
写文件:从计算机内存向磁盘文件中传送数据的操作。
文件操作的一般步骤
1、打开文件,获取文件指针2、利用文件指针对文件读写3、关闭文件,释放文件指针
文件的打开、读写和关闭
标准文件的打开和关闭由系统自动完成:
标准输入------键盘 文件指针:stdin
标准输出------显示器 文件指针: stdout
其它文件的打开与关闭由fopen和fclose完成
打开文件fopen: (获取文件指针)
FILE *fopen (char *filename, char *mode【打开方式】);
r----表示从文件中读取数据(read), t-----表示文本文件(text,默认方式),
W----表示向文件写入数据(write), b----表示二进制文件(binary),
a----表示在文件尾追加数据(append),
+----表示文件可读可写。
注意:字符串mode中字符先后次序
1、操作类型符在前,打开文件类型符在后
如,“rb”、“wt”,不可写成“br”、“tw”。
2、 “ + ”只能放在操作类型符的右边
如, “r+” 、“w+t”、“wb+”, 不可写成“+r” 、“+wt”、“+wb” 。
关闭文件fclose
int *fclose (FILE *fp);
使文件指针变量与文件“脱钩”,释放文件指针
FILE *fp;
fp = fopen ("wang.txt", "r+");
if (fp = = NULL)
{
printf ("the file :wang.txt not found! ");
exit (-1);
}
… … //读取和加工数据
fclose (fp); //关闭该文件
文件的读写
C语言提供了多种文件读写的函数,函数有:
格式化读写函数: fscanf 和 fprinf
字符读写函数: fgetc 和 fputc
字符串读写函数:fgets 和 fputs
数据块读写函数:fread 和 fwrite
格式化读写函数:fscanf 和 fprinf
fscanf
int fscanf ( FILE *fp, const char *format[,变量地址,…]);
功能:从fp所指向的文件中读取数据。
fprintf
int fprintf ( FILE *fp, const char *format[,表达式,…]);
功能:将表达式输出到fp所指向的文件中。
fscanf (fp, "%d,%f", &i, &t); //若文件中有3, 4.5,则将3送入i,4.5送入t
fprintf (fp, "%d,%6.2f", i, t); //将i和t按%d, %6.2f格式输出到fp文件
将一个3*3的二维数组写入到文件Data.txt中
文件的读写
(2) 字符读写函数:fgetc 和 fputc
fgetc
int fgetc (FILE *fp);
功能:从fp指向的文件中,读入一个字节(字符)
返回值:正常,返回读入字符的ASCII值;
读到文件尾或出错,返回值为EOF
fputc
int fputc (int c, FILE *fp);
功能:输入字符且将字符数据c输出到fp所指向的文件中去。
(待补充)
2021.6.3开始准备初稿
2021.7.2初稿完成
2021.7.3-7.31结合《C Primer Plus》完成二稿
初稿定名
《看懂C语言程序设计基础第二版》
Menou
All rights reserved
Zhili Lou .SZU. Shenzhen. China
References:
1、《C语言程序设计基础第二版》 王敬华 著
2、《C Primer Plus》(第6版)中文版/(美)普拉达(Prata,S.)著;姜佑译.
C语言程序设计教程(Ver 2.0)Ver.王敬华 讲授:刘宗香
第一章 C语言程序设计的预备知识
第二章 C语言程序设计基础
原码反码和补码
1、负数的补码(符号位不变其与按位取反)等于反码加一。正数三码相同
数制转换
十进制转二进制
整数转换:
1.首先用2整除一个十进制整数,得到一个商和余数
2.然后再用2去除得到的商,又会得到一个商和余数
3.重复操作,一直到商为小于1时为止
4.然后将得到的所有余数全部排列起来,再将它反过来(逆序排列),切记一定要反过来!
小数转换
采用"乘2取整,顺序排列"法:
1.用2乘十进制小数,可以得到积,将积的整数部分取出
2.再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出
3.重复操作,直到积中的小数部分为零,此时0或1为二进制的最后一位,或者达到所要求的精度为止
例如将0.125转换为二进制:
0.125 * 2 = 0.25 ------0
0.25 * 2 = 0.5 ------0
0.5 * 2 = 1.0 ------1
当小数部分为0就可以停止乘2了,然后正序排序就构成了二进制的小数部分:0.001
如果小数的整数部分有大于0的整数时,将整数部分和小数部分先单独转为二进制,
再合在一起就可以了,例如:
假设要将8.125 转换为二进制
现将8转为二进制:得到1000
再将0.125转为二进制:得到0.001
合并后为1000.001
关键 ASCII 码总结
0000 0000 |
0 |
0 |
0 |
NUL(null) |
空字符 |
0011 0000 |
60 |
48 |
30 |
0 |
字符0 |
0100 0001 |
101 |
65 |
41 |
A |
大写字母A |
0110 0001 |
141 |
97 |
61 |
a |
小写字母a |
常见ASCII码的大小规则:0~9<A~Z<a~z。
1)数字比字母要小。如 “7”<“F”;
2)数字0比数字9要小,并按0到9顺序递增。如 “3”<“8” ;
3)字母A比字母Z要小,并按A到Z顺序递增。如“A”<“Z” ;
4)同个字母的大写字母比小写字母要小32。如“A”<“a” 。
几个常见字母的ASCII码大小: “A”为65;“a”为97;“0”为 48 。
二进制位运算
一,位运算基础
位运算(包括与,或,取反,异或,左移,右移等)是程序设计中的一个重要的领域。
1,与运算:&
与运算的操作符为&。2个数进行与运算时,就是将这2个数的二进制进行与操作, 只有当2个数对应的位都为1,该位运算结果为1,否则运算结果为0。即:1&1=1;1&0=0;0&0=0.
比如计算15&10,首先15的二进制为:1111,10的二进制为1010,所以15&10为:
2,或运算:|
或运算的操作符为|。2个数进行或运算时,就是将这2个数的二进制进行或操作, 只要2个数对应的位有一个为1,该位运算结果为1,否则运算结果为0。即:1|1=1;1|0=1;0|0=0.
比如计算15&10,首先15的二进制为:1111,10的二进制为1010,所以15|10为:
所以15|10=15。
1、参与运算的两位只要有一
位为1,结果就为1;
2、只有两位同时为0,结果才
为0。
3,异或运算:^
异或运算的操作符为^。2个数进行异或运算时,就是将这2个数的二进制进行异或操作, 只要2个数对应的位相同,该位运算结果为0,否则运算结果为1。即:1^1=0;1^0=1;0^0=0.
比如计算15^10,首先15的二进制为:1111,10的二进制为1010,所以15^10为:
所以15^10=5。
4、“非”运算(~)
按位取反
计算机使用补码的形式来表示一个数。因为采用补码时符号位能和数据位一样参加运算,这便于运算器的设计和实现。
第二章:C程序特点
C程序是由多个函数构成的。
每个C程序中有且只有一个main函数。
main函数是程序的入口和出口。
不使用行号,无程序行的概念。
程序中可使用空行和空格。
引用C语言标准库函数(见附录B),一般要用文件包含预处理命令将其头文件包含进来。
用户自定义的函数,必须先定义后使用。
变量必须先定义后使用。
变量名、函数名必须是合法的标识符,标识符习惯用小写字母,大小写敏感。
不能用关键字来命名变量和函数。
函数包含两个部分:声明部分和执行部分,在C程序中,声明部分在前,执行部分在后,这两部分的顺序不能颠倒,也不能有交叉。
C语言的语句都是以分号结尾。
计算机程序设计语言的发展,经历了从机器语言,汇编语言,高级语言的发展过程;
机器唯一可识别的语言是机器语言;
C语言属于高级语言;
C语言程序可以在不同的操作系统运行,反映其良好的可移植性;
一个C程序是由许许多多函数组成的;而main函数位置可以任意;但程序的入口总是main
C源程序的最小单位是字符
编制C语言程序的基本步骤
编 辑:程序代码的录入,生成源程序*.c
|
编 译:语法分析查错,翻译生成目标程序*.obj
|
链 接:与其它目标程序或库链接装配,生成可执行程序*.exe
|
运 行
第三章:基本数据类型、运算符与表达式
1. 标识符
定义:用来标识变量、常量、函数等的字符序列
组成:
只能由字母、数字、下划线组成,且第一个字母必须是字母或下划线
C语言的关键字不能用作变量名
大小写敏感
2. 常量
定义:程序运行时其值不能改变的量(即常数)
常量的分类 :
值常量
整型常量: 10、15、-10、-30
实型常量: 12.5、 30.0、-1.5
字符常量: 'A'、'b'、'c'
字符串常量: "sum"、"A"、"123"
符号常量
用标识符来代表常量。其定义格式为:
#define 符号常量 常量
3. 变量
定义:其值可以被改变的量
变量的两要素 :变量名 、变量值 int a, b = 2; float data;
整型变量
基本型
类型说明符为int,在内存中占4个字节,其取值为基本整常数。4个字节,范围:0 — 2^32-1(32bit system)
短整型
类型说明符为short int。占2个字节。short int -32768~32767(sign)-2^15 — 2^15-1(0 include)
整型
类型说明符为long int或long ,在内存中占4个字节,其取值为长整常数。在任何的编译系统中,长整型都是占4个字节。在一般情况下,其所占的字节数和取值范围与基本型相同。
无符号型
类型说明符为unsigned。在编译系统中,系统会区分有符号数和无符号数,区分的根据是如何解释字节中的最高位,如果最高位被解释为数据位,则整型数据则表示为无符号数。
实型变量
单精度实型(float)
float f = 3.14, g; /*占4个字节(32位)*/
双精度实型(double)
double x, y; /*占8个字节(64位)*/
字符型常量
定义:用单引号括起来的单个普通字符或转义字符.
字符常量的值:该字符的ASCII码值
字符型变量char
占1个字节(8位)
通常用于存放字符ASCII码
可与int数据进行算术运算
存在有符号和无符号之分。默认情况下为有符号
字符串常量
定义:用双引号("")括起来的字符序列
存储:每个字符串尾自动加一个 '\0' 作为字符串结束标志
字符常量与字符串常量不同
关于printf()函数
%d:用于有符号整型数据,如int、short型数据;
%f:用于实型数据,如float、float型数据;
%c:用于字符型数据,如char型数据;
%s:用于字符串数据。
C之运算符
运算符的分类:
单目运算符:只带一个操作数的运算符。如:~运算符。
双目运算符:带两个操作数的运算符。如:*、/运算符。
三目运算符:带三个操作数的运算符。
自增、自减运算符++ --
作用:使变量值加1或减1。(只能用于变量)
种类:
前置 ++i, --i (先执行i=i+1或i=i-1,再使用i值)
后置 i++,i-- (先使用i值,再执行i=i+1或i=i-1)
位运算符:按位与(&)、按位或(|)、按位取反(~)、按位异或(^)、左移(<<)、右移(>>)六种。
左移(<<)
将变量对应的二进制数往左移位,溢出的最高位被丢掉,空出的低位用零填补。
a << n 变量a向左移动n位
右移同理
位运算符实例
将short类型数据的高、低位字节互换
#include <stdio.h>
void main ( )
{
short a = 0xf245 , b, c;
b = a << 8 ; //将a的低8位移到高8位赋值给b,b的值为0x4500
c = a >> 8 ; //将a的高8位移到低8位赋值给c,c的值为0xfff2
c = c & 0x00ff; //将c的高8位清0后赋值给c,c的值为0x00f2
a = b + c; //将b和c的值相加赋值给a,a的值为0x45f2
printf ("a = %x", a);
}
习题选
第四章 基本输入、输出和 顺序程序设计
格式化输出printf
有符号整数的输出 %[-] [+] [0] [width] [l] [h] d
[ ]:表示可选项。
-:左对齐(缺,右对齐)。
+ :输出正数,数前加+号(缺,不加)
0 :右对齐时,数据宽度小于width,左边补0(缺,补空格)。
width:数据显示的最小宽度。若实际宽度超出,按实际宽度输出。
(缺,按数据实际宽度输出)
字母l:d前加字母l(long), 按长整型格式输出数据。
字母h:d前加字母h(short),按短整型输出数据。
scanf函数的格式控制符
%[width] [l | h] Type
[ ]:表示可选项,可缺省。|表示互斥关系。
width:指定输入数据的域宽,遇空格或不可转换字符则结束。
Type:各种格式转换符(参照printf)。
l:用于d、u、o、x|X前,指定输入为long型整数;用于e|E、f前,指定输入为double型实数。
h:用于d、u、o、x|X前,指定输入为short型整数。
程序的控制结构
程序 = 数据结构 + 算法。
算法:就是解决问题的方法与步骤。
程序:算法的具体实现。
学习C语言,不仅要熟练掌握数据结构、语法规则,更重要的要掌握分析问题、解决问题的方法。
算法是程序的灵魂。
伪码是一种计算机不能识别、人能看懂的代码。该代码易于转换为计算机程序。
顺序程序设计举例
【例1】任意从键盘输入一个三位整数,要求正确地分离出它的个位、十位和百位数,并分别在屏幕上输出。
程序设计的分析:
设计算法前,先要确定分离个位、十位和百位数的方法。
可用求余法(假设a=456)。
个位:b0=a%10; (a=456, b0=6)
十位:a=a/10; b1=a%10; (a=45, b1=5)
百位:a=a/10; b2=a%10; (a=4, b2=4)
根据以上的分析,这个程序应这样设计:
(1) 定义一个整型变量x,用于存放用户输入的一个三位整数;再定义三个整型变量b0、b1、b2,用于存放计算后个位、十位和百位数。
(2) 调用scanf函数输入一个三位整数。
(3) 利用上述计算方法计算该数的个位、十位和百位数。
(4) 输出计算后的结果。
#include <stdio.h>
void main ( )
{
int x, b0, b1, b2; //变量定义
printf ("输入一个三位整数 : "); //提示用户输入一个整数
scanf ("%d", &x); //输入一个整数
b0 = x % 10; //用求余数法计算最低位
x=x/10; b1=x%10;
x=x/10; b2=x%10;
printf ("bit2 = %d, bit1 = %d, bit0 = %d\n", b2, b1, b0); //输出结果
}
第五章 选择结构程序设计
C程序中语句的分类
表达式语句
由表达式加上分号“;”组成。其一般形式为:表达式;
函数调用语句
由函数名、实际参数加上分号“;”组成。其一般形式为:函数名(实际参数表);
空语句
只有分号“;”组成的语句称为空语句。空语句是什么也不执行的语句。在程序中空语句可用来作空循环体。
复合语句
用 {…}括起来的一组语句。 一般形式为:
{ [数据说明部分;]
执行语句部分;
}
控制语句
用来实现一定的控制功能的语句称为控制语句 。
C语言用控制语句来实现选择结构和循环结构。
C语言有九种控制语句。可分成以下三类:
关系运算符、逻辑运算符、条件运算符
关系运算符
> (大于) >= (大于或等于)
< (小于) <= (小于或等于) 等优先级,比下面的优先级高 左结合性
== (等于) != (不等于) 等优先级,比上面的优先级低
关系表达式
用关系运算符连接起来的式子称为关系表达式。
例:a + b > c – d
x > 3 / 2
使用关系表达式注意:
(1)关系表达式的值不是0就是1,0表示假,1表示真。
(2)关系运算符的优先级相对较高,位于移位之后。
(3)关系表达式本身是表达式,因此,下达表达式合法
a <= x <= b 5 > 2 > 7 > 8
(左结合性,分部判断,但前半判断后即为表达式之结果,即0或者1,再予后者进行判断)
逻辑运算符和逻辑表达式
用逻辑运算符连接起来的式子称为逻辑表达式。
(1) 逻辑表达式表达式的值只能是0和1。
(2) 逻辑表达式求解时,并非所有的逻辑运算符都被执行。【节省算力】
a && b && c //只在a为真时,才判别b的值;//只在a、b都为真时,才判别 c的值
a || b || c //只在a为假时,才判别b的值;只在a、b都为假时,才判别 c的值
C控制语句:分支和跳转(选择结构程序设计)
If语句
If语句可嵌套
C语言规定,在缺省{ }时,else总是和它上面离它最近的未配对的if配对
Switch语句
第6章 循环结构程序设计
C控制语句:循环
紧记着循环控制变量需要一定要更新
while语句 (不确定循环次数未定,为假即结束)
while (表达式) 先判断表达式,再执行循环体
循环体语句;
(1) 如果while后的表达式的值一开始就为假,循环体将一次也不执行。
(2) 循环体中的语句可为任意类型的C语句。
(3) 遇到下列情况,退出while循环:
表达式为假(为0)。
循环体内遇到break、return。
do_while语句(出口条件循环,每次循环后检查条件,符合即跳出)
do 特点:先执行循环体,再判断表达式
循环体语句; while最后面的分号;不能省
while(表达式);
(1) 如果do-while后的表达式的值一开始就为假,循环体还是要执行一次。
(2)在if语句、while语句中,表达式后面都不能加分号,而在do-while语句的表达式后面则必须加分号,否则将产生语法错误。
其它同while
For语句(计数循环,即循环次数随条件语句确定)
for (表达式1;表达式2;表达式3)
循环体语句
for语句很好地体现了正确表达循环结构应注意的三个问题:
1、控制变量的初始化。
2、循环的条件。
3、循环控制变量的更新。
表达式1:一般为赋值表达式,给控制变量赋初值。
表达式2:关系表达式或逻辑表达式,循环控制条件。
表达式3:一般为赋值表达式,给控制变量增量或减量。
for语句注意事项:
(1) 表达式1、表达式2、和表达式3可以是任何类型的表达式。比方说,这三个表达式都可以是逗号表达式,即每个表达式都可由多个表达式组成。
(2) 表达式1、表达式2、和表达式3都是任选项,可以省掉其中的一个、两个或全部,但其用于间隔的分号是一个也不能省的。
(3) 表达式2如果为空则相当于表达式2的值是真 。【进入循环的条件】
(4) 循环体中的语句可为任意类型的C语句。
(5) for语句也可以组成多重循环,而且也可以和while语句和do-while语句相互嵌套。
(6) 循环体可以是空语句。
例:计算用户输入的字符数(当输入是回车符时统计结束)。
#include <stdio.h>
void main ( )
{
int n = 0;
printf ("input a string:\n");
for ( ; getchar( ) != '\n'; n++) ;
printf ("%d",n);
}
如何选择循环?
首先,判断需要用循环实现的目标,达成条件是什么?需要达到达成条件的工作次数确定吗?不确定,我们可以用什么来判断它达成目标?
然后,根据目标,我们可以选择入口式循环(for,while),入口式两种可互相替代,而出口式循环更注重结果,即达成条件,不注重循环次数,较为简洁。循环涉及到变量初始化,变量更新时,for会更合适
break与continue语句
break语句
在循环语句和switch语句中,终止并跳出循环体或开关体
(1) break仅用于循环语句和switch语句中。
(2) break只能终止并跳出最近一层的结构。
在嵌套循环的情况下,如何让break语句跳出最外层的的循环体?
for (…)
{
while (…)
{
……
if (…) break;
…
}
while循环后的第一条语句
}
实现方法:通过设置一标志变量tag,然后在每层循环后加上一条语句:if (tag) break; 其值为1表示跳出循环体,为0则不跳出。
continue语句
结束本次循环,跳过循环体中continue后尚未执行的语句,进行下一次循环。
(1) 仅用于循环语句中。
(2) 在嵌套循环的情况下,continue语句只对包含它的最内层的循环体语句起作用。
单元实验:水仙花数
(第三个程序)输出所有水仙花数。
所谓“水仙花数”是指一个三位数,其各位数字立方和等于该数本身。例如,153是一个水仙花数,因为153=13+53+33。
Resource Code
int main()
{
int a,b,c,i;
for(i=100;i<=999;i++)
{
a=i%10;
b=(i/10)%10;
c=(i/100)%10;
if(i==a*a*a+b*b*b+c*c*c)
printf("%d\n",i);
}
}
第七章 数组
用const定义数组需要在定义时初始化且,在后续使用中对这个数组是“只读”
一维数组
定义方式:
数据类型符 数组变量名[整型常量];
- 数组定义时,必须指定数组的大小(长度),大小必须是整型常量,不能是变量或变量表达式。
- 数组定义后,系统将给其分配一定大小的内存单元。数组所占内存单元= 数组大小 × sizeof(元素类型)short int a[20]; 则数组a所占内存单元的大小为:20 * sizeof(short) = 20 * 2 = 40(字节)。
- 占用内存中连续的存储单元,其中第一个数组元素的地址是数组的首地址。
一维数组的引用
数组变量名[下标];记得需要传地址操作时,数组整体可以用首项地址/数组名
而需要对数组特定元素或者从特定元素开始传址操作时,记得加取地址符号&或者使用首地址指针加减
(2) 只能逐个引用数组元素,不能一次引用整个数组!!
(3) 数组中的一个元素相当于一个变量,数组元素也称下标变量。对变量的一切操作适合于数组元素。
(4) 数组引用要注意越界问题。
一维数组的赋值
1、直接赋值
(4) 如果表达式的个数小于数组的大小,则未指定值的数组元素被赋值为0;
在定义数组时,如果不给数组变量赋初值,数组大小不能省略
2、通过循环来赋值
排序(冒泡、选择,)
冒泡排序法:
排序过程:(实现从小到大排序)
(1) 第一趟冒泡排序,将n个数中最大的数被安置在最后一个元素位置上;
方法:比较第一个数与第二个数,若为逆序a[0]>a[1],则交换;然后比较第二个数与第三个数;依次类推,直至第n-1个数和第n个数比较为止。结果最大的数被安置在最后一个元素位置上。依次比较,可用循环。
(2) 对前n-1个数进行第二趟冒泡排序,结果使次大的数被安置在第n-1个元素位置;
(3) 重复上述过程,共经过n-1趟冒泡排序后,排序结束【每一个元素都与其之前的数遍历,而因为它之后的数已经与元素比较然后在合适的位置上,因此实际上元素与全集进行了比较】
for (i = 1; i < NUM; i++) //趟数,共NUM-1趟
for (j = 0; j < NUM - i; j++) //实现一次冒泡操作
if (a[j] > a[j+1]) //交换a[j]和a[j+1]
{
t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
选择排序法:
排序过程:
(1) 首先通过n-1次比较,从n个数中找出最小的(最小数的下标), 将它与第一个数交换—第一趟选择排序,结果最小的数被安置在第一个元素位置上;
(2) 再通过n-2次比较,从剩余的n-1个数中找出次小的(
次小数的下标),将它与第二个数交换—第二趟选择排序;
(3) 重复上述过程,共经过n-1趟排序后,排序结束。
【找出下标,而没有交换数组的元素】
【例4】 用数组求Fibonacci数列 前20个数
#include <stdio.h>
void main( )
{
int i;
int f[20] = {1, 1};
for (i = 2; i < 20; i++)
f[i] = f[i-2] + f[i-1];
for (i = 0; i < 20; i++)
{
if (i % 5 == 0)
printf ("\n");
printf ("%12d", f[i]);
}
}
二维数组及多维数组
1、 二维数组的定义
定义方式:
数据类型 数组名[常量表达式1][常量表达式2];
2、二维数组元素的引用
形式: 数组名[下标1][下标2]
3、二维数组元素的初始化
分行初始化:也可以通过循环初始化/赋值;
字符串与数组
1、字符串的本质
字符串是一种以’\0’结尾的字符数组。
字符及字符串操作的常用函数
I (input) 输入
1、gets()函数
char str[80];
gets (str);
当输入:I□love□china!↙(□表示空格,↙表示回车)时,str中的字符串将是:“I love china!”
‘□’是字符串中的一部分【可输入空格,即,更像是输入一句话“sentence”】
缺点:gets函数的缺点,正是源自于它的优点,gets较于scanf函数的繁琐步骤【转换说明符】以及局限性【读词不读句】,gets无需复杂的转换说明,因为它是一个针对性极强的函数,而且gets读句,无需如scanf读句【本质上是读多个连在一起的词】要使用复数多个说明符,它简单易用,但就是因为gets函数对读取的字符串(字符数组)的元素没有限制,导致其有可能因为元素过多导致栈溢出【缓存区溢出(buffer overflow),栈溢出的一种】,从而威胁到数据安全,因此许多IDE都会针对可能导致栈溢出的函数进行警告报错甚至在库中删除/禁用该类函数。Write By Menou 2021.6.30
替代品:fgets(),gets_s()等,前者应用广,后者为C11可选拓展
通常情况下,现阶段fgets是最佳选择
2、scanf()函数
char str[80];
scanf ("%s", str);
当输入:I□love□china↙时,str将是:"I" ,
‘□’被当作分隔符【不可输入空格,即,更像是输入一个单词“word”】
当然也可以在转换标识符处添加读取字符串长度的最长限制或者最小限制(少则空格补位)
O(output)输出
1、puts函数
格式:puts(字符串地址) /*头文件为stdio.h*/
功能:向显示器输出字符串(输出完,换行)
例:char str[ ] = "I love china! “;
puts (str);
2、 printf函数
格式:printf("%s", 字符串地址) /*头文件为stdio.h */
功能:依次输出字符串中的每个字符直到遇到字符’\
例:char name[ ] = "John Smith";
printf ("%s\n", name);
printf ("%s\n", &name[5]);
配套fputs()//针对文件,输出屏幕-fputs(name,stdout)【标准输出】
字符串的长度
strlen函数
格式:strlen(字符串地址) /*文头件为string.h */
功能:计算字符串长度
字符串的复制:
strcpy (字符串1, 字符串2)【只是简单的copy,无法检测第一个数组能否容纳第二个数组】
char str1[20], str2[20];
scanf ("%s", str2);
strcpy (str1, str2); /*将str2复制到str1中*/
换而言之,strcpy也是一个可能导致栈溢出的函数
更谨慎的选择:
Strncpy函数,该函数前两个参数与上一个函数相同,新增的第三个参数指定了最大可拷贝字符数。
字符串比较:
strcmp (字符串1, 字符串2)
功能:比较两字符串的大小
返回值:a. 若字符串1< 字符串2, 返回负整数
b. 若字符串1> 字符串2, 返回正整数
c. 若字符串1== 字符串2, 返回零
同样的,strncmp是一个可以指定比较前n个字符的字符数组比较用函数
字符串的连接:
strcat (字符数组1, 字符数组2)【只是简单的拼接,无法检测第一个数组能否容纳第二个数组】
功能:把字符数组2连到字符数组1后面,形成新字符串
例:char str1[20] = "12345", str2[ ] = "6789";
strcat (str1, str2);
printf ("%s", str1);
换而言之,strcat也是一个可能导致栈溢出的函数
对应的解决方案:
Strncat函数,该函数前两个参数与上一个函数相同,新增的第三个参数指定了最大添加字符数。
字符串数组
是一个字符型的二维数组,该数组的每一行存放一个字符串
初始化:可以用二维数组初始化的方式初始化;char city[][10] = {{'B', 'e', 'i', 'J', 'i', 'n', 'g', '\0'},
{'S', 'h', 'a', 'n', 'g', 'H', 'a', 'i', '\0' }};
采用下列方法初始化;
char city[][10] = { "BeiJing", "ShangHai"};
第八章 函数
模块化编程
函数的概念与分类
函数其实就是一段可以重复调用的、功能相对独立完整的程序段。
具体分为:
无参数无返回值 void name (void)
无参数有返回值 int name (void) [int 可替换]
有参数无返回值 void name ()
有参数返回值 int name () [int 可替换]
函数参数的传递方式
根据实参传递给形参值的不同,通常有值传递方式和地址传递方式两种。
1、传值
函数调用时, 实参的值复制到形参中,存放于形参自已的单元中;调用结束,形参单元被释放,实参单元中的值不变。
特点:
① 形参与实参占用不同的内存单元
② 单向传递:调用者能向被调用者传数,被调用者不能向调用者传数
2、传址
方式:
函数调用时,将数据的存储地址作为参数传递给形参
特点:
① 形参与实参占用同样的存储单元(实参单元)
② 双向传递:调用者可向被调用者传数,被调用者也能向调用者传数
方法:
① 数组作为函数的参数
② 指针作为函数的参数(第9章)
用数组名作为函数参数时注意:
形参数组和实参数组的类型必须一致。
形参数组和实参数组的长度可以不相同,因传送的只是首地地址。
多维数组作为函数的参数时,第一维长度可省
例: 将任意两个字符串连接成一个字符串
#include <stdio.h>
void mergestr (char s1[ ], char s2[ ]);
void main ( )
{
char str1[40] = {"Hello "}; /*str1定义的要足够大*/
char str2[ ] = {"china!"};
mergestr (str1, str2);
printf ("%s\n", str1);
}
void mergestr (char s1[ ], char s2[ ])
{
int i, j;
for (i = 0; s1[i] != '\0'; i++) /*找到s1的结束标志'\0'; */
;
for (j = 0; s2[j] != '\0'; j++) /*将s2复制到s1的后边*/
s1[i+j] = s2[j];
s1[i+j] = '\0'; /*置字符串结束标志*/
}
变量的作用域和生存期
1、作用域和生存期的基本概念
*变量的作用域
即变量的作用范围(或有效范围,有效空间)。
按其作用域范围可分为两种:即局部变量和全局变量
*变量的生存期
即变量占用内存的时间。
按其生存期可分为两种:即动态变量和静态变量
动态变量和静态变量之区别
1)静态存储变量通常是在变量定义时就分定存储单元并一直保持不变,直至整个程序结束。静态变量,全局动态变量都是静态存储
2)动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放
3)静态存储变量是一直存在的,而动态存储变量则时而存在时而消失。通常把由于变量存储方式不同而产生的特性称为变量的生存期
4)静态存储只会初始化一次
函数的嵌套与递归调用
函数可以嵌套调用函数
函数递归调用:
函数直接或间接的调用自身叫函数的递归调用
【例1】递归的执行情况分析
#include <stdio.h>
void print (int w);
void main ( )
{
print ( 3 );
} 图-8-1 解释递归原理流程图
void print (int w) /*递归函数*/
{
int i;
if ( w != 0) /*递归结束条件, 若W = 0,递归结束*/
{
print (w-1);
for (i = 1; i <= w; ++i)
printf ("%d ", w);
printf ("\n");
}
}
为何程序执行结果是这样呢?而不是倒过来的三角形呢?
这涉及到递归计算的问题,如程序,当w=3时,只要w不等于0,if条件语句就会一直进入,假如我们把print ( 3 )看作是对这个函数的一级递归调用,那么在进入if后,立即就会产生对此函数的二级递归调用print ( 2),依次类推,而print(0)作为第四级调用其实也产生了,只不过值为void?【for循环进入条件不满足】,而运算优先级是从*递归到最低级递归,第三级递归就是print(1)w1=1,同理w2=2,w3=3,而根据for循环决定个数。
递归优秀案例:猴子吃桃问题
/*循环求第一天桃子*/
#include <stdio.h>
int peach_n( ) ;
void main ( )
{
printf ("%d",peach_n( ));
}
int peach_n( )
{
int i, a0=1;
for(i=9; i>=1; i--)
a0=2*(a0+1);
return a0;
}
Tower of Hanoi问题(汉诺塔问题)
问题描述:有A,B,C三个塔座,A上套有n个直径不同的圆盘,按直径从小到大叠放,形如宝塔,编号1,2,3……n。要求将n个圆盘从A移到C,叠放顺序不变,移动过程中遵循下列原则:
1、每次只能移一个圆盘
2、圆盘可在三个塔座上任意移动
3、任何时刻,每个塔座上不能将大盘压到小盘上
#include <stdio.h>
void move(char getone, char putone)
{
printf("%c--->%c\n", getone, putone);
}
void hanoi(int n,char A,char B, C)
{
if(n==1) /*递归结束条件*/
move(A,C);
else
{
hanoi(n-1, A, C, B); /*从A移到B,C为中介*/
move(A, C);
hanoi(n-1, B, A, C); /*从B移到C,A为中介*/
}
}
第八章编程题精选
// homework1 编写函数Mystrcopy(char str1[],char str2[])将字符串str2复制到str1中(不允许使用strcpy)
//20:23--21:16 done
void stringcopy(char str1[],char str2[])
{//自定义字符串拷贝函数
int i=0;
while(str2[i]!='\0')
{
str1[i]=str2[i];
i++;//21:12 bug fixed
}
//str1[i]='\0'; used for what?
}
int main()
{
char str1[20],str2[20];
//gets(str1);//输入字符串1
scanf("%s",&str1);
//gets(str2);//输入字符串2
scanf("%s",&str2);
stringcopy(str2,str1);//将字符串1拷贝到字符串2
printf("%s\n",str2);
printf("%s",str1);
//puts(str2);//输出结果
}
//homework2 编写一函数count(int n)计算13 + 23 + …+ n3 ,函数返回值为计算结果.
//5.18 Thu. 21:24 begin---21:30 first try---done!
int count(int n);//声明函数
int main()
{
int n;
printf("enter the number");
scanf("%d",&n);
count(n);
printf("%d",count(n));//输出模块
}
int count(int n)//定义函数
{
int i,all=0;
for(i=1;i<n+1;i++)
{
all+=i*i*i;
}
return(all);
}
//homework 3 编一函数Mystrcmp(char s1[],char s2[]),不使用strcmp函数但完成与strcmp类似的功能。
// 若s1<s2,返回-1;
// 若s1=s2,返回0;
// 若s1>s2,返回1。
//21:45 begin
//21:56 done-->bug 13
//22:11--->fix bug--->fail error
//22:33 first can run!--->fail!-->无法进入循环
//22:46 second run but fail-->仅仅比较首字母,循环失效原因不明
//23:11 finally done ! 原因查明,是return 0会跳出for循环
#include <string.h>
int Mystrcmp(char s1[],char s2[]);//声明函数
int main()
{
char s1[100],s2[100];
int l;
printf("please enter 2 string:\n");
scanf("%s",s1);
scanf("%s",s2);
l=strlen(s1)>strlen(s2)? strlen(s2): strlen(s1);
printf("%d\n",l);
printf("%d\n",Mystrcmp(s1,s2));
}
int Mystrcmp(char s1[],char s2[])
{
int i;
int l;
l=strlen(s1)>strlen(s2)? strlen(s2): strlen(s1);
for(i=0;i<l+1;i++)
{
if(s1[i]<s2[i])
{
return -1;
}
else if(s1[i]>s2[i])
{
return 1;
}
}
return 0;
}
2021.5.19 19:18 homework 4
//4、编写一函数 CheckNum(int m),用来判断一个数是否为素数,
// 是返回值为1,不是返回值为0。
//19:29 first try--->done!;19:34 improve the code;
//补充:
// 思路1):因此判断一个整数m是否是素数,只需把 m 被 2 ~ m-1 之间的每一个整数去除,
// 如果都不能被整除,那么 m 就是一个素数。
// 思路2):m 不必被 2 ~ m-1 之间的每一个整数去除,只需被 2 ~ 之间的每一个整数去除就可以了。
// 如果 m 不能被 2 ~ 间任一整数整除,m 必定是素数。
// 例如判别 17 是是否为素数,只需使 17 被 2~4 之间的每一个整数去除,由于都不能整除,可以判定 17 是素数。
// 原因:因为如果 m 能被 2 ~ m-1 之间任一整数整除,
// 其二个因子必定有一个小于或等于 ,另一个大于或等于 。
// 例如 16 能被 2、4、8 整除,16=2*8,2 小于 4,8 大于 4,16=4*4,4=√16,
// 因此只需判定在 2~4 之间有无因子即可。
/*
int CheckNum(int m);//函数声明
int main()
{
int n;
printf("This is a program used to judge one digital whether it's an prime number\n");
printf("now please input the number:");
scanf("%d",&n);
CheckNum(n);
printf("now press any key to check the result\n");
getchar();
getchar();
if(CheckNum(n)==1)
printf("this number is an primer number!\n");
else
printf("this number isn't an primer number!\n");
return 0;
}
int CheckNum(int m)//函数定义
{
int j;
for(j=2;j<m;j++)
{
if(m%j==0) break;//此处没有必要将运算单独写出,直接在if判断内写出运算即可
}//若m最终为素数,则j会在最后一次运算后=m,从而跳出循环,来到下一个if判断中
if(j==m)//素数判断,输出
return 1;
else
return 0;
}
*/
//homework 5 begin at 19:45
//20:11 first generation--->error
//20:56 唐琪鑫版本
//5,编写一函数sort(int n, int a[]) ,对数组a的前n个元素从大到小排序。
/*
#define SIZE 20
void sort(int n, int a[]);//函数声明
int main()
{
int i,n,a[SIZE];
printf("now input the digital into the shuzu!\n");
for(i=0;i<SIZE;i++)
{
scanf("%d",&a[i]);
}
printf("now decide how many number should be permutation:");
scanf("%d",&n);
sort(n,a[]);
for(i=0;i<SIZE;i++)
{
printf("%d",a[i]);
printf(" ");
}
return 0;
}
void sort(int n, int a[])//函数定义
{
int i;
for(i=0;i<n;i++)//冒泡排序选择,之后再编写选择排序
{
int j;
for(j=i+1;j<n+1;j++)
{
int t=0;
if(a[i]<a[j])
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
}
}
*/
//Tang Qixin edition
#include <stdio.h>
#define SIZE 20
void sort(int n, int a[]);//函数声明
int main()
{
int i, n,point;
int a[SIZE];
printf("now input the digital into the shuzu!\n");
for(i = 0; i < SIZE; i++)
{
scanf("%d", &a[i]);
}
printf("now decide how many number should be permutation:");
scanf("%d", &n);
sort(n, &a[0]);
//sort(n, a);//这两种都是等效的,对于含有数组的函数而言,调用的是数组的首个元素的地址
//point=a[0];//这一种似乎也是可行的,但是调试过程中,发现若输入n=13,则程序只会排序7项?
//sort(n, &point);//这一种先不用,此处尝试证明了函数是调用数组第一个元素的地址这一事实。
for(i = 0; i < SIZE ; i++)
{
printf("%d ", a[i]);
}
return 0;
}
void sort(int n, int a[])//函数定义
{
int i, j;
int temp = 0;
for(i = 0; i < n; i++)//冒泡排序选择,之后再编写选择排序
{
for(j=i+1;j < n; j++)
{
if(a[i] < a[j])
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
第九章 指针
指针与指针变量
变量地址
在定义变量时,系统要为其分配内存单元。内存单元的首地址即为该变量的地址。
指针与指针变量
指针:一个抽象概念,是指变量的地址
指针变量:一个变量类型,专门存放地址的变量 地址是指针变量i_pointer的内容(是i的地址)
&与*运算符
&后跟变量名,取变量的地址;
*后跟指针变量,指针所指单元的内容;/(单独解引用:*运算符给出指针指向的地址上贮存之值)
如:short int i=10, j, *i_pointer;/*(取I之地址赋值予i_pointer) */
i_pointer = &i; /*指针i_pointer所指单元的内容赋给j*/
j = *i_pointer; //(解引用) /*地址i_pointer所存的数据赋给j */
i = *i_pointer = *(&i)
指针变量的定义和引用
变量值的存取方法(访问方法)
直接访问:按变量名来存取变量值
例 int i;
i = 3; ——直接访问
间接访问:通过变量地址(指针变量)去访问变量
int i, *i_pointer;
i_pointer = &i;
*i_pointer = 20; -----间接访问
指针变量的定义 :[存储类型] +数据类型符 +*变量名;
int *p1, *p2;
float *q;
指针变量的赋值:
1、定义时赋值——[存储类型] 数据类型 *指针名 = 初始地址值;
例 int i;
int *p = &I;(记得指针变量是储存地址,要加取地址符)
int *q = p;——>用已赋值的指针变量作赋值
2、赋值语句赋值
int a;
int *p;
p = &a;
指针变量赋值的几种错误方法:
错误一,不知道/未声明变量及变量类型的情况下直接对指针变量赋值
int *p; (为什么不能直接对pointer赋值?直接赋予一个正确的地址也不行吗?)
p = 2000; (可以!!但是方式不对!!2000是一个数字不是地址,应该
写成(*int)2000这才是一个地址,另外,指针变量是变量,具有变量的性质)
错误二:不理解*单独放出是解引用的意思
int a;
int *p;
*p = &a; (*p 对应的是 p 这个地址里存放的值,不是地址)
应改成:p=&a;
错误三:不同的指针变量类型没有转换直接相互赋值
int a;
int *pi = &a;——————————————>
char *pc = &a; 最后一句应改成
注意:虽然经过强制类型转换后,指针变量的类型和值都改变了,但是这种改变不会影响原地址内存放的数据的类型和内容,只不过,如果原来是整型(int)的指针变量若转换成 char 型的变量指针(即从 4 个 bit 的地址转换成 1 个 bit 的地址),它所能指向的值就只剩下原来的 1 个 bit 的内容.(待考证)
零指针与空类型指针
零指针:(空指针)
-定义: 指针变量值为零
-表示: int * p = 0;
void *类型指针
表示: void *p;
使用时要进行强制类型转换
(待cpp补充)
指针和地址运算
指针变量的加、减运算
指针可以参与加法和减法运算,但其加、减的含义绝对不同于一般数值的加减运算。如果指针p是这样定义的:
ptype *p;并且p当前的值是ADDR,那么: p ± n 的值 = ADDR ± n * sizeof(ptype)
这和数据在内存中存放的方式以及数据本身类型有关系
这就是指针的运算之一:指针与整数的运算,只要加的是个整数,整数就会和指针所指向类型的大小(以sizeof显示的字节大小)相乘,无需自己再费劲苦心的考虑地址加上整数乘大小,实在很方便【通过实机测试 Clion ver.01.2021】如果相加的结果超出了初始指针指向的数组范围,计算结果则是不定义的,除非刚好等于数组最后一个元素之位置,C保证该指针有效。
指针与数组
数组的指针
数组的指针:数组在内存中的起始地址, 或数组变 量名, 或数组第一个元素在内存中的地址。
指向数组的指针变量
如果将数组的起始地址赋给某个指针变量,那么该指针变量就是指向数组的指针变量。
下面是对数组元素赋值的几种方法,它们从功能上是等价的
1、 char str[10];
int k;
for (k = 0; k < 10; k++)
*(str+k) = 'A' + k
2、 char str[10];
int k;
char *p;
p = str;
for (k = 0; k < 10; k++)
*(p+k) = 'A' + k
3、 char str[10];
int k;
char *p;
p = str;
for (k = 0; k < 10; k++)
*p++ = 'A' + k; /*相当于 *p = 'A' + k; p++;*/
注意:数组名是地址常量,切不可对其赋值,也不可做++或--运算。例如:int a[10];如果在程序中出现a++或a--则是错误的。
元素为指针的数组____指针数组
指针数组:用于存储数据地址的数组 数据类型符 *变量名[常量表达式];
例:
char c[3] = {'a', 'b', 'c'};
char *p[3];
p[0] = &c[0];
p[1] = &c[1];
p[2] = &c[2];
【例】利用指针数组对键盘输入的5个整数进行从小到大排序。
#include <stdio.h>
void main ( )
{
int i, j, t;
int a, b, c, d, e;
int *p[5] = {&a, &b, &c, &d, &e};
/*指针变量使用时必须先给其赋地址*/
scanf ("%d,%d,%d,%d,%d", p[0], p[1], p[2], p[3], p[4]);
for (i = 0; i < 4; i++) /*利用比较法排序*/
for (j = i + 1; j < 5; j++)
if (*p[i] > *p[j]) /*交换p[i]、p[j]所指向的变量值*/
{
t = *p[i];
*p[i] = *p[j];
指针与字符串
字符串表示形式—>用字符数组实现
例:
void main ( )
{
char string[] = "I love China! ";
printf ("%s\n", string);
printf ("%s\n", &string [7]);
}
用字符指针实现
例:
void main ( )
{
char *string;
string = "I love China! ";
printf ("%s\n", string);
printf ("%s\n", string+7);
//数组名就是一个指针
//这里是指针和地址的思想
}
字符指针变量与字符数组之比较
char *cp; 与 char str[20];
str是地址常量,不可赋值;cp是地址变量,可赋值。
char str[20]; str="I love China! "; (X)
char *cp; cp="I love China! "; (V)
cp接受键入字符串时,必须先开辟存储空间(先赋地址)
字符指针除了可赋值外,其它方面等同于字符数组
【例】 利用字符指针实现字符串的倒序排列
程序设计思想:
使用两个指针p和q。p指向字符串首地址,q指向字
符串未地址。
如果p<q,交换两指针指向单元的内容,交换后p增1,
指向下一个单元,q减1,指向上一个单元的内容,直到p
大于等于q为止。未地址与首地址间的关系为q=p+strlen(str)-1;
注意:无法使用strlen求字符数组的长度
#include <stdio.h>
void main ( )
{
char str[]="I Love China!", ch;
int i=0;
char *p, *q;
p = str; //p指向字符串的首
while(str[i]) i++;
q = p + i - 1;//q指向字符串的末
while (p < q) { /*交换p和q
ch = *p;
*p = *q;
*q = ch;
p++; q--;
}
printf ("%s\n", str);
}
右边的做法与左边一致,但是采用了
Sizeof求长度从而求末尾之做法,此处有个很有趣的现象,就是左边是p+n-1;而右边则需要-2,其原因待考证;其次,strlen只能用于字符串之求长度,至于为何无法用于同类的字符数组,带考究。
指针与动态内存分配
静态内存分配
系统根据变量或数组定义分配内存单元,这种内存分配方式称为静态内存分配 。
short int k; /*系统将给变量k分配2个字节内存单元*/
char ch[10]; /*系统将给数组ch分配10个字节的内存单元,首地址是数组名ch*/
静态内存分配一般是在知道数据量大小的情况下使用
如果事先并不知道数据的具体数量,编写程序时,数量由用户输入,然后根据数量输入变量之值。那该如何处理呢?
解决的方法有两种:
1、定义大尺寸的数组,如:int score[100]; 问题:内存浪费
2、动态分配内存
根据实际需要来分配一块大小合适的连续的内存单元。
动态内存分配可用函数malloc :
void *malloc( unsigned int size );
功能:分配一个大小为size字节的内存空间,返回该内存空间的首地址。
应用实例:
char i, *p;
p = (char *) malloc(4 * sizeof(char));
for (i = 0; i < 4; i++)
*(p + i) = i + 1; /*等同于p[I]=i+1;*/
3、动态内存释放:分配的内存空间用完要释放
void free (void *block); free( pscore );
(否则造成空间被长期占据,浪费内存资源)
编写程序先输入学生人数,然后输入学生成绩,最后输出学生的平均成绩
#include <stdio.h>
#include <malloc.h>
void main ( )
{
int i, num, *pscore;
float sumscore=0, averscore;
scanf ("%d", &num);
pscore = (int *) malloc(num * sizeof(int));
if ( pscore == NULL)
return;
for (i = 0; i < num; i++)
scanf ("%d", pscore + i);
for (i = 0; i < num; i++)
sumscore = sumscore + pscore[i];
averscore = (float)sumscore / num;
printf ("平均成绩是 %.1f\n", averscore);
free (pscore); /*释放动态分配的内存*/
}
指针作为函数的参数
参数传递方式:传值调用和传址调用
第10章 预处理命令
预处理命令:C源程序中以#开头、以换行符结尾的行
源程序生成执行文件的过程:
C语言源程序 编译 目标程序 链接 执行程序
.c或.cpp。 预处理 .obj .exe
种类:
宏定义 #define、#undef
文件包含 #include
格式:
“#”开头
占单独书写行
语句尾不加分号
宏定义
宏定义分为两种:不带参数的宏定义和带参数的宏定义。
不带参数的宏定义一般形式 ——>用标识符(宏名)表示单词串(宏体)
#define 标识符 单词串
注意:宏替换时仅仅是将源程序中与宏名相同的标识符替换成宏的内容文本,并不对宏的内容文本做任何处理。
宏定义注意事项
1.C程序员通常用大写字母来定义宏名,以便与变量名区别
2.宏定义可嵌套定义【这点容易导致后边混淆,要时刻牢记预处理只是替换】
3.程序中字符串常量即双引号中的字符,不作为宏进行宏替换操作
#define XYZ this is a test
printf(“XYZ"); 输出:XYZ,而不是:this is a test。
4. 宏定义一般以换行结束,不是以分号结束
5.在定义宏时,如果宏是一个表达式,那么一定要将这个表达式用()括起来,否则可能会引起非预期的结果。
6.#undef终止宏名作用域【可通过undefine使一个预处理命令变为局部命令】
带参数的宏定义
#define 标识符(参数列表) 单词串【标识符(参数列表)之间不能有空格】
宏展开:形参用实参换
宏体及各形参外一般应加括号()
例 #define POWER(x) x*x
z=POWER(x+y); z=x+y*x+y;
#define POWER(x) ((x)*(x))
z=POWER(x+y); z=((x+y)*(x+y));
文件包含
功能
一个源文件可将另一个源文件的内容全部包含进来
一般形式
#include <包含文件名> 或 #include “包含文件名”,其中
< >: 直接到系统指定的“文件包含目录”去查找被包含的文件 ;
" “:系统首先到当前目录下查找被包含文件,如果没找到,再到系统指定的“文件包含目录”去查找;
第十一章 复杂数据类型
结构体(结构)
设计程序时,最重要的步骤之一是选择表示数据的方法。在许多情况 下,简单变量甚至是数组还不够。C提供了结构变量(structure variable)提高你表示数据的能力,它能让你创造新的形式。此节将详细介绍C结构。我们先从结构(结构体)的定义开始
结构体是一种构造数据类型(用户自定义数据类型)
用途:把不同类型的数据组合成一个整体
结构声明(structure declaration)描述了一个结构的组织布局。声明类 似下面这样:
struct book
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
};以分号;结尾
其中:
struct是关键字,不能省略
类似于book是合法标识符,我们称book这类为结构布局,则上边的结构体称为具有book结构布局的结构体,它像是一个规范,规定了拥有book标识的结构体的元素构造,它告诉编译器如何表示数据,但是还没有为相应的数据分配空间,它是可省的,然而,如果打算多次使用结构模板,就要使用带标记的形式;省略则为无名结构体
间接定义法:先定义结构类型,再定义结构变量
struct 结构体类型名
{
数据类型名1 成员名1;
… …
数据类型名n 成员名n;
};
struct 结构体类型名 变量名列表;
直接定义法:定义结构体类型的同时定义结构体变量
struct Student_Info
{
char no[9]; //学号
char name[20]; //姓名
char sex; //性别
unsigned int age; //年龄
unsigned int classno; //班级
float grade; //成绩
} student1, student2;
结构体变量的引用
结构类似于一个“超级数组”,这个超级数组中,可以是一个元素为char 类型,下一个元素为forat类型,下一个元素为int数组。可以通过数组下标单独访问数组中的各元素,那么,如何访问结构中的成员?使用结构成员运算 符——点(.)访问结构中的成员。
结构体变量不能整体引用,只能引用变量成员
结构体变量的赋值
结构体变量初始化赋值
先定义结构体类型,再定义结构体变量时赋初值
struct 结构体类型名 初值表
{ … … };
struct 结构体类型名 变量名 = {成员1的值, …, 成员n的值};
定义结构体类型的同时,定义结构体变量并赋初值
struct [结构体类型名]
{ 初值表
… …
} 变量名 = {成员1的值,成员2的值, …, 成员n的值};
struct Student_Info
{
char no[9]; /*学号*/
char name[20]; /*姓名*/
char sex; /*性别*/
unsigned int age; /*年龄*/
unsigned int classno; /*班级*/
float grade; /*成绩*/
} student = {"20020306", "ZhangMing", 'M', 18, 1, 90};
结构体变量的赋值
对其成员逐一赋值,或者用已赋值的结构体变量对它赋值
【例】 计算学生5门课的平均成绩
结构体数组
结构体数组的引用
引用格式:结构体数组名[下标].成员名;
第12章 文 件
文件(file)通常是在磁盘或固态硬盘上的一段已命名的存储区。对我 们而言,stdio.h就是一个文件的名称,该文件中包含一些有用的信息。C把文件看作是一系列连续的字节,每个字节都能被单独读取。C提供两种文件模式:文本模式和二进制模式。
文件分类
文本文件: ASCII文件,每个字节存放一个字符的ASCII码
文本文件特点:存储量大、速度慢、便于对字符操作
二进制文件:数据按其在内存中的存储形式原样存放
二进制文件特点:存储量小、速度快、便于存放中间结果
文件操作
读文件:将磁盘文件中的数据传送到计算机内存的操作。
写文件:从计算机内存向磁盘文件中传送数据的操作。
文件操作的一般步骤
1、打开文件,获取文件指针2、利用文件指针对文件读写3、关闭文件,释放文件指针
文件的打开、读写和关闭
标准文件的打开和关闭由系统自动完成:
标准输入------键盘 文件指针:stdin
标准输出------显示器 文件指针: stdout
其它文件的打开与关闭由fopen和fclose完成
打开文件fopen: (获取文件指针)
FILE *fopen (char *filename, char *mode【打开方式】);
r----表示从文件中读取数据(read), t-----表示文本文件(text,默认方式),
W----表示向文件写入数据(write), b----表示二进制文件(binary),
a----表示在文件尾追加数据(append),
+----表示文件可读可写。
注意:字符串mode中字符先后次序
1、操作类型符在前,打开文件类型符在后
如,“rb”、“wt”,不可写成“br”、“tw”。
2、 “ + ”只能放在操作类型符的右边
如, “r+” 、“w+t”、“wb+”, 不可写成“+r” 、“+wt”、“+wb” 。
关闭文件fclose
int *fclose (FILE *fp);
使文件指针变量与文件“脱钩”,释放文件指针
FILE *fp;
fp = fopen ("wang.txt", "r+");
if (fp = = NULL)
{
printf ("the file :wang.txt not found! ");
exit (-1);
}
… … //读取和加工数据
fclose (fp); //关闭该文件
文件的读写
C语言提供了多种文件读写的函数,函数有:
格式化读写函数: fscanf 和 fprinf
字符读写函数: fgetc 和 fputc
字符串读写函数:fgets 和 fputs
数据块读写函数:fread 和 fwrite
格式化读写函数:fscanf 和 fprinf
fscanf
int fscanf ( FILE *fp, const char *format[,变量地址,…]);
功能:从fp所指向的文件中读取数据。
fprintf
int fprintf ( FILE *fp, const char *format[,表达式,…]);
功能:将表达式输出到fp所指向的文件中。
fscanf (fp, "%d,%f", &i, &t); //若文件中有3, 4.5,则将3送入i,4.5送入t
fprintf (fp, "%d,%6.2f", i, t); //将i和t按%d, %6.2f格式输出到fp文件
将一个3*3的二维数组写入到文件Data.txt中
文件的读写
(2) 字符读写函数:fgetc 和 fputc
fgetc
int fgetc (FILE *fp);
功能:从fp指向的文件中,读入一个字节(字符)
返回值:正常,返回读入字符的ASCII值;
读到文件尾或出错,返回值为EOF
fputc
int fputc (int c, FILE *fp);
功能:输入字符且将字符数据c输出到fp所指向的文件中去。
(待补充)
2021.6.3开始准备初稿
2021.7.2初稿完成
2021.7.3-7.31结合《C Primer Plus》完成二稿
初稿定名
《看懂C语言程序设计基础第二版》
Menou
All rights reserved
Zhili Lou .SZU. Shenzhen. China
References:
1、《C语言程序设计基础第二版》 王敬华 著
2、《C Primer Plus》(第6版)中文版/(美)普拉达(Prata,S.)著;姜佑译.