网站建设金思扬网络,网站的栏目管理,网站建设项目,福州工程建设信息网站CSDN成就一亿技术人
目录
一.操作符
一.算数操作符#xff1a;
二.位移操作符#xff1a;
三.位操作符#xff1a;
四.赋值操作符#xff1a;
五.单目操作符#xff1a;
六.关系操作符#xff1a;
七.逻辑操作符#xff1a;
八.条件操作符…CSDN成就一亿技术人
目录
一.操作符
一.算数操作符
二.位移操作符
三.位操作符
四.赋值操作符
五.单目操作符
六.关系操作符
七.逻辑操作符
八.条件操作符
九.逗号表达式
十.下标引用函数调用和结构成员
十一.表达式求值
二.数据类型的存储 一.数据的类型的介绍
1.整形家族:有符号和无符号的定义。
2.浮点型
3.构造类型:自定义类型
4.指针类型
5.空类型
二.整形在内存中的存储源码反码补码
练习
三.大小端字节序的介绍及判断
四.浮点型在内存中的存储解析 操作符分类 算数操作符 移位操作符 位操作符 赋值操作符 单目操作符 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用函数调用和结构成员 一.操作符
一.算数操作符 (加法操作) -(减法操作符) *(乘法操作符) /(除法操作符) %(取余操作符) 除了%操作符之外其他的几个操作符可以作用于正数和浮点数。 对于/操作符如果两个操作符都为整数执行整数除法而只要有浮点数执行除法就是浮点数除法。 %操作符的两个操作数必须为整数。返回的是整除之后的余数。 这样的写法是错误的 二.位移操作符 左移操作符 左边抛弃右边补0。 右移操作符 逻辑位移左边用0填充右边丢弃。 算数移位左边有原该值的符号位填充右边丢弃。 对于位移运算符不要移动负数位这个是标准定义的 int num10; num-1; //error 三.位操作符 //按位与 | //按位或 ^ //按位异或 他们的操作数必须是整数。 下面有几种计算一个整数中二进制的个数的方法。 这个程序是利用了数据类型在存储中的特性 四.赋值操作符 赋值操作符他可以让你对不满意的值进行重新赋值比如 int weight120; //体重 weight89; //不满意就赋值 double salary10000.0; salary20000.0; //使用赋值操作符赋值 赋值操作符可以连续使用 int a10; int x0; int y20; axy1; //连续赋值 复合赋值符 - * / % | ^ 可以这样写 int x10; xx10; x10; //复合赋值 可以用复合赋值符写这几种代码 五.单目操作符 ! 逻辑反操作 - 负数 正数 取地址 sizeof 操作数的类型长度(以字节为单位) ~ 对一个数的二进制按位取反 -- 前置后置-- 前置后置 * 间接访问操作符(解引用操作符) (类型) 强制类型转换 sizeof和数组。 前置和后置--的区别前置 后置 六.关系操作符 ! 用于测试“不相等” 用于测试“相等” 七.逻辑操作符 逻辑与:两个为真才为真否则都为假。 | | 逻辑或:有真就为真都为假才为假。 八.条件操作符 exp1?exp2:exp3 exp1语句成立就为exp2不成立就为exp3 九.逗号表达式 逗号表达式就是用逗号隔开的多个表达式。 逗号表达式从左向右依次执行整个表达式的结果是最后一个表达式的结果。 exp1,exp2,exp3.......expN 十.下标引用函数调用和结构成员 [ ]下标引用操作符 操作数一个数组名一个索引值 int arr[10]; //创建数组 arr[9]10; //实用下标引用操作数。 [ ]的两个操作数是arr和9. 这里使用了i访问了arr的每一个数。 函数调用操作符 接受一个或多个操作数第一个操作符是函数名剩余的操作数就是传给函数的参数。 . 结构体.成员名 - 结构体指针-成员名 struct Stu
{char name[10];int age;char sex[5];double score;
};
void set_age1(struct Stu stu)
{stu.age 18;
}
void set_age2(struct Stu* pStu)
{pStu-age 18;//结构成员访问
}
int main()
{struct Stu stu;struct Stu* pStu stu;//结构成员访问stu.age 20;//结构成员访问set_age1(stu);pStu-age 20;//结构成员访问set_age2(pStu);return 0;
} 十一.表达式求值 表达式求值的顺序一部分是由操作符的优先级和结合性决定的。 同样有些表达式的操作数在求值的过程中可能需要转换为其他类型。 隐式转换 C的整形算术运算总是至少以缺省整形类型的精度来进行的。 为了获得这个精度表达式中的字符和短整型操作数在使用之前被转换为普通整形这种转换称为整形提升。 整形提升的意义 表达式的整形运算要在CPU的相应运算器内执行CPU内整形运算器ALU的操作数的字节长度一般是int的字节长度同时也是CPU的通用寄存器的长度。 因此即使两个char类型的相加在CPU执行时实际上也要转换为CPU内整形操作数的标准长度。 通用CPUgeneral-purpose CPU是难以直接实现两个8比特字节直接相加运算虽然机器指令中可能有这种字节相加指令。所以表达式中各种长度可能小于int长度的整形值都必须先转换为int或unsigned int然后才能送入CPU去执行运算。 实例 char a,b,c; .... abc; b和c的值被提升为普通整形然后再执行加法运算。 加法运算之后结果被截断然后再存储于a中。 整形提升是按照变量的数据类型的符号位来提升的 //负数的整形提升 char c1-1 变量c1的二进制补码中只有8个比特位 1111111 因为char是有符号的char 所以整形提升的时候高位补充符号位即为1 提升之后的结果是 1111111111111111111111111111111 //整数的证书提升 char c21; 变量c2的二进制位补码中只有8个比特位 00000001 因为char是有符号的char 所以整形提升的时候高位补充符号位即为0 提升之后的结果是 0000000000000000000000000001 //无符号整形提升高位补0 下面代码中ab要进行整形提升但是c不需要整形提升 ab整形提升之后变成了负数所以表达式a0xb6b0xb600的结果是假但是c不发生整形提升则表达式c0xb60000000的结果是真。 所以程序输出的是c int main()
{char a0xb6;short b0xb600;int c0xb60000000;if(a0xb60)printf(a);if(b0xb600)printf(b);if(c0xb6000000)printf(c);
} 实例 c只要参与了表达式运算就会发生整形提升表达式c就会发生提升所以sizeofc是4个字节。 表达式-c也会发生整形提升所以sizeof-c是4个字节但是sizeofc就是1个字节。 int main()
{char c1;printf(%u\n,sizeof(c));printf(%u\n,sizeof(c));printf(%u\n,sizeof(-c));
} 算数转换 如果某个操作符的各个操作数不属于不同类型难么除非其中一个操作数转换为另一个操作数的类型否则操作就无法进行下面的层次体系称为寻常算术转换。 long double double float unsigned long int long int unsigned int int 如果某个操作数的类型在上面这个列表中排名较低那么首先要转换为另一个操作数的类型执行运算。 但是算术转换要合理要不然会有一些潜在的问题。 float f3.14; int numf;//隐式转换会有精度丢失 操作数的属性 1.操作符的优先级 2.操作符的结合性 3.是否控制求值顺序。 相邻的操作符先执行的因素是取决于他们的优先级如果两者的优先级相同取决于他们的结合性 操作符优先级可以参考这里C 内置运算符、优先级和关联性 | Microsoft Learnhttps://learn.microsoft.com/zh-cn/cpp/cpp/cpp-built-in-operators-precedence-and-associativity?viewmsvc-170 一些问题表达式 //表达式的求值部分由操作符的优先级决定。 a*bc*de*f //代码1在计算的时候由于*比的优先级高只能保证*的计算是比早但是优先级并不能决定第三个*比第一个早执行 所以表达式的计算顺序是 a*b c*d a*bc*d e*f a*bc*de*f 或者 a*b c*d e*f a*bc*d a*bc*de*f 表达式 c--c; //同上操作符的优先级只能决定自减--的运算在的运算的前面但是我们并没有办法得知操作符的左操作数的获取在右操作数之前还是之后求值所以结果是不可预测的是有歧义的。 非法 表达式,这种表达式在不同的编译器有不同的结果 int main() { int i10; ii-- - --i*(i-3)*i i; printf(i%d\n,i); } 代码这个 代码虽然在大多数的编译器上求得结果都是相同的但是上述代码answerfun()-fun()*fun();中我们只能通过操作符的优先级得知先算乘法再算减法函数的调用先后顺序无法通过操作符的优先级确定。 int fun() { static int count 1; return count; } int main() { int answer; answerfun()-fun()*fun(); printf(%d\n,answer); } 总结我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径那这个表达式就是存在问题的。 二.数据类型的存储 一.数据的类型的介绍 char //字符数据类型 —— 一个字节大小 short //短整型 —— 两个字节大小 int //整形 —— 四个字节大小 long //长整型 —— 四个字节大小 long long //更长的整形 —— 八个字节大小 float //单精度浮点数 —— 四个字节大小 double //双精度浮点数 —— 八个字节大小 //C语言有没有字符串类型? 为什么有int整形了还要short和long?:这是因为int在32位机器上表示21亿多而且占4个字节但有时候用不到这么大的数比如年龄所以有short来表式而且只占2个字节有时候int也不够用所以又有了long类型来表示 1.整形家族:有符号和无符号的定义。 ASCLL可以参考ASCII 表 | 菜鸟教程 (runoob.com)https://www.runoob.com/w3cnote/ascii.html unsigned 是无符号 unsigned没有符号位只有数值位 signed 是有符号 存储时会把最高位当成符号位其他位是数值位 char :因为字符存储的时候存储的是ASCII码值是整形所以归类的时候放在整形家族里 unsigned char signed char char是unsigned char 还是signed因为C语言没有给出标准所以不确定在 VS编译器上是charsigned char 如果要定义无符号的则要unsigned char这样写 short unsigned short [int] signed short [int] shortsigned short -32878~32767 unsigned shor 0~65535 int unsigned int signed int 在32位机器下其他的范围各有不同 int signed int -21474836482147483647 unsigned int 0~4294967296 long unsigned long [int] signed long [int] long signed long unsigned long 2.浮点型 float double 3.构造类型:自定义类型 数组类型 结构体类型 struct 枚举类型 enum 联合类型 union 4.指针类型 int* pi; char* pc; float* pf; void* pv; 5.空类型 void表示空类型(无类型) 通常应用于函数的返回类型函数的参数指针类型 二.整形在内存中的存储源码反码补码 计算机中的整数有三种2进制表示方法即源码反码和补码。 三种表示方式均有符号位和数值位两部分符号位都是用0表示‘正’用1表示‘负’而数值位正数的原反补码都相同。二级制最高位是符号位其他位是数值位 负整数的三种表示方式各不相同。 源码直接将数值按照正负数的形式翻译成二进制就可以得到源码。 反码将源符号位不变其他位依次按位取反就可以得到反码。 补码反码1就得到补码 int num10;//创建一个整形变量叫num这时num向内存中申请了4个字节空间来存放数据10 //4个字节-32个二进制 //源码00000000000000000000000000001010 //反码00000000000000000000000000001010 //补码00000000000000000000000000001010 int num1-10;//创建一个整形变量叫num1这时num1向内存中申请了4个字节空间来存放数据-10 //4个字节-32个二进制 //源码: 10000000000000000000000000001010 //反码111111111111111111111111111111110101 //源码转反码符号位之外其他位按位取反反码转源码符号位之外其他位按位取反。反码1补码补码-1反码。 //补码111111111111111111111111111111110110 对于整数来说数据存放内存中其实放的是补码在计算机系统中数值一律用补码来表示和存储原因在于使用补码可以将符号位和数值域统一处理同时加减法也可一统一处理CPU只有加法器此外补码于源码相互转换其运算过程是相同的不需要额外的硬件电路。 可以使用num取出num的地址因为VS为了方便展示所以显示的是16进制0a 00 00 00这是因为倒着存的num1负书可以验证 -10的补码是11111111111111111111111111111111011016进制算出来是ox ff ff ff 6f而存入却是f6 ff ff ff这证实了内存中的数据是倒着存入的(后面大端小端会讲原因) int main(){int num110;int num2-10;
} 源码计算1-1可以表示1(-1) 100000000000000000000000000000001 -110000000000000000000000000000001 1(-1)10000000000000000000000000000010-2//结果显然是错误的 补码 计算1-1可以表示1(-1) 1 00000000000000000000000000000001 -11111111111111111111111111111111111111 1(-1)1111111111111111111111111111111111110计 算完之后是补码转为反码是 1111111111111111111111111111111111111 在转为源码是 000000000000000000000000000000000所以在内存中存的是补码为了方便运算 练习 #includestdio.h
int main()
{
char a-1;
signed char b-1;
unsigned char c-1;
printf(a%d,b%d,c%d\n,a,b,c);
} -1的补码是11111111111111111111111111111111因为a是char类型大小只有8个字节所以只有 11111111存进来了再以%d打印%d比char a大会 发生整形提升之后的补码就是10000000000000000000000011111111 转为源码是100000000000000000000000000000001-1 signed char b和a的类型都是有符号整形所以ba-1 因为unsigned char c是无符号整形所以 发生整形提升之后会变成0000000000000000011111111255最高位是0变成了正数 经过计算补码和截断之后存储再按照类型整形提升之后得到的值是-1 -1 255 #includestdio.h
int main(){char a-1;signed char b-1;unsigned char c-1;printf(a%d,b%d,c%d\n,a,b,c);
} #includestdio.h
int main()
{char a -128;printf(%u\n, a);
} -128的补码是11111111111111111111111110000000因为a是char类型大小只有8个字节所以只有10000000存进a里 在以%u(以十进制无符号打印)会发生整形提升所以打印的是无符号数的原反补都是相同的所以直接打印 补码源码反码111111111111111111111111000000004294967168 #includestdio.h
int main()
{
char a128;
printf(%u\n,a);
} 下面因为存进a里面的值是和上面一样的所以%u整形提升后打印出来的值也是一样的 #includestdio.h
int main()
{int i -20;unsigned int j 10;printf(%d\n, i j);
} i的补码是111111111111111111111111111101100 unsigned int j;补码是000000000000000000000000000001010 计算过程是 ij111111111111111111111111111101100 000000000000000000000000000001010 补码11111111111111111111111111110110反码111111111111111111111111111110101 源码100000000000000000000000000001010-10 #includestdio.h
int main()
{
unsigned int i;for(i9;i0;i--){printf(%u\n,i);}
} 死循环的原因是i 是无符号整形所以i0也不成立出现4294967295是因为unsigned int 类型的范围是0~4294967296所以i是0在继续减不会变成-1因为-1不再unsigned int类型的范围里所以会变成4294967295 int main()
{char a[1000];int i;for (i 0; i 1000; i)a[i] -1 - i;printf(%d, strlen(a));
} 因为strlen到/0就不会继续了 而在ASCLL码中0就是/0实际255就是char -128道127的所有值 unsigned char i 0;
int main()
{for (i 0; i 255; i)printf(hello world\n);
} 死循环是因为255超unsiged的0~255的上线了 三.大小端字节序的介绍及判断 把0x 11 22 33 44存进内存一开始有这几种方法 低地址 高地址 11 | 22 | 33 | 44 44 | 33 | 22 | 11 11 | 22 | 44 | 33 11 | 44 | 33 | 22 存储进来主要是为了之后方便拿出来所以只留下了这两种称为大端字节序和小端字节序(字节序是以字节为单位来存储数据的) 11 | 22 | 33 | 44 大端把一个数据的低位字节的内容存放在高址值处把一个高数据的高位字节内容存放在低地址处。 44 | 33 | 22 | 11 小端把一个数据的低位字节的内容存放在低址值处把一个高数据的高位字节内容存放在高地址处。 char ch;类型是一个字节没有顺序 因为在计算机系统中是以字节为单位的每个地址单元都对应着一个字节一个字节为8 bit。但是在C语音中除了8 bit 的char之外还有16 bit的short型32 bit的long型(要看具体编译器)另外对于位数大于8位的处理器例如16位或者32位的处理器由于寄存器宽度大于一个字节那么必然存在着一个如何将多个字节安排的问题因此就导致了大端和小端存储模式。 列如一个16bit的short型x在内存中的地址为0x0010x的值为0x1122那么0x11位高字节0x22为低字节对于大端模式就将0x11放在地址值中即0x0010中0x22放在高地址中即0x0011中。小端模式刚好相反我们常用的x86结构是小端模式而KEIL C51则为大端模式很多的ARMDSP都为小端模式有些ARM处理器可以有硬件来选择是大端还是小端模式 44 | 33 | 22 | 11 因为小端把一个数据的低位字节的内容存放在低址值处把一个高数据的高位字节内容存放在高地址处。 所以这里是按照小端模式来存储的 还以这样判断大端和小端a的第一个地址如果是大端的话就是00小端是01再char*强制类型转换因为转化的类型char只有一个字节大小所以只保留了01让后再*解引用得出值和1比较就可以判断大端和小端了 要注意在Release x64下你电脑的杀毒软件会把你的程序认为是病毒换成Debug就行了 四.浮点型在内存中的存储解析
浮点数的存储规则num和*pFloat在内存中明明是同一个数为什么浮点数和整数的读取结果会差别这么大 根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示成下面形式·(-1)^S*M*2^E ·(-1)^S表示符号位当S0v为正数当S1v为负数 M表示有效数字大于等于1小于2 2^E表示指数位数 5.5-十进制浮点数二进制的浮点数101.1(-1)^0*1.011*2^2 1 0 1 . 1 1*2^2 0*2^1 1*2^0 1*2^-1 十进制9.0二进制1001.0 1.001*2^3(-1)^0*1.001*2^3 S0 M1.001,E3 IEEE 754规定 对于32位的浮点数最高的1位是符号位S接着的8为指数E剩下的23位为有效数字M 对于64位浮点数最高的1位是符号位S接着的 11位是指数E剩下的52位为有效数字M IEEE 754对有效数字M和指数E,还有一些特别的规定 前面说过1M2,也就是说M可以写成1.XXXX的形式其中XXXX是小数部分 IEEE 754 规定在计算机内部保存M时默认这个数的第一位总是1因此可以被舍去只保存后面的XXXX部分比如保存1.01的时候只保存01等到读取的时候在把第一位的1加上去这样做目的是节省1位有效数字以32位浮点数为例留给M只有23位1将第一位的1舍去以后等于可以保存24位有效数字 至于指数E情况就比较复杂 首先E为一个无符号整数(unsined int ) 这意味着如果E为8位它的取值范围为0~255如果E为11为它的取值范围为0~2047但是我们知道科学计算法中的E是可以取出 现负数的所以IEEE754规定存入内存时E的真实值必须加上一个中间数对于8位的E这个中间数是127对于11位的E这个中间数是1023比如2^10的E是10所以保存成32位浮点数时必须保存成10127137即10001001 然后指数E从内存中取出还可以再分成三种情况 E不全为0或不全为1 这时浮点数就采用下面的规则表示即指数E的计算值减去127或1023得到真实值在将有效数字M前面加上第一位的1. 比如 0.51/2的二进制形式为0.1由于规定正数部分必须为1即将小数点右移1位则为1.0*2^(-1)七阶码值为-1127126表示为 011111110而尾数1.0去掉整数部分为0补齐0到23位000000000000000000000000则其二进制表示形式为0 011111110 000000000000000000000000 E为全0 这时浮点数的指数E等于1-127(或者1-1023)即为真实值 有效数字M不再加上第一位的1而是还原为0.xxxxxxx的小数这样是为了表示-0以及接近于0的很小的数字。 E为全1 这时如果有效数字M全为0表示-无穷大(正负取决于符号位S 放一个整数9在整形n里再类型转换为float*类型取地址放进float*pFloat里是 以0 000000000 00000000000000000000101 S E M 的形式存进来的 E在内存中为全0这时E等于1-127即为真实值有效数M不再加上第一位的1而是还原为0.XXXXX成了一个很小的小数 %f打印范围没有这么小就没打印出来 9.0存进*pFloat是以0 100000001 000100000000000000的形式 以%d打印0 100000001 00010000000000000000的值也就是09567616 把9.0小数存进*pFloat里后面*pFloat打印出来自然也是9.0 #includestdio.h
int main(){int n9;float*pFloat(float*)n;printf(n的值为%d\n,n);printf(*pFloat的值为%f\n,*pFloat);*pFloat9.0;printf(num的值为:%d\n,n);printf(*pFloat的值为%f\n,*pFloat);
}