2.1-发展历程
2.2-编译流程
所谓的编译型语言,指的是一次编译,直接运行,它是直接把源文件编译成计算机可以直接识别和执行的二进制指令集!
源程序
使用C++编程语言,编写代码指令集(不能够直接被计算机识别和执行)!
预处理阶段
预处理阶段主要是去除注释,处理预处理指令!
- 注释
//单行注释
/多行注释/ - 预处理指令
- 宏定义
所谓的宏定义预处理指令其实就是定义一个标识符用来代表一个具体数据的过程,将来在所有引用这个具体数据的地方,我们统统用这个标识符代替,一旦我们的这个具体的数据在程序多处引用,我们只需要在这个标识符声明的地方将具体数据修改一下即可,不需要多处修改,减少工作量,将来在预处理阶段,我们的预处理程序会根据宏定义来替换所有的标识符为具体的数据(纯粹的文本替换,宏定义时不要吝啬括号)!
无参宏: #define 宏名 宏体 //#define PI 3.14
有参宏: #define 宏名(参数列表) 宏体 //#define SUM(X,Y) (X)+(Y)
特殊宏: 双引号,单引号,连接
#define A(x) #x //A(1) -> “1”
#define A(x) #@x //A(1) -> ‘1’
#define A(x,y) x##y //A(“12”,“34”) -> “1234”, A(12,34) -> 1234 - 包含头文件
任何东西在使用的时候,必须要先有这个东西,即存在声明,而声明存在于一个.h文件,那么我们就必须要在使用前,先包含这个头文件,如果存在于同一个文件内,就不需要包含,我们通过头文件包含预处理指令就可以让预处理程序帮我们将需要包含的头文件包含进来,我们只需要告诉预处理程序,包含哪个头文件即可!
包含系统头文件:#include <xxxx.h>
包含自己头文件:#include “xxxx.h”
防止头文件多次包含:
1-#pragma once
2-通过宏来避免
#ifndef xxxx
#define xxxx
…
#endif - 条件编译
#if 条件
#endif
//
#if 条件
#else
#endif
//
#if 条件
#elif 条件
#else
#endif - #pragma
设定编译器状态或者指示编译器完成一些指定动作!
防止头文件包含 #pragma once
引用动态库 #pragma comment(lib,“xxxxx”)
用于警告信息 #pragma warning(xxxxx)
编译输出窗口输出消息 #pragma message(“消息文本”)
- 宏定义
编译阶段
编译阶段其实就是将预处理处理好的程序交付给我们的编译程序进行编译,最终呢通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码或汇编代码!
汇编阶段
将编译好的中间代码或者汇编代码交付给我们的汇编程序,最终呢会生成对应汇编代码的二进制指令!
链接阶段
将程序相关的文件链接起来,生成一个完整的可执行程序!
可执行程序
可执行程序也是按照一定格式组织起来的,这种格式windows下被称之为PE格式,Linux被称之为:ELF格式!
Linux详看编译过程:
- 预处理
gcc -E main.c -o main.i
- 编译
gcc -S main.i -o main.s
- 汇编
gcc -c main.s -o main.o - 链接
gcc main.s -o main
2.3-基本语法
内存模型
组成:
从逻辑上看内存就是由一个一个小格子构成的存储空间,每个小格子我们称之为一个存储单元(字节[Byte]),存储单元是构成内存的基本单位,同时也是访问内存空间的基本单位!
每个存储单元(Byte),又是由8个更小的格子构成,这个更小的格子我们称之为位(比特[bit])!
- 换算
1 Byte = 8 bit,1KB = 1024Byte,1MB = 1024 KB - 比特流
由0/1构成的一串二进制流,比如:010100100010010010010001!
地址:
每一个存储单元都对应着一个唯一的标记,这个标记就是用来找到对应的存储单元的,我们把它称之为地址(这就类似于人类的房屋地址,每个房屋都对应着一个位置信息,同时通过位置信息我们就可以找到对应的房屋)!
存储:
数据在内存中存储时,如果是单字节数据存储(一个字节),高位bit存高位,低位bit存低位;如果是多字节数据存储(产生一个问题?那就是数据的高低字节是存放在内存空间的低地址还是高地址?),那么就对应两种存储模式:大端存储和小端存储!
数据: 0x12345678 高字节~低字节
地址: 高地址~低地址 (78 56 34 12),(12 34 56 78)
大端模式: 数据低字节 -> 高地址
小端模式: 数据低字节 -> 低地址
那么我们该如何判断是大端还是小端?其实很简单,我们只需要记住一句话: 数据的低字节存放在高地址还是低地址;另外大端,小端存储模式,讲的是多字节数据,高低位字节,高低地址!
四区:
- 栈区
栈是由编译器自动分配释放使用的,主要用于局部变量,函数参数等内存的分配,在编译阶段已经确定(在编译阶段就把栈区规划好),一旦离开对应的作用域,那么对应栈上面开辟的内存就会被自动释放掉!
栈的地址增长结构:高地址~低地址!
- 堆区
堆区是提供给开发人员手动分配和释放,通过malloc,free,new,delete等关键字进行堆上内存空间的开辟和释放,如果忘记释放,就会导致内存泄漏! - 数据区
- 全局区
用来存放全局变量和static变量(初始化和未初始化的全局变量和static变量是分开不同区域存储,都在全局区)! - 文字常量区
常量字符串存在于文字常量区中!
- 全局区
- 代码区
代码指令!
数据类型
概念:
由同一类具体数据(数据元素)所构成的集合以及建立在这个集合上面的操作所构成的集合,我们称之为数据类型!
栗子:1,2,3,4,5,6…(同一类数据元素) + ±*/%(运算操作) ,用一个标识来代表它,比如:int (数据类型)
本质:
指明:所存储数据的类型
指明:所占内存空间大小
本质:固定内存大小的别名!
种类:
基本数据类型:整形,浮点型,字符型,布尔类型
复杂数据类型:引用,数组,字符串,结构体,共用体,枚举,指针变量
语雀:数据类型
数据存储
常量:
一个具体的数据我们称之为常量,一旦修改它就不是这个数据了就是常量,比如3.14,“qiyawei”,‘c’,这些具体的数据我们都称之为常量!
- 常量定义
- 宏定义常量
#define PI 3.14 //宏定义常量,预处理阶段,就会将宏符号全部用具体的常量数据所替换! - Const常量
特点: 必须进行初始化,不可修改!- const修饰全局变量:
可以在本模块中使用,但不可被修改,在其它模块中不可使用! - const修饰局部变量:
通过变量名无法进行修改,通过指针间接指向可修改! - const修饰指针变量:
谁不可变,去除数据类型标识符就看const修饰的是还是指针变量名!
int const p; const修饰的是指针变量p,指针变量p自身不可修改!
const int* p;int const p; const修饰的是p,指针变量p所指向的地址所指向的内存空间不可修改! - const修饰函数形参:
如果是基础数据类型的值拷贝,原始对象不会因为函数操作而改变,所以不需要使用const特殊修饰!
参数为指针或者引用类型,意味着通过指针访问到的就是外部数据,如果我们想要指针指向的外部数据,我们可以底层const,也就是const修饰*来标记! - const成员变量:
const成员变量只能在构造函数中进行初始化,对于对象来说是不可变的,对于类来说,每个对象的const成员变量可以不一致! - const成员函数:
不可以修改成员变量! - const 类对象:
常对象不可调用非const函数!
类对象可以调用所有函数!
- const修饰全局变量:
- 宏定义常量
变量:
我们知道食材需要存储,那么我们的数据呢也是需要存储的,用来存放数据的存储单元我们称之为变量,通常呢我们会定义一个标识符用来代表这个存储单元我们称之为变量名。变量其实是对同一类具体数据的封装和抽象,它代表的是一类具体的数据,只有给这个变量赋予一个具体的数据时这个变量才具体代表一个数据,所以变量是用来存储一类具体数据的!
存储数据->声明变量->开辟内存空间:
开多大? 要存储数据的类型决定,由数据类型名进行标识!
开在哪? 通常我们会定义一个标识符(变量名)用来代表我们所开辟的那段内存空间!
- 定义变量
数据类型 变量名;//开辟内存空间 - 声明变量
extern int num;
只是告诉编译器我们声明了这样的一个变量它长这个样子,并没有开辟内存空间,它会去本模块或者其它模块中去找这个变量! - 二者区别
如果是extern声明的变量,我们只是告诉编译器,我们声明了这样的一个变量,它长这个样子,并没有开辟内存空间,它会去本模块或者其它模块中去找这个变量。如果我们是int num,这样的形式声明变量,那么就意味着不仅声明了一个长这个样子的变量,同时还在内存空间中开辟了相应的内存空间,声明和定义的区别就在于是否开辟内存空间!
数据运算
一切皆由变化起,变化皆有运算来!
建议使用大括号来区分优先级,优先级我也记不住,反正加括号就对了!