域名连接到网站,广东建设项目备案公示网站,网络营销推广的方式方法有哪些,广告设计制作方案目录
第一章 分支与循环语句
1. 悬空else问题 2. 循环输入问题
3. getchar缓冲区溢出问题
4. goto语句坑点
第二章 函数
1.为什么要有库函数
2.函数嵌套
3.链式访问
4.回调函数
5.函数递归
6.字符转换函数
7.模拟实现字符串拷贝strcpy
8.模拟实现字符串的长度s…目录
第一章 分支与循环语句
1. 悬空else问题 2. 循环输入问题
3. getchar缓冲区溢出问题
4. goto语句坑点
第二章 函数
1.为什么要有库函数
2.函数嵌套
3.链式访问
4.回调函数
5.函数递归
6.字符转换函数
7.模拟实现字符串拷贝strcpy
8.模拟实现字符串的长度strlen
9.模拟实现字符串比较strcmp
10.模拟实现字符串追加strcat
11.模拟实现字符串查找strstr
12. 模拟实现内存拷贝memcpy
13.模拟实现内存移动memmove
14.模拟实现内存比较memcmp
15. 认识strtok字符串分割 第三章 数组
1.变长数组的引入
2.数组越界问题
3.数组内存布局
编辑 4.数组传参
5. 二维数组传参
6. 一道经典数组题
7.函数指针数组的应用
第四章 操作符
1.算数操作符
2.移位操作符
3.逗号表达式
4.隐式类型转换
5.算术转换
第五章 指针
1.对指针的大小的理解
2.指针类型的意义
3.野指针问题
4. 如何规避野指针
5. 指针-指针
6.指针关系运算坑点 7. 指针变量的解引用
8. 栈随机化技术
9. 字符指针 10.函数指针传参
第六章 自定义类型
1.结构体
1.1 结构体三种访问方式 1.2 特殊的结构体声明
1.3 结构体自引用问题
1.4 认识匿名结构体
1.5 结构体隐藏问题
1.6 结构体内存对齐
1.7 结构体变量偏移量
2.位段
2.1 内存分配 2.2 位段的跨平台问题
2.3 位段的应用
3. 枚举
4. 联合体(共用体)
第七章 程序翻译
1.预处理
编辑
2. 编译
3. 汇编 4. 连接
5. 预定义符号 6. 宏和函数对比 7. 命令行定义宏变量 8. #undef 9. #ifdef #ifndef
10.#if #elif #endif
编辑
11. #error
12. strerror perror 编辑
13. #line
14. #pragme
15.特殊# 16.特殊##
第八章 关键字
1. 认识auto关键字
2. 认识register关键字
3. 认识extern关键字
4.认识static关键字
5.认识sizeof关键字
6.认识bool(_Bool)关键字
7.认识double关键字
8.认识switch case关键字
9.认识continue关键字
10.认识void关键字 11.认识return关键字 12.认识const关键字
13.认识struct关键字
编辑
14.认识typedef关键字
15.变量内容的存入和读取
16. char类型中特殊的-128 第九章 符号
1. 自增/自减运算符
2. 取整运算 3.负数取模(得余数)问题 第十章 动态内存管理
1. 验证C程序动态地址空间分布
2. 内存越界问题
3. C中动态内存“管理”体现
4. 认识realloc 5. 常见动态内存错误
5.1 对空指针的解引用 5.2 对动态开辟空间的越界访问 5.3 对非动态开辟内存使用free释放
5.4 使用free部分释放内存空间
5.5 对同一块动态内存多次释放
5.6 动态开辟内存忘记释放(内存泄漏) 5.7 一道经典面试题 6. 柔性数组
第十一章 文件
1. 文件的基本使用 2. 文件的顺序读写 3. sscanf sprintf
4. 文件的随机读写
4.1 fseek 4.2 ftell
4.3 rewind
5.feof判断文件是否正确结束
6.文件缓冲区
编辑 第十二章 函数栈帧(难点)
1. 认识相关寄存器
2. 认识相关汇编命令
3.栈帧创建销毁简单流程
3.1创建调用main函数 3.2 创建变量 形参实例化
3.3 调用Myadd函数-压栈
3.4 创建Myadd函数的栈帧-入栈
3.5 释放Myadd函数的栈帧-弹栈
3.6 Myadd函数结果返回
3.7 流程总结
第十三章 可变参数列表(难点)
1. 演示案例 编辑
2. 可变参数的原理
2.1 va_list va_end
2.2 va_start
编辑
2.3 va_arg
编辑 3. 命令行参数
第十四章 数据的存储
1. 为什么数据在内存中存放的是补码 2. 为什么会有大小端 3. 验证机器大小端
4. 浮点数在计算机中从存储
4.1 案例展示 4.2 存储形式
4.3 对有效数字M的特殊说明
4.4. 对指数上标E的特殊说明
4.5 指数E从内存中取出三种情况 4.6 案例分析 第一章 计算机基础知识
1. 计算机组成
计算机由硬件系统和软件系统所组成没有安装任何软件的计算机称为裸机 2. DOS系统 DOS是磁盘操作系统的缩写是个人计算机上的一类操作系统通过命令行窗口来操作计算机。早期的Windows系统就是基于DOS演进而来的现在DOS作为Windows系统安装后自带的程序存在 它主要应用在a.当Windows系统异常时可通过DOS系统修复b. 在DOS窗口中执行某些命令比如pingc. 一些脚本的运行通过DOS运行会更好
这里有一些DOS常见命令如下:
cd 路径表示切换路径dir 目录查看指定目录下所有文件md 盘符:路径/文件新建目录或者文件ipconfig查看本地IP配置信息rd 盘符:路径/目录名删除空目录del 盘符:路径/文件名删除文件ren 旧文件名 新文件名cls 清屏
3. window系统 Windows系统是一个图形化界面操作系统用户与图形用户界面交互比学习复杂的命令语言更容易优点有a.可浏览b.使用方便c.多任务运行
4. 冯洛伊曼体系结构 5. 计算机网络
5.1 分类 5.2 BS模型 浏览器/服务器浏览器将请求发送给Web服务器Web服务器对请求进行处理将响应发回浏览器 5.3 CS模型 客户端/服务器客户端向服务器发出请求服务器处理请求并将响应发送给客户端 5.4 IP地址 唯一标识网络上的每一台计算机IP地址分为IPV4和IPV6两个版本目前使用的是IPV4IPV4地址由4个8位二进制数组成总共32位IPV6是由128位二进制组成 为了理解方便IP地址通过点分十进制来表示即把每8位二进制转换成十进制数
5.5 HTTP 请求消息格式 四大组成请求行 请求头 空行 请求数据 响应消息格式 四大组成状态行、响应头、空行响应正文 get和post请求方法的区别 区别一GET经常用于获取数据POST经常用于提交数据
区别二 GET方法提交数据时将数据拼接到URL中客户端地址栏可见get方式没有请求体而POST方法提交数据时将数据置于消息主体内post方式有请求体
区别三不同浏览器对URL的长度有限制所以GET请求传送的数据量是有限的而理论上POST请求传送的数据量是不受限制的 URL格式 http://106.13.199.209:8080/korei/login.html
协议http主机106.13.199.209端口8080路径/korei/login.ht HTTP响应状态码 分类含义1**信息服务器收到请求需要请求者继续执行操作2**成功操作被成功接收并处理3**重定向需要进一步的操作以完成请求4**客户端错误请求包含语法错误或无法完成请求5**服务器错误服务器在处理请求的过程中发生了错误
6. 前端与后端
前端技术层面-用户交互用户使用的页面或者客户端web,htmlcssjs,CQt客户端
后端技术层面-处理逻辑和保存报错C/C Java Python mysql oracle 达梦数据库
7. 前台与后台
前台系统面向商业用户
后台系统企业内部用户
8. 机器/汇编/高级语言
机器语言利用二进制编码进行指令的编写
汇编语言以缩写英文作为标符编译
高级语言多种编程语言结合之后可以对多条指令进行整合 在操作细节以及中间过程等方面做了适当的简化 机器是无法识别高级语言的高级语言最终还是会编译为机器语言(0和1的二进制) 9. 了解jdk和Maven JDK安装配置教程(保姆级)-CSDN博客 超级详细的Maven使用教程-CSDN博客 说明一下
java语言的软件开发工具包其中包含JRE(Java虚拟机)Maven就是一款帮助程序员构建项目的工具
10.数据格式json
优点学习成本低操作过程简单上手难度低 API协同工具推荐apifox,它能让后端人员在不知道前端的情况下自己测试自己的代码效果发请求-返回json数据
11. REST软件架构风格(了解) 12. Nginx组件
三大作用1. 反向代理 2.负载均衡 3.动静分离
12.1 正向代理
正向代理代理的是客户端让我们能正常访问目的服务器对于服务器来说所有请求来自一台服务器 说明一下 我们需要在客户端配置代理服务器此时将代理服务器和客户端看成一个客户端这样服务器就不知道是那个客户端发送的请求通过代理服务器进行访问这就是正向代理
12.2 反向代理
反向代理代理的是服务器让大量的请求均衡的访问到某一台服务器上对于客户端来说响应来自一台服务器 客户端对反向代理是无感知的因为客户端不需要任何配置就可以访问我们只需要将请求发生到反向代理服务器上再由反向代理服务器去选择目标服务器获取数据后再返回给客户端 12.3 负载均衡
负载均衡是指通过某种算法将流量分发到多个服务器上以确保系统稳定性和高效性的技术手段 常见的策略
轮询(默认)就是全部遍历一遍 权重就是按照一定的比例分配数据包ip_hash相同的数据包让同一个处理过数据包的IP服务器继续处理(建立的映射) 12.4 动静分离 这个是已经被淘汰的技术当时是为了加快网站的解析速度可以把动态网页和静态网页分离由不同的服务器来解析加快解析速度减低原来单个服务器的压力 第二章 C/C介绍
1. C/C能做哪些东西
嵌入式开发 C服务器 系统软件 开发驱动 游戏开发 偏硬件 偏底层
2.对比C/C和JAVA C/CJAVA都是高级语言偏向自然语言 C的程序c可以直接运行C面向过程C面向对象JAVA面向对象 C/C偏硬件偏汇编语言编译效率更高相对JAVA运行效率偏低
3.C/C开发环境 C/C不能跨平台(但QT可以跨平台)所以不同的操作系统开发的C/C程序无法直接使用而嵌入式多在linux中运行所以做嵌入式开发需要linux环境 推荐在windows上安装虚拟机再安装ubuntu 当然直接使用vs2019这种集成开发环境也是没有问题的比较无脑我也比较喜欢
4. ubuntu安装(了解) VMware安装ubuntu · 语雀 4.1 VM三种网络模式(难点) Bridged桥接模式 原理
虚拟机直接连接到物理网络就像局域网LAN中的一台真实计算机VMware 创建一个“桥接”让虚拟网卡vNIC连接到宿主机的物理网卡pNIC相当于让虚拟机和物理机共享同一个网络接口DHCP 服务器如路由器自动分配 IP 地址或手动配置虚拟机和局域网的其他设备处于同一子网数据包在物理网络中直接传输虚拟机和其他设备可以互相访问不需要额外的 NAT 或代理 特点
虚拟机直接连接到物理网络与宿主机Host处于同一网段就像局域网中的一台独立计算机虚拟机的 IP 地址由物理网络的 DHCP 服务器如路由器分配或者手动配置与外部网络完全互通虚拟机可以直接访问互联网和局域网中的其他设备其他设备也能直接访问虚拟机 NAT地址转换模式 原理
VMware 提供一个虚拟 NAT 设备vmnet8所有 NAT 模式下的虚拟机通过这个设备共享宿主机的网络连接 虚拟机使用私有 IP 地址通常是 192.168.8.x宿主机作为网关使用 NAT网络地址转换 技术让虚拟机的数据流量通过宿主机访问外部网络外部设备无法直接访问虚拟机因为 NAT 只允许出站连接入站连接默认被阻止除非手动配置端口转发
特点
虚拟机通过宿主机的网络访问外部网络但外部设备不能直接访问虚拟机 VMware 提供一个虚拟 NAT 设备为虚拟机分配一个私有 IP 地址通常是 192.168.x.x 或 10.x.x.x宿主机充当网关负责转发虚拟机的流量到外部网络并进行 NAT 转换 Host-Only仅主机模式 主机模式与NAT模式相比没有虚拟的NAT设备 原理
VMware 创建一个独立的虚拟网络vmnet1仅允许虚拟机和宿主机通信无法访问外部网络所有 Host-Only 网络的虚拟机使用 VMware DHCP 服务器分配的 IP 地址如 192.168.56.x外部设备无法访问虚拟机也不能直接上网除非手动配置宿主机作为网关 特点
虚拟机只能与宿主机和其他 Host-Only 网络中的虚拟机通信无法访问外部网络除非手动配置 NAT 或端口转发VMware 创建了一个专用的虚拟网络并由 VMware 提供 DHCP 服务器为虚拟机分配 IP 地址完全隔离外部设备无法访问虚拟机虚拟机也不能访问互联网除非额外配置 第一章 分支与循环语句
1. 悬空else问题 现象 #define _CRT_SECURE_NO_WARNINGS
#includestdio.h
int main()
{int a 0;int b 2;if (a 1)if (b 2)printf(hehe\n);//不打印elseprintf(haha\n);//不打印return 0;
} 出现这个的主要原因是else会匹配离自己最近的那个if 解决方案 #define _CRT_SECURE_NO_WARNINGS
#include stdio.h
int main()
{int a 0;int b 2;if(a 1){if(b 2){printf(hehe\n);}}else{printf(haha\n);} return 0;
} 编程小建议if和else中的代码尽量写在代码块里{ } 2. 循环输入问题
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
int main()
{int ch 0;while ((ch getchar()) ! EOF){putchar(ch);}return 0;
}
getchar输入一个字符putchar输出一个字符 对EOF的正确解释 为End Of File的缩写通常在文本的最后存在用这个字符来表示文本结束
ASCII代码值的范围是0~127不可能出现-1所以EOF在C语言中为-1在while循环中以EOF作为文件结束标志
3. getchar缓冲区溢出问题 现象 #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{char password[20] { 0 };printf(请输入密码:);scanf(%s, password);//123456printf(请确认密码(Y/N):);char ch getchar();if (ch Y)printf(确认成功\n);elseprintf(确认失败\n);return 0;
} 出现这个的主要原因是回车会触发\n则上面代码匹配的时候总是会“确认成功” 解决方案 清除掉缓冲区中所有的\n 4. goto语句坑点
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includestring.h
#includestdlib.h
int main()
{char input[20] { 0 };system(shutdown -s -t 60);
tag:printf(请注意你的电脑将在1分钟内关机如果输入我是猪就取消关机\n);scanf(%s, input);if (strcmp(input, 我是猪) 0){system(shutdown -a);return 0;}goto tag;return 0;
}
goto语句一般要用标记跳转的标号来配合使用以达到跳转的目的
说明一下
goto语句一般用不到更多应用在终止程序在某些深度嵌套的结构的处理过程
且goto语句不能跨函数跨文件使用
第二章 函数
1.为什么要有库函数 为了支持可移植性和提高程序的效率所以C语言的基础库中提供了一系列类似的库函数方便程序员进行软件开发
2.函数嵌套
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
void test3()
{printf(hehe\n);//hehe
}int test2()
{test3();return 0;
}int main()
{test2();return 0;
}
一个函数内部有另一个函数的调用就叫做函数嵌套
3.链式访问
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
#includestring.h
int main()
{int len strlen(abc);printf(%d\n, len);//链式访问printf(%d\n, strlen(abc));return 0;
}
把一个函数的返回值作为另外一个函数的参数就叫做链式访问
4.回调函数 将一个函数A的地址传给另一个函数B(用函数指针接收),该函数B又通过解引用调用其他函数 #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
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 menu()
{printf(**************************\n);printf(**** 1. add 2. sub ****\n);printf(**** 3. mul 4. div ****\n);printf(**** 0. exit ****\n);printf(**************************\n);
}int Calc(int (*pf)(int, int))
{int x 0;int y 0;printf(请输入2个操作数:);scanf(%d %d, x, y);return pf(x, y);
}int main()
{int input 0;//计算器-计算整型变量的加、减、乘、除//ab a^b a|b ab ab abdo {menu();int ret 0;printf(请选择:);scanf(%d, input);switch (input){case 1:ret Calc(Add);printf(ret %d\n, ret);break;case 2:ret Calc(Sub);printf(ret %d\n, ret);break;case 3:ret Calc(Mul);//printf(ret %d\n, ret);break;case 4:ret Calc(Div);//printf(ret %d\n, ret);break;case 0:printf(退出程序\n);break;default:printf(选择错误重新选择!\n);break;}} while (input);return 0;
}
Clac这一个函数就能调用多个函数减少了代码的冗余Clac就像一个集成器
5.函数递归
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
void print(unsigned int n)//123
{if (n 9){print(n / 10);}printf(%d , n % 10);//1 2 3
}int main()
{unsigned int num 0;scanf(%u, num);//123//递归 - 函数自己调用自己print(num);//print函数可以打印参数部分数字的每一位return 0;
}
函数自己嵌套自己自己调用自己就叫做函数递归而一个正确的函数递归不仅需要一个限制条件而且还需要一个接近限制条件的条件 写递归代码的几个要求
不能写死递归必须要有跳出条件每次递归都应该逼近跳出条件递归层次不能太深防止栈溢出
6.字符转换函数 islower——判读小写字符 返回值描述: 是小写字符就返回非0
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include ctype.h
int main()
{char ch a;if (islower(ch)) {printf(小写字母\n);}return 0;
} isupper——判读大写字符 返回值描述: 是大写字符就返回非0
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include ctype.h
int main()
{char ch A;if (isupper(ch)) {printf(大写字母\n);}return 0;
}
7.模拟实现字符串拷贝strcpy char * strcpy ( char * destination , const char * source ); #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h
char* my_strcpy(char* des, const char* sou)
{assert(des sou);char* ret des;while (*des *sou){;}return ret;
}
int main()
{char arr1[20] { xxxxxxxxxxxxxxxxx };char arr2[] { hello };printf(%s\n, my_strcpy(arr1, arr2));//链式访问printf(%s\n, strcpy(arr1, arr2));//链式访问return 0;
}
strcpy拷贝的时候会把\0一起拷贝过去
8.模拟实现字符串的长度strlen size_t strlen ( const char * str) #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h//计数器
int my_strlen1(const char* str)
{assert(str);int count 0;while (*str ! \0) {count;}return count;
}int main()
{char arr[] abcdefg;printf(%d\n, my_strlen3(arr));printf(%d\n, strlen(arr));return 0;
} 字符串是以\0作为结尾的
9.模拟实现字符串比较strcmp int strcmp ( const char * str1, const char * str2) #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h
int my_strcmp(const char* s1, const char* s2)
{assert(s1 s2);//断言while (*s1 *s2){if (*s1 \0){//当读取到\0结束时return 0;}s1;s2;}return *s1 - *s2;
}int main()
{char arr1[] abce afd;char arr2[] abce f;if (!my_strcmp(arr1, arr2)) {printf(两个字符串相同\n);}else if (!strcmp(arr1, arr2)) {printf(两个字符串相同\n);}else {printf(两个字符串不同\n);}return 0;
}
10.模拟实现字符串追加strcat char *strcat (char *destination , const char* soure) ; #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h
char* my_strcat(char* dest, const char* src)
{char* ret dest;assert(dest src);//1. 找目标字符串中的\0while (*dest){dest;}//2. 追加源字符串,包含\0while (*dest *src){;}return ret;
}int main()
{char arr1[20] abc ;char arr2[] eeffff;printf(%s\n, my_strcat(arr1, arr2));printf(%s\n, strcat(arr1, arr2));return 0;
} 先找尾再追加字符串
11.模拟实现字符串查找strstr
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h
char* my_strstr(const char* str1, const char* str2)
{assert(str1 str2);if (*str2 \0)//如果查找的是空字符{return str1;}const char* first1 str1;const char* first2 str2;while (*str1 ! \0){if (*str1 ! *str2) {str1 first1;str2 first2;}else {str1;str2;}if (*str2 \0) {return first1;}}return NULL;
}int main()
{char arr1[] abbcdef;char arr2[] bcd;if (my_strstr(arr1, arr2)){printf(%s\n, my_strstr(arr1, arr2));}if (strstr(arr1, arr2)) {printf(%s\n, strstr(arr1, arr2));}else {printf(不存在\n);}return 0;
} 说明一下
需要考虑字符串为空的情况需要用另一个指针来记录它原来的位置以便不匹配时能重新匹配
12. 模拟实现内存拷贝memcpy void *memcpy (void *destnation, const void * source,size_t num ); #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h
void* my_memcpy(void* des, const void* cou, size_t num)
{assert(des cou);void* ret des;//记录地址while (num--){*(char*)des *(char*)cou;des (char*)des 1;cou (char*)cou 1;}return ret;
}
int main()
{int arr1[] { 1,2,3,4,5,6,7,8,9,10 };int arr2[] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr1) / sizeof(arr1[0]);int i 0;my_memcpy(arr1 2, arr1, 20);for (i 0; i sz; i){printf(%d , arr1[i]);}printf(\n);memcpy(arr2 2, arr2, 20);//memmove(arr2 2, arr2, 20);for (i 0; i sz; i){printf(%d , arr2[i]);}return 0;
}
说明一下
只要跟内存相关的都是void*都是需要强制类型转换 这里的num参数代表字节数的意思且memcpy是需要返回目的地址的起始地址
补充一下
在C语言中memcpy是不能自己拷贝自己的(重叠的内存块)自己拷贝自己有一个更安全的库函数memmove 但在vs2019中memcpy是可以自己拷贝自己的即memcpy的功能和memmove的功能相同
13.模拟实现内存移动memmove void *memmove(void *destinationconst void*source ,size_t num ); #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h
void* my_memmove(void* des, const void* cou, size_t num)
{assert(des cou);void* ret des;//记录地址//从前开始移动if (des cou) {while (num--){*(char*)des *(char*)cou;des (char*)des 1;cou (char*)cou 1;}}//从后开始移动else {while (num--){*((char*)des num) *((char*)cou num);}}return ret;
}int main()
{int arr1[] { 1,2,3,4,5,6,7,8,9,10 };int arr2[] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr1) / sizeof(arr1[0]);int i 0;my_memmove(arr1 2, arr1, 20);memmove(arr2 2, arr2, 20);for (i 0; i sz; i){printf(%d , arr1[i]);}printf(\n);for (i 0; i sz; i){printf(%d , arr2[i]);}return 0;
} memmove是可以自己拷贝自己的
14.模拟实现内存比较memcmp int memcmp (const void * ptr1const void * ptr2size_t num); #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestring.h
int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{assert(ptr1 ptr2);while (num--){if(*(char*)ptr1 ! *(char*)ptr2){return *(char*)ptr1 - *(char*)ptr2;}ptr1 (char*)ptr1 1;ptr2 (char*)ptr2 1;}return 0;
}int main()
{int arr1[] { 1,2,3,4,5,6,7,8,9,10 };int arr2[] { 1,2,2,3,4,5,6,7,7,2 };int sz my_memcmp(arr1, arr2,20);if (sz 0) {printf(arr1arr2\n);}else if (sz 0) {printf(arr1arr2\n);}else {printf(arr1arr2\n);}return 0;
}
说明一下
不论是字符串比较还是内存比较都是需要二者不同时返回str1 - str2返回值成功则返回0不成功返回str1 - str2
15. 认识strtok字符串分割 返回值描述如果找到标记则指向标记开头的指针。 否则为空指针。 当正在扫描的字符串中到达字符串结尾即空字符时始终返回空指针
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include string.h
#include assert.h
int main()
{char arr[] zpwbitedu.tech hehe;char* p . ;//分隔符的集合char tmp[30] { 0 };strcpy(tmp, arr);//临时拷贝//zpw\0bitedu\0tech\0char* ret NULL;for (ret strtok(tmp, p); ret ! NULL; retstrtok(NULL, p)){printf(%s\n, ret);}return 0;
} 说明一下 strtok函数的第一个参数不为 NULL 函数将找到str中第一个标记strtok函数将保存它在字符串中的位置 strtok函数的第一个参数为 NULL 函数将在同一个字符串中被保存的位置开始查找下一个标记 如果字符串中不存在更多的标记则返回 NULL 指针 第三章 数组
1.变长数组的引入
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
int main()
{int n 10;int arr[n] { 0 };return 0;
}
C99引入了变长数组的概念可以给[ ] 中加一个变量才可以C99之前是没有变长数组的[ ] 只能加一个常量
2.数组越界问题
#define _CRT_SECURE_NO_WARNINGS
#include stdio.h
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };printf(%d\n, arr[10]);printf(%d\n, arr[11]);printf(%d\n, arr[12]);return 0;
} 说明一下
数组越界不一定报错但是可能会有警告
3.数组内存布局 局部变量都是在栈区上面的而栈区的使用习惯是先使用高地址再使用低地址在开辟空间的角度不应该把数组认为成一个个独立的元素要整体开辟整体释放 4.数组传参 为了解决拷贝问题, 所有的数组传参都会发生降维都会降维成指向内部元素类型的指针
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
void show(int pr[])
{int i 0;for (i 0;i 10;i){printf(%d , *(pr i));//printf(%d ,pr[i]);}
}
int main()
{ int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int i 0;show(arr);return 0;
}
为了减低编程难度指针和数组都可以通过*和[] 进行解引用但它们的寻址方案完全不一样
5. 二维数组传参 说明一下
传入的参数是二维数组的首地址第二个test错误接收时int arr[][],可以用二维数组接收,但不能省略列数第四个test错误不能用一级指针接收,用指针接收,只能用数组指针(一级) 第五个test错误不能用一级指针数组接收,用数组接收,只能用二维数组第七个test错误不能用二级指针接收,用指针接收,只能用数组指针(一级)
6. 一道经典数组题 数组名取的是整个数组的地址1就会跳过整个数组的地址 数组名取的是整个数组的首地址1就会跳过一个元素 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{int a[4] { 1,2,3,4 };int* ptr1 (int*)(a 1);int* ptr2 (int*)((int)a 1);printf(%x,%x\n, ptr1[-1], *ptr2);return 0;
} 大多数机器都是小端机,存的时候用小端,取的时候也用小端
7.函数指针数组的应用 实现计算器的加减乘除 #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
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 menu()
{printf(**************************\n);printf(**** 1. add 2. sub ****\n);printf(**** 3. mul 4. div ****\n);printf(**** 0. exit ****\n);printf(**************************\n);
}int main()
{int input 0;//计算器-计算整型变量的加、减、乘、除//ab a^b a|b ab ab abdo {menu();int (*pfArr[5])(int, int) { NULL, Add, Sub, Mul, Div };int x 0;int y 0;int ret 0;printf(请选择:);scanf(%d, input);//2if (input 1 input 4){printf(请输入2个操作数:);scanf(%d %d, x, y);ret pfArr[input](x, y);printf(ret %d\n, ret);}else if (input 0){printf(退出程序\n);break;}else{printf(选择错误\n);}} while (input);//只有输入0才退出return 0;
} 说明一下
这个函数指针数组中存放的是加法函数减法函数乘法函数除法函数 函数指针数组更像是一个跳板的作用,可以减少代码冗余
第四章 操作符
1.算数操作符
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{int a1 6 / 5;printf(%d\n, a1);//1float a2 6 / 5;printf(%f\n, a2);//1.000000float a3 6.0 / 5.0;printf(%f\n, a3);//1.200000return 0;
}
说明一下
除了 % 操作符之外其他的几个操作符都可以作用于整数和浮点数%不能用于浮点数/ 操作符左右两边至少有一个为小数结果才为小数比如6/51,6/5.01.2
2.移位操作符
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{int a 2;//把a的二进制位向左移动一位int b a 1;printf(b %d\n, b);//4printf(a %d\n, a);//2int c -1;//把c的二进制位向右移动一位int d c 1;printf(c %d\n, c);//-1printf(d %d\n, d);//-1return 0;
}
说明一下
左移操作符移位规则左边丢弃右边补0右移操作符移位规则 逻辑移位右边丢弃左边补0算术移位右边丢弃左边补原符号位
补充一下 vs2019中对于右移操作符是采用的算术移位 对于移位运算符不要移动负数位这个是标准未定义的
3.逗号表达式 exp1 , exp2 , exp3 , …expN #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{int a 1;int b 2;int c (a b, a b 10, a, b a 1);//逗号表达式printf(%d\n, c);//13return 0;
}
逗号表达式就是用逗号隔开的多个表达式从左向右依次执行。整个表达式的结果是最后一个表达式的结果
4.隐式类型转换 隐式类型转化又叫做整形提升 至于为什么会发生整形提升解释起来很麻烦一句话表达式中各种长度可能小于int长度的整型值都必须先转换为int或unsigned int 然后才能送入 CPU 去执行运算
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{char a 3;char b 127;char c a b;printf(%d\n, c); //-126return 0;
} 这里的a b c三个都是char都没有达到int所以一定会发生整形提升整形提升是按照原符号位进行提升的
5.算术转换
第一位long double第二位double第三位float第四位unsigned long int 第五位long int 第六位unsigned int第七位int 如果某个操作数的类型在上面这个列表中排名较低那么首先要转换为另外一个操作数的类型后执行运算
比如说一个整数乘以一个小数结果是小数这就发生了算术转换
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{float f 3.14;int num f;//隐式转换会有精度丢失return 0;
}
这段代码存在潜在的问题 且算术转换都是向着精度更高的转换
第五章 指针
1.对指针的大小的理解
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{int* pa;char* pc;float* pf;printf(%d\n, sizeof(pa));//4或8printf(%d\n, sizeof(pc));//4或8printf(%d\n, sizeof(pf));//4或8return 0;
} 指针的大小跟指针的类型无关取决于机器的是32位的还是64位的 在32位的机器下指针的大小为4在64位机器下指针的大小为8
2.指针类型的意义
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{int arr[10] { 0 };int* p arr;char* pc (char*)arr;printf(%p\n, p);printf(%p\n, p 1);printf(%p\n, pc);printf(%p\n, pc 1);return 0;
} 指针的类型决定了指针向前或者向后走一步有多大(距离)
3.野指针问题
定义野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includestdlib.h
int main()
{//1.未初始化int* p;//p野指针*p 20;//2.越界访问int arr[10] { 0 };int* ps arr;int i 0;for (i 0; i 10; i){*ps i;//当i 10,ps野指针ps;}//3.指向的空间释放int* tmp (int*)malloc(sizeof(int) * 2);free(tmp);int* pt tmp;//tmp野指针return 0;
} 原因指针未初始化指针越界访问指针指向的空间已经被释放了但还是指向这段空间 #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int* test()
{int a 10;return a;
}int main()
{int*p test();*p 20;return 0;
} 同理这里的p指针也是野指针因为a变量是栈上开辟的出了作用域就被回收了则p指针指向的这一段就是已经被释放了的空间
4. 如何规避野指针
a.指针初始化 b.小心指针越界 c.指针指向空间释放及时置 NULL
d.避免返回局部变量的地址 e.指针使用之前检查
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{//当前不知道p应该初始化为什么地址的时候直接初始化为NULLint* pa NULL;//明确知道初始化的值int a 10;int* pc a;int* p NULL;if(p ! NULL)*p 10;return 0;
}
C语言本身是不会检查数据的越界行为的但编译器还是会报警告
5. 指针-指针 前提两个指针指向同一块空间 #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };char c[5];printf(%d\n, arr[9] - c[0]);//errprintf(%d\n, arr[9] - arr[0]);//9return 0;
}
指针-指针得到的是中间的元素个数而指针指针没有意义就像日期日期一样没意义所以不讨论
6.指针关系运算坑点
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{int arr[5] { 1,2,3,4,5 };int* ps NULL;//允许指向数组元素的指针与指向数组最后一个元素//后面的那个内存位置的指针比较for (ps arr; ps arr[5]; ps){*ps 0;}//但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。for (ps arr[4]; ps arr[-1]; ps--){*ps 0;}return 0;
} 说明一下
实际在绝大部分的编译器上是可以顺利完成任务的然而我们还是应该避免这样写因为标准并不保证它可行标准规定允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较 但是不允许与指向第一个元素之前的那个内存位置的指针进行比较 7. 指针变量的解引用 指针变量是由 空间(左值)内容(右值及地址)组成 0x1234如果是赋给p指针变量的空间是不会报错的由此推断出0x1234是赋给p指针变量的内容指针变量进行解引用使用的是指针变量的右值(内容及地址)
8. 栈随机化技术
由于存在栈随机化技术 使得每次重新编译打印同一个变量的时候都地址都不一样
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{int a 10;printf(%p\n, a);return 0;
}
9. 字符指针
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{char ch q;char * pc ch;char* ps hello bit;char arr[] hello bit;*ps w;//errarr[0] w;printf(%c\n, *ps);//hprintf(%s\n, ps);//hello bitprintf(%s\n, arr);//wello bitreturn 0;
}
说明一下 char* ps hello bit;不是把字符串 hello bit放到字符指针 ps 里而是把hello bit这个字符串的首字符的地址存储在了ps中 hello bit是一个常量字符串常量字符串是不能被修改则*ps w;这个语句就是错的 一道经典题 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{char str1[] hello bit.;char str2[] hello bit.;const char* str3 hello bit.;const char* str4 hello bit.;//*str3 w;if (str1 str2)printf(str1 and str2 are same\n);elseprintf(str1 and str2 are not same\n);if (str3 str4)printf(str3 and str4 are same\n);elseprintf(str3 and str4 are not same\n);return 0;
} 说明一下
这其实也很好理解hello bit.,这是一个常量字符串不能被修改又因为str1和str2都是指向同一个常量字符串自然也就不需要再开辟一段空间放相同的常量字符串srt1和str2虽然数组的内容一样但是str1和str2中的hello bit.是可以被修改所以开辟了2个不同数组存放hello bit.其实str1和str2中的内容是在编译期间将hello bit(字符串常量区中)拷贝过去的 str1和str2本身是数组str3和str4本身是指针 10.函数指针传参 函数名 函数名 *函数指针 函数指针 第六章 自定义类型
1.结构体
1.1 结构体三种访问方式 说明一下
结构体指针本身可以用-访问变量*结构体指针就变成结构体对象而结构体对象就可以直接用.来访问 1.2 特殊的结构体声明
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], * p;
int main()
{p x;return 0;
} 虽然上面两个结构体的内容是相同的,但是编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的
1.3 结构体自引用问题
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
struct Node
{int data;struct Node next;
};
int main()
{struct Node a;return 0;
}
说明一下 结构体是不能自己嵌套自己的,因为sizeof(struct Node)是未知的
1.4 认识匿名结构体
#include stdio.hint main() {// 定义一个包含匿名结构体的变量struct {int x;int y;} point1, point2;// 给 point1 和 point2 赋值point1.x 10;point1.y 20;point2.x 30;point2.y 40;// 输出printf(point1: (%d, %d)\n, point1.x, point1.y);printf(point2: (%d, %d)\n, point2.x, point2.y);return 0;
}在上面的代码中我们定义了一个没有名字的结构体直接在 struct {} 中定义了结构体的字段 x 和 y。然后通过 point1 和 point2 来引用它们而point1 和 point2 就是这个匿名结构体的两个实例 结构体没有给定名字所以我们无法在程序的其他地方再定义同样的结构体类型 主要应用场景 struct {int id;struct {int x;int y;}; // 匿名结构体
} obj;obj.id 1;
obj.x 10; // 直接访问匿名结构体的成员
obj.y 20;它主要应用在结构体嵌套结构体中可以使代码更加简洁
1.5 结构体隐藏问题 在编译器中是先定义next再typedef(最后调用)此时的结构体是匿名结构体所以会报一堆错
1.6 结构体内存对齐
首先得掌握结构体的对齐规则如下
第一个成员在与结构体变量偏移量为0的地址处其他成员变量要对齐到某个数字对齐数的整数倍的地址处 对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍 如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整 体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍 内存结构体内存对齐的原因 某些硬件设备只能存取对齐数据存取非对齐的数据可能会引发异常 某些硬件设备不能保证在存取非对齐数据的时候的操作是原子操作 相比于存取对齐的数据存取非对齐的数据需要花费更多的时间 某些处理器虽然支持非对齐数据的访问但会引发对齐陷阱alignment trap 某些硬件设备只支持简单数据指令非对齐存取不支持复杂数据指令的非对齐存取 修改默认对齐数 说明一下
#pragma pack(2) 可以将默认对齐数改成2 小结 结构体的内存对齐是拿空间来换取时间的做法所以在使用过程中让占用小的空间放在一起这样可以让满足内存对齐时 节省空间
1.7 结构体变量偏移量
offsetof是一个宏,能计算结构体的成员变量的偏移量需要引入头文件stddef.h
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stddef.h
#pragma pack(2)
struct S
{char c1;int i;char c2;
};int main()
{printf(%d\n, offsetof(struct S, c1));printf(%d\n, offsetof(struct S, i));printf(%d\n, offsetof(struct S, c2));return 0;
} 2.位段 跟结构体相比位段可以达到同样的效果虽然可以很好的节省空间但是有跨平台的问题 2.1 内存分配 位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟的位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s { 0 };s.a 10;s.b 12; s.c 3; s.d 4;return 0;
} 说明一下
大小端是字节序才考虑的这里不考虑 2.2 位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的位段中最大位的数目不确定16位机器最大1632位机器最大32写成27在16位机器会出问题位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是舍弃剩余的位还是利用这是不确定的
2.3 位段的应用
两个比特位有四种组合00,01,10,11
性别男女保密用两个比特位足够表示char a : 2就行了不用int a有时候用位段比定义变量定义结构体更加节省空间
3. 枚举
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
void menu()
{printf(*****************************\n);printf(**** 1. add 2. sub *****\n);printf(**** 3. mul 4. div *****\n);printf(**** 0. exit *****\n);printf(*****************************\n);
}enum Option
{EXIT,//0ADD,//1SUB,//2MUL,//3DIV,//4
};int main()
{int input 0;do{menu();printf(请选择:);scanf(%d, input);switch (input){case ADD://case 1:break;case SUB://case 2:break;case MUL://case 3:break;case DIV://case 4:break;case EXIT://case 5:break;default:break;}} while (input);return 0;
} 说明一下 枚举增加代码的可读性和可维护性 和#define定义的标识符相比较(在预处理阶段就替换了)而枚举有类型检查更加严谨 还可以防止了命名污染(封装)便于调试, 使用方便一次可以定义多个常量
4. 联合体(共用体) 联合的成员是共用同一块内存空间的这样一个联合变量的大小至少是最大成员的大小因为联合至少得有能力保存最大的那个成员
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
union Un
{char c;//1int i;//4
};int main()
{union Un u {10};u.i 1000;u.c 100;printf(%p\n, u);printf(%p\n, (u.c));printf(%p\n, (u.i));printf(%d\n, sizeof(u));//return 0;
} 联合体的内存对齐 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
union Un
{char a[5];//1 5int i;//4char c;//1
};int main()
{union Un u;printf(%d\n, sizeof(u));return 0;
} 说明一下
联合的大小至少是最大成员的大小当最大成员大小不是最大对齐数的整数倍的时候就要对齐到最大对齐数的整数倍 最大成员的大小是5最大对齐数是4所以结果是8
第七章 程序翻译 程序翻译的过程预处理-编译-汇编-连接 1.预处理 预处理过程头文件的展开-宏替换-去注释-条件编译 gcc -E test.c -o test.i 证明宏替换和去注释的先后顺序 预处理期间先执行去注释后进行宏替换 宏的有效范围 说明一下 宏的有效范围是从定义处往下有效之前无效在源文件的任意地方宏都可以定义与是否在函数内外无关
2. 编译 gcc -S test.i -o test.s
3. 汇编 gcc -c test.s -o test.o 4. 连接
把多个目标文件和连接库进行链接的
5. 预定义符号
__FILE__进行编译的源文件__LINE__文件当前的行号__DATE__文件被编译的日期__TIME__文件被编译的时间__STDC__如果编译器遵循ANSI C其值为1否则未定义
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
int main()
{printf(%s\n, __FILE__);printf(%d\n, __LINE__);printf(%s\n, __DATE__);printf(%s\n, __TIME__);printf(%s\n, __FUNCTION__);int i 0;FILE* pf fopen(log.txt, a);if (pf NULL){perror(fopen\n);return 1;}for (i 0; i 10; i){fprintf(pf, %s %d %s %s %d\n,\__FILE__, __LINE__, __DATE__, __TIME__, i);}fclose(pf);pf NULL;//printf(%d\n, __STDC__);//不支持return 0;
} 上面那些预定义符号都是内置的 6. 宏和函数对比 属性 #define定义宏函数执行速度更快存在栈帧的创建和销毁所以会慢一点参数类型宏的参数与类型无关函数的参数是与类型有关调试不方便调试的可以逐语句调试的递归不能递归的可以递归的 宏的特殊用法 说明一下
宏的参数可以出现类型但是函数做不到 建议写宏的时候全部大写 7. 命令行定义宏变量 命令行中定义符号: gcc test.c -D M10 8. #undef #undef是取消宏的意思主要用于限定宏的有效范围 9. #ifdef #ifndef 主要用于裁剪代码快速实现版本维护跨平台性等 10.#if #elif #endif #if 多用于判断宏的真假 11. #error #error的核心作用是可以进行自定义编译报错。还可以定制化文件名称和代码行号 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
//#define __cplusplus
int main()
{#ifndef __cplusplus #error 老铁你用的不是C的编译器哦 #endifreturn 0;
} 12. strerror perror
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include errno.h
#include string.h
int main()
{FILE* pf fopen(test.txt, r);if (pf NULL){printf(%s\n, strerror(errno));perror(fopen);return 1;}fclose(pf);pf NULL;return 0;
} 说明一下
strerror: 返回错误码所对应的错误信息error: 返回错误码所对应的错误信息(更清晰) 常见错误码介绍 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include string.h
int main()
{printf(%s\n, strerror(0));printf(%s\n, strerror(1));printf(%s\n, strerror(2));printf(%s\n, strerror(3));printf(%s\n, strerror(4));printf(%s\n, strerror(5));return 0;
} 13. #line 定制化文件名称和代码行号 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{ printf(%s, %d\n, __FILE__, __LINE__);
#line 60 hehe.h //定制化完成printf(%s, %d\n, __FILE__, __LINE__); return 0;
}
说明一下
_FILE_当前文件的文件名_LINE_当前代码的行号
14. #pragme 在编译中打印信息 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#define M 10
int main()
{
#ifdef M
#pragma message(M宏已经被定义了)
#endifreturn 0;
} 15.特殊# 宏中使用# 就是将内容转换成为字符串 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include string.h
#define TOSTRING(s) #s
int main()
{int abc 12345;char str[64] { 0 };strcpy(str, TOSTRING(abc));printf(%s, str);return 0;
} 16.特殊## 宏中使用## 就是连接内容组成新的字符串 第八章 关键字 C90一共有32个关键字C99比C90多了5个关键字但主流的编译器对C99关键字支持的不是特别好所以一般就以C90的32个关键字为标准
1. 认识auto关键字 被auto修饰的变量叫做局部变量默认都是auto修饰的不过一般省略 #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
int main()
{int i 0;auto int j 0;return 0;
}
2. 认识register关键字 被register所修饰变量会被放入CPU寄存区中从而达到提高效率的目的 但现在的编译器已经很智能化了,它能够自主的决断是否将变量放入CPU寄存器
注意被register修饰的变量不能取地址因为这个变量已经在寄存区中了地址是内存相关的概念
3. 认识extern关键字 被extern修饰的变量或者函数表示为声明外部属性 注意: extern int g_val 11;是错误的,extern只能声明,不能定义,初始化,赋值等等 编程好习惯: 声明变量或函数的时候,都带上extern比如: extern int g_val 100;extern void show();
4.认识static关键字
被static修饰的全局变量或函数只能在本文件内被访问不能被外部其他文件直接访问
被static修饰的局部变量会更改局部变量的生命周期将其放在静态区 5.认识sizeof关键字 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h int main()
{int arr[3] { 0,1,2 };printf(%d\n, sizeof(arr));//所有元素的地址大小-12printf(%d\n, sizeof(arr));//首元素的地址大小-4return 0;
}
说明一下 sizeof(数组名)是计算的所有元素的地址大小 sizeof(数组名)是计算首元素的地址大小 sizeof 变量或常量,有无括号都行,但是不能计算变量类型
6.认识bool(_Bool)关键字 C99引入了_Bool类型你没有看错_Bool就是一个类型不过在新增头文件stdbool.h中被重新用宏写成了bool为了保证C/C兼容性 在C99中bool用宏重新封装了_Bool
7.认识double关键字 0.1在double中存储时是会发生精度损失的所有double中的0.1和实际中的0.1是有区别的如果真的要做比较
解决方案 double中的0.1和实际的0.1的绝对值小于DBL_EPSILON,就认为它几乎等于实际的0.1 #define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includefloat.h
#includemath.h
int main()
{double x 1.0;double y 0.1;printf(%.50lf\n, x);printf(%.50lf\n, y);if (fabs((x - 0.9) - y) DBL_EPSILON) {printf(you can see me!\n);}else {printf(oops!\n);}return 0;
}
8.认识switch case关键字
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.hint main()
{int num 1;int b 1;switch (num){case 1:// int a 1;// errorbreak;case 2:b 1;break;case 3:{int c 1;}break;default:break;}return 0;
}
说明一下 在case中不能定义变量,如果要在case中定义变量必须加上代码块{} case匹配时,尽量把常见的情况放在前面
9.认识continue关键字 在for循环中continue是跳到改变循环变量的位置且建议在使用for循环语句的时候采用外小内大的规则
10.认识void关键字 说明一下 void本身就被编译器解释为空类型强制的不允许定义变量在linux中void的大小是1而在vs2019中void的大小是0 11.认识return关键字
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includewindows.h
int SumAdd() {int sum 0;for (int i 1; i 10; i) {sum i;}return sum;
}
int main()
{int sum SumAdd();printf(%d, sum);return 0;
} return返回的时候会通过寄存器的方式返回给函数调用方及时没有接收也一样 12.认识const关键字 在C语言中const机制是通过编译器检查实现的它标记const变量不能被直接修改但并未限制const变量的地址的引用则虽然加了const但还是被改变了 而在C中只要加上了const即使用指针间接引用都不会被改变
13.认识struct关键字 说明一下
空结构体的大小在不同的编译器下是不同的 在 C 语言中空结构体通常占 1 字节但具体取决于编译器
14.认识typedef关键字 说明一下
存储关键字有auto,extern,register,static,typedef存储关键字不可以同时出现也就是说在一个变量定义的时候只能有一个 左值表示空间,右值表示内容任何一个变量名在不同的应用场景中代表不同的含义
15.变量内容的存入和读取
先看现象 对于signed int b -10;来说 对于unsigned int d -10;来说 而上面那段代码关键在于到底是%d打印还是%u打印(数据究竟是以有符号还是无符号的形式取出的) 总结 变量存储时字面数据必须先转成补码再放入空间当中与变量本身类型无关
变量读取时 一定要先看变量本身的类型然后才决定要不要看最高符号位。如果不需要直接二进制转成十进制。如果需要则需要转成原码然后才能识别。(当然最高符号位在哪 里又要明确大小端)
16. char类型中特殊的-128
char类型只有8个bit位 -128的原码1000 0000 0000 0000 0000 0000 1000 0000
-128的反码1111 1111 1111 1111 1111 1111 0111 1111
-128的补码1111 1111 1111 1111 1111 1111 1000 0000
char类型只有8个bit位所以-128存入char中的时候会发生截断
-128在char中为1000 0000他们规定这个1000 0000就当作-128 数据类型的取值范围 : -2^(n-1)到2^(n-1)-1比如char就是-2^(7)到2^(7)-1 第九章 符号
1. 自增/自减运算符 不管是前置,还是后置,都是通过寄存器来改变值的注意: 在没有接收方的时候,前置和后置是一样的
2. 取整运算
四舍五入取整(round) 向负无穷取整(floor) 向正无穷取整(ceil) 向0取整(trunc)
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include math.h
int main()
{ const char * format %.1f \t%.1f \t%.1f \t%.1f \t%.1f\n; printf(value\tround\tfloor\tceil\ttrunc\n); printf(-----\t-----\t-----\t----\t-----\n); printf(format, 2.3, round(2.3), floor(2.3), ceil(2.3), trunc(2.3));printf(format, 3.8, round(3.8), floor(3.8), ceil(3.8), trunc(3.8));printf(format, 5.5, round(5.5), floor(5.5), ceil(5.5), trunc(5.5));printf(format, -2.3, round(-2.3), floor(-2.3), ceil(-2.3), trunc(-2.3));printf(format, -3.8, round(-3.8), floor(-3.8), ceil(-3.8), trunc(-3.8));printf(format, -5.5, round(-5.5), floor(-5.5), ceil(-5.5), trunc(-5.5)); return 0;
} 补充一下在vs2019中的取整规则是向0取整 3.负数取模(得余数)问题 定义如果a和d是两个自然数d非零可以证明存在两个唯一的整数 q 和 r 满足 a q*d r , q 为整数且0 ≤ |r| |d|其中q 被称为商r 被称为余数 在C语言中-10(-3)*3(-1)因为C语言中是向0取整所以商是-3余数是-1也叫负余数在Python中: -10(-4)*32因为Python中是向负无穷取整所以商是-4余数是2也叫正余数 所以在不同语言中同一个计算表达式负数“取模”结果是不同的 第十章 动态内存管理
1. 验证C程序动态地址空间分布 代码区-字符常量区-已初始全局变量-未初始化全局变量-堆区-共享区-栈区 2. CPU怎么和内存实现交互
总线的宽度决定CPU的寻址能力
控制总线决定了CPU对其他空间的控制能力和控制方式
数据总线的宽度决定了CPU单次传输数据的传送量也就是数据传输速度 当CPU想在内存中读取一个数字6时
首先CPU通过地址总线在内存中找到数字6的地址然后通过控制总线知道该操作是读还是写最后通过数据总线把数字6传输到CPU中
3. 电脑的32位和64位有什么区别
从CPU架构来说
32位CPU一次最多能处理32位的数据寄存器宽度为32位寻址能力有限而64位CPU一次最多能处理64位的数据寄存器宽度为64位处理速度更快能处理更大的数据量 从内存寻址能力
32位系统最多能寻址 4GB2³²字节 的内存而64位系统最多能寻址 16EB2⁶⁴字节 的内存
从软件兼容性
32位操作系统只能运行 32位程序不支持 64位程序而64位操作系统 既可以运行 64位程序也可以兼容 32位程序
2. 内存越界问题 结论
越界不一定报错对于数值越界访问的检查,是一种抽查机制程序退出内存泄漏问题就不在了被自动回收了内存泄漏问题对于那些永远不会主动退出的程序比如操作系统杀毒软件服务器等影响大
3. C中动态内存“管理”体现
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
int main()
{char* p (char*)malloc(sizeof(char) * 10);printf(before:%p\n,p);free(p);printf(after:%p\n, p);return 0;
} 说明一下
其实释放的字节会比实际上10个字节多得多申请的一定不止10字节malloc申请空间的时候系统给你的其实更多而多出来的那部分记录了这次申请的更详细信息 free的释放相当于取消关系使之后的p无法再使用
4. 认识realloc void* realloc (void* ptr, size_t size); #define _crt_secure_no_warnings 1
#include stdio.h
#include stdlib.h
int main()
{int* p (int*)calloc(10, sizeof(int));if (p NULL){perror(main);return 1;}//使用int i 0;for (i 0; i 10; i){*(p i) 5;}//这里需要p指向的空间更大需要20个int的空间//realloc调整空间int* ptr (int*)realloc(p, 20 * sizeof(int));if (ptr ! NULL){p ptr;}
} 这个函数调整原内存空间大小的基础上还会将原来内存中的数据移动到 新 的空间 调整内存空间的三种情况 情况一: 原有空间之后有足够大的空间 realloc会在原来的空间中添加40字节这种情况下, ptr和p的地址一样 情况二: 原有空间不足 realloc会在会另找一块空间,开辟80字节这种情况下, ptr和p的地址不一样 情况三: 不存在合适的空间 realloc有可能找不到合适的空间来调整大小将会返回空指针 5. 常见动态内存错误
5.1 对空指针的解引用
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
void test()
{int* p (int*)malloc(INT_MAX / 4);*p 20;//如果p的值是NULL就会有问题free(p);
}
int main()
{test();return 0;
}
错误原因: 没有判断是否为空指针 5.2 对动态开辟空间的越界访问
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
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;//当i是10的时候越界访问}free(p);
}
int main()
{test();return 0;
}
错误原因: 动态内存开辟也存在越界访问的情况 5.3 对非动态开辟内存使用free释放
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
void test()
{int a 10;int* p a;free(p);//ok?
}
int main()
{test();return 0;
}
错误原因free只能释放掉动态内存开辟的空间
5.4 使用free部分释放内存空间
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
void test()
{int* p (int*)malloc(100);p;free(p);//p不再指向动态内存的起始位置
}int main()
{test();return 0;
} 错误原因: p不再指向动态内存的起始位置且p也没有置成空指针 这就导致有一部分空间没被释放且找不到这块空间了
5.5 对同一块动态内存多次释放
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
void test()
{int* p (int*)malloc(100);free(p);free(p);//重复释放
}int main()
{test();return 0;
} 错误原因: 不能对同一块空间多次free
5.6 动态开辟内存忘记释放(内存泄漏)
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
void test()
{int* p (int*)malloc(100);if (NULL ! p){*p 20;}
}
int main()
{test();while (1);
} 错误原因: 忘记释放不再使用的动态开辟的空间会造成内存泄漏 5.7 一道经典面试题
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
#include string.h
void Test(void)
{char* str (char*)malloc(100);strcpy(str, hello);free(str);if (str ! NULL){strcpy(str, world);printf(str);}
}
int main()
{Test();return 0;
}
上面这段代码逻辑顺序错误,正确的应该是先判断是否是空指针,再free释放空间 6. 零长/柔性数组 C99 中规定结构体中的最后一个元素允许是未知大小的数组这就叫做『柔性数组』成员 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
struct S
{int n;//4int arr[0];//大小是未知
};int main()
{//期望arr的大小是10个整形struct S*ps (struct S*)malloc(sizeof(struct S)10*sizeof(int));if (ps NULL){perror(main);return 1;}ps-n 10;int i 0;for (i 0; i 10; i){ps-arr[i] i;}//增加struct S* ptr (struct S*)realloc(ps, sizeof(struct S)20*sizeof(int));if (ptr ! NULL){ps ptr;}//使用//释放free(ps);ps NULL;//struct S s {0};//printf(%d\n, sizeof(s));//?return 0;
} 优点拷贝次数少(指针解引用)方便内存释放提高访问速度的优点
缺点 结构中的柔性数组成员前面必须包含至少一个其他成员且它必须在最后面
说明一下
sizeof 返回的这种结构大小不包括柔性数组的内存包含柔性数组成员的结构用malloc()函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小但实际感觉用处不
第十一章 文件
1. 文件的基本使用 FILE * fopen ( const char * filename, const char * mode );// 打开文件 int fclose ( FILE * stream ); // 关闭文件 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{FILE* pf fopen(code11.c, w);if (pf NULL){perror(fopen);return 1;}//写文件//关闭文件fclose(pf);pf NULL;return 0;
}
三种打开文件的方式
r(只读) 为了输入数据打开一个已经存在的文本文件 w(只写) 为了输出数据打开一个文本文件(没有这个文件,会创建新文件)a(追加) 向文本文件尾添加数据 2. 文件的顺序读写
函数名适用于函数名适用于fgetc: 读取文件(字符)所有输入流fputc:写入文件(字符)所有输出流fgets: 读取文件(字符串)所有输入流fputs:写入文件(字符串)所有输出流fprintf:读取文件(格式化)所有输入流fscanf:写入文件(格式化)所有输出流fread:读取文件(二进制)所有输入流fwrite:写入文件(二进制)所有输出流
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{FILE* pf fopen(code11.c, w);if (pf NULL){perror(fopen);return 1;}//写文件{fputc(a, pf);fputs(2023_5_2, pf);fscanf(pf, %s, 星期二);char s b;fwrite(s, sizeof s, 1, pf);}// 读文件{int ret fgetc(pf);printf(%c\n, ret);char arr[10];fgets(arr, 1, pf);printf(%s\n, arr);char arrr[10];fprintf(pf, %s\n, arrr);char tmp;fread(tmp, sizeof tmp, 1, pf);}//关闭文件fclose(pf);pf NULL;return 0;
} 3. sscanf sprintf
scanf针对标准输入的格式化输入语句 - stdinfscanf针对所有输入流的格式化输入语句 - stdin/文件sscanf从一个字符串中读取一个格式化的数据 printf针对标准输入的格式化输出语句 - stdoutfprintf针对所有输入流的格式化输出语句 - stdout/文件sprintf把一个格式化的数据,转换成字符串
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
struct S
{char arr[10];int age;float f;
};int main()
{struct S s { hello, 20, 5.5f };struct S tmp { 0 };char buf[100] {0};//sprintf 把一个格式化的数据转换成字符串sprintf(buf, %s %d %f, s.arr, s.age, s.f);printf(%s\n, buf);//从buf字符串中还原出一个结构体数据sscanf(buf, %s %d %f, tmp.arr, (tmp.age), (tmp.f));printf(%s %d %f\n, tmp.arr, tmp.age, tmp.f);return 0;
}
虽然两次的值一样但是打印的形式却完全不一样这两个就像是格式化数据和字符的相互转换
4. 文件的随机读写
4.1 fseek int fseek ( FILE * stream, long int offset, int origin ); #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{FILE* pf fopen(test.txt, r);if (pf NULL){perror(fopen);return 1;}//读取文件int ch fgetc(pf);printf(%c\n, ch);//调整文件指针fseek(pf, -1, SEEK_CUR);ch fgetc(pf);printf(%c\n, ch);ch fgetc(pf);printf(%c\n, ch);//关闭文件fclose(pf);pf NULL;return 0;
} fseek : 根据文件指针的位置和偏移量来定位文件指针 4.2 ftell long int ftell ( FILE * stream ); 这个函数的主要功能是返回文件指针相对于起始位置的偏移量
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{FILE* pf fopen(test.txt, r);if (pf NULL){perror(fopen);return 1;}//读取文件int ch fgetc(pf);printf(%c\n, ch);ch fgetc(pf);printf(%c\n, ch);//指针相对于起始位置的偏移量int ret ftell(pf);printf(%d\n, ret);ch fgetc(pf);printf(%c\n, ch);//关闭文件fclose(pf);pf NULL;return 0;
} 4.3 rewind void rewind ( FILE * stream ); 这个函数的主要功能是: 让文件指针的位置回到文件的起始位置
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{FILE* pf fopen(test.txt, r);if (pf NULL){perror(fopen);return 1;}//读取文件int ch fgetc(pf);printf(%c\n, ch);ch fgetc(pf);printf(%c\n, ch);//让文件指针回到起始位置rewind(pf);//指针相对于起始位置的偏移量int ret ftell(pf);printf(%d\n, ret);ch fgetc(pf);printf(%c\n, ch);//关闭文件fclose(pf);pf NULL;return 0;
} 5.feof判断文件是否正确结束
在文件读取过程中不能用feof函数的返回值直接用来判断文件的是否结束 而是应用于当文件读取结束的时候判定是读取失败结束还是遇到文件尾结束 将test.txt的内容拷贝给test2.txt中 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{FILE* pfread fopen(test.txt, r);if (pfread NULL){return 1;}FILE* pfwrite fopen(test2.txt, w);if (pfwrite NULL){fclose(pfread);pfread NULL;return 1;}//文件打开成功//读写文件int ch 0;while ((ch fgetc(pfread)) ! EOF){//写文件fputc(ch, pfwrite);}if (feof(pfread)){printf(遇到文件结束标志文件正常结束\n);}else if(ferror(pfread)){printf(文件读取失败结束\n);}//关闭文件fclose(pfread);pfread NULL;fclose(pfwrite);pfwrite NULL;return 0;
} 6.文件缓冲区 ANSIC 标准采用“缓冲文件系统”处理的数据文件的所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区装满缓冲区后才一起送到磁盘上 如果从磁盘向计算机读入数据则从磁盘文件中读取数据输入到内存缓冲区充满缓冲区然后再从缓冲区逐个地将数据送到程序数据区程序变量等 缓冲区的大小根据C编译系统决定的 缓冲区刷新问题 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include windows.h
//VS2013 WIN10环境测试
int main()
{FILE* pf fopen(test.txt, w);fputs(abcdef, pf);//先将代码放在输出缓冲区printf(睡眠10秒-已经写数据了打开test.txt文件发现文件没有内容\n);Sleep(10000);printf(刷新缓冲区\n);fflush(pf);//刷新缓冲区时才将输出缓冲区的数据写到文件磁盘printf(再睡眠10秒-此时再次打开test.txt文件文件有内容了\n);Sleep(10000);fclose(pf);//注fclose在关闭文件的时候也会刷新缓冲区pf NULL;return 0;
} 主要刷新缓冲区的方式
fflush 表示刷新缓冲区fclose 也会自动刷新缓冲区
补充一下有些实际情况需要考虑缓冲区是否刷新的问题 第十二章 函数栈帧(难点)
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int MyAdd(int a, int b)
{ int c a b;return c;
}
int main()
{ int x 0xA;int y 0xB; int z MyAdd(x, y);printf(z %x\n, z);return 0;
}
1. 认识相关寄存器
寄存器名称作用eax通用寄存器保留临时数据常用于返回值ebx通用寄存器保留临时数据ebp栈底寄存器esp栈顶寄存器eip指令寄存器保存当前指令的下一条指令的地址
2. 认识相关汇编命令
汇编命令作用mov数据转移指令push数据入栈同时esp栈顶寄存器也要发生改变pop数据弹出至指定位置同时esp栈顶寄存器也要发生改变sub减法命令add加法命令call函数调用1. 压入返回地址 2. 转入目标函数jump通过修改eip转入目标函数进行调用ret恢复返回地址压入eip类似pop eip命令
3.栈帧创建销毁简单流程
3.1创建调用main函数
main函数也是需要创建的它是在_tmainCRTStartup函数中调用的 创建main( )函数的栈帧 完成状态寄存器的保存 堆栈寄存器的保存 函数内存空间的初始化 3.2 创建变量 形参实例化 说明一下
寄存器ebp指向当前的栈帧的底部高地址寄存器esp指向当前的栈帧的顶部低地址第一张图片的那2条mov汇编是在main函数的栈帧中开辟变量x和变量y并赋值第二张图片的那4条汇编做了一件事-形参实例化(且形参实例化是向从最右边开始实例化的)
3.3 调用Myadd函数-压栈 call这条汇编主要做 1. 压入返回地址 2. 转入目标函数
压入返回地址b0 53 8f 00是为了以后能找到
3.4 创建Myadd函数的栈帧-入栈 执行push汇编命令,esp的指向会变执行mov汇编命令执行sub汇编命令(开辟空间的大小和里面的代码有关 )
3.5 释放Myadd函数的栈帧-弹栈 说明一下
mov汇编命令: 就可以说Myadd的函数被释放了pop汇编命令: 会把ebp指向main函数的栈底esp也会变ret汇编命令: 会把b0 53 8f 00写回eip中
3.6 Myadd函数结果返回 add汇编命令: 会把esp8add汇编命令: 会得到eax中的值 Myadd函数的返回值是通过寄存器来返回的
3.7 流程总结
调用函数需要先形成临时拷贝形成过程是从右向左的临时空间的开辟是在对应函数栈帧内部开辟的 函数调用完毕栈帧结构被释放掉临时变量具有临时性的本质栈帧具有临时性 调用函数是有成本的成本体现在时间和空间上本质是形成和释放栈帧有成本 函数调用因拷贝所形成的临时变量变量和变量之间的位置关系是有规律的
第十三章 可变参数列表(难点)
1. 演示案例
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdarg.hint FindMax(int num,...)
{va_list arg;//定义可以访问可变参数部分的变量其实是一个char*类型va_start(arg, num);//使arg指向可变参数部分int max va_arg(arg, int);//根据类型获取可变参数列表中的第一个数据int i 0;for (i 0; i num - 1; i) {int curr va_arg(arg, int);if (max curr) {max curr;}}va_end(arg);return max;
}int main()
{int max FindMax(5, 11, 22, 33, 44, 55);printf(max%d\n, max);return 0;
} 说明一下
参数列表中至少有一个命名参数如果连一个命名参数都没有就无法使用 va_start如果在 va_arg 中指定了错误的类型那么其后果是不可预测的 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止这是可以的但是如果你想一开始就访问参数列表中间的参数那是不行的实际传入的参数如果是charshortfloat编译器在编译的时候会自动进行提升而在函数内部使用的时候根据类型提取数据更多的是通过int或者double来进行
2. 可变参数的原理
2.1 va_list va_end va_list : 定义可以访问可变参数部分的变量其实是一个char*类型va_end : 相当于把arg指针置成空
2.2 va_start va_start : 使arg指向可变参数部分
2.3 va_arg va_arg: 根据类型获取可变参数列表中的第一个数据这里的arg指针减去4字节,再加上4字节,可以说设计的非常巧妙
这个巧妙之处在于ap先是通过步长实现了指针指向的改变*(后-步长)拿到最终值
但这时的ap还是在步长之后的位置-步长并不会改名位置 #define _INTSIZEOF(n) ((sizeof(n) sizeof(int) - 1) ~(sizeof(int) - 1)) 这是一个求最小对齐数的宏这是4的倍数 理解一4的倍数 既然是4的最小整数倍取整那么本质是x4*mm是具体几倍对7来讲m就是2对齐的结果就是8而m具体是多少取决于n是多少如果n能整除4那么m就是n/4如果n不能整除4那么m就是n/41由此产生了一种写法4的倍数等于(n3)/4也就是( nsizeof(int)-1) )/sizeof(int) 理解二最小4字节对齐数 搞清楚了满足条件最小是几倍问题那么计算一个最小数字x满足 xn x%40就变成了 4字节对齐数等于((n4-1)/4)*4 也就是((nsizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] 理解三理解源代码中的宏 ((n4-1)/4)* 4设wn4-1表达式就变成了(w/4)*4其中一个数除4等价于二级制位右移2位一个数乘4等价于二级制位左移2位简洁版(n4-1) ~(4-1) 原码版( (sizeof(n) sizeof(int) - 1) ~(sizeof(int) - 1) ) 3. 命令行参数 说明一下
main函数也是可以传参的 第一个参数 argc 是个整型变量表示命令行参数的个数含第一个参数第二个参数 argv 是个字符指针的数组每个元素是一个字符指针指向一个字符串。这些字符串就是命令行中的每一个参数字符串argv数组的最后一个元素存放了一个 NULL 的指针
第十四章 数据的存储
1. 为什么数据在内存中存放的是补码 因为CPU只有加法器而使用补码就可以将符号位和数值域统一处理(即统一处理加法和减法)且不会需要额外的硬件电路 2. 为什么会有大小端 这是因为在计算机系统中,是以字节为单位的,比如: 每个地址单元都对应着一个字节而位数大于8位的处理器,比如:16位,32位处理器,由于寄存器宽度大于一个字节,那么必然会存在如何将多个字节安排的问题,这就导致出现的大,小端存储 3. 验证机器大小端 方式一通过地址来判断 #define _CRT_SECURE_NO_WARNINGS
#includestdio.h
int main()
{//写代码判断当前机器的字节序int a 1;char* p (char*)a;if (*p 1){printf(小端\n);}else{printf(大端\n);}return 0;
}
注在vs2019中编辑器是采用的小端存储 方式二通过联合体来判断 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int check_sys()
{union U{char c;int i;}u;u.i 1;return u.c;//返回1 就是小端//返回0 就是大端
}int main()
{int ret check_sys();if (ret 1)printf(小端\n);elseprintf(大端\n);return 0;
} 说明一下
联合体的地址是共用的char c和int i的地址是一样的
4. 浮点数在计算机中从存储
4.1 案例展示
#define _CRT_SECURE_NO_WARNINGS
#include stdio.h
int main()
{int n 9;float* pFloat (float*)n;printf(n的值为%d\n, n);// 正常printf(*pFloat的值为%f\n, *pFloat);*pFloat 9.0;printf(num的值为%d\n, n);printf(*pFloat的值为%f\n, *pFloat);// 不正常return 0;
} 4.2 存储形式
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数都可以用下面的形式保存 说明一下
(1)^S表示符号位当S0V为正数当S1V为负数M表示有效数字大于等于1小于22^E表示指数位 单精度浮点数模型 最高的1位是符号位s接着的8位是指数E剩下的23位为有效数字M 双精度浮点数模型 最高的1位是符号位s接着的11位是指数E剩下的52位为有效数字M
4.3 对有效数字M的特殊说明 有效数字M的取值范围是[1,2),即M可以写成1.XXXX的形式,其中XXXX表示为小数部分IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的XXXX部分 以32位浮点数为例,比如保存1.01的时候将1舍去, 只保存01M就会有24位有效位再等需要读取的时候再把第一位的1加上去
4.4. 对指数上标E的特殊说明 E为一个无符号整数unsigned int则当E为8位它的取值范围为0~255E为11位它的取值范围为0~2047 又由于科学计数法中的E是可以出现负数的所以IEEE 754规定存入内存时E的真实值必须再加上一个中间数 对于8位的E这个中间数是127对于11位的E这个中间数是1023 比如2^10的E是10所以保存成32位浮点数时必须保存成10127137即10001001
4.5 指数E从内存中取出三种情况 E不全为0或不全为(正常情况) 指数E的计算值减去127或1023得到真实值再将有效数字M前加上第一位的1 E全为0 存的时候E加上了127但还是为0说明这个2 ^ E中的这个E特别小 规定这时取的时候有效数字M不再加上第一位的1而是还原为0.xxxxxx的小数。这样做是为了表示±0以及接近于0的很小的数字 E全为1 存的时候E加上127居然全部都变成了1说明这个2 ^ E中的这个E特别大(正负取决于符号位s) 4.6 案例分析
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
int main()
{int n 9;float* pFloat (float*)n;printf(n的值为%d\n, n);printf(*pFloat的值为%f\n, *pFloat);*pFloat 9.0;printf(num的值为%d\n, n);printf(*pFloat的值为%f\n, *pFloat);return 0;
}
说明一下
对于第一个printf毫无疑问结果是9不解释对于第二个printffloat* pFloat (float*)n;它将n的地址强制转化成float*并赋给了pFloat 此时pFloat就认为这段二进制: 是float类型存入内存的二进制pFloat指向9并解引用最后又是以%f打印的所以结果为0.000000
对于第三个printf*pFloat 9.0;把9的值赋给了n,且pFloat是一个float* 的指针变量最后又是以%d的形式打印所以结果为1091567616 对于第四个printf和第三个printf同理不同之处是 第三个printf以浮点数存入以%d的形式打印 第四个printf中也是以浮点数存入但是却是以%f 所以结果应该为9.000000