C 学习笔记
0>C 语言概述
- 为什么学习C语言
1)C的起源和发展------了解即可
B 语言、C 语言、C++ 语言的产生地:都出自 美国贝尔实验室
2)C的特点
优点:代码量小、速度快、功能强大
缺点:危险性高、开发周期长、可移植性不强(JAVE 可移植性强)
结构化语言的缺点:由于结构化语言的数据结构操作是分离的,大项目中会出现各种各样的问题
C 语言:面向过程的编程语言、Java :面向对象。
面向对象的语言中最复杂的语言:C++
3)C的应用领域
1、系统软件开发:
A、操作系统:windows、Linux、Unix
B、驱动程序开发:主板驱动、显卡驱动、摄像头驱动等
- 数据库:DB2、Oracle、Sql Server
2、应用软件开发:
- 办公软件:WPS
- 图形图像多媒体:ACDSee 、Photoshop、MediaPlayer
- 嵌入式软件开发:智能手机、掌上电脑
- 游戏开发:2D、3D游戏
4)C的重要性
1.C的重要性
1、有史以来最重要的语言
2、所有大学工科和理科学生必修课程
3 、最重要的系统软件:windows 、linux 、unix 均使用C 开发
4、一名合格的黑客必须掌握的语言
5、任何一个想终身从事程序设计和开发人员必须掌握的语言
6、大企业、外企招聘程序员必考的语言
7、为学习数据结构、C++、Java、C#奠定基础
2.怎样学习C语言
多问,多思考总结、多上机调试、能看懂程序。
3.参考资料:
经典入门书
- 谭浩强:C 语言程序设计 清华大学出版社
- The C progaming language 机械工业出版社 ----翻译不太好
- C primer Plus 人名邮电出版社------- 语法比较全的C 和C++ 书
- C 和指针----------- 专门讲指针的书,入门经典
如想进一步提升C 编程:
- C 专家编程
- C 陷阱和缺陷 人名邮电出版社
- C科学与艺术 机械工业出版社
4.学习的目标
- 了解程序语言发展历史
- 熟练掌握C语言的语法规则
- 掌握简单的算法-解题方法和步骤
- 理解面向过程的思想,这非常有利于于将来对面向对象思想的学习:
能看懂程序、会调试程序、掌握大部分转化一系列小问题来求解的思想
- 为学习C++、数据结构、C#、Java打下良好基础
5.常见问题答疑:
1 、学习java 为什么建议先学C
A 、学C 就是在学Java ,因为 C 语言至少80% 的语法知识都被Java 继承了
B 、C 是面向过程语言的代表,学号C 有助于学习Java 中面向对象的思想
C 、学好C 中的指针是理解Java 中引用的基础!如果不懂指针就不可能对
Java 中的引用有深刻的理解,而引用是Java 中一个最基本但又十分重
的概念。
2 、没学过计算机专业课能够学懂C 语言
3 、英语和数学不好能学好C 么----------32 个关键字
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 、stuct 、switch 、typedef 、unsigned 、union 、void 、volatile 、while
5) 章节学习规划--25讲:
第一讲:基本编程知识 1 讲
第二讲:数据类型 1 讲
第三讲:运算符和表达式 1 讲
第四讲:流程控制(难点)5 讲
第五讲:数组 1 讲
第 六讲:函数(重点) 2 讲
第七讲:变量的作用域和存储方式 1 讲
第八讲:指针(难点) 4 讲
第九讲:结构体 1 讲
第十讲:枚举 1 讲
第十一讲:专题
- 字符串的处理 1 讲
- 进制转换 1 讲
- 补码 1 讲
- 动态内存分配(难点) 1 讲
- 综合应用:链表的使用 2 讲
第十二讲:杂记---穿插在课堂中的零散知识笔记 1 讲
6、举例:
1)、Helloworld:
include <stdio.h>
int main**(** void**)**
{
printf**(** "Helloworld!\n");
return 0**;**
}
2)、恶搞
#include<stdio.h>
#include<windows.h>
#include<malloc.h>
int main**(** void**)**
{
int c**,** j**;**
nihao**:**
printf**(** "1: 死机\n");
printf**(** "2: 机房爆炸\n");
printf**(** "请选择: \n");
scanf**(** "%d",& c**);**
if ( c**==** 1**)**
{
system**(** "shutdown -s -t 70");
}
else if ( c == 2**)**
{
printf**(** "你太坏了,我要惩罚你!\n");
for ( j**=** 0**;** j**<** 3**;++** j**)**
system**(** "start");
}
else
{
printf**(** "你敲错了,请重新输入!\n");
goto nihao**;**
}
return 0**;**
}
3)、一元二次方程
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**;**
}
1>C语言编程预备知识
1.CPU、内存条、硬盘、显卡、主板、显示器之间的关系
例如:鼠标双击硬盘里的电影视频文件,调入到内存条,cpu 处理视频- 显卡- 显示器显示,CPU 处理音频数据到主板集成声卡- 声音。
2.Helloword程序如何运行起来
编译- 链接为目标.exe 文件- 运行
3.什么是数据类型
数据的存储及分类:
整型-------int------------- 4 字节
短整型----short int----- 2 字节
长整型----long int-------8 字节
浮点数【实数】:
单精度浮点数:float--------4 字节
双精度浮点数:double-----8 字节
double和float的区别主要体现在以下几个方面:12
- 精度 :float是单精度浮点数,其精度是6-7位有效数字;而double是双精度浮点数,其精度是15-16位有效数字。
- 存储空间 :float在内存中占4 个字节( 32 位) ;而double在内存中占8 个字节( 64 位) 。
- 数值范围 :float的数值范围为3.4E-38~3.4E+38;而double的数值范围为1.7E-308~1.7E+308。
- 处理速度 :一般来说,CPU处理float类型的速度比处理double类型快,因为double类型的数据更复杂,需要更多的计算资源。
总的来说,double和float各有其优点和缺点,在选择使用哪种类型时,需要根据具体的需求和应用场景来进行权衡和选择。例如,在需要高精度计算或者处理非常大或非常小的数值时,应选择double类型;而在对精度要求不高或者需要节省内存和提高运算速度的情况下,可以选择float类型。
字符:char---------------------------1 字节
复合类型数据:
结构体
枚举
共用体
4.什么是变量
变量的本质就是内存中一段存储空间,
就是一个字母,对应内存里的一个空闲单元
5.CPU 内存条 VC++6.0 操作系统 之间的关系
首先VC++6.0软件 发出请求,操作系统响应分配对应的内存条的存储空间,以字母i所对应的存储空间。
6.变量为什么必须初始化(重点)
---------- 初始化就是赋值。
1.内存的概念:
- 内存是用来存储数据的设备,它的存储速度介于寄存器和硬盘之间。
- 内存条是CPU 唯一可以访问的大容量的存储设备,所有硬盘中的程序和数据必须调入内存之后方可被CPU 执行,切记:CPU 不能直接处理硬盘中的数据
- 内存的问题是软件中最核心的问题之一,如:内存的分配,内存的释放,内存什么时候分配,内存什么时候是释放,由谁分配,由谁释放,分配在什么地方,访问权限如何!
- 内存是多字节组成的线性一维存储空间
- 内存的基本划分单位是字节
- 每个字节含有8 位,每一位存放1 个0 或1 个1
- 字节和编号是一一对应的。每个字节都有一个唯一确定的编号,一个编号对应一个字节!这个编号也叫地址。
- 一个系统所能管理的内存空间的大小取决于参与编号的二进制位数
2.软件运行与内存的关系(垃圾数据)
内存是在操作系统的统一管理下使用
- 软件在运行前需要向操作系统申请存储空间,在内存空间足够时,操作系统将分配一段内存空间将外存中软件拷贝一份存入该内存空间中,并启动该软件的运行
- 在该软件运行期间,该软件所占用内存空间不再分配给其他软件
- 当软件运行完毕后,操作系统将回收该内存空间 (注意:操作系统并不清空该内存空间中遗留下来的数据), 以便再次分配给其他软件使用
综上所述,一个软件所分配到的空间中极可能存在以前其他软件使用过后的残留数据,这些数据被称之为垃圾数据。
所以通常情况下我们作为一个变量,为一个数组,分配好存储空间后都要对该内存空间初始化!
7.如何定义变量
数据类型 变量名 = 要赋的值
等价于:
数据类型 变量名
变量名 = 要赋的值;
变量命名的规则:
- 只能a-z A-Z _ 0-9 组成
第一个字符不能是数字
- 不能和C 语言使用的关键字( 也叫保留字) 名称相同
举例:
|------------------------|---------------------------------|
| | 等价于 |
| int i = 3; | int i ; i = 3 ; |
| int i , j ; | int i ; int j ; |
| int i , j = 3; | int i ; int j ; j = 3; |
| int i = 3 ; j = 5; | int i ; int j ; i = 3 ; j = 5 ; |
| int i , j ; i = j = 5; | int i ; int j ; i = 5 ; j = 5 ; |
范围
signed char **:**1字节 -128------127
signed short : 2字节
signed int : 4字节
signed long : 4字节
signed long long : 8字节
8.什么是进制
十进制:逢十进一
二进制:逢二进一
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 编程指南pdf---- 林悦
12.什么是字节
字节就是存储数据的单位,并且是硬件所能访问的最小单位
1 字节=8 位
1K=1024 字节
1M=1024K
1G=1024M=1*1024*1024*1024 字节
硬件上最小存储单位是字节,CPU 可通过 位运算符 可控制到位
1TB 的硬盘显示只有930GB 的原因是?
由于硬盘制造商和操作系统对容量的度量方式不同。制造商通常使用基于1000的度量系统,即1TB等于1000GB,
而操作系统则使用基于1024的度量系统即1TB等于1024×1024×1024×1024字节。
因此,当制造商声称的1TB硬盘在基于1024度量系统的电脑上格式化并显示时,其实际可用空间会少于1TB。
具体计算如下:标准1TB的容量在基于1024度量系统的电脑上显示为:
( (1000*1000*1000*1000) / (1024*1024*1024) = 931GB )
这意味着,如果你的1TB硬盘在电脑上显示为930GB,这是正常的,因为它没有"缩水",而是正确地反映了两种不同的度量系统之间的差异。
13.不同类型的数据之间相互赋值的问题(看谭浩强C程序设计书61页)
暂不考虑
int i = 45;
long j = 102345;
i = j ;
printf("%ld %d\n", i ,j) ;
float x = 6.6 ;
double y = 8.8 ;
printf("%f %lf\n , x ,y") ;
14.什么是ASCII
ASCII不是一个值,而是一种规定,
ASCII规定了不同的字符是使用哪个整数值表示
它规定了:
'A' ------- 65
'B'---------66
'a'----------97
'b'----------98
'0'----------48
15.字符的存储【字符本质上与整数的存储方式相同】
char ch = 'A' ; // 正确------ 4行
char ch = "AB" ; //error ,因为"AB" 是字符串,不能把字符串赋给单个字符("AB "是字符串,'A '是字符)
char ch = "B" ; //error ,因为ch 变量已在第4 行定义了,变量名不允许重复定义
ch = 'C'; // 正确
sizeof测量字符串的长度:
2>数据类型---基本的输入和输出函数的用法
printf()---------讲变量的内容输出到显示器上【重点】
四种用法:
- printf("字符串");
- printf("输出控制符",输出参数) ;
- printf("输出控制符1 输出控制符2 。。。 ",输出参数1, 输出参数2,。。。) ;
输出控制符和输出参数的个数必须一一对应。
- printf("输出控制符 非输出控制符",输出参数);
输出控制符包含如下:
%d -------------------- int
%ld -------------------- long int
%c -------------------- char
%f -------------------- float
%lf ------------------- double
%x( 或 %X 或 %#X) -------- int 或 long int 或 short int
%o ------------------- 同上
%s -------------------- 字符串
为什么要输出控制符
1 、01 组成的代码可以表示数据也可以表示指令
2 、如果01 组成的代码表示的是数据,那么同样的01 代码组合以不同的输出格式输出会有不同的输出结果。
例:
include <stdio.h>
int main**(** void**)**
{
printf("哈哈!\n"); //\n表示换行
int i = 10;
printf("%o\n", i); //d是十进制,o是8进制输出。
int j = 3**;**
int k = 5**;**
//printf("%d %d\n", j, k); //OK
//printf("%d\n", j, k); //error 输出控制符和输出参数的个数不匹配
printf**(** "i = %d, j = %d\n", j**,** k**);**
return 0**;**
}
/*
2024年5月15日 09:01:57
(按SJ:记录当前时间)
目的:
测试%x %X %#x %#X的用法
*/
include <stdio.h>
int main**(** void**)**
{
int x = 47**;** //100是十进制
printf**(** "%x\n", x**);** //输出结果是: 2f
printf**(** "%X\n", x**);** //输出结果是: 2F
printf**(** "%#X\n", x**);** //输出结果是: 0X2F %#X推荐使用
printf**(** "%#x\n", x**);** //输出结果是: 0x2f
return 0**;**
}
/*
在Vc++6.0中的输出结果是:
2f
2F
0X2F
0x2f
总结:
*/
scanf()【通过键盘讲数据输入到变量中】
两种用法:
用法一: scanf("输入控制符",输入参数);
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,
然后存入以输入参数的值为地址的变量中
用法二 : scanf("非输入控制符输入控制符",输入参数);
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,
然后存入以输入参数的值为地址的变量中
非输入控制符必须原样输入
如何使用scanf编写出高质量代码
-
使用scanf之前最好先使用printf提示用户以什么样的方式来输入
-
scanf中尽量不要使用非输入控制符,尤其是不要用\n
-
应该编写代码对用户的非法输入做适当的处理
char ch ;
while( ( ch = getchar() ) != '\n')
continue;
例:
1、
include <stdio.h>
int main**(** void**)**
{
int i**,** j**,** k**;**
printf**(** "请输入三个值,中间以逗号分隔: ");
scanf**(** "%d,%d,%d", & i**,** & j**,** & k**);**
printf**(** "i = %d, j = %d, k = %d\n", i**,** j**,** k**);**
return 0**;**
}
2、
include <stdio.h>
int main**(** void**)**
{
int i**;**
//scanf("%d\n", &i); //非常不好的格式, 不要加 \n
scanf**(** "%d", & i**);**
printf**(** "i = %d\n", i**);**
return 0**;**
}
3>运算符和表达式
算术运算符
+ - * /( 除) %( 取余数)
关系运算符
> >= <= !=( 不等于) == (等于)
逻辑运算符
!(非) && (并且) || (或)
!真 : 假
!假 :真
真&& 真:真
真&& 假:假
假&& 真:假
假&& 假:假
真|| 假:真
假|| 真:真
真|| 真:真
假|| 假:假
C语言对真假的处理
非零就是真
零为假
真用1 表示
假用0 表示
&&左边的表达式为假,右边的表达式肯定不会执行
||左边的表达式为真,右边的表达式肯定不会执行
赋值运算符
= += *= /= -=
优先级
算术 > 关系 > 逻辑 > 赋值
附录:一些琐碎的运算符知识
自增 自减 三目运算符 逗号表达式
运算符补充1 _ 自增[或自减]
分类:前自增------- ++i
后自增------ i++
前自增和后自增的异同:
相同:
最终都使i 的值加1
不同:
前自增整体表达式的值是i 加1 后的值
后自增整体表达式的值是i 加1 前的值
为什么会出现自增
代码更精炼
自增的速度更快
学习自增要明白的几个问题:
- 编程时应尽量屏蔽掉前自增和后自增的差别
- 自增表达式最好不要作为一个更大的表达式的一部分来使用
或者说:i++ 和++i 单独成一个语句,不要把它作为一个完整的复合语句的一部分来使用
如:
int m = i++ + ++i + i + i++;// 这样写代码不规范,且不可移植的代码
printf("%d %d %d" , i++ , ++i, i);// 同上
例如:
include <stdio.h>
int main**(** void**)**
{
int i**;**
int j**;**
int k**;**
int m**;**
i = j = 3**;** //等价于 i = 3; j = 3;
k = i**++;**
m = ++ j**;**
printf**(** "i = %d, j = %d, k = %d, m = %d\n", i**,** j**,** k**,** m**);**
return 0**;**
}
/*
在Vc++6.0中的输出结果是:
i = 4, j = 4, k = 3, m = 4
总结:
前自增整体表达式的值是i加1之后的值
后自增整体表达式的值是i加1之前的值
*/
运算符补充2 _ 三目运算符
A ? B : C
等价于:
if(A)
B;
else
C;
例:
include <stdio.h>
int main**(** void**)**
{
int i**;**
i = ( 0**>** 2 ? 5 : 1**);**
printf**(** "%d\n", i**);**
return 0**;**
}
运算符补充3 _ 逗号表达式
A, B, C, D
功能:从左到右依次执行
最终表达式的值是最后一项的值。
例如:
include <stdio.h>
int main**(** void**)**
{
int i**;**
int j = 2**;**
i = ( j**++,** ++ j**,** j**+** 2**,** j**-** 3**);**
printf**(** "%d\n", i**);**
return 0**;**
}
//i 最终=1 ;
位运算符:
& ------- 按位于
&& 逻辑与也叫并且
&&与& 的含义完全不同
1&1=1
1&0 =0
0&1=0
0&0 =0
5&7=5 21&7=5
5&1=1 5&10=0
| ------- 按位或
||----- 逻辑或
|------ 按位或
1|0=1
1|1 =1
0|1 =1
0|0=0
~ ------- 按位取反
~i就是把i变量所有的二进制位取反
^ ------- 按位异或
相同为零
不同为1
1^0= 1
0^1 = 1
1^1=0
0^0 =0
<< ------- 按位左移
i<<3 表示把i 的所有二进制位左移3 位, 右边补零
左移n 位相当于乘以2 的n 次方, 前提是数据不能丢失
面试题:
A)i = i*8;
B)i = i<<3:
请问上述两个语句,哪个语句执行的速度快
答案:B 快
>> ------ 按位右移
i>>3 表示把i的所有二进制位右移3位,左边一般是0,当然也可能补1
右移n位相当于除以2的n次方,前提是数据不能丢失
面试题:
A)i = i/8;
B)i = i>>3;
请问上述两个语句,哪个语句执行的速度快
答案: B 快
位运算的现实意义
通过位运算符我们可以对数据的操作精确到每一位
复习进制的知识:
1.什么叫n进制
逢n进一
2.把r进制转成十进制
3.十进制转成r进制
4.不同进制所代表的数值之间的关系
十进制的3981转化化成十六进制是F8D
十进制的3981和十六进制的F8D所代表的本质上都是同一个数字
二进制全部为零的含义--0000000000000的含义
1.数值零
2.心字符串结束标记符,'\0'
3空指针NULL
NULL本质也是零,而这个零不代表数字零,而表示的是内存单元的编号零
我们计算机规定了,以零为编号的存储单元的内容不可读,不可写
4> 流程控制【重点】
- 什么是流程控制
程序代码执行的顺序
- 流程控制的分类
如何看懂一个程序,分三步:
- 整个程序的流程
- 每个语句的功能
- 试数
顺序
选择
定义 :某些代码可能执行,也可能不执行,有选择的执行某些代码
分类:
if
1.if最简单的用法
格式:
if (表达式)
语句
功能:如果表达式为真,执行语句
如果表达式为假,语句不执行
2.if范围问题
1.
If( 表达式)
语句A ;
语句B ;
解释:if 默认只能控制语句A 的执行或不执行
If 无法控制语句B 的执行与否
或讲:语句B 一定会执行
2.
If( 表达式)
{
语句A ;
语句B ;
}
此时if 可以控制A 和语句B
由此可见:if 默认只能控制一个语句的执行或不执行
如果想控制多个语句的执行或不执行
就必须把这些语句用{}扩起来
3. if..else...的用法
4. if..else if...else...的用法
格式:
if(1)
A;
else if(2)
B;
else if(3)
C;
else
D;
例:
/*
一定要明白改程序为何会编译出错
*/
include <stdio.h>
int main**(** void**)**
{
double delta = - 1**;**
if ( delta > 0**)**
printf**(** "有两个解!\n");
printf(" 哈哈 !\n");
else if ( delta == 0**)**
printf**(** "有一个唯一解!\n");
else
printf**(** "无解!\n");
return 0**;**
}
5.if举例一求分数的等级
6.if的常见问题解析
1>. 空语句的问题
if (3>2 );
等价于
if (3>2 )
;// 这是一个空语句
2>.
if (表达式)
A ;
else
B;// 以上是正确的
if (表达式1 );
A ;
else
B;// 以上是错误的
3>.
if( 表达式1)
A;
else if ( 表达式2)
B;
else if( 表达式3)
C;
else
D;
// 即便表达式1 和2 都成立,也只会执行A 语句
4>.
if( 表达式1)
A;
else if ( 表达式2)
B;
else if( 表达式3)
C;
// 这样写语法不会出错,但逻辑上有漏洞
5>.
if( 表达式1)
A;
else if ( 表达式2)
B;
else if( 表达式3)
C;
else (表达式4 )//7 行
// 这样写是不对的,正确的写法是:
要么去掉7 行的(表达式4 )
要么在7 行的else 后面加 if
6>.
if( 表达式1)
A;
else if ( 表达式2)
B;
else if( 表达式1)
C;
else (表达式4 );
D;
// 这样写语法不会出错,但逻辑上是错误的
else (表达式4 );
D;
等价于
else
(表达式4 );
D;
switch
switch(语句)
(
case 常量表达式1:语句1;
case常量表达式2:语句2;
default: 语句3;
case常量表达式3:语句4;
)
1、所有的case后面的常量表达式为便于描述我们姑且称之为标签,
这些标签都只能是
(1 )枚举常量(有些书称为枚举元素),
(2 )数值常量,
(3) 字符常量,
(4 )常变量: const int i;i=5;
(5 )宏名中的一种,注意普通变量,枚举变量是不能作为标签使用的。
2 、switch 后面括号里的"表达式"允许是任何类型
但是Vc++ 中只允许为int 或char 类型
3 、执行完一个case 语句后,流程控制就转移到下一个case 字句继续执行。
case 常量表达式只是器语句标号的作用,并不是在该处进行条件判断。在执行switch 语句时,根据switch() 中表达式的值找到与之匹配的case 子句,就从此case 字句开始执行下去,不再进行判断。
4 、switch 是选择,不是循环,如果在switch 中遇到了break 语句,该语句的功能只能退出switch 语句转去执行它下面的语句。在switch 中continue 是错误的,除非switch 本身就属于for 或while 循环的一部分。
switch(i)
{
case 1 :语句1;
case 2 :语句2;
default :语句3;
case 3 :语句4;
}
当i==1 时,从case1 处开始往下一个不落 的执行,
当i==2 时,从case2 处开始往下一个不落 的执行,
当i==3 时,只执行case3 一句,
当i==4 时,从default 处开始往下一个不落的执行,
当i==0 时,执行情况同i==4 。
//把电梯程序看懂就ok了
//break终止switch
include <stdio.h>
int main**(** void**)**
{
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 ;
}
return 0**;**
}
循环
定义:某些代码会被重复执行
分类
for
- 格式:
for(1; 2; 3)
语句A;
// 执行顺序:
先执行1-----2 成立----- 语句A------- 执行3 (标志本次循环结束)-------2 成立---------- 语句A---------- 执行3 (标志本次循环结束)-----------2 不成立循环结束
--------------------------------------3 标志循环结束、1 只执行1 次
- 执行的流程【重点】
单个for 循环的使用
多个for 循环的嵌套使用
1.
for (1; 2; 3) // 1
for(4; 5; 6) // 2
A; // 3
B; // 4
整体是两个语句,1 2 3 是第一个语句
4 是第二个语句
2.
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;
}// 整体是一个语句
- 范围问题
1+2+3+.........+100
1+1/2+1/3+......+1/100
【本程序执行流程对初学者特别重要!】
例 1
/*
1+2+3+4+....+100
*/
include <stdio.h>
int main**(** void**)**
{
int i**;**
int sum = 0**;**
for ( i**=** 1**;** i**<=** 4**;** ++ i**)**
sum = sum + i**;**
printf**(** "sum = %d\n", sum**);**
return 0**;**
}
// 上面的程序执行的过程 分析 :
1> i=1 , 1<=4 成立
sum=1,i=2
2> i=2,2<=4 成立
sum=1+2=3,i=3
3> i=3,3<=4 成立
sum=1+2+3,i=4
4> i=4,4<=4 成立
sum=1+2+3+4 , i=5
5> i=5,5<=4 不成立
执行: printf("sum = %d\n", sum);
sum=1+2+3+4=10
例 2 :
//1+1/2+1/3+........+1/100
include <stdio.h>
int main**(** void**)**
{
int i**;**
float sum = 0**;**
for ( i**=** 1**;** i**<=** 100**;** ++ i**)**
{
sum = sum + 1.0**/** i**;** //是OK的 推荐使用
//sum = sum + (float)(1/i); 这样写是不对的
//也可以这样写: sum = sum + 1 / (float)(i); 不推荐
}
printf**(** "sum = %f\n", sum**);** //float必须用%f输出
return 0**;**
}
// 程序执行的流程详细分析:
1-> i=1 1<=100 成立
sum=0+1/1.00=1 ++i i = 2
2-> i=2 2<=100 成立
sum=1+1/2.0 ++i i = 3
3-> i=3 3<=100
sum=1+1/2.0+1/3.0 ++i i=4
................
练习:
- 举例
多个for 循环嵌套使用示例_2
//例1:
include <stdio.h>
int main**(** void**)**
{
int i**,** j**;**
for ( i**=** 0**;** i**<** 3**;** ++ i**)**
printf("嘿嘿!\n");
for (j=2; j<5; ++j)
printf("哈哈!\n");
printf("嘻嘻!\n");
return 0**;**
}
/*
在Vc++6.0中的输出结果是:
嘿嘿!
嘿嘿!
嘿嘿!
哈哈!
哈哈!
哈哈!
嘻嘻! ----------------------*/
//例2:
include <stdio.h>
int main**(** void**)**
{
int i**,** j**;**
for ( i**=** 0**;** i**<** 3**;** ++ i**)**
{
printf**(** "111!\n");
for ( j**=** 2**;** j**<** 5**;** ++ j**)**
{
printf**(** "222!\n");
printf**(** "333!\n");
}
printf**(** "444!\n");
}
return 0**;**
}
/*
在Vc++6.0中的输出结果是:
111!
222!
333!
222!
333!
222!
333!
444!
111!
222!
333!
222!
333!
222!
333!
444!
111!
222!
333!
222!
333!
222!
333!
444!
*/
//例3:
# include <stdio.h>
int main(void)
{
int i, j;
for (i=0; i<3; ++i)
for (j=2; j<5; ++j)
printf(" 哈哈!\n");
printf(" 嘻嘻!\n");
return 0;
}
/*
在Vc++6.0 中的输出结果是:
----------------------
哈哈!
哈哈!
哈哈!
哈哈!
哈哈!
哈哈!
哈哈!
哈哈!
哈哈!
嘻嘻!
Press any key to continue
----------------------*/
例4 :
/*
菲波拉契序列
1 2 3 5 8 13 21 34
*/
include <stdio.h>
int main**(** void**)**
{
int n**;**
int f1**,** f2**,** f3**;**
int i**;**
f1 = 1**;**
f2 = 2**;**
printf**(** "请输入您需要求的想的序列: ");
scanf**(** "%d", & n**);**
if ( 1 == n**)**
{
f3 = 1**;**
}
else if ( 2 == n**)**
{
f3 = 2**;**
}
else
{
for ( i**=** 3**;** i**<=** n**;** ++ i**)**
{
f3 = f1 + f2**;**
f1 = f2**;**
f2 = f3**;**
}
}
printf**(** "%d\n", f3**);**
return 0**;**
} //菲波拉契序列程序流程分析-实数:
- i=3 3<=6 成立
f3 = 1+2=3 f1=f2=2 f2=f3=3 ++i i=4
- i=4 4<=6 成立
f3=2+3=5 f1=3 f2=5 i=5
- i=5 5<=6 成立
f3=3+5=8 f1=5 f2=8 i=6
- i=6 6<=6 成立
f3=5+8=13 f1=8 f2=13 i=7
- i=7 7<=6 不成立
while
1 、执行顺序
格式:
while(表达式)
语句;
2 、与for 的相互比较
for 和while 可以相互转换
for (1; 2; 3 )
A;
等价于
1;
While(2)
{
A;
3;
}
while 和for 可以相互转化
但for 的逻辑性更强,更不容易出错,推荐多使用for
3 、举例
从键盘输入一个数字,如果该数字是回文数
则返回yes ,否则返回no
回文数:正着写和倒着写都一样
比如:121 、12321 都是回文数
例:求键盘输入随机数判断是否是回文数
include <stdio.h>
int main**(** void**)**
{
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");
return 0**;**
}
// 分析: 1> m=1234 成立
sum = 0*10 + 1234%10;
m = m/10 = 123;
2> m=123 成立
sum = 4*10+123%10=43
m=123/10=12
3> m=12 成立
sum=43*10+12%10=432
4> m=1
sum = 432*10+1%10=4321
m=1/10=0
5> m=0 不成立
最终sum=4321
4 、什么时候使用while ,什么时候使用for 没法说,用多了就知道了
do... while
格式
do{
......
}while( 表达式);
do...while. 并不等价于for ,当然也不等价于while
主要用于人机交互
例:一元二次方程 ax^2+bx+c=0
include <stdio.h>
include <math.h>
int main**(** void**)**
{
double a**,** b**,** c**;**
double delta**;**
double x1**,** x2**;**
char ch**;**
do
{
printf**(** "请输入一元二次方程的三个系数:\n");
printf**(** "a = ");
scanf**(** "%lf", & a**);**
printf**(** "b = ");
scanf**(** "%lf", & b**);**
printf**(** "c = ");
scanf**(** "%lf", & c**);**
delta = b***** b - 4***** a***** c**;**
if ( delta > 0**)**
{
x1 = (- b + sqrt**(** delta**))** / ( 2***** a**);**
x2 = (- b - sqrt**(** delta**))** / ( 2***** a**);**
printf**(** "有两个解,x1 = %lf, x2 = %lf\n", x1**,** x2**);**
}
else if ( 0 == delta**)**
{
x1 = x2 = (- b**)** / ( 2***** a**);**
printf**(** "有唯一解,x1 = x2 = %lf\n", x1**,** x2**);**
}
else
{
printf**(** "无实数解!\n");
}
printf**(** "您想继续么(Y/N): ");
scanf**(** " %c", & ch**);** //%c前面必须得加一个空格 原因略 -空白符
} while ( 'y'== ch || 'Y'== ch**);**
return 0**;**
}
//Alt+F8 自动对齐代码
break和continue
break
break 如果用于循环,则用来终止循环
break 如果用于switch ,则是用于终止switch
break 不能直接用于if ,除非if 属于循环内部的一个子句
例如:
for(i=0; i<3; ++i)
{
if(3>2)
break;//break 虽然是if 内部的语句
// 但break 终止的确是外部的for 循环
printf(" 嘿嘿!\n"); // 永远不会输出
}
在多层循环中,break 只能终止最里面包裹它的那个循环
例:
for(i=0; i<3; ++1)
{
for(j=1; j<4; ++j)
break ;//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
}
b=100;
break;// 终止的是第一个switch
continue
用于跳过本次循环余下的语句,
转去判断是否需要执行下次循环
例如:
- for (1 ; 2 ; 3 )
{
A;
B;
continue;// 如果执行该语句,则执行完语句后,会跳转到语句3 执行,C 和D 会被跳过去 ,C 和D 不会被执行
C ;
D ;
}
2 、
while( 表达式)
{
A;
B;
continue;// 如果执行该语句,则执行完该语句后,
// 会执行while 后面的 表达式 ,C 和D 都会被跳过去,C 和D 不会被执行
C;
D;
}
5>数组
为什么需要数组:
为了解决大量同类型数据的存储和使用问题
为了解决现实世界
数组的分类
一维数组
怎么定义一维数组
n 个变量连续分配空间
所有的变量数据类型必须相同
所有变量所占的字节大小必须相同
举例:
int a[5]={1, 2, 3, 4, 5};
一维数组名代表数组中所有的元素,
一维数组代表数组第一个元素的地址
a 是数组的名字,5 表示数组元素的个数,并且这5
个元素分别用a[0] 、a[1] 、a[2] 、a[3] 、a[4]
有关一维数组的操作
初始化
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} ;// 错误,5 表示下标,a[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];
赋值
#include <stdio.h>
int main**(** void**)**
{
int a**[** 5**];**
int i**;**
scanf**(** "%d", & a**[** 0**]);**
printf**(** "a[0] = %d\n", a**[** 0**]);**
scanf**(** "%d", & a**[** 3**]);**
printf**(** "a[3] = %d\n", a**[** 3**]);**
for ( i**=** 0**;** i**<** 5**;** ++ i**)**
printf**(** "%d ", a**[** i**]);**
return 0**;**
}
排序
求最大/最小值
倒置
例:
include <stdio.h>
int main**(** void**)**
{
int a**[** 8**]** = { 1**,** 2**,** 3**,** 4**,** 5**,** 6**,** 7**,** 8**};**
int i**,** j**;**
int t**;**
i = 0**;**
j = 7**;**
while ( i < j**)**
{
t = a**[** i**];**
a**[** i**]** = a**[** j**];**
a**[** j**]** = t**;**
i**++;**
-- j**;**
}
for ( i**=** 0**;** i**<** 8**;** ++ i**)**
printf**(** "%d\n", a**[** i**]);**
return 0**;**
}
查找
插入
删除
二维数组
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]
初始化
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}
};
int a[][5] = {
{0, 1, 2, 3, 4 },
{0, 1, 2, 3, 4 },
}
- 列表是必须给出的,行数可以由编译器来数
- 每行一个{}, 逗号分隔
- 最后的逗号可以存在,有古老的传统
- 如果省略,表示补零
- 也可以用定位(C99 ONLY )
操作
输出二维数组内容:
include <stdio.h>
int main**(** void**)**
{
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**(** "%-5d ", a**[** i**][** j**]);**
//-5 表示空 5 格位置, - 表示左对齐, -5d 后面空 2 格代表再空 2 格
printf**(** "\n");
}
return 0**;**
}
对二维数组排序
求每一行的最大值
判断矩阵是否对称
矩阵的相乘
多维数组
是否存在多维数组
不存在
因为内存是线性一维的
n 维数组可以当做每个元素是n-1 维数组的一维数组
例如:
int a[3][4];
该数组是含有3个元素的一维数组
只不过每个元素都可以再分成4个小元素
int a[3][4][5];
该数组是含有3个元素的一维数组
只不过每个元素都是4行5列的二维数组
6> 函数(重点)
为什么需要函数
避免了重复性操作
有利于程序的模块化
什么叫函数
逻辑上: 能够完成特定功能的独立的代码块
物理上:
能够接收数据[当然也可以不接受数据]能够对接受的数据进行处理
总结:
能够将数据处理的结果返回【当然也可以不返回任何值]
函数是个工具,它是为了解决大量类似问题而设计的
函数可以当做一个黑匣子
如何定义函数
函数的返回值 函数的名字(函数的形参列表)
{
函数的执行体
}
1.函数定义的本质是详细描述函数之所以能够实现某个特定功能的具体方法
2.return 表达式;的含义:
1>终止被调函数,向主调函数返回表达式的值
2>如果表达式为空,则只终止函数,不向主调函数返回任何值
3>break是用来终止循环和switch的,return是用来终止函数的例子:
void f()
{
return;//return只用来终止函数,不向主调函数返回任何值
}
int fO
{
return 10;//第一:终止函数,第二:向主调函数返回10
}
3.函数返回值的类型也称为函数的类型,因为如果函数名前的返回值类型和函数执行体中的 return 表达式:中表达式的类型不同的话,则最终函数返回值的类型 以函数名前的返回值类型为准
例子:
int f()
{
return 10.5://因为函数的返回值类型是int
}
函数的分类
有参函数 和 无参函数
有返回值函数 和 无返回值函数
库函数 和 用户自定函数
值传递函数 和 地址传递函数
普通函数 和 主函数(main函数)
一个程序必须有且只能有一个主函数
主函数可以调用普通函数 普通函数不能调用主函数
普通函数可以相互调用
主函数是程序的入口,也是程序的出口
注意的问题
函数调用和函数定义的顺序
如果函数调用写在了函数定义的前面,则必须加函数前置声明
函数前置声明:
1.告诉编译器即将可能出现的若干个字母代表的是一个函数2.告诉编译器即将可能出现的若干个字母所代表的函数的形参和返回值的具体情况
3.函数声明是一个语句,末尾必须加分号
4.对库函数的声明是通过#include<库函数所在的文件的名字,h>来实现的形参和实参
个数相同 位置-一对应 数据类型必须相互兼容
如何在软件开发中合理的设计函数来解决实际问题
一个函数的功能尽量独立,单一
多学习, 多模仿牛人的代码
函数是C语言的基本单位,类是Java,C#,C的基本单位常用的系统函数
double sqrt(double x):
求的z的平方根
int abs(int x)
求x的绝对值
double fabs(double x)
求x的绝对值
专题:
递归
可以参见我的数据结构视频
标准库stdio.h :
7>变量的作用域和存储方式
按作用域分:
全局变量
在所有函数外部定义的变量叫全局变量
全局变量使用范围: 从定义位置开始到整个程序结束
局部变量
在一个函数内部定义的变量或者函数的形参都统称为局部变量
void f(int i)
{
int j= 20;
}// i和j都属于局部变量局部变量使用范围:只能在本函数内部使用
注意的问题:
全局变量和局部变量命名冲突的问题
在一个函数内部如果定义的局部变量的名字和全局变量名
一样时,局部变量会屏蔽掉全局变量
按变量的存储方式
静态变量
自动变量
寄存器变量:cpu 内部可以存储数据的设备
8> 指针 ( 重点 )
指针的重要性
表示一些复杂的数据结构
快递的传递数据,减少了内存的耗用【重点】
使函数返回一个以上的指【重点】
能直接访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
总结:指针是C 语言的灵魂
指针的定义
地址
内存单元的编号
从零开始的非负整数
范围:4G 【0-4G-1 】
指针
1 、指针就是地址,地址就是指针
2 、指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址
的变量
3 、指针和指针变量是两个不同的概念
4 、但是要注意:通常我们叙述时会把指针变量简称指针,实际上它们含义
并不一样
5 、指针的本质就是一个操作受限的非负整数
2 个地址可以相减,但是不能进行除,加、乘
* 也叫取值运算符
指针的分类
1 、基本类型指针【重点】
Int * p; // p是变量的名字,int * 表示变量存放的是int类型变量的地址
//int * p; 不表示定义了一个名字为*p的变量
//int *p; 应该这样理解:p是变量名,p变量的数据类型是int*类型//int *p;
//所谓int*类型实际就是存放int变量地址的类型
int i = 3 ;
int j ;
p =&i ;
/*
1 、p 保存了i 的地址,因此p 指向i
2 、p 不是i ,i 也不是p ,更准确的说: 修改p 的值不影响i 的值,修改i 的值也不
会影响p 的值
3 、如果一个指针变量指向了某个普通变量,则
* 指针变量 就完全等同于 普通变量
例子:
如果p 是个指针变量,并且p 存放了普通变量i 的地址
则p 指向了普通变量i
*p 就完全等同于 i
或者说:
在所有出现* 的地方都可以替换成i
在所有出现i 的地方都可以替换成*p
*p 最准确的解释是: *p 表示的是以p 的内容为地址的变量
*/
j = *p ; // 等价于 j = I ;
printf( " i = %d , j=%d\n " , i , j ) ;
附注:
* 的含义
1. 乘法
2. 定义指针变量
int * p;
// 定义了一个名字叫p 的变量,int * 表示p 只能存放int 变量的地址
3. 指针运算符 ( 也叫取值运算符)
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量
则* p表示以p的内容为地址的变量
如何通过被调函数修改主调函数普通变量的值
1. 实参必须为该普通变量的地址
2. 形参必须为指针变量
3. 在被调函数中通过
* 形参名=........
的方式就可以修改主调函数相关变量的值
指针和数组
指针和一维数组
一维数组名
一维数组名是个指针常量
它存放的是一维数组第一个元素的地址
下标和指针的关系
如果p 是个指针变量,则
p[ i ] 永远等价于 * ( p+i )
确定一个一维数组需要几个参数【如果一个函数要处理一个一维数组,则需要接收该数组的哪些信息】
需要两个参数:
数组第一个元素的地址
数组的长度
指针变量的运算
指针变量不能相加 不能相乘 也不能相除
如果两个指针变量指向的是同一块连续空间中的不同存储单元,
则这两个指针变量才可以相减
一个指针变量到底占几个字节【非重点】
预备知识:
sizeof( 数据类型)
功能: 返回值就是该数据类型所占的字节数
例子: sizeof( int ) = 4 sizeof( char ) = 1
sizeof ( double )=8
sizeof ( 变量名 )
功能 : 返回值是该变量所占的字节数
假设p 指向char 类型变量(1 个字节)
假设q 指向int 类型变量(4 个字节)
假设r 指向double 类型变量(8 个字节)
请问: p q r 本身所占的字节数是否一样
答案: p q r 本身所占的字节数是一样的
总结:
一个指针变量,无论它指向的变量占几个字节
该指针变量本身只占四( 八) 个字节(不同编译器不同)
一个变量的地址是用该变量首字节的地址来表示
指针和二维数组 - 难点
3.指针和函数
4.指针和结构体
5.多级指针
示例:
int i = 10 ;
int * p = &i ; // p只能存放int类型变量的地址
int ** q = &p ; // q是int **类型,所谓int **类型就是指q只能存放int *类型变量的地址,
int *** r = &q ; // r是int ***类型,所谓int ***类型就是指r只能存放int ** 类型变量的地址,
//r = &p ; //error 因为r是int *** 类型,r只能存放int**类型变量的地址printf(" i=%d\n ",***r) ; //输出结果是10
只有***r才表示的是i,*r或**r或****r代表的都不是i
专题:
动态内存分配【重点难点】
传统数组的缺点:
1.数组长度必须事先制定,且只能是常整数,不能是变量
例子:
int a [ 5 ] ; //OK
int len = 5 ; int a[ len ]; //error
2、传统形式定义的数组,该数组的内存程序员无法手动释放
在一个函数运行期间,系统为该函数中数组所分配的空间
会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放
3、数组的长度一旦定义,其长度就不能在更改
数组的长度不能在函数运行的过程中动态的扩充或缩小
4、A函数定义的数组,在A函数运行期间可以被其它函数使用,
但A函数运行完毕之后,A函数中的数组将无法在被其他函数使用,空间已经释放传统方式定义的数组不能跨函数使用
为什么需要动态分配内存
动态数组很好的解决了传统数组的这4个缺陷
传统数组也叫静态数组
动态内存分配举例 动态数组的构造
假设动态构造一个int型一维数组[至少保证要看懂]
int *p = ( int *) malloc ( int len );
1、本语句分配了两块内存,一块内存是动态分配的,总共len个字节,另一块是静态分配的,并且这块静态内存是p变量本身所占的内存,总共4个字节
malloc只有一个int型的形参,表示要求系统分配的字节数、
2、 malloc函数的功能是请求系统1en个字节的内存空间,如果请求分配成功,则返回第一个字节的地址,如果分配不成功,则返回NULL
malloc函数能且只能返回第一个字节的地址,所以我们需要把这个无任何实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,因此
ma11oc前面必须加(数据类型*),表示把这个无实际意义的第一个字节的地址转化为相应类型的地址。如:
int *p = ( int * ) malloc ( 50 ) ;
表示将系统分配好的50个字节的第一个字节的地址转化为int*型的地址,更准确的说是把第一个字节的地址转化为四个字节的地址,这样p就指向了第一个的四个字节,p+1就指向了第2个的四个字节,p+1就指向了第i+1个的4个字节。p[0]就是第一个元素,p[i]就是第i+1个元素
double *p = ( double *) malloc ( 80 ) ;
表示将系统分配好的80个字节的第一个字节的地址转化为double*型
的
地址,更准确的说是把第一个字节的地址转化为8个字节的地址,这样p就指向了第一个的8个字节,p+1就指向了第2个的8个字节,p+1就指向了第1+1个的8个字节。p[0]就是第一个元素,p[i]就是第i+1个元素
3 、freep(p);
表示把p 所指向的内存给释放掉 p 本身的内存是静态的,
不能由程序员手动释放,p 本身的内存只能在p 变量所在的
函数运行终止时由系统自动释放
静态内存和动态内存的比较【重点】
静态内存是由系统自动分配,由系统自动释放
静态内存是在栈分配的
动态内存是由程序员手动分配,手动释放
动态内存是在堆分配的
跨函数使用内存的问题【重点】
静态内存不可以跨函数使用
所谓静态内存不可以跨函数使用更准确的说法是:
静态内存在函数执行期间可以被其它函数使用,
静态内存在函数执行完毕之后就不能再被其他函数使用了
动态内存可以跨函数使用
动态内存在函数执行完毕之后仍然可以被其他函数使用
//指针热身程序1
include <stdio.h>
int main**(** void**)**
{
int * p**;** //p是变量的名字, int * 表示p变量存放的是int类型变量的地址
int i = 3**;**
p = & i**;** //OK
//p = i; //error,因为类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值
//p = 55; //error 原因同上
return 0**;**
}
//指针热身程序2
include <stdio.h>
int main**(** void**)**
{
int * p**;** //p是变量的名字, int * 表示p变量存放的是int类型变量的地址
//int * p; 不表示定义了一个名字叫做 *p 的变量
// int * p; 应该这样理解: p是变量名, p变量的数据类型是 int * 类型
// 所谓int * 类型 实际就是存放 int 变量地址的类型
int i = 3**;**
int j**;**
p = & i**;**
/*
-
p保存了i的地址, 因此p指向i
-
p不是i,i也不是p,更准确的说: 修改p的值不影响i的值,修改i的值也不会影响p的值
-
如果一个指针变量指向了某个普通变量, 则
*指针变量 就完全等同于 普通变量
例子:
如果p是个指针变量,并且p存放了普通变量i的地址
则p指向了普通变量i
*p 就完全等同于 i
或者说: 在所有出现*p的地方都可以替换成i
在所有出现i的地方都可以替换成*p
*p 就是以p的内容为地址的变量
*/
j = * p**;** //等价于 j = i;
printf**(** "i = %d, j = %d\n", i**,** j**);**
return 0**;**
}
// 指针错误例 2 :
include <stdio.h>
int main**(** void**)**
{
int i = 5**;**
int * p**;**
int * q**;**
p = & i**;**
//*q = p; //error 语法编译会出错
//*q = *p; //error
p = q**;** //q是垃圾值,q赋给p, p也变成垃圾值
printf**(** "%d\n", * q**);** //13行
/*
q的空间是属于本程序的,所以本程序可以读写q的内容,
但是如果q内部是垃圾值,则本程序不能读写*q的内容
因为此时*q所代表的内存单元的控制权限并没有分配给本程序
所以本程序运行到13行时就会立即出错
*/
return 0**;**
}
//经典指针程序3-互换两个数字
include <stdio.h>
void huhuan_1**(** int , int**);**
void huhuan_2**(** int *, int *);
void huhuan_3**(** int *, int *);
int main**(** void**)**
{
int a = 3**;**
int b = 5**;**
huhuan_3**(&** a**,** & b**);** //huhuan_2(*p, *q); 是错误的, huhuan_2(a, b);也是错误的
printf**(** "a = %d, b = %d\n", a**,** b**);**
return 0**;**
}
//不能完成互换功能
void huhuan_1**(** int a**,** int b**)**
{
int t**;**
t = a**;**
a = b**;**
b = t**;**
printf("a = %d,b = %d\n", a, b);//子函数内可互换a和b的值
}
//不能完成互换功能
void huhuan_2**(** int * p**,** int * q**)**
{
int * t**;** //如果要互换p和q的值,则t必须是int *,不能是int,否则会出错
t = p**;**
p = q**;**
q = t**;**
}
//可以完成互换功能
void huhuan_3**(** int * p**,** int * q**)**
{
int t**;** //如果要互换*p和*q的值, 则t必须定义成int,不能定义成int *, 否则语法出错
t = * p**;** //p是int *, *p是int
* p = * q**;**
* q = t**;**
}
//经典指针程序4-指针使函数返回一个以上的值举例_1
include <stdio.h>
int f**(** int i**,** int j**)**
{
return 100**;**
// return 88;
}
void g**(** int * p**,** int * q**)**
{
* p = 1**;**
* q = 2**;**
}
int main**(** void**)**
{
int a = 3**,** b = 5**;**
g**(&** a**,** & b**);**
printf**(** "%d %d\n", a**,** b**);**
return 0**;**
}
//数组_1
include <stdio.h>
int main**(** void**)**
{
int a**[** 5**];** //a是数组名 5是数组元素的个数 元素就是变量 a[0] -- a[4]
// int a[3][4]; //3行4列 a[0][0]是第一个元素 a[i][j]第i+1行j+1列
int b**[** 5**];**
//a = b;//error a是常量
printf**(** "%#X\n", & a**[** 0**]);**
printf**(** "%#X\n", a**);**
return 0**;**
}
/*
在Vc++6.0中的输出结果是:
0X12FF6C
0X12FF6C
Press any key to continue
总结:
一维数组名
一维数组名是个指针常量
它存放的是一维数组第一个元素的地址
*/
//确定一个一维数组需要几个参数_1: 数组首地址 和长度
include <stdio.h>
//f函数可以输出任何一个一维数组的内容
void f**(** int * pArr**,** int len**)**
{
int i**;**
for ( i**=** 0**;** i**<** len**;** ++ i**)**
printf**(** "%d ", *( pArr**+** i**)** ); //*pArr *(pArr+1) *(pArr+2)
printf**(** "\n");
}
int main**(** void**)**
{
int a**[** 5**]** = { 1**,** 2**,** 3**,** 4**,** 5**};**
int b**[** 6**]** = {- 1**,-** 2**,-** 3**,** 4**,** 5**,-** 6**};**
int c**[** 100**]** = { 1**,** 99**,** 22**,** 33**};**
f**(** a**,** 5**);** //a是 int *
f**(** b**,** 6**);**
f**(** c**,** 100**);**
return 0**;**
}
//确定一个数组需要几个参数_3
include <stdio.h>
void f**(** int * pArr**,** int len**)**
{
int i**;**
for ( i**=** 0**;** i**<** len**;** ++ i**)**
printf**(** "%d ", pArr**[** i**]);** //*(pArr+i) 等价于 pArr[i] 也等价于 b[i] 也等价于 *(b+i)
printf**(** "\n");
}
int main**(** void**)**
{
int b**[** 6**]** = {- 1**,-** 2**,-** 3**,** 4**,** 5**,-** 6**};**
f**(** b**,** 6**);**
b**[** i**]**
return 0**;**
}
//确定一个一维数组需要几个参数_2
// a[3] 、 pArr[3] 、 *(pArr + 3)、*(a + 3) 都指的第四个元素
a = pArr
*(pArr + 3) = pArr + 3
/*
2009年11月14日10:45:51
一定要明白 10行的pArr[3] 和17行 19行的a[3] 是同一个变量
*/
include <stdio.h>
void f**(** int * pArr**,** int len**)**
{
pArr**[** 3**]** = 88**;** //10行
}
int main**(** void**)**
{
int a**[** 6**]** = { 1**,** 2**,** 3**,** 4**,** 5**,** 6**};**
printf**(** "%d\n", a**[** 3**]);** //17行
f**(** a**,** 6**);**
printf**(** "%d\n", a**[** 3**]);** // 19行
return 0**;**
}
/* 在Vc++6.0中的输出结果是:
4
88
Press any key to continue
*/
//指针变量的运算_!.cpp
include <stdio.h>
int main**(** void**)**
{
int i = 5**;**
int j = 10**;**
int * p = & i**;**
int * q = & j**;**
int a**[** 5**];**
p = & a**[** 1**];**
q = & a**[** 4**];**
printf**(** "p和q所指向的单元相隔%d个单元\n", q**-** p**);**
//p - q 没有实际意义
return 0**;**
}
include <stdio.h>
int main**(** void**)**
{
char ch = 'A';
int i = 99**;**
double x = 66.6**;**
char * p = & ch**;**
int * q = & i**;**
double * r = & x**;**
printf**(** "%d %d %d\n", sizeof ( p**),** sizeof ( q**),** sizeof ( r**));**
return 0**;**
}
//malloc的使用_1.cpp
/*
2009年11月17日10:21:31
malloc 是 memory(内存) allocate(分配)的缩写
*/
include <stdio.h>
include <malloc.h> //不能省
int main**(** void**)**
{
int i = 5**;** //分配了4个字节 静态分配 11 行
int * p = ( int *) malloc**(** 4**);** //12行
/*
-
要使用malloc函数,必须添加malloc.h这个头文件
-
malloc函数只有一个形参,并且形参是整型
-
4表示请求系统为本程序分配4个字节
-
malloc函数只能返回第一个字节的地址
-
12行分配了8个字节, p变量占4个字节, p所指向的内存也占4个字节
-
p本身所占的内存是静态分配的, p所指向的内存是动态分配的
*/
* p = 5**;** //*p 代表的就是一个int变量, 只不过*p这个整型变量的内存分配方式和11行的i变量的分配方式不同
free**(** p**);** //freep(p)表示把p所指向的内存给释放掉 p本身的内存是静态的,不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放
printf**(** "同志们好!\n");
return 0**;**
}
//malloc用法_2.cpp
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**);** //10
f**(** p**);** //p是int *类型
printf**(** "%d\n", * p**);** //200 第20行
return 0**;**
}
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**);** //第12行 本行动态的构造了一个一维数组, 该一维数组的产度是len, 该数组的数组名是pArr, 该数组的每个元素是int类型 类似于 int pArr[len];
//对一维数组进行操作, 如:对动态一维数组进行赋值
for ( i**=** 0**;** i**<** len**;** ++ i**)**
scanf**(** "%d", & pArr**[** i**]);**
//对位一维数组进行输出
printf**(** "一维数组的内容是:\n");
for ( i**=** 0**;** i**<** len**;** ++ i**)**
printf**(** "%d\n", pArr**[** i**]);**
free**(** pArr**);** //释放掉动态分配的数组
return 0**;**
}
include <stdio.h>
int main**(** void**)**
{
int i = 10**;**
int * p = & i**;** //p只能存放int类型变量的地址
int ** q = & p**;** //q是int **类型, 所谓int **类型就是指q只能存放int *类型变量的地址,
int *** r = & q**;** //r是int ***类型, 所谓int ***类型就是指r只能存放int ** 类型变量的地址,
//r = &p; //error 因为r是int *** 类型,r只能存放int **类型变量的地址
printf**(** "i = %d\n", *** r**);** //输出结果是10 只有 ***r才表示的是i, *r或 **r或 ****r代表的都不是i
return 0**;**
}
//多级指针_2.cpp
void f**(** int ** q**)**
{
//*q就是p
}
void g**()**
{
int i = 10**;**
int * p = & i**;** 类型
f**(&** p**);** //p是int *类型 , &p是int ** 类型
}
int main**(** void**)**
{
g**();**
return 0**;**
}
9>结构体
为什么需要结构体
为了表示一些复杂的事物,而普通的基本类型无法满足实际要求
什么叫结构体
把一些基本类型数据组合一起形成的一个新的复合数据类型,这个叫做结构体
如何定义结构体
3种方式,推荐使用第一种:
// 第一种 这只是定义了一个新的数据类型,并没有定义变量
struct Student
{
int age;
float score;
char sex;
};
//第二种方式
struct Student2
{
int age;
float score;
char sex;
}st2;
//第三种方式
struct
{
int age;
float score;
char sex;
}st3;
怎样使用结构体变量
赋值和初始化
定义的同时可以整体赋初值
如果定义完之后,则只能单个的赋初值
如何取出结构体变量中的每一个成员【重点】
1.结构体变量名.成员名
2指针变量名->成员名(第二种方式更常用)
指针变量名->成员名在计算机内部会被转化成(*指针变量名).成员名
的方式来执行
所以说这两种方式是等价的
例子:
struct Student
{
int age;
float score;
char sex;
};
int main(void)
{
struct Student st={80,66.6,'F'}://初始化定义的同时赋初值
struct Student * pst = &st; //&st不能改成st
pst->age=88; //第二种方式
st.age =10; //第一种方式
return 0;
}
1 、pst->age 在计算机内部会被转化成(*pst).age ,
这就是-> 的含义,这也是一种硬性规定
2 、所以 pst->age 等价于(*pst).age 也等价于 st. age
3 、我们之所以知道pst->age 等价于 st.age, 是因为pst->age 是被转化成了(*pst).age 来执行
4 、pst->age 的含义:
pst 所指向的那个结构体变量中的age 这个成员
结构体变量和结构体指针变量作为函数参数传递的问题
推荐使用结构体指针变量作为函数参数来传递
结构体变量的运算
结构体变量不能相加,不能想减,也不能相互乘除
但结构体变量可以相互赋值
例子:
struct Student
{
int age;
char sex;
char name[100]:
};//分号不能省
struct Student stl, st2;
stl + st2 st1*st2 st1/st2都是错误的
stl = st2或者st2=st1ff都是正确的
举例
动态构造存放学生信息的结构体数组
动态构造一个数组,存放学生的信息
然后按分数排序输出
include <stdio.h>
//第一种方式
struct Student
{
int age**;**
float score**;**
char sex**;**
};
int main**(** void**)**
{
struct Student st = { 80**,** 66.6F**,** 'F'}; //初始化 定义的同时赋初值
struct Student * pst = & st**;** //&st不能改成st
pst**->** age = 88**;** //第二种方式
st**.** score = 66.6f**;** //第一种方式 66.6在C语言中默认是double类型,如果希望一个实数是float类型,则必须在末尾加f或F, 因此66.6是double, 66.6f或66.6F是float
printf**(** "%d %f\n", st**.** age**,** pst**->** score**);**
return 0**;**
}
10>枚举
什么是枚举
把一个事物所有可能的取值一一列举出来
怎样使用枚举
枚举的优缺点
代码更安全
书写麻烦
11>专题
10.1>字符串处理
10.2>进制转换
10.3>补码
10.4> 动态内存分配(重点)
10.5>综合应用:链表的使用
12>杂记---穿插在课堂中的零散知识笔记
算法:
解题的方法和步骤
如何看懂一个程序,分三步:
1/流程
2/每个语句的功能
3/试数
如何学习一些需要算法的程序【如何掌握一个程序】
1. 尝试自己去编程解决它
但要意识到大部分人都是自己无法解决的,这时不要气馁,也不要自卑
如果十五分钟还想不出来, 此时我建议您就可以看答案了如果解决不了, 就看答案
2. 关键是把答案看懂, 这个要花很大的精力,也是我们学习的重点看懂一个程序要分三步: 流程、每个语句的功能、试数
3. 看懂之后尝试自己去修改程序,并且知道修改之后程序的输出结果的含义不建议看懂程序之后就立即自己敲程序
4. 照着答案去敲
5. 调试错误
6. 不看答案,自己独立把答案敲出来
7. 如果程序实在无法彻底理解,就把它背会,不过无法彻底理解的程序非常少,我自己只有在学数据结构时碰到过一个,学其他语言都没有碰到过
强制类型转化
格式:
(数据类型)(表达式)
功能:
把表达式的值强制转化为前面所执行的数据类型例子:
(int)(4.5+2.2) 最终值是6
(float)(5) 最终值是 5.000000
浮点数的存错所带来的问题
float和double都不能保证可以把所有的实数都准确的保存在计算机中
例:
float i=99.9;
printf( %f\n",i);
最终在Vc+46.0中的输出结果是:99.900002
因为浮点数无法准确存储,所以就衍生出来两个编程问题:
有一个浮点型变量x,如何判断x的值是否是零
if(x-0.000001<=0.000001)
是零
else
不是零
为什么循环中更新的变量不能定义成浮点型
The end
2024-05