零值的判断
指针型
只能用NULL来判断,不能用0来判断
int main(){
int* s =nullptr;
if(NULL == s){...;}
if(NULL != s){...;}
}
特殊情况:
struct Node{
char ch;
int num;
char str[5];
double dx;
int sum;
};
int main(){
int a =10;
Node x{};
int* s =nullptr;
int Node:: * ip =nullptr;//0xffff ffff
ip = &Node::num;//0x0000 0004
ip = &Node::sum;//0x0000 0018
//此处指针ip并非为0值
return 0;
}
ip的地址存放的是结构体内成员首地址的偏移量,未定义时为0xffff ffff来作为空指针。
bool型
int main(){
bool a;
if(a){...}
//直接使用a即可
}
float型
const float EPSION;
void main(){
float ft = ...;
if(ft>-EPSION && ft <EPSION){
//0
}else...
}
原因如下:
float在内存中的表示方法
float占4字节,也就是32位二进制位,有以下三部分组成:
1.符号位(Sign ,S):0代表正,1代表负
2.指数位(Exponent,E):用于储存科学计数法中的指数数据,并且采用移位储存
要加上127.
3.尾数部分(Mantissa,M):尾数部分
(-1)^S * M *2^E
例如:float a = 12.25 怎么转化为二进制储存?
12转化为二进制为1100,0.25 = 1/4 = 1/2^2 ,为0.01
或者:
0.25*2 = 0.5 取整数部分0
0.5*2 = 1 取整数部分1
因此12.25转化为二进制为1100.01.
而尾数部分要求 1=<M<2 因此需要移动小数点
1100.01-> 1.10001*2^3
小数点左移3位,因此指数位要乘上2^3.
指数位:3+127=>(0000 0011+0111 1111) ->(1000 0010)
尾数部分: 将刚计算的1.10001的小数部分放入即可(1000 1000)
依次存入内存之中:
(0 1000 0010 1000 1000)
四位转化:
(0100 0001 0100 0100)=> 41 44
利用内存演示一下
44 41 的原因是小端模式
计算的时候可以发现小数部分遇到乘2化不了1的数时,将会永远乘下去,例如0.1乘2永远不可能消灭小数部分。因此M的精度会有损失。
M有精度的损失 ->范围比较,不能使用等值比较。
大端小端模式
小端模式:数据的低位放在低地址空间,数据的高位放在高地址空间。
存放的时候是以一个存储单元为单位来存放,八位为一个存储单元,也就是一个字节为一个存储单元
上面的例子中
(0100 0001 0100 0100)从左到右先是数据的高位到低位
(0100 0001)为一个存储单元(0100 0100)为一个存储单元
对应的41 44,因此41存在内存的高位,44存在内存的低位
因此呈现出了我们上面内存中44 41 的样子
大端模式相反
数据的低位存放在高地址空间,数据的高位存放在低地址空间。
柔性数组
结构体中未定义大小的数组称为柔性数组,不占用结构体大小,只是占位标记,在使用时,需要多少定义多少的大小
struct StrNode{
int ref;
int len;
int size;
char data[];//柔性数组,此时data的大小为0,只是占位、标记,不占用结构体的大小
};//此结构体的大小为12
int main(){
StrNode* s =(StrNode*)malloc(sizeof(StrNode)+sizeof(char)*20);
//这样来定义这个结构体,数组的大小为20
}
位断
struct SNode{
int a:4;
int b:3;
int c:4;
int d:4;
}
这种方式叫做位断。
在正常情况下,结构体的大小需要补足空间的操作,int占4字节,char占1字节等等,但位断使得:a占四字节,b占3字节。
这样的方法可以节省空间,需要多少用多少。
具体就体现在ip地址中。
涉及到计算机网络
点分十进制法
在计算机网络中 ip 地址是 32 位无符号整型(unsigned int ) ,但是在描述 IP 地址采用”点分十进制记法” 例如 ip =2148205375 ; ip 地址的点分十进制记法为:“128.11.3.31”.不使用位运算
typedef unsigned char u_char;
typedef unsigned int u_int;
union IpNode{
u_int addr;
u_char s[4];
struct{
u_char s4;
u_char s3;
u_char s2;
u_char s1;
};
};
void iptoStr(u_int xip,u_char* buff,int len){
IpNode x;
x.addr = xip;
sprintf_s((char*)buff,len,"%d.%d.%d.%d",x.s1,x.s2,x.s3,x.s4);
}
int main(){
const int iplen = 32;
u_int xip = 2148205375;
u_char ipstr[iplen] = {};
iptoStr(xip,ipstr,iplen);
cout<<"ip: "<<ipstr<<endl;
return 0;
}
联合体中,addr s struct 共用4个字节空间
关于sprintf_s和sccanf_s的用法
printf()函数,scanf()函数我们经常会用到,一个将需要打印的呈现在屏幕之上,一个将屏幕上的截取下来到变量之中。
实际printf()函数返回的是数组的长度
scanf()我们可以理解为,将我们输入到会话框中的一系列字符串,转化成%d %s 等等格式存入变量。
而sprintf()与sscanf()与其相反
sscanf(const char *str, const char *format, …);
str:待解析的字符串;
format:字符串格式描述;
int year, month, day;
int converted = sscanf("20230423", "%04d%02d%02d", &year, &month, &day);
printf("converted=%d, year=%d, month=%d, day=%d/n", converted, year, month, day);
sprint()的用法:
int sprintf( char *buffer, const char *format [, argument] … );
printf 和 sprintf 都使用格式化字符串来指定串的格式,在格式串内部使用一些以“%”开头的格式说明符(format specifications)来占据一个位置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要 的字符串。
将整数123打印成一个字符串保存在s中
sprintf(s,"%d",123);