1、关于/和%:在取商时若要让结果为小数,则/运算符的两操作数至少有一个为小数即可,但取模运算时。然而%运算符的两操作数必须为整数,返回的是整除后的余数。
2、移位操作符:左移操作符和右移操作符(注意移位操作符的操作数只能是整数)
移位规则:(注意移动的是二进制位)
左移操作符(<<):左边丢弃,右边补0 (x< 右移操作符:分为两种情况:算术右移、逻辑右移(通常情况都是算术右移,如果要检验一下是那种右移方式,可以选择对一个负数进行右移操作,如果结果仍为负数则为算术右移,因为左边补的是原符号,若是正数则为逻辑右移) 算术右移(>>):右边丢弃、左边补原符号位 (x>>n,右移n位,运算结果:x/2^n) 逻辑右移:右边丢弃、左边补0 注意:逻辑位移是包含符号位在内的所有位都进行位移,算数位移是除符号位以外的位进行位移,用位移前的符号位来填补空位。(其实感觉和移完补原符号位一个道理) 首先回顾一下原反补知识:计算机中的整数(整数分为有符号数和无符号数)有三种表示方法,即原码、反码和补码(无符号数和有符号数的正数中的三种表达方式相同)。 三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位 负整数的三种表示方法各不相同。 原码: 直接将二进制按照正负数的形式翻译成二进制就可以。 反码: 将原码的符号位不变,其他位依次按位取反就可以得到了。补码:负数的补码等于反码+1 我们要知道在计算机中数据是以二进制补码形式储存的 有关移位运算符的应用:如下代码计算该数转化成的二进制数中有几个1,最低位的数字先与1按位与,再右移丢弃,达到每位都一一确认的效果。 优化后的版本如下:利用按位与每当执行num&(num-1) ,就会去掉二进制中的一个1,只要判断循环了几次为0即可以得出二进制中1的个数。 3、逻辑运算符(&&、||、!)和按位逻辑运算符(&、|、~、^) 逻辑运算包括逻辑与、逻辑或、逻辑异或、逻辑非、逻辑与非、逻辑或非等运算,运算结果只有“真”和“假”两种取值 关于逻辑运算符中的短路问题: 在&&(结合性从左向右)逻辑与运算中,若左边出现表达式为false,则可以不用进行后面的判断,在上述代码中因为a++,先引用a=0(所以判断为假,再自加计算a=a+1=1)可以不用进行后续的判断,bcd均为原值。同理,在||(结合性从左向右)逻辑或运算中若左边先出现了表达式为true,则可以不用再判断后面的表达式,如上述代码中,先引用a=0,再自加得到1,继续后面的表达式计算,++b(先计算得到3,b=3为ture)后面则不再继续判断,即得abcd分别为1 3 3 4 关于异或运算符的应用: 通过按位异或运算,可以实现两个值的交换,而不必使用临时变量,如下代码,可以记住的是两个数异或后得到的值与其中一个数再次异或后可以得到还原回另一个数的效果。即一开始的a=a^b^b 4、 关于整型提升:(cpu内整型运算的标准字节长度一般就是int型的字节长度) 在运算时,在低精度转换成高精度时(或是在表达式中各种长度可能小于int长度的整型值时)例如 尤其要注意打印格式,若为%d,则需将存储的补码形式转化为原码输出,且记得是有符号整型,所以最高位1为符号位该数字为负数,如上代码所示,若为%u,打印格式为无符号十进制整型,则最高位1不是符号位且不同转化成别的表达形式(正数和无符号位整数的原反补相同) 整型提升中参与了表达式运算的例子: 第一个sizeof打印为1,因为sizeof(c)里面的c没有进行运算,类型认为char不需要整型提升,可能会不解说那为什么转化成%u的格式还不需要整型提升?实际上%u格式打印的是sizeof(c)的值,所不是()里的c,而sizeof的返回值是unsigned型,也不需要整型提升 关于sizeof:求变量(类型)所占空间大小 sizeof(数据类型)则结果为数据类型的大小,如sizeof(char)则结果为1,占一个字节空间大小。sizeof(表达式)则结果为表达式最终结果的数据类型大小,如下sizeof(s=a+1),计算的是s的类型大小,s为short型占2个字节。数组的sizeof的值为数组元素所占空间的总和。 注意:当一个数组为形参时,其sizeof的值相当于指针的sizeof的值(所以下面两个函数求出的值都一样都为指针所占大小) sizeof用法_田_田_田的博客-CSDN博客_sizeof的用法(详细可以参考这个链接) 关于整型提升下面这个代码也比较易错: sizeof最后计算的是作为char型的c,所以上述代码打印的结果为1。 整型提升的方法:整型提升是按照变量的数据类型的符号位来提升的 负数整型提升时高位补充符号位,即补1(如上代码整型截断后的形式为11110110,整型提升时补符号位达到int型的32位为11111111111111111111111111110110) 正数和无符号数整型提升时补0 注意:1、要看截断后的最高位来判断正负,然后依据补充符号位进行整型提升。2、要注意我们赋予变量的数值时,起初本就是整型(int型),在赋给如char等变量时会发生截断而取一个字节。3、补码整型提升后还是补码,且进行运算或是操作的都是补码(所以要记得变成补码后再截断) 关于整型数据的分类: 关于结构体: a、结构的成员可以是标量、数组、指针甚至是其他结构体。 b、结构体传参有两种方式,传值和传址,但首选传址。(函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。) c、只能在定义结构体变量的同时进行初始化(如果在定义时不初始化,则要确保在使用之前该变量被赋值),和之前所说未定义的变量是一样的会默认时随机值。 关于结构体初始化: 如果你是像b2结构体变量这样赋值的,可以参考下这个链接或许有帮助C++对类(或者结构体)中字符数组赋值时,出现表达式必须是可修改的左值的问题_机械吴哥123的博客-CSDN博客 ( 注意函数要在函数包含的结构体变量定义的后面,不然容易出现报错说未定义该结构体,不要问我怎么知道的。) 关于整型存储:(记得数据是以补码形式在计算机中存储,但打印输出时要转换为原码) 搞清楚数据类型的存储形式及其规律才可以更好的判断代码出现的错误,如下形成死循环结果: 6、大小端模式: 注意:1、记得从左到右方向,左边为低地址,右边为高地址。2、大小端描述的是字节的存储顺序而不是二进制位,所以大小端模式又叫大端字节序存储模式和小端字节序存储模式 大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址 中; 小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地 址中。 若要查看软件中是采用何种存储模式,打开内存监视就可以发现了。或者用如下代码: 关于指针类型: 指针类型的意义:1、指针类型决定了指针解引用操作符能访问空间的大小:eg:char*p,*p可以访问一个字节,而int*p,*p可以访问4个字节,double*p,*p可以访问8个字节。2、指针类型决定了指针+1,-1,加的或者减的是几个字节:eg:char*p,p+1,跳过一个字符(一个字节);而int*p,p+1,跳过一个整型(四个字节) 7、关于逗号表达式 由结果可以看出:a、逗号表达式,从左向右依次执行,被计算且对引用的变量会有影响(例如abc的值分别有所变化) b、逗号表达式中整个表达式的结果是最后一个表达式的结果(如上面所示最后c为1) c、可以利用逗号表达式的判断与运行规律来简化代码(如上,从左到右的操作也可运用到函数中的类似情况的使用,再者就是最后一个表达式作为条件判断感觉有点巧妙,上述代码因为c=1>b=1为假,所以直接跳出循环没有打印y) 8、浮点型在内存中的存储int main()
{
int a = -1;
//10000000000000000000000000000001 原码
//11111111111111111111111111111111 补码 (移的是补码)
int b = a << 1;
//11111111111111111111111111111110 左移后(往右边补0)
//00000000000000000000000000000010 原码 %d打印该十进制数 -2
int c = a >> 1;
//11111111111111111111111111111111 右移后(往左补符号位1)
//10000000000000000000000000000001 原码 %d打印该十进制数 -1
printf("b=%d c=%d\n",b, c);
return 0;
}
int main()
{
int a = 4;
int count = 0;
int i;
for (i = 0; i <32; i++)
{
if (((a >> i) & 1) == 1)//通过一位一位右移来确定是否为1
count++;
}
printf("%d", count);
return 0;
}
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while (num)
{
count++;
num = num & (num - 1);
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
&、|、~运算符会根据1为真、0为假的规则对操作数的各二进制位进行逻辑运算
&&、||、!运算符会根据非0为真、0为假的规则对操作数的值进行逻辑运算
按位与运算符 & 逻辑与 && (且)
eg:5 & 4= 4 5 && 4=1
0101 (非0)&&(非0)=1
& 0100
——————————————
0100 (转为十进制数为4)
按位或运算符 | 逻辑或 ||(或)
eg: 5 | 4= 5 5 || 4=1
0101 有一个非0结果即为1
| 0100
——————————————
0101(转为十进制数为5)
按位异或运算符 ^ 逻辑非 !(取反)
eg:5 ^ 4= 1 !5= 0
0101 !(非0)=0
^ 0100 !(0)=1
——————————————
0001 (转为十进制数为1)
~运算符(单目运算符,所以只有一个操作数)
~4=7//0100取反变为1011
~运算符表示对一个数的二进制按位取反
按位异或 ^
0^0=0,0^1=1 可理解为:0异或任何数,其结果=任何数
1^0=1,1^1=0 可理解为: 1异或任何数,其结果=任何数取反
任何数异或自己,等于把自己置0
#include
int main()
{
char a = 0xb6;
//00000000 00000000 00000000 10110110 -原码
//截断后:10110110
//11111111111111111111111110110110 a整型提升后
//11111111111111111111111110110110 在%u格式下打印的的结果(二进制转为十进制数后)
//10000000000000000000000001001010 -原码 在%d 格式下打印的结果(-1001010=-74)
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("a=%d a=%u",a,a);
return 0;
}
int main()
{
char c = 1; //一个存储字节 00000001 -截断后
printf("%u %u\n", sizeof(c),c);
//sizeof返回值本就是unsigned 不需要整型提升
printf("%u %u\n", sizeof(+c),+c);
//c原码:00000000 00000000 00000000 00000001
//截断后:00000001
//整型提升为 00000000 00000000 00000000 00000001 +c和c打印的结果(转为十进制数后)
printf("%u %u\n", sizeof(-c),-c);
//-c原码:10000000 00000000 00000000 00000001
//补码: 11111111 11111111 11111111 11111111
//截断后:11111111
//整型提升为 11111111 11111111 11111111 11111111 -c打印的结果(转为十进制数后)
return 0;
}
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//指针的sizeof
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//同上
}
int main()
{
short s=0;
int a = 10;
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(s = a + 1));//sizeof(表达式)
printf("%d\n", s);// sizeof括号里的表达式不会真正参与运算,所以s还是为0
printf("%d\n", sizeof(arr));//sizeof(数组)
printf("%d\n", sizeof(ch));//同上
test1(arr);
test2(ch);
return 0;
}
int main()
{
char a = 4;//00000100 截断后
char b = 5;//00000101 截断后
char c;
printf("%d", sizeof(c = a + b));
}//a+b运算,ab分别整型提升
a 00000000 00000000 00000000 00000100
b 00000000 00000000 00000000 00000101
00000000 00000000 00000000 00001001 -a+b所得结果
但c=00001001 截断后的结果
因为a+b整型提升为int型后又赋值给c(char型)
所以这里存在隐式转换,可以这样理解(char)(a+b)
int main()
{
int a = -5;
//10000000 00000000 00000000 00000101
//11111111 11111111 11111111 11111011 补码
//1111111 11111111 11111111 111110110 a左移后
char c = a << 1;
//11110110 截断后储存在c中的形式
printf("%d %u", c, c);// 需要整型提升到32位 如下补符号位1
//1111111 11111111 11111111 111110110 %u打印的就是其转化为十进制后的数(注意最高位不是符号位)
//1111111 11111111 11111111 111110101 反码 由补码-1得
//1000000 00000000 00000000 000001010 原码 %d 打印的就是-1010=-10
int main()
{//
char a = 128;
//00000000 00000000 00000000 10000000 原码
//10000000 截断后(因为截断后的最高位为1,所以符号位为1为负数,整型提升时补1)
//11111111 11111111 11111111 10000000 整型提升后 %u打印 4294967168
//10000000 00000000 00000000 10000000 原码 %d打印 -10000000=-128
char b = -3;
//10000000 00000000 00000000 00000011 原码
//11111111 11111111 11111111 11111101 补码(如第3点所说)
//11111101 截断后
//11111111 11111111 11111111 11111101 整型提升后 %u打印 4294967293
//10000000 00000000 00000000 00000011 原码 %d打印 -011=-3
char c = -128;
//10000000 00000000 00000000 10000000 原码
//11111111 11111111 11111111 10000000 补码
//10000000 截断后(如第2点所说,由上面的32位的原码截断所得)
//11111111 11111111 11111111 10000000 整型提升后 %u打印 4294967168
//10000000 00000000 00000000 10000000 原码 %d打印 -10000000=-128
printf("%d %u\n", a, a);
printf("%d %u\n", b, b);
printf("%d %u", c, c);
return 0;
}
数据类型基本归类:
整型家族: 浮点型家族;
char(char在内存中存储的是ASCII码值)
unsigned char float
signed char double
short
unsigned short [int]
signed short [int] 构造类型:
int 数组类型
unsigned int 结构体类型 struct
signed int 枚举类型 enum
long 联合类型 union
unsigned long [int]
signed long [int] 空类型:
void表示空类型(无类型)
指针类型: 通常应用于函数的返回类型、函数的参数、指针类型
int*p;
char*p;
float*p;
void*p;
在数组类型中,eg: int a[10],则int[10]是数组类型(把数组名去掉后剩下的就是数组类型)
typedef struct book
{
char name[13];
int time;
}b;
int main()
{
b b1 = { "我叫书名",2019 };//一般这样初始化
//b b2;//如果你是这样先定义再逐个赋值会报错(说表达式必须是可修改的左值)
//b2.name = "我叫书名吗?";
//b2.time = 20190;
//定义后再初始化的话可以如下
b b3;
strcpy(b3.name, "我叫书名呀!");//数组记得要用strcpy,如果不用详情可见链接方法
b3.time = 20190;
printf("%s %d\n", b1.name, b1.time);
printf("%s %d\n", b3.name, b3.time);
return 0;
}
struct book
{
char name[13];
int time;
};
//typedef struct animal
//{
// char animal;
// int age;
// short tree;
//}animal;//把struct animal的类型名改为了animal(这里的animal是类型)
struct nature
{
char animal[7];
int age;
short tree;
struct book g5;//结构体成员可以是其他结构体变量
} g1, g2, g3;//创建的三个全局的结构体变量(这里是创建的变量)
void print(struct nature b1, struct nature* p)
{ //传址:struct nature*p=&b;
printf("%d\n", b1.age); //结构体成员访问的三种形式(.与->与*)
printf("%s\n", b1.g5.name);
printf("%d\n", (*p).age);
printf("%d\n", (*p).g5.name);
printf("%d\n", p->age);
printf("%s\n", p->g5.name);
}
int main()
{
struct nature g;//局部的结构体变量
//animal g;//被typedef重命名后可以这样创建结构体变量
struct nature g4 ={"小狗",3,11 };//定义变量的同时初始化,要按照结构体定义的变量顺序来赋值,一一对应,未赋值则默认为0
struct nature b = {"小猫",2,7,{"c语言",'2019'} };
print(b,&b); //b为传值,&b为传址
return 0;
}
char 占一个字节 8个bit位
signed char 有符号char类型
数据范围
原码: 补码; 补码转化为十进制后
00000000 00000000 0
00000001 00000001 1
00000010 00000010 2
00000011 00000011 3
······ ······· ·····
01111111 01111111 127
10000000 110000000 -128 (规定10000000的补码为-128)
10000001 11111111 -127
······ ······· ······
11111101 10000011 -3
11111110 10000010 -2
11111111 10000001 -1
可见在内存中signed char的表示范围为-128~127
unsigned char 无符号char类型(最高位不是符号位)
且原反补相同
数据范围: 转化为十进制数后
00000000 0
00000001 1
00000010 2
00000011 3
········ ·······
10000000 128
10000001 129
10000010 130
········ ······
11111111 255
可见在内存中unsigned char的表示范围为0~255
int main()
{
unsigned int i;//无符号整型,所以不存在负数
for (i = 9; i >= 0; i--) //只有当i<0时才会跳出循环
{
printf("%u\n", i);
}
return 0;
}
在这个代码中,一直到i=-1时
原码:10000000 00000000 00000000 00000001
补码:11111111 11111111 11111111 11111111
在内存中以补码形式储存,所以当i=-1时,因为其为无符号整型
i=4294967295 (补码的十进制数)
所以打印的结果为:9 8 7 6 5 4 3 2 1 0 4294967295 4294967294 4294967293·······
···0 4294967295 4294967294 4294967293 ······(后面一直循环0 4294967295 4294967294·····)
int check_sys()
{
int i = 1;
//00000000 00000000 00000000 00000001 二进制数
//Ox00 00 00 01 十六进制
return (*(char*)&i);//强制类型转换成char*类型,这样可以访问一个字节
} //再解引操作,返回第一个字节的值达到判断大小端的效果
int main()
{
int ret = check_sys();
if (ret == 1) // 若为小端,低位放到低地址
{ // 则存储形式为 01 00 00 00
printf("小端\n"); //所以第一个字节(01)的值1(返回1)
}
else //若为大端,低位放到高地址
{ //存储形式为 00 00 00 01 所以第一个字节(00)的值0 (返回0)
printf("大端\n");
}
return 0;
}
int main()
{
int a = -10;
//10000000 00000000 00000000 00001010 原码
//11111111 11111111 11111111 11110110 补码
//Oxff ff ff f6 十六进制
//因为是小端存储模式所以顺序为 f6 ff ff ff
char* r = (char*)&a;
printf("%d\n", a);
int* y = &a;
printf("%p\n", r);
printf("%p\n", r + 1);//指针跳过了一个字节 -char*
printf("%p\n", y);
printf("%p\n", y + 1);//指针跳过了四个字节 -int*
*r = 0;
//因为char*r只能访问一个字节所以第一个字节f6被赋值为0
//变成 00 ff ff ff
printf("%d", a);
//11111111 11111111 11111111 00000000 补码
//10000000 00000000 00000001 00000000 原码 %d打印-100000000=-256
return 0;
}
int main()
{
int a = 1;
int b = 3;
int c = (a > b ? a : b, a = a + 2, b = 1);
printf("%d %d %d\n", a, b, c);
//如果想要运行多个相关操作可以用逗号表达式简化
//就像先把a的值赋值给c,再把a+b的值赋值给b,再判断是否c>b
while (c = a, b = a + b, c > b)
printf("y");
return 0;
}
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,1<=M<2,也就是说M可以写成1.xxxxx的形式,其中xxxxx表示小数部分
2^E表示指数位。
例如:6.0 写成二进制110.0,按照上述写为(-1)^0*1.10*2^2
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
浮点数在内存中:
(在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的
xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。)
关于指数E(注意E为一个无符号整数)
这意味着,如果E为8位,它的取值范围为0~255;
如果E为11位,它的取值范围为0~2047。但是科学计数法中的E是可以出现负数的,
所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,
对于8位的E,这个中间数是127;
对于11位的E,这个中间数是1023。
如 2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001
eg:float a=9.0; 二进制为1001.0,转化为:(-1)^0*1.001*2^3
32位二进制存储表示为: (s=0,E=3+127=130,M=001····0)
0 10000010 00100000000000000000000
— ———————— ———————————————————————
S E(阶码) M(有效数字)
int main()
{
int n = 9;
//0 00000000 00000000000000000001001
float* pFloat = (float*)&n;
//(-1)^0*0.00000000000000000001001*2^(1-127)
printf("n的值为:%d\n", n); //9
printf("*pFloat的值为:%f\n", *pFloat); //%f格式保留六位小数即0.000000
*pFloat = 9.0;
//1001.0 -> (-1)^0*1.001*2^(3+127)
//该浮点数的二进制储存为0 10000010 00100000000000000000000
//又因为原反补相同所以01000001000100000000000000000000转化为的十进制数即为%d打印结果
printf("num的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);//9.000000
return 0;
}
浮点数的取出:
首先第一位为S(符号位)
后面8位或11位为E,关于E有如下请况:
1、E不全为0或不全为1时
指数E的计算值(转化为十进制后的数值)减去127(或1023)得到E的真实值(因为之前存储的时候加了)
然后再将有效数字M前加上第一位的1(之前存储的时候舍去的)
比如给一个补码 0010001000100001000000010001000000
这32位按从左往右,第1位为符号位,后8位为阶码,最后23位为尾数排开
即:0 01000100 0100001000000010001000000
转化:s=0,E=68(01000100的十进制数值)-127=-59,M=1.0100001000000010001(有效数字前加上第一位1)
得:(-1)^0*1.0100001000000010001*2^(-59)
2、E为全0时
指数E等于1-127(-126)或者1-1023(-1022)即为真实值
有效数字M不用再加上第一位的1(因为全为0时该尾数是超级小的数字,无限接近于0)
即还原为0.xxxxx的小数
例如:00000000000011010000000000000000
即:0 00000000 00011010000000000000000
转化:s=0,E=1-127(E为全0)=-126,M=0.0001101(有效数字前不用加上第一位的1)
得:(-1)^0*0.0001101*2^(-126)
3、E全为1时
例如:0 11111111 00000000000000000000000
s=0,E=255-127=128,M=1.0 (-1)^0*1.0*2^128
(如果有效数字M全为0,表示±无穷大(正负取决于符号位s))(wo bu shi hen dong)