本人关于自学浮点型数据存储与指针进阶课程的笔记

众所周知,指针是C语言学习过程中的一个大BOSS,一般而言,学会了指针,C的水平就相当于上了一个大台阶,接下来,我会把每天关于指针的学习笔记记录在下面,以便我自己不时的复习之用,由于我上的课程把浮点型数据的存储和指针进阶课程放在了一起,所以我就把浮点型的相关知识也记录在下面:
首先让我们先来聊一聊存储
1.什么是大端和小端
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
int a=20;
//00000000000000000000000000010100
//0x00 00 00 14 这是数据的补码,如果为小端保存,则在内存中为14 00 00 00
如果为大端保存,则为00 00 00 14
具体的检验代码为
int main()
{
int a = 1;//在int中的16进制是00000001
char*p = (char *)&a;//在char中的16进制是取前一个字节
if (*p == 1)
{
printf(“小端\n”);//01 00 00 00

}
else 
{
	printf("大端\n");//00 00 00 01
}

return 0;

}
2.
//输出什么?
int main()
{
char a = -1;
//10000000000000000000000000000001源码
//1111111111111111111111111111111111110反码
//1111111111111111111111111111111111111补码
//11111111在char类型下
signed char b = -1;
//11111111
unsigned char c = -1;//unsigned就是表示无符号数,则符号位的1,也计算在内
//11111111
printf(“a=%d,b=%d,c=%d”, a, b, c);
return 0;
}
答案是a=-1,b=-1,c=255
3.int main()
{
char a=-128;
//在内存中把char的-128规定为10000000
//10000000000000000000000010000000
//1111111111111111111111111111011111111
//111111111111111111111111111110000000----补码
//则在char中为1000000000000000–>0
//%d-----打印十进制的有符号数 00000001–>1
//%u-----打印十进制的无符号数 00000010–>2
printf("%u\n",a); 00000011–>3
return 0; …
} 01111111–>127
结果是4294967168 10000000–>-128
由于用的是%u, 10000001–>-127
所以是无符号 的整形提升 …
11111101–>-3
11111110–>-2
11111111–>-1
//则char 的范围是
127~-128
4.浮点型在内存中的存储
3.141591E10浮点数家族包括:float ,double,long double类型。浮点数表示的范围:float.h中定义
例子
int main()

{
int n = 9;
float *pfloat = (float *)&n;
printf(“n的值%d\n”, n);
printf("*pfloat的值%f\n", *pfloat);
*pfloat = 9.0;
printf(“n的值%d\n”, n);
printf("*float的值%f\n", *pfloat);
return 0;
}

结果为
n的值9
pfloat的值0.000000
n的值1091567616
float的值9.000000
下面我来解释一下
根据国家标准IEEE(电气和电子工程协会)754,任意一个两点制浮点字符V可以表示成下面的形式
(-1)^ S
M
2^E
(-1)^S表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于等于2
2^E表示指数位。
举例来说:十进制的5.0,写成二进制是101.0,相当于1.012^2,那么,根据上面V的格式,可以得出s=0.m=1.01;e=2
十进制的-5.0,写成二进制是-101.0,相当于-1.01
2^2.那么s=1,m=1.10,e=2
IEEE 754规定:对于32位的浮点数,最高的一位是符号位s,接着的8位是指数e,剩下的23位为有效数字m
对于64位的浮点数,最高的一位是符号位s,接着的11位是指数e,剩下的52位为有效数字m
IEEE 754对有效数字m和指数e,还有一些特别规定。
前面说过,1<=m<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的xxxxxx部分,比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去,这样做的目的,是节省1位有效数字。以32位浮点数为例,留给m的只有23位,将第一位舍去之后,等于可以保留24位有效数字。
至于指数E,情况就比较复杂。
首先,e为一个无符号数(unsigned int)这意味着,如果e为8位,他的取值范围为0~255,如果e为11位,它的取值范围为0 ~2047.但是,我们知道,科学计数法中是可以出现复数的,所以IEEE754规定,存入内存时e的真实值必须在加上一个中间数,对于8位的e,这个中间数就是127;对于11位的e,他的中间数就是1023.比如:
2^10的e是10,所以保存成32位浮点数时,必须保存为10+127=137,即10001001
然后,指数e从内存中取出还可以再分成三种情况:
1,e不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数e的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1.比如:0.5(1/2)的二进制形式为0.1,由于规定正数部分必定为1,即将小数点后移1位,则为1.0 * 2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位
00000000000000000000000
则其二进制表示形式是
0 01111110 00000000000000000000000
2,e全为0
这时,浮点数的指数e等于1—127(或者1—1023)即为真实值,有效数字M不在加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
//0 00000000 01100000000000000000000
//+/- 0.011 * 2^-126
//
3,e全为1
这时,如果有效数字m全为0,表示±无穷大(正负取决于符号位s)

好了,关于浮点数的表示规则,就到这里吧
一个例子
int main()
{
float f=5.5;
//5.5
//101.1
//(-1)^0 * 1.011 * 2^2
//s=0
//m=1.011
//e=2
//0 10000001 01100000000000000000000
//0x40b0000
//(-1)^0 * 1.011 * 2^2
//

return 0;

}
现在再去做一下前面的练习,你会有收获的
5.指针数组
指针数组是一个存放指针的数组
int * arr[10];//整形指针的数组
char *arr2[4]; //一级字符指针的数组
char *arr3[5];//二级字符指针的数组
应用例子
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int
parr[] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", (parr[i] + j));
}
printf("\n");
}
return 0;//逐行打出arr1,arr2,arr3
}//这只是一个例子,一般指针数组不会这么用
6.数组指针
//int p=NULL;//p是整形指针-指向整型的指针-可以存放整形的地址
//char
pc=NULL;//pc是字符指针-指向整型的指针-可以存放字符的地址
//以此类推,数组指针-指向数组的指针-可以存放数组的地址
int arr[10]={0};
//arr-首元素地址
//&arr[0]-首元素的地址
//&arr-数组的地址
//
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int(p)[10]=&arr;//数组的地址要存起来
//上面的p就是数组指针
return 0;
int main()
{
char
arr[5];
char
(pa)[5]=&arr;
return 0;
}
//char 表示pa指向的数组的元素类型是char
//pa是指针变量的名字
//第二个
说明了pa的类型是指针
//[5]说明Pa指向的数组是5个元素的
那么数组指针是怎么使用的呢?
既然数组指针指向的是数组,那么数组指针中存放的应该是数组的地址。
看代码:
#include<stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,0};
int(*p)[10]=&arr;//把数组arr的地址赋值给数组指针变量P,
//但是我们很少这样写代码

return 0;
}
一个数组指针的使用
void print1(int arr[3][5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
{
printf("%d “, arr[i][j]);
}
printf(”\n");
}

}//这是不用指针时的函数
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
print1(arr, 3, 5);//arr-数组名-就是首元素地址
return 0;
}
//
//
//
void print1(int(p)[5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d", ((p + i) + j));
}
printf("\n");
}
}
//这里我们用了数组指针
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
print1(arr, 3, 5);
return 0;
}
//
//
//
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int i=0;
int
p=arr;
for(i=0;i<10;i++)
{
printf("%d",p[i]);
printf("%d",(p+i));
printf("%d",
(arr+i));
printf("%d",arr[i]);
}
注意arr[i]*(arr+i)*(p+i)==p[i]
//arr-数组名-就是首元素地址
//所以这里输出的四个值是相同的

int arr[5];//arr是一个有5个元素的整形数组
intparr1[10];//parr1是一个数组,数组有十个元素,每个元素的类型是int,parr1是指针数组
int(*parr2)[10];//parr2是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型是int,parr2是数组指针

int(* parr3[10])[5];//parr3是一个数组,该数组有10个元素,每个元素的类型是数组指针,该数组指针指向的数组有5个元素,每个元素的类型是int
思考
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行有多少个元素。
//这样才方便运算。
void text(int arr)//ok?
{}
void text(int
arr[5])//ok?
{}
void text(int (arr)[5])//ok?
{}
int main()
{
int arr[3][5] ={0};
text(arr);
return 0;
}
//
//
7.函数指针
//函数指针-是指向函数的指针
在这里插入图片描述
主要的用法如下
void Print(char
str)
{
printf("%s\n",str):
}
int main()
{
void(p)(char)=Print;
(p)(“hello bit”);
return 0;
}
例子
代码1:(
( void()() )0 )();
//把0强制转换成:void(
)()函数指针类型-0就是一个函数的地址
//调用0地址处的该函数
《C陷阱和缺陷》
代码2:
void(* signal(int,void(*)(int)) )(int);
//signal是一个函数声明
//signal函数的参数有两个,第一个是int。第二个是函数指针,该函数指针指向的函数的参数类型是int,返回类型是void
//signal函数的返回类型也是一个函数指针,该函数指针指向的函数的参数是int,返回类型是void

这里我们尝试用typedef 来简化
typedef void(*pfui_t)(int);
pfui_t signal(int,pfui_t);
//typedef unsigned int uint;//typedef可以给复杂的类型名进行自定义,从而使代码可读性更高
int(*pa)(int,int)=Add;
printf("%d\n",pa(2,3));
printf("%d\n",Add(2,3));
printf("%d\n",(*pa)(2,3));
//Add为一个加法函数
//这三行代码最后输出的值相同

//
另一个例子
int Sub(int x, int y)
{
return x - y;

}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
//需要一个数组,这个数组可以存放4个函数的地址
int(*pa)(int, int) = Add;
int(*parr[4])(int, int) = { Add,Sub,Mul,Div };//函数指针的数组
int i = 0;
printf("%d\n", pa(2, 3));
for (i = 0; i < 4; i++)
{
printf("%d\n",parri );
}
return 0;

}
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_55513356/article/details/115281529