你是否曾与C语言初次相遇许多概念一知半解。如今带你重走来时路从头梳理语法、指针、内存、结构体等陪你扫清底层盲区为嵌入式开发筑牢根基。以此系列博客记录复盘全过程。一.计算机体系结构在了解C语言前我们先看看早期计算机的结构不可否认冯诺依曼体系结构为现代计算机的奠基石它将程序指令像数据一样存储在计算机内存中让计算机可以自动、连续地执行任务。这使得计算机从专用于特定计算的机器变成了通用的、可编程的工具。明确了计算机应由运算器、控制器、存储器、输入设备和输出设备五大部件组成。这种清晰的分工奠定了现代计算机硬件的基础框架。但随着时代的发展此类计算机结构已出现明显短板无法继续满足使用要求最根本的缺陷指令和数据共享同一条总线信息通路与内存交互导致CPU同一时刻要么取指令、要么取数据无法同时进行。随着CPU速度远快于内存这种“抢路”现象拖慢了整个系统的速度。现代计算机应运而生它以存储器为中心一方面负责与CPU进行数据交互完成作业一方面向I/O设备输入输出数据显然现代计算机CPU集成了运算器与控制器相比传统更加高效。二.进制转换我们先了解一下进制所谓不同进制其实本质即不同基数与位权的组合1.任意进制转换为十进制显然在其他进制转换为十进制的时候最为简单只需由高到低按照各位的权值进行加法运算即可2.二进制与八十六进制转换由于2与816进制的特殊性在转换时有着异常的便捷性对八进制而言由0-7表示最大数为7这与3位二进制所能表示的最大值相同故我们对二进制可拆分为三位一组。注意在整数部分一定要由低到高表示在高位不足三位时需补0在小数部分则相反由图上蓝色补足数易见对于16进制则同理由0-15表示从十开始用A-F代替与四位二进制数所能表示的最大值相同故采用四位一组与八进制转换完全同理思路参考8421编码也即BCD码。3.十进制转换位任意进制上图所示为短除法的思路也是多数老师会提及的但本人并不推荐此方法因为如果当数据较大时采用短除法显然较为麻烦故推荐使用“拼凑法”如下所示这种思路在进行十进制向二进制的转换时较为简单不过前提是熟知二进制各权重的数值对数字有一定的敏感性在初期可能比较茫然但在经过一定的训练后可以较为快速的拼凑出结果。在得到二进制后便可采用上述方法得到八与十六进制。三.基本数据类型及其转换与运算1.有符号数与无符号数显然当表示无符号数时整个机器字长全部二进制位表示数值位没有符号位所能表示最大数值易知即全1最小值为0当表示有符号数时最高位用于表示符号位1负0正表示范围参考下文。2.原码与补码将原码转换为补码时数值位取反加1正数原反补不变也可从右往左数至第一个“1”1左侧取反符号位不变右侧不变。3.有符号数表示范围显然有符号数原码表示范围关于0对称计算机底层以补码存储二进制数而补码相对原码存在特殊性以八位二进制为例0000 0000在补码中表示正负零当符号位为0时从0000 0001到0111 1111表示1-127即所能表示最大值为符号位为0其余全1当符号位为1时 1000 0000则相比原码可额外表示一个数则规定为最小负数-128对shortintlong等同理即补码的表示范围中可多表示一位负数且为最小负数。反码只是原码转补码中的一个中间状态并无实际作用移码通常仅用于整数表示范围平时学习接触不多本文不作过多阐述。4.intlongshortchar在64位OS中int占4字节long占8字节short占2字节char占1字节表示范围参考上文char cha; printf(int的大小%d\n,sizeof(int)); //int的大小4 printf(long的大小%ld\n,sizeof(long)); //long的大小8 printf(char的大小%ld\n,sizeof(char)); //char的大小1 printf(short的大小%ld\n,sizeof(short)); //short的大小2 printf(ch%c,%d\n,ch,ch); //cha,97manascii打开操作人员手册查看ASCII表显然char在计算机中存的都是对应表的二进制。unsigned short无符号短整型由于仅有两字节空间故表示范围较小容易出现溢出如下unsigned short n65535; unsigned short resultn1; printf(n%d,n1%d,n,result); //n65535,n10 unsigned short n10; unsigned short result1n1-1; printf(n1%d,n1-1%d,n1,result1); //n10,n1-165535 注意若直接输出n1或n1-1则会得到65536和-1这是因为系统默认为int在对short进行运算时会进行 强制转型为int此时由于int表示范围足够大故不会存在溢出5.float与doublefloat与double都由三部分构成数符阶码与尾数部分在表示精度和范围方面存在如下差距此外a为float类型占四字节b为double类型占八字节对常量小数0.9默认为double类型故也占八字节注意到a与b输出均为0.9这是因为系统默认保留六位小数而float与double所能表示的精度都大于六位float6-8位double12-16位故输出正确。实则由于0.9在计算机中无法精确表示存储时必定会出现误差而底层默认四舍五入故得到0.9后面的不准确的尾数被底层截掉只显示六位。float a0.9; double b0.9; printf(a%.20f,a); printf(b%.20f\n,b); //a0.89999997615814208984 b0.90000000000000002220当采用输出20位精度时则可见误差。float a0.9; double b0.9; printf(%f\n,a); //0.900000 printf(%f\n,b); //0.900000 printf(a%ld,b%ld,0.9%ld\n,sizeof(a),sizeof(b),sizeof(0.9)); //a4b80.98同时float赋值的0.9与double赋值的0.9并不相同原因也即小数默认为double在向float存储时出现精度丢失故二者不相同。if(ab) printf(yes\n); else printf(no\n); //no if(a0.9) printf(yes\n); else printf(no\n); //no if(b0.9) printf(yes\n); else printf(no\n); //yes最后float与double在绝大多数现代通用计算机上之所以是4字节和8字节是因为它们都遵循IEEE 754标准这个标准明确规定了单精度float是32位4字节双精度double是64位8字节。6.常量与变量1.常量顾名思义即值不可改变的量一旦定义了整个程序运行期间都无法改变。常量分两种1字面常量在代码里直接敲的数字或字符它们本身就是常量可以直接使用。整型常量123 //默认十进制signed int 123u //unsigned int 123l //long 123ul //unsigned long 0123 //八进制 0x123 //十六进制字符常量a,b,?,!等 数字字符0 对应ASCII表的48 特殊字符\0 对应ASCII表的0即NULL \a 响铃 \b 退格 \t 横向制表符 \\ 表示\字符 %% 表示%字符 \ 表示引号 \v 纵向制表符同\f \r 回车光标回到本行开头 此外在C语言的转义序列\中只能使用八进制\ooo和十六进制\xhh来表示ASCII码且默 认使用八进制如\100表示八进制100对应十进制64表示无法直接用十进制。如果用十进制可以 直接写整数如 char c 64;但这是整数常量不属于转义字符。浮点型常量3.14 默认为double类型增加后缀f转型为float3.14ffF都可则表示float类型 科学计数法3.14e2表示3143.14e-2表示0.0314亦然e,E都可字符串常量Hello world 共12字符11可见末尾\0字符串隐含作为结束标志第二种标识符用来表示常量名变量名函数名及数组名等常量有利于增强代码可读性。#define 标识符 值 宏定义方式标识符尽量大写用于区分普通变量 注意宏定义只做代码替换不做数据运算如 #define A 23 BA*A; 此时即为B23*2311而非25. 故在定义宏时尽量加括号括号优先级在运算符里最高此时#define A 23即为52. 变量即程序运行过程中其值可以发生变化的量。变量名的命名要遵循标识符的命名规则所有标识符都必须以字母或下划线开头不能以符号或数字开头除开头外其他位置可由字母、下划线或数字组成避免与关键字函数名等重复。两种命名规则 驼峰法: linux内核命名 MaxVal max_val MinVal min_val TmpNum tmp_val7.类型转换1.强制类型转换1 有无符号数之间转换保持位值不变仅改变这些位的解释方式2不同整数类型之间转换3整数类型强转总结4 整数与浮点数之间类型转换需要注意的是int与float之间的转换在int转换为float时存在精度丢失问题由于int占32b而float尾数仅23位即使算上IEEE754标准的一位隐藏位也仅24位不足以完整表示int型数据而由float向int转换时由于float表示范围远大于int则可能出现溢出以及小数转为整数时的精度丢失。总结int精度更高float表示范围更大故二者之间转换存在隐患。2.隐式类型转换计算机在运算时会默认将低精度数据类型自动转换为高精度数据类型如float-double; char,short-int-unsigned int -long其中float在运算时一定会转换为doublechar与short在运算时一定会转换为int。8.整数加减运算计算机底层对有符号数与无符号数的加减运算都采用同一套电路由一个多路选择器MUX和一个加法器组成以及控制器控制Sub发出信号协调工作。对于加法运算控制器发出信号将Sub位置0加数Y从一端进入MUX不做任何操作与加数X输出到加法器中相加得到结果对于减法运算以X-Y为例Sub此时置为1控制减数Y从带反向器的一端将Y全部位取反后经过MUX输出到加法器同时Sub将1输出到加法器作为末位与X和Y取反之后的值相加得到的结果即为X-Y的值。为什么该电路对于减法也可以实现呢?其实底层在运算减法时会将减数取负此时减法即变为加法而Sub的功能即控制减数取反同时通过Cin端口输入到加法器在做减法时实现末位加1实现对减数取负功能。而对于电路来说有符号数与无符号数的工作原理完全相同可以理解为运算器本身无法识别处理的二进制数据有没有符号只是在计算完后人为的对最高位权值进行解释有符号数则解释最高位为符号位无符号数则解释为数值位。至此整数加减运算的底层原理一览无余。四.运算符1.算数运算符双目运算符两个操作数 - * / %%:操作数必须是整型数据或与整型兼容的char且结果与左操作数同号单目运算符一个操作数 --2.赋值运算符 - * / %左边只能是变量右边可以是变量常量表达式当运算符两边数据类型不一致时参考第七节类型转换赋值运算符优先级只高于逗号运算符3.三目运算符逻辑表达式表达式1表达式2若逻辑表达式的结果非零则三目运算符结果为表达式1的值否则为表达式2的值.4.逗号运算符表达式1表达式2表达式3......表达式n其值取最后一个表达式n的值优先级最低5.sizeof表达式获取数据类型变量常量表达式在计算机中所占的内存返回unsigned long类型6.各运算符优先级当优先级相同时看结合方向其中单目运算符三目运算符和赋值运算符为自右向左方向其余均为自左向右方向。
郑州网站建设
网页设计
企业官网