目录
数据类型介绍
1. 整形家族
//字符存储的时候,存储的是ASCII值,是整型
//char 默认是unsigned char还是signed char标准没有规定,其他类型都默认是signed
char,unsigned char,signed char
short,unsigned short
int,unsigned int
long,unsigned long
关于char的取值范围
signed char
unsigned char
2. 浮点数家族
float
double
long double
3. 构造类型(自定义类型)
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
4. 指针类型
int* pi;
char* pc;
float* pf;
void* pv;
结构体的指针
...
5. 空类型
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。
整形在内存中的存储
1. 原码、反码、补码
1. 计算机中的整数有三种2进制表示方法,即原码、反码和补码。
2. 三种表示方法均有符号位和数值位两部分,最高位是符号位,符号位都是用0表示“正”,用1表示“负”。
3. 正整数的原、反、补码都相同。
4. 负整数的三种表示方法各不相同,
原码:直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
下面是原反补例子
int num = 10; //创建一个变量叫num,num向内存申请4个字节来存放数据 //4个字节是32个比特位 //00000000 00000000 00000000 00001010 原码 //00000000 00000000 00000000 00001010 反码 //00000000 00000000 00000000 00001010 补码 int num2 = -10; //10000000 00000000 00000000 00001010 原码 //11111111 11111111 11111111 11110101 反码 //11111111 11111111 11111111 11110110 补码
对于整形来说:数据存放内存中其实存放的是补码。为什么呢?
在计算机系统中,数值一律用补码来表示和存储。
原因在于使用补码可以将符号位和数值位统一处理,
同时,加法和减法也可以统一处理(CPU只有加法器)
此外,补码与原码相互转换,其运算过程 是相同的,不需要额外的硬件电路。
·
一个例子讲述为什么要补码,因为有些东西原码算不了
计算1 - 1 也就是1 + (-1)
原码计算
00000000 00000000 00000000 00000001 1的原码
10000000 00000000 00000000 00000001 -1的原码
10000000 00000000 00000000 00000010 相加后等于-2
·
下面是补码计算
00000000 00000000 00000000 00000001 1的补码
11111111 11111111 11111111 11111111 -1的补码
00000000 00000000 00000000 00000000 相加后的等于0
2. 大小端介绍
什么是大端小端?
大端存储模式,是指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中;
小端存储模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
为什么有大端和小端?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
设计一个函数来判断当前机器的字节序。
//思路:利用char*只访问第一个字节来判断差异 //只能输入1,返回1是小端,0是大端 int check(int a) { return *(char*)&a; } int main() { if (check(1)) printf("small\n"); // 01 00 00 00 else printf("big\n"); //00 00 00 01 return 0; }
3. 练习
1.
//输出什么? #include <stdio.h> int main() { signed char b = -1; unsigned char c = -1; printf("b=%d,c=%d",b,c); return 0; } //10000000 00000000 00000000 00000001 -1的原码,然后要转补码 //11111111 11111111 11111111 11111111 -1的补码 //11111111 补码存进char里,从低位开始截断 //b, c存放的都是11111111,%d是十进制形式打印有符号整型,所以需要整型提升 //11111111 11111111 11111111 11111111 b有符号类型符号位补齐, 然后转原码得-1 //00000000 00000000 00000000 11111111 c无符号类型高位补0,正数原反补相同
2.
#include <stdio.h> int main() { char a = -128; printf("%u\n",a); return 0; } //首先算出a的原码 //10000000 00000000 00000000 10000000 //将原码转成补码 //11111111 11111111 11111111 10000000 //存入char所以发生截断 //10000000 //%u - 按无符号整型打印,所以需要整型提升 //因为a的数据类型有符号所以符号位补齐 //因为%u, 所以直接当正数,原反补相同 //11111111 11111111 11111111 10000000
3.
#include <stdio.h> int main() { char a = 128; printf("%u\n",a); return 0; } //一般的步骤,1.算出原码,2.转成补码,3.截断,4.整型提升,5.按要求打印 //00000000 00000000 00000000 10000000 原码, 补码 //10000000 截断 //11111111 11111111 11111111 10000000 整型提升,因为类型是char所以符号位补齐
4.
int i = -20; unsigned int j = 10; printf("%d\n", i+j); //11111111 11111111 11111111 11101100 i的补码 //00000000 00000000 00000000 00001010 j的补码 //11111111 11111111 11111111 11110110 i+j 补码相加, 然后转原码打印
5.
unsigned int i; for(i = 9; i >= 0; i--) { printf("%u\n",i); } //unsigned int 导致i不会小于0所以死循环
6.
int main() { char a[1000]; int i; for(i=0; i<1000; i++) { a[i] = -1-i; } printf("%d",strlen(a)); return 0; } //解析: //1.这个for循环1000次,i从0到999 //2.可以得出a[i]等于-1,-2,-3,... //3.又因为每个元素是char类型,char的范围是-128到127 //4.所以真实存放的样子,-1,-2,-3,... -128,127,126,... 3 2 1 0 //5.strlen统计\0之前的字符个数,\0的ASCII值是0
7.
#include <stdio.h> unsigned char i = 0; int main() { for(i = 0;i<=255;i++) { printf("hello world\n"); } return 0; } //死循环,因为unsigned char范围就是0到255
浮点型在内存中的存储
1. 浮点数存储规则
根据国际标准,任意一个二进制浮点数 V 可以表示成这个形式:(-1)^S * M * 2^E
1. (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
2. M表示有效数字,大于等于1,小于2。
3. 2^E表示指数位。
例子
IEEE 754 规定:
对于32位的浮点数(float),最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数(double),最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
IEEE 754 对有效数字M有一些特别规定。
1. 前面说过,1≤M<2,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
2. 默认M的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。
3. 比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。
4. 这样做的目的是节省1位有效数字。
·
IEEE 754 对指数E有一些特别规定。
1. 首先,E是一个无符号整数(unsigned int)
2. 这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围0~2047。
3. 但是,科学计数法中的E是可以出现负数的
4. 所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
5. 。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即 10001001。
·
然后,指数E从内存中取出还可以再分成三种情况:
1. E不全为0或不全为1,这时,指数E的计算值减去127(或1023)得到真实值,再将有效数字M前加上第一位的1。
2. E全为0,这时指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1。
3. E全为1,这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
2. 一个浮点数存储的例子
//输出什么? int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); //9 printf("*pFloat的值为:%f\n",*pFloat); //0.000000 *pFloat = 9.0; printf("n的值为:%d\n",n); //1091567616 printf("*pFloat的值为:%f\n",*pFloat); //9.000000 return 0; } //算出9的二进制序列 //00000000 00000000 00000000 00001001 //当作浮点数存储就变成 //0 00000000 00000000000000000001001 //S E M //算出9.0二进制序列 //1001.0 //转化 //(-1)^0 * 1.001 * 2^3 //所以S = 0, M = 1.001, E = 3 //0 10000010 01100000000000000000000 //S E M
练习题
答:D
答:C
解析:1.补码,2.整型提升,3.截断
答:B
答:A
//思路:1.先用5层循环把所有情况列一遍 //2.说对了一半意味着一个为真一个为假,1+0=1 int main() { for (int a = 1; a <= 5; a++) { for (int b = 1; b <= 5; b++) { for (int c = 1; c <= 5; c++) { for (int d = 1; d <= 5; d++) { for (int e = 1; e <= 5; e++) { if ((b == 2) + (a == 3) == 1 && (b == 2) + (e == 4) == 1 && (c == 1) + (d == 2) == 1 && (c == 5) + (d == 3) == 1 && (e == 4) + (a == 1) == 1 && a*b*c*d*e == 120) { printf("a:%d b:%d c:%d d:%d e:%d\n", a, b, c, d, e); } } } } } } return 0; }
int main() { //因为凶手只有一个,所以就一次假设一个凶手进行遍历 for (char killer = 'a'; killer <= 'd'; killer++) { if ((killer!='a') + (killer=='c') + (killer=='d') + (killer!='d') == 3) { printf("%c\n", killer); break; } } }
int main() { int n; scanf("%d", &n); int arr[10][10] = { 0 }; //遍历赋值 for (int i = 0; i < n; i++) { for (int j = 0; j <= i; j++) { //第一列和对角线都是1 if (j == 0 || i == j) arr[i][j] = 1; else arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1]; } } //遍历输出 for (int i = 0; i < n; i++) { for (int j = 0; j <= i; j++) printf("%d ", arr[i][j]); printf("\n"); } return 0; }