c语言从入门到精通(全网炸裂)

目录

字符串​编辑初级知识

转义字符知识

​编辑

转义字符表

ASCII表

操作符

typeder 和static的用法

指针的详解

指针变量的大小

指针的初级

const修饰

指针进阶

字符指针​编辑

指针数组

数组指针 

 函数传参

函数指针

函数指针数组

指向函数指针数组的指针

 回调函数

模拟实现qsort函数

 指针的笔试题目

1.题目1

2.题目2​编辑

3.题目3

4.题目4

5.题目5

6.题目6

7.题目7

8.题目8

结构体详解

分支语句

if语句题目

Switch语句题目

循环语句

 几个常见的陷阱

for循环题目

几个循环题目

二分查找算法

 猜数字游戏

 关机程序

函数的详解 ​编辑

 函数题目

函数的嵌套

函数的递归

函数的迭代与递归

函数栈帧

斐波拉契数

辗转相除法

99乘法表

 数组的讲解

数组名

冒泡排序

三子棋程序

字符串逆序

计算一个数的每位之和

 交换数组

操作符详解

统计二进制中有多少个1

//判断一个数是否是2的次方数

练习2          二进制位 置0或者置 1 

获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列

编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同

整型提升

数据存储

字符串函数

结构体

结构体的自引用

结构体内存对齐

结构体传参

通讯录

枚举

联合

动态内存管理

vs调试技巧



字符串9d640eea344f40ee9dfca306aad92efd.png初级知识

 5fc6bf94020c48158cfc97386da39438.png

 字符串的结束标志是\0,arr1有隐藏\0,arr2的\0是随机的,他会不断寻找。c78a6ba5654b4e93a81b155aab9de957.png

转义字符知识

a1fe7c70e64447b6ac4a0c1e8f235c1f.png

他遇到\0就结束了,\0是字符串的结束标志。

转义字符表

e26ce4bcc7e742da925c7e6f21b334ab.png

 三字母词

//??)-]

??(-[

太老了,已经不用了。

转义字符加\就行了。

我举一个打印转义字符的程序给你们看看

9a55e8d4669742259239b1df66f2fa80.png

0e43a071c6754cd0877e520023ff6f79.png

 几个常见的打印形式

3dc693b1fc1b449bafa4c6508bd8c31f.png

 、

 \ddd表示1-3个八进制的数字,如\130是X,是一个字符

\xdd,dd表示十六进制数字,如\x30是0,这个是一个字符

7750a58fc51b46b4812e42b68ef01455.png

ef8f24d530ca45b18b617e22edb3fb54.png

 7786eebe344c4b2194ae98d130270222.png

ASCII表

32c56a5207ae42a68aa366194b8572be.png

 |注意\ddd和\xdd转义字符60ad045870404637ab3d67ab142b2ac1.png

操作符

 我只说逗号表达式

逗号表达式是从左到右依次计算,整个表达式的结果是最后 一个表达式的结果        

 4a44a5cad7c84bdea735ec84992f90c1.png

e56cf0e514394d5e8845ddf097d68b77.png

7e652f7e5fa542578b9112f8e60062bf.png

 9bbbd71091c343d38bfd03d49887fad6.png

总结:除了单目操作符和赋值操作赋是从右到左,其他全部是左到右

007dacffc0db4baf9b74b9b80e23e1ca.png

typeder 和static的用法

d48039f66a7a4aa9885e3e1848967fed.png

这里的typeder是关键词,相对于重定义效果,uint=uisgned int num效果

typeder struct Node 变量名相对于等于Node变量名 

8525f69bfe5b4943aa66fdb08f3c5848.png

static的用法

0bcce05b771a42bfae95bb3c0a24f5b3.png

这的test的个局部函数,进入作用域生命周期开始,出作用域生命周期结束,这个a就是局部变量,出了这个生命周期就结束了,所以打印了十个2。

98c21b22287e43b59660019fe9ad2a1c.png

加了这个static修饰局部变量时,出了作用域不销毁,相对于static int a下次没有用。

静态区变量只会创建一次。

本质上static修饰局部变量时,改变了变量的存储位置

04d579b40a7c4df49e70bd625bd243bb.png

static不能在多个文件修饰全局变量。切记切记,一个文件可以修饰全局变量

745b6aef3b1a4e0eb36387f4002e621c.png

总结:static修饰局部变量:

本质上影响变量的存储类型,一个局部变量是存再栈区的,但是被static修饰后就存在静态区了,因为存储类型的发生变化,生命周期跟着发生变化,变长了!。

6a04035f9e39464283457bab639196fe.png

总结·:static修饰全部变量时:

1.首先要知道全局变量具有外部链接属性的

2.全局变量被static·修饰后,外部链接属性就变成了内部链接属性,就是这个全局变量只能在自己的。c文件夹使用,其他文件看不到了,相当于作用域变小了。

static修饰函数

577b4f7217fa40ee9ecc89cbb3109bff.png

59355c87676e479c8258d1299673e8e0.png

总结:

static修饰函数时,只能在自己的。c文件里使用,其他文件即使声明的也不能使用。

register是寄存器

154cc9debb80435e82043ad942b4aaf1.png

 就是访问速度快些,但不是全部放在这里。

指针的详解

9575e0da0828428cb10a17cf19c88f75.png

我们可以把每个内存单元看成是一个编号,每个编号都有它相应的地址,这个地址就叫做指针

59cbfb22672c4c1f909664aba1c8a008.png

 任何变量创建都会在内存开辟空间。

&a取a的首地址,比如图里的5地址处。还有下图的第一个地址

123c9e652d104715b4f793684972739f.png

内存单元

编号-地址-地址也是指针

存放指针(地址)的变量叫做指针变量 

c252c257fa9a4a94a8c2b40331618bd3.png

 p指向了a。也就是指针变量p存放了a的地址,类型为int。

指针就是来存放地址的

*p就是解引用操作符,通过p存放的地址找到p所指向的对象,*p就是p所指向的对象,就是找到了对象。7700e8a2757540dd926c4fee6e7272f1.png

 这里*p找到了a就可以修改了。

指针就是地址,

指针变量就是存放地址的。

区别:口头语的指针就是指针变量。

指针变量的大小

32位上int*,char* float*,long* double*等等,不管是什么类型的指针,都是在创建在指针变量,指针变量是用来存放地址,取决于一个地址存放的时候的空间多大。

32位的机器是32个bit位-4byte

64-32bit-8byte

所以32位机器的指针变量大小是4个字节,64位是8个字节。

9ce279b041cd41cc82c2e350fcb1bc64.png

指针的初级

0661423b0ee24b1289449f988d9eb527.png

7e64ebc5a2c645d6885c0f99d59c1043.png

 把内存单元的编号称为地址,地址也叫指针。

指针就是内存单元的编号。

d460c85adead4489966f3b088ccbc868.png

取地址的时候取的是最开头的地址。

因为知道起始地址后面的3个地址也容易找到。

997614976bf847ff8fda1f57cb3ba2b0.png

总结:平时口头语的指针就是指针变量,实际的指针就是地址。 

指针变量里面是存放的是地址,通过这个地址可以找到一个内存单元。

8de906c36d2b40c0961bf97a394d77cd.png

 07fae284a7894f678d798438fe16619a.png

NULL是空指针,值为0

9475f4522c204f7ea35b9bf21eb09ceb.png

766059e947d74d6ca581ded938cbae87.png

2971d5f70fa64756adee05cdb9cb5cc4.png

int类型

2d00cd76539c41a7af358573710074c3.png

 char类型

d335047fb4814a488eb09c869bde3dae.png

 指针类型是有意义的:指针类型决定了指针在解引用的时候访问几个字节

如果是int*的指针解引用访问4个字节,char*的解引用访问1个字节。

068512ac41dd48b28610e82a10ae0661.png

 59c9179860c14210be5478e818ef0323.png

 4b9e89beaa19468f845cd5f93e875016.png

指针的类型决定了+-1操作的时候跳过的几个字节

可以通俗的理解指针迭代步长

char*指针

6f9da6703240438cbc39271810e6b7b9.png

 int*类型指针50a3fa3bece64cb0956a37a363f5b17f.png

f8945b29f4364411839f084066e5e865.png

991866fa4aa6417c857b9fa7b1a865dc.png

int*和float*的指针不能混用。

b7b0a92375e74f05919a36eb9e521cc4.png

这个地址编号存起来需要4个或者8个字节,但是这个地址向后访问1个或者2个或者4个字节或者8个字节

总结指针大小是4个或者8个字节,但是指针的类型决定了向后访问的权限的大小

54c05ea795ed482a8085d589371a3a97.png

 
pa是指针变量他的大小取决于他存的地址的大小,与a的大小没有关系

0f71617651a346cb81285630a908a5ed.png

 这里的p就是野指针。

野指针的原因有二个

1.没有初始化

2.越界访问。

672c50c499f34b78b1d25b3e940fd9b6.png

e0233af5967642a38021b04d5a457546.png

 22294495f8524938b6e5ce37b6c136b4.png

d13cb24cadf74bd9879734efa4961986.png

 8e8de503fbdf4adfac428b9085809110.png

fabcad89f16a46118788996f833fd914.png

一个是地址+1 下面是值+1

62115f2e701946a7a6bd13f6fa6c8cf3.png

指针-指针的绝对值=元素之前的个数。 

9f50afd19a0a42c888123f2a04e43a7a.png

 f39ee2f6e37c46eda6c761b227262d35.png

只要拿到a的地址和\0的地址,就可以求字符串的长度。 

9aba42d7daed48209bebad04e7000cb7.png

//指针-指针求字符串长度方法
int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - start;
}
int main()
{
	char arr[] = "abc";
	int len = my_strlen(arr);
	printf("%d\n", len);

	return 0;
}

a5da809b1fc647939bbe8691d826bc2b.png

指针-指针的绝对值得到的是元素之间的个数。3414d4a8fc754631812bf17a13decab0.png

54279623916c4198925a25c23f6397ed.png

f86e6cdf2f0648659ab95196134b50df.png

458d8e1c9f524e6bbeb943c2530514dd.png

允许你p1和p2比,不允许p1和p3比。 

360119f821ad416594ce5ec538d6ac7f.png

97861eac373e48018395f56d0bfdf355.png

 da97c019296544cd93abc3e6c0c44407.png

 56beb29fcb8c479ea39c7a0d61fd9ca1.png

这个里的p存储的是arr首元素的地址,首元素的地址+i解引用相当于访问的是这个数组的i个元素 

3f73ffb7b1b945228cb8b36e2fda40c7.png

arr是数组的首元素地址 (arr+i)在解应用的时候可以访问下标为i的元素。

指针也是一样  p指向数组的首元素地址 (p+i)解引用就可以访问下标为i元素了。

960c61e79a634bf39afaad5990f09b41.png

aad77d4982f94c87a316472330b1cab9.png

 c9da8957037945bbb0eec6b8cd96edab.png

a在内存中有自己的内存地址,pa虽然指向的a,存储了a的地址,但是pa也有自己的内存空间,同理ppa也是。

1177283222e84b2181c0540feedc9e27.png

29895945b205454489e099fc9ae45c50.png

这个*说明pa是指针,int说明pa指向的对象是int类型 

bb29c34ce04e4f27883262920b708fc7.png

第二个*说明ppa是指针,第一个int*说明 ppa指向的对象pa的类型是int*

二级指针是用来存放一级指针变量的地址的

指针数组

存放指针的数组就是指针数组本质是个数组

fce2b1c7daf642d08fcda8eb643e7462.png

在这里int arr[10] 这里的int 说明 arr存放的是整形类型     int *  parr 这里的int* 说明parr存放的是整形指针类型。

f25325b190b34532b5c22abb2fff277f.png

parr[i]就是访问下标为i的元素, 就相当于拿a的地址或者b的地址或者c的地址 然后在解引用就可以访问a或者b或者c

通过下标找到a的地址,解引用找到a的元素等等。

a2a1d967b8e54f6cb108376d107f8b2e.png

cc1d080134f64773b21ab68ac8330359.png

parr[i]就是访问下标为i的元素, 就相当于拿arr1的地址或者arr2的地址或者arr3的地址,也就是拿到了他们的数组名,+j然后再解引用就可以访问它第i行第j个元素了。

 arr1相对于第一行的数组名,也就是第一行的起始地址,加上j就可以访问第一行的某个元素的地址了。

6c804276dcb14bb1b7a5b2f4704e1199.png

const修饰

c7a14d2a3a2c41159d416665c6836afa.png

ea4d10698e484da5bae872b730c3da5f.png

b780fec164b04ce19f450876252483a9.png

指针进阶

字符指针ccb411a0b7df41dd8ccb6128557ed5ab.png

这个就是把字符串a的地址存在p里面了。 

16734da3081646c2b7bd59b6a886dcca.png

只要告诉我们这个字符串的起始地址,我们就可以向后打印,直到打印\0为止。

e7054556e59848938ed10b40e5de7299.png

 215ad81f4a0843cfaeb7b01d6f92a53b.png

 da8008d218694eca9e5308e9e5925c4a.png

 p1与p2都是指针,指向abcdef的起始地址,并且都是常量字符串,都是只读区域不能修改。所以一样

arr1与arr2虽然内容一样,但是开辟的内存首地址不同。因为arr1与arr2是不同的数组名。

指针数组

顾名思义就是存放指针的数组,本质就是数组。

bea4efc2e16f42ab974316aaee999663.png

 指针数组 说明数组里的每个元素是int*类型的

312756409235451399b4a0dff4720acd.png

 arr1是parr【下标为1】的数组名,存放的是首元素(1)的地址。 

288ae67b724842bdb8665e3985aca4c3.png

arr1是数组名,arr1相当于于首元素1的地址

parr有三个元素,分别是arr1,arr2,arr3,每个元素的类型是int*的指针

只要parr找到对应的下标,就相当于对应的数组名,就可以访问对应的元素起始地址,

parr【i】就相当于arr1或者arr2或者arr3这个元素,然而arr1又是数组名,数组名又是首元素地址,就可以1访问1的地址,在加上一个j,在解引用就可以访问arr1里的某个元素了。

4aef73b9b6fd4b19a874891b58c09db8.png

数组指针 

用来存放数组的地址的

存放数组的指针,本质是指针

   它的类型就是去掉名字,剩下的就是他的类型。数组指针类型。

c42fe0d1621242e78a03ed8904567df4.png

825c020f00374b788c29c7036e8c909c.png

 *没有类型才叫解引用

41c04745066549ceb94b03e3636e67b1.png

int*p1[10]这个p1是跟[]结合,说明p1是数组,int*说明里面存放的是整形指针。

int(*p2)[]这个p2是跟*结合,说明p2是指针,说明p2指向的是数组,每个元素是int类型。 

99d6587deda3401db52e18db085419fc.png

 数组名

数组名能表示首元素地址但是有二个意外

1。sizeof(数组名),单独放 这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2.取地址数组名 ,这里的数组名表示的是整个数组,取出的是整个数组的地址。因为&arr是取的整个数组,也是从arr[0]开始取的,他会一直取到这个数组完为止。+1跳过一个数组地址大小。

6902252c1ff14e9bae74e5581a0a4541.png

数组的地址也是从0开始,数组的首元素地址也是从0开始,只不过数组的地址+1,一次性跳过整个数组,是40个字节,而数组的首元素地址+1跳过的是4个字节。因为这个是int类型,有10个元素

f15f1f2c243f45a09f88f83cebae2248.png

12ba6ca58f2f4f0a93c7a912fb6a6422.png

 47f540a7131f444981135b7cbfc5aad3.png

分析 p是指向数组的,相当于指向整个数组的地址,也就是整个数组的地址,*p其实是整个数组,就相当于数组名,数组名代表整个数组,数组名又是首元素地址,所以*p本质上是数组的首元素地址。

访问元素是    *p+i就可以访问下标为i的元素的地址,解引用就可以访问下标为i个元素了 

99c6fc3b5bf44931be8fe97dc777ecdc.png

f0f7a7c427be4c19b07ce99b60ceba82.png2a2e11267175499c88dbc0de2faca984.png

int*p=arr 说明 p指向的是数组名 数组名又是首元素地址 ,所以用指针来接收,使用加*号,说明p是指针,他指向的数组名的类型是int类型 说明是int类型

p2指向的是数组,是整个数组的地址所以用指针来接收,说明p2是指针,为了避免与[]结合要*号加括号括起来好一些,这个数组有10个元素,每个元素是int类型。

dd75148fe5d84312a6377e7813d6c602.png

这个p指向了数组名的地址,这个数组有10个元素,每个元素的类型是int类型。它加上+i就可以访问下标为i元素的地址了,然后解引用就可以访问下标为

i的元素了。单独的*p就是找到了数组名

9a0eb2db8c5846ef8b650aa9dfcdbd3d.png

总结:

二维数组的数组名是第一行的地址也就是一维数组的地址,所以用一维数组指针来接受,然后

他一有三行,指向的是第一行 ,每一行有5个元素,所以指针指向的数组元素是5个,每个元素的类型是int类型,所以用int(*p)[5]来表示

(p+i)表示的是指向第i行的地址,也就是整行的地址,他+1表示跳过整个数组的大小也就是一维数组的地址,*(p+i)表示第i行的数组名也就是arr[i] 他+1表示跳过一个元素的大小。

如果是取地址第i行的数组名,+1就是跳过一行的地址大小。

081caba57a684c658d087984b4c11fa7.png

0e6bfeb3022d44c397e28d2be3ea1b0c.png

8da7d2a65c8248a4a0b48eb3fa48b091.png

二维数组的数组名相当于二维数组的首元素地址,这个首元素是第一行,也就是第一行的地址,第一行的地址相当于一维数组的地址 ,一维数组的地址需要一维数组的指针来存储。

所以需要指向一维数组的指针来接受。

p+i是指向i行的地址。,*(p+i)是指向第i行的数组名   相当于拿了第i行

数组名又是首元素地址,*(p+i)就是指向第i行的首元素地址,加上下标j就可以访问下标为j元素的地址,然后括起来解引用就可以访问第i行下标为j的元素了。

dab119fa93d94e95821607d2b62fbe5a.png7432da23bde24cffb5fbb6dc0bc8ecfd.pngefd065dfbb6d4586913d70c54ff98b88.png

 fc3444887d23415181079a8749ad0d73.png

3f7b70ea980b475e8b7c8350b5b7ec30.png

c807ffc73ca94e7899f490187415b587.png

&arr的类型是int(*)[10]是个数组,是存放数组的指针,+1相对于跳过一个数组,所以是40个字节。

 函数传参

c57c70a57cdf470195d42498aec24ece.png

int(*parr3)[10[5]  ------>  首先parr3是一个数组,它有10个元素,每一一个元素的类型是int(*)[5]  说明parr3的每一个元素是指针,该指针指向的数组,也就是存了该数组的地址,该数组有5个元素,每个元素是int类型。

bf1331759178454f8862dbffabeb13a5.png

e3373b95474c4c53a98f67b5ed9142cb.png

515818860b4c4c57aa363def0068b989.png

arr2是一个存放指针的数组,类型是int*,  arr2是数组名,arr2就是首元素地址,存放的就是int*的地址,   一级指针的地址就是用二级指针来接受。

210a6992d60543b184d3cc7cf6dc4db3.png

73e7e4819cf549e6930b381a13b75146.png

b3da290514154fcbae4c82f0569bb731.png

00a2f1d1fb99455bbce1e9ba48a8cef3.png

22539ea901b64e50b34a9a64f96174ad.png

d927d93d60c046f7ac509455eb538330.png

 二级指针可以用一级指针的地址、二级指针来传参。

指针数组,里面存放的是指针,传指针数组的数组名,相当于传指针的地址,一级指针的地址就可以用二级指针来接受。

函数指针

指向函数的指针,取地址函数名就是函数指针。

9fbde25d177a4d6c98925a0cfa940707.png

e79eca3dd4e64ede987313e6fa3d6462.png

 函数名和取地址函数名都是函数的地址所以 函数名=&函数名

0e646c9ee2bf44479849c73399ca8ff1.png

pf=&add----->(*pf)();

pf=add   -----pf();

pf是指针变量,它指向函数,返回类型是int类型,参数也是int类型,*说明,pf是指针,*pf相当于找到了那个函数,pf存放的函数的地址。

 int ret=pf(2,3)

 int ret=add(2,3)这个*可以不用写。 为什么不写因为函数名和取地址函数名一样,pf是·个指针,它指向的是函数的地址,*pf就相当于找到了那个函数名

在这里说一遍,函数名和取地址函数名是一样的,因为函数没有首元素地址。

那就相当于pf=add

0ee339574abc447aa206081ff61ae5e2.png

 efe3f189b93b465785efeb3ded312865.png

 7fd65137c04e4fa386b743ad086ef635.png

int add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 0;
	int b = 0;
	int(*pf) (int ,int ) = add;
	int red= pf(2, 3);
	printf("%d\n", red);
	return 0;
}

86159de490f4475e98c1b4adc002a98d.png

 其实就是对0强制类型转换,被解释为一个函数的地址。

首先void(*)()是一个函数指针类型,它把0强制类型转换成函数指针类型---->void(*)()0

然后对其解引用对其传参,其实这里的解引用可以不写,这个*相当于找到了这个函数,它你可以把(*void(*)()0)看成是一个函数指针变量,它的地址是0的地址 ,对它进行传参。,传参的参数为空---是一次函数调用,调用的是0地址处的函数 void (*pf)()

2dbc67bbaa10455e90d564c18e8deca8.png

303625f6d55c4c53afaacae5124c32ec.png

e4b067634a4d46e6943db23cfc5e1398.png

465aa61941614ef7b53d36d7878d4cd8.png

signal是一个函数名,它第一个参数是int类型,第二个参数是函数指针类型,它的返回值类型是一个void(*)(int)类型,也就是一个函数指针类型。

C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。

typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE

C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:

typedef unsigned char BYTE;

在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:

BYTE  b1, b2;

可以简化成这个样子  typedef void(*pf_t)(int)

4451b7dcffa84342b2f8292ff61e7197.png

d742a1d23f24431585a14065d3527b24.png

制作一个简单的计算器。

通过函数的指针来调用函数

void meau()
{
	printf("*******************\n");
	printf("****1.add 2.sub****\n");
	printf("****3.mul 4.DIV****\n");
	printf("****0.exit*********\n");
	printf("*******************\n");
}

int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x *y;
}
int DIV (int x, int y)
{
	return x / y;
}

void calc(int (*pf)(int, int))
{
	int a = 0;
	int b = 0;
	int red = 0;
	printf("请输入二个操作数\n");
	scanf("%d %d", &a, &b);
	red = pf(a, b);//pf里存的是add的地址,所以它调用到add函数里去了
	printf("%d\n", red);
}
int main()
{
	int input = 0;
	do
	{
		meau();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(add);
			break;
		case 0:
			printf("退出计算\n");
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(DIV);
			break;
		default:
			printf("选择错误请重新选择\n");
			break;
		}

	} while (input);
	return 0;
}

c1d97da051584a36a5728616ea9af3a4.png

 回调函数就是通过函数的指针调用函数。

这个也是回调函数。回调函数就是通过a函数的指针调用a函数·,因为pf存的是add的地址,地址就是指针,就可以用pf这个指针来调用a函数。calc这个就是b函数,b函数里存放的是a的地址。

pf里存的是add的地址,所以它调用到add函数里去了  

函数指针数组

49f6d193f992484ba58cf747bfcbe13b.png

这个arr就是函数指针的数组本质是数组,存放的函数指针,类型是int(*)(int,int)

找到arr[i]下标为i元素的函数就可以调用这个函数,这里有给误区这里的函数既是函数名又是函数地址,因为arr[]这个数组存的是函数名也就是函数的地址

ff63f0893cb041eab1bdaf75a74d4d6e.png510824138f7c44a991352255ffad9071.png

b55ac1a49d964e1d90249d170875ac74.png找到数组arr下标为i的函数名,你可以解引用,也可以不解引用,这里的解引用没有特殊作用,因为函数名和&函数是一个作用,也就是通过arr的下标找到了这个函数然后来访问这个函数。

void meau()
{
	printf("*******************\n");
	printf("****1.add 2.sub****\n");
	printf("****3.mul 4.DIV****\n");
	printf("****0.exit*********\n");
	printf("*******************\n");
}

int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x *y;
}
int DIV (int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int red = 0;
	int (*parr[5])(int, int) = { NULL,add,sub,mul,DIV };
	do
	{
		meau();
		printf("请选择\n");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出\n");
		}
		else if (input >= 1 && input <= 4)
			
		{
			printf("请输入二个操作数\n");
			scanf("%d %d", &x, &y);
			red = parr[input](x, y);
			printf("%d\n", red);
		}
		else
		{
			printf("选择错误\n");
		}
		
	} while (input);
	return 0;
}

这个就是函数指针数组的好处。

指向函数指针数组的指针

4a489e1286294d97a2b22a3f81165511.png

首先pfarr是个指针,它指向数组,这个数组的类型是int(*)[5](int,int),是一个函数指针数组

 回调函数

快排

84a18dc8d51543f6ba37668a95cce5dc.png

428b182fe36b4259b71c8fc91499d520.png

e3b09f4029a74be59bd004e08623f813.png

 cmp就是比较函数的地址 ,e1指向了你要比较的第一个元素,e2指向了你要比较的第二个元素

e1和e2是你要比较2个元素的地址。

通过用户自定义的函数比较出二个元素的大小传给cmp这个函数指针,分别对应的他二个参数,让cmp函数自己写出排序的顺序

第四个参数是比较待排序数据的2个元素函数

ba62d20f1b004d96a5d20e2804a8e768.png

 d794e11110594158a78c4491f71aebd9.png

ba79488513d94533b2804b471b9480e0.png


//快排
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

void*的指针不能直接解引用操作,void*是无类型指针,可以接受任何类型的地址。

void*是无类型指针,所以不能解引用操作,不能加1操作。

所以e1和e2要强制类型转换在解引用才能找到。

1eb9d1a0033440d79678d630c7f83184.png

//比较结构体里的字符串大小

struct stu
{
	char name[20];
	int age;
};
 int  cmp_stu(const void* e1, const void* e2)
{
	 strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);//结构体指针要用()起来。

}
void test()
{
	struct stu  s [] = {{"zhangsan", 18}, {"lisi",20}};
	
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu);

}
int main()
{
	test();
	return 0;
}

af7215aeb49047b49b58ee7c96b36b45.png

模拟实现qsort函数

就是main函数里的cmp_int是一个函数对吧,不是指针,这个函数也就相当于这个函数的地址传给了这个cmp这个函数指针,这个函数指针调用cmp_int这个函数

bc1dd52b4d2743e48f8822cc12a13812.png

e7655e4458bf4c4da0f384c5049e24e4.png

1、cmp()会返回int型返回值;
2、cmp()带有两个指针型形式参数const void *
其中,const为一个限定词,含有const限定词的参数,尤其是以指针传递地址的参数,不会改变其原本的值。
void *,为一个无类型指针,既可以通过强制类型转换,使其转换为任意类型的指针,甚至直接取指向的数据值。
注意cmp这个不是库函数,但名字必须是cmp

cmp作为一个比较函数,可以简单方便的对bsearch、qsort等函数提供交换依据。
它的基本形式为:

用cmp指向的那个函数来比 二个参数是二个元素的地址

95ad74f3ceb349eead52a49cb460197f.png1011734bc9b14f338e26ed80bc5b8ba0.png

首先base是你要排序的起始地址 首先要转换成char*类型,为什么要转换成char*类型,因为char*类型+多少字节方便,然后+上元素的个数就是j,当j等于0的时候也就是从起始地址开始,最后乘以宽度,这个宽度就是待排序的数据元素的大小,单位是字节,比如我们排int类型的数据就是乘以4

起始地址+偏移量的方法来计算

偏移量计算:=下标*宽度(元素的宽度啊)

de3d4bbe765442c4af10a81bd3a4a556.png

知道你要交换的起始地址还不够,还必须知道宽度 就是这二个元素的起始地址每个地址占的宽度。

7e4851bffdd34e39b17b1e763c5930d8.png

通过循环和逐个字节交换的方式:

因为我们无法直接知道传入的数据的具体类型,所以只能通过按字节逐个交换的方式来完成内存块的交换。循环的次数为 Width,即内存块的大小,这样可以确保我们对整个内存块进行遍历和交换。

循环的次数为 Width 是因为这段代码是用来交换内存块内容的通用函数,而不关心数据的具体类型。在这种情况下,我们必须以字节为单位进行操作,因为无法确定传入的数据的具体类型。

假设我们有一个包含 4 个整型元素的数组,每个整型元素占据 4 个字节。如果我们要交换两个元素,就需要将每个字节都进行交换。因此,循环的次数必须等于内存块的大小以确保每个字节都被正确地交换。

另外,通过循环的方式可以确保在不同平台上也能够正确地执行内存块的交换操作,不受数据类型或内存对齐方式的影响。这样的通用性使得这个交换函数可以适用于各种不同类型和大小的内存块,从而提高了代码的重用性和灵活性

int cmp_int(const void *e1,const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}

void Swap(char* e1,char *e2,int width)
{
	for (int i = 0; i < width; i++)
	{
		char tep = *e1;
		*e1 = *e2;
		*e2 = tep;
		e1++;
		e2++;
	}
}
void bubble_short(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//排序
			//cmp是你要比较的元素地址
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			  //比较的元素地址 和每个元素的字节大小宽度
			}
		}
	}
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_short(arr, sz, sizeof(arr[0]), cmp_int);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}
 

简单制作求最大值和求最小值的方法

int max_1 (int a, int b)
{
	return a > b ? a : b;
}
int min_1(int a, int b)
{
	return (a < b) ? a : b;
}

int main()
{ 
	int a = 0;
	int b = 0;
	int input = 0;
	printf("输入1是求最大值,输入2是求最小值\n");
	scanf("%d", &input);
	printf("输入二个操作数\n");
	scanf("%d %d", &a, &b);
	int (*pf[3])(int, int) = { NULL,max_1,min_1 };
	if (input >= 1 && input <= 2)
	{
		int red = pf[input](a, b);
	printf("%d\n", red);
	}
	return 0;
}


 指针的笔试题目

//深度讨论数组名
int main1()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));  //sizeof内部出现数组名代表是整个数组的大小 //16
	printf("%d\n", sizeof(a + 0));  //首元素地址+0还是首元素地址 4/8
	printf("%d\n", sizeof(*a));  //首元素地址进行解引用相当于a[0]是4个字节
	//*a中的a是数组首元素的地址,* a就是对首元素的地址解引用,找到的就是首元素
	//首元素的大小就是4个字节
	printf("%d\n", sizeof(a + 1));  //首元素地址+1 相当于a[1]的地址 是地址就是4/8
	printf("%d\n", sizeof(a[1]));  //第二个元素大小//4
	printf("%d\n", sizeof(&a));   //取地址数组名,取的是整个数组的地址,是地址就是4/8 
	printf("%d\n", sizeof(*&a));
	//&a----->的类型是int(*)[4] &a拿的是数组名的地址,数组指针解引用拿到了这个数组是
//是数组的大小就是16
	printf("%d\n", sizeof(&a + 1));
	//取数组名+1相当于跳了一个数组的大小,是地址就是4/8
	printf("%d\n", sizeof(&a[0]));
	//取第1个元素的地址 是地址就是4/8
	printf("%d\n", sizeof(&a[0] + 1));	       //取第2个元素的地址 是地址就是4/8
	return 0;
}


int main2()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//sizeof内部出现数组名代表是整个数组的大小 //6
	printf("%d\n", sizeof(arr + 0));//首元素'a'的地址---4/8
	printf("%d\n", sizeof(*arr)); //'a'的大小/1
	printf("%d\n", sizeof(arr[1]));//'a'的大小/1
	printf("%d\n", sizeof(&arr));//取出整个数组的地址是地址就是4/8
	printf("%d\n", sizeof(&arr + 1));//跳过一个数组的地址,是地址就是4/8
	printf("%d\n", sizeof(&arr[0] + 1));//'b'的地址是地址就是4/8
	return 0;

}
#include <string.h>
int main3()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//随机值 因为不知道编译器在哪里放\0
	printf("%d\n", strlen(arr + 0)); //随机值
	printf("%d\n", strlen(*arr));//相当于strlen('a')strlen(地址)//err 是一个野指针
	printf("%d\n", strlen(arr[1]));//野指针
	printf("%d\n", strlen(&arr));//取数组的地址,数组的地址也是从‘a'的地址开始的   随机值
	printf("%d\n", strlen(&arr + 1));//取数组名+1跳过了一个数组的地址,   随机值-6
	printf("%d\n", strlen(&arr[0] + 1));  // 首元素的地址+1相当于  是字符b的地址开始  随机值-1

    return 0;
}

int main4()
{
	
	char arr[] = "abcdef";//[a b c d e f\0]
	printf("%d\n", sizeof(arr)); //计算的是整个数组的大小,7
	printf("%d\n", sizeof(arr + 0));//首元素地址 相当于是‘a'的地址 是地址就是4/8
	printf("%d\n", sizeof(*arr));//’a'元素的大小  1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//取数组的地址 也是从‘a'的地址开始的 也是 4/8
	printf("%d\n", sizeof(&arr + 1));//跳了一个数组的地址是地址就是  4/8
	printf("%d\n", sizeof(&arr[0] + 1)); //相当于是’b'的地址  4/8
	return 0;
}
//strlen是求字符串长度的,关注的是字符串中的\0,计算的是\0之前出现的字符的个数
//	//strlen是库函数,只针对字符串
//	//sizeof只关注占用内存空间的大小,不在乎内存中放的是什么
//	//sizeof是操作符
int main5()
{
	char arr[] = "abcdef";//[a b c d e f\0]
	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	//printf("%d\n", strlen(*arr));//err
	//printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//‘a’字符的地址开始  6
	printf("%d\n", strlen(&arr + 1)); //跳过了\0 从\0后面开始  随机值
	printf("%d\n", strlen(&arr[0] + 1));  //从'b'字符开始走  5
	
	return 0;
}
int main()
{
	char* p = "abcdef";//[a b c d e f\0]
	printf("%d\n", sizeof(p));//p指向的是字符a的地址  4/8
	printf("%d\n", sizeof(p + 1)); //指向的是字符b的地址 4/8
	//printf("%d\n", sizeof(*p));  //err
	//printf("%d\n", sizeof(p[0])); //err
	printf("%d\n", sizeof(&p));//取出的是一级指针的地址  4/8
	printf("%d\n", sizeof(&p + 1)); //4/8
	printf("%d\n", sizeof(&p[0] + 1)); //4/8

	printf("%d\n", strlen(p)); //p指向的是a的地址  6
	printf("%d\n", strlen(p + 1)); //p指向的是b的地址 5
	//printf("%d\n", strlen(*p)); //P找到了a的这个字符 err
	///printf("%d\n", strlen(p[0])); //err
	printf("%d\n", strlen(&p)); //取出的是一级指针的地址 随机
	printf("%d\n", strlen(&p + 1));//随机
	printf("%d\n", strlen(&p[0] + 1));//5  //P+1  p指向的是b的地址 
	
	return 0;
}

2a6da45685024ceba483a6941e67b500.png

为什么strlen(&P)是随机值是因为指向p的那块空间上的地址存储方式不确定,有可能是大端存储,也可能是小段存储

2c1f4eef565b44e9b2afb66b40d5ac9d.png

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%d\n", sizeof(a[0]));a[0]是第一行这个一维数组的数组名,单独放在sizeof内部,a[0]表示第一个整个这个一维数组
	//sizeof(a[0])计算的就是第一行的大小 也就是第一行的数组名  16
	printf("%d\n", sizeof(a[0] + 1)); //没有单独放在sizeof内部,表示的是首元素地址 
	//也就是第一行这个一维数组的第一个元素的地址--a[0][0]的地址 然后+1 就是第一行第二个元素的地址  4/8
	printf("%d\n", sizeof(*(a[0] + 1)));  //第一行第二个元素的地址进行解引用 也就是arr[0][1]元素的大小  --4
	printf("%d\n", sizeof(a + 1));//  第一行的地址+1 相当于跳到了第二行的地址 是地址就是4/8
	printf("%d\n", sizeof(*(a + 1)));  //第二行的地址解引用 ---就相当于是第二行的数组名  16
	printf("%d\n", sizeof(&a[0] + 1));// 对第一行的数组名取地址 相当于是第一行的地址,第一行的地址+1相当于是第二行的地址	4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));  //第二行的地址解引用相当于拿到了第二行的数组名 16
	printf("%d\n", sizeof(*a)); //第一行地址解引用 拿到了第一行的数组名 16
	printf("%d\n", sizeof(a[3]));//第三行的数组名  虽然越界了 但是不影响计算它内部的大小  16

	return 0;
}

1.题目1

890cbd3642604ef4add471ab86f89e52.png

7a151008d1594f9bbb63db023e37a881.png

取数组的地址说明这个指针是的类型是int(*)[5]的,说明这个指针是数组指针,指针指向这个数组,数组有5个元素,每个元素的类型是int类型

题目的指针是int*类型,说明我们要强制类型转换成int*类型的

2.题目2cb01e0fd326b47148880ebc2fae25355.png

这里的p是一个结构体指针

1.结构体指针+1相当于跳过的是一个结构体的大小 已经知道一个结构体的大小是20个字节就相当于跳了20个字节。0x100000+20----ox1000014

2.把p强制类型转换成无符号长整形,,已经知道1,048,576是p转换成无符号长整形的数据,它+1= 048,577

3.把p转换成无符号整形指针类型  +1就相当于跳过一个无符号整形 相当于+4 = 1,048,576+4

3.题目3

cdb17571d080450bb267fa2694fdfbea.png

ca604de2b62442a7be44a7a4e933590e.png

取数组的地址说明这个指针是的类型是int(*)[4]的,说明这个指针ptr1是数组指针,指针指向这个数组,数组有4个元素,每个元素的类型是int类型

题目的指针是int*类型,说明我们要强制类型转换成int*类型的

ptr1[-1]是指向的是04 00 00 这块区域的 同理也是00是高地址的 04是低地址的拿出来的时候就是低地址在后面  00 00 00 04  以16进制打印的时候前面的0全部删除掉 就是4了6643a617efc041d09a28d3da67a47389.png

ptr2: 首先这个a是数组名,数组名是首元素地址,首元素地址+1是跳过一个整形也就是4个字节,题目强制类型转换成了int类型说明现在的a不是地址了是一个整数,它+1就说明加了一个整数

1f7418f06fc44e22bb86467dbff49fa0.png

503bbf4c734248098dfb3519be101ec2.png

比如1的16进制是 00 00 00 01

在内存是小端存储模式,01是低地址处,----01 00 00 00

a+1强制类型转换成int*类型的指针,所以ptr2就指向了 00 00 00 02这块起始地址。因为 00 00 00 02在内存中是以小端模式存储,拿出来的时候就是02 00 00 00 以16进制打印的时候就是200 00 00

9a23030372ac468a9f9ff0520d565a1b.png 因为·02是高位的地址就应该放在低地址处

4.题目4

b702977c8535441fb412cc001a00144a.png

b40199ea037e4463aa6d80e0f224c818.png

首先这个二维数组的元素是以逗号表达式的形式展开,你要明确这个数组里的元素是{1,3,5,0,0,0}p指向了第一行的数组名,此时的数组名表示的一行的地址,数组名没有放在sizeof内部,也没有取地址,说明数组名表示首元素地址  也就是a[0][0]的地址然后解引用就是1了

5.题目5

b492434a36954de4b9c7e6264734d17f.png

d6c28228bf15467c9389365bb50e2bf4.png

这里的a作为数组名,它是第一行的地址,它的类型是int(*)[5] ,因为二维数组的数组名代表的是第一行的地址,也就是第一行数组名的地址  一维数组名的地址就需要用一维数组的指针来接受。

这里的p是一个数组指针,它指向的是一个数组,这个数组有4个元素,每个元素的类型是int类型

这里把二维数组的数组名赋给数组指针    类型存在差异,但不影响,只不过是报警告

再来看看   p是一个数组指针 它+1跳过一个数组的大小,这个数组有4个元素,每个元素的类型是int类型,相当于跳了4个整形的大小,p[4][2]可以看成 *( *(p+4)+2)

*(p+4)这个相当于p跳了4个整形的大小 再图可以看出 它跳到了a[3][1]的 地址,然后解引用找到了arr[3]的数组名,就可以访问相当于arr[3][1]~ arr[3][4]这块区域的元素的地址,然后+2跳过二个整形长度指向了arr[3][3]这块地址然后解引用找到了arr[3][3] 这个元素 

我们是把p[4][2]看成arr[3][3]  ,指针-指针的绝对值是元素之间的个数,他们相差了4个 又因为地址再内存中是从低到高存放的,所以结果是-4

打印地址是打印它在内存中的地址,所以这里不考虑大小端字节序,也不考虑原码补码反码,

 b5479e31a1884970a613d25db800e46c.png

再x86的环境下就是FF  FF FC 记住打印内存是打印反码的形式打印内存

6.题目6

9d4cd629a2964a28b016963a17875eb3.png

44db0f5a4ea4431fb01ff8124a1fd45a.png

7.题目7

6ba3870e47684f4aaee03c5564af11ec.png

3a2907939874406285bf32d711d18111.png

pa是指向的是a的起始地址

pa++跳到了第二行,对pa解引用找到了a的地址相当于指向了a的地址

56b40f83eeac464f9bae7f5885599165.png

pa指向了a,a是一个指针数组pa指向的对象是一个char*类型的,第二*说明pa是一个指针

pa++=pa+1  自然是跳过一个char*类型  也就是4个字节。

int*p 是一个指针,它指向的对象是int类型,所以+跳过了4个字节。

9ecc4ba01a964dd283eae59c6732bc5e.png

8.题目8

23e5c247349d4f1d965dde986dfed11f.png

3a298622125a4276a5144d1f3ec49896.png

7208ae86ba4b4620920724230840e7e5.png

+

结构体详解

a8afb5828f2448aabd421a85b534913e.png

 这个就是结构体的创建和初始化。

printf(“%s %d %s %s”,s.name,s.age,s.sex,s.tele)  结构体对象.成员名

可以把结构体和指针结合起来玩

53f77a84c7924022808f52dc733a8d30.png

 ps箭头指向的s的成员,也是解引用效果。

结构体指针变量->成员名(得到了地址(指针))。

结构体对象.成员名

分支语句

if语句

b3b7a40d03f248a3a1d4f394678f93bd.png

else离最近的if语句结合。        

if语句题目

//1. 判断一个数是否为奇数
//2. 输出1 - 100之间的奇数

#include <stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);
	if (n % 2)
	{
		printf("奇数\n");
	}
	else
	{
		printf("不是奇数\n");
	}

	return 0;
}

#include <stdio.h>
int main()
{
	int i = 0;
	for (i = 0; i <= 100; i++)
	{
		if (i % 2)
		{
			printf("奇数%d\n",i);
		}
	}

	return 0;
}

int main()
{
	int i = 0;
	for (i = 1; i <= 100; i+=2)
	{
	
			printf("奇数%d\n",i);
	}

	return 0;
}

Switch语句题目

#include <stdio.h>
int main()
{
	int day = 0;
	scanf("%d", &day);
	switch (day)
	{
	case 1:
		printf("星期一\n");
		break;
	case 2:
		printf("星期二\n");
		break;
	case 3:
		printf("星期三\n");
		break;
	case 4:
		printf("星期四\n");
		break;
	case 5:
		printf("星期五\n");
		break;
	case 6:
		printf("星期六\n");
		break;
	case 7:
		printf("星期天\n");
		break;
	default:
		printf("输入错误\n");
		break;
	}

这里的每条case后面必须加break语句否则如下图所示。

break会跳出switch语句。

switch语句的表达式必须是整数。

 8a1a36d184154da0974f28e979e1e6b3.png

ed9fc020eeb9426faee57654282b11a1.png

914b57f0f6c94a1280e8607a95938372.png

题目解释

#include <stdio.h>
int main()
{
  int n = 1;
  int m = 2;
  switch (n)
 {
  case 1:
      m++;//进入这里m=3,没有break执行下一个
  case 2:
      n++;//n=2  没有break执行下一个
  case 3:
      switch (n)n=2
     {//switch允许嵌套使用
      case 1:
          n++;
      case 2:
          m++;m=4
          n++;n=3
          break;
     }
  case 4:
      m++;m=5
      break;
  default:
      break;
 }
  printf("m = %d, n = %d\n", m, n);
  return 0;
}

循环语句

while循环

8d68626e6071478aa58d1fadcb50637a.png

先看表达式是否为真,为真就执行表达式结果,然后继续看表达式是否为真,一直反复,知道表达式不满足,就跳出循环。

比如打印1-10的数

int main()
{
	/*int i = 1;
	while (i<11)
	{
		printf("%d ", i);
			i++;
	}*/
	return 0;
}

break和continue的用法和区别

11fe39f56c0b46c6970a56a3e61d6086.png

9978ffeb7cd34bad94296cd920c202d5.png

因为这里的i++在后面,没有执行。

break是跳出循环的,continue后面的语句不会执行。所以一直进入死循环        

区别:break用于循环的永久终止

continue是跳出本次循环后面的代码,直接进入判断部分,进入下一次循环。

可以这么改 

da75841fb1b640f8b624e51015ca6e3a.png

f24cc3c0637242388368f0d3a1692906.png

1942c058104c48009ace570e4b8b5aec.png

 几个常见的陷阱

717f384b1c754ade92f9cd25a4c6a79a.png

这里的\n也是占一个字符 

181aa073945a4ecb89533e5de4594cef.png

 for循环

a3b082cea8094bbcbb73ce4cf1fd5527.png

 d9149b5f1f8d4732aba855d5018ac01c.png

 c15bb4daa0e14d48bd33a11c3dad9bc7.png

 先初始化,在判断,执行循环语句,然后调整,直到判断不成立,就跳出循环。

for循环遇见break和continue

b62e01541e004d73b59d00ca98746495.png

与while一样

continue的后面的语句 不会执行,但是for循环的i++在前面。,他跳到了调整部分去了

247e090f65b6402ebd861a366aa26982.png

 92bee0aadfb84f5c9da3a256d77c2fa7.png

循环里的语句不要随便乱省阅

        

0a5bd4263ed447b4829d745ece6952a2.png

这里第二就是i=0的时候内层循环执行三次,此刻i=1,但是j'现在等于3,内层循环没有初始化所以就打印了三次。

 9ac28ee5d99c4ded98ef28a5aad920a1.png

for循环题目

 432ac676254b46aea620f678c9510eab.png

这里=是赋值,表达式2结果是假的,就执行一次。

do-while循环语句

a79fbc98abee4a94b9c5d2c96846d053.png

先执行循环语句,在判断表达式,如果表达式为真,就继续,否则跳出循环。 

e0a5a1dde92841f8b697af99abe669fb.png

8cab6fe4d35e4744a6a5eef41614fb48.png

3d8f6483415e47f783e6bb57e60abe77.png

几个循环题目

1. 计算 n的阶乘。
2. 计算 1!+2!+3!+……+10!
3. 在一个有序数组中查找具体的某个数字n。(讲解二分查找)
4. 编写代码,演示多个字符从两端移动,向中间汇聚。

//1. 计算 n的阶乘。
int main()
{
	int i = 0;
	int n = 0;
	int ret = 1;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		ret *= i;
	}
	printf("%d\n", ret);
	return 0;
}

//2. 计算 1!+ 2!+ 3!+ …… + 10!
int main()
{
	int i = 0;
	int n = 0;
	int ret = 1;
	int sum = 0;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		ret *= i;
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

二分查找算法

7f13671c91b241f8b182753de273461c.png首先把arr[0]为left arr[9]为right 中间元素为mid  先判断arr[mid]>或者 <你要找的元素的值 ,运气好的话中间的元素就是你要查找的值。如果不是你要找的值,就判断arr[mid】>k就right=mid-1  ,<就left=mid+1  

08a83376a0804fb389485faf0aee1a4f.png

6a451dc2a9fd4a869c9a6a4a3c59cef1.png

4471ce7257bb4af8a92eb7edb64e5009.png

0bf76cc91c87426280dfb0995d2945a2.png


 

 e079f7e0cebe489b966a46c870449d54.png

比如找元素7,他的下标为6,创建一个变量left,含义是起始的,right,是末端的,中间变量是mid(left+right)/2,left=mid+1,right=mid-1.比如arr[mid]<k,中间元素是下标是4,找的元素是下表是6,在后面,则是left=mid+1,right还是right。现在left下标是5,right下标是9,中间下标是7,找的下标是6,此时right变成了mid-1,就剩下二个元素了,就是6和7 下标是5和6  ,left=5,right=6,中间是5,,mid=5 此时必要要找的下标元素小,那就是left=mid+1就找到了。

#define _CRT_SECURE_NO_WARNINGS
//3. 在一个有序数组中查找具体的某个数字n。(讲解二分查找)
#include <stdio.h>
#include <string.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 7;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int left = 0;
	int right = sz - 1;


	while (left <= right)
	{
		int mid = left+(right-left) / 2;
		if (arr[mid] < k)
		{
			left = mid + 1;

		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			printf("找到了下标是%d\n", mid);
			break;
		}
		
	}
	if (left > right)
		{
			printf("找不到\n");
		}
	return 0;
}

adba0970fbff4d4cbbb94f49b7d67d9a.png

明确区别sizeof 和strlen()

46f1547178a64e1396d2ad5a88f4ada3.png

646d041fe14241a9b8db09c81f997ebe.png


//4. 编写代码,演示多个字符从两端移动,向中间汇聚

int main()
{
	char arr1[] = "abcrerewf";
	char arr2[] = "*********";
	int left = 0;
	int right = strlen(arr1) - 1;
	while (left<=right)

	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		printf("%s\n", arr2);
	     Sleep(1000);
		left++;
		right--;
	}
	
	return 0;
}

这里的Sleep是时间戳头文件是<windows.h>文件,就是起延时作用

system("cls"),头文件是<stdlib.h>,是一个清空屏幕的效果。

ce549280e49648ee8855732ce79cf004.png

c556a5bca1734ac784d05fb90aa26269.png

6d05456bfc16478ca7ddccc0b1db3e9e.png

50ff4d0e0ec6432f8326be3bacfc5f0e.png

//5. 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则
//提示登录成,如果三次均输入错误,则退出程序。4

int main()
{
	char password[20] = { 0 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("请输入密码:");
		scanf("%s", password);//假设密码是123;
		if (strcmp(password, "123") == 0)
		{
			printf("登录成功\n");
			break;

		}
		else
		{
			printf("密码错误\n");

		}
	}
	if (i == 3)
	{
		printf("三次全部错误退出程序\n");
	}
		return 0;
}

14cf0e114b60438794daed93089f7cbd.png

 猜数字游戏

dfef5221088246b6a086b1fbe278f6a1.png

猜数字游戏

1.电脑产生一个随机数

2.猜数字

3.猜大了

4.猜小了

5.直到猜对了,结束。

time(NULL)获得时间戳。头文件是<time.h>

rand()生成随机数的库函数

调用rand()之前必须调用srand(填一个随机数),头文件是<stdlib.>

生成随机数srand(usigned int ) time(NULL),这个就是生成随机数

但是这个只能调用一次。

//猜数字游戏实现

void game()
{
	int n = 0;
	int red =rand()%100+1;
	//2猜数字
	while (1)
	{
		printf("请猜数字\n");
		scanf("%d", &n);
		if (n < red)
		{
			printf("猜小了\n");
		}
		else if(n > red)
		{
			printf("猜大了\n");

		}
		else
		{
			printf("猜对了\n");
			break;
		}

	}


}
void meau()
{
	printf("*********************\n");
	printf("****** 1.play  ******\n");
	printf("****** 0.exit  ******\n");

}
int main()
{
	int input = 0;
	do
	{
		srand((unsigned)time(NULL));//产生随机数
		meau();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		    // 猜数字
			game();
				break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

b7f2ec0caf6949e683e25e7bb84ce6f3.png

 关机程序

858c1cb5f9da4eef91eb800f73c4eb36.png

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>//strcmp()函数的头文件
int main()
{
	char input[20] = { 0 };
	system("shutdown -s -t 120");// 必须写成1这个样子

		while (1)
		{
			printf("请注意,你的电脑在120秒内关机,如果输入:我是你爹,就取消关机\n");
			scanf("%s", input);
			if (strcmp(input, "爹") == 0)
			{
				system("shutdown -a");
				break;
			}
		}

	return 0;
}

fb4e506c821f4ba5880c4dbf5fc0ece5.png

函数的详解 287dffb7eeab42778a8718c39bab0ef7.png

fb14494aacc941448781e4cbfe7e9578.png

0e486e70ff1c41759192751bd2ef5b39.png

98d444cd09b443dba2eb7a2495f8bbd6.png

89afad1eab6547bd83a3df608ccbdbcc.png

当实参传递给形参的时候,形参是实参的一份临时拷贝。

对形参的修改不会影响实参。

只有通过指针来存储地址,指针来进行修改实参的值 

 a9f0304e938f4a85a2ca2aadd07b28cb.png

3da63d8266a94ee0805c0b037bdc54b7.png

a5607787ff4c4db0bb4783daa7d8b069.png

 函数题目

c7020ed33b3246c7aa3b148fb594ebd5.png

6857bb564c434c8f8dc289e5859548e8.png

bc9f5bb9b8ca4647892395716ec00c2b.png

素数,只能被1和他本身整数的数叫素数

比如7 只能被1和7整除的,拿2~(i-1)的数来试,如果发现2到(i-1)有数字来整除j,说明i不是素数。

6fde200fdb3c44a39dadb9d1aeae4c53.png

没必要从2到i-1之前来判断是不是素数,只需要判断sqrt(m)里的数是不是素数。

写一个函数可以判断一个数是不是素数。

打印100~200之间的素数
素数是只能被1和他本身整除的数
7
1和7整除
2 3 4 5 6

int main()
{
	int i = 0;
	int count = 0;
	for (i = 100; i <= 200; i++)
	{
		//判断i是否为素数
		//是素数就打印
		//拿2~i-1之间的数字去试除i
		int flag = 1;//flag是1,表示是素数
		int j = 0;
		for (j = 2; j <= i - 1; j++)
		{
			if (i % j == 0)
			{
				flag = 0;
				break;
			}
		}

		if (flag == 1)
		{
			count++;
			printf("%d ", i);
		}
		
	}
	printf("\ncount = %d\n", count);

	return 0;
}

#include <math.h>

sqrt是数学库函数
开平方
math.h

int main()
{
	int i = 0;
	int count = 0;
	for (i = 101; i <= 200; i+=2)
	{
		//判断i是否为素数
		//是素数就打印
		//拿2~i-1之间的数字去试除i
		int flag = 1;//flag是1,表示是素数
		int j = 0;
		for (j = 2; j <= sqrt(i); j++)
		{
			if (i % j == 0)
			{
				flag = 0;
				break;
			}
		}

		if (flag == 1)
		{
			count++;
			printf("%d ", i);
		}

	}
	printf("\ncount = %d\n", count);

	return 0;
}


写一个函数可以判断一个数是不是素数。

是素数返回1
不是素数返回0

int is_prime(int n)
{
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
		if (n % j == 0)
		{
			return 0;
		}
	}

	return 1;
}

int main()
{
	int i = 0;
	int count = 0;
	for (i = 101; i <= 200; i+=2)
	{
		//判断i是否为素数
		//是素数就打印
		//拿2~i-1之间的数字去试除i
		if (is_prime(i))
		{
			printf("%d ", i);
			count++;
		}

	}
	printf("\ncount = %d\n", count);

	return 0;
}

9c85ec5b3f3a43a1b60a24938b766fe3.png

 ba2f19dfb95f460380179c09fe2360c1.png

//2. 写一个函数判断一年是不是闰年。
//能被4整除并且不能被100整除的是闰年或者被400整除的是闰年
//1000-20000
int main()
{
	int i = 0;
	for (i = 1000; i <=2000; i++)
	{
		if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0)
		{
			printf("%d ", i);
		}
	}
	printf("\n");
	
	return 0;
}
int is_leap_year(int i)
{
	if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	int i = 0;
	for (i = 1000; i <=2000; i++)
	{
		if (is_leap_year(i))
		{
			printf("%d ", i);
		}
	}
	printf("\n");
	
	return 0;
}

48f80d7c177f4afca29740147d0f83ca.png

//二分查找
int cz(int arr[], int k, int sz)
{
	int left = 0;
	int right = sz - 1;

	while(left<=right)
	{
		int mid = left + (right - left) / 2;
		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			return mid;		
		}
	}
	return -1;	
}
int main()
{
	int arr[] = { 1,2,3,4,5 ,6,7,8};
	int k = 3;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int j = 0;
	int ret=cz(arr, k, sz);
		if (ret == -1)			
		{
			printf("找不到\n");
		}
		else
		{
			printf("找到了下标是%d\n", ret);
		}	
	return 0;
}

d838388f3c474a90a3be71ed479b5394.png

不要在函数内部求参数元素的大小

8cf6dfd786bf466dbe996305b4cfc6e0.png

//4. 写一个函数,每调用一次这个函数,就会将 num 的值增加1。
void add(int* p)
{
	(*p)++;
}
int main()
{
	int n = 0;
     add(&n);
	 printf("%d\n", n);
	 add(&n);
	 printf("%d\n", n);
	return 0;
}

 a6a4407eca1e497cb961ddd3b6b04e18.png

  

函数的嵌套

a936128777b0413790a05c34929b582d.png

链式访问。 

5e2261169d8a4679864564b41ea2bb0f.png

printf()函数返回值看字符个数,首先看最内层的函数,打印43,有二个字符,返回2,一个字符返回1

2a0a84d1a82d4c73867d7f7d605adf8b.png

虽然默认是int类型,但是不能这样做。非常不好

函数的递归

858dcd108a79425196e2631ef94bfa34.png

 de1d56b3f5da42fdb9ef00e03895b1b9.png

27b5b80369a94ad8997bb7235e5d39ec.png

这个是倒置的 

讲解递归方法 

af926da6b65c444ab40461adae725e51.png

271f0b66c7364459811e6d801966392b.png

8cd6efd019b645cf9ace9f85991e38b1.png

 24f3fb7e36cd4a8c8a3a35e68a8177aa.png

void  print(int n)
{
	if (n > 9)
	{
		print(n / 10);
	
	}
	printf("%d ", n % 10);
	
}
int main()
{
	int num = 0;
	scanf("%d", &num);
	print(num);
	

	return 0;
}

4bf90dddfbec44e5bbe8d4c9902b47a5.png

 a7a06c6311e6475bbb22203992dfb73f.png

1353a270a6a9457c84f71b35a1a1ef0e.png

8c618ab511c34fd295653de85a92fb56.png

8766392949124959b4802bac14d8c9bc.png

711e423280c142dda60e4b283ae5ad0a.png

这里的arr传的是首元素地址,地址就是用指针来接受,来存储,*p解引用来找到元素一直自加,直到找到\0才结束。

b12b01ac628d45b7a30ceee09639a425.png

4c8098bc583b42ef87afbc3ba08beb3a.png

这里的my_strlen(str+1),str是字符a地址,加一是字符b的地址 1+bc的长度

07b8cde2f36248829e837149ba529d13.png

d8b35cb3723041c3938b32df35a51dc2.png

76e08fc61c724968ab7d81f6c6aeaf2a.png

//实习自定义求字符串长度函数
int my_strlen(char* p)
{
	if (*p != '\0')
	{
		return 1 + my_strlen(p + 1);

	}
	else
		return 0;

}
int main()
{
	char arr[] = "abc";//[a b c\0]
	int ret=my_strlen(arr);
	printf("%d\n", ret);

	return 0;
}

aaed120eb4d94f1097f9ba2b962ef932.png


//例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19
int DigitSum(int n)
{
	if (n > 9)
		return DigitSum(n / 10) + n % 10;
	else
		return 1;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = DigitSum(n);
	printf("%d ", ret);
	return 0;
}

函数的迭代与递归

0e3ad94c00b94fbf98c2363b263df19f.png

1!=1,2!=2*1,3!=3*2*1...................

//利用递归求n的阶乘
int dg(int n)
{
	if (n <= 1)
	{
		return 1;
	}
	return n * dg(n - 1);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret=dg(n);
	printf("%d\n", ret);
	return 0;
}

12f6e57e86d64c06a8ad9f8e9b2785c0.png

函数栈帧

每一次函数调用,都会向内存栈区申请一块空间,这一块空间主要是用来存放函数中的局部变量,和函数调用过程的上下文的信息,这个一块空间一般叫:函数的运行时堆栈,也叫函数栈帧空间编译会自动根据需要开辟空间的。

779d8e65c5c54147a170e7df3eaaaf5d.png

2599b0936aed48cb95094f53673a213d.png

         

        

斐波拉契数

c2afe21d8984433094ed56518df7c856.png

 ece9c5aad6674fef8cc148219d91b143.png

//斐波那契数
//前二个相加等于后面一个
int fib(int n)
{
	if (n <= 2)
	{
		return 1;
	}
	else
		return fib(n - 1) + fib(n - 2);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d", ret);
	return 0;
}

 //斐波那契数
//前二个相加等于后面一个

 1 1 2 3 5 8 13 21 34 ......

a92753f3b9a947e181d5ac9905f59025.png

88b0469a183248d292b1c40f6c3ca3cb.png

 产生新的a和b0e3ccb89bf184c61940b1e0bc764ad91.png

 e4843e1782c34701abee23fa22821fa8.png

2fc9aab2182c49a38cb7459b732bfaaa.png


//斐波那契数
//前二个相加等于后面一个
int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 0;
	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d", ret);
	return 0;
}

辗转相除法

61ec6ee0917d4d60a2492558a9a3187d.png

 就是把a整除b的结果不等于0,就把c赋给b,b赋值给a,直到整除结果等于0,那个除的就是最大公约数。

4abbf929a4f1494a9df454cc10894441.png

 最小公倍数就是二个数相乘除以最大公约数。

int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	scanf("%d %d", &a, &b);
	while (a % b) {

		c = a % b;
		a = b;
		b = c;
	
	}
	printf("最大公约数%d\n", b);
	int ret = a * b / b;
	printf("最小公倍数%d\n", ret);

	return 0;
}

dfba10f4e09743e99fd03ee0a5cd5964.png

b3db780b93e84f4d9783346757d7b266.png

 db1ec33138964af6868793b11c2cc64c.png

//计算1 / 1 - 1 / 2 + 1 / 3 - 1 / 4 + 1 / 5 …… + 1 / 99 - 1 / 100 的值,打印出结果
//分子总是1,分母是1-100
int main()
{
	int i = 0;
	double sum = 0;
	int flag = 1;
	for (i = 1; i <= 100; i++)
	{
		sum += flag * (1.0 / i);
		flag = -flag;
	}
	printf("%lf", sum);
	return 0;
}

99乘法表

62a2936cae8f43ac810044bfb45e151a.png

66e4931cd30942e68bf8de7d08baafe7.png

 425ee53d9cc44031b5887e487c437984.png21bc6e43d4d34bce85543ced1192ca9d.png

 数组的讲解

0a9d606301204d08b4da7c1385136ed1.png

605a8b0b45b643688c5da06929dfe619.png

 ac4ccc51c8e04a7c8edbe125c9a2746e.png

 8150f027fb13486dbe608d210bc2345e.png

【】下标引用操作符号。 

3f20ee3514004392b7d2d6479be295d8.png

9eaa5ff76c65434e951a0b700f758b85.png

 ecdc2d2e3dc24574a77a74312339c19f.png

int类型地址相差4个字节。

952dc84f5dfb40d3a6ed55fc7e848121.png

 f29e5b5dea1f483381dadf66482fd4b0.png

数组在内存是连续存放的。 

f68bae535dfa48568b38df1cfd922b1a.png

二维数组不能省约列

 

26e4900a439c4a87a1c96592144e9cfd.png

 行和列的下标都是从0开始的

2cc910ee93a948e6a7ea7cbc2f431ce4.png
 可以把二维数组理解为一维数组的数组

6d35379ebcdf4872bad430ff7163fd3b.png

可以把arr【0】看成第一行的数组名。数组名家【j】可以访问这一行的某个元素。

e3c0f5fe30fb4c708d574c8222275c5e.png

 二维数组在数组中也是连续存放的

a6a8d397790248a19c1c94a8df53f280.png

 dcbca35581fe43d09412092efee0cee6.png

 a43b5394eabd41389c5c1acc16b33f7e.png

 越界了。

4578b729bfdb40089acb59dd7b01aa53.png

01c4199be79f4437a1d4470010dd521f.png

3b250598975c4f4c85e96a5c120cdd28.png

数组传参只传数组名。 

数组名

数组名是首元素地址。

c1903bb07a3546fda84f81ab375c3c4c.png

a8d69c8bfb6a4ddc95a39133e1a2cff4.png

取地址数组名 也是从首元素地址开始取的。

纠正

数组名能表示首元素地址但是有二个意外

1。sizeof(数组名)这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2.取地址数组名 ,这里的数组名表示的是整个数组,取出的是整个数组的地址。因为&arr是取的整个数组,也是从arr[0]开始取的,他会一直取到这个数组完为止。

除了这二个以外,其他的数组名就是首元素地址。

6739f19eb2114550a4e3a3ca33184315.png

b15498c711174aa09c0075587173d183.png

取地址数组+1跳过的整个个数组的。

f5b2eb02765c41cdaec2e9098c334994.png

二维数组的数组名理解

acc9ed3459b54944aae0c7ab7cb5f014.png

二维数组的数组名表示的是第一行的地址。

 我们要把二维数组想象成一维数组,相对于图中的有三行,就相当于有3个元素。

1行一个元素。那二维数组名表示首元素地址,就是第一行的地址,也就是第一个元素的地址。就是这个一维数组的地址。数组名表示这一行的地址,这一行也就是一维数组的地址。不是第一行第一个元素的地址。

arr+1表示第二行地址。

sizeof()arr/arr[0]                          这个计算的是行数

sizeof() arr[0]/arr[0][0]            一行的大小除以第一行第一个元素的大小就是求列数

1d71fbca9722434f9e055b2baea190f6.png

int main()
{
	int arr[3][4] = { 0 };
	printf("%d\n", sizeof(arr) / sizeof(arr[0]));//3  计算的是行数
	//这个sizeof(arr)计算的是整个数组的大小是48,sizeof(arr[0]) ,arr[0]是第一行的数组名 
	//第一行的数组名 放在sizeof内部,计算的是这一行的大小是16  ,因为一行有4个元素
	printf("%d\n", sizeof(arr[0]) / sizeof(arr[0][0]));//4  计算的是列数
	//sizeof(arr[0])计算的是一行的大小,是16,sizeof(arr[0][0])计算的是第一行第一个元素的大小是4。

	return 0;
}

aa1754ee7aa849fc8962b2d3acdec89a.png 

记住第一行的数组名是个地址,用sizeof内部起来就是一个字节大小。除了那二个特例看到数组名就是首元素地址,地址就是指针,记住,二维数组的数组名也是地址,只不过是第一行的地址。就是一维数组的地址。

可以把arr【0】看成第一行的数组名。

arr是二维数组的话,&arr是取二维数组的地址。

冒泡排序

7c3a6138453b4ef9a7b4a79c2303e74d.png

b4f7827c0c0f48a496ff00c4446b7e97.png

 冒泡的核心就是n个元素要比n-1趟,并且每一趟的元素在减少。比如10个元素要比9趟,第一趟比9趟,第二次比8趟,元素也从10个变成9个。

955215f4f9f7492ea6f1604e8d2915dd.png

//冒泡排序
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	//10个元素,10-1趟
	for (i = 0; i < sz - 1; i++)//
	{
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])//交换
			{
				int tep = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tep;
			}
		}

	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	  bubble_sort(arr,sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	
	return 0;
}

arr[j]>arr[j+1]这个是升序,把大于改成小于就是降序

7dc58b2da4304414808a07c1a079db90.png

 f9eb54bbc91840c5adaf9e57da811c84.png

三子棋程序

64c45d77383f4c14823ba933d63358a9.png

d45e4d16c6d14251be01d0bca0ddc1ec.png

字符串逆序

d213001505d04f2c89595be879b3f05e.png

 递归版本

就是先把a与f交换在逆序bcde,然后在交换b与e,逆序cd

a1b6cd71cd924c90988239abf22d29ef.png

迭代版本

 c23ca60a88db45d9a3932ea5ea0d5391.png

//字符逆序
void reveser(char arr[])
{
	int right = strlen(arr) - 1;//下标是从0开始的
	int left = 0;
	while (left < right)
	{
		int tep = arr[left];
		arr[left] = arr[right];
		arr[right] = tep;
		left++;
		right--;
	}
}
int main()
{
	char arr[] = "abcdefg";
	reveser(arr);
	printf("%s", arr);
	return 0;
}

核心就是交换。

计算一个数的每位之和

68c7f4bb1469434bbd39fe61bbcd2901.png

a9caade8fbfc4e76b70d48ae46acdc52.png

//写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
//例如,调用DigitSum(1729),则应该返回1 + 7 + 2 + 9,它的和是19
//输入:1729,输出:19
int print(int n)
{
	if (n > 9) {
		return  print(n / 10) + n % 10;//123  4
	}
	else
	{
		return n;
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum=print(n);
	printf("%d\n", sum);
	return 0;
}

 //编写一个函数实现n的k次方,使用递归实现

93b92d78e0804ce5afd997c195c3b365.png

n的k次方相当  于n*n的(k-1)次方

23fa22b38a4843c8837e58e1fb4a8906.png

 260eb19641ab40298589c0d4756851ac.png

 df5dd9efe2354cad9b0f699ccc80f099.png

//编写一个函数实现n的k次方,使用递归实现

double pow(int a, int b)
{
	if (b > 0)
	{
		return a * pow(a, b - 1);
	}
	else if (b == 0)
	{
		return 1;
	}
	else
	{
		return 1.0 / pow(a, -b);
	}
}
int main()
{

	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	double ret = pow(a, b);
	printf("%lf", ret);
	return 0;
}

82d8d894816d4df8a50d6de4ce1eac74.png

 交换数组

2e63067fd63d4b89a98a7339f79986e6.png

//创建一个整形数组,完成对数组的操作
//实现函数init() 初始化数组为全0
//实现print()  打印数组的每个元素
//实现reverse()  函数完成数组元素的逆置。
//要求:自己设计以上函数的参数,返回值
void reverse(int arr[], int sz)
{
	int left = 0;
	int right = sz - 1;
	while (left < right)
	{
		int tep = arr[left];
		arr[left] = arr[right];
		arr[right] = tep;
		left++;
		right--;

	}
}
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void init(int arr1[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		arr1[i] = 0;
	}
}
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int sz = sizeof(arr1) / sizeof(arr1[0]);
	print(arr1, sz);
	reverse(arr1, sz);
	print(arr1, sz);
	init(arr1, sz);
	print(arr1, sz);

	return 0;
}

操作符详解

20df79a3aa584d458f41f61cd4fd01f3.png

59ccb1cafc44467090cbee4f0923881d.png

 1f9a8984a35849f7980572a5a7ee82d5.png

正数的原码反码补码相同

负数的原码最高位数是1,正数为0

 整数在内存中存储的是补码

ae916ba8b29b4805a5182aaa105c963b.png

负数的左移与右移,移的是补码,打印的是源码

补码-1取反就是原码。

左移有乘2的效果

22f45f0ad5d94130b0b112354ed389ce.png

左移和右移只针对整数。

vs里的右移操作赋采用的是算数右移,右边丢弃、左边补原符号位

符号位是根据正数还是负数来确定的,正数补0,负数补1.

657de460d8084c82a55f82dcaa8fb389.png

5d85f96f4a70465faa54f36d6000ab0e.png

总结:

计算的是以补码形式计算,打印的是以原码形式存在。

be7d6b6419ed43fb8ebdd09799e03fb8.png

cefaf8bc27a343c68243351d1bbb30fa.png

 计算的时候要用补码,因为整数在内存中存储的是补码。

按位与二个同时为1就得1,有一个不得1就为0.

9b0ce62655884c28bc695265ffb95a09.png

 取反的时候不要动符号位。

异或是相同为0,相异为1

e559e28e2129405abbf7b92c2ae88be1.png

1b312297a1de4e1fa6b9da33cd50f237.png

8d9d15b356c849f6bc389cc6cb264e46.png

0异或a是a  a异或a是0 

异或支持交换律

5167124baa004afa948ed23cedfb0153.png

统计二进制中有多少个1

96f47f4f881941e7b12b0f53a14caad9.png

任何一个数a按位与1如果==1就说明a的二进制最低是1

a&1==0 说明a的二进制最低位是0

c4a59502c50e4c1daba0bd08f226ad17.png

就比如这段二进制代码 a&1 第1位是0 就&1 得0 就跳过最低位,看第二位,如果第二位&1等于1就让COUNT++,一直循环下去。这样就可以知道这段二进制代码有多少个1了。

方法1 利用&和>>操作符计算出二进制代码有多少个1.

98906b083a2942ec8a1e26a350a1446c.png

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int i = 0;
	int a = 0;
	while (~scanf("%d", &a))
	{
		int count = 0;
		for (i = 0; i < 32; i++)
		{
			if ((a >> i) & 1 == 1)
			{
				count++;
			}
		}
		printf("%d\n", count);
	}	
	return 0;
} 

46313fdf5f6d400692b37499f76a3e13.png

总结
把一个二进制向右移动i个位移到最低位和1进行按位与进行比,如果==1就说明二进制有1 

从右往左看,每次移到的位位数再增加,最高是32位

方法2:利用/2  %2的方法来

bf4d544d04d54c2bbc6551abe6f6f731.png

ee72854b22c845ad99a859e5a4f285ec.png

想得到二进制的每一位 /2  %2来解决这个问题

eeb6c6d905b2468eaa43cf6293695e0c.png

//考虑正负数问题
int count_one_bit(unsigned int n)
{
	int count = 0;
	while (n)
	{
		if ((n % 2) == 1)
		{
			count++;
		
		}
		n /= 2;
	}
	return count;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret=count_one_bit(n);
	printf("%d\n", ret);
	return 0;
}

方法3

利用n=n&(n-1)来达到把n的二进制最右边的1去掉

比如n=-1

1111 n

1110 n-1

1110 n

1101 n-1

1100 n

1011 n-1

1000 n

0111 n-1

0000 n

利用这种效果来实现二进制代码有多少个1

55903e705f8043d5afa31cd007c5b020.png

//判断一个数是否是2的次方数

2ebdcb2770d9419d994cd8e0276ef4dd.png

//判断一个数的是否是2的次方数
//n=n&(n-1)
// 0001
//0010
//0100
//1000
int main()
{
	int n = 0;
	scanf("%d", &n);
	if ((n & (n - 1)) == 0)
	{
		printf("YES\n");
	}
	else
	{
		printf("NO\n");

	}
	return 0;
}

练习2          二进制位 置0或者置 1 

编写代码将13⼆进制序列的第5位修改为1,然后再改回0?

4e47685dd455409c8a9fe9ce79ad90e1.png

443b91284ab4404e9bc65f924db53f9b.png

//编写代码将13⼆进制序列的第5位修改为1,然后再改回0 ?
// 0001 1101
int main()
{
	int n = 0;
	scanf("%d", &n);	// 13  0000 1101 
	//移到
	n = n | (1 << 4);  // 0000 1101 | 0001 0000==0001 1101
	printf("%d\n", n);
	//复原
//   0001 1101 变成 0000 1101
//   按位与1110 1111 即可   怎么变呢  1的二进制是  0000 0001 左移4位 0001 0000然后取反 1110 1111
	n = n & ~(1 << 4);
	printf("%d\n", n);
	return 0;
}

获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列

3412eade4862443d9465e5b2e53c2125.png

总结方法:

输入的数只要往右移,把每一个数移到最低位按位与上一个1就可以获得最低的是否是1了,可以明确判断出你输入的数转换成二进制形式有多少个1.

编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同

6397adc8ead54802afced720dc7c2a06.png

0321b6d5f2c34d429764119352c0bcfc.png

10ccecb5a11b4499a63e440e2aa244e5.png

795c1ddb117d43e6a445672d2a2b40a2.png

语法说说支持连续赋值,建议写代码不要这么写。

354688d9e71745e5bbdfe067b7fa12fc.png

53b9e24fc0704b70881bec1a2aef4284.png

96ca1e12b07046a08772322659a89cb5.png

c81495572a44425f89589b854646bc97.png

效果一样。 

强制类型转换。 

f72b86df9f934a7ca69681a1a709cd98.png

7c77415756154d7db08374f96e4bffe0.png

afa731b646854ef0bc808101208aafdf.png

 1是40 2是4/8,3是10,4是4/8

b0257a30324948599e7c25449a0512a0.png

23cbcf305d7f47c294b43831de6b2264.png

逻辑与,左边为假右边就不计算了。

逻辑或,左边为真,右边就不计算了

 0428c84ddcc940ad896e42f1e5a87625.png

逗号表达式是从左到右计算,整个表达式的结果是最后一个表达式的结果。

237dc5030fa34438b0b3e2e51fa8cb22.png

99735537754a49b5bfb6a27c2c83e155.png

 50a22fc78f0d430bbb57a1289a77ddf9.png

b7767d114f4e469788162019df18ae4b.png

edba08ff4104420ab324d7fecd9ba2ac.png

5ac4a07cf140431387b1c46815667539.png

整型提升

b5f9e9c5d7854700a0cf2da7bd61e0a9.png

61256e0a4dc544889cc0b99ba0326d66.png

 d67e20c0c212487e8348debaf7e80abb.pngd3a790ba9ace421d840ad465a36e8a3a.png

20039b6e649a4e5aa4e4082e942ab1ee.png

一个char类型的占1个bit 相对于8个biye位,他要整型提升是因为    难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)
。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。

例如图中的a为5,b为126  先把a的原码写出来相加得出来后8位,首先看最高位是0或者1,是1补1,是0补0,得出来的是补码,因为整数在内存中的存储是补码形式的,然后-1取反得原码,因为打印的都是以原码形式打印的。

a与b会发生整形提升,a与b的值会发生变化。

f8be04e04df044dd9e0fd7a4f2a709a5.png

无符号的数发生整形提升,高位补0

55b1bba5aa7345cfb685333279cc59f4.png

上面的这些大小都小于int类型

下面讨论大小大于或者等于int类型的整形提升

31786e7b602545569db9c4d96149ed1a.png

这些类型都是向上转换的。

91ac16415dbf4a92aa7e140464a5898b.png

数据存储

72dadfe03e46428984b4b1771153d8f4.png

01b15cdb50ce411fa3a09a96ab239664.png

107135d8c33e4403a63af8d2d6e3fc72.png

有符号数直接用int或者 signed int类型的

无符号直接用unsigned int  类型的

05064003bebb4cef89bdb54f16a176f7.png

无符号不分最高位是正数还是负数,统一正数。

a44d106b60f84f7aab17d3d255998764.png

1e2e1ca68c0f4b50bc9e0c98fe6cea05.png

整形在内存中以补码形式存储

1fe5e35fe0a3453daf909930b2d19a7b.png

2f20eef4bf7a4b11a5a64c1d09d7492c.png

1+-1是通过补码形式实现+-的

原码取反+1得补码

补码取反+1得原码

6d4d46e90ffc4d12baeac6dad18de031.png

052ed810fa6a4c24a3da1328301249f1.png

把一个数的高位字节序内容放在低地址处,把一个低位字节序内容放在高地址处。这个叫大端存储

把一个数的高位字节序内容放在高地址处,把一个低位字节序内容放在地地址处 这个叫小端存储

7073939ef6f24af49ab6f83003497eca.png

放与拿相反。比如放进去的是44 33 22 11,拿出去的是11 22 33 44

vs里存的是小端存储模式

63fb56f41f9948669888d14190ecbe81.png

2d699b757e144248be6ce70139404768.png

667cdd5936f346ba9cb74071c9e65569.png

主要看起始地址,对起始地址解引用看是不是1还是0,并且还要强制类型转换成char*类型,int类型一次性访问4个字节。

3ad494e4b4614b30b02c76676d1c7b5c.png

c1b1c77c4d4847caa7f8529f41f02fad.png

0861437787ed4889988aed34bc681fd5.png

30011b4925794b4c822c36960c5a6edb.png

1558d53ff0f047939d03f910a96333fe.png

由于c是无符号数,整形提升的时候最高位是1,由于是无符号数,前面直接补0,所以是255,正数的原码补码,反码一样。

35cc3d7147e2418a89eef06305cee350.png

字符串函数

1.strlen函数  求字符串长度的

3b35eb199cd0430fb3e8e7e5fc335220.png

a993220bfbba4ccfaaeb3d7b8312ccd7.png

11c1f0c3b33b45d39bb41a15534d03c1.png

836c4988d90e4dd8bedf539b4c23d1c9.png

bb63e47ff54443b8a33483bf292cc1e5.png

b62d5280c5344ea695f8212cf04e5e66.png



//求字符串长度
int my_strlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count++;
		*str++;
		
	}
	return count;
}
int main()
{
	char arr[] = "abcdef";
	int red = my_strlen(arr);
	printf("%d\n", red);
	return 0;
}

2.  strcpy  字符串拷贝

78838a2286914b4e942f6eaa8c7ffbfb.png

992326c6adff41d6aea4c3e0640f734c.png7b3590430b6a424a8e263478e3144d0a.png

fadcde6fe1934434946c043eb97f5c27.png

846e16bff81045c7aae0bd5b2af8db6a.png

//求字符串拷贝
char* my_strcpy(char* arr2, char* arr1)
{
	char *red = arr2;
	while (*arr2++ = *arr1++)//'\0'赋给arr2时arr2为假就停止循环
	{
		;
	}
	return red;

}
int main()
{

	char arr1[] = { "abc" };
	char arr2[40] = { 0 };
	 my_strcpy(arr2, arr1);
	printf("%s", arr2);
	return 0;
}

3、strcat  字符串连接

56f2507cb157429b8318b711907fae7e.png

9b6bce9f8621485d9b7068f6eb7a4406.png

3e7cfbfd28a84bcfb69d8e47ca486cf7.png

d8aed949c9d643f9a53a90ae1d6ec1d0.png

先找到需要连接的字符串末端'\0'处,然后拷贝

a20eae6b904046df82ddb744638a3ebb.png

拷贝是在\0这里进行拷贝。

//字符串连接
char* my_strcat(char* arr1, char* arr2)
{
	char* ret=arr1;
	while (*arr1 != '\0')
	{
		arr1++;
	}
	while (*arr1++ = *arr2++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[40] = { "hello" };
	char arr2[] = "bit";
	my_strcat(arr1, arr2);
	printf("%s", arr1);
	return 0;
}

4.strcmp-字符串比较函数。

3fef40e824594c87b6a73573d208211a.png

因为这个是二个不同的数组名,他们比较的肯定不一样,因为首元素地址不同,所以比较就不相同,这里比较的不是内容,是地址。

e5fd2abddb2141e7b809b53ed3dbec39.png

比较二个字符串应该用strcmp函数

f619af0b07374aebb816feac424e4e84.png

61cf29edf6754afcb11e6fcba6e7ed27.png

//求字符串比较函数
int my_strcmp(char* arr1, char* arr2)
{
	while (*arr1 == *arr2)
	{
		if (*arr1 == '\0')
		{
			return 0;//相等
			arr1++;
			arr2++;
		}
		return (*arr1 - *arr2);
		
	}
}
int main()
{
	char arr1[] = "abc";
	char arr2[]="bcd";
	int ret = my_strcmp(arr1, arr2);
	if (ret > 0)
	{
		printf(">0\n");
	}
	else if (ret<0)
	{
		printf("<0\n");

	}
	else
	{
		printf("==0\n");

	}
	return 0;
}

eead6e8f7cb7448e98ffa242afa5874b.png

这个代码关键就是*arr1和*arr2相同的情况下遇到'\0时应该终止循环,返回相等。

232f2bd27ba74a6f9de66e2bceec748d.png

1.strncpy  -字符串拷贝。

70d1cb5b5bf548bdae99d135dfbe0d63.png

2.strncat(字符串连接)

e017ba5774d94e48b51aff8597a1f176.png

加了n就是加了限定的条件,比如strncmp

3.strstr -----查找子串

41b2d5f94cc14ca6aae50816b105bdc2.png

如果找到了子串就返回第一个字符的地址,也就是b字符的地址,因为是从b字符开始才算找到子串。找不到返回空指针,NULL

//模拟实现一下strstr
char* my_strstr(char* arr1, char* arr2)
{
	char* s1 = arr1;
	char* s2 = arr2;
	char* p = arr1;
	while (*p)
	{
		s1 = p;
		s2 = arr2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 =='\0')
		{
			return p;
        }
		p++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "www.h";
	char arr2[] = "w.h";
	char* ret = my_strstr(arr1, arr2);
	if (ret == NULL)
	{
		printf("子串不存在\n");
	}
	else
	{
	 printf("%s\n", ret);
	}
	return 0;
}

95ed4b4084524f92905c93d182223639.png

673f65abf03f48babe13a03b7176eebd.png


strtok函数

427bd67eb84b42ffbe84b538c24732f9.png
bb75a672aae7437c8ac37c241c0694ae.png

c66490507e684190bc5745b4bbc46682.png

//strcok()函数
#include <string.h>
int main()
{
	char arr1[] = "zhangsan@1666.com";
	char arr2[30] = { 0 };
	strcpy(arr2, arr1); 
	const char* p = "@.";
	char* s2 = NULL;

	// 初始化只是初始话一次
	for (s2= strtok(arr2, p); s2 != NULL; s2=strtok(NULL, p))
	{
		printf("%s\n", s2);
			
	}
	return 0;
}


3. strerror?函数的使⽤?

6a02b68072014643a83379769f7ee5b6.png

strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。?
在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在 errno.h 这个头⽂件中说明
的,C语⾔程序启动的时候就会使⽤⼀个全⾯的变量errno来记录程序的当前错误码,只不过程序启动
的时候errno是0,表⽰没有错误,当我们在使⽤标准库中的函数的时候发⽣了某种错误,就会讲对应
的错误码,存放在errno中,⽽⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是
有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。?

68625cc7fdd544cb90bf83b4b9c7ea78.png


#include <errno.h>
int main()
{
	for (int i = 0; i < 10; i++)
	{
		printf("%s\n", strerror(i));
	}
	return 0;
}

19808a9d82e242169f05ae92896d359b.png

96ef13a63dc6424a93a5343320385c65.png

4.memcpy  -内存拷贝

851d3c7f23ba44ddbe3a74216e71083b.png

4a8c58afac3f49c490ccf88f27e6e512.png

81e67cee758545f7b500564a0fc2c3d1.png


模拟实现memcpy函数
void* my_memcpy(void* dest, void* src,size_t num)
{
	char* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;

}
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memcpy(arr1 + 2, arr1, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
 }
	return 0;
}
//不能解决重叠问题
重叠问题

6f7550548a08479688237b95f5efec43.png

3ee32f320cde4731967af3d4aab33722.png

memmove使⽤和模拟实现

3947e8552f0c4f6d86d4a22cd8f726f9.png

int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr1 + 2, arr1, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
 }
	return 0;
}
//不能解决重叠问题

2ab73a7200d84ac9be06e4edecfe743a.png

memset函数的使⽤

f056fe03e4854b07b32afd3431751ace.png

090ecb45d37548b2b29ca44a67c830bc.png

//memset内存设置
int main()
{
	char arr[] = "hello bite";
	memset(arr, 'x', 5);//以字节为单位来设置的
	printf("%s\n", arr);
	return 0;
}

 memcmp函数的使⽤

37593ba8b8924f69974f5e6c3ddb480d.png

//memcmp  函数的使⽤


int main()
{
	int arr1[] = { 1,2,3,4,5,6 };
	int arr2[] = { 1,2,3,4,6 };
	int ret = memcmp(arr1, arr2, 17);
	printf("%d\n", ret);
	return 0;
}

结构体

1223d0f4f0cd4af3a6467fda30518095.png

072475d9f56a4129b70c7ac0154537a1.png

96a6d38e9cc740f9b637268eb40911a3.png

d4f30bae32b14f6190730b20c7632883.png

//结构体
struct stu 
{
	int age;
	char arr[20];

};
struct bb
{
	struct stu s;
	int v;
};
int main()

{
	struct bb s1 = { {12,"cs"},66 };
	printf("%d %s %d", s1.s.age, s1.s.arr, s1.v);
	return 0;

}

结构体也可以定义结构体。

结构体的自引用

结构体也可以调用自己,只不过要存自己的的地址------>结构体的自引用

420f67118fb345b3a0bec5178b557f94.png

7db58dc7cff044f2bcc8f8a17b342cde.png

结构体内存对齐

86abd1f0452d40b58fbdbc2a03be6e77.png

cfa6f7a290e44022a766bc2c268d7ebc.png

首先找对c1,i,c2对应在内存空间的存储位置,比如c1在偏移量为0的位置,i占4个字节,4与8找一个最小值,就是4了,应该在这个数的整数倍位置存储,图中就是在4这个地方存储,4-7占4个字节,c2就是偏移量8的地方存储了。,0-8找一个最大对齐数的整数倍,最大是是占4个字节的i,0-8有9个字节,找一个4的倍数,那就是12了,12就是4的倍数。

offsetof宏,返回偏移量

3b8df173fd0443d49b73180566a64846.png

896be7f11b72447bbd6f30f5a618be0d.png

9d76b3584bd743e084e58d63292b6655.png

#pragma可以修改对齐数。

c307da75330c4155840ba45db91866fb.png

结构体传参

4c2831a7b44446b08e15bf6f7ce95a18.png


//结构体传参

struct s
{
	int a[20];
	int age;
};
struct s f = { {1,2,4},21 };
void print1(struct s f)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", f.a[i]);
	}
	printf("%d\n", f.age);
}
void print2(struct s*ps)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("%d\n", ps->age);
}
int main()
{
	print1(f);
	print2(&f);

	return 0;
}

a3bcf318219a4cb88a8f84a7707c7923.png

结构体传参应该传地址,地址节约内存。

位段

所谓位段就是:后面用比特位来节省空间

cc9a6b8f12f94ab1b3506b302f8056c9.png

feeb33d40bf344359eeec54088af530d.png

 如图  位段里的成员是从右到左占字节,如果内存不够则重新开辟新的内存空间
 

df882c8ea3f04a1980dc1958c052daea.png

c946e5ce3036409b80f9b42d3840cd30.png


说明假设在vs里成立

732c09fcafd142d58d4c0fc99bd52c42.png

使用位段的话节省了一个字节,不然的话就4个字节

bc07f7be1e1644fa9a5c5ee73187688c.png

ae5f9db418e34946823da8d725110258.png

18ace1c0607d4de483631d1461c72eb2.png


1.从右到左使用
2. 如果剩余空间不够下一个成员使用

2697a856a54e4bc9a5ecc9dd179aed77.png

位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员.

8770fbdf40604749a0732d175af63d49.png

通讯录

1088e0aa679d484ebd630ce3348f05fc.png

50d4e5391ff748e4a56456203dc7b8ae.png

枚举

枚举的取值默认是从0开始的,往后自加1

5cfa280261fc48b2960d181173824f4e.png

93fc260d5bb9433cb33812b253bbf80c.png

c2863e75c02b41a7907b92f1a020fa12.png

a1e705101a6943f8adf86da66b76f161.png

dfee3e82261c4210b37390b94a8b3081.png

//枚举
enum day
{
	a=1,//0+1
	b,//1+1
	c,//2+1
	d,//3+1
};

int main()
{
	printf("%d\n", d);
	return 0;
}

联合

dc9f5fa43911431c9631e0e2b7faac6d.png

3bbb409a97ea4eeca5dcd7ca2210a632.png

所谓共用体就是共用一块空间

4ad77ade604547f18c75cecc3a9cd9af.png

00539caa183a406fba778a22d3a3e1e6.png

41a2b17c27274cf5a39c886080e1b592.png

联合体的大小就是这个联合体变量最大的大小

d8b8103f20ec4c3ebd1f20fc5ea3bc31.png

i的二进制内存布局是  01 00 00 00

在联合体 u中  c和i共用一块空间,c对联合体i赋值1,
i的二进制内存布局是  01 00 00 00  ,根据小端存储模式
对c进行返回相当于返回的是首字节的地址,也就是01的地址
最后返回给ret即可

2525a7c44ed14c9b855a1f54a72ba995.png

首先对a进行取地址,为什么对取地址a强制类型转换,是因为判断大小端的时候,只需看首字节是否一致,char*类型只访问一个字节,他就可以完成这个事情。

5f379f9dae4a473e9ac7ce375b4928ea.png

3c69ced718e84385bae4b33630de63d1.png


//大小端问题
int chenk_sys()
{
	union u
	{
		char a;
		int b;
	}c;
	c.b = 1;
	return c.a;
}
int main()
{
	int ret = chenk_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

215f51f4f7fa4660aa8d521fc85817c5.png

//}
//  因为i和结构体变量S共用一块空间
union  u
{
	int i;//4
	struct s
	{
		char a1;
		char a2;
		char a3;
		char a4;
	}S;//4
};
int main()
{
	union u uu = { 0 };
	uu.i = 0x11223344;
	printf("%x %x %x %x ", uu.S.a1, uu.S.a2, uu.S.a3, uu.S.a4);
	
	return 0;
}

联合体大小问题

e25fcee519114d5681c3132207122701.png

char的字节是1 int的·字节是4 存在联合体对齐数,找对齐数最大的整数倍,比如int的就是最大对齐整数倍,就要浪费3个字节,因为char数组有5个字节,就必须浪费3个字节

8b97ac3bd0a14464886a48dddf9000f1.png

char数组再这里开辟了8个字节,自己用;15个字节,int也和char数组公用4个字节

结构体存在内存对齐,联合也存在内存对齐

//联合体大小问题
union u
{
    char a[5]; //1,8=1
    int i;//4,8=4,找4的倍数的字节。
}uu;
int main()
{
    printf("%d\n", sizeof(uu));

    return 0;
}

动态内存管理

148a069c25864495b3d2238df9461a11.png


p在内存中申请的40个字节空间,这个指针p指向的内存的起始地址

所谓动态内存管理就是随便更改开辟内存空间的大小

f47685674803430ca2502376c7b2dc0f.png

374d2780ab134affb75575a974b57196.png


 

//malloc函数

int main()
{
	
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
	return 0;

}

5c5a545751294148abf670000547bb2f.png

9493486473334db4a6b8a55756f3d632.png

7d1e42e755084fefa1307ea80e993a5b.png

//动态内存管理
#include <stdlib.h>
#include <errors.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		
		printf("%d ", *(p + i) );
	}
	return 0;
}

dfef554473144feb948d3275bd71793d.png8eeaae86c2c2451a9aa4b7b161977661.png

7724af853d48419796fc1f85dc8bc7b4.png

735a14bd98ea450c8fe898f212ebc18f.png

与malloc相匹配的函数是free

malloc是开辟堆区的函数,free是释放堆区的函数,

不用的时候要释放堆区,然后赋给空指针,否则就成为了野指针。

cb53d90efcd04c2e96be0a1055ebe267.png

free函数只是释放的指针的空间,还给的操作系统,但是p还记录着之前的那个起始地址,所有指针p现在很危险,如果不置位空指针,那么就很可能成为野指针

malloc函数只是帮你申请空间,但是没有帮你初始化
你要释放哪个空间,就把那个起始地址传给free函数即可

d25db54ddd5049bf9aa21f30f74a5bc1.png

calloc是开辟好了直接初始化,malloc是开辟了重新初始化。

如果已经通过了malloc或者calloc函数获得了动态空间,想改变其大小,可以用recalloc函数程序分配

f857e24953064cc9a5812a28f991c04d.png

情况1
当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。
情况2
当是情况2的时候,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。
由于上述的两种情况,realloc函数的使⽤就要注意⼀些

d69b4e52c492415ab9e9e25cc5305cc1.png

bc9c9df4c7184626b8eeabb77c38fb99.png

2376b95e7017471c970785661dd64a2d.png

dd010e57204540da831421febc49f2c4.png

d0e80d94c1cd4c968fe47b7dfad65f8d.png

realloc函数里为空指针 等价于malloc

常⻅的动态内存的错误

1. 对NULL指针的解引⽤操作

5efc2ef9fa5140ea87854939940f4672.png


void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	if (p != NULL)///如果p的值是NULL,就会有问题
	{
		*p = 20;
	}
	else
	{
		perrer("malloc");
         return 0;
	}
		
	free(p);
	p= NULL;
}


2.对动态开辟空间的越界访问

4994f72f9f564369970886df9af7c3d8.png

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i; 
	}
	free(p);
}

3. 对⾮动态开辟内存使⽤free释放

e9fb9d13b1b24d87a6e4303fd3f66d21.png

45744e1979444981a064ce559439991c.png

void test() 
{
	int* p = (int*)malloc(40);//在堆区申请空间
	free(p); //空间释放
}

bb71737cc04b49d39e35acf5877b48e4.png

4. 使⽤free释放⼀块动态开辟内存的⼀部分

8d300fe44e3b4d78a99189283d77b1b8.png

3ca002be274545a09b08d4dac8a2bf1a.png

指针p指向的起始地址+1的地址,我们知道free释放是从起始地址开始释放的

4.5 对同⼀块动态内存多次释放

417eb5ac79184ac68851d05271da69fb.png

正确写法

edce3ec2279f481bac067a96ed759682.png

4.6 动态开辟内存忘记释放(内存泄漏)

597d514f37044270bd8870cf68025302.png

忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。
切记:动态开辟的空间⼀定要释放,并且正确释放

da1c92356f1d4a05a37e00f7eb001a62.png

d2fbbd66fe4041b08f31e035be1ef7f9.png

vs调试技巧

5c91880939c442e5a63efac95d899cfe.png

程序运行就成死循环了,为什么呢?、

是因为先创建了i变量,首先说明一下局部变量是在栈区存储的,malloc,calloc,这些开辟内存的函数是在堆区存储的,static修饰的变量或者全局变是在静态区存储的。

有二点

1. 栈区内存的使⽤习惯是从⾼地址向
低地址使⽤的,所以变量i的地址是
较⼤的。
2. 数组在内存中的存放是:随着下标
的增⻓,地址是由低到⾼变化的。

2b5db0a653e741868f996227b4a5d7b2.png

4e44c5edfd0841baaa4c19707065efc5.png

总结:

先创建i变量,i变量就存储在栈区的高地址处。然后又因为数组在内存是连续存放的,然后又因为vs编译器存储地址在数组越界的地址里面多了二个导致数组越界最后一个地址和i的地址重合,就导致消除了i的值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值