目录
1,函数
什么是函数?函数(function)是完成特定任务的独立程序代码单元。
函数组成:修饰符(可以不带),返回类型,函数名,参数列表,{ },返回值(当返回类型为void时,表示无返回值)。
举例:
static int get_max(int a, int b ) //对于get_max()来讲,a,b为形式参数
{
if (a > b)
return a;
else
return b;
}
static --- 修饰符
int --- 返回类型
get_max --- 函数名
(int a,int b) --- 参数列表
{ }内的是函数体,内部的return 是返回值。
1,实参,形参
函数在调用的时候,实参传给形参,其实实参是形参的一份临时拷贝,改变形参不能改变实参
static int get_max(int a, int b ) //对于get_max()来讲,a,b为形式参数
{
if (a > b)
return a;
else
return b;
}
int main()
{
int x = 0, y = 0; //在这时,对于get_max()来讲,x,y为实际参数
scanf("%d %d", &x, &y);
int i = get_max(x, y);
printf("%d", i);
return 0;
}
在main()函数中,x和y是实际参数。在get_max中,a和b是形式参数。
2,传值调用,传址调用
函数的参数列表可以既可以传递变量也可以传递指针。
区别是:
传值调用:当传递变量的时候,函数形参会在内存中复制一份实参的数值给形参,不会对原有数值造成影响。
传址调用:函数的参数列表内是指针,指向变量地址,在函数内可以直接通过指针解引用得到实参,可以在这个基础上对参数进行修改。
示例:
void exchange1(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
void exchange2(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int x = 1, y = 2;
printf("x = %d y = %d\n", x, y);
exchange1(x, y);
printf("x = %d y = %d\n", x, y);
exchange2(&x, &y);
printf("x = %d y = %d\n", x, y);
return 0;
}
输出:
x = 1 y = 2
x = 1 y = 2
x = 2 y = 1
可以看到,第一次传值调用,并不会交换x,y的值;第二次传址可以成功交换
优缺点:
1,传址调用有可能会更改实参的值,传值不会修改;当在需要修改实参的时候,必须使用传址;为了防止在不需要的时候改变实参的值,我们可以在指针前加上const。
2,传址调用不需要复制一份实参,占用内存比传值调用低,效率更高。
综上:一般来讲,使用传址调用优势更加明显,当不需要更改实参时,记得加const。
3,函数定义和声明
示例:函数声明
void fun(void);
示例:函数定义
void fun(void)
{
;
}
函数定义在使用之前,不会报错,假如使用在定义之前,则需要加上函数声明,否则会报错。
函数在使用之前要么声明要么定义。
函数定义是一种更强的声明。
4,递归
递归的两个必要条件:
1,存在限制条件,当满足这个限制条件后。递归便不再继续
2,每次递归调用后,会越来越接近这个极限
递归出错的原因:
1,不满足两个条件;2,递归次数太多,栈溢出
简单介绍一下内存
我们需要知道内存布局是怎样的:
内存布局分为栈区;堆区;静态区
栈区:负责存储局部变量、函数形参,调用函数时的返回值等临时变量
在函数结束时,这些局部变量就会被销毁
堆区:动态内存分配的malloc/free;calloc;realloc;
直到程序结束,或者手动释放(free())才会释放内存空间
静态区:存储全局变量;静态变量
直到程序结束,自动释放。
每一次函数(包括main函数)调用,都要在栈区内存开辟一份空间,调用完毕,释放该内存空间,所以使用递归是需要做到两点:
1,不能死递归,必须有跳出条件,每次递归逼近跳出条件
2,递归层次不能太深
递归举例:递归计算从1加到N
//递归计算从1加到N
int cal(int n)
{
if (n == 1)
{
return 1;
}
return n+cal(n - 1);
}
int main()
{
int i = 100;
printf("%d\n", cal(i));
return 0;
}
输出:
5050
不适合递归的举例:计算第N个斐波那契数 -- 1;1;2;3;5;8;13;21;34;55....
int fib(n)
{
if (n <= 2)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int z = fib(n);
printf("%d", z);
return 0;
}
输出:当计算第十个数时,很快就有结果了
10
55
当计算第50个数的时候,等很久都没有结果
50
这是因为
返回值是两个数值函数,这回导致计算量指数上升,当计算第50个斐波那契数时,计算量接近2^50次。
当递归的方式,计算次数太多时,效率太低,用循环(迭代)效率比较高
修改代码:
int fib(n)
{
int a = 1, b = 1, sum = 1;
while (n > 2)
{
sum = a + b;
a = b;
b = sum;
n--;
}
return sum;
}
int main()
{
int n = 0;
scanf("%d", &n);
int z = fib(n);
printf("%d", z);
return 0;
}
输出:
46
1836311903
5,一些代码题
1,将字符串反序输出(迭代)
void reverse_string(char * str)
{
int len = strlen(str);
int temp = *str;
*str = *(str + len - 1);
*(str + len - 1) = '\0';
if (len > 3)
{
reverse_string(str + 1);
}
*(str + len - 1) = temp;
}
int main()
{
char arr[] = "abcdef";
reverse_string(arr);
printf("%s", arr);
return 0;
}
输出:
fedcba
2,sizeof()操作符
sizeof(s= 2+5)
sizeof内部的表达式不参与运算
sizeof在编译期间运行,内部的表达式不会执行,程序执行的时候,sizeof直接计算好结果,直接输出结果4了,并不会执行内部的语句
int main()
{
int a = 1, b = 2, c = 3;
char i = 1;
printf("%zd", sizeof(i = a + b));
printf("%d", sizeof(-i));
return 0;
}
输出:
14
sizeof在计算字符串(字符数组)长度时,会计算“\0”; 但是strlen()在计算字符串长度是,不会计算“\0”
strlen() 函数 --- 计算字符串长度,寻找\0前出现的字符个数
sizeof 操作符--- 计算(变量/类型)所占空间大小,单位是字节
3,两个字符串比大小:
char A={"abc"}
char B={'a','b','c'}
A比B长,但是strlen的计算结果会出错,造成B看起来比A长
int main()
{
char A[] = { "abc" };
char B[] = { 'a','b','c' };
char C[] = { 'a','b','c',"\0" };
printf("%d\n", sizeof(A));
printf("%d\n", sizeof(B));
printf("%d\n", sizeof(C));
printf("%d\n", strlen(A));
printf("%d\n", strlen(B));
printf("%d\n", strlen(C));
int i[5] = { 0,1 ,1 ,2 };
return 0;
}
输出:
4
3
5
3
35
3
4,按位取反等操作符
int main()
{
int a = 2;
int b = ~a;
printf("b = %d", b);
return 0;
}
输出:
-4
当&a时,表示取地址 ;&&表示 与操作符
当a&b时,表示a和b按位与
(类型)变量 ---- (int )a
表示强制类型转换 ----- 强制转换为整形
5,隐式转换
不同数据类型在混合运算时,时常会进行整型提升或者强制转换的操作,C语言中提升规则如下图:
上图中的long型在windows中占据4个字节和int一样,我认为上图应该表示8字节的类型,在windows中可以认为是long long型。
char 范围的大小是从 -128--127
隐式转换 的一种是整型提升
整形提升的条件:1,当计算的时候,2,只有char和short类型需要整形提升
整形提升:(只有char和short是小于整形的,才会整形提升)
比如:char类型会在计算时转换为int类型;
整形提升的方式:
1,整型提升是按照变量的数据类型的符号位来提升的
2, 有符号数:负数的整型提升:符号位为1,前面全部补1;
3,无符号数:正数 的整型提升:符号位为0;前面全部补0;
4,无符号数全部补0;
示例:从char和short整型提升到int
int main()
{
char a = 0xb6;
printf("a(char型) = %x \n", a);
short b = 0xb600;
short b1 = 0x0600;
unsigned short b2 = 0xb600;
printf("b (有符号负数) = %x \n", b);
printf("b1(有符号正数) = %8x \n", b1);
printf("b2(无符号数) = %8x \n", b2);
printf("b3(常量数字) = %8x \n", 0xb600);
printf("b3_size = %d \n",sizeof(0xb60));
int c = 0xb6000000;
printf("c(int型) = %x \n", c);
if (a == 0xb6) //在逻辑表达式计算的时候,需要整型提升,下同
{
printf("a\n");
}
if (b == 0xb600)
{
printf("b\n");
}
if (b1 == 0x0600)
{
printf("b1\n");
}
if (b2 == 0xb600)
{
printf("b2\n");
}
if (c == 0xb6000000);
{
printf("c\n");
}
return;
}
输出:
a(char型) = ffffffb6
b (有符号负数) = ffffb600
b1(有符号正数) = 600
b2(无符号数) = b600
b3(常量数字) = b600
b3_size = 4
c(int型) = b6000000
b1
b2
c
根据结果,我们可以看到:
1,从a,b,c的结果看,只有c打印了,可以得出:char和short类型才需要整型提升,int类型不需要
2,从b3结果常量数字本身应该是默认为int类型,不需要整型提升
3,从b1,b2的结果来看,当有符号正数和无符号数整型提升的时候,值不会变化。从a,b来看, 只有有符号数的负数会变化。
实际上,当整型提升后,值会发生变化的情况,一般都是从有符号负数提升导致的。
举例:从有符号int提升到无符号int
int main()
{
unsigned int a = 20;
int b = 13;
int k = b - a;
if (k > b + a) {
printf("k > b + a Ok\n");
}
if (k == b - a) {
printf("k == b - a Ok");
}
return 0;
}
输出:
k > b + a Ok
k == b - a Ok
乍一看,两个if表达式都通过很诧异,实际上是符合规则的;
1, 根据图示,int和unsigned int进行计算需要提升到unsigned int类型,也就是说,k和a+b对比的时候,左侧为有符号int,右侧为无符号int,需要提升。
k = (b-a)的值为一个有符号负数,我们可以看作是-7提升到无符号数,那么这将是一个非常大的数字,因为unsigned int类型的取值范围特别大。然而,a+b是个很小的数字,所以这个表达式通过。
2,第二个,左右类型不同,k需要提升;k本来就是b-a得到的,再提升为unsigned int肯定也是一样的值,都是值非常大的无符号数,所以表达式可以通过。
2,指针
一个内存单元:一个字节
计算可知:32位系统最大支持4g的内存,只能给内存编号这么多个
指针就是变量,称为指针变量,用来存放地址的变量
指针变量在32位平台上是4个字节,在64位平台上是8个字节(8个bit就是一个字节,64个bit位就是8个字节)
1,指针和指针类型
指针变量的大小都是4个字节(sizeof可知)
1,如果是int类型的指针变量,解引用一次,就可以访问四个字节的内存地址
2,如果是char类型的指针变量,解引用一次,只能访问一个字节的内存地址
指针类型的意义:
1,指针类型决定了:指针解引用时 的访问权限多大(能操作几个字节)
比如:int *ptr ;指向四个字节
2,指针类型决定了:指针走一步,能走多远(步长)
比如:char *ptr; ptr+1指的是跳过一个字节;
如果为int指针,那么ptr+1跳过四个字节(一个整形)
2,野指针
野指针:概念指针指向的位置是不可知的;
野指针成因:
1,指针未初始化(局部变量不初始化,默认是随机值) 这个随机值不一定是属于当前程序的,会导致非法访问内存
2, 越界访问
比如:
int mian()
{
int arr[10] = {0};
int*p = arr;
for (i = 0;i<=10;i++)
{
*p = i;
p++; //循环11次导致指针越界,非法访问内存
}
}
3,指针指向的空间被释放,这个指针就成为野指针了(内存动态管理)
如何避免野指针:
1,指针要初始化;(当不知道当前指针被初始化为什么时,直接初始化为空指针)
int * ptr = NULL;
当明确知道该初始化为什么时,直接初始化;
2,小心指针越界;
C语言本身不会检查是否越界;
3,指针指向空间释放及时置NULL;
4,指针使用之前进行有效性检查;
if(p != NULL)
{
}
3,指针运算
指针运算
++比*(解引用符)的优先级高
*i++ = 0; 表示先给指针i指向的数据赋值0,再把地址++;
1,指针的关系运算:
指针+1,直接跳过一个相同元素大小的地址。
int main()
{
int arr[5] = { 1,1,1,1,1 };
int* p = NULL;
int i = 0;
for(i = 0;i<10;i++)
{
printf("%p\n", &arr[i]);
printf("%d\n", arr[i]); //就算越界,也可以打印地址和数组变量,地址顺延,变量是随机值吧
}
for (p = &arr[0]; p < &arr[5];)
{
*p++ = 0; //指针的关系运算
printf("%d\n", *(p-1));
}
return 0;
}
输出:
//输出
000000F74375FC48
1
000000F74375FC4C
1
000000F74375FC50
1
000000F74375FC54
1
000000F74375FC58
1
000000F74375FC5C
-858993460
000000F74375FC60
-858993460
000000F74375FC64
-858993460
000000F74375FC68
-858993460
000000F74375FC6C
-858993460
0
0
0
0
0
4,指针-指针
指针-指针
int main()
{
int arr[10];
printf("%d\n", &arr[9] - &arr[0]);
//指针--指针得到的是指针的元素下标之差,不是内存地址之差
return 0;
}
输出
9
(指针 - 指针)得到的是指针的元素下标之差,不是内存地址之差
(指针 - 指针)的前提是两个指针指向同一块空间
PASS:指针+指针无意义。 就像日期—日期有意义,日期+日期无意义一样
例子:数组名和&数组名的区别
int main()
{
char arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0] + 1);
return 0;
}
输出
//输出
0000007C756FF788
0000007C756FF789
0000007C756FF788
0000007C756FF792
0000007C756FF788
0000007C756FF789
说明:
1,虽然数组地址和数组首元素地址都是一个,但是,步长不一致;
2,数组名本质上就是数组首元素地址,是同一个东西;
注意:两种情况下,数组名不是数组首元素地址
1,&arr -- 表示取的是整个数组的地址
2,sizeof(arr)表示求整个数组的大小
C语言标准的一个规定:
允许指向数组元素的指针与数组最后一个元素后面的内存位置的指针进行比较,但是不允许与指向数组第一个元素之前的内存位置的指针进行比较
5,介绍一下数组中的 [ ] 符号
首先,上一个结论,对于数组arr,arr[0] 和 0[arr] 是一样的
示例:
int main()
{
int arr[5] = { 1,2,3 };
printf("%d\n", arr[0]);
printf("%d\n", 0[arr]);
return 0;
}
输出
1
1
解释:
[]是一个操作符
编译器进行编译时,会将arr[2] 转换为 *(arr +2)
[]操作符符合交换律,所以可以这样写
arr[2] <==> *(arr+2) <==> *(arr+2) <==> *(2+arr) <==> *(2+arr) <==>2[arr]
6,二级指针
二级指针:就是一个指向指针的指针
代码示例:
int main()
{
int a = 0;
int* pa = &a; //pa是指向a变量的指针
int** ppa = &pa; //二级指针,指向指针pa的地址
return 0;
}
在上述代码中,a是变量,pa是指针,也可以叫一级指针;ppa就是二级指针
在声明二级指针时,两个*分别有不同的含义,( (int *)表示指针指向的对象的类型是int*指针)||((*ppa)表示这个变量是一个指针 )
7,指针数组
本质上指针数组就是一个数组,只不过它的数组元素是指针。
就像:
字符数组 存放字符
整型数组 存放整形 一样。
3,结构体
数组是一组相同类型元素的集合
结构体也是一些元素的集合,但是元素的类型可以不同
结构体是一些值的集合,这些值成为成员变量
比如:char int float doublt等等
示例:结构体
struct b
{
char c;
short s;
double d;
};
同时,结构体内部也可以包含其他结构体
示例:包含结构体的结构体
struct stu
{
//成员变量
struct b sb; //结构体成员变量可以说其他结构体
char name[20];//name
int age;//age
char id[20];//学号
}s1,s2; //s1和s2也是结构体变量,但是是全局变量
两种通过结构体找到成员的办法:“ . ” 和“ -> ”
示例:
void print1(struct stu t)
{
printf("%c %d %lf %s %d %s\n",
t.sb.c,
t.sb.s,
t.sb.d,
t.name,
t.age,
t.id);
}
void print2(struct stu* ps)
{
printf("%c %d %lf %s %d %s\n",
ps->sb.c,
ps->sb.s,
ps->sb.d,
ps->name,
ps->age,
ps->id);
}
上述有两个函数,那么对于结构体参数,我们应该选择print1的传值还是print2的传址呢?
最好选第二个。
首选传址调用,首选print2()函数的原因:
函数传参的时候,参数是需要压栈的,如果传递一个结构对象的时候,结构体过大,参数压栈的时候,系统开销比较大,这会导致性能的下降。
结论:结构体传参时,要传结构体的地址。
函数调用的参数压栈:
栈:是一种数据结构
遵循先进后出,先进先出,像一个只有一个出口的水管
每一个函数的调用,都会在内存的栈区上开辟一块空间;
内存分为栈区, 堆区, 静态区
函数传参有顺序,从右往左传递,比如对于ADD(int x,int y); 先传递y在传递x
因为 函数传参的动作很像压栈,也叫压栈传参。
4,调试
调试就是找bug的过程
调试是什么?有多重要?
所有发生的事情都一定有迹可循,如果问心无愧,就不容易掩盖也就没有迹象了,如果问心有愧,就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤儿上,这就是推理的途径。顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。
一名优秀的程序员就是一名侦探,每一次调试都是尝试破案的过程
调试又称为除错
常见错误分类:
1;编译型错误
直接看到错误提示信息(双击),解决问题,或者凭借经验就可以搞定,相对来说简单
2;链接型错误(link错误,双击无反应)
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符不存在或者拼写错误。
找不到函数,链接找不到,(函数不存在或者函数写错了)
3:运行时错误
借助调试,逐步定位问题,最难搞
调试的基本步骤:
发现程序错误的存在 (发现错误的人:1,程序员自己;2,软件测试人员;3,用户)
采用隔离或者消除的方式对错误定位 ()
确定错误的原因
提出纠正错误的解决办法
对程序错误予以改正,重新测试
优秀的代码:
1,代码运行正常
2,bug很少
3,效率高
4,可读性高
5,注释清晰
6,文档齐全
常见的coding技巧:
1,使用assert
2,尽量使用const
3,养成良好的编码风格
4,添加必要的注释
5,避免编码的陷阱
debug和release的区别
debug称为调试版本,它包含调试信息,并且不做任何优化,便于程序员调试程序,相对体积大一点
release称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便于用户很好地使用
release版本不能进行调试
最常用的几个快捷键:
F5:启用调试后,经常用来直接调到下一个断点处
F9:创建和取消断电的作用,可以在程序的任意位置设置断点,这样就可以使得程序在先想要的位置随意停止执行,继而一步步执行下去
F10:逐过程,通常用来处理一个过程,一个过程可以是一次函数的调用,或者一条语句
F11:逐语句,就是每一次执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最常用的)
CTRL+F5:开始执行不调试,如果你想让程序运行起来而不调试就可以直接使用
断点:就是程序执行到这个位置,就断开,程序停止了
介绍一个死循环小题目:
注意:该题目需要在32位系统下允许才可以达到效果
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
输出:hehe的死循环;
原因:
1,函数的 i 和 arr 这些局部变量存储在栈中
2,栈的数据是从低地址位向高地址位读取的。
3,在存储这写局部变量的时候,栈先使用高地址,再使用低地址。
解释:当i存储在地址高位的时候,循环次数足够大而且越界的情况下,arr【i】肯定会路过i的内存地址,那么当(arr + i)== &i 时,arr【i】就是 i 的值,i 的值会被修改为0;这种情况下,下一次循环又会回到arr [ 0 ],自然就会死循环。
其中C语言并未规定变量间需要空几个空格
//VC6.0环境--空0个整形
//gcc环境--空1个整形
//VS22013-2019环境--空2个整形
值得注意的是:当在64位的系统中,我发现 局部变量会先存储在低位再存储在高位,这时,当先声明 arr 再声明 i 便又会进入死循环。 同时,间隔的整形个数也不一样,32位间隔两个,64位需要间隔五个。
示例:64位系统进入死循环的办法:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
//修改i 和arr 的顺序
for (i = 0; i <= 15; i++) //修改循环次数
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
这时候,我们不选择调试版本,选择release,无论怎样都不会进入死循环。
//release进行了优化,不会出现死循环
//在release版本下,不论i创建在arr之前还是之后,地址都会比数组首元素地址低,这样便不会出现死循环
const修饰不同位置的作用
1,
int num1 = 11;
const int* p1 = &num1; //表示的是p1指向的对象不能被修改,不是指针p1不能被修改
p1 = #
2,
int num2 = 12;
int const* p2 = &num2; //表示的是p2指向的对象不能被修改,不是指针p2不能被修改
这时我们发现1和2表达的方式不同,但是效果是一致的
int const* p2 = &num2; 与 const int * p2 = &num2; 是等价的
3,
int num3 = 13;
int* const p3 = &num3; //表示的是p3不能被修改。
从3可知,当const修饰指针变量的时候,这个指针才不能被修改
结论:
const修饰指针变量的时候,放在*左边,修饰的是*p,表示指针指向的内容,是不能通过指针来改变的
const修饰指针变量的时候,放在*右边,修饰的是p,表示指针自己本身,不能改变所指向的内容
一个很有意思的现象。
const只能防止我们通过const修饰的这个参数去改变数据的值,但是并不妨碍我们通过其他方式修改
例如:
1,
const int i = 0;
int *pi = &i;
这时,我们可以通过指针pi来改变i的值。
2,
就算pi也被const了,我们也可以通过其他指针来改变。
const int i = 0;
const int *pi = &i;
int *ai= &i;
我们仍然可以通过指针ai来改变。
把指针赋值给指针
int main()
{
int a = 0;
int* pa = &a;
int* pb = pa; //可以直接赋值
printf("%p \n", pa);
printf("%p \n", pb);
return 0;
}
数据分类
整形家族
char short int long 这些都有有符号数和无符号数
浮点型家族:
float double
构造类型--自定义类型(结构体属于自定义)
struct array enum(枚举) union(联合体)
指针类型
空类型
void
5,数据的存储
大端小端介绍:
大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中;
示例:写一个程序判断大小端
int main()
{
int a = 1;
char* p = &a;
if (*p == 1)
{
printf("小段\n");
}
else
{
printf("大段\n");
}
return 0;
}
浮点数的存储
int main()
{
int n = 9; //以整形的视角存储数据
float* pfloat = (float*)&n; //把n的地址赋值给*float
printf("n的值为:%d\n", n); //以整形的视角读取
printf("*pFloat的值为:%f\n", *pfloat); //以浮点型的视角读取
//printf("*pFloat的值为:%f\n", n); //从n以浮点型视角读取 //这个数据没有意义,浮点型方式读取整形都是0;
*pfloat = 9.0; //以浮点型的视角存储数据
printf("n的值为:%d\n", n); //以整形视角读取
printf("*pFloat的值为:%f\n", *pfloat); //以浮点型视角读取
//printf("*pFloat的值为:%f\n",n);
int a = 10;
printf("\n");
printf("a的值为:%d\n", a);
printf("a的值为:%f\n", a);
return 0;
}
浮点数在内存中的存储
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S*M*2^E
(-1)^S表示符号位,当S=0,V为正数;当S = 1,V为负数
M表示有效数字,大于等于1,小于2.
2^E表示指数位
浮点数:5.5--10进制
二进制:101.1-->1.011*2^2 -->(-1)^0 * 1.011 * 2^2
对于32位浮点数,float类型:S占1位;E占8位;M占23位(bit)
对于64位浮点数,double类型:S占1位;E占11位;M占52位(bit)
重点:
1,M的取值位1<=M<2,所以第一位必然是1,1.xxx中的1不保存,只保存xxx,这样还能多存一位,提高精度
2,E是科学计数法,存在正负数,但是E的类型规定为无符号数,所以,直接在E的实际数字上+127,防止出现附属;比如,E的值位-1,那么E存储的时候就是126.
比如,f = 5.5f;
1.001 * 2^ 2
s = 0; M = 1.011 ; E =2;
s = 0; M =011; E = 2+127 = 129
0100 0000 1011 0000 0000 0000 0000 0000
40 b0 00 00
对于取出问题:E全为0.表示无穷小;E全为1,表示无穷大;
所以,9作为整数存入的时候,但是作为浮点数读取时,S和E==0;
9.0作为浮点数存入的时候,作为整数读取的时候,S和E的位置都有数字,所以数字很大