#include
//预定义 定义符号常量
#define NAME "fyf"
#define YEAR 1987
#define URL "https://www.qq.com"
/*
* 标识符identifier: 用于给变量,常量,函数,语句块等命名
*
* 常量:constant
* 整数常量:250 , 12345
* 实数常量:1.23 , 35.99 , 0.1 (float和double)
* 字符常量:
* -普通字符:‘f’ , 'y' , 'F'
* -转义字符:'/n', '/t', '/b'
* 字符串常量:"FYFfyf"
*
* 定义符号常量:
* 格式:#define 标识符 常量(宏定义)
* 为与变量区分,符号常量通常用大写字母表示,非硬性规定
* #define YEAR 1987
* #define NAME "fyf"
*/
/*
* 数据类型:
* 1.基本类型:
* a.整数类型 int
* b.浮点数类新 float double
* c.字符类型 char
* d.布尔类型 _Bool
* e.枚举类型 enum
* 2.指针类型
* 3.构造类型:
* a.数组类型
* b.结构类型
* c.联合类型
* 4.空类型
*
* 限定符:
* 整数类型:short int , int , long int , long long int
* short int <= int <=long int <=long long int
*
* 浮点数类型:float , double , long double
* 布尔类型:_Bool
* 枚举类型:enum
*/
int main(void)
{
printf("Name: %s,Year: %d年\n", NAME, YEAR);
// \n 输出换行 \ 同一行代码分行
/* \n 输出换行 \ 同一行代码分行 */
// printf print format
printf("Hello World!\n\
++++++\n\
+\n\
+\n\
++++++\n\
+\n\
+\n\
+\n\
+\n");
// 定义变量 various variable
// 数据类型 变量名
int a;
char b;
float c;
double d;
// 变量 = 常量 (变量可改变,常量不可改变)
a = 250;
b = 'F';
c = 3.14;
d = 3.141592653;
// %d %c %f 转换说明
printf("This is integer: %d\n" ,a);
printf("This is charactor: %c\n" ,b);
printf("This is float: %.2f\n" ,c);
printf("This is double: %10.10f\n" ,d);
// sizeof()运算符:返回变量的大小
// 用法:sizeof(object对象) sizeof(type_name类型) sizeof object对象
printf("The size of short int:%d\n", sizeof(short int));
printf("The size of int:%d\n", sizeof(int));
printf("The size of long int:%d\n", sizeof(long int));
printf("The size of long long int:%d\n", sizeof(long long int));
printf("The size of float:%d\n", sizeof(float));
printf("The size of double:%d\n", sizeof(double));
printf("The size of long double:%d\n", sizeof(long double));
printf("The size of char:%d\n", sizeof(char));
printf("The size of _Bool:%d\n", sizeof(_Bool));
printf("The size of a:%d\n", sizeof(a));
printf("The size of b:%d\n", sizeof(b));
return 0;
}
#include
void jolly(void);
void deny(void);
void nation1(void);
void nation2(void);
int main(void)
{
int dogs;
printf("How many dogs do you have?\n");
scanf("%d", &dogs);
printf("So you have %d dog(s)!\n", dogs);
printf("Dogs\n/nDogs\n");
jolly();
deny();
nation1();
printf(", ");
nation2();
printf("\n");
nation1();
printf(",\n");
nation2();
printf("\n");
return 0;
}
void jolly(void)
{
printf("Welcome! Jolly.\n");
}
void deny(void)
{
printf("Welcome! deny.\n");
}
void nation1(void)
{
printf("A, B");
}
void nation2(void)
{
printf("C, D");
}
#include
/*
*A.十进制(十进制计数法): 由是十个基数0,1,2,3,4,5,6,7,8,9表示,满十进一
*个人理解: 数位1[十个基数(符号)最大只能表示九,然后9+1,数位1已经没有符号表示十了,假设数位1上有十根辣条,数位1已经没有符号表示十根
*辣条了,然后把十根辣条捆成一捆,丢到数位2(1进位到数位2,进位2的1(一捆辣条)表示十根辣条,1*10(读成1个10更好理解),10为数位2的位权),
*所以数位2变成1,数位1变成0(因为十根辣条已经全部放到数位2了,所以数位1就一根辣条也没有了),所以10(读作一零更好理解)表示十]
*
* 二进制跟上面的十进制概念是一样的,只是基数变成0,1。满二进一
* 二进制转换成其他进制:
* (1111)2=1*2^3+1*2^2+1*2^1+1*2^0=8+4+2+1= (15)10十进制 (e)16十六进制 (17)8八进制
* (平时都是用十进制,不理解十进制的定义,思维固化,虽知道转换方法,但不懂其中的原理,时间一久就忘了,脑子转不过弯,知其然不知其所以然... --`)
* 1111 1111 = (255)10 [0000 0000,1111 1111]=[0,255]区间有256个符号表示0~255
* 1 0000 0000 = (256)10 [0000 0000,1 0000 0000]=[0,256]区间有257个符号表示0~256
* 位权:因为平时都习惯用十进制,所以位权就用十进制表示吧,换成其实进制脑子不够用(要不人类发明计算机干嘛呢~),习惯真可怕。。。
* 位权通俗来说就是每一数位的1表示多少根辣条(平时用开十进制,梗系用十进制表示位权啦,啊!!!十进制已经灌进了我脑中无法磨灭了)
*
* B.机器数的真值(机器数的形式值:看了定义感觉跟无符号数一样)
* 机器数:一个数在计算机中的二进制表示,机器数是带符号的
* 例如: 1000 0001的真值=-000 0001=(-1)10 1000 0001的形式值=(129)10
*
*
* C.二进制代码-->数据类型的本质就是分配存储空间长度-->转换说明才决定输出的数据字面量(常量)
*整数常量integer constants(C语言中字符常量character constants也被当作整数常量)
*在声明(declaration)变量时,整数常量(由上可知当然也包含字符常量,字符常量本质上在计算机存储二进制代码中算是整数常量的子集,)
*赋值给变量,如果该整数字面量(整数常量)大于或小于该数据类型所代表的整数字面量的区间,编译器编译时会报错,
*但非声明变量时不会报错,会出现整数溢出,得不到正确结果.
*
*以8bits有符号数为例,除去一个符号位,数值位共7位,模1000 0000,模mod为2^7。
*以十进制数为例,个位的模为10^1,十位的模位10^2,以此类推. 10^n/10^m=10^(n-m) 10^1/10^1=10^(1-1)=10^0=1
*以十进制为例,数学公式(同余式-通俗说就是余数相同):-3=7(mod10) mod10表示模是10, 7是(-3)对10的补数
*/
/*
位置计数系统小数的本质:
(0.1)10 = 1/10 一根辣条变10份,十分之一根辣条
= 1/10^1
= 1*10^0/10^1
= 1*10^(0-1)
= 1*10^-1
= 10^-1
10的0次方为什么等于1:
10^0 = 10/10 十根辣条除于十,变一根辣条
= 10^1/10^1
= 10^(1-1)
= 10^0
= 1
10的2次方除于10的1次方:
10^2/10^1 = 100/10 = 100 * 1/10 100根辣条,分成十份,只剩一份了
= 10
= 10^1
= 10^(2-1)
小数点向左移一位和向右移一位的本质:
(11.11)2 ---小数点左移一位---> (1.111)2
本质:(这里用十进制表述),从最高位起,两根辣条变一根,一根辣条变1/2根,1/2根变1/4根
(11.11)2 ---小数点右移一位---> (111.1)2
本质:跟上面道理一样,只不过刚好相反,2根辣条变4跟,以此类推......
*/
// 十六进制与二进制的关系:
// x[1] x[0] 一位x取值范围[0,15] --> [0,f]
// d[7] d[6] d[5] d[4] d[3] d[2] d[1] d[0] 四位d取值范围[0,15] --> [0000,1111]
// ( 5 f )16
// (0101 1111)2
// 四位二进制数 对应 一位十六进制数
//*理解的关键,满十六进位(把二进制的四位理解为十六进制中的一位就行了),这句才是理解重点,
//本质就是满十六进一,也是位置计数系统的本质啊
// 十六进制的一位用 0~f十六个符号表示,相当于用四位二进制 0000~1111十六种编码方式分别表示十六进制中的 0~f
// 八进制与二进制的关系原理也是一样的
//
int main(void)
{
//*tips:C语言将字符常量视为int数据类型而非char数据类型
//char字符类型本质上是整数类型,
//ascii码和unicode码都是用整数来表示字符和符号
//ascii码一个byte字节就能储存完(字母大小写和标点符号)
//ascii码中'A' 对应整数65
char c1 = 'A';
char c2 = 65;
char c3 = 255;
//int -128二进制:1000 0000 0000 0000 0000 0000 1000 0000 -128很特殊,它没有原码反码,只有补码,具体查看补码的定义
// 补码:1000 0000 0000 0000 0000 0000 1000 0000
//int -1二进制:1000 0000 0000 0000 0000 0000 0000 0001
// 补码:1111 1111 1111 1111 1111 1111 1111 1111
//-128-1 运算: 1(1111 1111 1111 1111 1111 1111 0111 1111) 结果为负数,补码与原码不一样,所以要得到正确结果还要转成原码
// 原码: 1000 0000 0000 0000 0000 0000 1000 0001 原码转换成十进制结果,得到-129
// 1*2^7+1*2^0=129 2^(n-1)为二进制中第n位的位权
//char c4 = -129; 编译时报错,溢出
//char c4 = 256; 编译时报错,溢出
//char c4 = -256; 编译时报错,溢出
short int hi1 = -32768;
int i1 = 'A';
int i2 = 65;
//非打印字符 P134:
//1.直接ascii码赋值(转换成int数据类型赋值)
char beep = 7;
//2.转义序列(escape sequence)
char newline = '\n';
short int int_newline = '\t';
/* 个人理解:字符常量虽被C语言视为int类型(整数常量),但声明时(declaration)给变量(除布尔型)赋值没有溢出overflow错误
(因为ascii码不超过8bit,视为int类型前面的bit全部填充为0,整数字面量在[-128,255]区间),
int数据类型声明变量赋值时,如果超过int类型的数值范围编译器会提示overflow出错
运算后溢出编译器不会提示错误,但得到的结果并不正确(整数溢出) */
short int a = 100,sum;
//布尔类型:true,false 1,0 占1bit存储空间
_Bool b;
//运算结果超出了声明时分配给变量sum的存储长度,整数溢出
sum = a + 1000000;
printf("sum:%hd\n", sum);
printf("char size:%dbyte\n", sizeof(c1));
printf("int size:%dbyte\n", sizeof(i1));
printf("character c1='A' 转换说明i-->%d,character c2=65 转换说明c-->%c\n", c1, c2);
printf("integer i1='A' 转换说明i-->%d,integer i2=65 转换说明c-->%c\n", i1, i2);
//P135 有需要就到转义序列表查看即可
/*常用 转义序列: \t \b \n \r \' \" \\ 非打印字符*/
printf("转义序列例如: '\\\\' '\\\"' '\\t',转义序列(非打印字符)\\n效果:%c", newline);
printf("字符变量character variable size:%d\n", sizeof(newline));//字符变量占一个字节
printf("字符常量character constants size:%d\n", sizeof('\n')); //得出tips的结论,字符常量占4个字节
printf("-128 C compiler当作int数据类型,size:%d\n", sizeof(-129));
printf("%c\n", c3);
printf("%hx,%hx,%x\n", -1,-128,-128-1);
printf("%hx\n", hi1);
printf("布尔数据类型_Bool variable b size:%d\n", sizeof(b));
return 0;
}
#include
//c库中的文件定义了一组常量,用来限定编译器complier运行的这台机器的整型数据类型的取值范围
#include
//预备知识:
//+1元:我有1元钱; -1元:我欠某人1块钱,也可以说我有-1元钱...
//1-2= 怎么得到结果-1
//1-2 = -2+1 = -1*2 + -1*-1 = -1*[2+(-1)] = -1*(2-1) = -1*1 = -1 (一个-1)
//-----------------------------------------------------------------------------------
//上面第三步还有一个知识点: 1 = -1*-1 (负负得正)
//那负数乘负数为什么得正呢。。。
//2-2 = 2+(-2) = 0
//[2+(-2)] * (-3) = 0 * (-3)
//2*(-3) + (-2)*(-3) = 0 乘法分配律
//(-6) + (-2)*(-3) = 0
//两边同时+6 6+(-6) + (-2)*(-3) = 6+0
// (-2)*(-3) = 0 + 6 = 6 由此推到出负负得正的结论
//日常生活中的解释: 我欠甲人3元,欠乙人3元, 我拥有(-3) + (-3) = -6元, 再-(-6),现实生活中(-)要还6元,
//但我现在身上有0元,所以我要先赚6元,所以0+6
//所以要-(-6)要减去负债6元 = +6赚6元
//-----------------------------------------------------------------------------------
//上面第四步还有一个知识点,公因式(commom factor)
// a*c + c*b = c*(a+b) 提取公因式
//-----------------------------------------------------------------------------------
//理解的关键点一:二进制数的原码补码 与 机器数的原码补码 有所不同
//理解的关键点二:平时我们已经习惯十进制的思维,所以用十进制举例来解释二进制的机器码原理,更有助于理解
// mod模为10,0~9十个基数,+表示正数,-表示负数(机器码中0表示正数,1表示负数原理是一样的);
// 取值范围:-10~+9,根据机器码补码规则,用-0表示-10;
// -9是+1对于模10的补数,根据机器码的规则,用-9(相当于机器码中的补码)代表-1(所以符号位不变,数值位求补码)
// 《计算机科学导论第三版》P32 图3-8 有助于具象化理解 关键点二
//
//二进制数原码和补码是相互的,二进制数原码0000 0001 补码1111 1111;二进制数原码1111 1111 补码0000 0001
//二进制数0000 0001(机器数+1) 补码1111 1111(二进制数+1的补码,机器数-1的补码)
//1000 0001(机器数-1的原码,机器数-1补码转原码,符号位不变)
//-----------------------------------------------------------------------------------
//二进制数运算 0000 0010
//参考:《计算机科学导论第三版》 P34 图3-9 P55 二进制补码加减法 P57 第一段话
//理解机器码补码加减运算关键点一:
// 引入数学概念:模运算
// 模是一个计量器的计数范围
// 计算机的数据位数(字长)是有限的,计算机也可以看成是一个计量器,所以必然是模运算,
// 比如说钟表,它所能表示的整点情况只有十二种,所以它的模是12
// a.在模运算中,假设模为M,一个整数和一个负数互为补数时,两个数的绝对值之和为模
// b.在模运算中,减去一个数等于加上这个数的补数
// 模运算: M=10 8+10=8
// 9+(-6)=3 9+4=3
// 9+(-6)+10=3
// 补数是一种处理有符号数的方法,用于变换数字的符号.在计算机科学应用中被称为"补码"
// 补码的定义: [x补] = M + x
// 1) x >= 0, 模M作为超出部分要被舍去,[x补]=x,因此正数的补码就是其本身
// 2) x < 0, [x补]就是x以M为模的补数
// 一个数的补数等于模加这个补数,再取模求余.
//
//-----------------------------------------------------------------------------------
//理解机器码补码加减运算关键点二:
// 参与运算的每个数和结果要在分配的二进制位的定义范围内,超出了就发生溢出错误了
//理解重点:(以4bit字长为例)本质就是[0000,1111]之间的数进行加法运算(同余定理解决了减法变加法的问题),
// 且结果还在[0000,1111]中, 结果代表的实际数字在设计计算机时规定了,根据规定方法换算得到原码即可,
// 但机器码原码所代表的实际数字(数学意义上的数字)也是设计计算机时人为规定的.
//
// +1111 1111 (-1的二进制数补码,非机器数-1补码)
// 取余位后的结果与(0000 0010 - 0000 0001结果相同,同余式)
// 1111 1111 数学意义上它是0000 0001补码(设计计算机电路时用1111 1111代表机器数-1补码)
// 所以符号位不变,余位取反+1这个转换规则并不是数学意义上的转换,而是人为规定的,实际的二进制数并没有所谓符号位
// 只是在计算机电路中这样转换得到机器数规定的-1补码,计算机中负数都以补码储存
// 十进制表述运算就是: 任何数-1(数学意义上的原码)等于任何数+1(数学意义上补码),取余位结果相同
//1.被减数大于等于减数绝对值,运算后直接得到结果
//2.被减数小于减数,运算后得到结果的补数
//数学运算时负数结果有负数符号,但机器数没有+-符号,而是用最高位表示符号位
//所以结果最高位为0,计算机就判定结果为正,最高位为1则判定结果为负数的,且为补码,得到正确结果还要
//根据机器数的补码转换规则转换成原码(所以才会有符号位不变,余位取反加1这种非数学意义上的补码转换规则。。。)
//-----------------------------------------------------------------------------------
// 补码 用补码代表相应的有符号整数
// 011 3
// 010 2
// 001 1
// 000 0
// 111 -1
// 110 -2
// 101 -3
// 100 -4
// mod模1000,减001等于加它的补码111,先别管它在计算机中所代表的的有符号整数
// 计算机设计中规定的,用001表示+1,第一位符号位0表示正;用001的补码111表示-1,符号位1表示负
//
//再往下深究在计算机中具体怎么实现就要到数字电路了,差不多得了--',以后有时间再深入理解...
//
//===================================================================================
//看来一堆资料后,最具象化,最简单的理解来了!!!不需要公式!!!
//计算机的字长是固定的,可以把计算机看成是一个计量器,
//比如说钟表就是一个计量器,钟表是对时间的计量,钟表上的计量值是有限的,所以进行的是模运算,模为12(这里只考虑整点值)
//再引申到计算机,假设非负整数n为字长,则计算机的模为2^n
//再拿钟表来理解,日常接触的东西,更容易理解,原理都是一样的
//比如说现在指针指向1点(现在是凌晨1点),然后+1(也就是顺时针转1格,顺时针计量值越来越大,所以是加法)
//然后指针指向2, 1+1=2
//也可以时光倒流,回到3个小时前(也就是-3,计量器逆时针转,往计量值越来越小的方向转,当然转回上一个模时计量值会重置为最大值),
//然后指针指向了10,不能理解得话就这样想,凌晨1点,时光倒流3小时,不就是前天10点吗(这是指针是指向计量值10)
//这里转折来了,我们可以把这个钟表计量器改良一下,改成计量值[0,15],相当于n=4,
//用计量值[0,7]代表非负整数[0,7];
//用计量值[8,15]代表负整数[-8,-1];
//(其实你可以用计量值代表任何数字,字母,表情符等,任何东西都行,只要你想得到的,十二生肖也行,这并不影响模和计量值的计算)
//接着上面,1-3,指针指向计量值14,而我们刚刚用计量值14代表负整数-2,所以得到结果-2
//参与运算的数和结果不能超过我们所规定的范围,否则会出现溢出,大于所代表的最大值,则正溢出,小于所代表的最小值则负溢出.
//n=2,取值范围[-2,1],计算机大部分才有补码方式储存数据,像下面那样:
// 00 01 10 11
// 0 +1 -2 -1
//1+1=2,超出取值范围,正溢出,(01)+(1)2=(10)2,计量值10在该例子中表示-2
//-2-1=-3,(10)2-(1)2=(01)2,计量值01代表+1,当然,真实的计算机运算中机器码是以补码相加来进行运算的,计算机只有加法器
//实际上就是模2^n的加减法运算,这个知识点还没看。。。第二天再看。。。准备睡觉
//然而事情并没有这么简单。。。补码的数学原理还要去啃一下《深入理解计算机系统》(原书第三版),呜呜呜~要崩溃了。。。--·
//
//===================================================================================
//《深入理解计算机系统》(原书第三版) 第二章 2.2 整数表示,看完了对计算机数据的存储理解更深入了
//学习还是要找对书啊~
//向量:有大小和方向
//bit vector(位向量)
//∑(求和符号,读音为sigma)
//负权值(负权重,negative weight)
//w=2,补码(二补数two's complement) B2T(11) = 1*(-2^1) + 1*2^0 = -1
//权值(权重weight value): 权值为负怎么理解呢
//附加知识点:十进制也好,二进制也罢,本质都是{计数系统},混合进制计数系统也是可以的,
// 例如,111,数位0,2为十进制,数位1为八进制. 则它们的权值为[1,10,80]
//我个人的理解->有两个人,拥有1块钱的有一人,欠债2块钱的有一人,补码11,数位0的权值为1,数位1的权值为-2,
//所以他们一共有多少钱?有上面B2T(11)函数可得结果
//
//unsigned encodings 无符号数编码
//原码(sign符号 magnitude大小) 补码(two's complement) 反码(one's complement) 都是机器数,
//机器数所对应的真正数值,称真值
//
// binary unsigned sign magnitude one's complement two's complement
// 000 0 0 0 0
// 001 1 1 1 1
// 010 2 2 2 2
// 011 3 3 3 3
// 100 4 -0 -3 -4
// 101 5 -1 -2 -3
// 110 6 -2 -1 -2
// 111 7 -3 -0 -1
//
//*补数加减法核心知识点(武林神功之一啊)
//附注: 10补数,9补数 2补数(two's complement),1补数(one's complement)的概念,知乎收藏夹[数字电路设计-第二讲]
// 理解末端进位,可以判断 被减数和减数 之间的大小
// 十进制:9补数,10补数
// 二进制:1补数,2补数
// 20191227个人理解: 9补数,让每一位值都变成9,比如333,求它的9补数,333+666=999,让每一数位值都为9
// 999再加1,每一位都满10进1,所以333的10补数是667,这就是所谓的10补数
// 需特别注意:0的10补数是0,而不是套公式所得到的值,因为计算机中w位数截断(计算机中这个没错,但从数学上讲,这个有疑问,待确认)
// 5的10补数是5,50的十补数是50
// two's complement 001和111为一对二补数,分别映射有符号整数1和-1
// 20191229 继续啃十补数,十补数的减法运算(二补数原理也是相同的)
// 被减数和减数数位不同时,取补数时位数以位数长的为准
// 设被减数为m,减数为n,w以参与运算的数中数位最长的为准
// A.m,n为正数,减数为负数的话,-(-n)=+n,无讨论意义:(20200101补充)
// m-n ---> m + (10^w - n) = m - n + 10^w
// 1) 如果 m ≥ n, m-n≥0,则减去10^w就可得到正确结果(模除效果也一样,模为10^w)
// 2) 如果 m < n, m-n<0,则再求补数, 10^w - (m - n + 10^w) = -m + n = n-m(这里模除,模为10^w,结果也是还n-m)
// -->加符号 -(n-m)得到正确结果
// 举例: m≥n:
// 010-001 ---> 010-001+1000 = 001 + 1000 = 1001 -->1000就是所谓的溢出部分,减去即可(模除1000结果一致)
// m
// 001-010 ---> 001-010+1000 = -001+1000 = 1000 - 001(得到001的2补数)= 111
// 111求-001这个正确结果: 先求10补数-->001,再加上负号-001。。。OK!打完收工
// B.m为负数,n为正数,因为n为负数的话,-(-n)=+n就没有讨论的意义了,:(20200101补充)
// 举例: -4-2=-6 --> 6+8 = (10-4)+(10-2) = (-4-2)+10+10 = 4 + 10
// 减去10--十补数之和(模除10效果一样)后再求十补数加负号才得到正确结果
// -001-001 --> 111+111=1110 1110-1000(1110 mod 1000)=110 110十补数=010 --加负号-> -010 (十进制-2)
//
// *处理器提供了一套它能支持的运算操作集合,称为指令集.
// 输入程序和数据:1+2 --> 计算机中:001+010=011 -->输出:3
// 输入程序和数据:7+7 --> 计算机中(设w=3):111+111=1(110) -->110-->输出:6 (溢出)
// |--->计算机 数据+指令得到的数据,映射到什么值与它无关
//
// additive inverse:加法逆元(或称相反数),对于任意一个数n,n和其加法逆元之和为加法单位元(即为零)
// 例如: 7的加法逆元为-7,-0.5的加法逆元为0.5
// -(-x)为什么等于x:x+(-x)=0=(-x)+x,-x为x的加法逆元,x也是-x的加法逆元,-(-x)为-x的加法逆元等于x
// x加上它的相反数-x等于0,-x加上它的相反数-(-x)等于0,因此-(-x)=x
// 加法单位元:在加法运算的集合中,加法单元位加上集合内的任何一个数x,和都会等于这个数x.
// 例如: x+0=x=0+x
// 乘法逆元:在实数范围内, x * 1/x =1,x和1/x互为乘法逆元(x不可能为0,因为1/0无意义),乘法单位元为1.
// x * 1/x = 1;
// -x * -1/x = 1;
//
//20191214暂时小结
// (以这个小结的为准,
// 之前的笔记因为个人理解不到位,还有国内课程教材真的不行啊...
// 英文原版>国内翻译>国内教材。。。
// 以看了国外书籍后的理解为准,毕竟这玩意别人才是原创...):
//
// 位向量(bit vector) --(函数function)[映射map]--> 真值 (位向量和真值一一对应,唯一性)
//
// sign magnitude(符号+大小,国内所谓的原码,这翻译不直观):
// 真值encode成sign magnitude--映射-->位向量,反之,位向量=sign magnitude--[映射]-->真值
//
// one's complement(国内译反码现在的机器基本都用two's complement了,这里就不展开了,理解什么是9补数和1补数就行了)
//
// two's complement(二补数,国内译补码):
// 真值encode成two's complement--映射-->位向量,反之,位向量=two's complement--[映射]-->真值
// two's complement 001和111互为二补数,分别映射有符号整数1和-1
//
// two's complemnt的加减法,本质就是位向量的模运算(详情阅读上面模运算的笔记)
// 二补数(补码)的运算结果正确,是位向量模运算后的余数映射到的真值结果与真值运算的结果相同,
// 当然前提是参与运算的数和结果要在补码映射的范围内,不然发生溢出,结果错误
//
//===================================================================================
//20191222 继续啃《深入理解计算机系统第三版》 P60 整数运算
// 无符号数加法(unsigned addition):----------------------------------------------------------start
// 取值范围: 0 <= x,y < 2^w
// w位无符号数加法运算: 0 <= x+y < 2^w + 2^w
// 0 <= x+y < 2 * 2^w
// 0 <= x+y < 2^w+1 其实x+y实际小于2^w -1,因为x+y最大只能到2^w -2
//
// 假设w=3 0 <= x,y < 2^w --> 0 <= x,y < 2^3 --> 0 <= x,y < 8
// 0 <= x+y < 2^3 + 2^3 -1 = 2*2^3 -1 = 2*8-1 --> x,y取最大值, 7+7 < 15
// or = 2^(3+1) -1 = 2^4 -1
// 二进制表示: x_max + y_max =111+111=1110 < 10000(d:2^w+1=2^3+1) -1=1111
//
// *无符号加法的核心知识点,无符号数加法的溢出检测:
// s=x+y >= x,则没有溢出
// 如果结果溢出了,s=x+y-2^w=x+(y-2^w), 由上我们已知y<2^w,所以y-2^w<0
// 所以 s=x+(y-2^w) < x
// 推导出结论: s>x,没有溢出; s
//
// 取模运算: 10 mod 2=0(10对2取模,等于10除于2的余数)
// 05 商
// 2 10 十位的1捆(10根/捆)辣条不够分成两捆
// 10 把十位的1捆拆散成10根放到个位
// 10 10根可以分成两份,5根一份(商)
// 0 余 余数为0
// 1 mod 10(1对10取模)
// 0 商
// 10 1
// 1 余
// +u/w w位无符号数加法(和模2^w,可以被视为一种形式的模运算)
//w(word) s(sum) u(unsigned)/w(word) x,y=[0,2^w -1]
// w=3 s=x+y=[0,2^w+1 -2] s=x +u/w y=x+y mod 2^w(x+y对2^w取模)
// 1(110) ------- (x+y除于2^w的余数)
// 1(101) |
// 1(100) |
// 1(011) map3
// 1(010) |
// 1(001) |
// 1(000)-map1--|
// 111--map2--|--map2-> 111 111
// 110 |--map3-> 110 110
// 101 | 101 101
// 100 | 100 100
// 011 map1 011 011
// 010 | 010 010
// 001 | 001 001
// 000--map0--|-map0&1->000 000
// x+y --映射(这里map short for mapper,map:地图)--> x +u/w y
// s(sum)列中末端进位的数值就是溢出的s
//加法逆元(或称 相反数): 例如, 7的加法逆元是-7
//*unsigned encode无符号整数编码的本质: 一个数的十进制转换成二进制,这个二进制的位表示就是这个数的无符号整数编码
// 十进制数2和3,用2位二进制编码(无符号整数编码)表示,分别是[10],[11]
// 2+3的和等于5,用二进制编码表示是[101],这里机器位数2位,丢弃最高位,我们得到位表示[01],映射十进制值1
// 结果与 binary:101 mod 100 = 01 decimal:5 mod 4 = 1 模除结果一致
// B2U本质就是一个数的二进制转十进制: B2U[111] = 1*2^2 + 1*2^1 + 1*2^0 B2U[100] = 1*2^2 + 0*2^1 + 0*2^0
// B2U[111-100] = 1*2^2 + 1*2^1 + 1*2^0 - (1*2^2 + 0*2^1 + 0*2^0)
// B2U[11] = 1*2^1 + 1*2^0
// |--> bianry: 11
// [辣条,辣条,辣条]的数量--位置计数法--| (两个数字表示的辣条数量都是一致的)
// |--> decimal: 3
//
// 二补数加法(two's complement addition)---------------------------------------------------start
// 预备数学知识: start
// 下面三个指数幂统称整数指数幂
// 正整数指数幂的定义: w个2相乘的积记作2^w,称为2的w次幂
// 负整数指数幂的定义: 负指数幂等于把幂指数变号后所得的幂的倒数 2^-w = 1/2^w
// 零指数幂(底数为非零整数,因为除数不能为0,0作除数毫无意义):
// 2^w/2^w=1 2^w *1/2^w=1 2^w*2^-w =1 2^(w-w)=1 2*2*2/2*2*2=1(除于一次2相当于分母的幂指数减一次1,w-1)
// 2^0=1
//
// 2*2^(w-1)=2^1 * 2^(w-1)
// =2^1+w-1 = 2^w
// 2^w/2^1=2^(w-1) 就是w个2相乘,除于2,不就还剩w-1个2相乘
// =2^w * 1/2^1 1/2^1符合2^-1的定义
// =2^w * 2^-1 =第一行的结果
// *个人见解:为什么把2^-w定义为1/2^w幂指数变号得到的幂的倒数
// 2^3=2*2*2
// 2^3 *2=2^3 *2^1=2*2*2*2=2^4
// 2^3/2^1=2^3 *1/2^1=2*2*2/2=2^2=2^(3-1) -->所以定义 1/2^1=2^-1 (*2^1增加两倍,*2^-1刚好缩小两倍)
// 这样正整数指数幂和负整数指数幂作用刚好相反
// 英文定义感觉更容易理解:
// The power (or exponent) of a number says how many times to use the number in a multiplication(乘法).
// A negative exponent means how many times to divide by the number
//
// 2^w 乘法: 2^a * 2^b-->2^(a+b)
// 除法: 2^a/2^b-->2^(a-b)
// 举例: 2^3 * 2^2 = 2*2*2 * 2*2 = 2^5 = 2^(3+2)
// 2^3 / 2^2 = 2*2*2 / 2*2 =2 8分成4份,1/4四分之一份等于2
// = 2^1 = 2^(3-2)
// 2*2*2 / 2*2 = 2*2*2 * 1/(2*2)
// *数学乘除法的精髓,升华人生的知识点,理解完感觉自己飞升进入新境界了--' ...:
// 除法的本质,分成相同的份数(有存在余数的情况)
// 除法竖式的本质:(没有什么是一根辣条不能解决的,如果不能,那就一捆...)
// 63.5
// 2 127.0 桌子2:1捆辣条(100根/捆) 桌子1:2捆辣条(10根/捆) 桌子0:7根辣条
// 12 1.桌子2:1捆辣条(100根),没法分成两捆了(100根/捆),拆散分成10小捆(10根/捆)放到桌子1,
// 7 2.桌子1:现在有12捆(10根/捆),分成2份,每份6捆
// 6 3.桌子0:7根辣条,分成两份,每份3根,余1根,1根用刀分成10份,放到桌子-1
// 10 4.桌子-1:10份(1份/0.1根),再平均分成两份,变成5份
// 10
// 0
// 乘法的本质,将相同的数加起来的快捷方式,运算结果成为积,从哲学角度讲,乘法是加法由量变到质变的结果
//
//*武林绝学知识点之一,二补数转无符号数 和 无符号数转二补数的本质:
// --> w-1
// B2U(w)[ x 位向量]= ∑ x(i) * 2^i
// i=0
// --> w-2
// B2T(w)[ x 位向量]= x(w-1)*-2^(w-1) + ∑ x(i) * 2^i
// i=0
// -->
// binary(x) two's complement(x) unsigned(x)
// [011] 3 3
// [010] 2 2
// [001] 1 1
// [000] 0 0
// [111] -1 7
// [110] -2 6
// [101] -3 5
// [100] -4 4
// 通过观察上表的规律,B2U和B2T两个函数(function),T2U 和 u2T 的推导:
// 这里的_w是指下标w,表示字长
// |----> = x (x>=0) 这里的x是指二补数的x
// T2U_w(x) -|
// |----> = x + 2^w (x<0)
// 1. x>=0的情况比较直观,直接看B2U和B2T两个函数就可以直接看出,
// 输入相同编码(位向量),在xi=x(w-1)=0时(也就是无符号数编码和二补数编码最高位为0时),
// 函数输出的结果是一样的,所以映射到的真值相等
// 2. x<0的情况,
// 输入相同编码,在xi=x(w-1)=1时,
// B2U B2T 输出结果 只有x(w-1)位的位权是不同的,余下的数位 数值和位权都一致,所以结果也是一致的
// 无符号编码: x(w-1)位位权 2^(w-1)
// 二补数编码: x(w-1)位位权 -2^(w-1)
// 所以T2U(x),只要 -2^(w-1) + 2^w +剩下的位*位权之和 (无符号和二补数的和都一样)
// = -2^(w-1) + 2*2^(w-1) +剩下的位*位权之和
// = -2^(w-1) + 2^(w-1) + 2^(w-1) +剩下的位*位权之和
// = 2^(w-1) +剩下的位*位权之和
// --> -->
// U2T原理跟上面的T2U是一样的,这里就不详细展开了(要详细了解可以看《深入理解计算机系统第三版》P51):
// 1. x(unsigned)<=x(T's_c_max),B2U(w)[x]和B2T(w)[x]映射到的真值x是相等的
// 2. x(unsigned)>x(T's_c_max),理解的关键点:2^(w-1) - 2^(w-1) -2^(w-1)
// =2^(w-1) - 2*2^(w-1)
// =2^(w-1) - 2^w
//
//二补数加法(two's-complement addition)
// w位二进制位置计数系统,有2^w种组合方式,平分成两组,2^w/2=2^w * 2^-1=2^(w-1),
// 一半代表非负数(非负数还要有一个组合代表0),一半代表负数,得到如下取值范围,
// 取值范围: -2^w-1 <= x,y <= 2^w-1 -1
// 它们的和: -2^w-1 + -2^w-1 <= x+y <= 2^w-1 -1 + 2^w-1 -1
//
// 对取模运算(这里暂时主要讨论整数的取模运算)做个小小的了结:
// 注: modulo(模数) division(分裂,除法) modulo division(模除法) division operation(除法运算)
// multiplication(乘法)
// modulo operation(模除,取模操作,取模运算):模除得到的是一个数除以另一个数的余数.
// 给定两个正整数:被除数a和除数n, a modulo n(缩写为a mod n)得到的是使用欧几里德除法(带余除法)时a/n的余数
// 通常情况下a和n都是整数(对实数,浮点数取模也是可以的,这里暂时不讨论),余数r的取值范围[0,n-1],
// a mod 1恒等于0(a/1,除法本质:/1分成1份,肯定等于这个数本身啊,也肯定不存在余数啊,所以恒=0),a mod 0未定义
//
// (110)
// (101)
// (100)
// 011
// 010
// 001
// 000
// 111
// 110
// 101
// 100
// 1(011)
// 1(010)
// 1(001)
// 1(000)
//
//======================================================================================================
//20200103 真值与编码之间的数学原理的个人的一点理解:
//20200105 囧 越写越长了。。。以最新写的为准,越往下越新...
//二补数编码(two's complement encode)
//
// 二补数编码 十进制真值 二进制真值
// w=3 011 --map--> 3 = 011
// 100 --map--> -4 = -100
//
// binary(真值) decimal(真值)
// -100 + -100 = -4 + -4 真值的运算
// = -1000
//
// (1000-100)+(1000-100) = 100 + 100 (二补数+二补数) -->把减法转换成二补数的加法来运算求结果
// = -100 + -100 + 1000 + 1000
// = -1000 + 1000 + 1000 (-1000为 -100 + -100 的正确运算结果)
// = -1000 + 10000
// = 1000 (1000为减法运算正确结果的绝对值的二补数)
// = 000 + 1000
// 1000(减法转换为二补数加法运算后得到的结果)
// --> 1000的二补数是1000 1000 + 1000 = 1 0000(二补数之和)
// --> 再加上负号= -1000,得到减法运算的正确结果
//这是补数运算的数学原理
//
//上面的例子在计算机中的运算
// 直接看上例中这一步: = 000 + 1000 = 1000
// 因为计算机的计算数位位数是有限的,这里假设字长w=3,计算机只保留结果最后3位,相当于截断了结果1000
// 计算机中得到的运算结果是000,相当于1000 mod 1000 = 000(000和0是一样的)
// 所以计算机运算得到的这个例子的结果并不是 真值运算结果的绝对值的二补数,
// 因此最后用B2T函数映射到了一个错误的真值(即所谓的溢出)
// 1000二补数编码被截断了最高位,最高位为负位权,相当于要减去一个数得到运算结果,
// 现在舍去了这一位,运算结果再加上这个数就得到舍去最高位后的映射结果
//
//理解的关键点: 计算机运算的截断相当于数学中的模除(modulo)
// -001的二补数编码就是该数的绝对值的二补数,即001的二补数111
// 减法转二补数加法运算,从运算过程可以看出,结果由三部分组成:
//
// 1) 被减数为非负数(减数为非负数,减去一个负数等于加上这个数,就没讨论意义了):
// 真值运算结果 + 减数的绝对值和其绝对值的二补数之和
//
// 1.1) 如果真值运算结果大于等于0,则直接得到结果(二补数编码中,正整数的编码其实就是其二进制数),
// 因为被计算机运算舍去最高位(截断结果)相当于模除了减数的绝对值和其绝对值的二补数之和
// (计算机中参与运算的两个数数值位数肯定是相同的,位数不足的也会填充到位数相等,
// 二补数之和肯定是会末端进位的)
// 1.2) 如果真值运算结果小于0,则得到结果是这个负数真值运算结果的绝对值的二补数
// 因此得到的结果是负数真值结果的二补数,通过函数B2T可以直接映射到正确的真值结果
// (二补数编码,负整数的编码本质就是其绝对值的二补数,二进制数)
// 例如: w=3 真值结果= -001
// 则计算机运算结果= 111, 111就是-001的二补数编码
// B2T(111)=(这里省略计算的公式) -001 = -1
//
// 2) 被减数为负数:
// 真值运算结果 + 被减数的绝对值和其绝对值的二补数之和 + 减数的绝对值和其绝对值的二补数之和
// (因为计算机中参与运算的数的数值位数相同,
// 因此 被减数的绝对值和其绝对值的二补数之和 = 减数的绝对值和其绝对值的二补数之和)
// 得到 负数真值结果的二补数 + 被减数或减数的绝对值和其绝对值的二补数之和
// 被减数或减数的绝对值和其绝对值的二补数之和(计算机截断结果相当于模除掉这部分)
// = 负数真值结果的绝对值的二补数(也是负数真值结果的二补数编码)
// 在计算机中这个二补数编码 --映射--> 负数真值结果
// w=3 -001 + -001 = -010, |-010|=010
// 二补数运算: 111 + 111 = 1110
// = 1000-001 + 1000-001
// = 1 0000 - 010
// 001的二补数 + 001的二补数 = 010的二补数,所以二补数运算的结果等于负数真值运算结果的绝对值的二补数
//20200105主要看下面这一段就行了:
// 二补数运算举例:
// 1) 计算机:
// -100 + -100 + -100
// 101 + 100 (先计算前面两个)
// = 1000 - 100 + 1000 - 100
// = -1000 + 10000 (-100 + -100 = -1000)
// = 1000 (-1000的4位二补数,也是-1000在计算机中的二补数编码,这里假设字长w=3)
// w=3,这里截断数位,结果=000
// 000 + 100
// = 000 + 1000 + -100
// = 000 + 100
// = 100 B2T--map--> -4
// 所以计算机中: -4 + -4 + -4 = -4-4-4 运行结果= -4
//
// 如果字长w不受限制,是可以计算出正确结果的:
// -100 + -100 + -100
// 100 + 100 (先计算前面两个)
// = 1000 - 100 + 1000 - 100
// = -1000 + 10000 (-100 + -100 = -1000)
// = 1000 (-1000的4位二补数,也是-1000在计算机中的二补数编码,这里假设字长w=3)
//
// -1000 + -0100 = -1100
// = 1000 + 1100
// = 10000 - 1000 + 10000 - 0100
// = -1100 + 100000
// = -01100 + 100000
// = 10100 B2T--map--> (-12)10 (-01100)2=(-1100)2
// (10100为-1100的5位二补数,也是-1100在计算机中的二补数编码,计算机中w至少5位才能存储真值-1100)
// (负数在计算机中都是以二补数编码储存的)
//
// decimal | binary | unsigned encode | two's complement encode
// 7 111 111 0111
// -7 -111 - 1001
// -8 -1000 - 1000
//二补数编码比二进制数字长(数位)多一位才能储存这个二进制数
//注:1000属于特殊情况,1000的4位(w=4)二补数位就是1000
// 1111的二补数编码01111
//-1111的二补数编码,编码过程:
// -01111 --> |-01111| --> 01111 --> 10000(一补数) --> 10001(二补数)
//
//无符号编码: 7 --> 111 --> 111
// -7 --> -111 --> -
//
//通过十补数运算来理解-->计算机中的二补数运算
//(原理一样,但二进制数运算有其特殊性,计算机中算法也有其特殊性):
// 9 - 4 = 09 - 04 (等价于09 + -04) = 05
// --> 减法转十补数加法运算(计算机运用的十补数加法运算,数学中并不需要这样算)
// --> 09 + 96
// = 09 + 100 - 04
// = 09 - 04 + 100
// = 05 + 100
// = 105
// --> 105减去溢出部分100(与105mod100结果一样)
// --> 得到正确结果05
//
// 4 - 9 = 04 - 09(等价于04 + -09) = -05
// --> 04 + 91
// = 04 + 100 - 09
// = 04 - 09 + 100
// = -05 + 100
// = 95 (95为05的二位十补数)
// --> 求95的二位十补数,得到05
// --> 再加上负号,得到正确结果 -05
//
// -3-1 = -03-01(等价于-03 + -01)
// --> 97 + 99
// = 100 - 03 + 100 - 01
// = -04 + 100 + 100
// = 96 + 100
// = 196
// --> w=2,舍去最高位,截断结果(196 mod 100等效)
// --> 再求96的二位二补数,加负号,得到正确结果
//
// -4-4 = -04-04(等价于-04 + -04) = -08
// --> 96 + 96
// = 100 - 04 + 100 -04
// = -08 + 100 + 100
// = 92 + 100
// = 192 (舍去最高位,截断结果,再求2位十补数加负号,得到结果-08)
//
//*发生溢出是因为字长有限,字长不受限制的话减法转十补数/二补数的加法是永远可以得到正确结果的
// 真值结果非负数,补数运算结果就模除得到正确结果,真值结果为负数,补数运算结果就求相同数位补数再加负号即可得到正确结果
//======================================================================================================
void limits_head_constants(void); //函数声明,function declaration
void unsigned_int_operation(void);
void integer_multiplication(void);//multiplication乘法
void a_modulo_n(void);
void integer_constants_limits(void);
int main(void)
{
short int x,s,a = 6,b = -7;
unsigned short int i;
char c;
s = a + b;
printf("s=a+b = %hd\n", s);
printf("s=a+b = %hx\n", s);
printf("s=a+b = %hx\n", a + b);
printf("size of i:%d\n", sizeof(i));
printf("s=a+b = %hu\n", a + b);
//整数常量255 4byte 0000 0000 0000 0000 0000 0000 1111 1111
//赋值给char 1111 1111(只保留最后1byte)是负数-1的补码
//转换说明 short int 1111 1111 1111 1111 负数填充1111 1111
printf("%hd %hx\t %hx %hx\n", c = 255, c = 255, x = -255, -255);
//0111 1111 整数填充 0000 0000 0111 1111 前面填充0000 0000
printf("%hd\v %hx\n", c = 127, c = 127);
printf("%hd\n", x = 32768);
printf("\x41 \077\n"); //转移序列 \xhh十六进制
printf("what is you name\" \n");
printf("%hx\n", 1-2);
printf("4294967295 hexadecimal: %x,decimal: %u\n", 4294967295, 4294967295);
//decimal(4294967296): 1 0000 0000 0000 0000 0000 0000 0000 0000
//int constant 4byte: 0000 0000 0000 0000 0000 0000 0000 0000
// --> printf --> 0
printf("4294967296 hexadecimal: %x,decimal: %u\n", 4294967296, 4294967296);
//decimal(-4294967296): - 1 0000 0000 0000 0000 0000 0000 0000 0000
//two's complement: 1 0000 0000 0000 0000 0000 0000 0000 0000
//int constant 4byte: 0000 0000 0000 0000 0000 0000 0000 0000
// --> printf --> 0
//*注:-1000类型的负数的二补数编码属于特殊情况,其它都是二补数编码比真值数码多一位储存
printf("-4294967296 decimal: %u\n", -4294967296);
//decimal(-4294967295): - 1111 1111 1111 1111 1111 1111 1111 1111
// --> 二补数编码储存: - 1111 1111 1111 1111 1111 1111 1111 1111
// --> 需要33bit: - 0 1111 1111 1111 1111 1111 1111 1111 1111
//two's complement: 1 0000 0000 0000 0000 0000 0000 0000 0001
//整数常量4byte: 0000 0000 0000 0000 0000 0000 0000 0001
// --> printf --> 1
printf("-4294967295 decimal: %d, unsigned: %u\n", -4294967295, -4294967295);
limits_head_constants();
unsigned_int_operation();
integer_multiplication();
a_modulo_n();
integer_constants_limits();
return 0;
}
void limits_head_constants(void)
{
printf("int Max:%d\n", INT_MAX);
printf("int Min:%d\n", INT_MIN);
printf("unsigned int Max:%u\n", UINT_MAX);
printf("long Max:%ld\n", LONG_MAX);
printf("long Min:%ld\n", LONG_MIN);
/*
mingw compiler
printf("long long Max:%lld\n", LONG_LONG_MAX);
printf("long long Min(hexadecimal):%llx\n", LONG_LONG_MIN);
printf("unsigned long long Max:%llu\n", ULONG_LONG_MAX);
*/
//cygwin gcc
printf("long long Max:%lld\n", LLONG_MAX);
printf("long long Min(hexadecimal):%lld\n", LLONG_MIN);
printf("long long Min(hexadecimal):%llx\n", LLONG_MIN);
return;
}
void unsigned_int_operation(void)
{
short unsigned int us1 = 10, us2 = 20, result;
//无符号数一般不这样用,这里只是验证一下无符号数的编码储存
result = us1 - us2;
//本质是位向量的运算(bit vector),加上模和补数的知识,转换说明相当于"function",最后得到(映射到真值)结果,
//映射到啥就是啥了~
//数学术语双射,《深入理解计算机系统》(原书第三版)P44 无符号数编码,补码编码的唯一性
//个人心得:w(字长:计算机分配的数据存储长度)一样的情况下,binary都一样,只是映射到不同的真值,
// 映射到unsigned,反射回binary,这个binary就是unsigned encodings;
// 映射到two's complement number,反射回binary,这个binary就是补码编码
//注:个人心得未必正确,只是目前个人的理解.
//B2U[w](x:bit vector) {二进制数binary to 无符号数编码unsigned encodings} =映射--> 无符号整数(真值)
//U2B[w](x:decimal value) {二补数(补码)two's complement to 二进制数binary } =反射--> 二进制数(补码表示)
//B2T[w](x:bit vector) {二进制数binary to 二补数(补码)two's complement} =映射--> 有符号整数(真值)
//T2B[w](x:decimal value) {二补数(补码)two's complement to 二进制数binary } =反射--> 二进制数(补码表示)
printf("Wrong result:%hu,Right result:%hd,Right result:%hu\n", result, result, us1 + us2);
return;
}
void integer_multiplication(void)
{
short int a=2, b=6, c=16384, r;
// short int 16384 binary: 0100000000000000
// * 0000000000000010
// 0000000000000000
// 0100000000000000
// 01000000000000000
// w=16 1000000000000000 (结果为0100000000000000像左移一位)
printf("2*6=%hi,overflow:2*16384=%hi\n", r=a*b, r=c*a);
return;
}
void a_modulo_n(void)
{
short unsigned int a=11, n=2;
// %操作符为整数取模,fmod()函数包含在math.h里,浮点数取模
// (short int)1 对整型常量进行强制类型转换
printf("a mod n = %hu, a mod 1 =%hu\n",a%n, a%(short int)1);
printf("sizeof((short unsigned int)1): %d\n", sizeof((short unsigned int)1));
printf("sizeof(a*(short unsigned int)1):%d\n", sizeof(a*(short unsigned int)1));
printf("sizeof(a*n): %d\n", sizeof(a*n));
return;
}
void integer_constants_limits(void)
{
long long int lli1 = 9223372036854775807; /*lli2 = -9223372036854775808*/
//备忘:test2.c是在Linux系统下写的。。。
//c标准规定long int型大于等于int,存储字长看编译系统(4byte\8byte)
//整数常量作为有符号整数编码,即用二补数编码储存
//储存长度是可变的,4byte和8byte两种字长(gcc编译系统的原因)
//clang编译系统使用64为绝对地址储存整数常量
//C语言标准,C编译器也还是在不断发展的,目前clang和gcc为主流
//写C还是用linux比较好,编译系统比较新
//4byte[-2^31,2^31-1], 8byte[-2^63,2^63-1]
printf("sizeof(long long int):%d\n",sizeof(lli1));
printf("sizeof(2147483647):%d\n", sizeof(2147483647));
printf("sizeof(2147483648):%d\n", sizeof(2147483648));
printf("sizeof(4294967296):%d\n", sizeof(4294967296));
printf("sizeof(9223372036854775807):%d\n", sizeof(9223372036854775807));
//溢出:printf("sizeof(9223372036854775808):%d\n", sizeof(9223372036854775808));
//printf--> -1 现在用的编译系统的问题,long int和int都是32bit,%ld跟%d一样...
printf("9223372036854775807 d:%d\n", 9223372036854775807);
printf("9223372036854775807 ld:%ld\n", 9223372036854775807);
printf("9223372036854775807 lld:%lld\n", 9223372036854775807);
//printf("sizeof(9223372036854775808) lld:%lld\n", 9223372036854775808);
//编译时报错,整数常量字长最长为64bit,
//0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
//最大有符号整数
printf("-9223372036854775807 lld:%lld\n", -9223372036854775807);
printf("-9223372036854775807 llx:%llx\n", -9223372036854775807);
/*
long long int 最小值的定义
# define LLONG_MIN (-LLONG_MAX - 1LL)
*/
// long long int 最小值的正确输出方式:
printf("-9223372036854775808 lld:%lld\n", -9223372036854775807-1);
printf("-9223372036854775808 llx:%llx\n", -9223372036854775807-1);
printf("9223372036854775808u llu:%llu\n", 9223372036854775808u);
return;
}
#include
// Floating-Point Operations 浮点数运算
//exponent指数 指数计数法:1.0e9
// 科学计数法:1.0*10^9
// 数字:1000000000
void float_double_limits(void);
void float_double_addition(void);
void float_double_subtraction(void);
void float_double_multiplication(void);
void float_double_division(void);
void float_double_format_specific(void);
void main(void)
{
float f1 = 111.111, fa = 1.4, fb = 0.1, \
fc = 1.3999999, fd = 1.3999995, fe = 1.3999994;
double dc = 1.3999999, dd = 1.3999995, de = 1.3999994;
printf("f1(d):%d\n",f1);
printf("sizeof(f1):%d\n", sizeof(f1));
//format specific %f 默认保留小数点后六位
// %a float默认保留小数点后六位
// %a double默认保留小数点后十三位
//double 即可用 %f 也可用 %lf
printf("fa(f):%f\n", fa);
printf("fb(f):%f\n\n", fb);
printf("fc(f):%f, fc(a):%a\n", fc, fc);
printf("fd(f):%f, fd(a):%a, fd(.7a):%.7a\n", fd, fd, fd);
printf("fe(f):%f, fe(a):%a\n\n", fe, fe);
//float double 格式说明符都是 %f
printf("dc(f):%f, dc(a):%a, dc(.7a):%.7a\n", dc, dc, dc);
printf("dd(f):%f, dd(lf):%lf, dd(a):%a, dd(.7a):%.7a\n", dd, dd, dd, dd);
printf("de(f):%f, de(a):%a, de(.7a):%.7a\n\n", de, de, de);
printf("fa(.10f):%.10f,fa(a):%a\n", fa, fa);
printf("fb(.10f):%.10f,fb(a):%a\n", fb, fb);
printf("fa(.6a):%.6a\n", fa);
printf("fb(.6a):%.6a\n", fb);
printf("\n");
float_double_limits();
float_double_addition();
float_double_subtraction();
float_double_multiplication();
float_double_division();
float_double_format_specific();
return;
}
void float_double_limits(void)
{
float fa = 1.3, fb = 1.1;
double da = 1.3, db = 1.1;
long double lda = 1.3;
//float: 1 8 23; size: 32
//double: 1 11 52; size: 64
//long double: 1
printf("sizeof(fa):%d\n", sizeof(fa));
printf("sizeof(da):%d\n", sizeof(da));
printf("sizeof(lda):%d\n", sizeof(lda));
printf("fb(.20a): %.20a,\nfb(.40a): %.40a\n", fb, fb);
printf("db(.20a): %.20a,\ndb(.40a): %.40a\n", db, db);
printf("fa(.20a): %.20a,\nfa(.40a): %.40a\n", fa, fa);
printf("da(.20a): %.20a,\nda(.40a): %.40a\n", da, da);
//%lf double; %Lf long double
printf("lda(.60La):%.60La\n\n", lda);
return;
}
void float_double_addition(void)
{
float fa = 1.4, fb = 0.1;
printf("fa + fb(float):%f,fa + fb(a): %a\n", fa + fb, fa + fb);
printf("fa - fb(float):%f,fa + fb(a): %a\n", fa - fb, fa - fb);
printf("fa + fb(.6a): %.6a\n", fa + fb);
printf("fa + fb(.6a): %.6a\n\n", fa - fb);
return;
// float double addition:
// 1.4 -->
// 整数部分: 2 | 1 --> 余 1
// 小数部分: 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0 rounding
// 1.4(binary): 1. 0110 0110 0110 0110 0110 011|0 * (2^0)10
// 1. 6 6 6 6 6 6(rounding)
// S E M
// 0 01111111 01100110011001100110011
//
// 0.1 -->
// 整数部分: 2 | 0 --> 余 0
// 小数部分: 0.1 * 2 = 0.2 --> 0
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
// 0.6 * 2 = 1.2 --> 1
// 0.2 * 2 = 0.4 --> 0
// 0.4 * 2 = 0.8 --> 0
// 0.8 * 2 = 1.6 --> 1
//
// 0.1(binary): 0.0001 1001 1001 1001 1001 100|11001 * (2^0)10
// 规格化: 0 0001.10011001100110011001100|1 * (2^-4)10
// --> 1.10011001100110011001100|1(rounding) * (2^-4)10
// --> 1. 1001 1001 1001 1001 1001 101|0(rounding) * (2^-4)10
// 1. 9 9 9 9 9 a * (2^-4)10
// 1. 9 9 9 9 9 a * (p^-4)10
//
// 1.4 + 0.1 = 1. 0110 0110 0110 0110 0110 011 * (2^0)10
// 1. 1001 1001 1001 1001 1001 100 * (2^-4)10
//
//提取(2^0)10,w字长有限,移位较小的浮点数确保结果准确度(移位阶码值小的)
//1. 0110 0110 0110 0110 0110 011 * (2^0)10 + 1. 1001 1001 1001 1001 1001 100 * (2^-4)10
//[1. 0110 0110 0110 0110 0110 011 + 1. 1001 1001 1001 1001 1001 100 * (2^-4)10] * (2^0)10
//[1. 0110 0110 0110 0110 0110 011 + 0. 0001 1001 1001 1001 1001 1001 100] * (2^0)10
//
// 1.01100110011001100110011
// 0.000110011001100110011001(rounding)--|
// 0.00011001100110011001101 <-----------|
// 1.10000000000000000000000
}
void float_double_subtraction(void)
{
//subtraction
//减数与被减数都是负数,直接符号位不变,阶码转换成与阶码值大的一致,尾数做加法即可;
//减数与被减数一正一负,阶码转换成与数值大的浮点数一致,
//符号位以尾数值大的为准,尾数(大)-尾数(小)-->尾数(大)+尾数(小)的二补数:结果肯定溢出,舍掉溢出位即可
//1.75 --> 1.11000000000000000000000 * (2^0)10
//1.75 --> 0 01111111 11000000000000000000000
//
//0.625--> 0.10100000000000000000000 * (2^0)10
//0.625--> 1.01000000000000000000000 * (2^-1)10
//0.625--> 0 01111110 01000000000000000000000
//
//以阶码大的为准,阶码小的转换成阶码大的
//1.75 --> 1.11000000000000000000000 * (2^0)10
//0.625--> 1.01000000000000000000000 * (2^-1)10
//0.625--> 0.10100000000000000000000 * (2^0)10 尾数变小,阶码变大
//
// 111000000000000000000000
// - 010100000000000000000000 --->|
// 101011111111111111111111 <---| one's complement
// + 101100000000000000000000 <---| two's complement
//----------------------------------
// 1100100000000000000000000 截断最高位(效果相当于模除mod),得到真值结果
// 100100000000000000000000 被减数大于减数的二补数运算
// (浮点数所有减法运算都是,与整数运算不同)
//result--> (1.00100000000000000000000)2 * (2^0)10
// (1.125)10
// (1.2)16
float fa = 1.75 , fb = 0.625;
printf("fa - fb=(.10f):%.10f,fa - fb=(.10a):%.10a\n", fa - fb, fa - fb);
printf("fb - fa=(.10f):%.10f,fb - fa=(.10a):%.10a\n", fb - fa, fb - fa);
return;
}
void float_double_multiplication(void)
{
// (1.75)10 --> (1.11)2 * (2^0)10
// (0.625)10 --> (0.101)2 --> (1.01)2 * (2^-1)10
// (1.11)2 * (2^0)10 * (1.01)2 * (2^-1)10
// (1.11 * 1.01)2 * (2^0 * 2^-1)10
// (1.11 * 1.01)2 * (2^-1)10
//-------------------------------------------------
//一般正常算法,但为什么这么算?不直观:
// 1.11
// * 1.01
// --------
// 1 11
// 00 0 (从乘法竖式本质来说这一步是不存在的和毫无意义的)
// 111
// --------
// 10.00 11
// 10.0011 * 2^-1 --> 1.00011 * 2^0
// 1.1800000000
//-------------------------------------------------
// (1.75)10 --> (1.11)2 * (2^0)10 --> 1.11
// (0.625)10 --> (0.101)2 * (2^0)10 --> 0.101
//小数乘法的本质:
// (1.110)2 --> 被乘数*8(2^3)倍 1110
// * (0.101)2 --> 乘数*8(2^3)倍 101
// ----------
//
// 1110
// * 101
// -----------
// 1110 *1倍 --> (1)10倍--->|
// 1110 *100倍 --> (4)10倍--->|<--*两倍进一位
// ----------- |
// 1000110 |--> 兼具运算速度与本质理解
// 1000110/2^6 = 1000110 * 2^-6 = 1.000110 = 1.000110 * 2^0
// |--> decimal *ten times(*十倍) 运算时逢十进一
// 10 --|
// |--> binary *two times(*两倍) 运算时逢二进一
//数学原理: 1 * 1 = 1
// 1*4 * 1 = 4 (2^2)
// 1*4 * 1*4 = 16 (2^4)
//
//乘法本质的一点理解:
// 102
// * 2
// --------
// 4 拆分成两步,更好理解本质
// 200
// 4 + 200 = 204
//
float fa = 1.75, fb = 0.625;
printf("fa * fb(.10a):%.10a, fa * fb(.10f):%.10f\n", fa * fb, fa * fb);
return;
}
void float_double_division(void)
{
//division
// 2/2 :2根辣条分成两份 --> 2/2=1
// 2/(1/2) :分成1/2根辣条每份,可分成4份 --> 2/(1/2)=2*2=4
// 倒数,指的是两个相乘等于1的数
// 除于一个数等于乘于一个数的倒数 乘法逆元
// (1/2)=2*2=4
// 数学是形式科学不是自然科学。。。啊。。。我要疯了。。。
//小数除法:
// -------
// 2.4 _/ 4.92
// -------
// 2.4*10 _/ 4.92*10
//
// 2.05
// -------
// 24 _/ 49.2
// 48
// ---------
// 1 20
// 1 20
// ---------
// 0
//
//
// 1.75 --> 1.11000000000000000000000 * (2^0)10
// 0.25 --> 0.01000000000000000000000 * (2^0)10
// 0.25 --> 1.00000000000000000000000 * (2^-2)10
//
// 1.11000000000000000000000 * (2^0)10 / 1.00000000000000000000000 * (2^-2)10
// (1.11000000000000000000000 / 1.00000000000000000000000) * (2^0)10/(2^-2)10
// (1.11000000000000000000000 / 1.00000000000000000000000) * [2^0-(-2)]10
// (1.11000000000000000000000 / 1.00000000000000000000000) * (2^2)10
//
// 1.11
// -------
// 1 _/ 1.11
// 1
// -------
// 1
// 1
// -------
// 1
// 1
// -------
// 0
// 1.11000000000000000000000 *(2^2)10
// --> sign(+): 0
// --> exponent: 2 + 127 = 129 (1000 0001)
// --> mantissa: 11000000000000000000000
// --> 0 10000001 11000000000000000000000
// --> 1.c00000 * (2^2)10
//
float fa = 1.75, fb = 0.25;
printf("fa/fb=(.10f):%.10f, fa/fb=(a):%a\n", fa/fb, fa/fb);
return;
}
void float_double_format_specific(void)
{
int fa = 1.75, fb = 1.75e1;
printf("sizeof(int) zd:%zd\n", sizeof(int));
printf("sizeof(int) d:%d\n", sizeof(int));
printf("int fa = 1.75: %d\n", fa);
printf("int fb = 1.75e1: %d\n", fb);
return;
}
#include
//===================================================================================
//二进制转十进制 1011 >> 1*2^3 + 1*2^1 + 1*2^0 = 11(10)
//十进制转二进制原理
// 11(10) = 1*2^3 + 1*2^1 + 1*2^0 = 1*8 + 1*2 + 1*1
// /2 /2 /2 位值 1 /2 /2 /2
// 1*2^2 + 1*1^1 1*4 + 1*1
// /2 /2 位值 1 /2 /2
// 1*2^1 1*2
// /2 位值 0 /2
// 1 1
//-----------------------------------------------------------------------------------
// 11(2) 1*2^1 + 1*2^0 10(2) 1*2^1 + 0*2^0
// /2 /2 位值 1 /2 /2 位值 0
// 1*2^0 1*2^0
// /2 位值 1 /2 位值 1
//-----------------------------------------------------------------------------------
// 101(2) 1*2^2 + 0*2^1 + 1*2^0
// /2 /2 /2 位值 1
// 1*2^1 0*2^0
// /2 /2 位值 0
// 1*2^0
// /2 位值 1
//-----------------------------------------------------------------------------------
//个人理解:位权 除 2,除到位权为1,位值*位权=位值*1=位值
// 两种情况: a. 1*1=1; b. 0*1=0
// 所以:结果为1,则位值为1;结果为0,则位值为0。
//理解关键:前一位的权值比当前位的权值大一个2^1,
// 所以当前位的权值被除到结果为1(2^0)时,前一位的权值被除到结果为2^1
//
//*20200111 整数十进制转二进制更简单的理解:
// 二进制(binary),每一数位的位值为0或1
// 位权大于等于2^1的,它们的位值与位权乘积之和(这里我把这个和叫做sum)肯定能被2整除(division:分裂,除法)
// 位权2^0=1
// 把位权为2^0的数位当做余数:
// 1) 该位数值为0,则 0 * 2^0 = 0 * 1 = 0
// (sum + 0) /2 = sum/2 + 0/2 = sum/2 + 0 --> sum可以被2整除
// 2) 该位数值为1,则 1 * 2^0 = 1 * 1 = 0
// (sum + 1) /2 = sum/2 + 1/2 = sum/2 + 1/2 --> 1不能被2整除
//
// 位权大于等于2^1的数位的位值与位权乘积之和,
// 1) 加上余数0,除于2,则余数为0,该位位值为0的情况
// 2) 加上余数1,除于2,则余数为1,该位位值为1的情况
//
// 除完第一次2以后,我们已得知第一个数位0的位值,不管余数为0或1都丢掉
// 此时数位1的位权变为2^1/2=2^0,以此类推,数位2的位权等于2^2/2=2^1
// 再重复除于2的步骤,直到求出最高数位的位值为止
//
//===================================================================================
//二进制浮点数小数部分转换十进制:
// .101 = 1*2^-1 + 0*2^-2 + 1*2^-3
// = 1/2^1 + 0/2^2 + 1/2^3
// = 1/2 + 0/4 + 1/8
// = 0.5 + 0 + 0.125 = 0.625
//十进制浮点数小数部分转换二进制:
// 0.625 * 2 = (1/2 *2) + (1/8 *2)
// 1.25 = 1 + 1/4
// 0.25 = 1/4 ----> 1
// 0.25 * 2 = 1/4 *2
// 0.5 = 1/2 ----> 0
// 0.5 *2 = 1/2 *2
// 1.0 = 1 ----> 1
// 得到 (.101)2
// 原理: (.111)2 = 1/2 + 1/4 + 1/8
// 第一次乘2,得到小数点后第一位数值,如果数值是1的话,乘2的结果肯定有整数1,反之,则数值为0
// 第二次乘2,得到小数点后第二位数值,跟上面道理是一样的,后面的依此类推
// 二进制,数值不是0就是1,真舒服...
// 数值为1的话,肯定能余1,为0的话就不会有余数1啦
//
//20200111十进制小数部分转二进制更精准的理解:
// 一定要简单!说话不能太沉醉!--'
// 位权小于等于1/2^2的,乘于2,它们之和还是小于1
// 位权等于1/2^1的,乘于2
// 1) 位值为0,加上上面的之和还是小于1
// 2) 位值为1,加上上面之和后结果大于等于1
// 知道第一位的位值后,丢弃第一位乘2的结果,继续乘于2,
// 相当于重复第一步,因为第二位的位权乘于2后已变成2 * 1/2^2 = 1/2^1
//
// 1/2 + 1/4 + 1/8 + 1/16
// = 1-1/2 + 1/2-1/4 + 1/4-1/8 + 1/8-1/16
// = 1 - 1/16
// = 15/16
// *这是位值全为1的情况,如果有位值为0, 15/16 - 位值为0的位权,就更小了
// *通过这个求解方法可知十进制小数部分转二进制中:
// 位权小于等于1/2^2的,乘于2,它们之和肯定小于1
//
//===================================================================================
//十进制乘法理解:
// 11
// * 11
// ------
// 11
// 11
// -------
// 121
//1. 第二行第一个数 a:1个1,结果放在个位 b:1个10,结果放在十位,是1,十位的权值是10
//2. 第二行第二个数 a:10个1,结果放在十位,是1 b:10个10,结果放在百位,是1
//二进制也是一样的,位置计数法原理都一样,举一反三。。。举一反N
//===================================================================================
//这里说的都是 机器码原码 机器码补码(与数学上的补码/补数有区别) 机器码移码
//原码用 符号位+绝对值 表示
//原码 011 3 补码 011
// 010 2 010
// 001 1 001
// 000 0 000
// 101 -1 111
// 110 -2 110
// 111 -3 101
// -4 100
//字长n=3时,可看出 -4只有补码没有原码,原码没有-0(100),补码中用-0(100)来表示-4
//-----------------------------------------------------------------------------------
//补码由来: 000 001 010 011 | 100 101 110 111 (1段 分隔符 2段)
// 100 101 110 111 | 000 001 010 011 (2段 分隔符 1段)
// -4 -3 -2 -1 0 1 2 3
//特性 001与111互为补码,mod模为1000
//===================================================================================
//offset binary code 偏移二进制码
//小知识点:w(字长)=3,000的2补数是000
// offset binary code
// excess_3(bias=(2^w-1)-1=3) excess_4(bias=2^w-1=4)
// decimal code | two's complement binary | (bias binary=011) (bias binary=100)
// 3 011 110 111
// 2 010 101 110
// 1 001 100 101
// 0 000 011 100
// -1 111 1(010) 1(011)
// -2 110 1(001) 1(010)
// -3 101 1(000) 1(001)
// -4 100 111 1(000)
//由上表可看出: excess_3与IEEE754标准的excess_127原理是一样的,只是exponent field的字长word不一样
//因此同理可得: 000,111用作 非规格化值 和 特殊值 的移码(课本译作阶码,移码有多个专业名字,具体看英文wiki,太多不想记,脑子不够用...)
// [001,110]用作 规格化值(standard format) 的移码,观察移码映射到的十进制值,可通过移码(把移码编码当无符号整数编码看)直观地比较大小
// 这就是用移码做指数字段编码的原理
//20200102*: w位二进制数计算,移码减偏置量还等于加偏置量前的补码(二补数)的本质
// 1) 加移码后结果没有溢出,011+011=110, 110-011=110+(1000-011)=110-011+1000(加二补数的本质)
// 结果溢出,计算机只保留w=3位,得到正确结果,又得到011
// 2) 加移码后结果溢出,计算机只保留结果w=3位,例如: 001-011=001+(1000-011)=001-011+1000=1001-011
//
//计算机中浮点数最直观的理解:
// sign exponent mantissa
//float(单精度浮点型): 0 0000 0000 0000 0000 0000 0000 0000 001 最小非规格化数
// 二进制表示: value(binary) = 0.0000 0000 0000 0000 0000 001 * 10^(0000 0001-0111 1111) excess_127=bias=127(decimal)=0111 1111(binary)
// = 0.0000 0000 0000 0000 0000 001 * 10^(-0111 1110)
// 十进制表示: value(decimal) = 1/2^23 * 2^(-126)
// = 2^(-23) * 2^(-126)
// = 2^(-149)
// 最小非规格化数(十进制表示) = M * 2^E = 2^-23 * 2^(-126) = 2^(-149)
// ≈ 1.4 * 10^(-45)
//
// sign exponent mantissa
//float(单精度浮点型): 0 0000 0001 0000 0000 0000 0000 0000 000 最小规格化数
// 二进制表示: value(binary) = 1.0000 0000 0000 0000 0000 000 * 10^(0000 0001-0111 1111) excess_127=bias=127(decimal)=0111 1111(binary)
// = 1.0000 0000 0000 0000 0000 000 * 10^(-0111 1110)
// 十进制表示: value(decimal) = 1*2^0 * 2^(-126) = 2^-126
// = 1/2^126
// ≈ 1.2 * 10^-38
//
// sign exponent mantissa
//float(单精度浮点型): 0 1111 1110 1111 1111 1111 1111 1111 111 最大规格化数
// 二进制表示: value(binary) = 1.1111 1111 1111 1111 1111 111 * 10^(1111 1110-0111 1111) excess_127=bias=127(decimal)=0111 1111(binary)
// = 1+(1-0.0000 0000 0000 0000 0000 001) * 10^(0111 1111)
// = [1+(1- 10^-00010111] * 10^(0111 1111)
// 十进制表示: value(decimal) = [1+(1- 2^-23)] * 2^127
// = (2 - 2^-23) * 2^127
// = 2*2^127 - 2^-23 * 2^127
// = 2^(1+127) - 2^(-23+127)
// = 2^128 - 2^104
// = 2^128 - 2^104
// = 2^128 - 2^104
// ≈ 3.4 * 10^38 = 3.4e38
//
//结论:浮点数有正负0,正负float取值范围是对称的,float的取值范围[-3.4e38,3.4e38],double/long double同理
//-----------------------------------------------------------------------------------
// 6.75(10) >> 110.11(2)
// 科学计数法 十进制 123.456*10^2(10) 1.23456*10^4(10) 0.123456*10^5(10)
// 二进制 A: 1.1011*2^2(2) 整数部分最高位1放小数点前
// B: 0.11011*2^3(2) 整数部分最高位1放小数点后
//计算机中以A方式存储浮点数的尾数,默认隐含1.
//在内存中尾数以1011存储,整数部分最高位肯定为1,隐藏1.,原因如下:
//不以B方式储存是为了节省存储空间吧...到底是不是要去问创造C语言的大佬( ̄ェ ̄;)
//计算机中浮点数的存储方式(以float 6.75 为例,float数据类型存储长度为4个字节byte 32位bit):
//以下内容在《计算机科学导论》P36 更详细
//附加知识点:尾数以符号加绝对值表示法储存(sign+mantissa) 《计算机科学导论》P30
// 符号 指数 定点数 科学计数法
// 符号 指数 尾数 规范化
// 阶符1bit 阶码7bit
// 1bit 8bit 23bit
// 0 0 10 (隐含1.)1011 注:非真实存储数据,只是描述存储了哪部分(小数点左边保留一位数码)
// 0|1 余码 (隐含1.)10110000000000000000000 尾数右边填充0直到填满23位存储单元
//比较两个浮点数大小的先后顺序,出现数值相等的情况才会继续下一步,不相等则直接得到比较大小的结果:
// 先比较 (个人假想,待验证)符号位sign (+ > -) (0 > 1) 这个是我自己想的... 等后面看到教程或书籍有提到才能证实--'
// 指数exponent 规范化后 指数越大当然浮点数越大
// 尾数mantissa 用比较整数的方法比较大小 从高位向低位比较 01110 > 01101
//===================================================================================
//fractional 分数的,小数的
//fraction 分数,小数
//standard 标准
//移码(偏码.增码.译码,余码)
//offset binary:偏移二进制码
//excess:超过,超过的量 1)excess_127:偏移量127(IEEE754偏移量) 2)excess_128(标准偏移量)
//IEEE754标准 移码(计算机中存储的移码)
//-----------------------------------------------------------------------------------
//《深入理解计算机系统》(第三版) P80 图2-35 理解计算机浮点数格式的秘籍啊......
//指数exponent的值分三种情况,以float数据类型为例:
// A. 规格化的值:
// exponent field为[0000 0001,0111 1111] --> [1,254]
// E= e-127(bias偏置值) = [1,254]-127 ---> [-126,127]
// = [0000 0001,0111 1111] - [0111 1111]
// M(尾数的值) = 1+f(描述小数值 .f-1 f-2 ...)
// mantissa尾数 隐含1. ,所以规格化的值无法表示0
//
// B. 非规格化的值:
// 阶码字段全是0,exponent field指数字段(书本译作阶码)为[0000 0000]
// E= 1-127(bias偏置值) = -126
// M(尾数的值) = f(描述小数值)
// mantissa尾数 隐含0.(书本不隐含1.)
// sign为0,exponent全为0: 1) mantissa全为0 表示 +0
// 2) mantissa: M = f 表示 +0.bbb...
// sing为1,exponent全为0: 1) mantissa全为0 表示 -0
// 2) mantissa: M = f 表示 -0.bbb...
//
// C 特殊值:
// a) exponent field[1111 1111],小数域全为0[0000 0000 0000 0000 000]
// sign为0,+∞ 正无穷
// sign为1,-∞ 负无穷
//
// b) exponent field[1111 1111],小数域为非0,结果值被称为"NaN"(Not a Number)
//看完上面这段文字如果你一脸懵逼,你是不是怀疑人生,觉得自己很蠢,你就是个傻子,哈哈哈哈~
//没事~接下来直接看下面这个正浮点数的表~哈哈哈哈哈哈哈哈
//
//正浮点数: 1) 这里的f表示小数部分,小数=整数部分.小数部分
// 2) 下表中 假设指数字段3bit,尾数字段2bit,符号位不说也知道是1bit吧
// sign exponent mantissa word
// float 1bit 8bit 23bit 4byte 32bit
// double 1bit 11bit 52bit 8byte 64bit
// 3) b(bias偏置量)=2^(3-1)-1=3
// 4) e:假定阶码(指数)字段是一个无符号整数所表示的值
// E:偏置之后的阶码(指数)值
//
// --------------------bias(binary)------------------------bias(decimal)-------------------------------------------------------------------
// float 1000 0000 - 0000 0001=0111 1111 1*2^7 - 1 = 127
// double 同上,位数填满到11位 1*2^10 - 1 = 1023
// -------位表示bit representation--------
// sign[S] exponent[E] mantissa[M] [S] [E]decimal--fraction [M]mantissa ---------------[value]--------------
// 非规格数--------------------------------- e E=(1-b) 2^E binary binary *decimal decimal fraction
// 0 000 00 (0+f) + 0 -2 1/4 0.00 (0.00)2 * 2^-2 (0*1 + 0*1/2 + 0*1/4) * 1/4 = 0/16
// 0 000 01 (0+f) + 0 -2 1/4 0.00 (0.01)2 * 2^-2 (0*1 + 0*1/2 + 1*1/4) * 1/4 = 1/16
// 0 000 10 (0+f) + 0 -2 1/4 0.00 (0.10)2 * 2^-2 (0*1 + 1*1/2 + 0*1/4) * 1/4 = 2/16
// 0 000 11 (0+f) + 0 -2 1/4 0.00 (0.11)2 * 2^-2 (0*1 + 1*1/2 + 1*1/4) * 1/4 = 3/16
// 规格数-----------------------------------
// 0 001 00 (1+f) + 1 -2 1/4 1.00 (1.00)2 * 2^-2 (1*1 + 0*1/2 + 0*1/4) * 1/4 = 4/16
// 0 001 01 (1+f) + 1 -2 1/4 1.01 (1.01)2 * 2^-2 (1*1 + 0*1/2 + 1*1/4) * 1/4 = 5/16
// 0 001 10 (1+f) + 1 -2 1/4 1.10 (1.10)2 * 2^-2 (1*1 + 1*1/2 + 0*1/4) * 1/4 = 6/16
// 0 001 11 (1+f) + 1 -2 1/4 1.11 (1.11)2 * 2^-2 (1*1 + 1*1/2 + 1*1/4) * 1/4 = 7/16
//
// 0 010 00 (1+f) + 2 -1 1/2 1.00 (1.00)2 * 2^-1 (1*1 + 0*1/2 + 0*1/4) * 1/2 = 8/16
// 0 010 01 (1+f) + 2 -1 1/2 1.01 (1.01)2 * 2^-1 (1*1 + 0*1/2 + 1*1/4) * 1/2 = 10/16
// 0 010 10 (1+f) + 2 -1 1/2 1.10 (1.10)2 * 2^-1 (1*1 + 1*1/2 + 0*1/4) * 1/2 = 12/16
// 0 010 11 (1+f) + 2 -1 1/2 1.11 (1.11)2 * 2^-1 (1*1 + 1*1/2 + 1*1/4) * 1/2 = 14/16
//
// 0 011 00 (1+f) + 3 0 1 1.00 (1.00)2 * 2^0 (1*1 + 0*1/2 + 0*1/4) * 1 = 16/16
// 0 011 01 (1+f) + 3 0 1 1.01 (1.01)2 * 2^0 (1*1 + 0*1/2 + 1*1/4) * 1 = 20/16
// 0 011 10 (1+f) + 3 0 1 1.10 (1.10)2 * 2^0 (1*1 + 1*1/2 + 0*1/4) * 1 = 24/16
// 0 011 11 (1+f) + 3 0 1 1.11 (1.11)2 * 2^0 (1*1 + 1*1/2 + 1*1/4) * 1 = 28/16
//
// 0 100 00 (1+f) + 4 1 2 1.00 (1.00)2 * 2^1 (1*1 + 0*1/2 + 0*1/4) * 2 = 32/16
// 0 100 01 (1+f) + 4 1 2 1.01 (1.01)2 * 2^1 (1*1 + 0*1/2 + 1*1/4) * 2 = 40/16
// 0 100 10 (1+f) + 4 1 2 1.10 (1.10)2 * 2^1 (1*1 + 1*1/2 + 0*1/4) * 2 = 48/16
// 0 100 11 (1+f) + 4 1 2 1.11 (1.11)2 * 2^1 (1*1 + 1*1/2 + 1*1/4) * 2 = 56/16
//
// 0 101 00 (1+f) + 5 2 4 1.00 (1.00)2 * 2^2 (1*1 + 0*1/2 + 0*1/4) * 4 = 64/16
// 0 101 01 (1+f) + 5 2 4 1.01 (1.01)2 * 2^2 (1*1 + 0*1/2 + 1*1/4) * 4 = 80/16
// 0 101 10 (1+f) + 5 2 4 1.10 (1.10)2 * 2^2 (1*1 + 1*1/2 + 0*1/4) * 4 = 96/16
// 0 101 11 (1+f) + 5 2 4 1.11 (1.11)2 * 2^2 (1*1 + 1*1/2 + 1*1/4) * 4 = 112/16
//
// 0 110 00 (1+f) + 6 3 8 1.00 (1.00)2 * 2^3 (1*1 + 0*1/2 + 0*1/4) * 8 = 128/16
// 0 110 01 (1+f) + 6 3 8 1.01 (1.01)2 * 2^3 (1*1 + 0*1/2 + 1*1/4) * 8 = 160/16
// 0 110 10 (1+f) + 6 3 8 1.10 (1.10)2 * 2^3 (1*1 + 1*1/2 + 0*1/4) * 8 = 192/16
// 0 110 11 (1+f) + 6 3 8 1.11 (1.11)2 * 2^3 (1*1 + 1*1/2 + 1*1/4) * 8 = 224/16
// 特殊值-----------------------------------
// 0 111 00 (1+f) +∞ 正无穷
// 0 111 ... (1+f) NaN(Not a Number)
// ----------------------------------------
//负浮点数道理同上[-0,-∞]
//===================================================================================
int main(void)
{
//power 幂 科学计数法 1*10^8、2.234*10^6
//exponent 指数计数法中的指数 指数计数法 1.334e10、2.14e-3
//实数的两种格式,在C语言中可以像下面=后面那样这样省略:
//decimal notation 11.11, 0.11=.11, 11.0=11
//exponential notation 1.23e2, 0.23e4=.23e4, 23.0e4=23e4, 19.11e-1=19.11
float f1 = 1e6, f2 = .001234, f3 = 123, f4 = 666.666, f_var, f_infinity, f_min;
double d1, d2 = 666.666, d_max=1.79e308;
long double ld1, ld2 = 6.6666666;
//format specifier:格式说明符(c primer plus中文版翻转换说明,我觉得格式说明符更好)
//format specifier example: %d %f...
//decimal notation 十进制计数法 notation 记号,符号
//exponential notation 指数计数法,e指数计数,
printf("These is decimal notation float: %f, %f, %f, %f,.\n", f1, f2, f3, f4);
printf("These is decimal notation double: %f, %f.\n", d1, d2);
printf("These is exponential notation float: %e, %e, %e, %e.\n", f1, f2, f3, f4);
printf("These is exponetial notation double: %e, %e.\n\n", d1, d2);
//*注意:windows下用gcc编译(8.1.0),long double存在问题(网上说的,我linux系统删了,没法验证,但现在在windows7下确实输出结果有问题)
//*long double 后续补充: 《深入理解计算机系统》P119 3.3 第二段 历史上特殊的浮点数格式,不建议用,不能移植...好吧,反正我也没有要用...
printf("These is decimal notation long double: %Lf, %Lf.\n", ld1, ld2);
printf("These is exponetial notation long double: %Le, %Le.\n\n", ld1, ld2);
//float有效为最少6位,double有效位至少10位,long double有效位至少13位,有效位就是小数点后保留的位数
//char constant(字符常量)与int constant(整数常量一样),占4个字节
//*编译器默认假定浮点数常量为double型,8个字节,64位,想要float或long double constant的在后面加suffix后缀
//浮点数常量后面加f或F,编译器会把这个浮点数常量看作float类型
//浮点数常量后面加l(L小写)或L,则编译器会看作long double类型,建议加L,小写L跟1(阿拉伯数字一)太像了。。。
//suffix:后缀
printf("Size of float/double/long double constant(no suffix): %d,%d,%d.\n", \
sizeof(111.111111), sizeof(111.1111111111e-2), sizeof(111.1111111111111));
printf("Size of float constant(have suffix f or F): %d.\n", sizeof(111.11F));
printf("Size of long double constant(have suffix L): %d.\n", sizeof(111.11L));
printf("Size of float variable: %d.\n", sizeof(f1));
printf("Size of dobule variable: %d.\n", sizeof(d1));
printf("Size of long dobule variable: %d.\n", sizeof(ld1));
printf("sizeof(f1*2.0f): %d, sizeof(2.0f*2.0f): %d, sizeof(2.0f*2.0): %d\n", \
sizeof(f1*2.0f), sizeof(2.0f*2.0f), sizeof(2.0f*2.0));
printf("sizeof(2e10+1.0):%d, sizeof(f_var=2e10+1.0):%d\n", sizeof(2e10+1.0), sizeof(f_var=2e10+1.0));
//printf("This is double:%.10f\n", d2);
//printf("This is long double:%.20Lf\n", ld2);
//*知识点:C99标准添加的十六进制浮点数常量格式:
// 1.ap10 mantisa:(1.a)16; 重点->p10=2^10
printf("float (hexadecimal,(mantissa)16*(2^E)10): %a\n", f_var=5.5F); //%a format specifier,以16进制hexadecimal指数形式输出浮点数
printf("float (hexadecimal,(mantissa)16*(2^E)10): %a\n", f_var=1.2e-38F);
printf("float Max(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_var=3.4e38);
printf("float Min(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_var=-3.4e38);
printf("float Max(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_var=3.4e38);
printf("float Min(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_var=-3.4e38);
printf("float Max(decimal): %f\n", f_var=3.4e38);
printf("float Min(decimal): %f\n", f_var=-3.4e38);
printf("double Max(decimal): %f\n", d_max);
printf("double Max(exponential notation): %e\n", d_max);
printf("double Max(hexadecimal,(mantissa)16*(2^E)10): %a\n", d_max);
//infinity 无穷,无限
printf("float +infinity(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_infinity=3.4e39);
printf("float +infinity(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_infinity=3.5e38);
printf("float -infinity(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_infinity=-3.4e39);
printf("float -infinity(hexadecimal,(mantissa)16*(2^E)10): %a\n", f_infinity=-3.5e38);
return 0;
}
// ANSI(american national standards institute):美国国家标准学会
// 'x' 一个字符 | x | |
// "x" 一个字符串 | x | \0 |
// \0 空字符:null character(非打印字符)
//scanf()函数会自动添加空字符
//scnaf() 只会读取fyfa fyfb中的fyfa
//--------------------------------------------------------------
//conversion specification(转换说明) 例如: %d %f
// 1) printf() --> conversion specification表 《c primer plus第六版》P195
//--------------------------------------------------------------
//字符串常量 编译器会添加空字符
#include
#include /* 该头文件提供strlen()函数 */
//编译程序的时候,程序中的符号常量会被替换成所定义的值(常量),编译时替换(compile-time substitution)
//符号常量后面的内容被用来替换符号常量 《c primer plus第六版》p189
#define C 'A'
#define NAME "Fu YunFeng." /* 定义符号常量 */
#define PI 3.141593 /* 定义符号常量(symbolic constant) */
void code_new_line(void);
void circle_float_arithmetic(void);
void circle_double_arithmetic(void);
void circle_long_double_arithmetic(void);
void define_and_const(void);
void stdio_include_stddef_size_type(void);
void conversion_specification_modifiers_and_flags(void);
void printf_function_stack_a(void);
void printf_function_stack_b(void);
void print_long_string_methods(void);
void main(void)
{
//c_var[40]是一个可容纳40个字符的数组
//该字符数组占用内存中连续 1*40个字节byte
//定义字符类型数组
char c_var[40];
//该短整型数组占用内存中连续 2*5个字节byte
short int si_var[5];
printf("character string 字符串,string constant Name: %s\n", NAME);
// \b 光标向左移一位,一个字符占一位
printf("Input You Name:__________\b\b\b\b\b\b\b\b\b\b");
scanf("%s", c_var);
printf("My Name is:%s\n", c_var);
//strlen()函数获取字符串的长度,获取空字符(null character)之前的字符串的长度
// strlen()函数 和 sizeof运算符 返回值为无符号整数类型(unsigned integer) 注:网上说的...不知道对不对
// 输出返回值用%zd或%u,C99和C11标准用%zd,较早的C用%u
// sizeof运算符用法示例: 1)sizeof(object); 2)sizeof object; 3)sizeof(type);
printf("string length, strlen(c_var): %u\n", strlen(c_var));
printf("string length, strlen(NAME): %u\n", strlen(NAME));
printf("sizeof(c_var): %u\n", sizeof(c_var));
//计算机主存每字节都有一个唯一的物理地址(physical address),物理寻址physical addressing
//&取数组变量的地址(虚拟地址virtual address\虚拟寻址virtual addressing)
//CPU芯片上的内存管理单元(memory management unit: MMU)将虚拟地址转换成物理地址
//《深入理解计算机系统第三版》 P560
printf("sizeof(&c_var): %u\n", sizeof(&c_var));
//&c_var是数组第一个字节的虚拟地址(个人理解,待验证)
printf("lx(&c_var)/64bit virtual address(hexadecimal): %lx\n", &c_var);
printf("sizeof(si_var): %u\n", sizeof(si_var));
printf("sizeof(&si_var): %u\n", sizeof(&si_var));
code_new_line();
circle_float_arithmetic();
circle_double_arithmetic();
circle_long_double_arithmetic();
define_and_const();
stdio_include_stddef_size_type();
conversion_specification_modifiers_and_flags();
printf_function_stack_a();
printf_function_stack_b();
print_long_string_methods();
return;
}
void code_new_line(void)
{
printf("233333333333333333333333333333333333333333, This is my name: %s\n", NAME);
//处理很长的printf()语句的两种方法:
// 1)
printf("233333333333333333333333333333333333333333, This is my name: %s\n",
NAME);
// 2)
printf("233333333333333333333333333333333333333333, ");
printf("This is my name: %s\n", NAME);
return;
}
//格式说明符(format specific)
//注:long double类型有点特殊,需要考虑硬件,操作系统等因素,虽然编译器有定义这个类型,我暂时用不到就不折腾了...
// [1] scanf()
// float: %f %e %a
// double: %lf %le %la
// long double: %Lf %Le %La
// [2] printf()
// %f %e 默认保留小数点后六位
// float: %f %e %a
// double: %f %e %a
// long double: %f %e %a
// 浮点数不加后缀,默认double类型
void circle_float_arithmetic(void)
{
float r, l, a;
printf("sizeof(r): %zd, sizeof(PI): %zd\n",
sizeof(r), sizeof(PI));
printf("input r(floating-point number):______\b\b\b\b\b\b");
scanf("%f", &r);
l = 2.0 * PI * r;
a = PI * r * r;
printf("circle_l(f): %f, circle_a(f): %f\n", l, a);
printf("circle_l(e): %e, circle_a(e): %e\n", l, a);
printf("circle_l(a): %a, circle_a(a): %a\n", l, a);
return;
}
void circle_double_arithmetic(void)
{
double d_r, d_l, d_a;
printf("sizeof(d_r): %zd,sizeof(PI): %zd\n",
sizeof(d_r), sizeof(PI));
printf("input d_r(floating-point number):______\b\b\b\b\b\b");
scanf("%lf", &d_r);
d_l = 2.0 * PI * d_r;
d_a = PI * d_r * d_r;
printf("circle_l(f): %f, circle_a(f): %f\n", d_l, d_a);
printf("circle_l(e): %e, circle_a(e): %e\n", d_l, d_a);
printf("circle_l(a): %a, circle_a(a): %a\n", d_l, d_a);
return;
}
void circle_long_double_arithmetic(void)
{
float f_a = 1.3F;
double d_a = 1.3;
printf("sizeof(f_a): %zd\n", sizeof(f_a));
printf("%.60f\n", f_a);
printf("%.60e\n", f_a);
printf("%.60a\n", f_a);
printf("sizeof(d_a): %zd\n", sizeof(d_a));
printf("%.60f\n", d_a);
printf("%.60e\n", d_a);
printf("%.60a\n", d_a);
return;
}
void define_and_const(void)
{
//const关键字(key woird)限定一个变量为只读,赋值编译器会报错
// short long signed unsigned const 限定符
//用法比define更灵活
// PI = 5.555; 符号常量不能更改其内容
// csi_b = 88; const限定变量只读,不可更改
const short int csi_a = 55, csi_b = 66;
printf("csi_a(%%hd)%hd\n", csi_a);
printf("csi_b(%%hd)%hd\n", csi_b);
// 计算机中储存的是 3.14159的近似值
// %% printf()转换说明 打印一个百分号
printf("PI(%%f): %f\n", PI);
printf("PI(%%.10f): %.10e\n", PI);
// double类型 尾数52位,因此只能计算最长值 %.13a,超过这个长度后面只是填充0而已
printf("PI(%%.13a): %.13a\n", PI);
printf("PI(%%.15a): %.15a\n", PI);
return;
}
// printf("格式化字符串/转换说明", 待打印列表)
// 待打印列表项: 常量,变量,表达式的值
// 《c primer plus第六版》P200 表4.5 转换说明修饰符
// 需要注意的: l和整数转换说明使用,L和浮点数转换说明使用
void stdio_include_stddef_size_type(void)
{
int i_a = 111;
float f_a = 1.0, f_b = 2.0, f_var_a = 1.0, f_var_b;
double d_var_a = 1.0;
size_t size_type_a;
// long unsigned int
// |
// V
// __SIZE_TYPE__
// |------------> 定义size_t类型(underlying type:底层类型)
// 具体怎么定义的要看头文件源码,目前水平有限,暂时理解到这了...
//
// stddef.h(在包含stdio.h时已包含其中,书本上说的,具体怎么实现我目前还不懂。。。)头文件中,
// C标准只规定了sizeof返回值类型为无符号整数类型
// #define __SIZE_TYPE__ long unsigned int
// (可移植性更好 sizeof返回值类型,不同编译器,
// unsigned int, unsigned long, unsigned long long都有可能)
// z修饰符和整数转换说明一起使用,表示size_t类型的值
// printf() conversion specification modifiers 输出函数转换说明修饰符
printf("sizeof(int): %zd\n", sizeof(int));
printf("sizeof(size_t): %zd --> long unsigned int\n", sizeof(size_t));
printf("sizeof(sizeof(int)): %zd --> long unsigned int\n",
sizeof(sizeof(int)));
printf("sizeof(float): %zd\n", sizeof(float));
printf("sizeof(double): %zd\n", sizeof(double));
printf("sizeof(1.0): %zd\n", sizeof(1.0));
printf("sizeof(f_a): %zd\n", sizeof(f_a));
printf("constant 99.99 (x): %x\n", 99.99);
printf("sizeof(f_b): %zd\n", sizeof(f_b));
//printf() 的浮点数转换说明为什么只有 %f
//历史遗留问题,为了兼容
//因为在K&R C中,表达式或参数中的float类型会被自动转换成double类型
//具体看《c primer plus第六版》P201
// f_a*f_b表达式返回值打印结果可看出,一般而言,ANSI C不会自动把float转换成double
//《c primer plus第六版》P201 第8行
//从f_var_a和d_var_a两个变量的打印结果可印证,printf()的float仍自动转换成double
//所以没有float类型的专用转换说明,float和double类型都是用%f
printf("sizeof(f_a*f_b): %zd\n", sizeof(f_a*f_b));
printf("sizeof(f_a*2.0): %zd\n", sizeof(f_a*2.0));
printf("sizeof(f_a*d_var_a): %zd\n", sizeof(f_a*d_var_a));
printf("f_var_a=1.0 (a): %a\n", f_var_a);
printf("d_var_a=1.0 (a): %a\n", d_var_a);
printf("f_var_a=1.0 (x): %x\n", f_var_a);
printf("f_var_a=1.0 (lx): %lx\n", f_var_a);
printf("d_var_a=1.0 (x): %x\n", d_var_a);
printf("d_var_a=1.0 (lx): %lx\n", d_var_a);
//《c primer plus第六版》P201 表4.5 printf()中的标记(flags)
// +标记 值为正,则前面显示+号;值为负,则前面显示-号
printf("f_a(%%+.2f): %+.2f\n", f_a);
// (一个空格)标记 值为正,则前面显示一个空格;值为负,则前面显示-号
printf("f_a(%% .2f): % .2f\n", f_a);
// %10d 整数字段宽度10(个人感觉10个字符宽度更容易理解)
printf("i_a(%%10d): *%10d*\n", i_a);
// %010d 整数字段宽度为10,数值位数不足的前面填充0
printf("i_a(%%010d): *%010d*\n", i_a);
// %-10d 整数字段宽度10, -标记 左对齐
printf("i_a(%%-10d): *%-10d*\n", i_a);
// 浮点数类型的修饰符和标记也是相同原理
// %10.2f 表示字段宽度10,保留小数点后两位,其它的就全部展示了
// 要了解看《c primer plus第六版》P203就好了
printf("11.3(%%e): *%e*\n", 11.3);
printf("11.3(%%.2e): *%.2e*\n", 11.3);
// rounding 舍入
printf("11.3(%%.1e): *%.1e*\n", 11.3);
printf("11.4(%%.1e): *%.1e*\n", 11.4);
printf("11.5(%%.1e): *%.1e*\n", 11.5);
printf("11.3(%%10.2e): *%10.2e*\n", 11.3);
printf("1.3(%%10.2e): *%10.2e*\n", 1.3);
// 下面的两种情况会对结果进行舍入rounding
printf("1.399(%%10.2e): *%10.2e*\n", 1.399);
printf("1.399(%%10.1e): *%10.1e*\n", 1.399);
return;
}
void conversion_specification_modifiers_and_flags(void)
{
const double cd_a = 123456.789;
float f_a = 3.0;
double d_a = 3.4e39;
printf("cd_a(%%.3f): %.3f\n", cd_a);
// %E e输出变大写字母E
printf("cd_a(%%E): %E\n", cd_a);
// flags # 显示前缀
printf("cd_a(%%#lx): %#lx\n", cd_a);
// %X 十六进制中的字母输出大写字母格式
printf("cd_a(%%#lX): %#lX\n", cd_a);
// .3d 整数数值位数不够的前面填充0
// 5d 整数数值位数不够的前面填充空格
// 相同点,整数数值位数足够或超过的,以原值为准输出
// 10d 字段宽度10
printf("6(*%%10.3d*): *%10.3d*\n", 6);
// character string 字符串 flags 标记
printf("NAME([%%s]): [%s]\n", NAME);
// %.5s 只输出字符串前面5个字符
printf("NAME([%%.5s]): [%.5s]\n", NAME);
// %30s 输出字符串宽度30,字符数不足的前面填充空格
printf("NAME([%%30s]): [%30s]\n", NAME);
printf("f_a(%%ld): %ld\n", f_a);
printf("d_a(%%ld): %ld\n", d_a);
return;
}
//标记: printf() --> - 左对齐; + 正数加正号,负数加负号; 空格; 0 填充0; # 前缀
// 注: %.5d(输出整数位数最少为5,不足补0)
// %.5f(保留小数点后5位)
// %.5s(输出字符串前5个字符)
// %5d %5f %5s (输出字段宽度为5)
//
// 转换说明的意义:
// 转换说明实际上是翻译说明,
// 把计算机中储存的二进制数"转换"(翻译)成文本(字符/字符串)打印出来
// 并不改变计算机中储存的数据(原始值)
//
//温故知新: binary division 二进制除法
// _____
// 1000_/ 1101
// 1) 最高位1不能分成1000份
// 2) 先把1拆成10份,10就是10份个1,加上后一位的1份,就是10+1=11
// 3) 后面同理,懒得写了...
// 这里我不转换成十进制来读,十进制来理解原理就好
//
void printf_function_stack_a(void)
{
unsigned short int usi_a = 16352U, usi_b = 49120U;
// 16352U 0011 1111 1110 0000
// 16352 0000 0000 0000 0000 0011 1111 1110 0000
// 49120U 1011 1111 1110 0000
// 49120 0000 0000 0000 0000 1011 1111 1110 0000
unsigned int ui_a = 3220176896, ui_b = 3220176896U;
unsigned long int uli_a;
double d_a = 1.0;
printf("ui_a(%%u): %u\n", ui_a);
//有符号整数常量4byte, 32bit编码储存不了有符号数3220176896,编译器转换成64bit储存
printf("int constant 3220176896(%%x): %x, sizeof(3220176896): %zd\n",
3220176896, sizeof(3220176896));
printf("int constant 3220176896U(%%x): %x, sizeof(3220176896U): %zd\n",
3220176896U, sizeof(3220176896U));
//为正数时前面的0输出是被省略了,输出负数时就可以看出前面是有填充1的
printf("int constant 3220176896(%%lx): %lx\n", 3220176896);
printf("int constant -3220176896(%%lx): %lx\n", -3220176896);
printf("int constant -3220176896(%%x): %x\n", -3220176896);
printf("3220176896U(%%lx): %lx\n", 3220176896U);
printf("usi_a(%%hu): %hu\n", usi_a);
printf("usi_a(%%a): %a\n", usi_a);
printf("usi_a(%%e): %e\n", usi_a);
printf("usi_a(%%x): %x\n", usi_a);
printf("usi_a(%%lx): %lx\n", usi_a);
printf("d_a(%%lx): %lx\n", d_a);
printf("1) 16.125(%%f): %f\n", 16.125);
printf("2) 16.125(%%lx): %lx\n", 16.125);
printf("3) 16.125(%%a): %a\n", 16.125);
printf("4) 16.125(%%e): %e\n", 16.125);
/*
exponent
true value binary two's complement excess_3
3 [ 011]--| |-- 011 --| |-- 110 --| |-- 1 011
2 [ 010] | | 010 | | 101 | | 1 010
1 [ 001] | | 001 | | 100 | | 1 001
0 [ 000] | map | 000 | +011 | 011 | -011(+101) | 1 000
-1 [-001] | | 111 | | 1 010 | | 111
-2 [-010] | | 110 | | 1 001 | | 110
-3 [-011] | | 101 | | 1 000 | | 101
-4 [-100]--| |-- 100 --| |-- 111 --| |-- 1 100
binary --> two's complement -- +011 --> excess_3
binary 000(包括000)以上+011为正数加正数
这运算结果区间都为正数(这区间的结果并不是结果的二补数编码,因为编码位数不够,最高位符号位为1,会有溢出)
binary -001(包括-001)以下+011为负数加正数,这里负数转换为二补数进行加法运算
运算结果为正,则modulo模除1000得到正确结果;运算结果为负,则得到结果的绝对值的二补数
excess_3 000(包括000)以上非负数,111唯一负数的二补数,
-011转换成+101二补数运算
结果000(包括000)以上,结果为正,模除1000得到正确结果
[101,111]负数结果区间,得到正确结果绝对值的二补数
1 100由于两个负数相减,转换成二补数运算,相当于加了两次1000,所以模除1000后得到正确结果绝对值的二补数
阶码:二补数运算。 有符号整数:二补数编码运算
*/
return;
}
void printf_function_stack_b(void)
{
//参数传递 stack栈 《c primer plus第六版》P212
//程序把传入的值放入被称为栈的内存区域
//计算机根据变量类型把这些值放入栈中
//参数传递机制因编译器实现而异
//书本中可能编译器的版本问题
//目前我用的编译器并未出现里面所介绍的输出错误
float n1 = 3.0;
double n2 = 3.0;
short int si_return_value;
int i_return_value;
long int n3 = 2000000000;
long int n4 = 1234567890;
printf("n1(%%ld): %ld,n2(%%ld): %ld,n3(%%ld): %ld,n4(%%ld): %ld\n",
n1, n2, n3, n4);
printf("n1(%%d): %d,n2(%%d): %d,n3(%%d): %d,n4(%%d): %d\n",
n1, n2, n3, n4);
// 大部分C函数都有一个返回值
// 可赋值给变量,可作为参数传递,可参与运算
// *总之,就是可以把返回值当作其它数值一样使用
//下面两句代码,输出且把返回值赋值给变量
si_return_value = printf("12345 67890: %f\n", n2);
i_return_value = printf("12345 67890: %f\n", n2);
// 标准C库函数printf()返回值为打印字符的个数
// 由运行输出结果可得到printf()函数返回值,
// 有结果可知计算字符个数结果包括:空格,转义序列(转义字符)
printf("return value of printf function (%%hd): %hd\n", si_return_value);
printf("return value of printf function (%%hd): %d\n", i_return_value);
printf("sizeof(si_return_value): %zd\n", sizeof(si_return_value));
printf("sizeof(si_return_value): %zd\n", sizeof(i_return_value));
// printf()返回值是一个整数类型,size: 4byte
// 是int还是unsigned int还要验证...
printf("sizeof(printf function return value): %zd\n",
sizeof(printf("12345 67890,%f\n", n2)));
return;
}
void print_long_string_methods(void)
{
// 打印较长字符串的三种方法:
// [1]
printf("abcd");
printf("efg\n");
// [2]
// ANSI c引入的字符串连接
// 两个双引号括起来的字符串之间用空格隔开
// C编译器会把这两个字符串看作是一个字符串
// 多个字符串也一样
// 比较容易记的说法: C编译器把多个用空格隔开的字符串看作一个字符串
printf("a" "b" "c\n"); /*本质*/
printf("abcd"
"efg\n");
printf("123"
"456"
"789\n");
// [3] 注:字符串的第二行代码必须从最左边开始,
// 不然前面的空格会成为字符串一部分,输出的字符串就会包含空格
/*
printf函数中的字符串常量不能含有enter回车键产生的换行符
包含的话编译器会报错
示例:
printf("abcd
efg\n");
*/
// 反斜杠\ 加 enter(回车)的组合 --> \换行符
printf("a\
bcd\n");
//通过输出结果可知字符串代码第二行前面的空格是两个非显示字符
//*字符串在内存的存储并非我想的那么简单,目前水平有限,先跳过。。。--'
printf("%zd\n", printf("a\
bc\n"));
printf("%lx\n", "a\
bc\n");
printf("%zd\n", printf("a\
b\n"));
printf("%lx\n", "a\
b\n");
return;
}
#include
// 《c primer plus第六版》P217 第三段重点
// *键盘只能生成文本字符: 字母,数字,标点符号
// 要输入整数1024
// 键盘键入字符 1 0 2 4
// scanf函数 把 字符串 转化成 整数1024
// character 字符; character string 字符串.
//
//printf()和scanf()函数参数:
// [1] printf()函数使用变量,常量,可求值的表达式,有返回值的函数
// [2] scanf()函数使用指向变量的指针
//
//scanf()读取基本类型变量的值,在变量名前加&
//scanf()读取字符串,储存到字符数组中,不使用&
void scanf_format_character_string(void);
void scanf_conversion_specification_character(void);
//void scanf_return_value(void);
void printf_star_modifier(void);
void scanf_star_modifier(void);
void usage_tips_for_printf(void);
void main(void)
{
int age_a, age_b, age_c, age_d;
float f_asset_a, f_asset_b, f_asset_c, f_asset_d;
double d_asset_a;
char name_a[10], name_b[10], name_c[10], name_d[10];
printf("sizeof(f_asset_a): %zd,sizeof(d_asset_a): %zd\n",
sizeof(f_asset_a), sizeof(d_asset_a));
printf("input age asset name: ");
// 每个输入项之间至少一个空白(换行符、空格、制表符)
// scanf()函数使用空白把输入分成多个字段
// 依次把转换说明和字段匹配时跳过空白
// 内存区域:
// [...|数字字符|空白字符|数字字符|空白字符|字母字符|...]
// 举例: scanf()函数 使用%d转换说明
// 我想输入整数32
// 使用键盘键入文本字符1和8
// 3 2
// 数字字符 数字字符
// 空白字符会被scanf()函数跳过
// 遇到第一个数字字符或+/-符号字符开始读取,保存后再读取下一个字符
// 读取到非数字字符,scanf()函数把非数字字符放回输入
// 程序下一次读取时,从这个放回的非数字字符读取
// 因此输入 32 40000fyf (正常的输入方式 -->:32 40000 fyf)
// scanf()函数读取的结果也是正确的
scanf("%d %lf %s", &age_a, &d_asset_a, name_a);
// scanf() double类型转换说明要加修饰符l %lf,与pirntf()不同
// conversion specification 转换说明
// modifier 修饰符
// *《c primer plus第六版》P220 scanf()转换说明,修饰符
printf("Age: %d, Asset: %.2f, Name: %s\n", age_a, d_asset_a, name_a);
// 打印变量的指针(地址)
// %p 指针
printf("integer variable age virtual address: %p\n", &age_a);
// %s %d 转换说明顺序
// scanf()函数先读取非空白字符,读取一个,保存,再读取下一个字符
// 读取到空白字符,读取结束(与转换说明%s匹配的字符串字段读取结束)
// 再读取下一个与转换说明%d匹配的整数字段
printf("input name age: ");
scanf("%s %d", name_b, &age_b);
printf("Name: %s, Age: %d\n", name_b, age_b);
// %d %s 转换说明顺序
// 如果输入A,scanf()函数读取到A后,将停止读取,并将A放回输入
// 不会把值赋给指定变量
// ***如果scanf()函数带多个转换说明,C规定在第一个出错处停止读取输入(后面的读取全部停止)
printf("input age name: ");
scanf("%d %s", &age_c, name_c);
printf("Age: %d, Name: %s\n", age_c, name_c);
printf("age(%%d): %d\n", age_d);
printf("age(%%x): %x\n", age_d);
printf("&age(%%x): %x\n", &age_d);
// %d 一个转换说明(注:这里假设每个scanf函数只有一个转换说明,因此一次读取就是一个scanf函数调用)
// 输入A,scanf()函数停止读取,把A字符放回输入
// 程序下一次读取时,从这个A字符开始读取
// 如果程序只有%d输入转换符,scanf()函数就一直无法越过A字符
printf("input age: ");
scanf("%d", &age_d);
printf("age(%%d): %d\n", age_d);
printf("age(%%x): %x\n", age_d);
printf("&age(%%x): %x\n", &age_d);
// %s
// scanf()函数跳过空白字符,直到遇到第一个非空白字符
// 读取,保存,再读取下一个字符,保存
// 直到再次遇到空白字符,这个空白字符不保存
// (这个空白字符放不放回输入不知道,书上没写。。。不过放不放也没啥意义)
printf("input name: ");
scanf("%s", &name_d);
printf("name_d(%%s): %s\n", name_d);
scanf_format_character_string();
scanf_conversion_specification_character();
//scanf_return_value();
printf_star_modifier();
scanf_star_modifier();
usage_tips_for_printf();
return;
}
// 注: 在C语言中,scanf()并不是最常用的输入函数,但它能读取不同类型的数据
// C语言还有其它的输入函数,例如: getchar()和fgets(),会在后面的章节中介绍
void scanf_format_character_string(void)
{
int i_sfcs_a, i_sfcs_b, i_sfcs_c, i_sfcs_d;
// scanf函数允许在格式字符串中加入普通字符
// * 空白字符包括(空格字符,制表字符,换行字符和没有空白字符的特殊情况)
printf("input i_sfcs_a(int), i_sfcs_b(int);");
printf("(%%d,%%d): ");
//scanf(",%d%d", &i_sfcs_a, &i_sfcs_b);
//从上一个回车键输入后已经开始读取
//回车键产生的换行符与,逗号不匹配,多个转换说明,
//根据C标准规定,这个scanf函数直接停止读取
//*因此普通字符并不像转换说明一样(前面加空白字符的特殊情况:即不加空白字符)可以跳过空白字符然后读取
//要解决这个问题,在普通字符前加空白字符(非特殊情况),解决方法如下
//scanf(" ,%d%d", &i_sfcs_a, &i_sfcs_b);
scanf("%d,%d", &i_sfcs_a, &i_sfcs_b);
printf("i_a,i_b: %d,%d\n", i_sfcs_a, i_sfcs_b);
//scanf格式字符串中,[转换说明前]加空白字符(包括无空白字符的特殊情况,就是无字符)
//[转换说明前]加空白字符包括特殊情况作用是匹配输入项时跳过空白字符.
//scanf格式字符串中,转换说明加普通字符
//输入时格式要与scanf格式字符串的格式相匹配,
//才能让scanf函数能够从缓冲区中正确读取数据
//* 简单说,转换说明前有空白字符(包括无空白字符的特殊情况),
// scanf函数读取输入时就会跳过输入到缓冲区的空白字符
//* 转化说明之间有普通字符,输入时就要输入普通字符与格式字符串匹配
//例如: scanf("%d,%d"); 第一个输入项: 32,
//输入:(读取时跳过空白字符)整数类型|,逗号|(读取时跳过空白字符)整数类型
// 注: 第二个%d匹配输入项,后面可以输入空字符,scanf读取第二个整数输入项时,
// 遇到空白字符,会停止第二个输入项的数据读取,把空字符放回输入缓冲区,下一次读取时从这个空白字符读取
printf("input i_sfcs_a(int), i_sfcs_b(int);");
printf("(%%d : %%d): ");
scanf("%d : %d", &i_sfcs_c, &i_sfcs_d);
printf("%d %d\n", i_sfcs_c, i_sfcs_d);
return;
}
void scanf_conversion_specification_character(void)
{
int i_a, i_b, i_c, i_d;
char c_a, c_b;
printf("input int i_a i_b(%%d%%d):");
scanf("%d%d", &i_a, &i_b);
printf("%d,%d\n", i_a, i_b);
printf("input int i_a i_b(%%d %%d):");
scanf("%d %d", &i_c, &i_d);
printf("%d,%d\n", i_c, i_d);
// %c与普通字符情况一样,前面加空白字符的特殊情况(即不加空白字符)
// 都是不会跳过输入时的空白字符的
// scanf("%c") 和scanf(" %c")是有区别的
// (注: scanf函数的%c是特殊情况,其它转换说明无这种区别)
// %c读取一个字符,包括回车键生成的换行符
// 假设执行到上一个scanf函数,我们输入数据,按回车,
// 生成的换行字符会存入输入缓冲区
// scanf("%c")会读取这个换行字符
// scanf(" %c")则可正确读取,不会读取输入缓冲区中的换行字符(ascii码)
// scanf转换说明前没有空格
printf("input char c_a(%%c):");
scanf("%c", &c_a);
printf("c_a(%%c):%c\n", c_a);
//换行符ascii码是10,C语言换行符是'\n'
//空格符(space)ascii码是32
printf("c_a(%%hhu):%hhu\n", c_a);
// scanf转换说明前有空格
printf("input char c_b( %%c):");
scanf(" %c", &c_b);
printf("c_b(%%c):%c\n", c_b);
// 'A' ascii码是65
// %hhu printf和scanf都有这个转换说明
// 作用:输出或输入一个一个字节的无符号整数
printf("c_b(%%hhu):%hhu\n", c_b);
return;
}
// 总结: %d 转换说明匹配输入项
// |空白字符前|整数类型数据|空白字符后|
// | |
//跳过空白字符 读取开始 读取结束 读到空白字符后,放回输入缓存区
//程序有下一个scanf函数的话,从放回的空白字符开始读取
//
// scanf 转换说明 例如: scanf("%d"); 和 scanf(" %d"); 是等价的
// scanf格式字符串的空白字符(包括无空白字符的特殊情况)作用是跳过输入缓冲区的空白字符
// %c 是个例外 scanf("%c"); 和 scanf(" %c"); 是不等价的
// scanf格式字符串中的普通字符,输入时要输入相匹配的普通字符,不匹配的话会停止读取
// 普通字符并不会被保存,只有与转换说明相匹配的输入被保存到指定变量
// scanf只有单个转换说明,输入不匹配,scanf会停止读取,程序有下一个scanf的话,下一个scanf继续读取
// scanf如果有多个转换说明,输入不匹配的话,C标准规定程序停止读取,即使后面代码中还有scanf
//void scanf_return_value(void)
//{
// //scanf返回值为int类型
// // 转义序列(escape sequence)
// // \" 反斜杠后面的字符,不是它ascii字符本来的意思了
// // " C中原意是字符串符号, \" 转义序列,意思是双引号符号字符本身
// // %d 转换说明,转换成整数类型
// // %% 转换说明,转换成百分号字符本身
//}
// * modifier *修饰符
// printf("%*d", width, integer); *表示字段宽度,不想预先指定字段宽度时用*代替
void printf_star_modifier(void)
{
unsigned int before, after;
double d_a = 1111111.1111;
printf("input width before/after decimal point: ");
scanf("%d %d", &before, &after);
// %*.*f 第一个*表示浮点数字段宽度,位数不足的填充空格,位数足够或超过的原样输出即可
// 第二个*表示浮点数小数点后保留位数
printf("%*.*f\n", before, after, d_a);
return;
}
void scanf_star_modifier(void)
{
int ssm_i_a;
//scanf的*修饰符与printf的*修饰符作用不同
//scanf("%*d%*d%d", &int);
//读取输入缓冲区时,跳过前两个输入项,读取和保存第三个输入项到指定变量
printf("&ssm_i_a(%%*d%%*d%%d): ");
scanf("%*d%*d%d", &ssm_i_a);
printf("ssm_i_a(%%d): %d\n", ssm_i_a);
return;
}
// usage 用法
void usage_tips_for_printf(void)
{
int utfp_i_a, utfp_i_b, utfp_i_c, utfp_i_d, utfp_i_e, utfp_i_f,\
utfp_i_g, utfp_i_h, utfp_i_i, utfp_i_j, utfp_i_k, utfp_i_l;
printf("input(%%d %%d %%d %%d %%d %%d): ");
scanf("%d %d %d %d %d %d",
&utfp_i_a, &utfp_i_b, &utfp_i_c, &utfp_i_d, &utfp_i_e, &utfp_i_f);
printf("input(%%d %%d %%d %%d %%d %%d): ");
scanf("%d %d %d %d %d %d",
&utfp_i_g, &utfp_i_h, &utfp_i_i, &utfp_i_j, &utfp_i_k, &utfp_i_l);
printf("%d %d %d %d %d %d\n",
utfp_i_a, utfp_i_b, utfp_i_c, utfp_i_d, utfp_i_e, utfp_i_f);
printf("%8d %8d %8d %8d %8d %8d\n",
utfp_i_a, utfp_i_b, utfp_i_c, utfp_i_d, utfp_i_e, utfp_i_f);
return;
}
#include
//定义符号常量,预编译时后面的字符串替换前面的符号常量(文本)
#define OPERATORS "= + - * /"
#define SECOND_PER_MINUTE 60
void while_block(void);
void addition_substraction_operator(void);
void sign_operator(void);
void sixty_four_game(void);
void multiplication_division_operator(void);
void operator_precedence(void);
void sizeof_operator_and_size_type(void);
void second_to_minute(void);
int main(void)
{
// operator:运算符; expression:表达式; statement:语句;
// operand:运算数,操作数;
// = 赋值运算符(assignment operator)
//格式字符串中的转换说明%s,后面的待输出项类型和熟练要与转换说明相匹配
printf("These is operators: %s\n", OPERATORS);
while_block();
addition_substraction_operator();
sign_operator();
sixty_four_game();
multiplication_division_operator();
operator_precedence();
sizeof_operator_and_size_type();
second_to_minute();
return 0;
}
void while_block(void)
{
//这里的 =运算符 表示 初始化而不是 赋值
const int ONE = 1;
//把整数值0赋给变量start,赋值表达式
// 可修改的左值 = 表达式的值(包括常量,变量,可求值的表达式)
int start = 0;
// C的当前标准
//(早期C标准的lvalue: left value
//,rvalue: right value的定义已经不能满足当前的状况)
// 左:
// *modifiable lvalue(可修改的左值)
// object locator value(对象定位值)
// 右:
// *value of an expression(表达式的值)
// 包括常量,变量,可求值的表达式
//《c primer plus第六版》中文版的定义:
//赋值语句的目的是把值储存到内存位置,储存值的内存存储区域称为数据对象(data object)
//我自己根据《c primer plus第六版》英文版的翻译
//考虑一个赋值语句.它的目的是储存一个值在一个内存位置.
//数据对象是一个 用于存储整个值的数据存储区域的 通用术语
while (start<10)/* starting the while loop */
//花括号之间的内容是要被重复的内容,用计算机术语说,就是程序循环这些语句
/* start of block,花括号以及花括号括起来的部分被称为block */
{
// 目前个人的理解,下面整句为赋值语句,
// start + 1为表达式的值(可求值的表达式),它是程序计算的一个临时值,之后会被丢弃,应该是赋值给左值后丢弃吧,个人推测...
// 常量是由系统在main函数运行前把常量赋值给常量的...(网上的说法,也不知道对错,待以后验证吧...)
// 只有变量是可以引用内存地址的(个人理解,待验证,目前知识水平有限...)
start = start + 1;
// operand(the item项 to the left of =) = operator operate on
// 操作数(运算数)[赋值运算符左侧的项] = 操作数(运算数) 操作符(运算符)操作(运算) 操作数(运算数)
// modifiable lvalue(可修改的左值) = 操作数(运算数) 操作符(运算符)操作(运算) 操作数(运算数)
printf("%d\n", start);
} /* end of block */
printf("The End!\n");
return;
}
//addition operator和subtraction operator称为bianry operator(二次元...哦,不,二元运算符XD)
//即这些运算符需要两个运算数(operand)才能完成运算
void addition_substraction_operator(void)
{
int ao_i_a, ao_i_b, ao_i_c;
//assignment statment,赋值语句;assigement operator,赋值运算符;
//可修改的左值(modifiable lvalue) = 表达式的值(an value of expression)(变量,常量,可求值的表达式)
ao_i_a = 2;
ao_i_b = 1;
// 关键知识点:
// * ao_i_a + ao_i_b表达式,程序计算的一个临时值,计算完成后会被丢弃,它并不能表示特定的内存位置,
// * 个人推理:计算出的临时值,应该直接从寄存器直接赋给变量(可修改的左值的内存存储区域:数据对象)了,很合理吧...
// * 因此不能赋值给它
ao_i_c = ao_i_a + ao_i_b;
printf("ao_i_a + ao_i_b = %d\n", ao_i_c);
ao_i_c = ao_i_a - ao_i_b;
printf("ao_i_a - ao_i_b = %d\n", ao_i_c);
return;
}
// sign operator(符号运算符): + 和 -
// unary operator(一元运算符),完成运算只需要一个运算数(operand)
void sign_operator(void)
{
int so_i_a, so_i_b;
so_i_a = 5;
so_i_b = -so_i_a;
printf("so_i_b = -so_i_a = %d\n", so_i_b);
return;
}
// count初始化等于1
// while循环,循环一次,count+1
// 循环1: count = 2
// 循环2: count = 3
// ... ...
// 循环63: count = 64
// 循环条件count<64,语句块循环了63次,count=64,循环停止
void sixty_four_game(void)
{
int count = 1;
double number = 1.0, total = 1.0;
printf("count ");
printf("number ");
printf("total\n");
printf("%3d. %22.1f %25.1f\n", count, number, total);
while (count<64)
{
count = count + 1;
// * multiplication operator乘法运算符
number = 2.0 * number;
total = total + number;
printf("%3d. %22.1f %25.1f\n", count, number, total);
}
printf("The Max of number: %22.1f\n", number);
printf("The Max of total: %23.1f\n", total);
printf("The End!\n");
return;
}
//*打通数学的任督二脉...
//数学知识温习,前面的c文件也有,但有点难找,懒得翻回去找了...
//数学源于生活-->形象-->抽象
//形象:
// 9 / 3 = 3 1. 9根辣条,分成3份,每份3根
// 9 / 1 = 9 1. 9根辣条,分成1份,每份9根
// 9 / 1/9 = 81 1. 9跟辣条,分成1/9份,每份81根(9根就是1/9份,因此,一份就是81!!!)
// (9除以1/9这个我想了挺久,我感觉这个形象最靠谱...)
//抽象:
// 在代数体系中,除法,减法的定义
// 除于一个数等于乘于这个数的乘法逆元(倒数)
// 减去一个数等于加上这个数的加法逆元(相反数)
//division operator除法运算符 /
void multiplication_division_operator(void)
{
int var;
//整数除法
//在C语言中,整数除法的计算结果小数部分会被丢弃,称为截断(truncation)
//C99规定 趋零截断
printf("7/4 = (%%d): %d\n", 7/4);
printf("7/4 = (hexadecimal): %x \n", 7/4);
printf("var = 7/4 = (hexadecimal): %x \n", var = 7/4);
//欠7根辣条,分成4份,每份欠1.75根
printf("-7/4 = (integer): %d \n", -7/4);
printf("-7/4 = (hexadecimal): %x \n", -7/4);
printf("var = -7/4 = (hexadecimal): %x \n", var = -7/4);
// 7 4bit two's complement: 0111
// -7 4bit two's complement: 1001
// 4 4bit two's complement: 0100
// -4 4bit two's complement: 1100
// 7/-4 --> 0111/1100 --> 0111/0100 -->
// 1.11
// ____________
// 0100_/ 0111
// 0100 下面的注释:除了写明是十进制的,其余的都是二进制数
// --------------
// 11 0 <-- 11 拆成 110份 每份 (1/2)10
// 10 0
// --------------
// 1 00
// 1 00 <-- 10份(1/2)10 拆成10份 (1/4)10
// --------------
// 0
// 1.11小数部分会被截断(truncation),小数部分丢弃
// --> 1 --> 0001 --> 有一个运算数为负数(二补数编码最高位为1) --> 1111
printf("7/-4 = (%%d): %d\n", 7/-4);
printf("-7/-4 = (%%d): %d\n", -7/-4);
//浮点数除法
//浮点数常量为双精度浮点类型double
printf("sizeof(7.0)(%%zd): %zd\n", sizeof(7.0));
printf("7.0 = (%%lx):%lx\n", 7.0);
printf("4.0 = (%%lx):%lx\n", 4.0);
printf("7.0/4.0 = (%%f) %f\n", 7.0/4.0);
printf("7.0/4.0 = (%%lx) %lx\n", 7.0/4.0);
// operand_1:
// 7.0 --> 111 * (2^0)10 --规格化--> 1.11 * (2^2)10
// exponent: bias_1023 --> 2 + 1023 = 1025
// 00000000010 阶码进行二补数运算(非二补数编码运算)
// + 01111111111
// -----------------
// 10000000001
// mantissa: 1.11 --隐含1.--> 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
// sign exponent mantissa
// 0 10000000001 1100000000000000000000000000000000000000000000000000
// hexadecimal: 401c000000000000
//
// operand_2:
// 4.0 --> 100 * (2^0)10 --规格化--> 1.00 * (2^2)10
// exponent: bias_1023 --> 2 + 1023 = 1025
// 00000000010 阶码进行二补数运算(非二补数编码运算)
// + 01111111111
// -----------------
// 10000000001
// mantissa: 1.00 --隐含1.--> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
// sign exponent mantissa
// 0 10000000001 0000000000000000000000000000000000000000000000000000
// hexadecimal: 4010000000000000
//
// operand_1 / operand_2:
// 1.11 * (2^2)10 / 1.00 * (2^2)10
// = 111 / 100 = 1.11 --> 1.11 * (2^0)10
// exponent: bias_1023 --> 0 + 1023 = 1023
// 00000000000 阶码进行二补数运算(非二补数编码运算)
// + 01111111111
// -----------------
// 01111111111
// mantissa: 1.11 --隐含1.--> 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
// sign exponent mantissa
// 0 01111111111 1100000000000000000000000000000000000000000000000000
// hexadecimal: 3ffc000000000000
//
// * 整数和浮点数乘除法:
// * 关于sign的问题,乘法或除法,有一个operand为负,则运算结果sign为1;
// * 两个operand都为正或都为负,则运算结果sign为0;
// * 乘除法: 直接乘除
// * 加减法: exponent转换成与大的expoent一致,尾数进行加减运算
//CPU ALU(算术逻辑单元)必须按这个数学运算规则来实现乘除运算
//只能根据C标准的浮点数存储格式和算法算出结果,但运算过程怎么实现的目前还不知道
//(数字电路还没学,只有一点浅薄的硬件知识,理解未必正确或精准)
//
// ______
// 1.1 _/ 11.01
// 11.01/1.1 = 11.01*10/1.1*10 = 110.1/11
//
printf("-7.0/4.0 = %f\n", -7.0/4.0);
printf("-7.0/4.0 = %lx\n", -7.0/4.0);
printf("7.0/-4.0 = %f\n", 7.0/-4.0);
printf("7.0/-4.0 = %lx\n", 7.0/-4.0);
printf("-7.0/-4.0 = %f\n", -7.0/-4.0);
printf("-7.0/-4.0 = %lx\n", -7.0/-4.0);
//浮点数整数混合,一般情况下,还是要避免这种情况
//计算机并不能真正浮点数和整数相除,浮动数和整数在内存中的存储方式完全不同
//下面两句代码中,在进行除法运算前,编译器会把整数转换为浮点数
printf("6.0/3 = %f\n", 6.0/3);
printf("6/3.0 = %f\n", 6/3.0);
return;
}
// *乘法与除法运算,同号为正,异号为负(不深究的话其实记住这个就足够了)
//如果要刨根问底,这就说来话长了...
//
//预备知识:
// N自然数包括 0,正整数 (N*,N+非零自然数,正整数)
// Z整数包括 负整数,0,正整数
// N+正整数集合,在加法运算下是封闭的,即两个正整数相加还是正整数
// 负整数是有了0之后,从正整数集拓展出来的数(可以看作正整数集关于零的对称集)
// 实数包括 有理数和无理数
// 有理数:包括所有分数,整数, 总能写成整数,有限小数,无限循环小数
// 无理数:无限不循环小数,例如:圆周率,根号2
//
//从自然数集说起...
//自然数集N
//自然数集规定了加法和乘法以后,可以证明:
// 加法对称性:
// 加法结合律: (1 + 2) + 3 = 1 + (2 + 3)
// 乘法对称性:
// 乘法结合律: (1 * 2) * 3 = 1 * (2 * 3)
// 乘法对加法的左右分配律: (2 + 5)*2 = 2*2 + 5*2
// 2 2 --> 两个2=4 --|
// |--> 两个2加两个5 = 14
// 5 5 --> 两个5=10 --|
// | |
// 7 7 --> 两个7相加 = 14
//
//非零自然数n的加法逆元是-n,称为"负整数"
//非零自然数称为"正整数"
// a + (-b) 简记成 a-b
//减法其实就是加法运算和取逆运算的一个复合
//
//人们希望 加法具有 对称性 和 (乘法)对加法的左右分配律
// x = (-a)*b
// x + a*b
//= (-a)*b + a*b
//= (-a + a)*b
//= 0*b
//= 0
//人们把(-a)*b定义为a*b的加法逆元,也就是-(a*b)
//异号为负,(-a)*b=a*(-b)=-(a*b)
// x = (-a)*(-b)
// x + a*b
//= (-a)*(-b) + a*b
//= [(-a)*(-1) + a]*b --> (-1)*b = -b 结果是b的加法逆元 --> (-1)*(-a) = -(-a) 结果是(-a)的加法逆元
//= [-(-a) + a]b
//= (a + a)b
//= ab + ab
//因此把(-a)*(-b)定义为a*b???这个是我自己推论的,不知道对不对,上面的是知乎大佬的原文...
//啊...不纠结这个了...先跳过这里,以后再深究...
//目前记住乘法和除法同号为正,异号为负就行了... 20200227
// operator precedence 运算符优先级
void operator_precedence(void)
{
int result;
//跟数学四则运算规则是一样的,那必须的,要不然有啥意义呢...
// 优先级: 小括号() > +/-正负符号 > 乘法/除法 > 加法/减法 > =赋值运算符
// 优先级相同,顺序从左到右 (=运算符除外)
result = 5 + 2*8/4;
printf("result(%%d): %d\n", result);
printf("5 + 2*8/4 =(%%d): %d\n", 5 + 2*8/4);
printf("5 + 2*8/-4 =(%%d): %d\n", 5 + 2*8/-4);
printf("%d\n", -(3+2)*6+(4+3*(8+5)));
// -(3+2)*6+(4+3*(8+5))
//= -5*6+(4+3*(8+5))
//= -5*6+(4+3*13)
//= -5*6+(4+39)
//= -5*6+43
//= -30+43
//= 13
return;
}
void sizeof_operator_and_size_type(void)
{
// size_t类型
// 文件test7.c中,stdio_include_stddef_size_type()函数的注释中有介绍
// 目前我正在用的编译器,size_t类型是long unsigned int的底层类型
// size_t类型让程序的移植性更好,不同编译器的size_t类型的
// (上层类型不相同,为了更容易描述我自己定义的,并没有上层类型这个概念)
size_t st;
printf("sizeof(st): %zd\n", sizeof(st));
// typedef关键字 可为现有类型创建别名,例如:
// typedef int integer
// integer n = 3;
// 使用size_t类型时,头文件可用typedef关键字根据不同系统替换标准的类型
return;
}
// % modulus operator求模运算符
// 求模运算符只用于整数
// 左侧整数除以右侧整数的余数
void second_to_minute(void)
{
int second, min, sec;
printf("input second(negative number->quit): ");
// 空白字符包括转换说明前无空白字符的情况(%c除外)
// 读取输入缓冲区时会自动跳过 非符号字符 和 数字字符
scanf("%d", &second);
// * 记住一个重点: 整数常量是有符号整数!!!(无符号整数常量要加后缀u或U)
// 这样就能清楚知道,整数常量在内存中的储存方式
// 条件表达式看变量的类型,根据以下规则判断大小即可,OK了~
// 无符号整数0000最小,1111最大
// 突然有点明白java为什么没有无符号整型了...
// 有符号整数0111最大,0000,1000最小
// 1. 符号位0>1
// 2. 符号位0,111-->000 由大到小
// 3. 符号位1,111-->000 由大到小
//数值位由高位向低位比较,因为1000>0111,
//因为 后面位值最小的情况 也比 后面位值最大的情况 大
//所以高位数值大的数,它的值就大
while (second >= 0)
{
// 结果小数截断(truncation)
// 数学上的除法理解为拆分成60份
// 这里的除法可以形象化为60一份,最大可以分几份,最小单元为1,不拆分
min = second / SECOND_PER_MINUTE;
// 结果为余数
// 拆分60一份,余下不足60的数
// / 运算,同号为正,异号为负.
// % 运算,第一个运算数为正,则结果为正
// 第一个运算数为负,则结果为负
// * C标准规定,可通过 a-(a/b)*b 来计算 a%b (a,b为整数)
// 这里的取模运算可以形象化为60一份,最大可以分几份,最小单元为1,不拆分
// 剩余不足60的余数就是结果
sec = second % SECOND_PER_MINUTE;
printf("(minute:second) %d:%d\n", min, sec);
printf("input second(negative number->quit): ");
scanf("%d", &second);
}
printf("The End\n");
return;
}
#include
void decrement_operator(void);
void increment_decrement_precedence(void);
//汇编语言 assembly language
//汇编知识(这玩意边看边查就好,不常用的话记不住的,搞不好掉头发...):
// 带.前缀的语句为链接的语句
// mov 把内存内容赋给寄存器(拷贝,复制等...)
// lea(load effective address加载有效地址) 1.把内存地址赋给寄存器;2.把值赋给寄存器
// q l w b 分别为 64bit 32bit 16bit 8bit (大小指示符)
// 例如: movq,movl,leaq,leaw
// $1 1为立即数,立即数前必须放一个$ (AT&T格式)
// 0x1 1为立即数,立即数前必须放一个0x (intel格式)
//
// * 16个通用寄存器 《深入理解计算机系统第三版》 P120 图3-2
// * %eax就是64位的%rax的一部分,占32位
// AT&T格式 寄存器前缀% 例如: %rax
// intel格式 寄存器无前缀 例如: rax
//
// 寄存器怎么用应该也不是绝对的,是不断发展的...所以暂时别管它们特定的用途
// %eax 累加寄存器: 函数返回值要存放在这个累加寄存器,返回值64bit,则低32bit存%eax,高32bit存%edx
// %ebx 基地址寄存器: 存放计算中间值(个人猜测应该是前面所说的程序计算的临时值吧...),或一个指针
// push pushq 压栈
// pop 出栈
//
// * objdump: gun的反汇编器(disassembler) 反汇编-->可重定位目标程序(二进制文件) 后缀为 .o
// * 类似抓包工具(tcpdump) dump: v (尤指在不合适的地方)丢弃; n 垃圾场
// *
// * gcc -Og 优化调试(debug)体验,支持不影响调试的优化
// * gcc -O0 默认编译级别
// *
// * #include
// * void main(void){
// * int i = 1, j;
// * j = ++i;
// * return;
// * }
// *
// * gcc -c xxx.c
// * objdump -d xxx.o
// *
// * 0000000000000000 :
// * 0: 55 push rbp
// * 1: 48 89 e5 mov rbp,rsp
// * 4: 48 83 ec 30 sub rsp,0x30
// * 8: e8 00 00 00 00 call d
// * d: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1 (立即数1存入内存地址)
// * 14: 83 45 fc 01 add DWORD PTR [rbp-0x4],0x1 (内存地址存储的值加1,计算过程当然还要经过ALU和寄存器)
// * 18: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] (%eax一般存放返回值)
// * 1b: 89 45 f8 mov DWORD PTR [rbp-0x8],eax (拷贝%eax值到内存地址)
// * 1e: 90 nop
// * 1f: 48 83 c4 30 add rsp,0x30
// * 23: 5d pop rbp
// * 24: c3 ret
// * 25: 90 nop
// 学会查看可重定位目标程序的反汇编文件对理解程序在计算机中的运行很有用(intel格式对我这种没汇编基础的比较容易...)
// i++和++i到底在机器级别是怎么实现的查看反汇编文件就好,不同编译系统,不同优化级别还是有差异的,具体环境具体分析吧
//
// #include
// void main(void){
// int i = 1, j = 0;
// j = i++;
// return;
// }
// * 0000000000000000 :
// * 0: 55 push rbp
// * 1: 48 89 e5 mov rbp,rsp
// * 4: 48 83 ec 30 sub rsp,0x30
// * 8: e8 00 00 00 00 call d
// * d: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1
// * 14: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0
// * 1b: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
// * 1e: 8d 50 01 lea edx,[rax+0x1]
// * 21: 89 55 fc mov DWORD PTR [rbp-0x4],edx
// * 24: 89 45 f8 mov DWORD PTR [rbp-0x8],eax
// * 27: 90 nop
// * 28: 48 83 c4 30 add rsp,0x30
// * 2c: 5d pop rbp
// * 2d: c3 ret
// * 2e: 90 nop
//
void main(void)
{
int i_a = 1, i_aa = 1, i_b = 1, i_c = 1, i_d = 1;
// ++ 递增运算符(increment operator),将其运算对象operand递增1
// * 看了运行结果,i++后缀模式,表达式运算结果等于变量值,然后变量的值自增1
// * ++i前缀模式,表达式运算结果等于变量值加1,然后变量的值自增1
// * 初始值相等,变量自增次数相同,变量的值还是相等的,只不过一个表达式运算结果是自身,一个是自身加1后的结果
//
// 程序实际运行过程还是要看编译系统生成的汇编代码,不过我现在不懂汇编,看不懂...
// 不同编译系统实现的过程是不同的,不同的-O optimize编译优化等级产生的机器代码也是不同的,符合C标准就行
//
// i++;
// --> i;
// --> i = i + 1;
// ++i;
// --> i + 1;
// --> i = i + 1;
//
//test_base.c 重要知识点(编译系统的编译过程)
//gcc --help查看gcc帮助文档
//通过汇编文件(文本格式)理解
// reverse engineering 逆向工程
//
// 栈(stack):
// 先进后出,后进先出
// |_______|
// 16 |_______| <-- -16(%rbp)
// 15 |_______|
// 14 |_______|
// 13 |_______|
// 12 |_______|
// 11 |_______|
// 9 |_______|
// 8 |_______|
// 7 |_______| <-- -8(%rbp)
// 6 |_______|
// 5 |_______|
// 4 |_______|
// 3 |_______| <-- -4(%rbp)
// 2 |_______|
// 1 |_______|
// 0 |_______| <-- stack top(栈顶随着压栈和出栈变化)
// | 1byte |
//
// 注: %rbp只有在 -O0 不优化的编译条件下,还具有帧指针的含义
//堆(heap)
//不要对一个变量使用自增/自减运算符的两种情况:
// 1.一个变量出现在一个函数中的多个参数中,不要使用自增/自减运算符,
// 不同编译系统计算结果不同(因为计算顺序不一定相同)
// 因为C标准并未定义结果是什么
// 所以下面两行代码不应使用
printf("i_a: %d, i_a++: %d\n", i_a, i_a++);
printf("i_b: %d, ++i_b: %d\n", i_b, ++i_b);
// 2.一个变量在一个表达式中出现多次,要避免使用自增运算符
// 不同编译系统计算结果不同(因为计算顺序不一定相同)
// 因为C标准并未定义结果是什么
// 所以下面这行代码不应使用
i_aa = i_aa++;
printf("i_aa = i_aa++: %d\n", i_aa);
i_c++;
++i_d;
printf("i_c++: %d\n", i_c);
printf("++i_d: %d\n", i_d);
decrement_operator();
increment_decrement_precedence();
return;
}
// > < relational operator 关系运算符
// -- decrement operator 递减运算符
void decrement_operator(void)
{
int do_i_a = 5, do_i_b = 5;
while (do_i_a-- > 0) {
printf("do_i_a: %d\n", do_i_a);
}
while (--do_i_b > 0) {
printf("do_i_b: %d\n", do_i_b);
}
return;
}
// 自增运算符和自减运算符优先级只低于圆括号
// 优先级:
// () > ++/-- > +/-(一元运算符) > * / (二元运算符) > +/-(二元运算符) > =(赋值运算符)
// precedence: 优先,优先权
void increment_decrement_precedence(void)
{
int idp_a = 1, idp_b = 0;
-++idp_a;
//idp_b = -++idp_a;
//printf("idp_b: %d\n", idp_b);
printf("idp_a: %2d\n", idp_a);
//从输出结果可以推理出++运算符优先级比一元运算符-优先级还高
//通过反汇编文件可以证实这个推理
return;
}
// * 自增和自减运算符使用时需注意 《c primer plus第六版》 P275
// 1. 一个变量多次出现在一个函数的多个参数中
// 2. 一个变量多次出现在一个表达式中
// 不要使用自增和自减运算符,因为不同的编译系统对相同代码的计算顺序处理不同
// C标准并未定义结果应该是什么
// 具体看书上的内容,这里就不展开了,具体实践中结合运算结果和反汇编文件分析即可,主要还是看编译系统怎么处理
// 其实前面我所写的代码 i = i++; 就符合了上面的第2条,所以不要写这种代码,因为换了编译系统输出结果就不相同
//========================================================================================
// 1bit一个物理地址,1byte(8bit)一个虚拟地址,虚拟地址由操作系统分配
// 1B = 8bit
// 1KB = 1024B
// 1MB = 1024KB
// 1GB = 1024MB
// 1TB = 1024GB
// 32位cpu和64位cpu是指cpu一次能够处理的数据宽度
// 总线位宽(带宽),这个概念暂时不展开
// 直接说地址总线位宽
// 以32位地址总线位宽为例:
// 32位系统一般有32跟地址总线,可提供的可寻物理地址范围为 2^32B (2^32种组合方式)
// 2^10 * 2^10 * 2^10 * 2^2 种组合
// 1024 * 1024 * 1024 * 4 B
// 1024 * 1024 * 4 KB
// 1024 * 4 MB
// 4 GB 这个过程反过来更好理解
// 一个物理地址指向一个1B内存空间(虚拟地址与物理地址的映射由操作系统决定)
// RAM random access memory 随机存取存储器 主存(内存),CPU高速缓存
// ROM read only memory 只读存储器 多用于存放固件
//
// 《深入理解计算机系统第三版》P113 机器级代码 machine-level code
// optimize 优化 gcc -O
// ISA(指令集架构,instruction set architecture)
// PC(程序计数器,program counter.在x86-64中用%rip示),
// 给出将要执行的下一条指令在内存中的地址,PC储存的也是指令
// IR(指令寄存器 instruction register)
// ip(instruction pointer) r我暂时理解为register吧...
// 数据结构的知识: stack 栈; heap 堆
//
// 数据格式:《深入理解计算机系统第三版》P119
// 字节byte 8位
// 字word 16位
// 双字double word 32位 "long word"
// 四字quad word 64位
// 数据格式:《深入理解计算机系统第三版》P119 图3-1
// C语言数据类型在x86-64中的大小
//
// 《计算机科学导论第三版》P84 5.8.4 指令集
// 指令:有操作码(opcode)和操作数(operand)组成
// 操作码指明了在操作数上操作的类型
// 操作数含有操作数或操作数地址(寄存器地址或内存地址)
// 控制单元: PC程序计数器 IR指令寄存器
//
// *** 三星好评知识点:
// * 《计算机科学导论第三版》P84 5.8.5 处理指令
// * 《计算机科学导论第三版》P84 5.8.7 指令周期 在没有汇编和数字电路知识之前,理解这些概念模型已经够用了~
// 计算机每条指令使用一个指令周期
// 每个指令周期通常由三个步骤组成:取指令,译码,执行
//
//
// 数据格式:《深入理解计算机系统第三版》P120 图3-2
// CPU通用目的寄存器
// %rbp register buttom pointer 基址寄存器
// %rsp register stack pointer 栈指针寄存器
//
// immediate 立即数(立即的)
//
// *理解寄存器和内存 看懂网课《计算机科学速成课》P6
// 1.保存1的电路
// ____
// A-----\ \
// |OR >------+----- output
// B--+--/___/ |
// |______________|
//
// A输入1,B输入0,OR输出1
// output线上回路给B线
// 相当于B输入1
// A停止输入1
// B --> OR --> B 形成回路,电路记录了"1"
// A再输入或停止,也无法改变这个回路的状态
//
// 2.保存0的电路
// ____
// A-----| \
// |AND )------+----- output
// B--+--|___/ |
// |______________|
//
// A,B输入1,则output为1,回路为1
// B停止输入,output还是为1
// A停止输入(则回路也停止)
// A无论再设什么值,电路始终输出0
//
// 3.and-or latch (and-or 锁存器)
// latch:弹簧锁,插销
// _________________________________
// | ____ |
// +--\ \ ___ |
// |OR >-------------| \ |
// input set-----/___/ |\ |AND )------+----- output
// reset--------------| >-------|___/
// |/
// NOT
// 第一步:
// input set设置1
// reset设置0,经过NOT门变1
// AND门相当于输入了两个1,output1,回路1
// input set设置0后,回路1 加 NOT门,AND还是保持output1
// 第二步:
// reset设置1,经过NOT门变0
// AND门output0,回路变0
// 总结:
// input set设置1,ouput1,保存1
// reset设置1,ouput0,保存0
//
// 4.gated latch (门锁存器)
// 4.1先看抽象化后的gated latch门锁,看它所要完成的操作,就更容易理解里面的逻辑门为什么那样设计
//
// 数据输入 ________
// data input -----| gated | 数据输出
// | latch |----- data out
// write enable -----|________|
// 允许写入线
// 数据输入和允许写入都从0开始
// 第一步,写入并保存1:
// 允许写入线1,数据输入1,数据输出1
// 允许写入线0,数据输入线不管是1或0,数据输出都为1,值被存起来了
// 第二步,写入0:
// 再次设置允许输入线为1,数据线输入0,数据输出为0
// 设置允许输入线为0,数据输入线不管是1或0,数据输出都为0
// --> 只保存1bit没啥用,因此用一组门锁存器组成寄存器
//
// 设计成这样是为了逻辑上更容易理解,比 输入设置 和 复位 更好理解吧...
// 4.2 gated latch 抽象化前:
//
// _________________________________
// | ____ |
// ___ +--\ \ |
// data input --------+---------------| \ |OR >--+ |
// | |AND )------/___/ | |
// +-------)--------------|___/ | |
// | | | ___ |
// | | |\ ___ +-------| \ |
// | +----| >---------| \ |\ |AND )---------+----- output
// | |/ NOT |AND )-----| >-------------|___/
// write enable --+-----------------------|___/ |/
// NOT
// *4.3 理解下面这个过程:
// 实现output 0 --> 1:
// write enable --> 1
// data input --> 1
// output 1
// write enable --> 0
// (这个只是说明:data input --> 1或0,output都为1)
// output保存1
// 实现output 0 --> 1:
// write enable --> 1
// data input --> 0
// output --> 0
// write enable --> 0
// output保存0
//
// 4.3.1 write enable --> 1
// _________________________________
// | ____ |
// ___ +--\ \ |
// data input ---0----+---------------| \ |OR >--+ |
// | |AND )---0--/___/ | |
// +---1---)--------------|___/ | |
// | | | ___ |
// | | |\ ___ +-------| \ |
// | +----| >---1-----| \ |\ |AND )---------+--0-- output
// | |/ NOT |AND )--1--| >------0------|___/
// write enable --+----1------------------|___/ |/
// NOT
//
// 4.3.2 data input --> 1 回路1
// _________________________________
// | ____ |
// ___ +--\ \ |
// data input ---1----+---------------| \ |OR >--+ |
// | |AND )---1--/___/ | |
// +---1---)--------------|___/ | |
// | | | ___ |
// | | |\ ___ +-------| \ |
// | +----| >---0-----| \ |\ |AND )---------+--1-- output
// | |/ NOT |AND )--0--| >-------------|___/
// write enable --+----1------------------|___/ |/
// NOT
//
// 4.3.3 write enable --> 0 回路1
// _________________________________
// | ____ |
// ___ +--\ \ |
// data input ---1----+---------------| \ |OR >--+ |
// | |AND )---0--/___/ | |
// +---0---)--------------|___/ | |
// | | | ___ |
// | | |\ ___ +-------| \ |
// | +----| >---0-----| \ |\ |AND )---------+--1-- output
// | |/ NOT |AND )--0--| >-------------|___/
// write enable --+----0------------------|___/ |/
// NOT
//
// 4.3.4 write enable --> 1 回路1
// _________________________________
// | ____ |
// ___ +--\ \ |
// data input ---1----+---------------| \ |OR >--+ |
// | |AND )---1--/___/ | |
// +---1---)--------------|___/ | |
// | | | ___ |
// | | |\ ___ +-------| \ |
// | +----| >---0-----| \ |\ |AND )---------+--1-- output
// | |/ NOT |AND )--0--| >-------------|___/
// write enable --+----1------------------|___/ |/
// NOT
//
// 4.3.5 data input --> 0 回路0
// _________________________________
// | ____ |
// ___ +--\ \ |
// data input ---0----+---------------| \ |OR >--+ |
// | |AND )---0--/___/ | |
// +---1---)--------------|___/ | |
// | | | ___ |
// | | |\ ___ +-------| \ |
// | +----| >---1-----| \ |\ |AND )---------+--0-- output
// | |/ NOT |AND )--1--| >-------------|___/
// write enable --+----1------------------|___/ |/
// NOT
//
// 4.3.6 write enable --> 0 回路0
// _________________________________
// | ____ |
// ___ +--\ \ |
// data input ---0----+---------------| \ |OR >--+ |
// | |AND )---0--/___/ | |
// +---0---)--------------|___/ | |
// | | | ___ |
// | | |\ ___ +-------| \ |
// | +----| >---1-----| \ |\ |AND )---------+--0-- output
// | |/ NOT |AND )--0--| >-------------|___/
// write enable --+----0------------------|___/ |/
// NOT
// 看一下理解就好,直接记抽象化的门锁存器(这个容易记--')...
//
// 5. 8-bit register(8bit寄存器):
// 现在的CPU一般都是64bit寄存器啦~原理是一样的,位宽更大而已
// 一根线连接所有允许写入线
// |
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable |----|________|
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable |----|________|
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable |----|________|
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable |----|________|
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable |----|________|
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable |----|________|
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable |----|________|
// | ________
// data input -----)---| gated |
// | | latch |----- data out
// write enable +----|________|
//
// 存入8bit数据:
// 连接所有允许输入线的线设置1
// 然后每根数据写入线输入数据(正规讲法就是高电平就是1,低电平就是0咯...)
// 数据写入后,允许输入线设置为0,数据保存成功
//
// 8bit register(8bit寄存器):
// 8条数据输入线
// 8条数据输出线
// 1条允许写入线
//
// 6.内存跟寄存器的锁存器排列方式不同,如果跟寄存器一样,要用的线就太多了,因此内存采用矩阵排列锁存器(网格)
// 行线和列线都为1,确定门锁存器,通过AND门来判断,输出--> [行+列选择测试线]
//
// [行+列选择测试线] 与 允许写入线(一条允许写入线连接所有门锁存器) 信号再通过一个 AND门
//
// [行+列选择测试线] 与 数据输入线(一条数据输入线连接所有门锁存器) 信号再通过一个 AND门
//
// [行+列选择测试线] 与 允许读取线(一条允许读取线连接所有门锁存器) 信号再通过一个 AND门
//
// 一个256bit内存,需要16根行线,16根列线,1根允许写入线,1根数据输入线,1根数据读取线
// 这三根线都分别连接所有门锁存器,分别靠和[行+列选择测试线]一起通过AND门来判断操作
// 理解到这个层次差不多了...再深究下去头发要没了
//
// 7. multiplexer
// 20200308 数字电路部分暂停,水太深了.先继续i++和++i的旅程...告辞,感觉有脱发的危险了,555555555~~~~ T-T
//
//
#include
void main(void)
{
return;
}
// 表达式和语句
// 1. 大多数语句(statement)由表达式(expression)构成
// 2. 表达式由运算符(operator)和运算对象(operand)组成
// 3. 运算符操作的对象就是运算对象(operand)
// 4. 运算对象可以是常量,变量或二者的结合,例如:
// 5
// -8
// a
// a + 5
// b = 6 + 5 赋值表达式
// b = a + 5 赋值表达式
// b > 10 关系表达式
// a * (b + 6)
// --> b + 6 是它的子表达式(subexpression),子表达式即较小的表达式
// --> 两个子表达式的乘积
// 5. 每个表达式都有一个值,要获得这个值,必须根据运算符优先级规定的顺序来执行操作
// a. 赋值表达式的值与赋值操作符(=)左边变量的值相同
// b. 关系表达式的值不是1就是0
//
// 6. 语句(statement)
// a. 在c中,大部分语句都以分号结束.
// ; 空语句
//
// b. 一条语句(有效的,有意义的,合理的语句)相当于一条完整的计算机指令,例如:
// a = 10;
// 但一条完整的指令并不一定是一条语句,例如:
// y = 15 + (x=8);
// 该语句的子表达式x=8是一条完整的指令,但它只是语句的一部分
// 所以用分号来识别这种情况下的语句
//
// c. C把末尾加上一个分号的表达式都看作是一条语句(表达式语句),例如:
// 8;
// 5 + 6;
// 上面两句语句并没有对程序做什么(我的理解是,在计算机中作为临时值,执行完就被丢掉了,并没有什么实际作用)
//
// d. 合理的,有效的(sensible)语句:更典型的,语句可以改变值或调用函数,例如:
// x = 11;
// i++;
// y = sqrt(x);
//
// * 表达式语句:
// * x = 10;
// * printf("%d\n", a);
// * 注意:在C语言中,赋值和函数调用都是表达式 (函数是有返回值的,符合每个表达式都有一个值,调用函数时会返回值)
// (没有所谓的"赋值语句"和"函数调用语句")
// 应该叫"赋值表达式语句"和"函数表达式语句"
// * 迭代语句: while(i<10)
// * 跳转语句: return 0;
//
// int a; 声明不是表达式语句,因为 int a不是表达式,没有值
//
// condition 条件
// while(i<5) 关键字key word(测试条件test condition) 迭代语句(又是也称结构化语句)
// 后面只接一句语句的话可以不用花括号{}
// 后面接多句语句的话要用花括号括起来{},这种语句称为复合语句.
//
// side effect 副作用
// 从C语言的角度看,副作用的主要目的是对表达式的求值
// a = 50;
// 1 + 1;
// i++;
// printf(); 函数调用也是表达式
//
// sequence point 序列点
// 序列点是程序执行的点,在该点上,所有副作用都在进入下一步前发生
// 在C语言中,语句中的分号标记了一个序列点
// *任何一个完整表达式的结束也是一个序列点
//
// 完整表达式(full expression),就是指这个表达式不是另一个更大表达式的子表达式
// 例如:
// 1. 表达式语句中的表达式;
// 2. while 测试条件中的表达式(是完整表达式,它的结束是一个序列点,所有副作用发生后再进入下一歩)
// 都是完整表达式
// while(i++ < 5) <-- 序列点(while测试条件中的表达式,完整表达式,执行下一语句前要发生所有副作用)
// printf("%d\n", i); <-- 所以程序输出i自增1后的值
//
// y = (2 + i++) + (6 + ++i); 整个赋值表达式语句才是完整表达式,分号标记了序列点
// --> 我个人理解: y = (2 + i++) + (6 + ++i)表达式, y = (2 + i++) + (6 + ++i);完整表达式
// 2 + i++不是一个完整表达式,是子表达式,因此不能保证2 + i++发生所有副作用后再进行后面的运算
// 要避免写这类语句(这个语句符合不该使用自增运算符的两个条件之一)
//
// 复合语句,花括号括起来的一条或多条语句,符合语句也称为块(block)
// while (i<2)
// {
// i = i + 1;
// printf("%d\n", i);
// }
// 带复合语句的while循环,花括号括起来的称为复合语句,也称为块
// while (i<2) {
// i = i + 1;
// printf("%d\n", i);
// }
// while的另一种风格,这种风格突出block块属于while循环,不过对编译器来说都一样
// 《c primer plus第六版》 P285 简单语句概念
//
//带参数的函数 P293
#include
/* 有兴趣的可以详细了解:ANSI C之前,C的函数声明不是函数原型,只显示函数名和返回值类型(无参数类型) P295 */
//
//原型(prototype),即函数的声明(function declaration),描述函数的返回值和参数(形参parameter)
// 1.该函数没有返回值(void空)
// 2.声明int类型的参数
void while_loop_function(int n); /* 函数原型声明function prototype declaration */
//int while_loop_function(int n); 函数返回值类型为int
//全局变量(global variable)
int gv_a = 0;
//主函数,程序入口
void main(void)
{
//局部变量(local variable)
char c;
int a;
float f;
//自动类型转换
c = 'A';
a = 5.5F;
f = 10;
while_loop_function(c);
while_loop_function(a);
while_loop_function(f);
while_loop_function(10);
return;
}
// 函数头圆括号中包含了一个int类型变量n的声明
// 该函数接受一个int类型的参数
void while_loop_function(int n)
{
while (n-- > 0){
printf("-");
}
printf("\n");
return;
}
// formal argument / formal parameter 形参(形式参数)
// actual argument / actual parameter 实参(实际参数)
// C99标准中:
// actual argument / actual parameter term术语 argument实参
// formal argument / formal parameter term术语 parameter形参
// 1.形参是函数头圆括号中声明的变量,
// 例如: 上面程序的变量n
// 2.实参是函数调用时提供(传递)的值,然后赋给形参
// 例如: 上面程序的 变量c,a,f;
// 常量10;
// 表达式也可以,比如 c+1
//
// void while_loop_function(int n) 函数头
// 圆括号中的变量声明创建了称为形参的变量n
//
// while_loop_function(10); 函数调用(function call)
// 把实参10传递给函数
//
// while_loop_function(a);
// 把a的值5赋给n
//
// * 变量名(局部变量local variable)是函数私有的,所以相同变量名在不同函数中是不冲突的
// const 恒定的,不变的
//
// 强制类型转换 --> 显式转换
// 自动类型转换 --> 隐式转换
// 结合律(求值顺序从左到右或从右到左)
// while语句是一种迭代语句
//
#include
int while_null_statement(void);
//scanf()函数返回值,读取成功返回1,读取失败返回0
int main(void)
{
//这里用long int类型常量(constant)0L,
//用0其实也没问题(int类型常量0会隐式转换为long int)
//但还是建议保持数据类型的一致性
long int num, sum = 0L;
unsigned short int status;
printf("input integer:");
status = scanf("%ld", &num);
printf("status: %hu\n", status);
//该while循环的测试条件(test condition)为如下表达式(expression),也是(full expression)
// status == 1 (这表达式结束就是一个序列点sequence point)
// == 相等运算符(equality operator)
// status == 1 status是否等于1
// status = 1 赋值1给变量status
while (status == 1){
//只有对测试条件求值时,才决定继续还是终止循环
sum = num + sum;
printf("sum: %ld\n", sum);
printf("input integer:");
status = scanf("%ld", &num);
}
while_null_statement();
printf("Game Over!\n");
return 0;
}
// 数学小知识点:
// -1000 - 0001 = -1001
// 减去一个数,加上它的二补数
// = 10000 - 1000 + 10000 - 0001
// = 100000 - 1001
// = 10111
// 假设位宽为4bit,结果为0111
// B2T(0111)= 7
// B2T(10111)= -16 + 7 = -8 最高位位权: -2^4
// 7 + (-2^4) = 8
//
// * clang是llvm编译系统(low level virtual machine)的前端
// llvm objdump -d -x86-asm-syntax=intel
// 与gcc objdump -d -m intel 不相同
// iteration 迭代
// multiplexer 多路复用器
// while循环的通用形式:
// while (expression)
// statement
// statement可以是以分号结束的简单语句,也可以是花括号括起来的复合语句
//
// expression都是使用关系表达式,expression的值为非零(真),执行statement部分一次
// 再判断expression
// 直到expression值为0(假),循环结束
// 每次循环都称为一次迭代(iteration)
//
// entry condition 入口(进入)条件
// while (expression) 这里的expression(表达式)是一个入口条件
// 必须满足条件才会进入循环体
// infinite loop 无限循环
// 1.
// while (n < 3);
// 2.
// while (n < 3)
// ;
// 推荐用第二种方式
// 测试条件后面的语句
// ; null statement空语句
//
// *不同系统对回车的处理是不同的:
// 1. windows /r/n ascii(decimal): 13 10
// 2. linux /n ascii(decimal): 10
// 3. mac 运行下面这段代码可得出结果
// (对不对还要写代码验证,网上查是这么说的...)
// 换行 ascii 10
// 回车 ascii 13
// q ascii 113
int while_null_statement(void)
{
char var, char_a;
// char,short类型自动转换为int
scanf("%c", &var);
printf("c_0: %hd\n", var);
//%c scanf函数的conversion specification会读取键盘的enter字符
while (scanf("%c", &char_a) == 1){
printf("c_1: %hd;\n input: ", char_a);
scanf("%c", &var);
printf("c_2: %hd\n", var);
}
return 0;
}
#include
#include
int true_value(void);
int main(void)
{
const double PI = 3.14159;
double double_input;
true_value();
//while循环经常依赖测试表达式作比较,
//一般常用的表达式为关系表达式(relational expression)
//出现在关系表达式中的运算符被称为关系运算符(relational operator)
// P328 表6.1 关系运算符
// absolute value 绝对值
scanf("%lf", &double_input);
// fabs函数,返回值为浮点数的绝对值
// double_input和PI的差的绝对值小于等于0.0001,while循环才会结束
while (fabs(double_input - PI) > 0.0001){
printf("No! input again: ");
scanf("%lf", &double_input);
}
return 0;
}
//表达式一定有一个值,关系表达式也不例外
//表达式为真的值是1,表达式为假的值是0
// while (q){
// printf("q\n");
// }
// 测试表达式q的值为真1
//关系表达式为真,求值为1;关系表达式为假,求值为0
//*在C中,所有非零值都为真(1),只有0被视为假,表达式一定有一个值,函数大部分也要返回值(这句话是核心,重点)
int true_value(void)
{
int a;
//while(a == 1)
// printf("true_value() run!");
return 0;
}
// == 关系相等运算符: 检查左值右值是否相等(比较)
// = 赋值运算符: 把右值赋给可修改的左值
// 关系运算符用于构建关系表达式
// 5 == n , n == 5是一样的
//
// 总结:
// 关系表达式真时值为1,关系表达式值为假时值为0
// 在C中,所有非零值为真,0为假,表达式都有一个值,函数大部分都有返回值
// *重要知识点:
// C标准库:
// 函数原型声明放在头文件 xxx.h (编译系统预处理阶段,读取并插入头文件文本)
// 函数定义放在其它源文件 xxx.c (已经提前编译好,编译系统链接阶段就是链接这些文件)
#include
#include
// 符号常量/明示常量(manifest constant)
#define NAME "fyf"
// C99新增_Bool类型
// 表示真或假的变量称为布尔变量(boolean variable)
// _Bool类型变量只能储存1(真)或0(假)
// 把非零值赋给_Bool类型变量是,该变量会被设置为1(这反映C把所有非零值视为真)
int bool_variable(void);
int for_loop(void);
int for_loop_nine_methods(void);
int for_example(void);
int comma_operator(void);
int exit_condition_loop(void);
int nested_loop(void);
int nested_loop_other(void);
int array(void);
int for_array_average(void);
// 函数原型声明 function prototype declaration
// 1.函数名; 2.返回值类型; 3.传入参数
// 不写函数原型声明其实编译系统编译时会进行隐式函数原型声明,不过编译时会有警告 -w选项忽略警告
// 还是不要偷懒,即使编译过了,也可以运行,但可能有预料不到的错误(而且C99标准已经删除了隐式函数声明法则)
double power(double n, int limit_e);
//double power(double, int); 这样声明也可以
// 让编译系统知道函数返回值的类型,知道返回值有多少字节数据
// 函数声明有参数的话可以让编译系统知道传入参数的类型和个数
// C编译系统只会从头到尾编译一遍,函数调用需要知道函数返回值(和参数类型个数,如果有的话)
// 编译到函数调用语句,还没有到函数定义,所以编译系统只能通过函数原型声明知道这些信息
// (C编译器太古老了,较新的语言基本没有函数声明了)
// encounter 遇到,遭遇
// P388 对函数声明和使用带返回值的函数有比较详细的解释(很值得看的知识点)
//1.编译器遇到调用函数语句时,还没有遭遇到函数定义,不知道函数的返回值类型
// 通过前置声明(forward declaration)可让编译器知道函数的返回值类型,函数定义在别处
// (函数定义如果在main函数前,函数原型声明可以省略,但这不是C的标准风格)
// 附:通常函数定义(function definition)放在在其它文件中
//2.printf(),scanf()这些函数的原型声明(function prototype declaration)包含在stdio.h头文件里,
// #include 预处理指令,读取并插入头文件里的文本
// 这段main()程序是一个驱动(driver)示例,这段程序用来测试其它函数(function)
int main(void)
{
_Bool a = 1, b = 'a', c = 0, d = 88
, e = "abcd";
int input_limit_e;
double power_return_value, input_n;
printf("a = 1: %3d\n", a);
printf("b = 'a': %d\n", b);
printf("c = 0: %3d\n", c);
printf("d = 88: %2d\n", d);
printf("e = \"abcd\": %2d\n", d);
// 函数调用,读取函数返回值
// 函数的返回值存放在寄存器或内存(stack栈)
// 函数原型声明的返回值类型(必须,也是需要使用函数原型声明的原因):
// 编译系统编译到函数调用代码的时候,需要知道函数调用要读取的返回值的类型,知道数据类型才能知道要读取多长位宽的数据
// 但还没有编译到函数定义,所以需要通过函数原型声明知道返回值类型
// 函数原型声明的参数(非必须):
// 让函数调用可以传入正确的实参类型和个数
// 1.隐式函数声明,c99标准后已淘汰
// 2.函数类型声明 function type declaration
// int xxx();
// 缺点: 编译器检查不到函数调用时传入实参的类型和个数错误
// 3.函数定义声明,把函数定义放主函数前
// 缺点: 不利于代码的组织
// 4.函数原型声明 function prototype declaration
// int xxx(int xxx, float xxx);
// int xxx(void);
// 直接用函数原型声明就完事了...
// * C编译器很古老,有历史包袱...只会从头到尾编译一遍,所以有以上这些问题,比较新的语言就不会有这些声明的问题
bool_variable();
for_loop();
for_loop_nine_methods();
for_example();
comma_operator();
exit_condition_loop();
nested_loop();
nested_loop_other();
array();
for_array_average();
// scanf函数返回值为成功读取输入项个数,读取成功几个项返回值就为几,读取成功0个就为0
// EOF,end of file,文件终止符
// EOF在windows,linux,mac中有区别,具体上网查去,老子现在没心情也没空...
printf("input number and exponent:\n");
// 函数原型里n为double类型,输入整数类型也没关系,double类型等级比所有证书类型高,
// 整数类型会被自动转换(隐式转换)成double类型
// 函数原型power函数底数的变量用double类型是因为double类型能表示的范围更大更全面
//
// 输入 10 文本(ascii码),编译系统把它转换成 10.0 文本,再转换成double类型的binary储存
// (编译器具体怎么实现不清楚,但这个思路和结果应该是对的...
// 至少结果是对的,目前水平有限,这个思路让我能理解,未必精准或正确)
// 输入 a 文本或符号文本(这些转换成表示double类型的文本想想就过分啊...),所以当然输入错误咯...
while (scanf(" %lf %d", &input_n, &input_limit_e) == 2){
// 函数调用,把函数返回值返回给主函数
power_return_value = power(input_n, input_limit_e);
printf("number^exponent = %.2f\n", power_return_value);
}
printf("Game over! Bye~");
return 0;
}
int bool_variable(void)
{
// C99提供老stdbool.h头文件,该头文件定义了_Bool的别名bool,
// 把true和false定义为1和0的符号常量
// 包含了该头文件后,代码可以与C++兼容,true和false,bool为c++的关键字
bool a;
int b;
char c;
printf("input decimal number:\n");
// 1. 精简写法:
while (scanf("%d", &b)){
printf("true\n");
}
// 2. 非常啰嗦写法...
// 不用小括号, ==运算符优先级也比=高
//a = (scanf("%d", &b) == 1);
//while (a){
// printf("true\n");
// a = scanf("%d", &b) == 1;
//}
// 3. 很啰嗦写法...
//a = scanf("%d", &b) == 1;
//while (a){
// printf("true\n");
// a = scanf("%d", &b) == 1;
//}
// 4. 啰嗦写法...
//a = scanf("%d", &b);
//while (a){
// printf("true\n");
// a = scanf("%d", &b);
//}
//越啰嗦,可读性越高,但写起来确实啰嗦啊...
scanf("%c", &c);
printf("false! %d\n", c);
return 0;
}
// 知识点总结:
// precedence of relational operators(关系运算符优先级)
// 关系运算符优先级比算术(arithmetic)运算符低,比赋值运算符高
// 优先级高的先结合
// 关系运算符之间的优先级:
// 高优先级组: < <= > >=
// 低优先级组: == !=
// 与其他大多数运算符一样,关系运算符的结合律也是从左到右
// P340 表6.2 运算符优先级
// while语句是一种入口条件循环
// while (test expression/test condition)
// statement
// statement可以是简单语句,也可以是复合语句(花括号括起来)
// expression部分为假或0前,重复执行statement部分
// P343
// 1.不确定循环(indefinite loop) while测试表达式为假0之前,不确定要循环多少次
//
// 2.计数循环(counting loop) 循环之前就知道要循环多少次
// (1) 计数器初始化
// (2) 测试条件(与有限值比较)
// (3) 计数器递增(更新)
// 计数循环我们用for循环,虽然while循环(计数循环)也能实现
//for循环可以把上述3个行为(初始化,测试,递增[更新])组合在一起
int for_loop(void)
{
// a. 计数器初始化
// b. 循环一次(测试表达式为真);测试条件为假,终止循环,跳过计数器更新,执行后面的代码
// c. 计数器更新(递增)
int a;
// a = 1; 初始化只执行一次(once),循环开始前
// for循环圆括号中的表达式也叫控制表达式,它们都是完整表达式
// a++循环结束时求值
for (a = 1; a <= 5; a++){
printf("for loop: %d\n", a);
}
// 由此可看出,a <= 5表达式为假0,循环结束,后面的a++表达式不执行
printf("out of for loop, a: %d\n", a);
return 0;
}
int for_loop_nine_methods(void)
{
int n, m = 2;
//int n, m;
char c;
double f;
// for循环 控制表达式 n--,n=n-2,n=n-10也可以,根据需求
for (n = 5; n >= 0; n--){
printf("decrement counter n-- : %d\n", n);
}
// for循环 控制表达式 n++,n=n+2,n=n+10也可以,根据需求
for (n = 5; n <= 10; n = n + 2){
printf("increment counter n = n + 2 : %d\n", n);
}
// 可以用字符代替数字计数,(该程序能正常运行,是因为字符在计算机内部跟数字一样以二进制数存储)
for (c = 'a'; c <= 'z'; c++){
printf("character: %c, ASCII(decimal): %d\n", c, c);
}
// 上面三条测试条件都是测试 迭代(iteration)次数(控制循环次数)
// 还可以测试其他条件,例如 n * n < 100,测试限制n的平方的大小
// 递增的量可以算术增长,也可以几何增长
for (f = 1.0; f <= 10.0; f = f * 1.2){
printf("f * 1.2 : %.13f %a\n", f, f);
}
// 第三个表达式可以使用任何合法的表达式,无论是什么表达式,每次迭代都会更新该表达式的值
// int y = 10;
// for (x = 1; y <= 50; y = ++x * 2 + 5)
// 这里可以这么理解,计数器x的代数计算的值赋值给y,对y作测试
// 可以正常运行,但编程风格不好
for (n = 1; n <= 5;){
// 计数器更新部分也可以放进循环体内
n = n + 2;
}
printf("%d\n", n);
// for循环的控制表达式,三个表达式可以使用不同的变量
for (n = 1, f = 1.0; f <= 5.0; f = 2 * n * f){
printf("OK_1!\n");
}
//clang编译器,m没有初始化,测试表达式的值为假0
//for (; m <= 5;){
//for循环控制表达式三个表达式可以省略,但两个分号不能省略
//测试条件为空语句,值为真,程序无限循环
//for (;;){
//语法上虽然可以省略三个表达式,但这样代码貌似没有任何实际意义啊...
for (m = 1; m <= 5;){
m++;
printf("%d\n", m);
}
printf("%d\n", m);
return 0;
}
// *** for循环控制表达式总结(核心知识点,遵循一下三点原则):
// 1. 计数器初始化
// 1.1 第一个表达式也可以是其他表达式(即使函数表达式也可以)
// 1.2 只在循环开始前执行一次(once)
// 2. 测试条件(测试表达式)
// 2.1 如果测试条件为空语句,则值一直为真,程序无限循环
// 3. 计数器更新(递增:算术递增,几何递增都可以;可以把这个表达式放入循环体内)
// 3.1 放在控制表达式,循环结束一次,执行一次
// 3.2 放在循环体内,循环一次,执行一次
// 注: 可以省略三个表达式,但两个分号不能省略(其它随便你折腾...)
// 1表达式 可以放在for语句前进行初始化
// 2表达式 空语句值一直为真
// 3表达式 可以放循环体内
// 个人总结(while和for的区别):
// while (测试条件test condition)
// for (控制表达式,三个表达式)
// while和for都是入口条件循环(entry condition loop)
//
// while: 1.不确定循环; (程序运行完成前无法确定循环迭代次数)
// 2.计数循环; (程序运行前就可知循环迭代次数,计数循环一般用for)
// for循环只需记住几个重点就好:
// 三个控制表达式,三个表达式都可以省略,两个分号不能省略
// 计数器初始化可以在for语句前,只在for循环前执行一次
// 计数器更新可以在循环体内,迭代一次,执行一次;在控制表达式,循环迭代结束一次,执行一行
// 测试条件为空语句,值为真
// 形式:
// for (initialize; test; update)
// statement
//
// initialize 初始化 (计数器初始化)
// initialize表达式在for循环迭代之前值执行一次
// test 测试 (测试条件)
// update 更新 (计数器更新)
int for_example(void)
{
int x;
//三个控制表达式
for (x = 1; x < 1; x++){
printf("OK");
}
printf("%d\n", x);
return 0;
}
// 其他赋值运算符
// 可修改的左值,右值(常量,变量,可求值的表达式)
// y += 2 y = y + 2
// y -= 2 y = y - 2
// y *= 2 y = y * 2
// y /= 2 y = y / 2
// y %= 2 y = y % 2
// y += x y = y + x
// y *= x + 2 y = y * (x + 2)
// 1.组合形式的赋值运算符让代码更紧凑
// 2.与一般形式相比,这些组合形式的赋值运算符生成的机器代码更高效
// 逗号运算符(comma): ,
// 逗号运算符并不局限于for循环使用
// 逗号运算符的两个性质:
// 1.保证了被它分隔的表达式从左到右求值
// (换言之,逗号是一个序列点sequence point,左侧项的所有副作用side effect在执行右侧项之前发生)
// n++, n * 2; 左侧子表达式,右侧子表达式
// 2.整个逗号表达式的值是右侧项的值
int comma_operator(void)
{
int a, x, y, z;
//逗号comma表达式的值是右侧(项/表达式)的值
a = (5,6);
printf("%d\n", a);
//先执行赋值表达式,右侧子表达式什么也没做(实际在计算机内部有没有做还要看汇编代码)
a = 5,6;
printf("%d\n", a);
//逗号运算符,保证被分隔的表达式从左至右求值
//逗号运算符,整个逗号表达式的值是右侧项的值 (逗号运算符运算求值)
//圆括号,先对逗号表达式(comma expression)求值
x = (y = 3, (z = ++y + 2) + 5);
// ------------------------ --> 逗号表达式
printf("x = (y = 3, (z = ++y + 2) + 5); ");
printf("x: %d\n", x);
//逗号运算符,整个逗号表达式,两个子表达式(subexpression)
//整个逗号表达式的值等于逗号右边的子表达式的值
x = y = 3, (z = ++y + 2) + 5;
// ---------------------------- --> 逗号表达式
printf("x = y = 3, (z = ++y + 2) + 5; ");
printf("x: %d\n", x);
return 0;
//逗号运算符(comma operator),当作一个序列点(sequence point)
//逗号运算符的运算符优先级是C所有运算符里最低的(google查的...)
}
// *逗号也可用作分隔符,不是逗号运算符,例如在变量声明和函数参数列表中的逗号就是分隔符
// P259 表5.1
// 组合赋值运算符 与 赋值运算符 优先级 相同
// x *= y + 5 与 x = x * (y + 2) 相同
// 运算符优先级
// 运算符 结合律
// () 从左到右
// + - (一元) 从右到左
// * / 从左到右
// + - (二元) 从左到右
// = 从右到左
//
// *逗号运算符把两个表达式连接成一个表达式(逗号表达式),并保证左边的子表达式先求值
// 整个逗号表达式的值是逗号运算符右侧子表达式的值
// 入口条件循环 entry condition loop
// while for
// 循环每次迭代前检查(执行)测试条件
// 出口条件循环 exit condition loop
// 出口条件循环 do while
// 循环的每次迭代之后检查(执行)测试条件
// do
// {
// statement
// } while ( expression );
int exit_condition_loop(void)
{
int a = 1;
do
{
printf("exit condition loop: %d\n", a);
a++;
} while (a <= 5);
printf("exit loop: %d\n", a);
return 0;
}
//入口条件循环用for或while都可以:
// 让for像while一样
// for ( ;test; ) 与 while (test) 效果相同
//
// 让while像for一样
// 计数器初始化;
// while (测试条件) {
// 循环体;
// 计数器更新;
// }
// 与 for (计数器初始化;测试条件;计数器更新) 循环效果相同
// 具体看 P372,觉得有点啰嗦,懒得做笔记...
// 我个人总结的话: while适合不确定循环,for适合计数循环
// do while 适合先执行一次循环的迭代,再执行测试条件
// nested loop 嵌套循环
// 指在一个循环内包含另一个循环
int nested_loop(void)
{
int m;
char c;
// outer loop 外层循环
for (m = 0; m <= 5; m++){
// loop body
printf("row %d: ", m);
// inner loop 内层循环
for(c = 'a'; c <= 'z'; c++){
// nested loop body
printf("%c", c);
}
printf("\n");
}
return 0;
}
// 嵌套循环(nested loop)中,外层循环(outer loop)每迭代一次,内层循环(inner loop)迭代到测试条件为假0为止
int nested_loop_other(void)
{
int m;
char c;
// 三个控制表达式
for (m = 0; m <= 5; m++){
//函数的核心,外层循环每迭代一次,内层循环计数器初始化的值递增
//当然,还可以把递增扩展到内层循环测试条件(测试表达式),这里就不展开了
for (c = 'A' + m; c <= 'F'; c++ ){
printf("%c", c);
}
printf("\n");
}
return 0;
}
// array数组,是按顺序存储的一系列类型相同的值
// 整个数组有一个数组名,下标(subscript)必须是整数,下标从0开始计数,数组的元素依次存储在内存中相邻的位置
// 通过整数下标(subscript)访问单独的项(item)或元素(element)
// 编译器不会检测数组下标的错误
// int a[10]; 声明可储存10个int类型值的数组
// a[0] = 10; 数组下标(subscript)从0开始
// a[15] = 10; 数据还是会被存放,有可能会导致程序异常中断
// 数组的类型可以是任意数据类型
int array(void)
{
// 声明int类型数组
int a[9];
// 声明char类型数组
char c[9];
a[0] = 15;
printf("%d\n", a[0]);
return 0;
}
int for_array_average(void)
{
int subscript, score[5];
double sum = 0;
printf("input 5 scores:\n");
for (subscript = 0; subscript < 5; subscript++){
scanf("%d", &score[subscript]);
//隐式转换 int --> double 类型级别低的转换为类型级别高的
sum += score[subscript];
}
printf("scores: ");
for (subscript = 0; subscript < 5; subscript++){
printf("%d ", score[subscript]);
}
printf("\n");
printf("average: %.2f\n", sum/5.0);
return 0;
}
// 键入enter键后程序开始读取输入缓冲区的数据
// 函数返回值的循环示例
// 幂power
// n^e n为底数,e为指数
double power(double n, int limit_e)
{
int e;
//从算法理解,先把power的结果(幂的值)初始化为1.0
double pow = 1.0;
//幂的指数e在for循环三个控制表达式的第一个表达式计数器初始化中初始化
for (e = 1; e <= limit_e; e++){
// 指数为1,幂的值为底数n本身
// 指数为2,幂的值为n * n,再赋给左侧的项
// 指数为3,幂的值为n * n * n,再赋给左侧的项
// ......
pow *= n;
// 左侧的项乘以右侧的项,再把乘积付给左侧的项
}
//返回值类型已在函数头声明,为double类型
//可以返回 常量,变量,表达式
//返回值为 常量的值,变量的值,表达式的值
return pow;
}
#include
#include
int getchar_putchar(void);
int isalpha_function(void);
int main(void)
{
const double pass_line = 60.0;
double score;
int pass_count = 0, input_count = 0;
printf("Input score:\n");
// 输入int,scanf函数会把输入值promotion升级为double类型
while (scanf("%lf", &score) == 1){
input_count++;
if (score >= pass_line){
pass_count++;
}
}
//if (input_count != 0){
// printf("How many scores: %d, ", input_count);
// printf("How many pass: %d", pass_count);
//}
//if (input_count == 0){
// printf("No score!");
//}
if (input_count != 0){
printf("How many scores: %d, ", input_count);
printf("How many pass: %d\n", pass_count);
}
else {
printf("No score!\n");
}
getchar_putchar();
isalpha_function();
return 0;
}
// if语句被称为branching statement分支语句或selection statement选择语句
// if (expression)
// statement
//
// if (expression)
// statement
// else
// statement
//
// if (expression){
// statement
// }
// else {
// statement
// }
//
// statement可以是简单语句或复合语句
// 复合语句必须用花括号括起来成为一个块,跟循环语句是一样的
// getchar()和putchar()函数只处理字符 (它们通常是预处理宏,不是真正的函数,第16章有函数的宏的知识点)
// ch = getchar()与scanf("%c", &ch)效果相同
// putchar(ch)与printf("%c", ch)效果相同
// 不需要转换说明,比scanf()和printf()更快,更简洁
int getchar_putchar(void)
{
char ch;
// getchar函数返回值为成功读取的字符
ch = getchar();
putchar(ch);
ch = getchar();
putchar(ch);
printf("enter key(ASCII): %d;", ch);
// escape character 转义字符
// escape sequence 转义序列
/* '\' 续行符和转义字符; '\\' 把\转义为符号字符 */
printf("\\n(ASCII): %d.\n", '\n');
// short和character会被自动转换(隐式转换)为int,升级(promotion)
// 所以ch+1为int运算,只是把结果存入ch时会产生溢出截断
//ch = getchar();
//while (ch != '\n'){
// if (ch == ' '){
// putchar(ch);
// ch = getchar();
// }
// else {
// putchar(ch + 1);
// ch = getchar();
// }
//}
//把读取输入字符放在while循环测试条件里执行,代码更简洁
while ((ch = getchar()) != '\n'){
if (ch == ' '){
putchar(ch);
}
else {
putchar(ch + 1);
}
}
printf("\n%d\n", ch);
return 0;
}
// 组合赋值运算符: += -= *= /= (运算符优先级与赋值运算符相等,结合律从右到左)
// 关系运算符: == != > >= < <= (运算符优先级,大于赋值运算符,小于算术运算符)
// alpha 希腊字母第一个字母. 在字母解释法中,也代表字母A
// ctype.h头文件包含一系列处理字符的函数,参数为特定类型字符,函数返回值为真1或非0,反之为假0
// isalpha()函数的原型声明在ctype.h头文件中
// isalpha()函数接受一个字符作为参数
// 字母字符,函数返回值为真(非0值); 非字母字符,函数返回值为0
int isalpha_function(void)
{
char ch;
printf("isalpha('A'): %d\n", isalpha('A'));
printf("isalpha('f'): %d\n", isalpha('f'));
printf("isalpha(','): %d\n", isalpha(','));
// ch = getchar() 读取输入字符并赋给ch字符类型变量
while ((ch = getchar()) != '\n'){
if (isalpha(ch)){
putchar(ch + 1);
}
else {
putchar(ch);
}
}
putchar(ch);
printf("ASCII: %d\n", ch);
printf("game over!");
return 0;
}
// P421 表7.1 ctype.h头文件中处理字符的函数
// 字符测试函数 测试特定类型字符,是特定类型字符,返回值为真,反之为假0
// 字符映射函数 返回值为修改后的值,但不修改原值
#include
#include
#define RATE1 1.0
#define RATE2 2.0
#define RATE3 3.0
#define STEP1 kwh_a * RATE1
#define STEP2 (kwh_b - kwh_a) * RATE2
#define STEP3 (kwh_c - kwh_b) * RATE3
int step_rate(void);
int division(void);
int prime_number(void);
int main(void)
{
//step_rate();
//division();
prime_number();
return 0;
}
int step_rate(void)
{
const double kwh_a = 10.0, kwh_b = 20.0, kwh_c = 50.0;
double kwh;
printf("input kwh:\n");
// 11 --> 11.0 int --> double promotion升级
// 11.0 --> 11 double --> int demotion降级
// 输出为映射真值,与升级降级不同,不要弄混
while (scanf("%lf", &kwh) == 1){
if (kwh < 0){
printf("wrong data!\n");
}
else if (kwh <= kwh_a){
printf("count: %.3f\n", kwh * RATE1);
}
else if (kwh <= kwh_b){
printf("count: %.3f\n", STEP1 + (kwh - kwh_a) * RATE2);
}
else if (kwh <= kwh_c){
printf("count: %.3f\n", STEP1 + STEP2 + (kwh - kwh_b) * RATE3);
}
else {
printf("count: %.3f\n", STEP1 + STEP2 + STEP3 + (kwh - kwh_c) * RATE3);
}
}
// P424
// if else嵌套形式也可以实现,效果一样,但还是上面的else if清晰明了
// if else嵌套形式有兴趣可以了解
//
// if (expression)
// statement;
// else
// if (expression)
// statement;
// else
// statement;
// 嵌套的if else语句被视为一条语句,因此可以不用花括号括起来,但括起来让代码更加清晰
// 好了,说了这么多if else嵌套语句,总结,else if语句真香...
printf("game over!\n");
return 0;
}
// C99标准要求编译器至少支持127层嵌套
// nested if 嵌套if
// if与else的匹配(先if后else,else if不在这里的讨论范围)
// else与离它最近的if语句(包括有括号和无括号的if语句)匹配
// 反正就是与最近的if语句相匹配呗...
// *找else相匹配的if要向前找
// *编译器是忽略缩进的,因此不能通过缩进来判断if与else的匹配
//
// if (expression)
// statement;
// if (expression) -- if
// statement; | 匹配
// else -- else
// statement;
//
// if (expression){ -- if
// statement; |
// if (expression) | 匹配
// statement; |
// } |
// else -- else
// statement;
//
// 100^2 = 100 * 100 100^1 = 100
// 100^2 * 100^1 = 100 * 100 * 100 = 100^(2+1) = 100^3
// 100^0.5等于100的平方根
// 100^0.5 * 100^0.5 = 100^(0.5+0.5) = 100^1
int division(void)
{
int div, num;
for (div = 2, scanf("%d", &num); div < num; div++){
// num % div
// num模除div,余数为0的就是能整除的
if (num % div == 0){
printf("num / div = %d, div = %d\n", num / div, div);
}
}
// optimize 优化
return 0;
}
// 数学知识点:
// 自然数: 0和正整数
// 素数(质数prime number): 大于1的自然数,且除了1和自身,不能被其它自然数整除
int prime_number(void)
{
long unsigned int div, num;
// bool为_Bool的别名,在stdbool.h头文件中定义
bool is_prime_number;
while (scanf("%lu", &num) == 1)
{
// for 计数循环(counting loop)
// 根据素数的定义,除了1和数本身,依次循环2至少数的前一位数
// for循环开始前div,is_prime_number初始化,且仅初始化一次
for (div = 2, is_prime_number = true; div < num; div++)
{
// 如果数能被其它自然数整除,这个数就不是素数,标记flag设置为假false
if (num % div == 0){
printf("Not prime number, %lu is divisible by %lu.\n"
, num, div);
is_prime_number = false;
}
}
// is_prime_number为真,说明数不能被除1和本身之外的自然数整除,是素数(质数)
if (is_prime_number == true){
printf("Yes, %lu is prime number!\n", num);
}
}
return 0;
}
// 2也是素数(比较特殊的素数),但在程序中,for测试条件为假,is_prime_number为真,
// 没经过if测试语句验证,经过if测试语句的话is_prime_number就为假了,所以for测试条件为假算是歪打正着吧...
// 这段程序还是有个缺陷,会把0,1(可通过while测试条件验证)当成素数,学到后面的逻辑运算符再完善
// 逻辑运算符
// && 与
// || 或
// ! 非
// if else
//
// if (expression)
// statement
//
// if (expression)
// statement1
// else
// statement2
//
// if (expression1)
// statement1
// else if (expression2)
// statement2
// else
// statement3
//
// statement可以是一条简单语句或复合语句
#include
#include
#define SPACE ' '
#define COMMA ','
int char_count(void);
int lower_character(void);
int main(void)
{
char_count();
lower_character();
return 0;
}
int char_count(void)
{
char ch;
unsigned int count = 0;
while ((ch = getchar()) != '.'){
if (ch != SPACE && ch != COMMA){
count++;
}
}
printf("character count: %d\n", count);
ch = getchar();
printf("function char_count last input(%%d): %d\n", ch);
return 0;
}
// 逻辑运算符 logical operator
// && 与 左右两边的运算对象都为真,整个表达式的值为真
// || 或 左右两边的运算对象只要有一个为真,整个表达式的值就为真
// ! 非 运算对象为真,整个表达式的值为假;运算对象为假,整个表达式的值为真
//
// P440
// iso646.h
// && 与 and
// || 或 or
// ! 非 not
// iso646.h头文件定义了一批常用运算符的可选拼写(定义了宏)
// 因为不同地区的键盘是有不同的,有的键盘不一定有美式键盘的一些符号
// 但我是直接用美式键盘的,因此对这个无感...
// 目的:解决有的键盘没有这些符号的问题
// alternative n 可供选择的; adj 可供替代的
// spellings 拼写
// 感觉翻译成 可供替代的拼写 更好理解
// 简而言之:非美式键盘与可能没有某些符号键,导致不能输入某些运算符,
// 因此C的iso646.h头文件定义了一组宏,提供了可替代的拼写,代替无法输入的运算符
// 宏的定义有点像符号常量
// P440
// 逻辑运算符优先级
// !优先级之比圆括号运算符低,与递增运算符相同,(应该+/-正负运算符也是一样的)
// &&和||的优先级比关系运算符低,比赋值运算符高
// &&优先级比||高
// 运算符优先级operator precedence暂时小结(不全):
// ()圆括号
// >> !逻辑运算符非,+/-正负符号,++/--自增自减运算符
// >> + - * / % 加减乘除 模除
// >> == != > >= < <= 关系运算符
// >> &&逻辑运算符与and >> ||逻辑运算符或or
// >> =赋值运算符和+=等组合赋值运算符
// >> ,逗号运算符comma
// P440
// *我觉得是很重要的一个知识点:求值顺序
// 两个运算符共享一个运算对象,运算顺序取决与运算符优先级(例如:5+3-2 5+3*2)
// 除了两个运算符共享一个运算对象的情况外,C通常不规定复杂表达式的哪个部分先求值
// a = (5 + 3) * (2 - 1)
// C把先求值5+3,还是先求值2-1的决定权交给编译器的设计者
// 但c保证&&和||的求值顺序从左往右(还有一个是逗号运算符)
// &&或||是一个序列点,左侧项副作用发生完才会执行下一步运算
// C语言的陷阱...
// C只有两个运算符共享一个运算对象,&& || , ?:运算符的求值顺序是确定的
// 因此要多点用(),不然在不同的编译器、操作系统、特定机器下,运算结果可能会有不同
// 多用()才是一个好习惯...
// 短路计算:
// && operand1为0假,短路计算,整个逻辑表达式为假
// || operand1为1真,短路计算,整个逻辑表达式为真
// *重要概念
// 优先级: 决定运算对象与相邻两个不同运算符(我感觉更精确的说法是优先级不同的运算符)的结合顺序
// 结合律: 决定运算对象与相邻两个相同优先级运算符的结合顺序
// 3 + 2 * 5 *优先级比+高,2先与5结合
// 3 + 2 - 5 +和-优先级相等,按结合律从左往右结合,先+后-
// 简而言之:两个运算符共享一个运算对象,与运算符优先级高的结合;运算符优先级相等的,根据结合律(从左往右或从右往左)
// a = 2 && 1 < 2 (&&右侧的操作数为1 < 2的值,所以可以把1 < 2看作&&右侧的操作数)
//解析: 根据优先级,操作数2与&&结合,对左侧操作数求值为2
// c规定&&求值顺序从左往右,&&作为序列点,左侧操作数副作用发生完,再对右侧操作数求值
// 根据优先级,操作数1与<结合,1 < 2子表达式为&&的右操作数,求值结果为1
// &&表达式结果为1,再把1赋值给a
//
// && || 逻辑运算符,序列点,保证左侧操作数求值完(副作用发生完),再对右侧操作数求值
// , 逗号运算符,序列点,同上
// ; 序列点,左侧语句副作用发生完,再执行右侧语句
int lower_character(void)
{
char ch;
int n;
printf("\na: %d ,z: %d.\n", 'a', 'z');
printf("lower character:\n");
for (ch = 'a'; ch <= 'z'; ch++){
// char,shor int运算时被升级(promotion)成int类型
// for (ch = 97; ch <= 122; ch++){
printf("%c ", ch);
}
printf("\n");
for (n = 90; n <= 130; n++){
if (n >= 'a' && n <= 'z'){
printf("Lower character, %d, %c\n",
n, n);
}
else {
printf("Not lower character: %d.\n",
n);
}
}
return 0;
}
// P443
// EBCDIC不像ASCII码那样连续编码和相邻字符一一对应
// 因此不能像ASCII码处理
// 要判断EBCDIC是否为小写字符,用islower()函数来处理
// islower()函数原型声明在ctype.h头文件
#include
#include
#include
#define INPUT_FINISH_FLAG '|'
#define LINE_FLAG '\n'
int text_counter(void);
int conditional_operator(void);
int max_integer(void);
int cans_of_paint(void);
int main(void)
{
_Bool bool_a;
bool bool_b;
int int_a;
printf("sizeof(int_a):%zd\n", sizeof(int_a));
printf("sizeof(bool_a):%zd sizeof(bool_a):%zd\n",
sizeof(bool_a), sizeof(bool_b));
printf("true:%d false:%d\n", true, false);
//text_counter();
//conditional_operator();
//max_integer();
cans_of_paint();
return 0;
}
// 空白字符(空格,制表符,换行符): ' ','\t','\n'
// 排除空白字符: c != ' ' && c != '\t' && c != '\n'
// 检查空白字符: c != ' ' || c != '\t' || c != '\n'
//
// c提供了检查空白字符的函数isspace(),函数原型声明在ctype.h头文件
// isspace(c),c为空白字符,函数返回值为真,c为非空白字符,函数返回值为假
int text_counter(void)
{
bool word_flag = false;
char ch;
int lines_counter = 0;
int words_counter = 0;
while ((ch = getchar()) != INPUT_FINISH_FLAG){
// lines
if (ch == '\n'){
lines_counter++;
}
// words
// 遇到第一个非空白字符
// 如果 字符为非空白字符 且 单词标记为假(伪代码,大白话...)
if (!isspace(ch) && !word_flag){
words_counter++;
word_flag = true;
}
// 读取非空白字符之后读取到的首个空白字符
// 重置word_flag为假
// 如果 字符为空白字符 且 单词标记为真
if (isspace(ch) && word_flag){
word_flag = false;
}
}
printf("words number:%d\n", words_counter);
printf("lines number:%d\n", lines_counter);
return 0;
}
// 条件运算符(唯一一个三元运算符) ?:
// 条件表达式: expression1 ? expression2 : expression3
// 如果expression1的值为真,整个条件表达式的值为expression2的值
// 如果expression1的值为假,整个条件表达式的值为expression3的值
// 非0为真,0为假
// 相当于:
// if (y >= 0)
// x = -y;
// else
// x = -y;
int conditional_operator(void)
{
int x, y;
printf("input y:");
while (scanf("%d", &y) == 1){
x = (y >= 0) ? -y : -y;
printf("x=%d y=%d\n", x, y);
}
return 0;
}
int max_integer(void)
{
int a, b, max;
while (scanf(" %d %d", &a, &b) == 2){
max = (a > b) ? a : b;
// a > b a为最大值:取a值
// 以下为!(a > b)的两种情况:取b值
// a = b a和b都为最大值,因此取a值或b值都是一样的
// a < b b为最大值
printf("max=%d\n", max);
//条件运算符的第2和第3个运算对象可以是字符串
printf("%s", (a > b) ? "a > b" : "a <= b");
//printf("%s", a > b ? "a > b" : "a <= b");
//条件运算符优先级比关系运算符低,因此下面的语句效果也是一样的,
//但上一句可读性更高
}
}
int cans_of_paint(void)
{
const int can_area = 20;
int paint_area, cans;
printf("tips: input number of paint area\n");
while (scanf("%d", &paint_area) == 1){
cans = paint_area / can_area;
cans += (paint_area % can_area == 0) ? 0 : 1;
printf("cans of paint: %d\n", cans);
};
printf("over!\n");
return 0;
}
// ?: 条件运算符 是 三元运算符
// 需要三个运算对象,每个运算对象都是一个表达式
// expression1 ? expression2 : expression3 (条件表达式)
// expression1的值为真,则整个表达式的值为expression2的值
// expression1的值为假,则整个表达式的值为expression3的值
//20201202 test git
//git config --global credential.helper store (github免密码,要先账号密码登陆一次)
#include /* 提供整数类型大小相关的信息,里面定义了明示常量 具体《c primer plus》P190 */
#include /* 提供浮点数类型大小相关的信息,里面定义了明示常量 */
//ascii(american standard code for information interchange):美国信息交换标准代码
//包含0~9,大小写字母和常用符号,用一个字节的数据表示
//unicode(unique universal uniform character encode):唯一 通用 统一 字符编码
//utf-8(8-bit unicode transformation format):8比特位unicode转换格式
//ALU(arithmetic logic unit):算术逻辑单元
//PS(program counter):程序计数器
//USB(universal serial bus):通用串行总线
//shell是一个命令行解释器,如果输入命令行的第一个单词不是shell内置命令,那么shell会假设这个是
//可执行文件的名字,它将加载并运行这个文件
//sizeof是运算符(operator):唯一一个单词形式的运算符
/*
|<-- compilation system编译系统
|
|--> cpp(pre-processor)预处理器
|--> ccl(compiler)编译器
|--> assembler(as)汇编器:将指令转变为机器码
|--> linker(ld is short for link editor)链接器
*/
/*
cpu(central processing unit):中央处理器
main memory:主存(我们平常所说的内存条)
*/
//program:程序,编码指令
//程序是一个指令序列
//这一段代码以字节序列的方式存储在文件中,每个字符都对应一个ascii码,中文字符是unicode码
//只由ascii码构成的文件称为文本文件(也有unicode文本文件),所有其他文件都称为二进制文件
//所有数据都是一串比特表示,区分不同数据对象的唯一方法是读取数据对象上下文,
//在不同的上下文中,一个相同的字节序列可能表示一个整数,浮点数,字符串或机器指令.
//进程(process)是操作系统对一个正在运行的程序的抽象
//并发运行是指一个程序的指令和另一个程序的指令是交错执行的
//单核处理器一个时刻只能处理一个程序,多核处理器一个时刻同时能够执行多个程序
//处理器在进程间切换让单核处理器看上去能并发处理多个程序
//操作系统实现这种进程间交错执行的机制称为上下文切换
#include /* standard input & output . head头文件 */
// * 重要知识点(编译系统的编译过程):
// * stdio.h是C编译器软件包的标准库的一个文件,它提供键盘输入和屏幕输出的支持
// * cpp(c pre-processor:预处理器)会读取stdio.h文件(C标准库的头文件)的内容并插入到程序文本中
// * 还有删除注释,字符常量的替换
// * 结果得到另外一个C程序,通常以.i作为文件扩展名
// * 源程序source program(文本text)hello.c --[cpp]-->修改了的源程序modified source program(文本)hello.i,
// * 旁注:(这里的文本编码格式是utf-8, 8bit unicode transformation format);
// * utf-8可节省编码储存长度,用utf-8编码unicdoe长度可变;
// * unicode码(4字节)是兼容ascii码(1字节)的.
// * hello.i --[ccl: c compiler]--> hello.s汇编程序assembly program(文本)
// * hello.s --[as: assembler]--> hello.o(relocatable object program可重定位目标程序(binary二进制文件))
// * hello.o --[ld: linker]--> hello可执行目标程序executable object program(二进制)
// * 把hello程序要调用到的标准C库的函数合并到hello.o程序中,
// * 比如调用到printf函数,printf函数存在于一个名为printf.o的单独编译的可重定位目标程序中,
// * printf.o以某种方式合并到hello.o文件,ld负责处理这种合并,
// * 合并后得到文件hello,可执行目标文件(简称可执行文件),可以加载到内存中,有系统执行.
int main(void)
{
float f_var_a;
// \b : 光标左移一位
// \r : 光标回到当前行起始处
// \n : 光标移到下一行起始处
printf("Enter floating point number: $__________\b\b\b\b\b\b\b\b\b\b");
scanf("%f", &f_var_a);
printf("hello world!\n");
return 0;
//函数体内程序结束,不加的话运行到函数内的最后一句代码之后也是会结束返回的
//return后面可以加返回值
//加在这里,下面的代码就不会运行了
printf("hello world!\n");
}
//屏幕上显示的这段代码只是ascii码对应的字体文件的屏幕输出(这只是我个人的简化理解)
/*
个人的一点简化的理解(省略很多细节,而且未必正确,但目前对我的知识水平来说解释得通...):
假设w=3,无符号数,源程序数据:7+7(文本)--编译系统-->可执行文件(二进制数据:111+111)
shell命令行解释器,输入可执行文件名称,按回车(这过程在计算机中的细节去看《深入理解计算机系统》P7)
111和111加载在cpu寄存器堆,alu(arithmetic logic unit)读取数据进行加法运算
得到数据1(110)--保留110,
%u(format specific格式说明符),编译系统编译后应该是个机器指令
这个机器指令是把110处理成字符 6的ascii编码
ascii编码从寄存器--->总线-->图形适配器-->显示器 显示6在屏幕上
w=3,B2U(bit vector) --映射map--> [0,7]的ascii编码
按照我们人类的思维,x+y应该最大值是7+7=14
经过上面一段描述的过程
发生了所谓的溢出,得到的结果6= 14-8 = 14 mod 8
short unsigned x=1;(文本文件)--编译系统(compilation system)--
--> 二进制文件(机器语言代码)中 数据x=[0000 0000 0000 0001]
C编译器根据C语言的标准来实现
*/
// 计算机基础知识:
// 半导体,常温条件下导电性能介于导体和绝缘体之间的材料
// 晶体管,它是一个开关,可以用控制线路来控制开或关
// 电极(可当成输入)
// |
// /
// 控制线control wire --------| <---- "|"为半导体,是一个门电极gate electode
// \ 通过控制线改变门的电荷electrical charge of the gate
// | 来控制半导体材料的导电性
// 电极(可当成输出)
// 布尔代数(boolean)
// 1.NOT Gate 非门:
// 电极
// |_____ output输出
// /
// input输入 --------|
// \
// |
// __|__
// ___ "接地"
// input off, output on 电流往output方向流动
// input on , output off 电流往接地方向流动
// logic table for Not:
// ____________________
// | input | output |
// | true 1 | false 0 |
// | false 0 | true 1 |
// --------------------
// 2.AND Gate 与门:
// _____ ____ _____ output
// \/ \/
// ---- ----
// | |
// inputA inputB
// logic table for And:
// ________________________________
// | input A | output B | output |
// | true 1 | true 1 | true 1 |
// | true 1 | false 0 | false 0 |
// | false 0 | true 1 | false 0 |
// | false 0 | false 0 | false 0 |
// --------------------------------
// 3.OR Gate 或门:
// current电流
// __________ ___
// | \___/ |
// current ---| | |-- output
// |__ ____/-\____|
// \___/ |
// | |
// inputA inputB (transistor晶体管)
// ________________________________
// | input A | output B | output |
// | true 1 | true 1 | true 1 |
// | true 1 | false 0 | true 1 |
// | false 0 | true 1 | true 1 |
// | false 0 | false 0 | false 0 |
// --------------------------------
// 4.XOR Gate 异或门:
//
// =|=AND-Not-|
// |=OR------|=AND--
// ________________________________
// | input A | output B | output |
// | true 1 | true 1 | false 0 |
// | true 1 | false 0 | true 1 |
// | false 0 | true 1 | true 1 |
// | false 0 | false 0 | false 0 |
// --------------------------------
//-----------------------------------------------------
// ALU: arithmetic logic unit
// an arithmetic unit and a logic unit
//-----------------------------------------------------
// 5.half adder 半加法器:
// _______________________
// | input | output |
// ------------------------
// | A | B |carry| sum |
// ------------------------
// | 0 | 0 | 0 | 0 |
// | 0 | 1 | 0 | 1 |
// | 1 | 0 | 0 | 1 |
// | 1 | 1 | 1 | 0 |
// ------------------------
// =|=XOR-- SUM
// |=AND-- CARRY
// 抽象(封装成一个单独组件):
// _____________
// A--| half adder |-- SUM
// B--|_____________|-- CARRY
// 6.full adder 全加器:
// 注: 这里的C是指低位的进位
// _____________________________
// | input | output |
// -------------------------------
// | A | B | C |carry| sum |
// -------------------------------
// | 0 | 0 | 0 | 0 | 0 |
// | 0 | 1 | 0 | 0 | 1 |
// | 1 | 0 | 0 | 0 | 1 |
// | 1 | 1 | 0 | 1 | 0 |
// -------------------------------
// | 0 | 0 | 1 | 0 | 1 |
// | 0 | 1 | 1 | 1 | 0 |
// | 1 | 0 | 1 | 1 | 0 |
// | 1 | 1 | 1 | 1 | 1 |
// -------------------------------
// _____________
// A--| half adder 2|-- CARRY -------------------------------- OR ------ C[i+1]
// B--|_____________|-- SUM---| |
// | _____________ |
// |----A--|half adder 1 |-- CARRY---|
// C[i]--------------------------- B--|_____________|-- SUM ------------ SUM[i] (i>=0)
// C[i+1]会发生进位CARRY的必要条件:
// 1. 全加器2只有A和B都为1时才会进位,它的SUM为0,所以全加器2最大只能为1,不会进位CARRY
// 2. 全加器1只有A(half adder2 SUM)和B都为1时才会进位
// 2.1 全加器1 A(全加器2 SUM)为1的情况,只有全加器2 A和B中其中一个为1,一个为0,全0或全1 SUM都为0
// 2.2 全加器1 B(低一位的进位C为1)
// 总结:半加器1和半加器2不会发生同时进位CARRY的情况
//
// 全加器抽象:
// ___________
// A----| full |----C[i+1]
// B----| adder |
// C[i]----|___________|----SUM[i]
//
// 7.8bit ripple carry adder(8位行波进位加法器):
// _____________
// A--| half adder |-- SUM ----------------------------------------------- SUM[0]
// B--|_____________|-- CARRY--|
// | ___________
// |--C[i]--| full |--SUM[i]---------------SUM[1]
// A--| adder |
// B--|___________|--C[i+1]---后面继续堆全加法器就行了
//
// 最高位的全加器如果发生进位carry,就会发生溢出overflow,超过8bit所能表示的数据
// * 现代的计算机用的加法电路有点不同,叫"超前进位加法器"
// 简单的CPU做乘法运算只是多做几次加法
// 现在的计算机和手机有专门的加法器,比较复杂,但本质也只是堆逻辑门
//
// 8. arithmetic & logic unit 算术逻辑单元
// 里面很多逻辑门,我们不必关心这些细节,抽象化:
// 8bit 8bit
// inputA inputB
// 4bit __|__ __|__ flags(常用的,还有很多其他标志,输出0/1-->假/真):
// operation code --> \ \ / / --> overflow (用一条线连接加法器最高位carry输出即可)
// \ \/ / --> zero (可用于两个数是否相等)
// \________/ --> negative (可用于比较连个数的大小)
// |
// output
// 8bit
// logic unit:
// zero flag(只要有1个1输入,输出结果就会为0假)
// =OR--|
// |--OR--|
// =OR--| |
// | |--OR--NOT--output
// =OR--| |
// |--OR--|
// =OR--|
//
// 注意: 各种门电路是一种抽象,我们只考虑输入和输出就好,不要太纠结于真实电路
//