Ox00 开篇
- 原码、反码和补码
- 原码:原码的最高位保存的是它的符号位,负数的最高位为1
- 反码:反码就是将原码除符号位外按位取反
- 补码:补码就是反码+1.
- 二进制、十进制和十六进制
- 二进制:0 1
- 十进制:0 1 2 3 4 5 6 7 8 9
- 十六进制:0 1 2 3 4 5 6 7 8 9 A B C D E F(方便计算)
- 进制间的转换方式
- 不同进制到十进制的转换
- 权位相加法 (0xFF -> 15* (161) 15*(160))
- 十进制到不同进制的转换
- 辗转相除法,最终逆向获取余数
- 10 -> bin
- 10 / 2 > 5 0
- 5 / 2 > 2 1
- 2 / 2 > 1 0
- 1 / 2 > 0 1
- 十六进制、八进制和二进制间的转换
- 十六进制到二进制: 4个二进制 == 一个十六进制位 c(1100) f(1111)
- 八进制到二进制:3个二进制 == 一个八进制位 5(101)6(110) 7(111) "\o567"
- 数据在计算机中的存储形式
- 任何数据在计算机中都是以二进制补码方式存储的,正数三码合一
- 补码的意义在于将减法转换成加法
Ox01 C语言部分
- C语言的诞生
- 编译型语言和解释型语言
- 编译型语言: C/C++
- 指已将源代码完全转译为可执行程序代码,编译完的程序可以脱离语言环境独立运行,但修改则需修改源码
- 解释型语言: JS/Python/Java
- 源码仅转译为中间代码,运行需依赖语言解释器,跨平台性能好,修改较为便捷
Ox10 基础语法
- 关键字
- 宏
- 宏的使用 define
- 有参宏:
- #define show(a) printf(a); // 接受字符串并输出
- 无参宏:
- 定义常量:
- 定义一组有意义的数值(不需要关注具体的值):
- #define UP 1
- #define DWON 10
- 宏的缺陷
- 有参宏: 没有类型检查,注意优先级(内联函数)
- show(1); // 没有类型检查的例子
- // 使用宏记得加括号 #define calc(n) (n+n)
- #define calc(n) n+n
- // int number = 4 + 4 * 5 = 24;
- int number = clac(4) * 5;
- 定义常量的时候
- 常量没有类型检查,C++推荐使用const
- 定义一组有意义的数据时,数据之间没有产生联系,推荐使用枚举
- 变量
- auto int number; // 表示这是一个自动变量
- register int number; // 寄存器变量,通常将使用很多的数据作为寄存器变量
- static int number; // 静态变量,指挥初始化一次
- extern int number; // 声明一个其它(当前)文件已经定义的变量,不能初始化
- 数据类型
- 常量
- 字符型
- 直接字符
- 转义字符
- ''转义
- '\x00'16进制
- '\o00'8进制
- 整型
- 八进制
- 十六进制
- 后缀
- B :二进制数。 binary
- Q (O):八进制数。 octonary
- D :十进制数。 decimal
- H :十六进制数。 hexadecimal
- U :无符号
- L : 长整型
- UL:无符号长整型
- LL:longlong
- 浮点型
- 字符串型
- 变量
- 字符型
- 整型
- short;int;long;long long;
- 浮点型
- 进制转换
- 变量使用
- 赋值符号
= 左右两边类型必须匹配 ,如果是基本类型,可以不匹配, 编译器可以自动隐式转换
- 类型不匹配时, 可以使用强制类型转换进行赋值
- 显式转换(强制转换)
- 显示转换就是人工的进行一个转换
- int f = (int)123.123;
- // 浮点数转整数会进行小数截断
- 隐式转换
- char a = 'a';
- // a 被自动转换为 int 类型
- int b = a;
- // 在进行计算的时候,表达式的类型和表达式中最大类型相同
- auto number = 'a' + 123.0 + 456;
- // 函数传参的时候进行转换
- void show(int);
- show('a'); // movsx eax, 97
- // mov 参数, eax
- 字符串输入输出
- 格式化控制符(占位符)
- %c %d %f %lf %u %hd %s %S(wchar_t*)
- 使用 printf 函数
- printf("%-*.*lf", 10, 2, 1.1);
- printf("%-10.2lf", 1.1);
- %-10.2lf => %lf => double
- %-10.2lf => 不足10个字符宽度补全,否则不变
- %-10.2lf => 小数部分精度为2
- %**-**10.2lf => 左对齐
- 1.10XXXXXX X 表示空格
- 使用 scanf 函数
- 安全版本: 主要用于防止缓冲区溢出
- scanf_s(): 需要在字符(串)的后面加上缓冲区大小
- 运算符和表达式
- 运算符的种类
- 算数运算符(从左到右)
- [ + - * / % ++ --]
- 为了避免不同编译器的不同解释,不应该在表达式中使用自增\减
- 赋值运算符(从右到左)
- a = b = c = d = 0;
- [ += -= *= /= %= =]
- a += b * c + d; => a = a + (b * c + d);
- 逻辑运算符
- && || !
- 短路运算:先执行表达式的一部分,根据执行的结果确定是否执行另一部分
- && 短路运算符: 左边失败就不执行右边
- 任意一边为假就为假 a10 b10 a==b || a++
- || 短路运算符: 左边成功就不执行右边
- 三目运算符
- [表达式 ? 语句一 : 语句二; ]
- 作用if else 相同,表达式成立执行语句一、否则执行语句二
- 位运算符
- [ |(or) &(and) ^(xor) ~(not) >>(ror shr sar) <<]
- 逗号运算符
- , 逗号运算符表达式的值是最后的,但是前面的所有语句都会执行
- (10 ,1.1, 100, "123") -> "123"
- (a=10, b=a,c=a+b,++b) -> a(10) b(11) c(20) 表达式 11
- sizeof 运算符
- 获取类型的大小
- sizeof 表达式
- sizeof(类型) 推荐都加括号
- 运算符的优先级和结合性
- 表达式的种类:
- 赋值表达式: a= 10; 10
- 算数表达式: 1 + 1; 2
- 关系表达式: 1 > 2; false
- 逻辑表达式: 1 && 0: false
- 函数表达式: func(1) 结果是返回值
- 顺序结构
- 选择结构
- 特点:多个分支只会执行一个
- B ? C : D
- if else if else
- 单IF
- 多else结构
- 通常用于指定的范围的判定
- 如果if不成立就执行elseif,如果都不成立就执行else
- switch
- case 标签的顺序要求 : 顺序不分先后
- default 标签表示默认
- break关键字用于跳出switch结构
- 通常用于指定值的判定
- case: default:
- case成立就执行case,否则执行default
- 分支的执行和位置没有任何关系
- break; 如果在case中没有编写break,那么会顺序往下执行,
- 循环结构
- 特点:根据条件循环一定次数
- 循环的种类:
- 入口条件循环:在进入循环的时候首先进行判断
- for:for(语句1; 语句2; 语句3) { 代码块4 }
- 语句1: 通常用于初始化,只会执行1次
- 语句2:循环条件,没执行一次循环体,就要判断一次,判断次数比循环次数+1,肯定是最后一个被执行,条件一旦省略就是死循环
- 语句3:通常用于自增,执行次数和循环次数相同
- 执行顺序: 1 2 4 3 2 4 3 2 4 3 2....
- while
- while(表达式) {代码块}
- 出口条件循环: 先执行语句块再进行判断
- do while
- do { 代码块}while(条件)
- 先执行代码,在判断条件
- break和continue
- break: 可以用于 switch 和循环内,用于直接结束当前最内层的循环
- continue: 只能用于循环内,用于跳过当前最内层循环的剩下部分,进入下次循环
- 循环的嵌套
for (int i = 0; i < 10; ++i)
{
for (int j = i; j < 10; ++j)
{
printf("*");
}
printf("\n");
}
// 杨辉三角
// 菱形
// 九九乘法表
// 水仙花
- 死循环的形成条件
- for(;;): 如果中间的表达式不填写数据,就是死循环
- while(1): 表达式永远为真,只能通过break(goto return)之类的跳转语句结束
- while 与 for 的转换
for (int i = 0; i < 3; ++i)
{
printf("%d ", i);
}
int i = 0;
while (i < 3)
{
printf("%d ", i);
++i;
}
Ox20 中级语法
- 一维数组
- 是用连续的空间存放一组类型相同的数据,数组的大小不可变
- 字符数组
- strlen 求长
- strcpy 拷贝 strcpy_s(VS) strncpy(CRT) strncpy_s(VS)
- strcat 拼接
- sprintf 字符串输出
- sscanf 字符串输入
- strstr 字符串查找
- gets\puts 输入\输出一行,换行
- 一般数组
- memcpy 内存拷贝
- memset 内存赋值
- memmove 内存移动
- memcmp 内存比较
- 数组的初始化
- 整数
- int number[] = {1,2,3}; 大小是由初始化的数据决定的
- int number[10]; // 没有给值,具体的值与所在位置相关
- int number[10] = {}; // 写不写0都是0
- int number[10] = {1,2,3,4}; // 结果是1234000000
- 字符串
- const char* buff1 = "abcde";
- const char buff2[] = "abcde";
- const char buff3[] = { 'a','b','c','d','e' };
- const char* buff4[] = { "abc","def" };
- int len1 = sizeof(buff1); //内存占用
- int len2 = strlen(buff1); //字串占用
- int len3 = _countof(buff1); //单元素占用
- 数组的访问
- 方括号运算符:[]
- 越界问题: 数组下标从0开始, 下标大于等于数组元素个数就会越界
- number[9]; 下标从0开始,越界访问可能产生问题
- number[10] = 0; 可能会产生问题(GS)
- 可能会修改其它变量的数据
- 在堆空间中越界修改一定出错
- 数组的输入输出
- 字符串到数字的转换
- atoi: 只能转十进制
- strtol: 推荐使用,"\x64"->100
- StrToInt: 只能转十进制
- sscanf: 不推荐
- 字符串和字符串数组
- const char *str = "123454678";
- str 保存在栈区,是一个指针, "12345678"在常量区,str指向了常量区
- char str[] = "123435678";
- str在栈区,是一个数组,"12345678"在常量区,执行完毕,str保存的是"12345678";
- 数组的遍历
- 二位数组
- 定义和初始化方式(定义时,数组的低维的最大元素个数不能省略.)
- int number[][10] = {{1, 2}, {3, 4}, {5, 6}};
- int number[10][10] = {{1, 2}, {3, 4}, {5, 6}};
- 花括号中嵌套了几个花括号就是给几行进行了赋值
- 保存的内容如下
- 二维数组的遍历
- 二维字符数组
- 辨别字符串数组的维度
- {"1", "2", "3", "4"}
- const char* str[4] = { 0 }; // 存储的是字符串的地址,不能修改
- char str[4][2]; // 二维数组,保存了所有的字符串,能够修改
- 枚举
- 枚举的定义
- enum DIR { UP, DOWN, LEFT, RIGHT};
- 如果不指定起始的值,值从0开始
- 下一个值是根据当前的值决定的,比如当前是LEFT =2,RIGHT 就是3
- 结构体
- typedef 关键字
- typedef用于定义别名,通常和结构体以前使用
- 由于C语言的规定,不支持直接使用结构体的名字进行变量的定义所以,使用typedef 关键字取别名可以简化类型的定义
struct s { int n; };
s obj = { 1 }; // 只能在 C++ 中使用
struct s obj = { 1 };
- 结构体变量的大小等于结构体每个字段占用大小之和(但是会受到对齐粒度的影响)
- 结构体的定义
// 结构体通常用于存储有关联但是类型不同的一组数据
struct _TEST{
int a;
char b;
};
// 定义了一个结构体并直接产生了一个变量
struct _TEST {
int a;
char b;
}abc = {1, 'a'};
// 定义了结构体并且取别名,
typedef struct _TEST {
int a;
char b;
}TEST, *PTEST;
// 加了 typedef 就是别名,没加就是变量
TEST test; // 没有初始化
TEST test = { 0 }; // 全都初始化为0
TEST test = { 1 }; // 除第一个全都初始化为0
- 访问和修改
- 使用指针: PTEST pTest = &test; pTest->a = 10;
- 使用结构体本身:Test.a = 10;
- 大小计算
- 结构体的总大小是结构体内最大类型的倍数
- 例如最大的是double,那么结构体大小肯定是8的倍数
- 结构体的成员所在的偏移和成员本身的类型相关
- 成员类型为 float, 那么它所在的便宜必须 sizoef(float) 4的倍数
struct _TEST{
char a;
int b;
char c;
double d;
char e;
};
sizeof(_TEST) == 32;
// a X X X b b b b
// c X X X X X X X
// d d d d d d d d
// e X X X X X X X
#pragma pack(1) 内核编程和网络传输都可能用到
- 联合体
- 联合体的定义:
- union u {int n; doube d; char c};
- 联合体的大小是占用空间最大的字段的大小
- 每个字段都共用一块内存. 每个字段的首地址是一样的, 但是字段的类型和大小不一样
- 访问和修改
- 大小的计算
- 联合结构体
- 函数
- 函数的声明和定义
- 函数的组成
- 函数头
- 返回值: 就是函数调用表达式的结果,返回值必须和返回值的类型相同
- 函数名: 标识当前的函数,命名规则和变量相同
- 形参列表: 当前函数接收的参数个数和类型
- 函数体
- 声明和定义
- 声明: void func(int); 声明一个函数没有返回值并且接受一个int参数
- 声明必须要在使用前,然后函数可以没有声明
- 当函数的定义位置在调用之前就不需要声明了
- 声明通常写于头文件,实现 通常位于CPP
- 定义(实现): 通常是函数原型后加上花括号编写函数体
- 函数的调用
- 函数传参
- 形参:形式参数,函数内的局部变量,是实参的拷贝
- 实参:实际参数,传入的具体的值
void show(int n)
{
// 这里的n是一个形参
print("%d\n", n);
}
// 实参的名字和形参没有关系
int number = 10;
// number 就是一个实参
show(number);
show(10);
// 传参结束后,函数内实际
void show(int n)
{
// 将形参赋值为实参
// n = number(10);
// 这里的n是一个形参
print("%d\n", n);
}
push dword ptr:[number] // 栈保存的是 number 的值
- 不同类型的参数传递
- 数组的传参
- int arr[] => void show(int *) => void show(int [])
- int arr[10][10]
- void show(int (*a)[10], int size)
- void show(int [][10], int size)
- arr[10][10] 每一个元素是一个数组,数组的类型是 int[10]
- int(*p)[10]
- 基本类型的传参
- void show(想要传入的类型)
- 结构体的传参
- void show(结构体的名称 变量名)
- 不推荐使用结构体的值传递,占位置,其次不好逆向
- 函数的传参
- 考验的就是函数指针,通常用于windows编程和STL编程
- 传参方式
- 值传递
- 实参和形参的内存空间是独立的, 在函数内部修改形参不会影响实参.
- 地址传递
- 通过指针的方式来传递实参的地址, 因为是指针, 在函数内部可以通过指针的语法来修改实参.
- 传参时参数个数,类型,顺序需要匹配
- 函数返回值
- 函数的返回值类型是void时说明函数没有返回值
- 函数的返回值也属于将函数内部的值传递到函数的外部. 也有传递方式
- 值传递
- 址传递
- 函数的递归
- 内联函数
-
- inline: 在函数前使用inline定义内联函数
-
-
- 缺点: 如果内联函数内的代码过多,会导致所在函数的代码膨胀
- 作用域与生命周期
- 作用域
- 作用域, 限制了一个变量名的使用范围 , 离开作用域之后,变量就不能再被使用 . 不同作用域的变量名可以同名. 同名时, 默认使用小作用域的变量名.
- 复合语句作用域(例如for循环的大括号,if语句的大括号等)
- 函数作用域
- 文件作用域
- 生存周期
- 自动生存周期
- 进入作用域就被分配内存空间
- 离开作用域之后内存空间就被回收
- 手动生存周期
- 使用堆空间分配函数:malloc来分配内存空间
- 使用堆空间释放函数free来释放内存空间
- 变量类型
- 局部变量(定义在函数内部的变量)
- 全局变量(定义在函数外部的变量)
- 静态变量, 定义变量时加了static关键字的局部或全局变量
- 进入作用域后,静态局部变量的内存空间早已被分配, 且再重复进入作用域也不会再次分配空间. 离开作用域后,静态局部变量的内存空间不会被销毁
- 堆空间
- 申请堆空间: malloc
- malloc: 直接申请空间,没有初始化
- realloc: 重新申请空间,把原空间的数据拷贝到新的空间
- calloc : 申请空间并初始化 new int[10]{0} 多了初始化
- 释放堆空间: free
Ox30 高级语法
- 一级指针
- 定义
- 在 C 语言中指针之间可以随意赋值,C语言对类型的要求并不严格
- 指向的类型* 指针的名字 = &指向的变量;
- 如果目标是常量那么指针必须也是常量
- char* c; const char* cc = c; C++ 中常量可以保存非常量,非常量不能保存常量值的
- 常量指针和指针常量,const 在*前面还是后面
- 常量指针: 是一个指针,指向了常量,指针可变,指向内容不可变
- const int* ptr; int const * ptr;
- 指针常量: 是一个常量,类型是指针,指向内容可变,指针本身不可以指向其他东西
- 类型
- 悬空指针: 指向了被释放的空间
- 空指针: 指向了 0 的指针
- 野指针:未初始化的指针
- 运算
- 算术运算
- 指针 +或- 一个整数
- +: 指针+1,加的是 sizoef(指针类型)
- 被加的整数表示一个单位, 这个单位具体是多少字节取决于指针的类型 , 表达式的结果是指针类型
- pDosHeader + pDosHeader.e_lfanew 错误的写法
- 指针 - 指针
- 得到两个地址相隔的元素个数, 不是字节数. 表达式的结果是整型
- -:指针-1,数组中计算的是它们之间的元素个数,指针减法一般没有意义。
- 解引用/取内容
- *运算符
- 用指针保存的地址作为首地址. 用指针的类型作为字节数, 取出这个地址上的指定大小的内容.
- 数组指针
- 一个指针,指向了数组
- 保存数组首地址的指针
- 定义格式1: int (*p)[10] ; 表示指向一个一维数组的指针. p+1时, 1指的是10个int型元素.
- 定义格式2: int (p)[3][4]; 表示指向一个二维数组的指针, p+1时, 1指的是34个整型. 而 p[0] +1 中的1指的是4个int型元素
- 指针数组
- 一个数组,保存了指针
- char* arr[10]; // 一个数组,保存的是字符串的首地址
- const int* arr[10]; // 一个数组,保存的是const int类型数据的地址
- 函数指针
- 保存函数地址的指针. 使得指针可以当成函数一样被调用
- void show(int); // 函数原型
WINBASEAPI
BOOL
WINAPI
CreateProcessA(
_In_opt_ LPCSTR lpApplicationName,
_Inout_opt_ LPSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOA lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
// 指针不区分导入或者导出
typedef BOOL
// WINAPI 表示调用方式 * 表示是指针 PFUNC 是一个函数指针类型
(WINAPI* PFUNC)(
_In_opt_ LPCSTR lpApplicationName,
_Inout_opt_ LPSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOA lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
VOID* test(int n, double* m);
VOID* (*test)(int, double*);
- 预处理指令
- 文件包含
- #include 实际上进行的是赋值粘贴的操作
- 有两种方式可以防止头文件的重复包含
- #pragma(once)
- #ifdef XXX \ #define XXX \ #endif
- 宏定义
- #define 用于定义常量,有参宏或一组有意义的名字
- 条件编译
- #if #else #endif # ifdef #ifndef
- 通常用于根据不同的环境生成代码
- 特殊的宏
- 文件操作
- 打开文件
- 以只读方式打开文件:
r
- 以只写方式打开文件:
w
- 以可读可写方式打开文件:
r+ / w+
- 文件必须存在才打开文件:
r / r+
- 总是以创建新文件的方式打开文件:
w / w+
- 以续写的方式打开文件 :
a+
- 以覆盖的方式打开文件 :
w / w+
- 以二进制形式打开文件 :
b
- 读文件
- 从文件读取一个字符:fgetc
- 从文件读取一串字符串:fgets
- 格式化读取: fscanf
- 从文件读取二进制数据:fread
- 写文件
- 将字符写入到文件:fputc
- 将字符串写入到文件:fputs
- 格式化写入: fprintf
- 将二进制数据写入到文件:fwrite
- 文件读写位置操作
- 获取文件读写位置:ftell
- 设置文件读写位置:fseek
- SEEK_SET:开始位置
- SEEK_CUR:当前位置
- SEEK_END:结束位置
- 关闭文件
- 绝对路径和相对路径
- 绝对路径: 从根目录开始进行查找: 通常是磁盘盘符开始的
- 相对路径: 相对于当前的工作路径进行的寻址(exe所在的路径)
- 生存周期和作用域:
- 局部变量(栈)
- 全局变量(静态数据区) .data
- 静态变量(静态数据区) .data
- 堆变量(堆空间)
- 常量数据区,保存的是所有的常量 .rdata
- 其它标准函数
- clock(time): 获取当前的时间
- rand/srand: (rand\stdlib)
- srand通常在初始化的时候调用一次, srand((unsigned int) time(0));
// 使用当前时间初始化随机数种子
srand((unsigned int)time(0));
for (int i = 0; i < 10; ++i)
{
// 使用当前时间初始化随机数种子
// srand((unsigned int)time(0));
// 输出生成的随机数
printf("%d ", rand());
}
- _getch/ _kbhit(conio.h)
- _getch(): 无回显输入
- _kbhit(): 检查键盘击键,不会接受缓冲区内的输入
if (_kbhit())
charch = _getch();
- ctype.h
- islower: 判断是不是小写
- tolower: 将字母转换为小写
- isalpha: 判断是不是字母
- isdigit: 判断是不是数
- limits.h 跨平台编程 process.h _beginthreadex