初心:在一个人摸索下十分痛苦,不知道哪里开始复习,真题以及视频都没有,我希望在自己摸索完之后,能把宝贵的经验分享给需要的人,也希望各位以后也可以帮助更多的人,接下来让我们一起开始学习C语言和数据结构吧!!!
座右铭:“莫问收获,但问耕耘”
目录
如何看懂一个程序:
基本数据类型
什么是变量:
变量为什么必须初始化:
1、先来看看未初始化的代码:
如何定义变量:
什么是进制:
常量在c语言中如何表示:
字符char:
常量以什么样的二进制代码存储在计算机中:(了解即可)
什么是字节:
char使用常见问题解析:
什么是ASCII:(具体的表可以在csdn搜索)
基本的输出(printf)的用法:
常用输出控制符包含如下:
为什么需要输出控制符:
用简单的scanf函数输入数据:
1、scanf函数的一般形式:
2、scanf的第一种用法:
3、scanf的第二种用法:
4、如何使用scanf编写出高质量的代码:
运算符基本概念(考试重点):
1、c的运算符号:有以下几类:
C语言运算符优先级:
除法(/)运算符细节:
取余(%)运算符的细节:
逻辑运算符的细节:
1、!(非)代码例子:
2、&&(并且)代码例子:
3、&&(并且)的陷阱例子:
什么是表达式:
自增自减++、- -:
逗号运算符:
1、逗号表达式:
2、未使用表达式的值--------代码分析:
3、使用表达式的值--------代码分析:
三目运算符:
流程控制(是我们学习C语言的第一个重点):
1、什么是流程控制:
2、流程控制的分类:
选择结构:
第一种形式if:
1、if最简单的用法:
2、if的范围问题(重点):
3、if----else的用法:
4、if----else if----else 的用法:
5、C语言对真假的处理:
7、if的常见问题解析 :
选择结构switch:
1、格式:
2、switch用法简介:
break的用法【重点】:
1、break不能直接用于if:
2、break不能直接用于if,除非if属于循环内部的一个子句:
3、在多层switch循环中,break只能终止距离它最近的switch!:
continue的用法:
循环结构:
循环结构---for循环:
1、格式:
2、for循环执行流程:
3、for代码简单例子------计算1到4的总和:
4、for循环计算1-10之间的奇数例子:
5、1到10之间能被3整除的数的和的例子:
6、郝斌老师的课间作业:
7、for循环强制类型转化:
8、1+1/2+1/3+...+1/100的程序:
浮点数存储所带来的问题:
多层嵌套for的使用:
进制:
while循环:
1、执行流程:
2、与for的相互比较:
3、while举例
4、例子(判断回文数):
5、菲波拉契序列例子:
6、什么时候时候while,什么时候使用for
do...while循环:
1、执行流程:
2、do...while和while的区别:
3、do...while代码例子:
4、一元二次方程用do...while求解程序代码:
流程控制总测试:
数组:
1、为什么需要数组:
2、数组的简单使用:
一维数组:
1、怎么定义一维数组:
2、有关一维数组的操作:
二维数组:
1、初始化:
2、输出二维数组内容:
多维数组:
函数:
1、为什么需要函数:
2、什么叫函数:
3、如何定义函数:
4、函数的分类:
5、函数举例_判断一个数组是否是素数(不用调用函数的例子):
6、注意的问题:
7、形参和实参:
8、如何在软件开发中合理的设计函数来解决问题:
9、常用的系统函数:
10、递归:
11丶函数复习:
12、变量的作用域和存储方式:
指针【C语言最重点】:
1、指针基础:
2、个人csdn补习指针(题外):
3、指针的重要性:
4、指针的定义:
5、指针和指针变量的区别:
6、指针常见错误解析:
7、指针经典程序_互换两个数字
3、可以完成互换功能:
8、指针星号的三种含义:
9、复习前面所有指针知识 实参和形参永远是不同 :
10:如何通过被调函数修改主调函数普通变量的值:
11、指针和数组:
12、指针和一维数组:
13、指针和下标的关系:
14、确定一个一维数组需要几个参数:
15、复习上节课---确定一个一维数组需要几个参数:
16、指针变量的运算:
17、指针_何谓变量的地址 一个指针变量到底占几个字节:
18、动态内存分配【重点】:
19、为什么需要动态分配内存:
20、malloc函数的理解:
21、malloc函数使用的简单介绍:
22、malloc函数的用法:
23、动态内存分配举例_动态数组的构造:
24、动态内存和静态内存的比较【重点】 :
25、多级指针:
26、指针-静态变量不能跨函数使用详解【重点】:
27、指针-动态变量可以跨函数使用详解【重点】:
结构体:
1、为什么需要结构体,什么叫结构体【重点】:
2、如何定义结构体:
3、怎样使用结构体变量概述:
4、结构体的赋值和初始化:
5、如何取出结构体变量中的每一个成员【重点】:
6、复习前面的知识总结:
7、通过函数完成对结构体的输入和输出:
8、应该发送内容还是发送地址【重点】:
9、结构体变量的运算:
10、冒泡排序:
11、结构体_综合应用_学生管理系统(存储,排序,输出):
枚举:
1、怎么使用枚举:
2、枚举的应用:
进制转化复习:
数据表示(原码、反码、补码、移码):
链表:
链表代码(暂时不会,需要理解复习):
位运算符:
&--按位与:
|--按位或:
~--按位取反:
^--按位异或:
<<--按位左移:
>>--按位右移:
二进制全部为零的含义---00000000的含义有:
如何看懂一个程序,分三步:
1、流程2、每个语句的功能
3、试数
对一些小算法的程序:
尝试自己去编程解决它,大部分人都自己无法解决
如果解决不了,就看答案
关键是把答案看懂,这个要花很多时间精力,也是我们学习的重点
看懂之后尝试自己去修改程序,并且知道修改之后程序的不同输出结果的含义
照着答案敲
调试错误
不看答案,自己独立把答案敲出来如果程序实在无法彻底理解,就把程序背会(极少这些情况)!
数据类型
#include
/*
整数
整型----int 占用4个字节
短整型----short int 占用2个字节
长整形----long int 占用8个字节
浮点型【实数】
单精度浮点数----float 小 占用4个字节
双精度浮点数----double 大 占用8个字节
字符
char 占用一个字节
复合类型数据
结构体
枚举
共用体
*/
变量的本质就是内存中一段存储空间。
#include
int main() {
int i = 3;//3最终是存放在内存中,程序终止之后3所占的空间会被释放
printf("i=%d\n", i);//%d 是输出整形的 后面会详细讲
return 0;
}
输出结果:
所谓的初始化就是赋值的意思。
已经初始化的代码:
#include
int main() {
int i = 88;
printf("i=%d\n", i);//输出88
return 0;
}
#include
/*
数据类型 变量名 = 要赋的值;
等价于
数据类型 变量名;
变量名 = 要赋的值;
*/
举例子:
#include
int main() {
int i = 88;
//等价于 int i; i=88;
printf("i=%d\n", i);//输出88
return 0;
}
#include
/*
十进制就是逢十进一
二进制就是逢二进一
十六进制就是逢十六进一
*/
例子:(ps:在算进制转换可以使用位权展开法进行计算-----具体百度)
#include
int main() {
int i = 88;//10进制
printf("i=%x\n", i);//输出58
}
#include
/*
整数:
十进制:传统的写法
十六进制:前面加0x或0X
八进制:前面加0 注意是数字零不是字母o
浮点数:
传统的写法
float x = 3.1;//传统
科学技术法
float x = 3.2e3;//x的值是3200
float x = 123.45e-2;//x的值是1.2345
*/
代码展示:
#include
int main() {
float x = 123.45e-2;//赋值不加F 默认是double类型 加了F如:123.45e-2F;是float类型
printf("%f\n", x);//输出1.234500
}
#include
/*
总结:字符用单引号,字符串用双引号。
单个字符用单引号括起来
'A'表示字符A
'AB'错误
字符串用双引号括起来
"A"正确,因为"A"代表'A' '\0'的组合 '\0'代表了字符串的结束,但不算字符串长度
*/
#include
/*
整数是以补码的形式转化为二进制代码存储在计算机中
实数是以IEEE754标准转化为二进制代码存储在计算机中
字符的本质实际也是与整数的存储方式相同
*/
#include
/*
字节就是存储数据的单位,并且是硬件所能访问的最小单位
1字节 = 8位
1k = 1024字节
1M = 1024K
1G = 1024M
*/
#include
int main() {
char ch = 'A';//ok 等价于 char ch ; ch = 'A';
//char g = "AB";//错误 因为"AB"是字符串,我们不能把字符串赋值给单个字符
//char b ="A";//错误 凡是字符串都默认加\0 ch变量只能存放一个 所以报错
// 因为"A"代表'A' '\0'的组合 '\0'代表了字符串的结束,但不算字符串长度
//char k ='AB'//错误的 单引号只能扩一个字符
printf("%c\n", ch);//输出A
return 0;
}
#include
/*
ASCII不是一个值,而是一种规定
ASCII规定了不同的字符是使用哪个整数值去表示
它对规定了
'A' -- 65
'B' -- 66
'a' -- 97
'b' -- 98
'0' -- 48
*/
代码展示:
#include
/*
printf() --将变量的内容输出到显示器上
四种方法:
1、printf("字符串\n");
2、printf("输出控制符", 输出参数);
3、printf("输出控制符1,输出控制符2,输出控制符3", 输出参数1, 输出参数2, 输出参数3);
输出空字符和输出参数的个数必须一一对应。
4、printf("输出控制符 非输出控制符", 输出参数); (ps:很少用)
*/
代码例子:
#include
int main() {
printf("欢迎一起学习插本c语言\n");//\n表示换行 第一种方法
int i = 10;
int j = 12;
printf("%d\n",i);//d是十进制 //第二种方法 输出10
printf("%d %d\n",i,j ); //第三种方法 输出12
}
#include
/*
% d ---- 以十进制整形数据输出
% ld ---- 长整形输出
% c ---- 输出一个字符
% f ---- 输出实数,以小数形式输出,默认情况下保留小数点6位。(float,double都推荐用% f输出)
% lf ---- 输出实数 (对应double) ps:(float,double都推荐用% f输出)
%s ---- 输出字符串
%e ---- 指定以指数形式输出实数
% x(或者% X后者% #X----- 注意大小写) ---- 输出十六进制 (八进制很少用 ,这里不作解析)
*/
来聊聊陌生的%x:
#include
int main() {
int x = 100;//100是十进制 0100代表8进制 0X100表示16进制
printf("%d\n",x);//100
printf("%x\n", x);//输出16进制的100 // 64
printf("%#X\n", x);//目的是加前缀让别人知道是十六进制 0X64
printf("%#x\n", x);//一般用大写的 小写的不要 0x64
return 0;
}
扩展知识:
#include
/*
C语言%f和 %lf的区别
%f和%lf分别是float类型和double类型用于格式化输入输出时对应的格式符号。
其中:
float,单精度浮点型,对应%f。
double,双精度浮点型,对应%lf。
在用于输出(printf)时:
float类型可以使用%lf格式
double类型如果使用了%f格式可能会导致输出错误。
在用于输入(scanf)时:
double 类型使用了%f格式,会导致输入值错误。
float类型使用double类型不仅会导致输入错误,还可能引起程序崩溃。
*/
1、01组成的代码可以表示数据也可以表示指令。
2、如果01组成的代码表示的是数据的话,那么同样的01代码组合以不同的输出格式输出就会有不同的输出结果。
scanf () 【通过键盘将数据输入到变量中】
格式代码如下:
#include
/*
scanf("输入控制符", 输入参数);//输入参数=地址列表
*/
用法一:scanf("输入控制符", 输入参数);
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中。
代码例子如下:
#include
int main() {
int j;//定义一个变量j
printf("请您估算下23年专升本C语言程序设计的分数:\n");
scanf("%d", &j);
printf("您估算的分数是:%d", j);
return 0;
}
代码运行结果:
用法二:scanf("非输出控制符 输入控制符", 输入参数);
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中。非输出控制符必须原样输入。
代码例子:
#include
int main() {
int i;
scanf("m%d", &i);//含非输出控制符 m
printf("i=%d\n", i);
return 0;
}
运行结果与分析:
含非输出控制符号m,如果我们想输出我们输入的数字就必须要把m(非输出控制符也要输入上去,才能输入123)
正确的输入方式是:
输入多位数字:
#include
int main() {
int i,j,k,l;
scanf("%d %d %d %d", &i,&j,&k,&l);//输入数字要用空格隔开,系统才会知道
printf("政治:%d,英语:%d,数学:%d,计算机程序设计:%d\n", i,j,k,l);//科目和都号都是非控制输出符号,会一同输出
return 0;
}
代码运行结果:
1、使用scanf之前最好先使用printf提示用户以样的方式来输入。
2、scanf中尽量不要出现非输入控制符,尤其是不要用\n
3、应该编写代码对用户的非法输入做适当的处理【非重点】
while(((ch=gerchar())!='\n')
continue;
算术运算符 :+ - * / %(取余)
关系运算符:> < == >= <= !=
逻辑运算符:!(非)、&&(并且)、||(或)
位运算符: <<(左移) 、>>(右移)、 ~(按位取反)、 | (按位或)、 ^(按位异或)、 &(按位与)
赋值运算符: =(赋值及其扩展赋值运算符)
条件运算符: ?:(三目运算符)
逗号运算符:,
指针运算符:*和&
求字节数运算符:sizeof
强制类型转换运算符:(类型)
成员运算符:.->
下标运算符: [ ]
优先级 | 运算符 | 名称含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | -- |
() | 圆括号 | (表达式)/函数名(形参表) | -- | ||
. | 成员选择(对象) | 对象.成员名 | -- | ||
.> | 成员选择(指针) | 对象指针->成员名 | -- | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
-- | 自减运算符 | --变量名/变量名-- | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | |||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 取余 | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | |||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 |
右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | - |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取余后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 加后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | - |
说明:
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
除法/的运算结果和运算对象的数据类型有关,两个数都是int,则商是int,若商有小数,则截取小数部分;
例子:16/5==3 18/7==4 5/3==1 3/5==0
被除数和除数只要有一个或两个都是浮点型数据,则商也是浮点型;
例子:16/5.0=3.20000 -13/4==-3
如果负数/负数得正;
例子:-13/-3==4 -12/-6==2
取余%的运算对象必须是整数,结果是整除后的余数,其余数的符号与被除数相同;
例子: 13%3==1 13%-3=1 -13%3==-1 以上例子13和-13都是被除数
测试取余运算符的代码例子:
//取余运算符:
int main() {
//==是等于 =是赋值
printf("-13取余6==%d,18取余7==%d\n", -13 % 6, 18 % 7);
//本来想把%取余符号当输出控制符输出,结果不行,只能换中文了
}
代码结果:
逻辑运算符:!(非)、&&(并且)、||(或)
C语言对真假的处理
非零是真(重点记住)
零是假
真是用1表示,假是用0表示(和原码补码反码刚好相反)
&&左边的表达式为假,右边的表达式肯定不会执行
||左边的表达式为真,右边的表达式肯定不会执行!真 假
!假 真
真&&真 真
真&&假 假
假&&真 假
假&&假 假
真||假 真
假||真 真
假||假 假
真||真 真
#include
int main() {
printf("%d", !- 3);//-3非0即真 !真=假 输出0
}
#include
int main() {
int i = 10;
int k = 20;
int m;
m = (3 > 2) && (k < 8);//真&&假 假输出0
printf("m=%d,k=%d\n", m, k);//输出m=0,k=20
}
&&左边的表达式为假,右边的表达式肯定不会执行
#include
int main() {
int i = 10;
int k = 20;
int m;
m = (1 > 2) && (k = 5);//假&&真 ,此时我们需要注意k到底等于多少?
printf("m=%d,k=%d\n",m,k);//输出 m=0,k=20
//m=0因为假输出0 k是20 因为只要左边为假 右边无论执行还是不执行结果都是假
//假设本串代码是m是真的话,k就会变成5
}
- 表达式就是利用运算符链接在一起的有意义,有结果的语句;
- 例如: a + b; 就是一个算数表达式, 它的意义是将两个数相加, 两个数相加的结果就是表达式的结果
- 注意: 表达式一定要有结果
自增[或自减]
分类:
前自增(减) --i ++i
后自增(减) i-- i++
前自增和后自增的异同:
相同:
最终都使i的值加1
不同:
前自增整体表达式的值是i加1之后的值
后自增整体表达式的值是i加1之前的值
为什么会出现自增:
代码更精炼
i自增的速度更快
学习自增要明白的几个问题:
1、我们编程时应该屏蔽掉前自增和后自增的差别
2、自增表达式最好不要作为一个为更大的表达式的一部分使用
或者说
i++和++i单独成一个语句,不要把它作为一个完整符合语句的一部分来使用
如:(考试喜欢这样)
1、printf("%d %d %d",i++,++i,i );//同上(考试会考,工作千万不要使用!!!)
2、int m =i++ + ++i + i + i++;//这样写不规范的代码,而且是不可移植的代码
在不同机器上运行结果不一样 i++不知道后面运行没就执行+ ++i
顺序点:, () ; i++,++i i++在逗号后面就一定会生效
代码例子:
#include
int main() {
int i;
int j;
int k;
int m;
i = j = 3;//等价于i=3; j=3;
k = i++;//后自增整体表达式的值是i加1之前的值
m = ++j;//前自增整体表达式的值是i加1之后的值
printf("i=%d, j=%d, k=%d, m=%d\n", i, j, k, m);//i=4, j=4, k=3, m=4
return 0;
}
符号:英文逗号,
多用于for循环中 for(i = 0, j = n-1; i < j; i++ , j--) //这里有三个表达式 用分号隔开
逗号运算符的优先级在c语言中是最低的
表达式:使用逗号把多个表达式连接起来的式子,最后一个表达式的值作为整个逗号表达式的值。
执行规则:从前往后依次执行各表达式。
把最后一个表达式的值作为整个逗号表达式的值。
#include
int main() {
int a = 3, b = 5, t;
t = a, a = b, b = t;
//等价以下三条语句
t = a; //t=3
a = b; //a=5
b = t; //b=3
//执行完该程序段后,a=5,b=3。交换功能!
}
#include
int main() {
int a1, a2, a3, b, c, d;//定义六个整形变量
a1 = (b = 6, c = 7, d = 5);//d的值5,赋给a1,a1=5
a2 = (++b,c--,d+1);//d+1的值6,赋给a2,a2=6 b=7,c=6
a3 = b,c,d;//(a3=b),c,d a3=7
//分析:(a3=b),c,d 形成逗号表达式 该逗号表达式从前往后计算,b赋值给a3后
// 再执行c,d cd已经没有含义了,cd没有参与运算,只是执行了。
//这行有两个运算符一个赋值运算符、一个逗号运算符,
//已知逗号运算符在c语言中是最低的,所以先进行赋值操作
printf("%d,%d,%d\n", a1, a2, a3);//输出5,6,7
}
例题:
#include
int main() {
int a = 1;
printf("%d\n", ((a += 4, a + 5), a / 2)); //输出2 5/2,整数/整数=整数
}
/*/
a+5不会改变a的值 相反=就会改变a的值
*/
例子:
#include
int main() {
int i;
int j = 2;
i = (j++, ++j, j + 2, j - 3);
printf("%d\n", i);//1
}
/*/
j++后逗号后面j就会发现变化j变成3(因为逗号是顺序点)
++j在自增后是4
j+2的值,没有改变,因为+2没有赋值给j
j-3=4-3=1
i+2的值是没有改变的 除非写成i+=2
*/
三目运算符,它需要3个数据或表达式构成条件表达式
格式:
表达式1?表达式2(结果A):表达式3(结果B)
- 示例:
考试及格 ? 及格 : 不及格;
求值规则:
- 如果"表达式1"为真,三目运算符的运算结果为"表达式2"的值(结果A),否则为"表达式3"的值(结果B)
- 注意点
- 条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符
- 条件运算符?和:是一个整体,不能分开使用
代码例子:
#include
int main() {
int a = 10;
int b = 20;
int max = (a > b) ? a : b;
printf("max = %d", max);//20
//思路:10>20吗如果大于则输出a,否则输出b
//显然10不大于20,那么输出b
return 0;
}
程序代码执行的顺序。
1、顺序结构(按书写顺序从上至下依次执行)
2、选择结构(某些代码可能执行,也可能不执行,有选择的执行某些代码)
3、循环结构(其特点是,在给定条件成立时,反复执行某程序段, 直到条件不成立为止,给定的条件称为"循环条件",反复执行的程序段称为"循环体")
分类:
1、if最简单的用法。
2、if的范围问题
3、if----else的用法4、if----else if----else 的用法
5、C语言对真假的处理
6、if举例--求分数的等级
7、if的常见问题解析
#include
if(表达式) {
语句块1;
}
后续语句;
/*
功能:如果表达式为真,执行语句
如果表达式为假,语句不执行*/
if最简单的用法例子解析:
#include
int main() {
if (3) {//非0即真
printf("AAAA\n");
}
if (0) {//0即假 不能输出
printf("BBBB\n");
}
if (0==0) {//0==0可以输出
printf("CCCC\n");
}
return 0;
}
//输出结果:AAAA CCCC
总结:
1、
if(表达式)
语句A;
语句B;
解释:if默认只能控制语句A的执行或不执行
if无法控制语句B的执行或不执行
或者讲:语句B一定会执行2、
if(表达式){
语句A;
语句B;
}
此时if可以控制语句A和语句B
由此可见:if默认只能控制一个语句的执行或不执行
如果想控制多个语句的执行或不执行就必须把这些语句用{}括起来
if的范围问题(重点)例子:
不常用的写法(不推荐使用,会给骂死):
#include
int main() {
if (3> 2)//会输出cccc dddd
printf("cccc\n");//第一个语句
printf("dddd\n");//第二个语句
//因为系统从上往下去执行
return 0;
//}
细节:
#include
int main() {
if (1 > 2)//会输出 dddd
printf("cccc\n");//第一个语句
printf("dddd\n");//第二个语句
//if默认控制一个语句,if判断条件为假,所以不输出cccc
//因为系统是顺序执行,会输出dddd
return 0;
}
强烈推荐这样写if选择结构:
#include
int main() {
if (3 > 2) {
printf("cccc\n");//第一个语句
printf("dddd\n");//第二个语句
}
printf("加了{}就可以输出两个语句了\n");
return 0;
}
//输出结果:cccc
dddd
加了{}就可以输出两个语句了
if--else含义:
if第二种形式
- 如果表达式为真,则执行语句块1,否则执行语句块2
- else不能脱离if单独使用
格式:
#include
if(表达式){
语句块1;
}else{
语句块2;
}
后续语句;
if--else语句例子:
#include
int main() {
int a = 4, b = 5;
if (a > b) {//如果4大于5就输出666
printf("666");
}
else {//否则输出777
printf("777");//777
}
return 0;
}
if第三种形式
如果"表达式1"为真,则执行"语句块1",否则判断"表达式2",如果为真执行"语句块2",否则再判断"表达式3",如果真执行"语句块3", 当表达式1、2、3都不满足,会执行最后一个else语句
众多大括号中,只有一个大括号中的内容会被执行
只有前面所有添加都不满足, 才会执行else大括号中的内容。
if----else if----else 的用法格式:
#include
int main() {
if (表达式1) {//如果
语句块1;
}
else if (表达式2) {//否则如果
语句块2;
}
else if (表达式3) {//否则如果
语句块3;
}
else {//否则
语句块4;
}
后续语句;
return 0;
}
if----else if----else 的用法例子:
#include
int main() {
int age=19;
if (age > 40) {
printf("给房卡\n");
}
else if (age > 25) {
printf("给名片\n");
}
else if (age > 18) {
printf("给网卡\n");
}
else {
printf("给好人卡\n");
}
printf("买烟\n");
}
//输出结果:给网卡
// 买烟
非0(零)是真
0(零)就是假
真用1表示
假用0(零)表示
6、if举例--求分数的等级:
#include
int main() {
float score ;
printf("请输入您的c语言程序设计期末成绩:\n");
scanf("%f",&score);
if (score >= 90 &&score < 100) {
printf("您的成绩是:%f\n", score);
printf("优秀\n");
}
else if (score >80 && score < 90) {
printf("您的成绩是:%f\n", score);
printf("良好\n");
}
else if (score >60 && score < 80) {
printf("您的成绩是:%f\n", score);
printf("及格\n");
}
else {
printf("您的成绩不合格,请您继续努力!");
}
return 0;
}
if例子二:
#include
int main() {
int a, b, c,t;//等价于int a; int b; int c;
printf("请输入三个整数\n");
scanf("%d %d %d", &a, &b, &c);
//编写代码完成a是最大值 b是中间值 c是最小值
if (a < b) {
t = a;
a = b;
b = t;
}
if (a < c) {
t = a;
a = c;
c = t;
}
if (b < c) {
t = b;
b = c;
c = t;
}
printf("%d %d %d\n", a, b, c);
return 0;
}
//此程序如果输入相等的数字就不行
1、空语句的问题:
if (3 > 2);
等价于
if (3 > 2);//这是一个空语句
2、常见的问题:
if(表达式1)
A;
else
B;//是正确的
if(表达式1);
A;
else
B;//是错误的 ,因为系统没有开头以else开头的语句
3、当表达式1和二都成立的话,只会执行表达式1的语句,因为C语言是顺序执行
# include
int main(void) {
if (表达式1) {
A;//输出A
}
else if (表达式2) {
B;
}
else if (表达式3) {
C
}
else {
D;
}
return 0;
}
4、没有else的情况:
if (表达式1) {
A;
}
else if (表达式2) {
B;
}
else if (表达式3) {
C
}
这样写语法不会出错,但逻辑上有漏洞
如果3个表达式都不成立,程序不会输出任何东西
5、假设else中有表达式:
if (表达式1) {
A;
}
else if (表达式2) {
B;
}
else if (表达式3) {
C
}
else (表达式4){
D;
}
这样写是不对的,正确的写法是要么去掉 else (表达式4)中的(表达式4)
要么在else (表达式4) else后面加if
switch(表达式){
case 常量表达式1:
语句1;
break;
case 常量表达式2:
语句2;
break;
case 常量表达式n:
语句n;
break;
default:
语句n+1;
break;
}
#include
int main() {
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;
}
以上程序的分析:
case 1、case 2、case 3、default是程序入口
val判断值和谁相等,若找到程序入口是case 1,
那么case 1、case 2、case 3、default都会失效/被屏蔽掉了
程序从上往下去执行若用户输入的值超过case就执行default
若注释掉break的switch语句:
#include
int main() {
int val;
printf("请输入您要进入的楼层:");//输入1
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;
}
输出结果以及分析:
输入1;
运行结果是:
请输入您要进入的楼层:1
1层开!
2层开!因为程序找到程序入口会屏蔽掉case 1、case 2、case 3、default
然后从上往下执行相当于我们看到的代码是:
printf("1层开!\n");
//break;
printf("2层开!\n");
break;
printf("3层开!\n");
break;
printf("没有盖到这一层\n");
break;
}从上往下执行遇到break就掉出循环所以输出:
1层开!
2层开!
break如果用于循环是用来终止循环
break如果用于switch,则是用于终止switch
break不能直接用于if,除非if属于循环内部的一个子句
if(3>2){
break; //break 语句只能在循环或开关中使用
}
#include
int main() {
int i;
for (i = 0; i < 3; i++) {
if (3 > 2) {
break;//break虽然是if内部的语句,但break终止的却是外部的for循环
printf("哈哈!\n");//永远不会输出
}
}
#include
int main() {
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;
}
break;//终止外层的switch
case 2:
a++;
b++;
break;
}
printf("%d %d\n", a, b);//1 0
return 0;
}
用于跳过本次循环余下的语句,转去判断是否需要执行下次循环
例子1:
例子1:
for(1;2;3){
A;
B;
contine;//如果执行该语句,则执行完该语句后,会执行语句3,C和D都会被跳过去,C和D不会被执行
C;
D;
}
例子2:
例子2:
while(表达式){
A;
B;
contine;//如果执行该语句,则执行完该语句后,会执行表达式,C和D都会被跳过去,C和D不会被执行
C;
D
}
定义:某些代码会被重复执行
分类
for
while
do...whilebreak和continue
for(初始化表达式;循环条件表达式;循环后的操作表达式) {
循环体中的语句;
}
例子:
for(int i = 0; i < 10; i++){
printf("快来看c语言\n");
}
首先执行"初始化表达式",而且在整个循环过程中,程序只会执行一次初始化表达式
接着判断"循环条件表达式"是否为真,为真执行循环体中的语句
循环体执行完毕后,接下来会执行"循环后的操作表达式",然后再次判断条件是否为真,为真继续执行循环体,为假跳出循环
重复上述过程,直到条件不成立就结束for循环
# include
int main() {
int i ;
int sum = 0;
for (i = 1; i <= 4; i++) {
sum = sum + i;
}
printf("sum=%d\n", sum);
return 0;
}
/*
这个for循环的思路:
第一步 i=1 1<=4 sum=1 i=2
第二步 i=2 2<=4 sum=2 i=3
第三步 i=3 3<=4 sum=6 i=4
第四步 i=4 4<=4 sum=10 i=5
第五步 i=5 5<=4 X for循环结束
执行顺序1、i=1 2、i<=4 3、sum = sum + i;4、i++ 注意第一步在for循环中只执行一次
3执行结束才表示for循环一次结束 顺序:1243 243....2
*/
#include
int main() {
int i;
int sum = 0;
for (i = 1; i < 10; i += 2) {//i+=2;等价于 i=i+2;
sum = sum + i;
}
printf("sum=%d\n", sum);//25
printf("i=%d\n", i);//11
}
/*
1 3 5 7 9
sum=0+1+3+5+7+9=25;
循环了5次 1243 i=11 判断式不成立 for循环结束
*/
循环结构---for嵌套if的用法 :
#include
int main() {
int i;
int sum = 0;
for (int i = 1; i < 10; i++) {
if (i % 3 == 0) {
sum = sum + i;
printf("本次输出的数是:%d\n", i);
}
}
printf("sum=%d\n", sum);
return 0;
}
//输出结果:
/*
本次输出的数是:3
本次输出的数是 : 6
本次输出的数是 : 9
sum = 18
*/
求1到100之间的奇数之和
求1到100之间的奇数的个数
求1到100之间的奇数的平均值
求1到100之间的奇数之和与偶数之和
代码例子:(个人代码能力有限,觉得不好可以百度或者csdn看看有没有更简洁的办法!)
#include
int main() {
int i;
int sum = 0;
int os = 0;//偶数的总和
int js = 0;//奇数的总和
float avg = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
os = os + i;//计算偶数的和
}
else {
js = js + i;//计算奇数的和
sum = sum + 1;//计算奇数的个数
}
}
avg = js*1.0 / sum;//计算奇数的平均值 一个小数/整数 商一定是小数
//1.0默认double类型 再转float所以会显示可能丢失数据
printf("1到100之间的偶数的和:%d\n", os);
printf("\n");
printf("1到100之间的奇数的和:%d\n", js);
printf("1到100的奇数的个数是:%d\n", sum);
printf("1到100之间的奇数的平均值是:%f\n", avg);
return 0;
}
//输出:
/*
1到100之间的偶数的和:2550
1到100之间的奇数的和:2500
1到100的奇数的个数是:50
1到100之间的奇数的平均值是:50.000000
*/
强制类型转化:
格式:
(数据类型)(表达式)
功能:
把表达式的值强制转化为前面所执行的数据类型
例子:(int)(4.5+2.2)最终值是 6
(float)(5) 最终值是 5.000000
#include
int main() {
int i;
float sum = 0;
for (i = 1; i <= 100; i++) {
sum = sum + 1 / (float)(i);//强制类型转换把后面i的值强制转换为float 一个整数/小数商是小数
//sum=sum+(float)(1/i);这样写是错误的因为:sum=sum+1/1=1.000000 + 0.000000 ...0.000000
//更简单的方法是:sum=sum+1.0/i;
}
printf("sum: %f\n", sum);//float必须用%f输出0.000000(默认6位)
return 0;
}
/*
1+1/2+1/3+...+1/100的程序
输出结果:sum: 5.187378
详细步骤举例:
1-> i=1 1<100 成立
sum=0+1/1.0 i++ i=2
2-> i=2 2<100 成立
sum=1+1/2.0 i++ i=3
(以此类推)
*/
floathe1double都不能保证可以精确的存储一个小数
例子:
#include
int main() {
float i = 99.9;
printf("%lf", i);//输出99.900002
return 0;
}
举例:
有一个浮点型变量x,如何判断x的值是否是零
if(|x-0.000001|<=0.000001){
是0;
}
else{
不是0;
}
为什么循环中更新的变量不能定义成浮点型
因为循环中更新的变量不能定义浮点型
例子:for(float i=5.0;i>0;--i)
循环 5 4 3 2 1
有可能i会是0.0几就比0要大了
执行顺序:1245A6532B (5和2都只判定一次)
#include
int main() {
for (1; 2; 3) {
for (4; 5; 6) {
printf("A");
}
}
printf("B");
return 0;
}
细节:
多层嵌套for (其实都默认加 {} 就行 )
1、for(1;2;3)//1
for(4;5;6)//2
A;//3
B;//4
整体是两个语句,1 2 3是第一个语句 4是第二个语句 for默认控制一个语for(1;2;3)
2、(等价上面)
for(1;2;3){ //1
for(4;5;6){//2
A;//3}
}
B;//4
整体是两个语句,1 2 3是第一个语句 4是第二个语句 for默认控制一个语for(1;2;3)2、 for(4;5;6){
A;
B;
}
整体是一个语句3、for(7;8;9)
for(1;2;3){
A;
B;
for(4;5;6)
C;
}
整体是1一个语句 for默认控制一个语句
多个for循环嵌套的例子:
#include
int main() {
int i, j;
for (i = 0; i < 3; i++) {
for (j = 2; j < 5; ++j) {
printf("哈哈\n");
}
}
printf("嘻嘻\n");
return 0;
}
/*
运行结果:哈哈9次 嘻嘻1次 重点是明白执行顺序!!!
哈哈
哈哈
哈哈
哈哈
哈哈
哈哈
哈哈
哈哈
哈哈
嘻嘻
*/
例子:
#include
int main() {
int i, j;
for (i = 0; i < 3; i++)
printf("嘿嘿\n");
for (j = 2; j < 5; ++j)
printf("哈哈\n");
printf("嘻嘻\n");
return 0;
}
/*
运行结果:for循环默认控制一个语句 一个语句执行3次就下一个
嘿嘿
嘿嘿
嘿嘿
哈哈
哈哈
哈哈
嘻嘻
*/
执行顺序:1 2 4 5 6 8 9 7 6 10 3 2......
B表示2进制
O表示8进制
D表示十进制
H表示十六进制
10转2的例子:
185(十进制)转(二进制)=10111001
1 0 1 1 1 0 0 1
...512 256 128 64 32 16 8 4 2 1
(128+32+16+8+1)=10111001
10转8的例子:
185(十进制)转(八进制)=271
先把185(十进制转成二进制)=10111001
然后砍3位(只要1的部分)
1 0 / 1 1 1 / 0 0 1 百、十、个
2 1 4 2 1 4 2 1
2+(421)+1=271
10转16的例子:
185(十进制)转(十六进制)=119
先把185(十进制转成二进制)=10111001
然后砍4位(只要1的部分)
1 0 1 1 /1 0 0 1 十、个(多出来就进位)
8 4 2 1 8 4 2 1
(8+2+1)/ (8+1)=119
16转10的例子:
核心:16的一位顶4位2进制
32(十六进制)转(十进制)=50
0 0 1 1
8 4 2 1 3是0011
0 0 1 0 2是0010
8 4 2 1
得出32转成2进制是110010
然后2进制110010转10进制即可得出16进制的32转成10进制是什么
1 1 0 0 1 0
64 32 16 8 4 2 1
把1的底下的数加起来 32+16+2=50
8转10的例子:
同理!!!无论怎么转用上面的方法都可以!
1、执行流程。
2、与for的相互比较。
3、while举例。
4、什么时候时候while,什么时候使用for。
格式:
while ( 循环控制条件 ) {
循环体中的语句;
能够让循环结束的语句;
....
}
while循环执行流程:
1、首先会判定"循环控制条件"是否为真, 如果为假直接跳到循环语句后面
2、如果"循环控制条件"为真, 执行一次循环体, 然后再次判断"循环控制条件"是否为真, 为真继续执行循环体,为假跳出循环
3、重复以上操作, 直到"循环控制条件"为假为止
for和while循环可以相互转换
for(1;2;3){
A;
}
等价于:
1;
while(2){
A;
3;
}
while和for可以相互转化
但for的逻辑性更强,更不容易出错,推荐使用for
例子(1到100之间的数):
#include
int main() {
int sum = 0;
int i=0;
while (i <= 100) {
sum = sum + i;
i++;
}
printf("sum=%d\n",sum);//5050
return 0;
}
从键盘输入一个数字,如果该数字是回文数,则返回yes,否则返回no
什么是回文数:正着写倒着写一样
比如:121 12321 都是回文数
代码:
#include
int main() {
int val;//存放带判断的数字
int m;
int sum = 0;
printf("请输入您需要判断的数字: ");
scanf("%d", &val);//121
m = val;//为了将val的值赋值给m中用m去计算,保留val的值用于后面的判断
while (m) {
sum = sum * 10 + m % 10;
m /= 10;
}
if (sum == val) {
printf("Yes!\n");//Yes!
}
else {
printf("No!\n");
}
return 0;
}
/*
判断回文数程序
while (m) { //m非0即真 m为0时跳出程序
sum = sum * 10 + m % 10;//重点理解取余
m /= 10;//除一个数相当于砍掉他的各位
}
输入121
m=121=avl
第一轮while是0+121%10=0+1=1 121/10=12 m是12 整数/整数没有小数点
第二轮while是1*10+12%10=10+2=12 12/10=1 m是1
第三轮while是12*10+1%10=120+1=121 1/10=0 m是0
0就退出while
sum=121=avl 121就是回文数
回文数while内容的详细例子:
#include
int main() {
int n;
int f1,f2,f3;
int i;
f1 = 1;
f2 = 2;
printf("请输入您需要求的想的序列:");
scanf("%d",&n);//6
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);//13
return 0;
}
/*
菲波拉契序列
1 2 3 5 8 13 21 34
假设用户输入6
1> i=3 3<=6 成立 f1=1 f2=2
f3=1+2=3 f1=f2=2 f2=f3=3 i++ i=4
2> i=4 4<=6 成立
f3=2+3=5 f1=f2=3 f2=f3=5 i=5
3> i=5 5<=6 成立
f3=3+5=8 f1=f2=5 f2=f3=8 i=6
3> i=6 6<=6 成立
f3=5+8=13 f1=f2=8 f2=f3=13 i=7
5> i=7 7<=6 不成立
*/
通常使用for,没法说,用多自然懂!!
格式:
do {
循环体中的语句;
能够让循环结束的语句;
....
} while (循环控制条件);
do...while循环执行流程:
1、首先不管while中的条件是否成立, 都会执行一次"循环体"
2、执行完一次循环体,接着再次判断while中的条件是否为真, 为真继续执行循环体,为假跳出循环
3、重复以上操作, 直到"循环控制条件"为假为止
题外:do...while,并不等价于for,当然也不等价于while
主要用于人机交互
do...while无论如何都会执行一次循环体,而while则要经过判断才决定是否执行循环体
#include
int main() {
int num = 0;
do {
printf("请输入密码,验证您的身份\n");
scanf("%d", &num);
} while (123456 != num);
printf("主人,您终于回来了\n");
return 0;
}
/*
无论如何都要执行一次循环体
输入密码若是循环控制语句中的123456就结束do...while循环
输出主人,您终于回来了
反之一直提示 : 请输入密码,验证您的身份
*/
#include
# include
int main() {
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 * 4 * 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);//前面必须得加一个空格,原因略
} while ('y'==ch ||'Y'==ch);//输入y/Y退出继续循环否则退出程序
return 0;
}
/*
一元二次方程用do...while实现
1、首先要懂得如何去算一元二次方程
2、理解代码的流程控制
3、了解头文件
sqrt 开根
*/
主要是看懂流程(不要求看懂程序):
为了解决大量同类型数据的存储和使用问题。
为了模拟现实世界。
#include
int main() {
int a[5] = { 1,2,3,4,5 };
//a 是数组的名字,5表示数组元素的个数,并且这5个元素分别用a[0]、a[1]、a[2]、a[3]、a[4]
//数组是从0开始的
int i ;
for (i=0; i < 5; i++) {//循环遍历数组元素0-4
printf("%d\n",a[i]);//1 2 3 4 5
}
return 0;
}
为n个变量连续分配存储空间(不是连续的一定不是数组)
所有的变量数据类型必须相同
所有变量所占的字节大小必须相等
例子:
int a[5];
一维数组名不代表数组中所有的元素,
一维数组名代表数组第一个元素的地址。
初始化
完全初始化int a[5]={1,2,3,4,5};
不完全初始化,未初始化的元素自动为零(0)
int a[5]={1,2,3};
不初始化,所有元素是垃圾值
nt a[5];
清零(0)
int a[5]=0;
错误写法:
int a[5];//表示里面有五个元素 0-4
a[5]={1,2,3,4,5};//错误,不在定义时出现,就代表下标了 a[5]表示第六个元素
只有在定义数组的同时才可以整体赋值
其他情况下整体赋值都是错误的
int a[5]={1,2,3,4,5};
a[5]=100;//错误,因为没有a[5]这个元素,最大的元素是a[4]
int a[5]={1,2,3,4,5};
int b[5];//b是垃圾值
如果要把a数组中的值全部赋值给b数组
错误的写法:
b=a;//错误 b和a都是数组名 数组名是第一个元素的地址a[0],b[0]的地址
正确的写法:
for(i=0;i<5;i++){
b[i]=a[i];
}
赋值:
#include
int main() {
int i;
int a[5];//垃圾值
scanf("%d", &a[0]);
printf("a[0]=%d\n", a[0]);
for (i = 0; i < 5; i++) {
printf("%d\n", a[i]);
}
return 0;
}
/*
10
a[0]=10
10
-858993460
-858993460
-858993460
-858993460
除了输入的a[0]是10 其他都是垃圾值
*/
倒置代码例子:
#include
int main() {
int i,j,t;
int a[7] = {1,2,3,4,5,6,7};
j = 6;
i = 0;
while (i < j) {
t = a[i];//把a[i]的值赋给t 下标
a[i] = a[j];//第一轮把a[6]的值7赋值给a[0]依次循环
a[j] = t;//第一轮把t的值赋值给我a[6]=1 相当于1和7互换位置 依次循环
i++;
--j;
}
for (i = 0; i < 7; i++) {
printf("%d\n", a[i]);//7 6 5 4 3 2 1
}
return 0;
}
/*
倒置:
int a[7] = {1,2,3,4,5,6,7};
a[0]是1 以此类推 a[6]是7
*/
int a[3][4];
int a[i][j];//i是行,j是列
总共是12个元素,可以当做3行4列看待,这12元素的名字依次是:
第0列 第1列 第2列 第3列
第0行 a[0][0] a[0][1] a[0][2] a[0][3]
第1行 a[1][0] a[1][1] a[1][2] a[1][3]
第2行 a[2][0] a[2][1] a[2][2] a[2][3]
a[i][j] 表达第i+1行第i+1列的元素
int a[m][n];该二维数组右下角位置的元素只能是a[m-1][n-1]//a[2][3]
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}//最后一个可以不用逗号
};
#include
int main() {
int i, j;
int a[3][4] = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}//最后一个可以不用逗号
};
for (i = 0; i < 3; i++) {
for (j = 0; j < 4; j++) {
printf("a[%d][%d]=%-5d ",i,j,a[i][j]);//负是左对齐,5是间距
//-5d使结果数字中间有4个空格,再加上printf里头还输入了两个,一共是6个空格在两个数字之间
}
printf("\n");
}
return 0;
}
/*
二维数组的遍历
*/
输出结果:
a[0][0]=1 a[0][1]=2 a[0][2]=3 a[0][3]=4
a[1][0]=5 a[1][1]=6 a[1][2]=7 a[1][3]=8
a[2][0]=9 a[2][1]=10 a[2][2]=11 a[2][3]=12
是否存在多维数组
不存在
因为内存是线性一维的
n维数组可以当做每个元素是n-1维数组的数组
比如:(若不懂看看一维数组的定义)
int a[3][4];
该数组是含有3个元素的一维数组
只不过每个元素都可以再分成4个小元素
int a[3][4][5];
该数组是含有3个元素的一维数组
只不过每个元素都是4行5列的二维数组
避免了重复性操作
有利于程序的模块化
例子(重点是结合笔记看懂代码含义):
#include
//1、为什么需要函数
//void 没有返回值函数,max是函数的名字,i和j是形参(形式参数)
void max(int i, int j) {
if (i > j) {
printf("%d\n", i);
}
else {
printf("%d\n",j);
}
}
int main() {
int a,b, c, d, e, f;
a = 1;
b = 2;
c = 3;
d = 9;
e = -5;
f = 100;
max(a, b);//1,2 //此处是实参(实际参数)
max(c, d);//3,9
max(e, f);//-5,100
return 0;
}
/*
函数的第一个例子
*/
逻辑上:能够完成特定功能的独立的代码单元
物理上:
能够接受数据[当然也可以不接受数据]
能够对接受的数据进行处理
能够将数据处理的结果返回[当然也可以不返回任何值]
总结: 函数是个工具,它是为了解决大量类似问题而设计的
函数可以当做一个黑匣子(相当于java的封装)
例子(重点是结合笔记看懂代码含义):
#include
//2、什么叫函数
int f(void) {//括号中的void表示该函数不能接受数据 int表示函数返回值是int类型的数据
return 10;//向主调函数(main)返回10 f是被调用函数
}
void g(void) {
//return 10;//与上一行的行首的void相矛盾 返回值类型与函数类型不匹配
}
int main() {
int j=88;
j = f();//扩号里不能写值,否则会报错,因为被调用函数中有(void)
printf("%d\n", j);//10
//j = g();//报错 因为g函数没有返回值
return 0;
}
/*
函数:
函数分有参函数和无参函数
无参:调用该函数时无需传入数据,,格式:类型名 函数名(void)
有参:调用该函数时需要传入数据,,格式:类型名 函数名(形参)
*/
函数的返回值 函数的名字(函数的形参列表){
函数的执行体
}1、函数定义的本质是详细描述函数之所以能够实现某个特定的功能的具体方法
2、return 表达式;的含义:
3、函数返回值类型也称为函数的类型,因为如果函数名前的返回值类型和函数执行体中的 return 表达式;中表达式类型不同的话,
则最终函数返回值的类型,以函数名前的返回值类型为准。
第二小题例子(return 表达式;的含义):
1> 终止被调函数,向主调函数返回表达式的值
2> 如果表达式为空,则只终止函数,不向主调用函数返回任何值
3> break是用来终止循环和switch的,return是用来终止函数的
例子:
1、
void f(){
return;//return只用来终止函数,不向主调用函数返回任何值
}
2、
int f(){
return 10 ;//第一:终止函数,第二:向主调函数返回10
}
return例子:
#include
void f(void) {//被调用函数
int i;
for (i = 0; i < 5; i++) {
printf("大家辛苦了!\n");
return ;
}
printf("同志们好!\n");
}
int main() {//主调函数
f();
return 0;
}
/*
仅仅输出了:大家辛苦了!
*/
break例子:
#include
void f(void) {//被调用函数
int i;
for (i = 0; i < 5; i++) {
printf("大家辛苦了!\n");
break ;
}
printf("同志们好!\n");
}
int main() {//主调函数
f();
return 0;
}
/*
输出:
大家辛苦了!
同志们好!
*/
第三小题的例子:
#include
//3、如何定义函数
//例子:
int f() {
return 10.5;//因为函数的返回值类型是int,所以最终f返回的是10而不是10.5
}
int main() {
int i = 99;
double x = 6.6;
x = f();//x=10.000000 还有很多0
printf("%lf\n", x);//lf默认6位小数点 10.000000
return 0;
}
有参函数 和 无参函数
有返回值 和 无返回值函数
库函数 和 用户自定义函数
值传递函数 和 地址传递函数
普通函数 和 主函数(主函数)一个程序必须有且只能有一个主函数
主函数可以调用普通函数,普通函数不能调用主函数
普通函数可以相互调用
主函数是程序的入口,也是程序的出口
#include
int main() {//主调函数
int val=5;
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;
}
/*
函数举例_判断一个数组是否是素数
什么是素数:只能被1和自己整除的数叫素数(1不是素数)
这个程序只能对一个整数进行判断是否是素数,若我们有n个就要重复去写这个判断代码,十分不高效。
所以我们要用函数来进行判断,将来想用直接调用函数即可
这个程序主要是理解for循环结束后i的值是什么!
*/
函数举例_判断一个数组是否是素数(用调用函数的例子):
#include
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() {
int m;
int i;
scanf("%d", &m);
if (Isprime(m)){//表达式不为0就会判断为真 true是真 false是假
printf("Yes!\n");
}
else {
printf("No!\n");
}
return 0;
}
函数调用和函数定义的顺序
如果函数调用写在了函数定义的前面,则必须加函数前置声明
函数前置声明:
1、告诉编译器即将可能出现的若干个字母代表的是一个函数
2、告诉编译器即将可能出现的若干个字母所代表的函数的形参和返回值的具体情况
3、函数声明是一个语句,末位必须加分号
4、对库函数的声明是通过#include <库函数所在的文件的名字.h>来实现
报错例子1:
//void f(void);//函数声明分号不能丢掉
int main() {
f();
return 0;
}
void f(void) {
printf("哈哈");
}
/*
被调用函数写在调用函数下面会报错,因为程序入口在main,从上往下执行,当程序执行到f();
系统不知道你这个是函数名,所以报错
除错方式:
1、要么加函数声明--告诉系统f();是函数名
2、要么把被调用函数写在调用函数的前面
这样就不错报错了
*/
报错例子2:
#include
void f(void);//第二行 函数声明
void g(void) {
f();//因为函数f的定义放在了调用f语句后面,所以语法出错
}
void f(void) {
printf("哈哈\n");
}
int main() {
g();
}
/*
一定要明白该程序为什么是错误的
一定要明白该程序第2行生效之后程序为什么就正确了
*/
函数声明总结:
正确的方式:1、把被调用函数写在调用函数的前面。
#include
void f(void) {
printf("哈哈");
}
int main() {
f();
return 0;
}2、若被调用函数写在调用函数后面,则需要加函数声明,程序就不会报错了。
#include
void f(void);
int main() {
f();
return 0;
}
void f(void) {
printf("哈哈");
}
个数相同 位置一一对应 数据类型必须相互兼容
代码例子:
#include
void f(int i) {//形参
printf("%d\n",i);//5
}
int main() {
f(5);//实参
return 0;
}
合理函数1:
求1到某个数字之间(包括该数字)所有的素数,并将其输出
只用mian函数时间,有局限性
1、代码的重用性不高
2、代码不容易理解函数是C语言的基本单位,类是java,c#,c++的基本单位
合理函数1代码:
//合理函数1
int main() {
int val;
int i;
int j;
scanf("%d",&val);
printf("\n");
for (i = 2; i <= val; i++) {
//判断i是否是素数,是输出,不是不输出
for (j = 2; j < i; j++) {
if (0 == i % j) {
break;
}
}
if (j == i) {
printf("%d\n",i);
}
}
return 0;
}
/*
求1到某个数字之间(包括该数字)所有的素数,并将其输出
只用mian函数时间,有局限性
1、代码的重用性不高
2、代码不容易理解
*/
合理函数2:
求1到某个数字之间(包括该数字)所有的素数,并将其输出
用一个函数来判断一个数字是否是素数,
优点是:代码的可读性相比上面的合理函数1程序更容易理解
代码的可重用性也比上面的合理函数1程序高
缺点是:
可重性仍不是非常高,
比如求1000个数字,求它们每个数字从1到它本身的素数
则
for (i = 2; i <= val; i++) {
if (IsPrime(i)){
printf("%d\n", i);
}}
要写1000次。
合理函数2代码:
#include
//合理函数2
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() {
int i;
int val;
scanf("%d", &val);
for (i = 2; i <= val; i++) {
if (IsPrime(i)) {
printf("%d\n", i);
}
}
}
/*
求1到某个数字之间(包括该数字)所有的素数,并将其输出
用一个函数来判断一个数字是否是素数,
优点是:代码的可读性相比上面的合理函数1程序更容易理解
代码的可重用性也比上面的合理函数1程序高
缺点是:
可重性仍不是非常高,
比如求1000个数字,求它们每个数字从1到它本身的素数
则
for (i = 2; i <= val; i++) {
if (IsPrime(i)){
printf("%d\n", i);
}
}
要写1000次。
*/
合理函数3:
郝斌老师的意思是之前那些你要用到的那些功能都要再写一次,而用函数的时候调用就好
用两个函数来实现求1到某个数字之间所有的素数,并将其输出
本程序和合理函数2程序相比较
代码量更少了,可重用性更高
合理函数3代码:
#include
//合理函数3
//本函数的功能是:判断m是否为素数,是返回true,不是返回false
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;
}
}
//本函数的功能是把1到n之间所有的素数在显示器上输出
void TraverseVal(int n) {
int i;
for (i = 2; i <= n; i++) {
if (IsPrime(i)) {
printf("%d\n", i);
}
}
}
int main() {
int val;
scanf("%d", &val);
printf("\n");
TraverseVal(val);
return 0;
}
/*
郝斌老师的意思是之前那些你要用到的那些功能都要再写一次,而用函数的时候调用就好
用两个函数来实现求1到某个数字之间所有的素数,并将其输出
本程序和合理函数2程序相比较
代码量更少了,可重用性更高
*/
double sqrt (double ); 求的x的平方根 调用:sqrt(5.5);
int abs(int x); 求x的绝对值
double fabs(double x); 求x的绝对值
超重点:后面更!!!
#include
int f(int i) {
int i = 99;//和形参重定义 报错 因为形参i和变量i都在f函数里面使用,就重复定义了
printf("i=%d\n", i);
return 1;
}
int main() {
int i = 10;//这个i就可以使用 因为这个i和f函数的i不在一个范围里
f(i);
return 0;
}
全局变量:
1、在所有函数外部定义的变量叫全局变量
2、全局变量使用的范围:从定义位置开始到整个程序结束例子:(int k= 99;是全局变量)
#include
int k= 99;
void f(int i) {
printf("i=%d\n", i);//i=8
}int main() {
f(8);
return 0;
}
局部变量:
在一个函数内部定义的变量或者函数的形参,都统称为局部变量
void f(int i) {//形参
int j = 20;
}
i和j都属于局部变量
局部变量的使用范围:只能在本函数内部使用
注意的问题:
全局变量和局部变量命名冲突的问题:
在一个函数内部如果定义的局部变量的名字和全局变量名
一样时,局部变量会屏蔽掉全局变量
注意的问题代码:
#include
int i= 99;//全局变量
void f(int i) {//局部变量
printf("i=%d\n", i);//i=8
}
int main() {
f(8);
return 0;
}
/*
注意的问题:
全局变量和局部变量命名冲突的问题
在一个函数内部如果定义的局部变量的名字和全局变量名
一样时,局部变量会屏蔽掉全局变量;
*/
一定要理解下面这段程序为什么报错,因为c语言是顺序执行,全局变量下面的代码都能使用这个全局变量,而全局变量上面的代码想用这个全局变量就会识别不到从而出现错误!
错误的代码例子:
#include
void g() {
printf("k=%d\n", k);//k 为声明的标识符 所以报错!
}
int k = 1000;
void f(void) {
g();
printf("k=%d\n", k);
}
int main() {
f();
return 0;
}
/*
一定要明白这个程序为什么是错的
也要明白把第3行的代码放在第7行的后面,为什么程序就ok了!
*/
正确的代码例子:
#include
int k = 1000;
void g() {
printf("k=%d\n", k);//k=1000
}
void f(void) {
g();
printf("k=%d\n", k);//k=1000
}
int main() {
f();
return 0;
}
/*
一定要明白这个程序为什么是错的
也要明白把第3行的代码放在第7行的后面,为什么程序就ok了!
*/
指针热身程序1(超重点):
# include
int main() {
int* p;//p是变量名字,int*表示p存放的是int类型变量的地址
int i = 3;
p = &i;//正确
//p = i;//错误的,因为类型不一致,p只能存放int类型的变量地址,不能存放int类型变量的值
//p = 55;//错误的,原因同上
printf("%d\n", *p);
return 0;
}
/*
int * p;
int * 是数据类型 p是变量名
*/
指针热身程序2(超重点):
# include
//指针热身程序2
int main() {
int * p;//p是变量名字,int*表示p存放的是int类型变量的地址
//int * p;不表示定义了一个名字叫做*p的变量
//int * p;应该这样去理解:p是变量名,p变量的数据类型是int *类型
// 所谓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
或者说: 在所有出现*p的地方都可以替换成i
在所有出现i的地方都可以替换成*p
printf("%d\n", *p);//3 i也是3 所以说等同于
printf("%d\n", p);//输出是&i的地址 因为p存放了i的地址
*p 就是以p的内容为地址的变量
*/
j = *p;//等价于j=i;
printf("i=%d,*p=%d,j=%d\n", i, *p, j);//i=3,*p=3,j=3
return 0;
}
指针就是地址,地址就是指针
地址就是内存单元的编号(以ASCLL码为准)
指针变量是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样指针的本质就是一个操作受限的非负整数
加深指针理解的图(纯手工):
配上图的代码:
假设我们定义了一个指针变量 int *p;
1、 p:p是一个指针变量的名字,表示此指针变量指向的内存地址,如果使用%p来输出的话,它将是一个16进制数
2、 *p:*p表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量。3、 &p:&是取地址运算符,&p就是取指针p的地址。等会,怎么又来了个地址,它到底和p有什么区别?
p和&p区别在于指针p同时也是个变量,既然是变量,编译器肯定要为其分配内存地址,无论是普通的变量还是指针变量在内存中都有一个地址 ,就像程序中定义了一个int型的变量i,编译器要为其分配一块内存空间一 样。而&p就表示编译器为变量p分配的内存地址,而因为p是一个指针变量,这种特殊的身份注定了它要指向另外一个内存地址,程序员按照程序的需要让它指向一个内存地址,这个它指向的内存地址就用p表示。而且,p指向的地址中的内容就用*p表示。
# include
int main() {
int * p;
int i = 3;
p = &i;
printf("%p\n", &i);//输出:000000F36DDCF994 输出的是i变量的地址
printf("%p\n", p);//输出:000000F36DDCF994 通过指针变量p输出i变量的地址
printf("%p\n", &p);//输出:000000F36DDCF978 指针变量p本身自己的地址
printf("%d\n", *p);//输出:3 也就说*p和i是等价的 它们之间可以替换
printf("%d\n", i);//输出:3 也就说*p和i是等价的 它们之间可以替换
printf("%p\n", &*p);//输出:000000F36DDCF994 输出p的指向i变量3的地址(也就是i本身的地址,也可以说是p的地址,因为p保存了i的地址)
return 0;
}
表示一些复杂的数据结构
快速的传递数据,减少了内存的耗用
使函数返回一个以上的值
能直接访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
总结:指针是C语言的灵魂
地址:
内存单元的编号
地址是从零开始的非负整数
范围:4G[0--4G-1](09年的视频,现在不会这么少了)
指针:
指针就是地址,地址就是指针
指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样
指针的本质就是一个操作受限的非负整数
认真思考这句话待补充:
一个变量的(内存)地址称为该变量的“指针”,通过指针能找到以它为地址的内存单元。而指针变量是用来存放另一个变量的地址的(即指针)。
指针:
# include
int main() {
int a = 3;
printf("%d\n",&a);//输出:1664219524 则存放变量a的内存单元的地址 &a 被称为指针(地址)。
}
指针变量:
# include
int main() {
int a = 3;
int* p;//定义一个整形的指针变量p
p = &a;//指针变量存放的是另一个变量的地址
printf("%d\n",&a);//输出:1664219524 则存放变量a的内存单元的地址 &a 被称为指针(地址)。
printf("%d\n", p);//输出:1664219524
//因为p存放了a的地址,所以p指向普通变量a
//*p就等于a 互相等价
printf("%d\n",a);//3
printf("%d\n",*p);//3
//*p 就是以p的内容为地址的变量(也就是a的值,p存放了a的地址,p的地址就是a的地址,所以是3)
}
核心:定义了指针变量,一定要给它进行赋值(分配地址),之后才可以使用指针变量,否则就会出现程序异常,否则会出现以下这些错误:
常见错误1:
# include
//指针常见错误解析1
int main() {
int* p;
int i = 5;
//*p = i;//错误 使用未初始化的局部变量p 因为没有赋值 所以p里面是垃圾值 垃圾值也有地址
//相当于把i赋值给一个不属于自己的单元就是错误了
p = &i;//这样就正确了 p保存了i的地址 p指向i *p==i
printf("%d\n", *p);//5
}
常见错误2:
# include
//指针常见错误解析2
int mian() {
int i = 5;
int* p;
int* q;
p = &i;
//*q = p;//错误 = 无法从int *类型转换为int (语法编译时会报错)
//*q = *p;错误
//p = q;//错误 q是垃圾值,q赋值给p,p也变成垃圾值。
printf("%d\n",*q);//错误 12行
/*
q的空间是属于本程序的,所以本程序可以读写q的内容,
但是如果q内部是垃圾值,则本程序不能读写* q的内容
, 因为此时*q所代表的内存单元的控制权限并没有分配给本程序
所以本程序运行到12行时就会出错
*/
}
个人举例子:
# include
int main() {
int* p;
printf("%d\n", &p);//正确 p是垃圾值,但是可以访问和获取他的垃圾值,垃圾值也是地址。
printf("%d\n", *p);//错误 *p也是垃圾值,*p代表另一个单元,这个单元的控制权限没有给你。
//要是p存放了某个变量的地址,*p就可以使用了 因为p会指向那个变量 从而*p==那个被保存的变量。
}
1、不能完成互换功能:
//1、不能完成互换功能
void huhuan1(int a, int b) {
int t;
t = a;
a = b;
b = t;
return ;
}
int main() {
int a = 3;
int b = 5;
int t;
int i;
huhuan1(a, b);
printf("a=%d b=%d\n", a, b);//a=3 b=5
return 0;
}
2、不能完成互换功能:
//2、不能完成互换功能
void huhuan2(int *p, int *q) {
int *t;
t = p;
p = q;
q = t;
return ;
}
int main() {
int a = 3;
int b = 5;
huhuan2(&a, &b);
printf("a=%d b=%d\n", a, b);//a=3 b=5
return 0;
}
对于第2个不能完成互换功能的程序进行分析:
传参时的图:
huhuan2(&a, &b);
void huhuan2(int *p, int *q)
进行void huhuan2(int *p, int *q)运算时的图:
超重点(必须理解 ):
a 和b是静态的局部变量,他的空间一旦分配好,到程序结束还是在这个地址,不会因为被调用函数修改地址而影响mian函数里面的a和b的地址,即使p和q互换了,也不影响 a,b,所以仍然是输出3,5!
程序一旦执行就会给a b 和形参指针变量 p q进行分配四块独立的存储空间
第2个程序的拓展:
//不能完成互换功能
void huhuan2(int *p, int *q) {
int *t;//如果要互换p和q的值,则t必须是int*类型,不能是int ,否则会报错
t = p;
p = q;
q = t;
printf("p=%d q=%d\n",p,q);
printf("*p=%d *q=%d\n", *p, *q);
return;
}
int main() {
int a = 3;
int b = 5;
huhuan2(&a, &b);
printf("a=%d b=%d\n",&a,&b);//
return 0;
}
/*
这只是互换了地址,互换地址不会改变值 p是地址,*p是值
p=-1060111436 q=-1060111468
*p=5 *q=3
a=-1060111468 b=-1060111436
*/
#include
//可以完成互换功能
void huhuan3(int* , int* );//声明函数可以不写形参
int main() {
int a = 3;
int b = 5;
huhuan3(&a, &b);//实参 a的地址 b的地址
printf("a=%d b=%d\n", a, b); //a = 5 b = 3
return 0;
}
void huhuan3(int* p, int* q) {//形成:指针变量 p 和q
int t;//如果要互换*p和*q的值,则t必须定义成int,不能定义成int * ,否则语法会错误
t = *p;//p是int * 类型, *p是int类型
*p = *q;
*q = t;
return;
}
/*
实参传递地址给指针变量相当于
int *p=&a; 那么*p就是a的值
int *q=%b; 那么*q就是b的值
互换pq改变不了值,相反互换*q,*p可以改变值 因为p,q是代表地址,修改地址改变不了值
*/
1、乘法
2、定义指针变量
int *p;
//定义了一个名字叫p的变量,int*表示只能存放int变量的地址3、指针运算符
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量
则*p表示以p的内容为地址的变量
main函数里面的a,b都是main函数里面的静态局部变量
swap_1里面i,j,t都是swap_1里面的局部变量
程序开始执行系统会为a b i j t 自动分配静态存储空间 一旦程序结束会自动释放静态存储空间
即使实参和形参的变量名一样但是他们仍然不同的变量,因为作用域不一样,都是在不同的函数里面当局部变量。
在同一个函数下就不能出现两个变量a,因为重定义。
1、实参必须为普通变量的地址
2、形参必须是指针变量
3、在被调函数中通过
*形参名=.....;
的方式就可以修改主调函数相关变量的值
代码例子:
#include
void swap_2(int* p, int* q) {//形参 是p和q接收a和b的地址,不是*p *q
int t;
t = *p;
*p = *q;
*q = t;
}
int main() {
int a = 3;
int b = 5;
swap_2(&a, &b);//实参
printf("a=%d,b=%d\n",a,b);//a=5,b=3
return 0;
}
指针和一维数组
一维数组名
一维数组名是一个指针常量
它存放的是一维数组第一个元素的地址
代码例子:
#include
int main() {
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
//也可以理解为有3行,元素为4个的一维数组 (3个一维数组)
int b[5];
//a = b;//错误 a是常量 常量不可以赋值常量
printf("%#x\n",&a[0]);//以十六进制输出
printf("%#x\n", a);//
return 0;
}
/*
输出结果:
0xe118f758
0xe118f758
总结:
一维数组名
一维数组名是一个指针常量
它存放的是一维数组第一个元素的地址
*/
如果p是指针变量,则p[i]永远等价于*(p+i)
#include
//数组的空间是连续的
int main() {
int a[5] = { 1,2,3,4,5 };
int* p;
p = a;//a是int * 类型 p = &(a[0]); 因为a是数组名 数组名是数组元素的第一个地址
printf("%d\n",a[0]);//1
printf("%d\n", *p);//1
printf("%d\n", *p);//输出1 代表p[0]
printf("%d\n", p[3]);//输出4
printf("%d\n", *(p + 1));//输出2 等价于p[1]
}
这集郝斌老师的视频没找到,以下是别人的总结:
看其他老师的个人总结:
一定要结合上面去看:
#include
int main() {
//数组元素之间的地址是相连的
int a[5] = { 10,20,30,40,50 };//int型占据4个字节
int* p;
int i;
for (i = 0; i < 5; i++)
{
printf("a[i]=%d %d\n", i, &a[i]);
}
printf("a代表的地址:%d\n",a);//数组名代表数组的首地址(首元素地址)即&a[0]
printf("a+1代表的地址:%d\n", a+1);//即&a[1]
printf("a+2代表的地址:%d\n", a+2);//即&a[2]
printf("a+3代表的地址:%d\n", a+3);//即&a[3]
printf("a+4代表的地址:%d\n", a+4);//即&a[4]
printf("\n");
printf("&a[2]+2的地址:%d\n", a[2 + 2]);//即&a[4]
printf("%d\n", *a);//输出a[0]
printf("%d\n", *(a+1));//输出a[1]
}
/*
输出结果:
a[i]=0 1229978760
a[i]=1 1229978764
a[i]=2 1229978768
a[i]=3 1229978772
a[i]=4 1229978776
a代表的地址:1229978760
a+1代表的地址:1229978764
a+2代表的地址:1229978768
a+3代表的地址:1229978772
a+4代表的地址:1229978776
&a[2]+2的地址:50
10
20
*/
如果一个函数要处理一个一维数组,则需要接收该数组的那些信息
需要两个参数:
数组第一个元素的地址
数组的长度
确定一个一维数组需要几个参数_1:
//确定一个一维数组需要几个参数_1
//f函数可以输出任何一个一维数组的内容
void f(int *pArr,int len) {
//*pArr是1
//*(pArr+1)是2
int i;
for (i = 0; i < len;i++) {
printf("%d\n",*(pArr+i));
printf("\n");
}
}
int main() {
int a[5] = { 1,2,3,4,5 };
int b[6] = { -1,-2,-3,-4,-5,-6 };
int c[100] = { 1,99,22,33 };//后面没有赋值的都是0
f(a,5);// a是int *类型 因为a存放了a[0]的地址所以是int *类型
}
画图理解:
确定一个一维数组需要几个参数_2:
//确定一个一维数组需要几个参数_2
void f(int* pArr, int len) {
pArr[3] = 88;//等价于 *(pArr+3)
}
int main() {
int a[6] = {1,2,3,4,5,6};
printf("%d\n", a[3]);//4
f(a, 6);
printf("%d\n", a[3]);//88
printf("%d\n", &a[3]);//a[3]的地址
}
/*
一定要明白 pArr[3]和 a[3] 是同一个变量
*/
画图理解:
确定一个一维数组需要几个参数_3:
#include
//确定一个一维数组需要几个参数_3
void f(int* pArr, int len) {
//*pArr是1
//*(pArr+1)是2
int i;
for (i = 0; i < len;i++) {
printf("%d ",pArr[i]);//*(pArr+i)等价于PArr[i] 也等价于a[i] 也等价于*(a+i)
//输出1 2 3 4 5
printf("\n");
}
}
int main() {
int a[5] = { 1,2,3,4,5 };
int b[6] = { -1,-2,-3,-4,-5,-6 };
int c[100] = { 1,99,22,33 };//后面没有赋值的都是0
f(a, 5);
return 0;
}
void f(int* pArr, int len) {
pArr[2] = 10;//pArr[2]==*(pArr+2)==*(a+2)==a[2]
}
int main() {
int a[5] = { 1,2,3,4,5 };
//a = &a[2];//错误 因为a是常量 常量不能放在等号左边
//a == &a[0];
//*a==a[0];
printf("%d\n",a[2]);
f(a,5);
printf("%d\n", a[2]);
return 0;
}
指针变量的运算
指针变量不能相加,不能相乘,也不能相除
如果两个指针变量指向的是同一块连续空间中的不同的存储单元
则这两个指针变量才可以加减
代码例子:
#include
int main() {
int i = 5;
int j = 10;
int* p = &i;
int* q = &j;
//p-q 没有实际意义 因为i和j不是连续的地址空间
int a[5];
p = &a[1]; //p q指向一个连续空间中的不同的存储单元
q = &a[4];
printf("p和q所指向的单元相隔%d个单元\n", q - p);//p和q所指向的单元相隔3个单元 4-1=3
//相当于植树问题,第一棵树和第四棵树之间有三个间隔
return 0;
}
一个指针变量到底占几个字节
预备知识:
sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子:sizeof(int)=4; sizeof(char)=1;
sizeof(double)=8;sizeof(变量名)
功能:返回值是该变量所占的字节数假设p指向char类型变量(1个字节)
假设q指向int类型变量(4个字节)
假设r指向double类型变量(8个字节)
p q r 本身所占的字节数是一样的总结:
一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占8个字节(64位电脑)、32位电脑占4个字节。一个变量的地址是用该变量的首字节地址来表示的
代码例子:
#include
int main() {
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));//指针变量
//64位就输出 8 8 8 郝斌老师的应该是32位输出4 4 4
return 0;
}
传统数组的缺点:
1、数组的长度必须事先指定,且只能是常数,不能是变量
例子:
int a[5];//ok
int len=5; int a[len];//错误 不能是变量
2、传统形式定义的数组,该数组的内存程序员无法手动释放。
在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,直到函数运行完毕时,数组空间才会释放。
代码例子:
#include
void f(void) {
int a[5] = { 1,2,3,4,5 };//这20个字节的存储空间程序员无法手动编程释放它,
//它只能在本函数运行完毕时由系统主动释放 int--4个字节 4*5=20
}
int main() {
return 0;
}
3、数组的长度一旦定义,其长度就不能再更改
数组的长度不能在函数运行的过程中动态的扩充或缩小。
4、A函数定义的数组,在A函数运行期间可以被其他函数使用,
但A函数运行完毕之后,A函数中的数组将无法再被其他函数使用。
函数使用
传统方式定义的数组不能跨函数使用
代码例子:
#include
void g(int* p, int len) {
p[2] = 88;// p[2]==a[2];
}
void f(void) {
int a[5] = { 1,2,3,4,5 };//这20个字节的存储空间程序员无法手动编程释放它,
//它只能在本函数运行完毕时由系统主动释放 int--4个字节 4*5=20
g(a, 5);
printf("%d\n",a[2]);
}
int main() {
f();
return 0;
}
动态数组很好解决了传统数组的这4个缺陷
传统数组也叫静态数组
malloc函数返回这块内存的首地址,你需要一个指针来接受这个地址。但由于函数的返回值是void *类型,所以必须强制转换成你所接受的类型。也就是说这块内存将来你要来存储什么类型的数据:
比如 :
int * p=(int *)malloc(4);
在堆上分配4个字节的内存,返回这块内存的首地址,把地址强制转换成int *类型后,给int *类型的的指针变量p;同时告诉我们这块内存将来存储int类型的数据,也就是说你只能通过指针变量p来操作这块内存。这块内存本身没有名字,对他的访问是匿名访问。
int *p;//p是变量名 int*表示p存放的是int类型变量的地址
(int *)malloc(4);---是int * 相当于存放了malloc分配的地址,再赋值(返回)给p,就不会有垃圾值了(个人理解,不一定对)
int *p=(int *)malloc(4*len);
(int*)把第一个字节的地址当成 int 类型地址 意味着这个变量占据4个字节,按照4个划分
(char *)把第一个字节的地址当成 char 类型地址 意味着这个变量占据1个字节,按照1个划分
(double *)把第一个字节的地址当成double类型地址 意味着这个变量占据8个字节,按照8个划分
(int *)是声称后面这个数组含有len个的元素的数组每一个类型都是int类型
#include
#include //不能省
int main() {
int i = 5;//分配了4个字节 静态分配 5行
int* p = (int*)malloc(4);//6行
/*第一、malloc 函数返回的是 void * 类型,如果你写成:p = malloc (sizeof(int));
则程序无法通过编译,
报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。*/
/*
1、要使用malloc函数,必须添加malloc.h这个头文件
2、malloc函数只有一个形参,并且形参是整型
3、4表示请求系统为本程序分配4个字节
4、malloc函数只能返回第一个字节的地址
5、(64位计算机)6行分配了12个字节,p变量占8个字节,p所指向的内存也占用4个字节
(32位计算机)6行分配了8个字节,p变量占8个字节,p所指向的内存也占用4个字节
6、p本身所占的内存是静态分配,p所指向的内存是动态分配的
*/
* p = 5;//*p代表的就是一个int 变量,只不过*p这个整形变量的内存分配方式和5行的i变量的分配方式不同
free(p);//free(p)表示把p所指向的内存给释放掉
//p本身的内存是静态的,不能由程序员手动释放,p本身的内存只能在在p变量所在的函数运行终止时由系统自动释放
//printf("%d\n", sizeof(p));
return 0;
}
/*
malloc是memory(内存)allocate(分配)的缩写
*/
#include
#include
void f(int *q) {
*q = 200; //把p变量的内容发给q,pq的内容是一样的,q是p的内容拷贝 p内容是存放这4个字节的内容
//q也存放这四个字节的地址,q就指向这四个字节的地址 *q就代表这4个字节
}
int main() {
int* p = (int*)malloc(sizeof(int)); //p指针指向开辟的新的空间的起始地址
//sizeof(int)返回值是int所占的字节数
//p是静态分配的 malloc是动态分配的
*p = 10;
printf("%d\n",*p);//10
f(p);//p是int *类型, &p是int **类型,p==&malloc
printf("%d\n",*p);//200
return 0;
}
图解:
加入free释放空间:
#include
#include
void f(int *q) {
*q = 200; //把p变量的内容发给q,pq的内容是一样的,q是p的内容拷贝 p内容是存放这4个字节的内容
//q也存放这四个字节的地址,q就指向这四个字节的地址 *q就代表这4个字节
free(q);//把q所指向的内存释放起来
}
int main() {
int* p = (int*)malloc(sizeof(int)); //p指针指向开辟的新的空间的起始地址
//sizeof(int)返回值是int所占的字节数
//p是静态分配的 malloc是动态分配的
*p = 10;
printf("%d\n",*p);//10
f(p);//p是int *类型 ,p=&malloc分配的地址
printf("%d\n",*p);//-572662307 因为p指向的内存已经被释放了
return 0;
}
图解:
这就是为什么把指针变量p传给指针变量q可以的理由:
#include
#include
int main() {
int* p;
int i = 10;
p = &i;
int* q;
printf("%d\n", p);
printf("%d\n", &i);
q = p;
printf("%d\n", q);
}
/*
输出:
475003972
475003972
475003972
*/
动态一维数组的构造:
#include
#include
int main() {
int a[5];//如果int占4个字节的话,则本数组总共包含有20个字节,每四个字节被当做了一个int变量来使用
int len;
int* pArr;
int i;
//动态的构造一维数组
printf("请输入你要存放的元素的个数:\n");
scanf("%d", &len);
pArr = (int *)malloc(4 * len);//动态构造一个数组,因为pArr存放第一个元素的地址
//动态构造了一个一位数组,该一维数组的长度是len,
//数组名是pArr,该数组的每个元素是int类型 类似 int pArr[len];
//对一位数组进行操作,对动态一维数组进行赋值
printf("请输入一维数组的值:\n");
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
void g(int i) {
int a[5];
printf("%d\n", i);
}
void f(void) {
g(5);
printf("hh\n");
}
int main() {
f();
return 0;
}
/*
输出:
5
hh
main函数调用f,f调用g函数,执行完g函数返回执行hh,再返回到main函数,结束调用f、g函数就出栈,内存就是放了,main函数return后也会自动释放内存空间。
*/
图:
#include
int main() {
int i = 10;
int* p = &i;
int** q = &p;
int*** r = &q;
//r=&p;//错误因为r是int ***类型,r只能存放int **类型变量的地址
printf("i=%d\n",***r);//10
return 0;
}
图理解:
代码加深理解:
#include
void f(int ** q) {
//*q就是p 存放p变量的地址,它的内容就是p的地址
//q存放了p的地址,则q指向p *q就等价于p
**q = 3;
printf("q=%d\n",q);
printf("*q=%d\n", *q);
}
void g() {
int i = 10;
int* p = &i;
printf("&p=%d\n", &p);//p存放i的地址所以和&i一样
printf("p=%d\n", p);
printf("&i=%d\n", &i);
f(&p);//p是 int *类型,&p是int **类型(因为p是int *类型,
//你想保存他的地址只能是int * *类型)
printf("i=%d\n", i);
printf("*p=%d\n", *p);
}
int main() {
g();
return 0;
}
/*
其实很好理解可以看看这个例子:
int i=3;
int *p=&i;
//p等于&i;---- *p等于i
int * *q=&p;
//可以理解为(int*)*q 括号里面是相当于把int *看成整体 括号外的*可以理解为int *
//q等于&p------*q等于p
*/
运行结果:
#include
void f(int **q) {//q是个指针变量,无论q是什么类型的指针变量,都只占64位电脑:8个字节,32位4个字节
int i = 5;
//*q等价于p q和**q都不等价于p
//*q = i;//错误 因为*q=&i 等价于p=&i;
*q = &i;//等价于p=&i;
}
int main() {
int* p;
f(&p);
printf("%d\n", *p);//5 本语句语法没问题,但逻辑上有问题
//因为调用f函数结束后,i和q的内存空间会被系统释放掉,
//比如:原本你手上有个钥匙是一个房子的,但现在房子卖了,
// 虽然钥匙在你手上,但是你不应该去开那个门,访问权限就没了
printf("%d\n", *p);
printf("%d\n", *p);
return 0;
}
/*
指针 静态变量不能跨函数使用详解【重点】
输出结果:
5
-858993460
-858993460
之所以第一个输出能输出5是因为,f函数结束前,p保存了i的地址。
后面的两次因为f函数已经结束,函数内的地址,已经被释放了,所以输出垃圾值
*/
#include
#include
void f(int **q) {//q是个指针变量,无论q是什么类型的指针变量,都只占64位电脑:8个字节,32位4个字节
*q = (int*)malloc(sizeof(int));//sizeof(数据类型)返回值是该数据类型所占的字节数
//等价于p=(int *)malloc(sizeof(int));
//*q = 5;//错误相当于 p=5; p只能存放地址
**q = 5;//*p等于5
}
int main() {
int* p;
f(&p);
printf("%d\n", *p);//5
return 0;
}
/*
指针 动态变量可以跨函数使用详解【重点】
因为是手动分配地址,程序员自己用free去释放,
哪怕f函数调用结束,系统也不会自己释放该内存。
*/
为什么需要结构体:
为了表示一些复杂的事务。而普通的基本类型无法满足实际要求。
什么叫结构体:
把一些基本类型数据组合在一起形成的一个新的复合数据类型,这个叫结构体 。
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;
赋值和初始化
如何取出结构体变量中的每一个成员
结构体变量的运算
结构体变量和结构体指针变量作为函数参数传递的问题
举例:
动态构造存放学生信息的结构体数组
链表:
赋值和初始化:
定义的同时可以整体赋初始值
如果定义完之后,则只能单个的赋值。
代码例子:
#include
struct Student//定义一个数据类型,这个数据类型是一个结构体类型
{
//成员:
int age;
float score;
char sex;
};
int main() {
struct Student st = {22,66.6,'F'};//初始化 定义的同时赋初值
//struct Student 相当于是一个数据类型 st变量名
struct Student st2;
st2.age = 10;
st2.score = 77.7;
st2.sex = 'F';
printf("%d %f %c\n", st.age, st.score, st.sex);
printf("%d %f %c\n", st2.age, st2.score, st2.sex);
return 0;
}
1、结构体变量名.成员名
2、指针变量名- > 成员名(第二种方式更常用)
指针变量名- > 成员名 在计算机内部会被转化成(*指针变量名).成员名方式来执行
*指针变量就是那个变量,所以说这两种方式是等价的
struct Student st = {22,66.6,'F'};
struct Student *p=&st;//&st不能改成st
p->age = 88;//(*p)=st
例子:
#include
struct Student
{
int age;
float score;
char sex;};
int main() {
struct Student st = {22,66.6,'F'};//初始化 定义的同时赋初值
//struct Student 相当于是一个数据类型 st变量名
st.age=10;//第一种方式
struct Student *p=&st;//&st不能改成st
p->age = 88;//第二种方式
return 0;
}1、 p->age 在计算机内部会被转化成(*p).age,没有什么为什么,这就是->的含义,这是一种硬性规定
2、所以 p->age 等价于(*p).age 也等价于 st.age
3、我们之所以知道p->age 等价于st.age,是因为p->age是被转化成(*p).age来执行的。
4、p->age 的含义:
p所指向的那个结构体变量中的age这个成员
辅助1、2、3理解上面解析:
辅助4去理解:
代码输出总结:
#include
struct Student
{
int age;
float score;
char sex;
};
int main() {
struct Student st = {22,66.6F,'F'};//初始化 定义的同时赋初值
//struct Student 相当于是一个数据类型 st变量名
st.age=10;//第一种方式
struct Student *p=&st;//&st不能改成st
p->score = 88.8f;//第二种方式 66.6在c语言中默认是double类型,如果希望一个实数是float类型,则必须在末尾加f或F
printf("%d %f\n", st.age, p->score);
return 0;
}
/*
输出结果:
10 88.800003
浮点数不能准确存储,有些可以,有些不可以,丢失精度
*/
#include
struct Student
{
int age;
char sex;
char name[100];
};//分号不能剩
int main() {
struct Student st = {22,'F',"小陈"};//初始化 定义的同时赋初值
//struct Student 相当于是一个数据类型 st变量名
struct Student* p = &st;
printf("%d %c %s\n", st.age,st.sex,st.name);
printf("%d %c %s\n", p->age, p->sex, p->name);
//p->age 转化成 (*p).age 等价于st.age ----*p==st;
return 0;
}
/*
*输出结果:
22 F 小陈
22 F 小陈
*/
结构体变量和结构体指针变量作为函数参数传递问题
推荐使用结构体指针变量作为函数参数来传递。
#include
#include
struct Student
{
int age;
char sex;
char name[100];
};
void InputStudent(struct Student* p) {//p---64位电脑占据8个字节,32位占据4个字节
(*p).age = 10; //*p==st
strcpy(p->name, "张三");
//p->sex = 'F';
(*p).sex = 'F';//和上一句代码等价
}
void OutputStudent(struct Student stu) {
printf("%d %c %s\n", stu.age, stu.sex, stu.name);
//实参把st给了stu 相当于stu拷贝了st的内容,通过stu.成员名输出,
//和st.成员名一样
}
int main() {
struct Student st;
InputStudent(&st);//对结构体变量输入 必须发送st的地址
OutputStudent(st);//对结构体变量输出 可以发送st的地址也可以发送st的内容
//printf("%d %c %s\n", st.age, st.sex, st.name);
return 0;
}
/*
* 本函数无法修改主函数st的值,所以本函数是错误的
void InputStudent(struct Student* p) {
stu.age=10;
strcpy(stu.name,"张三");//不能写成stu.name="张三";
stu.sex='F';
}
*/
#include
#include
struct Student
{
int age;
char sex;
char name[100];
};
void InputStudent(struct Student* p) {//p---64位电脑占据8个字节,32位占据4个字节
(*p).age = 10; //*p==st
strcpy(p->name, "张三");
//p->sex = 'F';
(*p).sex = 'F';//和上一句代码等价
}
void OutputStudent(struct Student * stu) {
printf("%d %c %s\n", (*stu).age, (*stu).sex, (*stu).name);
//等价于:
//printf("%d %c %s\n", stu->age, stu->sex, stu->name);
//也等价于:
//printf("%d %c %s\n", st.age, st.sex, st.name);
}
int main() {
struct Student st;
InputStudent(&st);//对结构体变量输入 必须发送st的地址
OutputStudent(&st);//对结构体变量输出 可以发送st的地址也可以发送st的内容,
//但为了减少内存的耗费,也为了提高执行速度,推荐发送地址
//printf("%d %c %s\n", st.age, st.sex, st.name);
return 0;
}
/*
示例:
发送地址还是发送内容
目的:
指针的优点之一:
快速的传递数据,
耗用内存小,
执行速度快。
*/
结构体变量不能相加,不能相减,也不能相互乘除。
但结构体变量可以相互赋值。
struct Student
{
int age;
char sex;
char name[100];};
int main(){
struct Student st1,st2;
st1+st2; st1*st2; st1/st2; //都是错误的
st1=st2 或者 st2=st1 都是正确的
}
重点要自己写,读懂程序
#include
void sort(int * a,int len) {
int i, j,t;
for (i = 0; i < len - 1; i++) {//表示整体要比较几次
for (j = 0; j< len - 1 - i;j++) {//具体的某一次比较
if (a[j] > a[j + 1]) {//大于表示升序,小于表示降序
t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
}
int main() {
int a[6] = { 10,2,8,-8,11,0 };
int i;
sort(a, 6);//a是数组首个元素的地址
for (i = 0; i < 6; i++) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
/*
第一次比较
i=0 0<5
j=0 j<5 j++
a[0] a[1]
a[1] a[2]
a[2] a[3]
a[3] a[4]
a[4] a[5]
此时a[5]就是第一轮比较的最大值
第二次比较
i=1 i<5
j=0 j<4 j++
a[0] a[1]
a[1] a[2]
a[2] a[3]
a[3] a[4]
此时a[4]就是第二轮比较的最大值
第三次比较
i=2 i<5
j=0 j<3 j++
a[0] a[1]
a[1] a[2]
a[2] a[3]
此时a[3]就是第三轮比较的最大值
第四次比较
i=3 i<5
j=0 j<2 j++
a[0] a[1]
a[1] a[2]
此时a[2]就是第四轮比较的最大值
第五次比较
i=4 i<5
j=0 j<1 j++
a[0] a[1]
此时a[1]就是第四轮比较的最大值
i=5 i<5
j=0 j<1 j++
i不小于5循环结束
printf("%d\n", a);
printf("%d\n", &a[0]);
-1546650920
-1546650920
*/
动态构造存放学生信息的结构体数组
动态构造一个数组,存放学生的信息
然后按分数排序输出
# include
# include
struct Student
{
int age;
float score;
char name[100];
};
//输入
void input(struct Student* z, int len) {
int i;
for (i = 0; i < len; i++) {
printf("请输入第%d个学生的信息:", i + 1);
printf("age=");
scanf("%d", &z[i].age);
printf("name=");
scanf("%s", z[i].name);//name是数组名,本身就已经是数组首元素地址,所以不用加&
printf("score=");
scanf("%f", &z[i].score);
}
}
//排序
void sort(struct Student* z, int len) {
int i, j;
struct Student t;
for (i = 0; i < len - 1; i++) {
for (j = 0; j < len - 1 - i; j++) {
if (z[j].score > z[j + 1].score) {//大于是升序,小于是降序
t = z[j];
z[j] = z[j + 1];
z[j + 1] = t;
//这里是互换位置,把学生信息交换位置,不是互换成绩,
}
}
}
}
//输出
void e(struct Student* z, int len) {
int i;
for (i = 0; i < len; i++) {
printf("第%d个学生的信息:", i + 1);
printf("age= %d\n", z[i].age);
printf("name= %s\n", z[i].name);
printf("score= %f\n", z[i].score);
printf("\n");
}
}
int main() {
int len;
struct Student * p;
printf("请输入学生的个数:\n");
printf("len=");
scanf("%d", &len);
//动态的构造一维数组 p就相当于是数组名了
//len是有多少个元素 struct Student*是表示这些元素都是struct Student 类型的
p = (struct Student *)malloc(len *sizeof(struct Student));
//函数实现
input(p, len);
sort(p, len);
e(p, len);
return 0;
}
/*
//malloc创建一个结构体数组,p指向这个数组的首地址,相当于数组名
所以p[0].age就符合,结构体变量名.成员名的规定,而这是一个结构体里的整型变量,所以输入时需要&符号
name是数组所以不用
当然自己试过
int main(){
int a[2];
scanf("%d",a);//不会报错
printf("%d\n",a[0]);
}
什么是枚举
把一个事物所有可能的取值一一例举出来
怎么使用枚举:
见下面代码
枚举的优缺点
代码更安全,
书写麻烦
//只定义一个数据类型,并没有定义变量,
//该数据类型的名字是 enum WeekDay
enum WeekDay {//enum是枚举的意思
MomDay=1,//从1开始 因为枚举值默认从0开始
TuesDay,
WedresDay,
ThursDay,
FriDay,
SaturDay,
SunDay,//星期日
};
int main() {
//int day;//day定义int类型不合适
//enum WeekDay day = 1;//不能写数字,因为MomDay的值本质就是1,如果赋值1逻辑就错了
//day的值只能是enum WeekDay里面的值 不能把数字直接赋值给day
enum WeekDay day = MomDay;
printf("%d\n", day);//输出1
return 0;
}
#include
//枚举的应用!
enum WeekDay {
MomDay ,//因为枚举值默认从0开始
TuesDay,
WedresDay,
ThursDay,
FriDay,
SaturDay,
SunDay,//星期日
};
void f(enum WeekDay day) {//本函数的目的只是期望接受0-6之间的数字,将形参i定义为枚举
switch (day)
{
case 0:
printf("MomDay!\n");
break;
case 1:
printf("TuesDay!\n");
break;
case 2:
printf("WedresDay!\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() {
f(FriDay);//虽然FriDay本质上就是4;f(4)就是错的,也不能写成f(10;)
//星期五,但是从0开始
}
/*
FriDay 作为实参传给形参enum WeekDay day
那么此时day就是4
接下来就是用到switch里面
*/
0表示正号 1表示负号(相反,计算机是非0即真)
最高位是符号位,其余n-1位表示数值
正数的原码、补码、反码,是一样的
负数的反码是在负数原码的基础上按位取反
负数的补码在负数的反码的基础上末位+1
假设n=8
+1 原 0 0 0 0 0 0 0 1
+1 反 0 0 0 0 0 0 0 1
+1 补 0 0 0 0 0 0 0 1
-1 原 1 0 0 0 0 0 0 1
-1 反 1 1 1 1 1 1 1 0
-1 补 1 1 1 1 1 1 1 1
移码就是在补码的基础上符号位取反
【+1】补 0 0 0 0 0 0 0 1 【-1】 补 1 1 1 1 1 1 1 1
【+1】移码 1 0 0 0 0 0 0 1 【-1】 移码 0 1 1 1 1 1 1 1
补码的补码等于他的原码
例题:某整数的16位补码为FFFFH,则该数的十进制值为-1
FFFF=15(H) 16位的一位是2进制的4位 15等于1111
1 1 1 1 1 1 1 1 1 1 1 1
反1 0 0 0 0 0 0 0 0 0 0 0
补 1 0 0 0 0 0 0 0 0 0 0 1
答案:-1
链表
算法:
通俗定义:
解题的方法和步骤
狭义定义:
对存储数据的操作对不同的存储结构,要完成某一个功能所执行的操作是不一样的
比如:
要输出数组中所有的元素的操作和
要输出链表中所有元素的操作肯定是不一样的
这说明:
算法是依附于存储结构的
不同的存储结构,所执行的算法是不一样的。
广义定义:
广义的算法也叫泛型
无论数据是如何存储的,对该数据的操作都是一样的我至少可以通过两种数据结构来存储数据
数组:
优点:
存取速度快
缺点:
需要一个连续的很大的内存
插入和删除元素的效率很低链表:
专业术语:
首节点
存放第一个有效数据的节点
尾节点
存放最后一个有效数据的节点
头结点
头结点的数据类型和首节点的类型是一模一样的
头结点是首节点前面的那个节点
头结点并不存放有效数据
设置头结点的目的是为了方便对链表的操作
头指针
存放头结点地址的指针变量
确定一个链表需要一个参数
头指针
优点:
插入删除元素效率高
不需要一个连续的很大的内存
缺点:
查找某个位置的元素效率低
# include
# include
# include
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=05&7=5 21&7=5
5&1=1 5&10=0例子:
按位与
int i = 5;
int j = 7;k = i & j;
printf("%d\n", k);//50 1 0 1---5
0 1 1 1---7
-------
0 1 0 1---5
21&7=516 8 4 2 1
1 0 1 0 1---21
0 0 1 1 1--7
-----------
0 0 1 0 1---55&10=0
16 8 4 2 1
0 0 1 0 1---5
0 1 0 1 0---10
------------
0 0 0 0 0---0-5&10=10
(不会)
//按位与
int main() {
int i = 21;
int j = 7;
int k;
k = i & j;
printf("%d\n", k);//5
k = i && j;
//k的值只能1或0,因为&&是逻辑运算符,
//逻辑运算符的结果只能是真或假,在c中,真是用1表示,假是用0表示
printf("%d\n", k);//1真
}
|--按位或
||逻辑或
|按位或
1|0=1
1|1=1
0|1=1
0|0=0例子:
按位或
3|5=7
16 8 4 2 1
0 0 0 1 1---3
0 0 1 0 1---5
-----------
0 0 1 1 1---7
//按位或
int main() {
int i = 3;
int j = 5;
int k;
k = i | j;
printf("%d\n", k);//7
return 0;
}
~--按位取反:
~i就是把i变量所有的二进制代码取反
一种简单粗暴得到答案:
简单算出结果的公式:
设目标数为:X(正数)
则取反结果:~X = -(X+1)
设目标数为:-X(负数)
则取反结果:~X = (X+1)
# include
# include
int main() {
int i = 3;
int k = ~i;
int j = -2;
int g = ~j;
printf("%d\n", k);//-4
printf("%d\n", g);//1
return 0;
}
相同为零
不同为1
1^0=1
0^1=1
1^1=0
i<<1 表示把i的所有二进制位左移3位,右边补0
左移n位相当于乘以2的n次方,前提是数据不能丢失面试题:
A:i=i*8;
B:i<<3;
请问上述两个语句,那个语句执行的速度快
答案:B快
//<<移动
int main() {
int i = 3;
int j = 5;
int k;
k = i << 3;//3*2的3次方
printf("%d\n", k);//24
return 0;
}
i>>1 表示把i的所有二进制位右移3位,左边一般是0,当然也可以补1
右移n位相当于除以2的n次方,前提是数据不能丢失
面试题:
A:i=i/8;
B:i>>3;
请问上述两个语句,那个语句执行的速度快
答案:B快
1、数值零
2、字符串结束标记符 '\0'
3、空指针NULL
NULL本质也是零,而这个零不代表数字零,而表示的是内存单元的编号零(地址0)
我们计算机规定了,以零为编号的存储单元的内容不可读,不可写
用来存放字符数据的数组是字符数组,字符数组中的一个元素存放一个字符。
切记空格也算一个字符。
代码例子:
int main() {
char c[10] = {'i',' ','a','m',' ','h','a','p','p','y'};
int i;
printf("%c %c\n", c[2], c[7]);
printf("\n");
for (i = 0; i < 10; i++) {
printf("%c", c[i]);
}
return 0;
}
/*
输出结果:
a p
i am happy
*/