笔记目录
第一章、C语言简介
第一节、c语言概述
1.C语言的特点
- 优点:
代码量小,速度快,功能强大 - 缺点:
危险性高,开发周期长,可移植性不强
2.C语言的关键字
- 32个关键字:(有系统定义,不能重作其他定义)
auto break case char const
continue default do double else
enum extern float for goto
if int long register return
short signed sizeof static struct
switch typedef unsigned union void
volatile while
3.C语言的应用领域
1)系统软件的开发
- 操作系统:Windows、Linux、Unix
- 驱动程序:主板驱动、显卡驱动、摄像头驱动
- 数据库:DB2、Oracle、SQL server
2)应用软件开发
- 办公软件:WPS
- 图形图像多媒体:ACDSee Photoshop mediaplayer
- 嵌入式软件开发:智能手机、掌上电脑
- 游戏开发:2D、3D游戏
4.参考资料
谭浩强《C语言程序设计》清华
《The C programming langugae》机械工业
《C Primer Plus》60元 人民邮电
《C和指针》65 人民邮电
《C专家编程》绝版
《C陷阱与缺陷》人民邮电30
《C科学与艺术》机械工业
5.举例:一元二次方程的解
代码如下:
# include <stdio.h>
# include <math.h>
int main(void)
{
//把三个系数保存到计算机中
int a = 1; //=不表示相等,表示赋值
int b = 5;
int c = 6;
double delta; //delt存放的是 b*b - 4*a*c
double x1; //存放一元二次方程的其中一个解
double x2; //存放一元二次方程的其中一个解
delta = b*b - 4*a*c;
if (delta > 0)
{
x1 = (-b + sqrt(delta)) / (2*a);
x2 = (-b - sqrt(delta)) / (2*a);
printf("该一元二次方程有两个解, x1 = %f, x2 = %f\n", x1, x2);
}
else if (delta == 0)
{
x1 = (-b) / (2*a);
x2 = x1; //右边赋给左边
printf("该一元二次方程有一个唯一解, x1 = x2 = %f\n", x1);
}
else
{
printf("无解\n");
}
return 0;
}
6.关于vc++6.0的一些问题
如果在程序起名字的时候含有“…”,那么需要在名字后面加后缀“.cpp”。否则,在编译运行那一块将无法点击。
第二章、基本编程知识
第一节、预备知识
1.CPU 内存条 硬盘 显卡 主板 显示器 之间的关系
- 例子:视频的播放是如何进行。
首先是在硬盘上放着的,点击视频,操作系统会把硬盘上的数据调用到内存条上(CPU不能直接处理硬盘上的数据),然后CPU再去处理内存条上的视频。接着图像会通过显卡被显示器显示出来,如果是声音,通过声卡发出声音。主板提供一个传输的作用,把内存条,显卡等有效的组织在一起(插在主板的插槽上)。
2.HelloWorld程序如何运行
# include <stdio.h>
int main (void)
{
printf("欢迎来到c世界!\n");
return 0;
}
- 通过编译和链接最终生成可执行的exe程序,点感叹号时,软件请求操作系统执行exe程序,操作系统调用cpu,cpu就把它执行完毕。
3.什么是数据类型
-
基本数据类型
整数:
1)整型 — int (4字节)
# include <stdio.h>
int main (void)
{
int i=10;//表示 i 变量时整型变量,所谓整型变量就是指i只能存放整数不能存放实数
printf("%d\n",i);
return 0;
}
- 如果i=10.6并不会报错,只会出现警告,仍能运行
2)短整型—short int (2字节)
3)长整型—long int (8字节)
浮点数【实数】
1)单精度浮点数—float(4字节)
2)双精度浮点数—double (8字节)
字符:
char
char i='A';
printf("%c\n",i);
- 复合类型数据
结构体
枚举
共用体
4.变量
- 利用变量方便对数据的存储。变量的本质就是内存的存储空间。
- 变量名的首字母是字母或下划线
- 变量名的其他字母包含下划线、数字、字母
- 不能使用关键字
5.CPU 内存条 VC++6.0 操作系统 之间的关系
int i;
i=3;//3最终是存放在内存中,程序终止以后3所占的空间被释放
6.变量为什么 必须初始化(所谓初始化就是赋值的意思)
- 当变量没有初始化时,vc++6.0会自动给变量赋一个很大的值。但还有一种情况:软件从前遗留下来的数据赋给了变量。(操作系统并不清空内存空间遗留下来的数据)
7.如何定义变量
- 数据类型 变量名 = 要赋的值;
等价于
数据类型 变量名;
变量名 = 要赋的值;
举例子:
int i = 3;等价于 int i;i=3;
int i,j;等价于 int i ;int j ; - 如何计算变量或类型占内存的大小
8.什么是进制
十进制就是逢十进一
二进制就是逢二进一
n进制就是逢n进一 - B表示二进制数,O表示八进制数,D表示十进制数,H表示十六进制数
- 进制的转换
- 不同进制所代表的数值之间的关系
十进制的3981转化成十六进制是F8D
十进制的3981和十六进制的F8D所代表的本质上都是同一个数 - printf的用法:
- %d表示以十进制输出
- %x或%X表示以十六进制输出
- %o表示以八进制输出
9.常量在C语言中是如何表示的
包括:整型、实型、字符型、字符串、枚举型
- 整
- 十进制:传统的写法
- 十六进制:前面加0x或0X
- 八进制:前面0,注意不是字母o
- 浮点数
- 传统的写法
float x=3.2;//传统 - 科学计数法
float x=3.2e3;//x的值是3200
float x=123.45e-2;//x的值是1.2345
- 字符
- 单个字符用单引号括起来
'A’表示字符A
'AB’错误
“AB”正确 - 字符串用双引号括起来
“A”正确,因为“A”代表了‘A’‘\0’的组合
10.常量以什么样的二进制代码存储在计算机中
- 整数是以补码的形式转化为二进制代码存储在计算机中
- 实数是以IEEE754标准转化为二进制代码存储在计算机中的(具体可参见末尾的—穿插在课堂中的零散知识笔记–浮点数的存错所带来的问题)
- 字符的本质实际也是与整数的存储方式相同
11.代码规范化
- 推荐书籍《高质量C/C++编程》—林锐
- 成对法则(“()”“{}”成对的敲)
- 内部代码需要缩进
- 多敲空格
作用1:代码的可读性更强
作用2:是程序不容易出错
12.什么是字节 - 字节就是存储数据的单位,并且是硬件所能访问的最小单位
- 1字节=8位;1K=1024字节;1M=1024K;1G=1024M
13.不同类型数据之间相互赋值的问题
暂不考虑
14.什么是ASCII
char ch = "AB";//error 因为“AB”是字符串,我们不能把字符串赋给单个字符
char ch = "A";//error
char ch = 'AB';//error,‘AB’是错误的
- ASCII不是一个值,而是一种规定,ASCII规定了不同的字符是使用哪个整数值去表示
- 它规定了:
‘A’----54
‘B’----66
‘a’----97
‘b’----98
‘0’----48
15.字符的存储[字符本质上与整数的存储方式相同]
第二节、C语言程序的基本结构
#include <stdio.h>
void main()
{
printf("Hello World!\n");
}
对于该结构来说:
- 以.h为后缀的文件我们称为头文件,可以是C标准库中的头文件,也可以是自定义的头文件,自定义的头文件需要带 “ ”
- main()函数是C程序处理的起点入口
第三节、C语言的编译和执行
- 编译
形成目标代码/文件。目标代码是编译器的输出结果,常见扩展名是“.o”或“.obj” - 连接
将目标代码跟C函数库相连,并将源程序所用的库代码与目标代码合并
最终形成可执行的二进制机器代码 - 执行
在特定的机器环境下运行C应用程序
第四节
- 幻数:在程序中直接使用的常数,称为幻数
- 良好的程序设计风格建议把幻数定义为:
宏常量
const常量
一、宏常量
- 宏常量:用一个标识符来表示的常量
- 宏定义:#define 标识符 字符串(是编译预处理指令,不用加;)
例:#define PI 3.14159 - 存在的问题:没有数据类型,编译器在宏替换时不进行类型检查。只进行简单的字符串替换,极易产生意想不到的错误
二、const常量
4. 必须在定义时赋初值
例:const double pi = 3.14159
5. const常量与宏常量相比的优点是什么
const常量有数据类型,编译器能对其进行类型检查
某些集成化调试工具可以对const常量进行调试
第三章、基本的输入输出函数的用法
第一节、printf( )
- 含义:将变量的内容输出到显示器上
- 四种用法:
- printf (“字符串\n”);
printf ("哈哈!\n");//\n表示换行
- printf (“输出控制符”,输出参数):
int i = 10;
printf("%d\n",i);//d是十进制
- printf (“输出控制符1 输出控制符2 …”,输出参数1,输出参数2…);
- 输出控制符和输出参数的个数必须一一对应
- printf(" 输出控制符 非输出控制符",输出参数);
输出控制符包含如下:
%d---------------int
%ld----------------long int
%c----------------char
%lf-------------------double
%mf:输出数据的最小域宽,(例:%10f),数据位宽大于m,按实际位宽输出;数据位宽小于m时,右对齐,左补空格
若是%-10f时,右补空格
%.nf:显示精度(例:%.3f,32.67878:32.678)
例:%10.4f,32.67847823:(补三个空格)32.6784
例:%10.0f,32,678232:(补8个空格)33
%x(或者%X或者%#X)------------int或long int或short int
%o-----------------同上
%s-------------------字符串
int x=47;
printf ("%x\n",x);//输出结果:2f
printf ("%X\n",x);//输出结果:2F
printf ("%#X\n",x);//输出结果:0X2F(这种输出结果最好,推荐使用)
printf ("%#x\n",x);//输出结果:0x2f
- 为什么需要输出控制符:
- 01组成的代码可以表示数据也可以表示指令
- 如果01组成的代码表示数据的话,那么同样的01代码组合以不同的输出格式输出就会有不同的输出结果
- 在 printf 函数中,不是简单地用 “%f” 格式声明,而是在格式符 “f” 的前面加了 “7.2”。表示的意思是在输出 x1,x2 时,指定数据占 7 列,其中小数占 2 列。这样做有两个好处。
第二节、scanf( )
- 含义:通过键盘将数据输入到变量中
- 两种用法:
- scanf(“输入控制符”,输入参数);
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,然后存入以参数的值为地址的变量中。
int i;
scanf(”%d“,&i);//&i表示i的地址 &是一个取地址符
printf (”i=%d\n“,i);
数据位宽:scanf(“%2d%2d”,&a,&s);
例:输入1234,则a=12,s=34
2. scanf(“非输入控制符 输入控制符”,输入参数);
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中。非输入控制符必须原样输入。
int i;
scanf("m%d",&i);//1:输入:m123;2:输入:123(错误)
printf("i=%d\n",i);1:输出:i=123;2:输出:-12324341(垃圾数字)
scanf("%d,%d",&a,&b);//输入2,3(必须与“”内的格式一致)
scanf("a=%d,b=%d",&a,&b);//输入a=3,b=4
附:
scanf()的格式字符
%d :输入十进制int型
%f,%e:输入float型(不能指定输入数据的精度)
%c:输入一个字符(包括空白字符)
*:输入项在读入后不赋给相应的变量,
例:scanf("%2d%*2d%2d",&a,&b);//输入123456
printf(“a=%d,b=%d”,a,b);//输出a=12,b=56
3. scanf(“输入控制符,输入控制符,输入控制符”,输入参数,输入参数。输入参数);
int i,j,k;
scanf("%d,%d,%d",&i,&j,&k);
printf("i=%d,j=%d,k=%d\n",i,j,k);
- 如何使用scanf编写出高质量的代码
- 使用scanf之前最好使用printf提示用户以什么样的方式来输入
- scanf中尽量不要使用非输入控制符,尤其是不要用\n
int i;
scanf("%d\n",&i);//非常不好的格式,不要加\n
printf("i=%d\n",i);
- 应该编写代码对用户的非法输入做适当的处理【非重点】
while ((ch=getchar())!='\n')
continue;
- 在 scanf 函数中,格式声明为 “%lf”,表示输入的是双精度型实数。从上面的运行结果来看输入 “1 3 2” 时,两个数字之间是用空格分开,如果用其他符号(如逗号)会出错。我们输入的 “1 3 2” 虽然是整数,但是由于指定了用 %lf 的格式输入,因此系统会先把这 3 个整数转换成为实数 1.0,3.0,2.0,然后赋值给变量 a,b,c。
第三节、单个字符的输入输出
- 字符输出函数putchar()
例;putchar(ch)//向屏幕输出一个字符,字符型变量ch的值 - 字符输入函数getchar()
例:ch=getchar()
从键盘接收的字符作为getchar()的函数值,无参数
例:大写英文字母转换成小写英文字母
#include<stdio.h>
main()
{
char ch;
printf("press a key and then press enter:");
ch =getchar();
ch=ch+32;
putchar(ch);
putchar('\n');//不能用“”
}
第四节、getchar的使用
将输入字符先放入输入缓冲区队列中,再从缓冲队列读取字符,直到键入回车符或文件结束符EOF时,程序才认为输入结束。一行输入结束,getchar()才开始从输入缓冲队列读取字符,前面函数没读走的数据仍在缓冲队列中,将被下一个函数读取。
使用getchar()输入字符时的怪象,相当于一次性把键盘输入的一行字符都放入缓冲区,然后再从输入缓冲去逐个读取字符
第五节、用%c格式符输入数据时存在的问题
在%c前加上空格用以忽略用户输入的空白字符
第四章、如何编写出更有价值的程序
- 在程序的开头写上注释:
/*
2019年8月11日10:10:45
目的:
测试%X %x %#x %#X的用法
*/ - 在程序结尾写上注释:
/*在vc++6.0中输出的结果是:
…
总结:…
*/
第五章、数据类型
第一节、自动类型转换
1.不同类型数据的运算结果的类型是什么?
取值范围较大的那种类型
2.当取值范围大的类型转化为取值范围小的类型时,通常都是不安全的,会发生数据溢出(小蛇吞大象 )
3.上溢出
一个数值运算结果>类型能表示的最大数
进位超过最高位而发生进位丢失
或进位到达最高位而改变符号位
4.下溢出
浮点数运算结果<类型能表示的最小数
第二节、强制类型转换
1.格式:
(数据类型)(表达式);
2.功能
把表达式的值强制转换为前面所制定的类型
例子:
(int )(4.5+2.2);最终值是6
(float)(5);最终值是5.000000
第三节、浮点数的存错所带来的问题
float和double都不能保证可以精确的存储一个小数
举例:
1.有一个浮点型变量x,如何判断x的值是否是零
if (|x-0.000001|<=0.00001)
是零
else
不是零
2.为什么循环中更新的变量不能是定义成浮点型
循环中更新的变量不要定义成浮点型
第六章、运算符和表达式
第一节、算术运算符
- +,-,*,/(除),%(取余数)
/ :两个数都是int,则商就是int;若商有小数,则截取小数部分;被除数和除数中只要有一个或两个都是浮点型数据,则商也是浮点型。
% :运算对象必须是整数,结果是整除后的余数,其余数符号与被除数相同。被除数是正的,余数就是正的;被除数是负的,余数就是负的。(13%-3==1) - 随机函数rand()
生成一个在0~32767之间的随机数
如何生成一个指定范围(如1~100)内的随机数:
magic=rand()%100+1;//1~100 - 常用的标准数学函数
函数名 | 功能 |
---|---|
exp(x) | ex |
pow(x,y) | xy |
sqrt(x) | x的平方根(x>=0) |
fabs(x) | ∣ \mid ∣x ∣ \mid ∣ |
log(x) | lnx(x>0) |
log10(x) | lgx(x>0) |
sin(x) | sinx.x为弧度值 |
cos(x) | cosx,x为弧度值 |
注:使用这些数学函数时,必须在代码前加#include <math.h>
第二节、关系运算符
<, <=, >, >=, !=, ==
第三节、逻辑运算符
!(非),&&(并且),||(或)(具有短路特性)
-
!真--------假;
-
!假--------真;
-
真&&真----真;
-
假&&真----假;
-
真&&假----假;
-
假&&假----假;
-
真||假------真;
-
假||真------真;
-
真||真------真;
-
假||假------假;
-
C语言对真假的处理:非零是真,零是假;真是1表示,假是0表示。
int i = 10;
int k = 20;
int m;
m = (1>2) && (k=8);//不含有分号的是表达式,含有分号的就是语句。
printf ("m=%d,k=%d\n",m,k);//m=0,k=20.因为1>2是假的,所以k=8就不执行,故k不变
- &&左边的表达式为假,右边的表达式肯定不会执行
- ||左边的表达式为真,右边的表达式肯定不会执行
第四节、赋值运算符
+=,=,/=,-= ,*=
第五节、自增 自减 三目运算符 逗号表达式
1.自增(或者自减)
a.分类:
前自增------++i
后自增------i++
b.前自增和后自增的异同:
相同:
最终都使i的值加1
不同:
前自增整体表达式的值是i加1之后的值(先+后用)
后自增整体表达式的值是i加1之前的值(先用后+)
例:m=++n;
运行时为:n=n+1;
m=n;
例:m=n++;
运行时为:m=n;
n=n+1;
例:m=++n-2;
运行时:n=n+1;//n=6
m=n-1;//m=4
例:m=n++ -2;
运行时:m=n-2;//n=6
n=n+1;//m=3
int i;
int j;
int k;
int m;
i=j=3;
k=i++;
m=++j;
printf("i=%d,j=%d,k=%d,m=%d\n",i,j,k,m);
//结果是:i=4,j=4,k=3,m=4
c.为什么会出现自增
代码更精炼
自增的速度更快
d.学习自增要明白的几个问题:
- 我们编程时应该尽量屏蔽掉前自增和后自增的差别
- 自增表达式最好不要作为一个更大的表达式的一部分来使用,或者说,i++和++i单独成一个语句,不要把它作为一个完整的复合语句的一部分来使用
例如:
int m=i++ + ++i +i+ i++;//这样写不行,程序不可移植
2.三目运算符
A?B:C
等价于
if(A)
B;
else
C;
int i;
i= (1>2 ?5:1);
printf ("%d\n",i);
3.逗号表达式
格式:
(A,B,C,D)
功能:
从左到右执行
int i;
int j=2;
i= (j++,++j,j+2,j-3);//j=4,最后一个j-3赋给i,所以为1
printf ("%d\n",i);
// i=1
- 优先级别:
算术>关系>逻辑>赋值
第七章、流程控制(学习C语言的第一个重点)
1.什么是流程控制
程序代码执行的顺序
2.流程控制的分类
第一节、顺序执行
第二节、选择执行
- 定义
某些代码可能执行,也可能不执行,有选择地执行某些代码。 - 分类
1) if
a. if最简单的用法
- 格式
if (表达式)
语句 - 功能:
如果表达式为真,执行语句
如果表达式为假,语句不执行
if (3 >2)
printf ("AAA\n");//会输出
if (3)
printf("Aaa\n");//会输出
if (0)
printf (NNN\n");//不会输出
if (0==0)
printf("ZZZ\n");//会输出
b. if的范围问题
if(表达式)
语句A;
语句B;
解释:
if默认的只能控制一个语句A的执行或不执行
if无法控制语句B的执行或不执行
或者讲:语句B一定会执行
if (1 > 2)
printf("AAA\n");
printf("DDD\n");
//在vc++6.0中输出的结果是DDD
if(表达式)
{
语句A;
语句B;
}
此时if可以控制语句A和语句B
- 由此可见:if默认只能控制一个语句的执行或不执行;如果想控制多个语句的执行或不执行就必须把这些语句用{ }括起来
c. if…else…的用法
d. if…else if…else…的用法
- 格式:
if (1)
A;
else if (2)
B;
else if (3)
C;
else
D;
double delta = -1;
if (delta > 0)
printf ("有两个解!\n");
printf ( "哈哈!\n");//不是if语句里面的,这里匹配出错,不能这样写
else if (delta==0)
printf ("有一个唯一解!\n");
else
printf ("无解!\n");
e. C语言对真假的等级
- 非零是真
- 零是假
- 真用1表示
- 假用0表示
f. if举例–求分数的等级
# include <stdio.h>
int main (void )
{
float score ;//分数
printd ("请输入您的考试成绩:");
scanf ("%f",&score);
if (score > 100)
printf ("这是做梦!\n");
else if (score >=90 && score <=100)//不能写成 90<=score<-100
printf ("优秀!\n");
else if (score >=80 && score <90)
printf("良好!\n");
else if (score >=60 && score<80)
printf ("及格!\n");
else if (score >=0 && score<60)
printf ("不及格,继续努力!\n");
else
printf ("输入的分数过低,不要如此自卑!\n");
return 0;
}
g. if的常见问题及解析
- 空语句的问题
if (3>2) ;
等价于
if (3>2)
; //这是一个空语句
if (1>2); //此处的空语句类似于 if () ; (所以1>2成立是执行空语句)不能加;
printf (”AAA\n“);
printf("BBB\n");
//执行后为:
AAA
BBB
if (表达式1)
A;
else
B;
是正确的
if (表达式1);
A;
else
B;
是错误的
if (表达式1)
A;
else if (表达式2)
B;
else if(表达式3)
C;
else
D;
即便表达式1和2都成立,也只会执行语句A
if (表达式1)
A;
else if(表达式2)
B;
else if(表达式3)
C;
这样写语法不会出错,但逻辑上有漏洞
if(表达式1)
A;
else if (表达式2)
B;
else if (表达式3)
C;
else(表达式4)//第7行
D;
这样是不对的,正确的写法是:
要么去掉第七行的(表达式4)
要么在第七行的else后面加if
if (表达式1)
A;
else if(表达式2)
B;
else if (表达式3)
C;
else (表达式4);
D;
这样写语法不会出错,但逻辑上是错的
else (表达式4);
D;
等价于
else
(表达式4);
D;
h.如何看懂一个程序,分三步:
1. 流程
2. 每个语句的功能
3. 试数
如何学习一些小算法的程序
1. 尝试自己去编程解决他,大部分人都自己无法解决
2. 如果解决不了就看答案
3. 关键是把答案看懂之后,这个要花很大的精力,也是我们学习的重点
4. 看懂之后尝试自己去修改程序,并且知道自己修改之后程序的不同输出结果的含义
5. 照着答案去敲
6. 不看答案,自己独立把答案敲出来
小算法的程序:
1. 判断一个数字是否是素数
2. 判断一个数字是否是回文数
3. 编程实现求一个是进制数字的二进制形式
4. 求一个数字的每位是奇数的数字取出来组合形成的新数字
5. 求一个数字倒过来的数字
2 )switch
a.用法:看懂电梯程序
int val;
printf ("请输入您要进入的楼层:");
scanf ("%d",&val);
switch (val)
{
case 1:
printf("1层开!\n");
break;
case 2:
printf ("2层开!\n");
break;
case 3:
printf ("3层开!\n");
break;
default:
printf ("没有盖到这一层!\n");
break;
}
b.
c.
第三节、循环执行
- 定义
某些代码会被重复执行 - 分类
1 )for
a. 格式:
for (1;2;3)
语句A;
# include <stdio.h>
int main (void)
{
int i;
int sum = 0;
for(i=1;i<=9;++i)
sum = sum + i;
printf ("sum= %d\n",sum);
return 0;
}
b. 执行的流程【重点】
-
单个for循环的使用
-
多个for循环的嵌套使用
for (1;2;3)//1
for(4;5;6)//2
A;//3
B;//4
整体是两个语句,1 2 3是第一个语句
4是第二个语句
总结:
第一行的for 循环如果不执行了再执行B
for(1;2;3)
for(4;5;6)
{
A;
B;
}
整体式一个语句
3.
for (7;8;9)
for(1;2;3)
{
A;
B;
for(4;5;6)
C;
}
整体式一个语句
c.范围问题
d.举例
1 + 2 + 3 + 4 + 5… + 100;
1 +1/2 + 1/3 + 1/4 + … + 1/100;
//例2
# include <stdio.h>
int main (void)
{
int i=1;
float sum=0;
for (i=1;i<=100;i++)
sum = sum + 1/(float)i;//把i强制转换为浮点型
//或者sum=sum+1/(float)(i);
printf ("sum=%f\n",sum); //float必须用%f输出
return 0;
}
//结果:sum=5.187378
e.练习
1.求1到100之间的奇数之和
2.求1到100之间的奇数的个数
3.求1到100之间的奇数的平均数
4.求1到100之间的奇数之和,再求1到100之间的偶数之和
2 ) while
a.执行顺序
格式:
while (表达式)
语句;
例子:
int i=1;
int sum=0;
while (i<101)
{
sum = sum + i;
i++;
}
printf ("sum=%d\n",sum);
b.与for的相互比较
for(1;2;3)
A;
等价于
1;
while(2)
{
A;
3;
}
总结:
while 和for可以相互转化,但for的逻辑性更强,更不容易出错,推荐多使用for。
c.举例
- 从键盘输入一个数字,如果该数字是回文数,则返回yes,否则返回no。
回文数:正着写和侧着写都一样
比如:121、12321都是回文数
int val;//存放待判断的数字
int m;
int sum=0;
printf("请输入您需要判断的数字:");
scanf("%d",&val);
m=val;
while(m)
{
sum=sum*10+m%10;//理解它
m/=10;
}
if(sum==val)
printf("yes!\n");
else
printf("no!\n");
- 斐波拉契序列:1 2 3 5 8 13 21 34…
# include <stdio.h>
int main(void)
{
int f1,f2,f3;
f1=1;
f2=2;
int n;
int i;
printf("请输入数字");
scanf("%d",&n);
if(n==1)
f3=1;
else if(n==2)
f3=2;
else
{
for(i=3;i<=n;i++)
{
f3=f1+f2;
f1=f2;
f2=f3;
}
}
printf("f3=%d",f3);
return 0;
}
d.什么时候使用while ,什么时候使用for
用多了自然而然就知道了
3 ) do…while
a.
格式:
do
{
…
}while (表达式);
例子:二元一次方程的求解
# include <stdio.h>
# include <math.h>
int main (void)
{
double a,b,c,x1,x2,l;
char ch;
do{
printf ("请输入数字a=");
scanf ("%lf",&a);
printf ("请输入数字b=");
scanf ("%lf",&b);
printf ("请输入数字c=");
scanf ("%lf",&c);
l = b * b - 4 * a * c;
x1 = (-b + sqrt(l))/(2 * a);
x2 = (-b - sqrt(l))/(2 * a);
if (l>0){
printf ("该二元一次方程有两个不同实数解:x1=%lf,x2=%lf",x1,x2);
}
else if (l==0)
{
printf ("该二元一次方程有两个相同实数解:x1=%lf,x2=%lf",x1,x2);
}
else
{
printf("该方程无解");
}
printf("您是否还要进行运算:(Y/N)");
scanf(" %c",&ch);//这里%c前面必须加一个空格
}while('y'==ch||'Y'==ch);
return 0;
}
- 总结:do…while并不等价于for,也不等价于while,主要用于人机交互
4) break和continue【重点】
1.break
- break如果用于循环则是用来终止循环的
- break如果用于switch,则是用于终止switch
- break不能直接用于if,除非if属于循环内部的一个子句
例子:
for (i=0;i<3;++i)
{
if (3>2)
break;//break虽然是if内部的语句,但它终止的是外部的for循环
printf ("hihi1!\n");//永远不会输出
}
- 在多层循环中,break只能终止距离他最近的一个循环
例子:
int i,j;
for(i=0;i<3;++i)
{
for(j=1;j<4;++j)
break;
printf ("同志们好!\n");
}
//输出结果为:
同志们好!
同志们好!
同志们好!
- 在多层switch嵌套中,break只能终止距离他最近的switch
int x=1,y=0,a=0,b=0;
switch (x) //第一个switch
{
case 1:
switch (y) //第二个switch
{
case 0:
a++;
break; //终止的是第二个switch
case 1:
b++;
break;
}
b=100;
break; //终止的是第一个switch
case 2:
a++;
b++;
break;
}
printf ("%d %d\n",a,b); //26行
//最终输出结果是1 100
2.continue
- 用于跳过本次循环余下的语句,转去判断是否需要执行下次循环
例子:
for(1;2;3)
{
A;
B;
continue;//如果执行该语句,则执行完该语句后,会执行语句3,C和D都会被跳过,C和D不会被执行
C;
D;
}
while (表达式)
{
A;
B;
continue; //如果执行该语句,则执行完该语句后,会执行表达式,C和D都会被跳过去,C和D不会被执行
C;
D;
}
5)递推
1.递推的基本方法
正向递推:一步一步推得结果
反向逆推:由结果一步一步推得初值
2.递推的本质
把一个复杂的计算过程转化为一个简单过程的多次重复计算
第八章、数组
1.为什么需要学习数组
为了解决大量同类型数据的存储和使用问题
为了模拟现实世界
例子:
int a [5] = {1,2,3,4,5};
// a是数组的名字,5表示元素的个数,并且这5个元素分别用a[0],a[1],a[2],a[3],a[4]表示
int i;
for (i=0;i<5;++i)
printf ("%s\n",a[i]);
2.数组的分类
3.高效的数组初始化
memset(a,0,sizeof(a));
用sizeof(a)来获得数字组a所占的内存字节数
所用到的头文件 #include<string.h>
4.高效的数组赋值的方法
memcpy(b,a,sizeof(a));//数组a复制给数组b,要求数组a,b长度一致
所需要的头文件 #include<string.h>
第一节、一维数组
-
怎样定义一维数组
为n个变量连续分配存储空间
所有的变量数据类型必须相同
所有变量所占的字节大小必须相等
例子:
int a [5];
一维数组名不代表数组中所有的元素,一位数组名代表数组第一个元素的地址 -
有关一维数组的操作
a. 初始化
1)完全初始化
int a [5] = {1,2,3,4,5};
2)不完全初始化,未被初始化的元素自动为零
int a [5] = {1,2,3};
3)不初始化,所有元素是垃圾值
int a [5];
4)清零
int a [5]={0};
5)错误写法:
int a [5];
a [5] = {1,2,3,4,5};//错误
只有在定义数组的同时才可以赋值,其他情况下整体赋值都是错误的
int a [5] = {1,2,3,4,5};
a [5] = 100;//error因为没有a [5]这个元素,最大只有a[4]
int a [5] = {1,2,3,4,5};
int b [5];
//如果要把a数组中的值全部复制给b数组
错误写法:
b=a;//error
正确的写法:
for (i=0;i<5;++i)
b [i]=a[i];
b.赋值
int a [5];
int i;
scanf ("%d",&a[0]);
printf ("%d\n",a[0]);
scanf ("%d",&a[3]);
printf ("%d\n",a[3]);
for (i=0;i<5;++i)
ptintf ("%d",a[i]);
c.排序
d.求最大/最小值
f.倒置
int a [7] = {1,2,3,4,5,6,7};
int i,j;
int t;
i=0;
j=6;
while (i < j)
{
t = a[i];
a[i] = a[j];
a[j] = t;
i++;
--j;
}
for (i=0;i<7;++i)
printf("%d\n",a[i]);
//输出为:7,6,5,4,3,2,1
g.查找
h.插入
i.删除
//其他的看书
第二节、二维数组
- int a[3][4];
总共是12个元素,可以当做3行4列看待,这12个元素的名字依次是:
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
a[i][j] 表示第i+1行第j+1列的元素
int a[m][n];第二维数组右下角位置的元素只能是a[m-1][n-1]
2.初始化
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int a[3][4] = {
{1,2,3,4},
{5,6,7,8},
{9 ,10,11,12}
};
3.输出二维数组的内容
int a[3][4] = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
int i,j;
//输出数组内容
for (i=0;i<3;++i)
{
for (j=0;j<4;++j)
printf ("%d ",a[i][j]);
printf ("\n");
}
4.对二维数组的排序
5.求每一行的最大值
6.判断矩阵是否对称
第三节、多维数组
- 是否存在多维数组
不存在
因为内存是线性一致的
n维数组可以当做每个元素是n-1维数组的一维数组
比如:
int a[3][4];
该数组是含有3个元素的一维数组
只不过每个元素都可以再分成4个小元素
int a[3][4][5];
该数组是含有3个元素的一维数组
只不过每个元素都是4行5列的二维数组
第四节、数组的应用
- 筛法求100以内所有的素数
第一步:初始化数组的值,将a[2]=2,a[3]=3…
第二步:筛选2的倍数,3的倍数直到根号100的倍数
第三步:将筛选出来的值都变成0,再将其输出出来
程序的实现:
#include<stdio.h>
#include<math.h>
#define N 100
void SiftPrime(int a[],int n);
void PrintfPrime(int a[],int n);
int main()
{
int a[N+1];
SiftPrime(a,N);
PrintfPrime(a,N);
return 0;
}
void SiftPrime(int a[],int n)
{
int i,j;
for(i=2;i<=N;i++)
{
a[i]=i;
}
for(i=2;i<=sqrt(N);i++)
{
for(j=i+1;j<=N;j++)
{
if(a[i]!=0&&a[j]!=0&&a[j]%a[i]==0)
{
a[j]=0
}
}
}
}
void PrintfPrime(int a[],int n)
{
int i;
for(i=2;i<=N;i++)
{
if(a[i]!=0)
{
printf("%d\t",a[i]);
}
}
printf("\n");
}
- 文曲星猜数游戏
设计函数:
由计算机随机生成一个各位相异的4位数字,由人来猜
每次提示:xAxB
A前面的数字表示有几个数字猜对了位置也对了;
B前面的数字表示有几个数字猜对了但位置不对
思路:
用数组a存计算机随机生成的各位相异的4位数:MakeDigit(a)
用数组b存人猜的4位数:InputGuess(b)
比较a和b的相同位置元素,得到A前面数字:IsRightPosition(a,b)
比较a和b的不同位置元素:IsRightDigit(a,b)
程序实现:(部分函数)
int main()
{
...
MakeDigit(a);//随机生成一个各位相异的4位数
printf("How many times do you want to guess?");
scanf("%d",&level);//最多允许猜的次数
count=0;//记录用户猜的次数
do{
printf("No.%d of %d times\n",count,level);
if(InputGuess(b)==0)
continue;
count++;//记录已经猜的次数
rightPosition=IsRightPosition(a,b);//统计数字和位置都猜对的个数
rightDigit=IsRightDigit(a,b)-rightPosition;//统计数字猜对位置不对的个数
printf("%dA%dB\n",rightPosition,rightDigit);
}while(rightPosition!=a&&count<level);
if(rightPosition==4)
printf("Congratulation,you guess the right number at No.%d\n",count);
else
printf("Sorry,you haven't guess the right number,see you next time!\n");
return 0;
}
//随机生成一个各位相异的4位数---第一种方法
void MakeDigit(int a[])
{
srand(time(NULL));
a[0]=rand()%10;//千位数字
do{
a[1]=rand()%10;//百位数字
}while(a[0]==a[1]);
do{
a[2]=rand()%10;//十位数字
}while(a[0]==a[2]||a[1]==a[2]);
do{
a[3]=rand()%10;//个位数字
}while(a[0]==a[3]||a[1]==a[3]||a[2]==a[3]);
}
//随机生成一个各位相异的4位数---第二种方法
void MakeDigit(int a[])
{
int i,j,temp;
srand(time(NULL));
for(i=0;i<10;i++)
{
a[i]=i;
}
for(i=0;i<10;i++)
{
j=rand()%10;
temp=a[j];
a[j]=a[i];
a[i]=temp;
}
}
//用户输入
int InputGuess(int b[])
{
int i,ret;
for(i=0;i<<4;i++)
{
ret=scanf("%ld",&b[i]);
if(ret!=1)
{
printf("Input Data Type Error!\n");
fflush(srdin);//清空输入缓冲区
return 0;//表示输入数据不合法
}
}
if(b[0]==b[1]||b[0]==b[2]||b[0]==b[3]||b[1]==b[2]||b[1]==b[3]||b[2]==b[3])
{
printf("The numbers must be different from each other ,input again\n");
return 0;//表示输入数据不合法
}
else
{
return 1;//表示输入数据合法
}
}
//A前面的数字
int IsRightPosition(int magic[],int guess[])
{
int rightPosition=0;
int=j;
for(j=0;j<4;j++)
{
if(guess[j]==magic[j])
{
rightPosition++;
}
}
return rightPosition;
}
//B前面的数字
int IsRightDigit(int magic[],int guess[])
{
int rightDigit=0;
int j;k;
for(j=0;j<4;j++)
{
for(k=0;k<4;k++)
{
if(guess[j]==magic[k])
{
rightDigit++;
}
}
}
return rightDigit;
}
- 数组元素的访问与螺旋矩阵
第九章、函数【C语言的第二个重点】
第一节、函数定义、调用
1.为什么需要函数
- 函数避免了重复性操作
- 有利于程序的模块化
例子:
下列函数:
# include <stdio.h>
int main(void)
{
int a=1,b=2,c=32,d=11,e=31,f=54;
if (a>b)
printf ("%d\n",a);
else
printf ("%d\n",b);
if (c>d)
printf ("%d\n",c);
else
printf ("%d\n",d);
if (e>f)
printf ("%d\n",e);
else
printf ("%d\n",f);
return 0;
}
等价于函数:
# include <stdio.h>
void max (int i,int j) //max是函数的名字,i和j是形参(形式参数),void表示函数没有返回值
{
if (i>j)
printf ("%d\n",i);
else
printf ("%d\n",j);
}
int main(void)
{
int a=1,b=2,c=32,d=11,e=31,f=54;
max (a,b);
max (c,d);
max (e,f);
return 0;
}
2.什么叫函数
- 逻辑上:能够完成特定功能的独立的代码块
- 物理上:
能够接收数据【当然也可以不接收数据】
能够对接受的数据进行处理
能够将数据处理的结果返回【当然也可以不返回任何值】
# include <stdio.h>
int f(void) //括号中的void表示该函数不能接受数据,int表示函数返回值是int类型
{
return 10;//向主调用函数返回10
}
void g(void)//8行 函数名前面的void表示该函数没有返回值
{
// return 10;//error 与8行行首的void相矛盾
}
int main(void)
{
int j=88;
j = f();
printf ("%d\n",j);
//j = g();//error因为g函数没有返回值
return 0;
}
- 总结:函数是个工具,它是为了解决大量类似问题而设计的函数,可以当做一个黑匣子
3.如何定义函数
格式:
函数的返回值 函数的名字(函数的形参列表)
{
函数的执行体
}
- a.函数定义的本质是详细描述函数之所以能够实现某个特定功能的具体实现方法
- b.return 表达式; 的含义:
- 终止被调函数,向主调函数返回表达式的值
- 如果表达式为空,则只终止函数,不向被调函数返回任何值
- break是用来终止循环和switch的,return 是用来终止函数的
例子:
# include <stdio.h>
void f(void)
{
int i;
for(i=0;i<5;++i)
{
printf ("大家辛苦了!\n");
//break;//当这里为break 时,输出为:大家辛苦了!同志们好!
return ;//当这里为return 时,输出为:同志们好!
}
printf ("同志们好!\n");
}
int main(void)
{
f();
return 0;
}
void f()
{
return ;//return只用来终止函数,不向被调函数返回任何值
}
int f()
{
return 10;//第一:终止函数; 第二:向主调函数返回10
}
- c.函数返回值的类型也称为函数的类型,因为如果函数名前的返回值类型和函数执行体中的return表达式; 中表达式的类型不同的话,则最终函数返回值的类型以函数名前的返回值类型为准
例子:
# include <stdio.h>
int f()
{
return 10.5;//因为函数的返回值类型是int ,所以最终返回的是10而不是10.5
}
int main(void)
{
int i=99;
double x=6.6;
x=f();
printf ("%lf\n",x);
return 0;
}
4.函数的分类
- a.有参函数和无参函数
- b.有返回值和无返回值
- c.库函数和用户自定义函数
- d.值传递函数和地址传递
- e.普通函数和主函数(main函数)
以下几句话要背会:
一个程序必须有且只有一个主函数
主函数可以调用普通函数。普通函数不能调用主函数
普通函数可以相互调用
主函数是程序的入口也是程序的出口
5.举例子:
- 例1:
# include <stdio.h>
void max1 (int i,int j) //max是函数的名字,i和j是形参(形式参数),void表示函数没有返回值
{
if (i>j)
printf ("%d\n",i);
else
printf ("%d\n",j);
}
int max2 (int i,int j)
{
if(i>j)
return i;
else
return j;
}
int main(void)
{
int a=1,b=2,c=32,d=11,e=31,f=54;
//当用函数max2时
printf ("%d\n",max2(a,b));
printf ("%d\n",max2(c,d));
printf ("%d\n",max2(e,f));
//当用函数max1时
max1 (a,b);
max1 (c,d);
max1 (e,f);
return 0;
//max1有两个作用:找最大值和进行处理;而 max2只是找最大值;所以将来编写代码主要看目的是什么
- 例2:求素数
方法一:
# include <stdio.h>
int main (void)
{
int val;
int i;
scanf ("%d",&val);
for(i=2;i<val;++i)
{
if (val%i==0)
break;
}
if (i==val)
printf("Yes!\n");
else
printf ("no!\n");
return 0;
}
方法二:(用函数的方法)
# include <stdio.h>
bool IsPrime(int val)
{
int i;
for(i=2;i<val;++i)
{
if (val%i==0)
break;
}
if (i==val)
return true;
else
return false;
}
int main (void)
{
int m;
scanf ("%d",&m);
if (IsPrime(m))
printf ("Yes!");
else
printf ("No!");
return 0;
}
- 例3:
例子:
下列两个函数中都有i,但是并不冲突,每个函数的i只在该函数中使用
# include <stdio.h>
void f(int i)
{
}
int main (void)
{
int i;
i = f(5);//但是 f 函数中没有返回值,所以不能赋值给 i
return 0;
}
6.注意的问题
- a.函数调用和函数定义的顺序
如果函数调用写在了函数定义的前面,则必须加函数前置声明
函数前置声明:
- 告诉编译器即将可能出现的若干个字母所代表的是一个函数。
- 告诉编译器即将可能的若干个字母所代表的函数的形参和返回值的具体情况。
- 函数声明,这是一个与句末尾必须加分号
- 对库函数的声明是通过 #include <库函数所在的文件的名字.h>来实现的
举个例子:(如果定义的函数在主函数的后面那么在主函数前应该加一个函数声明)
例1:
# include <stdio.h>
void f(void);//函数声明,分号不能丢
//声明的作用:告诉软件接下来有个函数没有返回值,没有形参
int main (void)
{
f();
return 0;
}
void f(void)
{
printf ("哈哈!\n");
}
例2:
#include <stdio.h>
void f(void);
void g(void)
{
f();//如果不加函数声明,则会报错;因为函数f的定义放在了调用f语句的后面,所有语法出错
}
void f(void)
{
printf ("哈哈!\n");
}
int main (void)
{
g();
return 0;
}
- b.形参和实参
个数相同 位置一一对应 数据类型必须相互兼容
举个例:
# include <stdio.h>
void f (int i,float x)
{
printf ("%d\n",i);
}
int main (void)
{
f(9,6.6);//必须是int和float,位置不能颠倒
return 0;
}
- c.如何在软件开发中合理的设计函数来解决实际问题
一个函数的功能尽量独立,单一
多学习,多模仿牛人的代码
函数是C语言的基本单位,类是java,c#,c++的基本单位
例1:
/*判断一个数字是否是素数;
用单独的函数来实现,代码的可重用性提高*/
# include <stdio.h>
bool IsPrime(int val)
{
int i;
for (i=2;i<val;++i)
{
if (0==val%i)
break;
}
if (i==val)
return true;
else
return false;
}
int main (void)
{
int val;
int i;
scanf ("%d",&val);
if (IsPrime(val))
printf ("Yes!\n");
else
printf ("No!\n");
return 0;
}
例2:
/*求1到某个数字之间(包括该数字)所有的素数,并将其输出
用1个函数来判断一个数字是否是素数
优点:
代码更容易理解
代码的可重用性高
缺点:
可重用性仍然不是非常高,
比如求1000个数字,求他们每个数字从1到他本身的素数,
则
for (i=2;i<=val;++i)
{
if (IsPrime(i))
printf ("%d\n",i);
}
要写1000次
# include <stdio.h>
bool IsPrime(int m)
{
int i;
for(i=2;i<m;++i)
{
if (0==m%i)
break;
}
if (i==m)
return true;
else
return false;
}
int main (void)
{
int val;
int i;
scanf ("%d",&val);
for(i=2;i<=val;++i)
{
if (IsPrime(i))
printf ("%d\n",i);
}
return 0;
}
7.常用的系统函数
- double sqrt (double x)
求x的平方根 - int abs (int x)
求x的绝对值- double fabs (double x)
求x的绝对值
- double fabs (double x)
8.如何合并成一个完整的程序
- 如果是标准库函数时,只需在主函数前加上定义这些标准库函数的头文件就可以了
- 若是用户自定义的函数时(同样加上编译预处理指令头文件在主函数前),一种是放在主函数前面;一种是放在主函数后,要加上函数定义语句(例:加上int Average(int a,iny);)加在主函数前,起到声明的作用。
第二节、函数封装与程序的健壮性
- 外界对函数的影响—仅限于入口参数
- 函数对外界的影响—仅限于一个返回值和数组、指针形参
- 信息隐藏
检查入口参数的有效性、合法性
检查函数调用是否成功
第三节、断言
- 程序中的假设
- 某个特定点的某个表达式的值一定为真
- 某个特定点的某个表达式的值一定位于某个区间等
- 如何确定这些假设的真假呢?
断言(Assert)
–测试程序中假设的正确性
–如果假设被违反,则中断程序的执行
断言的表达:
在<assert.h>中定义宏
void assert(int expression);
expression为真,无声无息
expression为假,中断程序 - 问题:使用条件语句代替断言,可不可以?
使用断言便于在调试程序时发现错误,不会影响程序执行效率 - 仅用于调试程序,不能作为程序的功能
- 何时适合使用断言呢?
检查程序中的各种假设的正确性
证实或测试某种不可能发生的状况确实不会发生 - 使用断言的基本原则
使用断言捕获不应该或者不可能发生的情况
每个assert只检验一个条件
第四节、函数的递归调用
- 函数直接或间接调用自己,成为递归调用自己成为递归调用
例如:
long Fact(int n)
{
if(n<0)
return -1;
else if(n==0||n==1)
return 1;
else
return n*Fact(n-1);
}
//Fact(3)=3*Fact(2)
// Fact(2)=2*Fact(1)
// Fact(1)=1
// 结束
例子:汉诺塔问题:将n个圆盘借助C从A移到B
void Hanoi(int n,char a,char b,char c)
{
if(n==1)
{
Move(n,a,b);
}
if(n>1)
{
Hanoi(n-1,a,c,b);//将上面的N-1个圆盘由A移到C
Move(n,a,b);//将第n号圆盘从A移到B
Hanoi(n-1,c,b,a);//将上面n-1个圆盘从C移到B
}
}
- 堆栈溢出:往堆栈中存入数据超出预先给堆栈分配的容量
第五节、向函数传递数组
- 调用形参含有数组的函数时,实参应该为数组名
- 例1(一维数组):
//定义函数
void ReadScore(int score[],int n)
{
int i;
printf("Input score:");
for(i=0;i<n;i++)
{
scanf("%d",&score[i]);
}
}
//调用函数
#include<stdio.h>
#define N 40
void ReadScore(int score[],int n);
int main()
{
int score[N],aver,n;
printf("Input n;");
scanf("%d",&n);
ReadScore(score,n); //用不带下标的数组名做函数实参
printf("Average score is %d\n",aver);
return 0;
}
- 例2:(二维数组)
//函数的定义
void AverforCourse(int score[][COURSE_N],int sum[],float aver[],int n)
{
int i,j;
for(j=0;i<COURSE_N;j++)//先遍历每门课程
{
sum[j]=0;
for(i=0;i<n;i++)
{
sum[j]=sum[j]+score[i][j]
}
aver[j]=(float)sum[j]/n;
}
}
//函数的调用
第六节、计算最大值函数实现
//函数的定义
int FindMaxValue(int x[],int n)
{
int maxValue,i;
maxValue=x[0];
for(i=1;i<n;i++)
{
if(x[i]>maxValue)
{
maxValue=x[i];
}
}
return maxValue;
}
//函数的调用
#include<stdio.h>
#define N 40
int ReadScore(int scpre[]);
int FindMaxValue(int x[],int n);
int main()
{
int score[N],maxValue,n;
n=ReadScore(score);
printf("Total students are %d\n",n);
maxValue=FindMaxValue(score,n);
printf("The highest score is %d\n",maxValue);
return 0;
}
第七节、线性查找
例:查找某学号学生的成绩
//主函数
#define N 40
#include<stdio.h>
int ReadScore(long num[],int score[]);
int LinSearch(long num[],long x,int n);
int main()
{
int score[N],n,pos;
long num[N],x;
n=ReadScore(num,score);
printf("Input the serching ID:");
scanf("%ld",&x);
pos=LinSearch(num,x,n);
if(pos!=-1)
{
printf("score=%d\n",score[pos]);
}
else
{
printf("Not found!\n");
}
return 0;
}
//定义函数
int LinSearch(long num[],long x,int n)
{
int i;
for(i=0;i<n;i++)
{
if(num[i]==x)
{
return i;//找到时返回下标
}
}
return -1;//找不到时返回-1
}
int ReadScore(long num[],int score[])
{
int i=-1;
do{
i++;
printf("Input num,score:");
scanf("%ld%d",&num[i],&score[i]);
}while(score[i]>=0);
return i;
}
- 线性查找的性能:
- 最好情况:要找的就是第一个
- 最坏情况:要找的是最后一个
- 平均情况:查找次数是数据量的一半
- 如何查找的更快
二分查找(要求必须是排好序的数据表)
- 将表的中间位置记录关键字与查找关键字比较,如果两者相等,则查找成功;否则将表分成前、后两个子表,根据比较结果,决定查找哪个子表
- 何时确定是找不到
算法实现:
int BinSearch(long num[],long x,int n)
{
int low =0,high=n-1,mid;
while(low<=high)
{
//mid=(high+low)/2;//当数组很大时,low和high之和大于有符号整数的极限值就会发生数值溢出,使mid成为一个负数
mid = low+(high-low)/2;//正确的方式
if(x>num[mid])
{
low=mid+1;
}
else if(x<num[mid])
{
high=mid-1;
}
else
{
reutrn mid;
}
}
return -1;
}
- 二分查找的性能:
比较次数少,查找速度快,平均性能好;
每执行一次,将查找空间减少一半,是计算机科学中分治思想的完美体现。 - 缺点:
关键字有序排列,否则需先排序;
采用顺序存储结构,插入和删除数据需移动大量的数据;
适用于不经常变动而查找频繁的有序表。
第八节、冒泡排序
- 将最大的元素在每次比较中沉到底部
- 算法实现
void BubbleSort(int score[],int n)
{
int i,j,temp;
for(i=0;i<n-1;i++)
{
for(j=1;j<n-i;j++)
{
if(score[j]<score[j-1])
{
//交换相邻元素
temp=score[j];
score[j]=score[j-1];
score[j-1]=temp;
}
}
}
}
第九节、排序算法的函数实现
- 交换法排序
- 算法实现
void ChangeSort(int score[],int n)
{
int i,j,temp;
for(i=0;i<n-1;i++)
{
for(j=i+1;j<n;j++)
{
if(score[j]<score[i])
{
temp=score[j];
score[j]=score[i];
score[i]=temp;
}
}
}
}
- 选择法排序
- 算法实现
void SelectionSort(int score[],int n)
{
int i,j,k,temp;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(score[j]<score[k])
{
k=j;//记录最小下标位置
}
}
if(k!=i)//若最小不在下标位置i
{
temp=score[k];
score[k]=score[i];
score[i]=temp;
}
}
}
第十章、变量的作用域和存储方式
1.按作用域分
- a.全局变量
在所有函数外部定义叫全局变量叫全局变量
在全局变量使用范围:从定义位置开始到整个程序结束
例子:
int k=1000;
void g()
{
printf ("k=%d\n",k);
}
//k在g函数的外面,k在任何函数中都可以用
但如果:
void g()
{
printf ("k=%d\n",k);
}
int k=1000;
//这时会报错,程序运行是由上到下的,k在g 函数的下面,则会出错;
//一定要把`int k=1000`放在函数g的前面
- b.局部变量
在一个函数内部定义的变量或者函数的形参都统称为局部变量
局部变量使用范围:只能在本函数内部使用
例子:
void f(int i)
{
int j = 20;
}
//i和j都属于局部变量
- c.注意的问题
全局变量和局部变量命名冲突的问题
在一个函数内部如果定义的局部变量的名字和全局变量名一样时,局部变量会屏蔽掉全局变量
# include <stdio.h>
int i=99;
void f(int i)
{
printf ("i=%d\n",i);
}
int main (void)
{
f (8);
return 0;
}
//最后输出的值为:i=8
2.按变量的存储方式
- a.静态变量
- b.自动变量
- c.寄存器变量
第十一章、指针
第一节、介绍
- int *pa;
- 意思是指针变量pa指向一个整型数
- 赋值:int *pa=&a;//a为整型数,将一个整型数的地址赋给pa
- pa和&a还是有区别的,pa是可变的,而&a是不变的
- NULL是什么
- 它是空指针,即无效指针
- P=0和P=NULL的区别在于:P=NULL可以明确的说明P是指针变量而不是一个数值型变量
- 空指针不一定就是指向地址为0的存储单元的指针:并非所有的编译器都使用0地址,某些编译器为空指针使用不存在的内存地址
- 如何访问指针变量指向的存储单元中的数据
- 通过间接寻址运算器访问(引用)指针变量指向的变量的值
- 指针的解引用:通过指针来访问指针所指向的变量值
- 指针变量的定义与初始化
#include<stiod.h>
int main()
{
int a=0,b=1;
int *pa,*pb;
pa=&a;
pb=&b;
printf("a=%d,b=%d\n",a,b);
printf("*pa=%d,*pb=%d\n",*pa,*pb);
return 0 ;
}
第二节、指针变量做函数参数
- 指针变量的解引用
- 为什么要用指针变量做函数参数
- 典型事例:
- 1.call by value:(用此种方法只实现了形参值的交换,并没有实现实参值的交换)
- 2.call by reference(此种方法交换了两数的地址)
- 3.也可用数组进行交换
第三节、函数指针
- 什么是函数指针
函数指针就是指向函数的指针变量
形式:数据类型 (*指针变量名)(形参列表);
- 定义函数指针时的常见错误
- 函数指针有什么用
- 将函数指针作为一个函数的形参时,只要将不同的函数作为实参传给形参,那么在该函数内就可以调用不同的函数了
- 编写通用性更强的函数
例:(虽然在这个程序中没有实际价值)
#include <stdio.h>
void Fun(int x,int y,int (*f)(int, int));
int Max(int x,int y);
int Min(int x,int y);
int Add(int x,int y);
int main()
{
int a,b;
scanf("%d,%d",&a,&b);
Fun(a,b,Max);
Fun(a,b,Min);
Fun(a,b,Add);
return 0;
}
void Fun(int x,int y,int (*f)(int,int))
{
int result;
result=(*f)(x,y);
paintf("%d\n",result);
}
int Max(int x,int y)
{
printf("max=");
return x>y?x:y;
}
int Min(int x,int y)
{
printf("min=");
return x<y?x:y;
}
int Add(int x,int y)
{
printf("sum=");
return x+y;
}
- 函数指针的典型应用—编程实现升序和降序排列
- 正确理解指针的概念:
- 指针是一种特殊的数据类型
- 指针类型的变量,称为指针变量
- 指针不是地址,指针变量的值是一个地址
- 想让指针变量指向哪个存储单元,就让其保存哪个单元的地址:保存一个变量的地址;保存一个数组的地址;保存一个字符串的首地址;保存一个函数的入口地址
- 使用指针变量的基本原则:
- 明确指针指向了哪里—初始化的目的
- 明确指针指向单元的内容是什么—基类型
- 只能指向同一基类型的数据—一个(x型)的指针指向一个(x型)的变量
- 指针的重要作用:
- 作函数参数,向函数传递变量或函数的地址
- 实现动态分配内存,实现动态数组和动态数据结构
- 指向变量的指针作为函数参数:
- 被调函数根据传入的地址读写它不能直接访问的变量的值
- 指向函数的指针,作函数参数:
- 被调函数根据传入的不同地址调用不同的函数
第四节、字符数组与字符指针
- 字符输入输出字符串
#define STR_LEN 80
char str[STR_LEN+1];
//方法一:逐个字符输入输出
for(i=0;str[i]!='\0';i==)//一般不用字符串长度控制,如i<STR_LEN
{
putchar(srt[i]);
}
putchar('\n');
//方法二:整体输入输出字符串
scanf("%s",str);//本来就是字符数组,故不用&,直接用数组名即可
printf("%s\n",str);
//方法三:
gets(str);//从键盘输入,不等价与scanf
//scanf不能输入带空格的字符串,而gets可以
puts(str);//输出到屏幕上,等价于printf
- scanf("%s",str);—用%d输入数字或%s输入字符串时。在开始读之前会跳过空格、回车或制表符等空白字符 ,再次遇到这些字符 时,系统认为读入结束,因此不能输入带空格的字符串
- gets(str);—以回车换行符作为终止符,可输入带空格的字符串,因为空格和制表符都是字符串的一部分
- gets()将回车从缓冲区读走,过意getchar()等待用户输入
- scanf()不读走回车,回车仍留在缓冲区中,回车会被getchar()读走
- getchar()可用于清空缓冲区中的空白字符
- scanf(" %c",&ch)也可用于清空缓存区中的空白字符
- 字符串的表示与存储
- printf(“How are you”);—用双括号括起的一串字符是字符串常量,系统自动为其添加空字符’\0’。用空字符’\0’标志字符串的末尾—字符串结束标志
- 转义字符为一个字符
- 连接字符“\”:
printf(“how are you.Press
a key and then press Enter:\n”);//第二行的必须在开头
表示为:how are you.Press a key and then press Enter:
方法二:两端直接用“ ”
- 字符数组的定义和初始化
- 字符数组的定义:
#define STR_LEN 80
char str[STR_LEN+1];
- 字符数组的初始化
用字符常量的初始化列表对数组初始化:
char str[6]={'C','h','i','n','\0'};
用字符串常量直接对数组初始化
char str[6]={"China"};//末尾自动加一个结束字符'\0'
char str[6]="China";//可省略花括号
char str[]="China";//可省略数组的长度
char str[10]="China";//当数组长度超过字符的长度时,自动把后面的数组元素定义为空字符
- 字符指针的定义和初始化
字符指针就是指向字符串首地址的指针
- 定义一个字符指针,使其指向一个字符串常量
char *pStr="Hello China";
//与char str[]="Hello China";的区别在于指针可修改,而数组不可修改
- 使用字符指针的基本原则
正确使用字符指针需牢记以下基本原则:
明确字符串被保存到了哪里
明确字符指针指向了哪里
char *pStr;//没有告诉指向哪里
pStr[0]=‘a’;//错误
*pStr=‘a’;//错误
scanf("%s",pStr);//错误
一个x型的指针指向x型变量的地址
永远要清楚你正在操作哪块内存
永远清除这种操作是否合理合法
第五节、字符串处理函数
- strlen(字符串);
- strcpy(目的字符数组,源字符串);
- strcat(目的字符数组,源字符串);
- strcmp(字符串1,字符串2);
- 计算字符串长度
#include<string.h>//头文件必须要有
char str[10]={"China"};
printf("%d",strlen(str));
//输出结果为5
- 字符串复制
str2=str1;这种方式无法进行复制
正确的方法:strcpy(str2,str1);//注意复制方向!str2须足够大
strncpy(str2,str1,n);//更安全,n是字符串的大小
#include<string.h>
strcpy(str2,strcpy(str1,"Hello"));//多重复制,函数有返回值才可以
- 字符串连接
strcat(str2,str1);//str2必须足够大
strncat(str2,str1,n);//更安全
strcat(str2,strcat(str1,"Hello"));
- 字符串比较
if(str2==str1)不能这样比较,但是合法的,这是比较地址的大小而不是比较内容的大小
if(strcmp(str2,str1)==0)
if(strncmp(str2,str1,n)==0)//最多比较字符串中n个字符的大小
第六节、向函数传递字符串
- 向函数传递字符串时,有两种方法:
- 既可用字符数组作为函数参数
- 也可用字符指针做函数参数
- 它们都属于传引用调用:传字符串的首地址,而非字符串中的全部字符
- 计算实际字符个数
unsigned int MyStrlen(const char *pStr)//从右往左读,指针变量,指向字符常量
{
unsigned int len=0;
//for(;*pStr!='\0';pStr++)
for(;*pStr;pStr++)//简化为判断是否为真
//还可以将其合并为一个表达式
for(;*pStr++;)
{
len++;
}
//继续优化上述代码
unsigned int MtStrlen(const char *pStr)
{
const char *start =pStr;
while(*pStr)
{
pStr++;
}
return pStr-start;
}
return len;
}
unsigned int MyStrlen(const char *pStr)//从右往左读:指针变量,指向字符常量
{
unsigned int len=0;
for(;*pStr1='\0';pStr++)
{
len++;
}
return len;
}
- const的作用:保护指针变量指向的内容不被修改;一旦改变了指针变量的内容编译器就会报警。
- 字符串复制
- 用字符数组编程实现:
void MyStrcpy(chaar dstStr[],char srcStr[])
{
int i=0;
while(srcStr[i]!='\0')
{
dstStr[i]=srcStr[i];
i++;
}
dstStr[i]='\0';//前面的循环没有将结束标志复制,这里我们单独将结束标志复制到字符串中
}
- 用字符指针编程实现字符串复制
void MyStrcpy(char *dstStr,char *srcStr)
{
while(*srcStr!='\0')
{
*dstStr=*srcStr;
srcStr++;
dstStr++;
}
*dstStr='\0';
//优化上述代码
while(*srcStr)
{
*dstStr++=*srcStr++;
}
*dstStr='\0';
//继续优化上述代码
void MyStrcpy(char *dstStr,const char *srcStr)
{
while(*dstStr++= *srcStr++)//循环在复制空字符后才会终止
{
;
}
}
}
第七节、从函数返回字符串
编程实现strcat()功能
#include<stdio.h>
#define N 80
char *MyStrcat(char *dstStr,char *srcStr);
int main()
{
char first[2*N+1];
char second[N+1];
char *result=NULL;
printf("Input the first string:");
gets(first);
printf("Input the second string:");
gets(first);
printf("Input the second string:");
result=MyStrcat(first,second);
printf("The result is:%s\n",result);
return 0;
}
char *MyStrcat(char *dstStr,char *srcStr)
{
char *pStr=dstStr;
while (*dstStr!='\0')
{
dstStr++;
}
while(*srcStr!='\0')
{
*dstStr=*srcStr;
srcStr++;
dstStr++;
}
*dstStr='\0';
return pStr;//返回字符串首地址
}
第十二章、指针和数组
第一节、指针和一维数组之间的关系
- 一维数组元素的引用
- 数组名代表数组的首地址&a[0]
&a[i]—(a+i)
a+1不是加上1个字节,取决于a的类型
a+1—a+sizeof(基类型)
a+i—a+i*sizeof(基类型) - 数组元素的等价引用形式
a[i]—*(a+i)
用下标形式访问数组元素的本质
计算该元素在内存中的地址
- 指针和一维数组做函数参数
- 被调函数的形参声明为数组类型,用下标访问数组元素
void InputArray(int a[],int n)
{
int i;
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
}
void OutputArray(int a[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%4d",a[i]);
}
printf("\n");
}
- 被调函数的形参声明为指针类型,用指针算术运算访问数组元素
void InputArray(int *p,int n)
{
int i;
for(i=0;i<n;i++)
{
scanf("%d",p++);
}
}
void OutputArray(int *p,int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%4d",*p++);
}
printf("\n");
}
- 小结:
- 指针与一维数组间的关系和关键:牢记a[i]—*(a+i)
- 一维数组和指针做函数形参时等同的
- 数组指针并非在所有情况下都是等同的:sizeof(数组名)和sizeof(指针变量名),不可互换
第二节、指针和二维数组之间的关系
- 小结:
- 指针与二维数组间的关系的关键
理解二维数组的行指针和列指针
二维数组在内存中按行存储,但可以以两种方式看待它
一个x型的指针指向x型的数据
第三章、指针数组及其在字符串处理中的应用
- 指针、数组及其他类型的混合
- 基本数据类型
int 、long 、char、short、float、double… - 数组是一种从其他类型派生的数据类型
每个元素都有一个类型,称为数组的基类型 - 指针是一种从其他类型派生的数据类型
指向x型变量的指针,x称为指针的基类型 - 任何类型都可作为指针或数组的基类型
一个类型派生出新的类型,新的类型又派生出新的类型,…
- 指针数组与指向数组的指针
- 用数组作为指针的基类型—指向数组的指针
例如:int (*p)[5];
p— * —int [5] - 用指针做数组的基类型—指针数组
元素为指针类型的数组 - 定义形式为:数据类型 *数组名[数组长度];
例如:char *p[5];
p—[5]—char *
- 字符串的排序
- 利用数组
- 利用指针数组
第四章、指针数组的应用:命令行参数
- 命令行参数有什么用
使用户可以根据需要来决定程序干什么、怎么干 - 如何访问命令行参数
int main(int argc,char* argv[])
- argc:命令行参数的数量(包括程序名本身)
- argv:指向命令行参数的指针数组
- argv[0]:为指向程序名的字符指针
- argv[1]~argv[argc-1]为指向余下的命令行参数的字符指针
- 演示命令行参数与main函数形参间的关系
int main(int argc,char *argv[])
{
int i;
printf("The number of command line arguments is:%d\n",argc);
printf("The program name is:%s\n",argv[0]);
if(argc>1)
{
printf("The other arguments are following:\n");
for(i=1;i<argc;i++)
{
printf("%s\n",argv[i]);
}
}
return 0;
}
- 运行程序:
- 首先在project中找到set program argument
- 在弹出的对话框中输入命令行参数
第十三章、结构体和共同体
第一节、结构体类型与结构体变量
- 为什么需要结构体?
能将逻辑相关但类型不同的数据放在一起存储,将不同类型的数据放在一起定义一种数据类型—结构体类型 - 如何定义一个结构体变量
方法一:先定义结构体类型在定义变量名
struct student //定义结构体类型
{
long studentID;
char studentName[10];
char studentSex;
int yearOfBirth;
int score[4];
];
struct student stu1;//定义变量名
方法二:在定义结构体类型的同时定义变量
struct student
{
long studentID;
char studentName[10];
char studentSex;
int yearOfBirth;
int score[4];
}stu1;//分号一定不能省略
方法三:直接定义结构体变量(不指定结构体标签)
struct
{
long studentID;
char studentName[10];
char studentSex;
int yearOdBirth;
int score[4];
}stu1;
- typedef—给数据类型定义一个别名
关键字typedef为已存在的类型定义一个别名,并未定义新类型
- 定义结构体变量:
struct student stu1;和STUDENT stu1;
方法一:
struct student //定义结构体类型
{
long studentID;
char studentName[10];
char studentSex;
int yearOfBirth;
int score[4];
];
typedef struct student STUDENT;//定义变量名
方法二:
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
int yearOfBirth;
int score[4];
}STUDENT;//分号一定不能省略
方法三:
typedef struct
{
long studentID;
char studentName[10];
char studentSex;
int yearOfBirth;
int score[4];
}STUDENT;//分号一定不能省略
- 结构体变量的初始化
初始化列表中成员的顺序必须和结构体类型定义的顺序一致
第二节、结构体和数组的嵌套
- 在一个结构体内包含了另一个结构体作为其成员
- 嵌套结构体变量的初始化
- 结构体数组的定义和初始化
第三节、结构体所占内存的字节数
- 计算结构体类型占用内存字节数
用sizeof获得结构体大小
sizeof(变量或表达式)
sizeof(类型)
例:printf("%d\n",sizeof(struct sample));或printf("%d\n",sizeof(SAMPLE));
- 内存对齐
- 对于大多数计算机,数据项要求从某个数量字节的倍数开始存放
- short型数据从偶数地址开始存放,而int型数据则被对齐在4字节地址边界
- 为了满足内存地址对齐的要求,需要在较小的成员后加入补位
- 为什么要求内存地址对齐呢
提高内存寻址效率
结构体在内存中所占的字节数不仅与所定义的结构体类型有关,还与计算机系统本身有关
不同的系统和编译器,内存对齐方式可能会不同,是机器相关的
计算结构体所占内存的字节数时,一定要使用sizeof运算符
第四节、对结构体的操作
访问数组的元素:通过下标(位置)选择数组元素
访问结构体变量的成员:通过名字访问结构体的成员
- 如何访问结构体的成员
- 访问结构体变量的成员:
成员选择运算符(圆点运算符)
结构体变量名.成员名:stu1.studentID=100002;
例:stu1.studentName="王刚";//错误
正确:strcpy(stu1.studentName,"王刚");//正确
- 字符串不能直接进行点的访问
- 对嵌套的结构体成员,必须以级联方式访问
例:stu1.birthday.year=1991;
- 结构体变量的赋值操作
第五节、结构体指针
- pt++的含义:将pt指向下一个数组元素
第六节、向函数传递结构体
- 向函数传递结构体
- 向函数传递结构体的单个成员:复制单个成员的内容
- 向函数传递结构体的完整结构:复制结构体的所有成员
- 向函数传递结构体的首地址:仅复制一个地址
- 结构体变量做函数参数
- 只是将实参的一个副本传递给形参,实参和形参仍然是占用不同的存储单元。所以这里对p的修改是不会影响实参结构体变量的成员值的
- 结构体指针做函数参数
- 结构体变量做函数返回值
- 用const保护结构体指针指向的结构体
- 结构体的一个重要应用—封装函数参数
- 结构体数组做函数参数计算n个学生m门课程的平均分
//结构体
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
DATA birthday;
int score[4];
float aver;
}STUDENT;
void AverScore(STUDENT stu[],int n,int m)
{
int i,j,sum[N];
for(i=0;i<n;i++)
{
sum[i]=0;
for(j=0;j<m;j++)
{
sum[i]=sum[i]+stu[i].score[j];
}
stu[i].aver=(float)sum[i]/m;
}
}
- 结构体数组做函数参数计算n个学生m门课程的平均分和总分
//结构体
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
DATA birthday;
int score[4];
float aver;
int sum;
}STUDENT;
void AverScore(STUDENT stu[],int sum[],int n,int m)
{
int i,j,sum[N];
for(i=0;i<n;i++)
{
sum[i]=0;
for(j=0;j<m;j++)
{
sum[i]=sum[i]+stu[i].score[j];
}
stu[i].aver=(float)sum[i]/m;
}
}
void AverScore(STUDENT stu[],int n,int m)
{
int i,j;
for(i=0;i<n;i++)
{
stu[i].sum=0;
for(j=0;j<m;j++)
{
stu[i].sum=stu[i].sum+stu[i].score[j];
}
stu[i].aver=(float)stu[i].sum/m;
}
}
- 用结构体类型封装函数参数的好处?
精简参数个数、使函数接口更简洁、可扩展性好 - 小结
第七节、枚举类型
- 枚举—一一列举
- 应用场合:当某些量仅由有限个整型数据值组成时
- 枚举类型的声明:
enum weeks {SUM,MON,TUE,WED,THU,FRI,SAT};
enum weeks {SUM=7,MON=1,TUE,WED,THU,FRI,SAT};
typedef enum weeks {SUM,MON,TUE,WED,THU,FRI,SAT}WEEKS;
enum weeks today;//定义
WEEKS today;//定义
第八节、共用体
- 结构体与共用体
结构体:把关系紧密且逻辑相关的多种不同类型的变量,组织到一个统一的名字之下
共用体,也称联合:把情形互斥但逻辑相关的多种不同类型的变量,组织到一个统一的名字之下 - 共用体与结构体的不同点
- 共用体的两个应用
- 第一个:节省存储空间
- 第二个:构造混合的数据结构
- 小结
第十四章、动态数据结构的C语言实现内存映像
第一节、C语言的内存映像
第二节、动态内存分配
第三节、动态数组
- 举例:
- 动态数组的实现
第十四章、专题——动态内存分配【重点难点】
1.传统数组的缺点
-
a.
数组长度必须事先制定,且只能是常整数,不能是变量
例子:
int a[5]; //OK
int len = 5; int a[len]; //error -
b.
传统形式定义的数组,该数组的内存程序员无法手动释放
在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,知道该函数运行完毕,数组的空间才会被系统释放
# include <stdio.h>
void f(void)
{
int a[5] = {1,2,3,4,5};
//20个字节的存储空间,程序员无法手动编程释放它,它只能在本函数运行完毕时由系统自动释放
}
int main(void)
{
return 0;
}
-
c.
数组的长度一旦定义,其长度就不能再更改
数组的长度不能在函数运行的过程中动态的扩充或缩小 -
d.
A函数定义的数组,在A函数运行期间可以被其他函数使用,但A函数运行完毕之后,A函数中的数组将无法在被其他函数使用
传统方式定义的数组不能跨函数使用
# include <stdio.h>
void g(int * pArr, int len )
{
pArr[2] = 88; //pArr[2] ==a[2]
}
void f(void)
{
int a[5] = {1,2,3,4,5};//20个字节的存储空间程序员无法手动编程释放它,
g(a,5); //它只能在本函数运行完毕时由系统自动释放
printf("%d\n",a[2]);
}
int main(void)
{
f();
return 0;
}
2.为什么需要动态分配内存
动态数组很好地解决了传统数组的这4个缺陷
传统数组也叫静态数组
#include <stdio.h>
#include <malloc.h> //不能省
int main (void)
{
int i = 5; //分配了4个字节 静态分配 11行
int * p = (int *)malloc(4); //12行,malloc后面的括号里只能写一个不能写两个
/*
1.要使用malloc函数,必须添加 malloc.h 这个头文件
2.malloc函数只有一个形参,并且形参是整数
3. 4表示请求系统为本程序分配4个字节
4. malloc函数只能返回第一个字节的地址
5. 12行分配了8个字节,p变量占4个字节,p所指向的内存也占4个字节
6. p本身所占的内存是静态分配的,p所指向的内存是动态分配的
*/
* p = 5; //*p代表的就是一个int变量,只不过*p这个整型变量的内存分配和11行的 i 变量的分配方式不同
free(p); //free(p)表示把p所指向的内存给释放掉 p本身的内存是静态的,不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放
printf ("同志们好!\n");
return 0;
}
- malloc的用法
# include <stdio.h>
# include <malloc.h>
void f(int * q)
{
// *p = 200; //error
// q = 200;
// **q = 200; //error
*q = 200;
// free (q); //把q所指向的内存释放掉 本语句必须注释掉,否则会导致第20行的代码出错
}
int main (void)
{
int * p=(int *)malloc (sizeof(int )); //sizeof (int )返回值是int 所占的字节数
*p = 10;
printf ("%d\n",*p); //200 第20行
return 0;
}
3.动态内存分配举例 _ 动态数组的构造
# include <stdio.h>
# include <malloc.h>
int main (void)
{
int a[5]; //如果int 占4个字节的话,则本数组总共包含有20个字节,每四个字节被当做了一个int 变量来使用
int len;
int * pArr;
int i;
//动态的构造一维数组
printf ("请输入你要存放的元素个数:");
scanf("%d",&len);
pArr = (int *)malloc(4*len); // 类似于int pArr [len]; 本行动态的构造了一个一维数组,该数组的长度是len,该数组的数组名是pArr,该数组的每个元素是int 类型
//对一维数组进行操作,如:对动态一维数组进行赋值
for(i=0;i<len;++i)
scanf("%d",&pArr[i]);
//对一维数组进行输出
printf("一维数组的内容是:")
for(i=0;i<len;++i)
printf ("%d\n",pArr[i]);
free(pArr); //释放掉动态分配的数组
return 0;
}
4.静态内存和动态内存的比较
- a.静态内存是由系统自动分配,由系统自动释放
- b.静态内存是在栈分配的
- c.动态内存是由程序员手动分配,手动释放
- d.动态内存是在堆分配的
5.跨函数使用内存的问题
例1:(静态内存不能跨函数使用)
# include <stdio.h>
void f(int ** q) //q是个指针变量,无论q是什么类型的指针变量,都只占4个字节
{
int i=5;
//*q等价于p q和**q都不等价于p
//*q = i; //error 因为*q = i;等价于 p = i ;这样是错误的
*q = &i; // p = &i;
}
int main(void)
{
int *p; //13行
f(&p);
printf ("%d\n",*p); //16行 本语句语法没有问题,但逻辑上有问题
return 0;
}
例2:(动态函数可以跨函数使用)
# include <stdio.h>
# include <malloc.h>
void f(int **q)
{
*q = (int *)malloc(sizeof(int)); //sizeof(数据类型) 返回值是该数据类型所占的字节数
//等价于 p = (int *)malloc(sizeof(int));
// q=5;
// *q=5; //p = 5;
**q=5; //*p = 5; // 把动态内存更改为5个字节
}
int main(void)
{
int * p;
f(&p);
printf ("%d\n",*p);
return 0;
}
第十五章、枚举
1.什么是枚举
把一个事物所有可能的取值一一列举出来
例1:
# include <stdio.h>
//只定义了一个数据类型,并没有定义变量,该数据类型性的名字是enum WeekDay
enum WeekDay
{
MonDay,TuesDay,WednesDay,ThursDay,FfiDay,SaturDay,SunDay
};
int main (void)
{
//int day; //day定义成int类型不合适
enum WeekDay day = SunDay;
printf("%d\n",day);
return 0;
}
2.怎样使用枚举
例子:
# include <stdio.h>
enum weekday
{
MonDay,TuesDay,WednesDay,ThursDay,FriDay,SaturDay,SunDay
};
void f(enum weekday i)
{
switch(i)
{
case 0:
printf ("MonDay!\n");
break;
case 1:
printf ("TuesDay!\n");
break;
case 2:
printf ("WednesDay!\n");
break;
case 3:
printf ("ThursDay!\n");
break;
case 4:
printf ("FriDay!\n");
break;
case 5:
printf ("SaturDay!\n");
break;
case 6:
printf ("SunDay!\n");
break;
}
}
int main (void)
{
f(FriDay);
return 0;
}
3.枚举的优缺点
优点:
代码更安全
缺点:
书写麻烦
第十六章、专题
第一节、补码
1.原码
原码:
也叫 符号-绝对值码
最高位 0 表示正 1表示负,其余二进制位是该数字的绝对值的二进制位
原码简单易懂
加减运算复杂
存在加减乘除四种运算 ,增加了CPU的复杂度
零的表示不唯一
2.反码
反码运算不便,也没有在计算机中应用
3.移码
移码表示数值平移n 位,n称为移码量
移码主要用于浮点数的阶码的存储
4.补码
- a.已知十进制求二进制
-
求正整数转二进制
除2取余,直至商为零,余数倒序排序 -
求负整数的二进制
先求与该负数相对应的正整数的补码,然后将所有位取反,末尾加1,不够位数时,左边补1
例:(-3)→
# include <stdio.h>
int main(void)
{
int i=-3;
printf ("%#X\n",i); //用16进制输出 -3的补码
return 0;
}
//输出为:0XFFFFFFFD
- 求零的二进制
全是零
- b.已知二进制求十进制
- 如果首位是0,则表明是正整数,按普通方法来求
- 如果首位是1,则表明是负整数,将所有位取反,末尾加1。所得数字就是该负数的绝对值。
- 如果全是零,则对应的十进制数字就是零
例:
# include <stdio.h>
int main (void)
{
int i = 0XFFFFFFEF;
printf("%d\n",i);
return 0;
}
//输出结果为:-17
注:如果是负数,最高位一定是1
- c.8位二进制所代表的十进制示意图
5.学习目标
在VC++6.0中一个int 类型的变量所能存储的数字的范围是多少
int 变量所能存储的十六进制最大的整数是:7FFFFFFF
int 类型变量所能存储的绝对值最大的负整数用十六进制表示是:
最小负数的二进制代码是多少
最大正数的二进制代码是多少
已知一个整数的二进制代码求出原始的数字
数字超过最大正数会怎样
不同类型数据的相互转换
第二节、进制转换
1.十进制转 r 进制
方法:除 r 取余,直至商0,余数倒序排列
2.r 进制转化为十进制
例:201(8)=18º + 08¹ + 2*8² = 129
3.二进制与十六进制的转化
二进制转化为十六进制
例:(0101110)(2)→(0010,1110)(2)左补一个0 →
(2 E)(16)
十六进制转化为二进制
例:(2E)(16)→(0010 1110)(2)
4.二进制与八进制
5.十六进制与八进制
第三节、链 表
1.算法
- a.通俗定义
解题的方法和步骤 - b.狭义定义
对存储数据的操作
对不同的存储结果,要完成某一个功能所执行的操作是不一样的。
比如:
要输出数组中所有的元素的操作和
要输出链表中所有元素的操作肯定是不一样的
这说明:算法是依附于存储结构的,不同的存储结构,所执行的算法是不一样的。 - c.广义定义
广义的算法也叫泛型
无论数据是如何存储的,对该数据的操作都是一样的
2.我们至少可以通过两种结构来存储数据
- a.数组
- 优点:
存取速度快 - 缺点:
需要一个连续的很大的内存
插入和删除元素的效率很低
- b.链表
- 专业术语 :
-
头结点
头结点的数据类型和首节点的类型是一模一样的
头结点是首节点前面的那个节点
头结点并不存放有效数据
设置头结点的目的是为了方便对链表的操作 -
头指针
存放头结点地址的指针变量 -
首节点
存放第一个有效数据的节点 -
尾结点
存放最后一个有效数据的节点 -
确定一个链表需要一个参数:
头指针
例1:
# include <stdio.h>
//定义可一个链表节点的数据类型
struct Node
{
int data;
struct Node * pNext;
};
int main(void)
{
struct Node * pHead; //pHead用来存放链表头结点的地址
pHead = CreateList();
TraverseList(pHead);
return 0;
}
例2:
# include <stdio.h>
# include <malloc.h>
# include <stdlib.h>
struct Node
{
int data; //数据域
struct Node * pNext; //指针域
};
//函数声明
struct Node * create_list(void);
void traverse_list(struct Node *);
int main(void)
{
struct Node * pHead = NULL; //等价于 struct Node * pHead = NULL;
pHead = create_list(); //create_list()功能:创建一个非循环单链表,并将该链表的头结点的地址付给pHead
traverse_list(pHead);
return 0;
}
struct Node * create_list(void)
{
int len; //用来存放有效节点的个数
int i;
int val; //用来临时存放用户输入的结点的值
//分配了一个不存放有效数据的头结点
struct Node * pHead = (struct Node *)malloc(sizeof(struct Node));
if (NULL == pHead)
{
printf("分配失败, 程序终止!\n");
exit(-1);
}
struct Node * pTail = pHead;
pTail->pNext = NULL;
printf("请输入您需要生成的链表节点的个数: len = ");
scanf("%d", &len);
for (i=0; i<len; ++i)
{
printf("请输入第%d个节点的值: ", i+1);
scanf("%d", &val);
struct Node * pNew = (struct Node *)malloc(sizeof(struct Node));
if (NULL == pNew)
{
printf("分配失败, 程序终止!\n");
exit(-1); //终止程序
}
pNew->data = val;
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
void traverse_list(struct Node * pHead)
{
struct Node * p = pHead->pNext;
while (NULL != p)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
return;
}
- 优点:
插入删除元素效率高
不需要一个连续的很大的内存 - 缺点:
查找某个位置的元素效率很低
第四节、位运算符
- 位运算符的现实意义:
通过位运算符我们可以对数据的操作精确到每一位 - & ----按位与
&&逻辑与 也叫并且
&&与&的含义完全不同
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
5 & 7 = 5
21 & 7 = 5
例子:
# include <stdio.h>
int main (void)
{
int i = 5;
int j = 7;
int k;
k = i & j;
printf ("5%d\n",k);
k = i && j;//k的值只能是1或0,因为&&是逻辑运算符,逻辑运算符的结果只能是1或者0
printf ("%d\n",k);
return 0;
}
//输出为:5 1
/* 5 : 0 1 0 1
7 : 0 1 1 1
& : 0 1 0 1-----5
*/
- | -----按位或
||逻辑或
|按位或
1 | 0 = 1
1 | 1 = 1
0 | 1 = 1
0 | 0 = 0
# include <stdio.h>
int main (void)
{
int i = 3;
int j = 5;
int k;
k = i | j;
printf ("%d\n",k);
return 0;
}
//输出为:7
/* 3 : 0 0 1 1
5 : 0 1 0 1
| : 0 1 1 1-----7
*/
- ~ -----按位取反
~i 就是把 i 变量所有的二进制位取反
# include <stdio.h>
int main (void)
{
int i = 3;
int k;
k =~i;
printf ("%d\n",k);
return 0;
}
//输出结果为7
-
^------按位异或
相同为零,不同为1
1^0 = 1
0^1 = 1
1^1 = 0
0^0 = 0 -
<<-----按位左移
i<< 3 表示把 i 的所有二进制左移三位,右边要补零
左移n位相当于乘以2的n次方,前提是数据不能丢失
面试题:
A. i = i * 8;
B. i = i << 3;
请问上述两个语句,哪个语句执行的速度快?
答案:B 快
例子:
# include <stdio.h>
int main (void)
{
int i = 3;
int j = 5;
int k;
k = i<<1;
printf ("%d\n",k);
return 0;
}
//输出为:6
-
------按位右移
i >> 3 表示把 i 的所有二进制右移三位,左边一般是补零,当然也可能补1
右移n位相当于除以2的n次方,前提是数据不能丢失
面试题:
A. i = i / 8;
B. i = i >> 3;
请问上述两个语句,哪个语句执行的速度快?
答案:B 快