1.字符'A'的值为65,字符'a'的值为97,空格的值为32
2.c语言规定字符串型常量的结尾需要加一个字符串结束标志,所以比如"CHINA"5个字符其实是占用了6个字节,如图所示:

3.强制类型转换

当你的左右两个数都是整型时,做的其实是整型运算,5/2=2然后再将2转换成浮点型2.000000。

当我们在运算之前先将整型i进行强制类型转换成float浮点型,这样运算出来的结果就是对的了。
4.printf函数
printf函数可以输出各种类型的数据,其原理是将这些类型的数据格式化成字符串之后,放入到标准的输出缓冲区,然后展示到屏幕面前
5.printf函数的具体代码格式
%c 字符;%d 带符号整数;%f 浮点数;%s 字符串
6.printf函数的格式化输出(空格补齐)


通过上面两张对比图可以看到printf函数可以使输出的结果数据右对齐,如果需要左对齐使用%-3d即可。这里的3表示的是不够三位的使用空格补齐,超过三位的不变。如下图所示这里的%5.2f表示的是不够五位补空格,小数点后面保留两位。

7.整型进制转换
123(十进制)——>001111011(二进制)——>173(八进制)——>7b(16进制)
十进制的数字转换成二进制的方法是将十进制的数字不断除以2直到0为止然后从后往前取余数,这里要注意的是首位数字的0表示正数,1表示负数。如下图所示转换过程。

printf函数可以打印出不同进制的数字:
%d:十进制;%o:八进制;%x:16进制

如果想知道二进制需要在内存试图里面看到,而这里本来应该是0000007b但是实际的存储是7b000000,原因是cpu采用了小端方式进行数据存储,因此地位在前、高位在后。

8.scanf函数
scanf之所以是会阻塞等待你的输入是因为输入缓冲区里面是空的
一般格式如下:

使用注意:当我们需要输出char类型的数据时,需要注意下面这种情况
代码:

结果:

scanf是从输入缓冲区里面读取字符的,所以一开始读取i的值的时候只是把i的值读取了但是换行符\n没有读取,但是scanf读取下一个数据的时候发现是字符类型的,所以就把换行符\n读取了。
过程:

解决方法:在读取字符之前加一个空格
代码:

结果:

9.算数运算符,关系运算符和逻辑运算符
sizeof(数据)是运算符
逻辑非的优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符
算数运算符:+,-,*,/
关系运算符号:>,<,=
逻辑运算符号:&&,||,!
10.数组的定义和初始化

上图我们可以看到数组的大小是要一开始就需要给定的,并且不能使用常量来定义。

当你初始化数组的大小是大于你实际储存内部的数据时,其余的部分会为0,由此我们可以写出直接赋值全为0的数组。

11.数组的访问越界

代码中并未改变i的值但是最后i的值却变了,我们打开内存视图可以看到i的值存储的位置就是a[6]存储的位置,所以a[6]赋值的7便成为了i的值,这就是访问越界的危险性

12.数组的传递
代码:

结果:

当给自定义函数进行数组传递时可以看到,明明将数组传递过去了,但是循环打印的结果却打印了数组5个值当中的2个值,于是加上打印a的值可以看出我们传递的数组其实是一串数字,那这一串数字其实是这个数组在内存中的地址。所以其实当你用数组名传递的时候传递的其实是数组的地址,并且函数的调用是用值来传递的,所以在print函数中数组名被弱化成了指针。由于电脑是64位的所以指针的大小是8个字节,int整型是4个字节所以算出来的大小为i<2,最终只打印出了两个值。
注意点:1.数组名作为实参传递到子函数的时候传递的是数组的起始地址
2.数组内的数据可以被子函数进行改变
13.字符数组的初始化和使用

这两种初始化方式,推荐写第二种;并且定义字符数组的大小需要比实际存储的数据要多一个,这是因为字符数组需要存贮一个结束符‘\0’。
14.scanf读取字符串
使用scanf读取字符串的时候,如果有空格只能分开来读取,scanf遇到空格会自动停止读取,如下图所示

15.gets函数和puts函数

当读取字符串时,一般使用gets来读取,gets是遇到\n换行符停止读取;gets是将\n换行符转换成'\0'实际并未存储\n换行符。
16.str系列字符串操作函数
代码:

结果:

(1)strlen统计的是字符串长度并不是字符数组长度。arr字符数组的长度是20,但是里面只存储了hello这5个字符,所以strlen得出的值为5;
(2)strcat(拼接字符串)和strcpy(拷贝字符串),它们的第一个参数必须是字符数组名不能是字符串常量(比如"hello");
(3)strcmp(a,b)比较a和b的大小是看它们的ascall码值不是比较它们的长度,
a>b结果为正整数,a=b结果为0,a<b结果为负整数;
17.指针
指针其实实际上就是地址,但是存储的是起始地址,如下图所示。比如int类型的指针变量存储的是0x00000002的地址,然后根据定义的数据类型来决定要访问多少个字节,int类型就从起始位置开始访问4个字节,char类型就访问一个字节;

指针的定义和初始化,可以联想一下i_pointer相当于藏宝图,然后利用*(取值)去找宝藏,最终通过间接访问去修改数据;


指针的大小

指针本身的大小是不变的,只与操作系统的位数有关,64位操作系统的指针大小是8个字节,32位操作系统的指针大小是4个字节;但是加入*(取值)符号之后的大小就是指针变量基类型的大小,如int *i_pointer就是4个字节,char *c_pointer是1个字节;
注意:int *a,*b,*c;这种才是定义三个指针变量的正确方法
18.指针的传递

上述代码的结果,可以看出当利用子函数去改变主函数定义变量的值时这样的写法是失败了的。本质是当执行change函数时又创建了一个空间去储存j的值,实际改变的是j的值;

利用指针变量来修改变量的值,代码如下:

此时j是指针变量,里面存储的是i的地址,在子函数change中利用指针变量访问i的地址,然后进行修改;(函数的调用是值传递,但是传递地址实际上并未违反规则,实际的过程是 int *j=&i)

19.指针的偏移
一般如果定义一个 int i = 1,i+1的结果会是2;但是当定义int *p ,p+1的值会是多少呢?

可以看到当p+1的时候并不是在p原先的值上面+1,而是根据指针的基类型来进行偏移

当数组名作为实参传递给子函数时,是被弱化为指针的
20.指针与动态内存申请

一般在定义变量的时候,它的存储位置是在栈空间的,当使用malloc去申请空间时,数据的存储是在堆空间。申请动态内存的代码如下:

注意点(1)malloc参数是申请空间的大小,返回的是空类型的指针,所以要进行强制转换;接收的指针不能为空类型的指针是因为void*类型的指针是不能进行偏移的;
(2)malloc是申请的堆上面的空间,需要手动free释放空间
(3)free释放空间时p的位置不能发生改变,必须是malloc返回的地址
(4)指针本身的大小和指向空间的大小是不同的,指针本身的大小是固定的不会改变,64位操作系统是8个字节32位操作系统是4个字节
21.堆和栈的差异
栈空间的内存是操作系统释放的,堆空间的内存是需要手动free释放(或者程序结束)的;
代码:

结果:

当printf_stack函数调用结束时,系统自动释放了空间,所以当在主函数打印时会乱码;但是printf_malloc函数调用结束时没有手动free释放空间,在主函数依旧可以打印。
22.函数的声明和定义
注意点:
(1)函数可以嵌套调用但不能嵌套定义
(2)其他函数不能调用main函数,main函数是由主函数进行调用的
23.递归调用
当函数调用的函数是自己本身时称之为递归函数,递归一定要有结束条件否则会形成死循环
例子1:求n的阶乘(利用递归的方式)
递归的思路:
(1)找公式:n!=(n-1)!*n
(2)循环结束条件:n=1时n!=1;n=0时n!=0;n>=0;
代码:

例子2:青蛙跳台阶
青蛙一次只能眺一格台阶或者两格台阶,如果有n个台阶青蛙有多少种跳法
(1)找公式
在跳到最后一个台阶之前我们只有两种可能性,一种是在倒数第一个台阶,一种是在倒数第二个台阶;所以可以推断出公式为:f(n)=f(n-1)+f(n-2);
(2)结束条件
首先n不可能小于0的,所以n>=0;其次公式中很明显有两个特殊的数字1和2,n=1时f(1)=1,n=2时f(2)=2,然后就是正常公式的算法了;
代码:

24.局部变量和全局变量

全局变量存储的空间不在栈和堆上,而是在数据段上;

当局部变量和全局变量重名时,系统规定采取就近原则;如下图代码所示main中的i匹配的时int i = 5,print函数匹配的是int i形参的值,而实参传递给形参的值为3,所以打印出来的就是main中i=5,print中i=3

形式参数在函数没有被调用时是不占内存空间的
25.结构体的声明和使用
代码:

结构体类型的声明结尾是以:结尾,结构体访问成员用.来进行访问。
当使用scanf输入字符数组时不需要使用&符号,数组名就是存放的地址
结果:

结构体数组的声明和使用:
代码

结果:

26.结构体对齐的三大法则:
1 自然对齐规则:结构体的成员将按照自然对齐原则来排列。自然对齐要求每个成员的地址是其数据类型大小的整数倍。例如,一个int类型的成员通常会被对齐到4字节边界(整除4),而一个double类型的成员通常会被对齐到8字节边界,
2 填充字节:如果一个成员的大小不是对齐边界的整数倍,编译器通常会在其后添加一些填充字节,以确保下一个成员能够正确对齐。填充字节的大小取决于编译器和目标平台,通常是1字节或更多。
3 结构体的总大小:结构体的总大小通常等于其最大成员的大小的整数倍。这是为了确保结构体数组的每个元素都按照相同的规则对齐
代码:

结果:

student_type1成员大小分别是8,4,2应该是14个字节结果确实16个字节,实际在内存中存储的是16个字节如下图所示,根据结构体对其法则当是14个字节时不符合结构体的总大小是最大成员大小的整数倍,所以填充了2个字节

而student_type2成员大小分别是4,1,2应该是7个字节,但是在实际存储中可以看到当存储完char类型的数据后,自动填充了1个字节,然后才存储的short类型的数据;这是因为不满足自然对其规则,如果不填充1个字节那么short数据的地址就不是是其数据类型大小的整数倍不能被2整除(意思是假设现在处于地址为14的位置能被short大小为2整除,如果不填充1个字节就在地址为13的位置不能被short大小为2进行整除)
![]()
27.结构体指针

通过结构体指针访问结构体成员有两种方式
方式1:(*p).这里加()的原因是运算符.的优先级比*的优先级要高
方式2:p->这种方式为常用方式,方式1不推荐使用

结构体指针使用malloc申请空间,对成员赋值并且访问。这里要注意给字符串赋值需要使用strcpy不能直接进行赋值。
28.typedef的使用

typedef相当于重命名的作用,就想许多作者一样有自己的名字也有笔名,那typedef就是将周树人重命名为鲁迅,从此鲁迅就是周树人周树人就是鲁迅。stu=struct student;pstu=struct student*(注意是pstu不是*ptsu)
29.c++引用
c语言中为什么要使用c++的引用?
因为它太好用了,可以帮助我们理解一些难的代码,其次c++完全兼容c语言
什么是引用?
当需要改变主函数里面变量的值时就加上&引用,不改变就不加

30.子函数改变主函数中一级指针变量

当利用指针改变主函数指针变量的值时,结果发现程序已经崩溃,并没有改变变量的值

当换成引用时,结果显示是可以改变的;这其实就是引用的目的,因为*p=NULL实际存储的是0,值为0的时候内存是不能访问的,就相当于现在给你一张藏宝图但是藏宝图显示的位置没有宝藏;同时也要注意&引用的写法:引用必须和变量名紧邻
31.c++引用案例
案例一:引用改变结构体成员的值

没有加&引用时,主函数的stu s在内存中创建了一个空间,子函数的s也在内存中创建了一个空间,所以子函数改变的是自己创建s的值,主函数的值不会改变

当在子函数的形参中加上了&引用符,结果显示主函数的值改变了;至于其原理和过程不重要也不考,你只需要知道当加上了&引用符号的时候在子函数中操作变量就相当于在主函数上操作变量。
案例二:改变结构体指针指向的malloc申请内存中成员的值
通过下面的代码可以看出,在并没有使用引用&的情况下,依然是改变了主函数中的值

原因就是在子函数并没有改变指针p的值而是利用指针p去间接访问malloc申请的内存空间中的值,所以不需要加引用&

1160

被折叠的 条评论
为什么被折叠?



