目录
一、从源代码到exe
二、基本数据类型
三、字符在屏幕上的显示原理
四、溢出现象
五、类型转换规律
六、短路问题
七、指针变量类型的作用
八、指针类型的扩展——多级指针
九、指针类型的扩展——指针数组
十、指针类型的扩展——数组指针
十一、一维数组-名-特性
十二、二维数组-名-特性
十三、大端存储-小端存储
十四、函数在源代码中的三种状态
十五、堆区与栈区的区别
十六、函数的执行原理
十七、枚举类型
十八、typedef
二十、const
二十一、全局/局部变量生命周期与作用域
二十二、static静态变量
二十三、extern
二十四、宏define
二十五、字符串原理
二十六、struct与union结构体联合体
二十七、链式存储与顺序存储
二十八、文件处理模型
二十九、文件复制原理
三十、组件化技术分类
知识点:
知识点:
字面值常量的前缀,如果是八进制就在常量前加0,如果是十六进制就加0x,如果是二进制就加0b,十进制则不需要前缀。
字面值的后缀,因为无符号的后缀为u,所以无符号的int类型后缀为u,正常int类型没有后缀,short类型没有后缀,long类型为l,无符号long类型为ul,long long类型为ll,无符号long long类型为ull,char无后缀。浮点型不区分有无符号,float类型为f,double没有后缀,long double为l,但一般用不到这个类型。
知识点:
溢出现象中有这么几个问题:
答:char类型的-1的原码为1000 0001,因为计算机在存储时存的是补码,所以要对除符号位之外的二进制进行取反变为反码1111 1110,在对其进行+1变为补码1111 1111。
答:以图中这个短整型为例,-1的二进制存储为16个1,在加1的过程中会不断进位变为17位的二进制1 0000 0000 ......,但短整型只能存储16个二进制位,那么最前面的1就会出现溢出舍弃,称这个现象位溢出现象。
答:我们知道,有符号的短整型最大值的二进制除符号位为0其他位都为1,对其进行+1就变为符号位为1其他位都为0,因为符号位为1是负数,并且此时为补码,要还原为原码就要对其取反再+1,先取反变为16个1,不看符号位,他的10进制为32767,再+1就是32768,最后补上符号就是最小值-32768。
知识点:
知识点:
知识点:
知识点:
知识点:
知识点:
答:我们知道,p指向的是数组的地址,也就是取地址符加上数组名,那么*p就是对取地址后的数组名再*操作,因为&与*为互逆操作,所以此时*p就等同于数组名,所以通过数组指针访问数组成员空间时,只要将数组名替换为*p即可。例如访问第三行第四列的成员空间正常来写就是 数组名[3][4];换成数组指针的写法就是(*p)[3][4]; 。
图片解析:
先来看这张图,我们定义了一个一维数组,在这个数组中,他的数组名本质上就是个地址,是这个数组的首地址,我们也可以理解为这个数组第0个元素的地址,那么在这个地址上加上2意思就是向后移动两列,找到的就是这个数组第2各成员的地址。在这里有个公式 *(参考地址+偏移)==参考地址[偏移] 讲的其实就是[]的计算过程,意思就是通过数组名找到第0列的地址,再向后偏移四位就找到了第4列的地址,然后通过*操作就可以得到他的空间。快捷方式就是 参考地址[偏移]。最后要说的是arr与&arr的区别,虽然他们两个的值相同,但是代表的意义不同,arr代表的是数组第0列的地址,向后偏移1位找到的是第1列的地址,而&arr找到的是这个数组的地址,向后偏移1位找到的就是数组后边的地址。
知识点:
图片解析:
在图中,我们定义了一个int类型的二维数组。在这个数组中,数组名找到的就是第0行的地址,所以我们在数组名上加1可以找到第1行的地址,再*操作就可以得到第一行的空间。在得到行空间的基础上想要得到这行中某一成员的空间,就要在这个行空间上加上偏移量,找到某一成员的地址,再*操作就可以得到成员的空间了。这其实就是二维数组寻找成员空间的计算过程,化简之后就变成了数组名[][],前面是行后边是列。那么二维数组名取地址找到的就还是二维数组的地址,它加上1找到的就是二维数组后面的地址,并且*操作后得到的空间大小与二维数组的空间大小相等。
知识点:
答:我认为他就是一个嵌套寻找空间的过程,就是不断利用*(参考地址+偏移)这个公式来逐层寻找空间。如果是访问三维数组中某一成员的空间,那么就是三层嵌套,第一层先利用数组名加偏移找到某一个平面的地址,对他*操作后得到这个平面的空间,再利用这个空间加偏移找到这个平面中某一行的地址,*操作后得到这行的空间,最后利用得到的这个行空间加偏移找到某一个成员的地址,对整体*操作后就可以得到这个三维数组中某个成员的空间了。
图片知识点解析:
这里我们定义一个int类型的变量,他在内存中所占的空间为4个字节,因为内存中一般以16进制存储。我们知道,两个16进制就相当于8个2进制位,也就是1个字节,所以4个字节就i相当于8个16进制。所以这个9的16进制就是00 00 00 09.那么无论是大端存储还是小端存储,他的左边都是低地址,右边都是高地址。如果是大端存储,那么就是低地址存储高位,高地址存储低位,这与我们平时的思维很像,它通常用于数据传输,而小端存储则与其相反,它是低地址存储低位,高地址存储高位,通常用于内存存储。那么我们如何验证内存存储是否是小端存储呢?我们可以通过定义一个整型1,并且将它的地址强转成字符型指针,然后赋值给char类型的指针p,如果此时p指向的空间为1,则可以证明存储方式是小端存储。
图片知识点解析:
函数的三种状态包括声明状态,定义状态和调用状态、要定义一个函数,他首先需要有返回值类型,然后加上函数名和括号,括号中写上要定义的形参,再加上{},最后在{}中写入流程结构。在声明函数时,就是将{}和其中的内容去掉,再加上;,如果函数没有进行前置声明,那么就会出现重定义的语法错误。再调用函数时,我们只需在()中写入实参,就可以将实参传入形参,即可进行函数的调用。在我们自定义函数时,最佳的顺序就是先对我们设计好的函数进行声明,然后将调用函数写入到程序中,看逻辑是否正确,最后按照我们的需求对函数进行定义,书写函数的流程结构。
知识点:
图片知识点讲解:
从宏观来看,函数的执行就像阿拉丁神灯一样,在我们调用执行的时候他就会出现,执行完毕就会消失。
从微观来看,在我们执行一个程序时,main函数会首先入栈,之后调用哪个函数哪个函数就入栈,执行完毕后就会pop弹出。这里遵循一个先进后出,后进先出的原则。那么在调用函数时,我们会将局部变量(也就是形式参数和声明变量)存放在栈区,在函数结束调用后会自动回收。
函数名实际就是个地址, 也就是说他代表函数在代码区上的地址,既然是个地址,那么我们就可以创建一个指针来指向它、调用它。所以我们在栈区创建一个要指向函数类型的指针,并指向它,在这里我们要注意的是,我们在指向它时有两种方式,一种是显示方式:就是用p(指针)指向&函数名,那么在调用时就要用(*p)(实参)来调用,另一种方式是隐式方式:也就是直接用指针指向函数名,那么在调用时则可以直接用指针加(实参)来调用。因为第二种方式比较简洁明了,所以在调用执行函数时我们通常选择隐式方式。
图片知识点解析:
枚举类型的全称是enumeration,我们在使用的时候就用enum,它是一种扩展类型,在这之前我们学习过数组,并且在以后还会学到struct结构体以及union联合体。enum可以对数据类型进行扩展,创建自定义的类型,但这个类型的值一定是固定有限的。比如生肖、血型或是方向。他的格式是enum 枚举名称 {内容1,内容2......};他在创建时,会默认给内容赋上编码,从0开始。我们也可以通过内容=整数来手动编码,但编码本身无意义,只是为了增强可读性。在使用我们创建的类型时,格式为:enum 枚举名称 变量名 = 值;enum最主要的功能就是增强代码的可读性。
图片知识点解析:
type是类型的意思,define是定义,那么typedef就是对类型的重新命名,定义一个新的类型。他的基本格式是typedef+现有类型名+自定义名称。比如图中左侧,将COVID-19重命名为新“新冠”,但是无论是COVID-19还是新冠,指的都是同一种东西。那么我们要想对函数指针或数组指针重命名,我们只需在表达式前面加上typedef,并且将原来的变量名换成自定义名称即可。那么什么是嵌套定义呢,比如说我们先将int类型重定义为Integer,再将Integer重命名为Integer INT,然后还可以将Integer INT在重命名为INT MYint,但是无论多少次重命名,这些自定义类型的本质都是int类型。
模板: typedef 现有类型名 自定义名称
图片知识点解析:
const就像是一把锁,如果在定义时表达式前面加上了const,那么所定义的变量就会变为只读状态。在做项目时,我们如果不想让用户改变某个变量的值,那么我们就会在定义变量时加上const。const在指针上的用法有三种,如果在*和p前面都加上const,就相当于将指针锁住并且将它指向的地址也锁住,也就是指向不可改,内容也不可改。如果只在*前面加const,就是将指向的地址锁住,就是指向可改,内容不可改。要是只在p前面加上const,那么就是将指针锁住,但仍可以通过*p改变他指向的空间,也就是指向不可改,内容可改。
图片知识点解析:
全局变量是在函数外定义的变量,它会被分配到全局/静态区,它的作用域是整个项目内,在项目内任何位置都可以通过extern来对它进行引用,但切记不可以二次赋值,它的生命周期是整个项目 开始到结束。局部变量就是定义在函数内的变量,它会存放在栈区,它的生命周期是从函数入栈到弹出,作用域是所在函数内。当全局变量与局部变量重名时,不会出现重定义的语法错误,而是会将全局变量屏蔽,这里遵循一个临近原则。
图片知识点解析:
static所定义的是静态唯一变量,他所定义的变量会被存放在全局/静态区。static变量的值只能初始化一次,如果未进行初始值,系统会自动为其赋值0。当我们在调用函数时,函数内的变量会随着执行结束弹出而被栈区自动回收掉,但如果定义的变量为static变量,他将不会被回收。所以他的生命周期是程序开始运行到结束。它的作用域是定义所在的位置,如果是在项目中定义,它的作用域就是整个项目;如果实在目标文件那么作用域就是所在目标文件;如果实在某个函数内定义,那作用域就是这个函数。比如说我们在定义一个全局变量时,在前面加上了static,使其成为了静态全局变量,那么他就是当前源代码的全局变量,不可以在其他源代码中通过extern来引用。
static 理解为局部内的 唯一变量 相当于全局,但全局不可访问
图片知识点解析:
extern就是外部变量链接,它用于声明来自外部的变量,它可以将全局/静态区中的全局变量在外部进行声明,然后所声明的位置就可以使用被声明的变量了,在声明时要注意的是只能声明,不能赋值定义,否则就会出现重定义的语法错误。所以他的格式是extern 数据类型 变量名; 切记extern声明的变量不能赋值,有extern没赋值,有赋值没extern。
图片知识点解析:
define就是宏替换,宏是整体的意思,那么宏替换就是整体替换,它发生在预处理期,它有两种替换方式。一种是#define 宏名 替换内容,这是内容没有可变部位的方式,而如果内容中有可变的部位,他就是#define 宏名(可变部位) 内容模板,其中包括可变部位和不变内容,可变的一般为数组名或数据类型。它也可以进行宏的嵌套定义,将D替换为C,将C替换为B,再将B换成A。宏替换最大的意义也是增强代码的可读性。它是一个纯复制粘贴的过程,而typedef是真正的重新命名,定义了一个新的类型。
图片知识点解析:
首先来看字符,字符用char来表示,他代表1个字节的字符,只能表示ASCII码值,不能表示汉字字符,他的赋值方式可以是影像赋值,也可以是十进制、八进制或是十六进制的形式。它存储的是字符的编码,并以影像的方式呈现出来。
那么字符数组和字符串有什么区别呢?首先他们都是表示若干个字符的组合,但是字符串要有字符的结束标记'\0',有了'\0'的字符数组才能叫作字符串,但是我们手动来赋'\0'太过于麻烦,所以我们在定义字符串时通常用" "来初始化,因为他会在字符串的结尾自动赋上'\0'(注意'\0'也会占一个字符空间),那么在初始化后我们将这个字符数组称之为字符串,将" "中的内容称之为字符串常量。只要是常量,就会放在常数区。在代码中,字符串常量可以理解为一个地址,就是字符串所在常量区的首地址。
由于str1[]是局部变量,所以会在栈区被创建,在初始化时,后面的字符串会被在常数区先创建出来,而后在栈区对其进行复制。p1和p2也在栈区被创建,由于Hello\0之前已经在常数区被创建出来了,所以p1、p2可以直接来指向Hello\0。
在这里面,我们尤其要搞懂哪些值是常量,先来看左下角这几个bug出在哪里。由于p1和p2是指向常数区的地址,所以用指针当作数组名不可以对他的成员进行更改,只可以改变指针的指向,这里就像const *p一样,指针的指向可改而指向的内容不可以改。下面这个错误是因为str1是数组名,而我们知道数组名是地址常量,并且右面的内容是字符串常量,将一个常量赋给另一个常量,会出现左值不可更改的错误。但是由于str1创建的字符串是在栈区内,所以它的成员的值是可以被修改的。
图片知识点:
答:struct是存储多个类型对象的内存块,并且这些对象不重叠。而union是一块内存,他的大小等于数据对象最大的空间大小,他的对象会重叠,因此在任何时候只能存储一个数据对象。struct占用的内存大小等于所有成员占用的内存总和,而union占用的内存大小等于他成员占用最大的内存大小。
答:在结构体创建时会有一个对齐不齐的准则,他会选出所使用的数据类型中字节数最大的类型作为标准,并以它的字节数为一个单位,在创建空间时,如果某类型小于单位剩余字节数,那可以直接将这个类型在该单位的空间中创建,如果大于剩余字节数,那么就会再创建一个单位空间对其进行存储。要注意的是,数组的字节数是以它其中一个成员的字节数为标准的。对齐补齐的意义就是可以节省空间,提高空间使用效率。比如图中这个,这些数据类型最大字节数为4,所以一个单位空间就是四个字节,那么我们可以看到第二排剩余一个字节的空间,而最后一排只使用一个字节,如果将最后一排的char类型放在第二排进行创建,那么就会节省四个字节的空间。
图片知识点:
图片知识点:
图片知识点解析:
文件复制我们先用fopen函数打开被复制文件和目标文件的缓冲区,在创建from和to两个文件类型的指针指向他们,我们还需创建一个buffer数组来当作中间的缓冲区。而后我们通过fread函数将在被复制文件中的数据先读到buffer数组中,然后再通过fwrite函数将buffer数组中的数据写入到目标文件,直至被复制文件内的光标到达尾部。
图片知识点解析:
静态库需要放到项目中,他会跟随项目一同打包为exe文件,所以如果exe用到的组件较多,那么他的文件体积会很大,并且每个程序的代码区都会有一份静态库,会造成空间浪费。他还不易更新,在更新时要将所有程序都更新一遍,他的优点就是移植方便。动态链接库是独立在exe外边的,它属于资源共享,谁想用谁就调用它即可,并且升级也很简单,只要更新自己就行,因为不需要打包在exe文件内,所以文件体积也会较小。