1. 移位操作符的对象只能是整数,只能对整数的二进制位进行移动。
2. 二进制是数值的一种表示形式。一个整数占了四个字节,相当于一个整数可以用32位二进制位序列表示,那么这时候该如何判断正负呢?规定:这32位二进制序列的头一位如果是1,就是负数;如果是0,就为正数。(一个二进制序列的头一位就是符号位,根据0和1来判断符号)。
3. 不仅如此,就算是二进制的表示,也有原码与补码,反码之分。值得注意的是:在计算机里面存储与进行各种运算等等,都是通过补码进行计算的;而在屏幕上呈现的打印出来的你能看到的数据则是原码形式。
4. 那么二进制表示形式的原码,反码,补码之间又有哪些联系呢?对于正数而言(符号位为0),其原码反码补码是一模一样的。然而对于负数而言,它的反码与补码要通过计算得到。
5. 就负数而言,(原码可以变成反码与补码):反码是原码基础上符号位不变,其他位按位取反。而补码的话就是反码加1。(补码也可以变成原码):也是符号位不变按位取反,然后加1。
6. 顺便再提一下,在内存当中存储的都是补码形式的二进制,在计算机里面参与各种运算,都是以补码的形式参与,而我们看到的都是原码的值。
7. 左移:二进制补码左边丢弃,右边补0;(算术)右移:二进制补码右边丢弃,左边补符号位。
8. 在内存当中补码全为1的只有-1这个奇葩。
9. .位操作符有按位与,按位或与按位异或,它们的操作数也必须都是整数,同时也是针对二进制位进行计算,由于存储在计算机当中的都是补码,因此就是对补码进行位操作。按位异或的操作是对应的二进制位相同为0,不相同为1。
10. 对于异或操作符^,需要注意以下特点:
1. 两个相同的整数异或结果为0。
2. 0异或任何整数,结果还是那个整数。
3.异或是支持交换律的。
11. 在c语言中,变量也可以拥有一个新的类型:布尔类型,_Bool flag = true或者_Bool flag = false.
这个布尔类型就是专门用来表示真假的,要使用布尔类型,必须得包含一个头文件: “stdbool.h”
12. 一个字符串常量,如“abcdef”,这个常量字符串本身就是地址,是其首个字符的地址,都不用用&取地址。
13. sizeof的内部表达式是不参与计算的。因此括号内部的等式是不影响外面的。但为什么是不计算的呢?当我们写一个代码的时候,比如说源文件是test.c,我们最终运行的是test.exe,原代码到可执行程序,中间需要编译+链接,然后最终会生成可执行程序,可执行程序才可以运行。如果说括号里面的表达式要运行的话,必须在生成可执行程序后才能够运行。但是很遗憾的是:sizeof这个操作符的处理在编译阶段就已经处理了。
14. 取反操作符~是对二进制补码的每一位按位取反(包括符号位)。
15. ++与--都分为前置与后置,后置++的规则是先在代码语句中使用,在自增1。前置++是先自增1,然后再在代码语句中使用(不管是在打印,赋值,还是函数啊等等,都一样)
16. 数组传参,形参可以写成数组,也可以写成指针,而实参就是数组名。
17. %s是用来打印字符数组的,如果你要打印整形数组的话,只能循环遍历一个一个的打印,然后在这个循环里面,可以用下标访问进行打印,用指针也可以进行打印。
18. 在这边还是得再尤其强调一次,当用数组传参的时候(比如说是实参就是数组名arr),不管你形参用什么去接收,在函数体里面,arr本质上就是代表一个指针,代表元素的地址。如果你要访问某一个数组的元素,用arr[ i ]与*(arr+i)都是一样的一个道理,这两种表达中,后者更为底层,因为前一种表达运行时实际上也是转化为后一种表达。
19. 对于并且(&&)操作符而言,只要一旦左边发面有假(或为0),那么在操作符右边就不会运行下去了无需计算,因为反正最后的结果肯定是假,因此操作符右边的式子的效果不会产生,因为根本就没有执行; 对于或者( | |)操作符而言,只要一旦左边发面有真(或为非0),那么在操作符右边就不会运行下去了无需计算,因为反正最后的结果肯定是真,因此操作符右边的式子的效果不会产生,因为根本就没有执行。这个叫做短路。
20. 逗号表达式其实就是用逗号隔开的多个表达式:exp1,exp2,exp3,exp4,exp5........expn。那么它的计算规则是怎么样的呢?它是从左向右依次执行,整个表达式的结果就是最后一个表达式(expn)的结果,但是别忘了之前的那些表达式都会被依次执行,会产生各种各样的效果。
21. 再次警告一下:如果函数传参的时候不是传址调用,那么形参就是实参的一个临时拷贝,你在函数体内改来改去,不会影响我的实参。结构体变量与平时我们再讲的一些变量,道理其实是一样的,地位平等。当你用传址调用时,函数体内部的造成变化是会对全局有影响。
22. C语言的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的,就是说至少也得是一个整型类型,以这样的精度来计算。那么为了获得这个精度,表达式里面的字符和短整型操作数(什么是操作数自己应该懂得噢)在使用之前会被转换为普通整型,这种转换就被称为整型提升。
23. C语言的整形运算(涉及到char,short,int)在运算的时候char与short类型都会被提升为int类型,这一切都是为了适应我们的CPU,因为CPU里的整型运算器的操作数一般就是int类型的字节长度,同时也是CPU通用寄存器的长度,因此,即使是两个char类型的相加,在CPU执行时实际上也要转化为CPU内整型运算器的操作数的标准长度。这一切都是为了适应我们的CPU。
24. 整型提升简而言之就是这样:
1. 先看你这个数据的数据类型是有没有符号的。
2. 如果是没有符号的类型,那么就高位补0,也就是说把它(二进制补码)补到32位这个长度。
3. 如果是有符号的类型,注意: 此时2进制补码的最高位就是符号位,高位补充符号位。把它(二进制补码)补到32位这个长度。
25. 比如说我要printf("%d",c),%d是打印十进制的整数,而我们的c是char类型的变量,因此在打印的时候也需要整型提升,再次强调,只有负数的原码补码要进行转化,而正数的原码反码不用,一样的。
26. 如果表达式某个操作符的各个操作数属于不同类型,除非其中一个操作数转换为另一个操作数的类型,否则操作无法进行。到底什么意思呢?比如说一个int类型的与另外一个float类型的两个操作数相加,因为它们各自的类型都是大于等于整型的,并且操作数之间类型不一样,这时候就不能直接进行操作,编译器会进行类型转换,也就是算术转换,转化顺序依次为:int , unsigned int , long int , unsigned long int , float , double , long double
27. 对于优先级而言,一定是在相邻的操作符才讨论优先级。如果在表达式当中,两个操作符不相邻,无优先级之说.
28. unsigned int 修饰的时候对数字的二进制补码形式是没有任何影响,只是对二进制补码解读的时候会有影响,于此同时,你这个负数虽然外表上人模狗样还是个“负数”,其实已经是一个很大很大的数值了.
29. 对于一个数值,如果想要得到它n进制形式的每一位数,只需要把这个数值不断%n,然后再/n去更新这个数值,如此循环往复。如果你要得到一个数值二进制形式下的每一位数,就可以把这个数值不断%2,然后再/2去更新这个数值,如此循环往复。但这样子的话是有缺陷的,比如对于负数而言。这时候对于负数而言的话,为了避免缺陷,需要把负数类型强制转化为unsigned int,这样子的话在数值上其实每个负数变成了很大很大很大的一个正数(我再次强调一下,在内存当中存的是二进制的补码,对正数而言,原码与补码都是一样的),虽然如此,但是那个二进制序列的形式仍然没有发生一丁点变化,这就没有背离我的初衷.
30. 假设我现在有一个32位的二进制序列,不管是几位,反正是个二进制序列就对了,那么我该如何得到这个二进制序列的最低位呢?事实上,我只需要对这个二进制序列&(按位与)1(补码形式: .........0000000001)即可,这样子返回的结果就是那个二进制序列的最低位.
31. 按位与,按位或,按位取反,左移右移等位操作符都产生的只是一个效果,而对内存当中的二进制的布局与具体变量的数值等等都不会产生任何实质影响,也就是说表达式的结果比如说是左移或者右移或者按位与等等之后的结果,但是实际上对于本身没有发生改动的,没有副作用
32. 神奇表达式 n = n&(n-1) 对n 的二进制补码序列进行物理消灭1 ,这个表达是每执行一次,就会使n的二进制序列(补码)中的1减少一个(变为了0),随着1的不断减少,最终这个二进制序列会是一个全0的状态
33. 在c语言中,sizeof的返回值是unsigned int(无符号的整数),size_t是sizeof计算的结果的类型,它其实也就是unsigned int
34. 对于某一个数值的n进制表示形式下,如果要得到最后一位,你只需要把那个数值%进制数;如果要得到除去最后一位的“那个形式”,只需要把那个数值/进制数。