写在前面:本笔记为个人学习各平台C语言系列课程所作,仅供交流学习,不得作他用。
1. 背景知识
C语言的变量在使用前需要先定义,确定其类型。C语言的发展有两个方向:
总体而言:早期的C语言面向底层,强调类型。面向应用的C语言会较为忽视类型。
2. 基本类型
| 整数 | char、short、int、long、long long |
| 浮点数 | float、double、long double |
| 逻辑 | bool |
| 指针 | - |
| 自定义 | - |
整数long long,浮点long double和逻辑bool是C99的类型。一般可以把逻辑并入整数类型中。
整数在计算机内部是二进制,自然数是二进制(补码),浮点数是编码类型。因此计算机内部可以直接对整数和自然数进行运算,但是无法直接对浮点数进行运算。
sizeof:运算符,给出某个类型或者变量在内存中占据的字节数,输出结果为ld类型。eg:sizeof(int)和sizeof(i)。
3. 整数类型
(1)数据内存
| 类型 | 64位环境 | 32位环境 |
| char | 1 | 1 |
| short | 2 | 2 |
| int | 4 | 4 |
| long | 8 | 4 |
| long long | 8 | 8 |
1字节=8bit,int类型1个字=4字节=32bit,表示范围[-2^32,2^32-1]。
CPU和RAM之间靠总线连接,CPU里有32位的寄存器,则存储、处理和传输数据时都是32位的。现在更常见的是64位。int是最基础的衡量单位,通常是一个字(寄存器大小),32位计算机的int类型字长是32bit,64位计算机的int类型字长是64bit。
(2)内部表达:二进制
int类型表示了一个寄存器,比int小的是一些位,比int大的为几个寄存器连接。
一个字节为8bit,可以表示[0-255]的无符号数。
将最高位赋予符号意义,1表示负数、0表示正数。补码和反码解决了计算机内部二进制计算需要另外符号的困境,使计算简化。
补码:计算机中100000000-原码=补码。补码的存在就是为了加上原码使和为1+8位0,在计算机中会默认丢掉最高位1,即剩下0。从计算层面,补码=反码+1。
反码:原码每个0\1反过来。
有符号数表示的范围如下:
中间为0,负数边有2^n的一半,正数边有2^n的一半-1(因为中间有0,不能完全对半分)。
(3)整数越界
符号数加减就按照这个来看。顺时针:做减法。逆时针:做加法。
这种情况一般出现在令一个字符为数字的情况下:char c=-128。c-1=127。
如果是无符号数:unsigned char c=255。c+1=0。(上面圆环-1换成255)
(4)整数格式化
整数有很多种格式,但在输入输出时只有两种。小于int的char和short都与int同方式输出(%d),比int大的与long long同方式输出。
以0开头的数字字面量是八进制,以0x开头的数字字面量是十六进制。
char c=012; int i=0x12; printf("%d %d",c,i); printf("%o %x",c,i);输出%d表示希望输出十进制,八进制下的012转换为十进制是10,十六进制下的0x12转换为十进制是18,第一行输出10和18。第二行%o和%x就是指输出八进制和十六进制,所以输出12和12。
各种进制都只是一种表示方式,把计算机内部同一个数字表示为不同的字符串。
(5)类型选择
整数类型有这么多种,是因为早期计算机为了准确表达内存和寄存器,做底层程序的必要。
没有特殊需要,就选择int。当前计算机多为32位或64位,一次读写运算基本就是一个int。选比int短的类型可能更慢。同时现代编译器会设计内存对齐,更短的整数类型有可能也占据了一个int内存。
4. 浮点类型
(1)基本类型
从范围可见,float和double在靠近0的两侧有一小部分是无效的,且double的无效范围更小,精度更高。
从有效数字可见,float在第八个数字开始不太准确,double在第16个数字开始不太准确。
(2)输入输出
%e和%E是科学计数法式的输出,区别在于小写大写。%f是普通小数输出,可以写成%.xf表示要x位小数的输出,这种表示方法是四舍六入五成双的。
如果要输出无穷大和不符合数学规则的数字呢?
采用%f输出时,printf()输出正数/0时得到输出inf,负数/0时得到输出-inf,0/0时得到输出nan。
采用%d输出时,printf()输出任何数/0都会报错。因为无穷大只能用浮点数来表达。
(3)浮点运算
计算机内部无法完全精准的表示出一个数字,比如%.30f输出0.0049,你会得到一个0.0048接很多位小数的数字,它很接近0.0049。这就导致如下代码输出如下结果:
前面说过,float只有七位有效数字,所以打出%.10f时输出的数字和正确结果有点差异。这里也说明,浮点计算的精度是有问题的。所以计算时一般不使用浮点数进行计算,避免误差累计。
一般也不用==来判断两个浮点数是否相等,而是计算两个浮点数的差的绝对值,判断是否小于一个很小的数。
在上面的代码例子中,1.345f使用f来表示这个数字是float类型。如果不带f的纯小数,默认为double类型。
(4)内部表达
浮点数在计算机内部不是二进制数,而是用一种编码形式实现。比如上图前11位表示指数部分。
计算时,CPU会带着专门计算浮点数的硬件进行浮点计算。这个硬件接收到编码的浮点数,先解码,再计算,最后重新编码返回结果给内存。计算float和double的部件、方式是一样的。
5. 字符类型
(1)输入输出
一个例子:
char a=1; char b='1';这里a的char是整数类型,会直接输出1。b的char是字符类型,如果是以d%形式输出,会得到49。
int a; char b; char c; scanf("%c",&b); scanf("%d",&a); c=a;如果在scanf里以%c的形式输入,则输入是字符类型,如上面的b。输入‘1’时,%c形式输出的b是‘1’,%d形式输出的b是49。
如果想输入一个整数给字符类型的变量,需要先输入给一个整数类型的变量,再把它赋给字符类型变量。如上面的a和c。输入1时,c无法以%c形式输出,可以以%d形式输出1。但如果输入49,%c形式输出的c是‘1’。
输入字符给字符类型变量的方式有两种:一是直接以%c形式输入,二是以%d形式输入该字符对应的ASCII编码。
同理:判断'字符'==对应ASCII码,会得到肯定的结果。
(2)混合输入
对于这种混合输入,第一种带空格的正常读取。第二种如果在终端输入时不小心在两个数据中打了空格,第二个c会得到空格的字符‘ ’。
(3)字符计算
有字符参与的运算,先利用ASCII码将其转换为整数,然后计算,最后利用ASCII码转换为字符。
(4)大小写转换
(5)逃逸字符
上图中,\"指的是输出时打出的双引号,可以避免系统认为双引号之间的是一整个字符串。
注意:我们运行程序是在终端输入输出的,终端说白了也是一个人为编写的程序(通常称为shell),不同的终端对同一种逃逸字符可能会有不同的结果。
\b回退:指的是删除前一个,补上后一个。比如:
printf("123\b1\n");会得到输出:121。
\n换行和\r回车:来源于早期打字机的动作。
6. 类型转换
(1)自动类型转换
比如:char类型和short类型一起时,会自动转换为short。
(2)强制类型转换
多用于把大的数据类型转换为小的数据类型。
上图中,short类型最大值为32767,因此(short)32768会得到-32768。
注意:强制转换只是从那个变量计算出了一个新的类型的值,它并不改变那个变量(值和类型都不变)。
注意:强制类型转换的优先级比四则运算要高。所以想要计算结果整体转类型,需要打小括号。
7. 逻辑类型(布尔)
(1)基本知识
最早的C语言没有布尔类型,一个判断式对就是1错就是0。到C9的时候有这种类型。基本使用如下
但由于C语言本质上没有布尔这种类型,需要引入一个头文件才能调用,所以布尔类型的变量本质上和int类型一样,可以直接把它们赋值为整数。
(2)逻辑运算
逻辑量是关系运算或逻辑运算的结果,而逻辑运算是对逻辑量的运算,结果只有1和0。
可以利用逻辑运算符表示区间。eg:x<4 && x>1。不能直接写为1<x<4。
小写字母换为a和z即可。
优先级:!> && > ||。至今大部分运算符优先级如下:
(3)短路
注意:对于和运算,左边为错时不做右边,如果右边有赋值之类的运算是不会赋值的。最好不要把赋值和普通计算写入逻辑运算,可以避免短路。
(4)条件运算符
格式为A = (条件)?成立的值:不成立的值。
条件运算符是从右向左结合的,尽量不要写太复杂的嵌套表达。
(5)逗号运算符
一个例子:
i= 3+4,5+6; i=(3+4,5+6);因为逗号优先级最低,比赋值还低,所以第一个式子的i=7,而5+6的11没有赋值给任何一个变量,会报错。第二个式子用括号提升了优先级,会取逗号右边的值11,因此i=11。
这种逗号运算符主要在for运算中使用,可以让你在括号里写很多个计算。