2 学习C语言的数据类型、运算符与表达式
2.1 C语言的数据类型
C语言提供了以上一些数据类型,在程序中对用到的所有数据都必须指定其数据类型。数据有常量与变量之分,它们分别属于以上这些类型。例如整型数据包括整型常量和整型变量。
2.2 常量与变量
2.2.1 常量
1. 常量:在程序运行过程中,其值不能被改变的量称为常量。(常量不能被取地址)
2. 宏定义指令(# define):是一个预处理指令,用于定义宏。例如," # define PI 3.1415926 " ,这里将 " PI " 定义为一个宏,在编译预处理阶段,代码中所有出现 PI 的地方都会被替换成常量 3.1415926 。
2.2.2 变量
1. 变量:在程序运行过程中,其值可以发生改变的量称为变量(是系统在内存空间RAM当中分配的一段空间,空间的值可以伴随程序的运行动态的做出修改)。在C语言中,要求对所有用到的变量作强制定义,也就是 " 先定义,后使用 ” 。(变量可以寻址,取地址来获得变量在内存中的位置)
2. 字节(Byte)是用来描述计算机存储的最小单位,1 Byte = 8 bit 。
3. 变量在内存当中的位置叫内存地址。
#include<stdio.h> int main (void) { int i ; printf("%p\n",&i); return 0; }
在C语言中, printf("%p\n", &i); 这行代码的作用是打印出变量 i 在内存中的地址。具体解释如下:
(1) &i : & 是取地址运算符, &i 表示获取变量 i 的内存地址。
(2) %p :是 printf 函数的一个格式控制符,专门用于以十六进制的形式输出指针(地址)值。
(3) \n :是换行符,使输出结果在新的一行显示。
4. 标识符(identifier):对变量、符号常量、函数、数组、类型等数据对象命名的有效字符序列。
规则:(1)只能由字母、数字和下划线3种字符组成;
(2)第一个字符必须为字母或下划线,不能为数字;
(3)不能是关键字,下表为C语言中的关键字:
C语言中的关键字 auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while (4)大小写敏感;
(5)尽量不要使用二类字例如 " include " " define " 等。
(注意:在gcc编译器中,允许将 " $ " 作为标识符的一部分来使用)
2.3 整型数据
2.3.1 整型常量的表示方法
1. 整型常量即整常数。在C语言中,整常数可用以下3种形式表示。
(1)十进制整数(DEC):如123、-456、4。(范围:0——9)
(2)八进制整数(OCT):以0开头的数是八进制数。(范围:0——7)
(3)十六进制整数(HEX):以0x开头的数是十六进制数。(范围:0——F)
基于计算机的硬件是基于二进制逻辑设计的,其内部的存储单元和运算单元都是以二进制位(0和1)来表示和处理数据的。所以,无论何种数据类型,包括整型常量,在计算机中最终都要以二进制形式存储和运算。
1. 十进制转二进制
(1)整数部分采用除 2 取余法,将十进制数除以 2,取余数,然后将商继续除以 2,直到商为 0。将每次得到的余数从下往上排列,就是对应的二进制整数部分。(辗转相除法)(2)小数部分采用乘 2 取整法,将十进制小数乘以 2,取所得结果的整数部分,然后将小数部分继续乘以 2,重复这个过程,直到小数部分为 0 或者达到所需的精度为止。将每次得到的整数部分按顺序排列,就是对应的二进制小数部分。(辗转相乘法)
例如,将十进制数 10 .625 转换为二进制:
整数部分十进制转二进制
10 / 2 = 5 …… 0 5 / 2 = 2 …… 1 2 / 2 = 1 …… 0 1 / 2 = 0 …… 1
小数部分十进制转二进制
0.625×2 = 1.25,整数部分是1,此时小数部分为0.25; 0.25×2 = 0.5,整数部分是0,此时小数部分为0.5; 0.5×2 = 1.0,整数部分是1,此时小数部分为0 ,转换结束。 整数部分从下往上取余数得到二进制数为 1010,小数部分0.625转换为二进制为 0.101 ,综上: 10 .625 转换为二进制为 1010.101 。
2. 八进制转二进制
因为 1 位八进制数对应 3 位二进制数,所以将八进制数的每一位转换为对应的 3 位二进制数即可。例如,将八进制数 13 转换为二进制:
八进制的 1 转换为二进制是 001
八进制的 3 转换为二进制是 011
所以八进制数 13 转换为二进制是 001011,去掉前面的 0 为 1011。
八进制数 0 1 2 3 4 5 6 7 二进制数 000 001 010 011 100 101 110 111 3. 十六进制转二进制
由于 1 位十六进制数对应 4 位二进制数,把十六进制数的每一位转换为对应的 4 位二进制数就行。例如,将十六进制数 1A 转换为二进制:
十六进制的 1 转换为二进制是 0001
十六进制的 A(A 对应十进制的 10)转换为二进制是 1010
所以十六进制数 1A 转换为二进制是 00011010,去掉前面的 0 为 11010 。
十六进制数 0 1 2 3 4 5 6 7 二进制数 0000 0001 0010 0011 0100 0101 0110 0111 十六进制数 8 9 A B C D E F 二进制数 1000 1001 1010 1011 1100 1101 1110 1111
2.3.2 整型变量
1. 整型数据在内存中的存放形式
数据在内存中是以二进制形式存放的。
如果定义了一个整型变量i:
int i; /*定义为整型变量*/ i = 10; /*给i赋以整数10*/
十进制数10的二进制形式为 1010 。int占4个字节,则该数据存放为 " 0000 0000 0000 0000 0000 0000 0000 1010 " 。实际上,数值是以补码(complement)表示的。
(1)正整数的补码和该数的原码(即该数的二进制形式)相同。
(2)负整数的补码是将该数的绝对值的二进制形式,按位取反再加一(取绝对值,按位取反再加一)。
(3)在存放整数的存储单元中,最高位是符号位,该位为0,表示数值为正;该位为1则表示数值为负。
(4)负整数补码到原码的转换:补码减一再按位取反。
例:求 -10 的补码方法:
①取-10 的绝对值 10 ;
②10的绝对值的二进制形式为 1010 ;
③对1010 取反得 1111 1111 1111 1111 1111 1111 1111 0101 (一个整数占32 位);
④再加1得 1111 1111 1111 1111 1111 1111 1111 0110 。
2. 整型变量的分类(共8类)
序号 | 名称( " [ ] " 中的内容可有可无) | 字节数 (Byte) | 比特(位)数 (bit) | 取值范围 | |
1 | 有符号短整型 [ signed ] short [ int ] | 2 | 16 | (-2^(15)) ~ (2^(15)-1) | |
2 | 无符号短整型 unsigned short [ int ] | 2 | 16 | 0 ~ (2^(16)-1) | |
3 | 有符号整型 [ signed ] int | 4 | 32 | (-2^(31)) ~ (2^(31)-1) | |
4 | 无符号整型 unsigned int | 4 | 32 | 0 ~ (2^(32)-1) | |
5 | 有符号长整型 [ signed ] long [ int ] | 8 | 64 | (-2^(63)) ~ (2^(63)-1) | |
6 | 无符号长整型 unsigned long [ int ] | 8 | 64 | 0 ~ (2^(64)-1) | |
7 | 有符号长长整型 [ signed ] long long [ int ] | 8 | 64 | (-2^(63)) ~ (2^(63)-1) | |
8 | 无符号长长整型 unsigned long long [ int ] | 8 | 64 | 0 ~ (2^(64)-1) |
对于有符号整型,由于最高位为符号位,所以取值范围为 -2^31 ~ 2^31-1 ,而不是 -2^32 ~ 2^32-1 。
C语言没有具体规定以上各类数据所占内存的字节数,只要求long型数据长度不短于int型,short 型不长于int型。具体如何实现,由各计算机系统自行决定。" sizeof( ) " 为长度运算符。
① " %d " 用于输出有符号十进制整数,包括正整数、负整数和零,对应的变量类型通常是 int 、short 、 long 等有符号整型。
② " %u " 用于输出无符号十进制整数,只能表示非负整数,对应的变量类型通常是 unsigned int 、 unsigned short 、 unsigned long 等无符号整型。
3. 整型数据的溢出
(1)对于有符号数
一个有符号短整型变量只能容纳 -32768 ~ 32767 范围内的数,无法表示大于 32767 或小于 -32768 的数。遇到此情况就发生“溢出”。但运行时并不报错。
(2)对于无符号数
一个无符号短整型变量只能容纳 0 ~ 65535 范围内的数,无法表示大于 65535 或小于 0 的数。遇到此情况就发生“溢出”。但运行时并不报错。
2.4 浮点型数据
2.4.1 浮点型常量的表示方法
1. 十进制小数形式:它由数字和小数点组成(注意必须有小数点)。0.123、123. 、123.0、0.0都是十进制小数形式。
2. 指数形式:如123e3或123E3都代表123X10^3。字母e(或E)之前必须有数字(正负都可),且e后面的指数必须为整数。(浮点数就是平常所说的实数)
2.4.2 浮点型变量
1. 浮点型数据在内存中的存放形式
浮点型数据与整型数据的存储方式不同,浮点型数据是按照 " 符号位、阶码、尾数 " 形式存储的。
(1)符号位:用来表示该浮点数的正负,通常0表示正数,1表示负数。
(2)阶码:对已知数的绝对值转换为二进制数,用科学计数法表示,将2的幂次项+偏移量表示为阶码。
(3)尾数:采用原码表示,存储的尾数是小数点后的数,后面补零。
例如:-6.25 ,为负数,所以符号位为1。 -6.25 的绝对值转换为二进制数: 110.01 ,用科学计数法表示为 1.1001 * 2^2 , 对应的阶码为2的幂次项 " 2 " + 偏移量(对于单精度浮点数,偏移量为127;对于双精度浮点数,偏移量为1023)。尾数为小数点后的数 "1001 "后面补零。
2. 浮点型变量的分类
序号 | 名称 | 字节数 (Byte) | 比特(位)数 (bit) | 取值范围 | |
1 | 单精度浮点数 float | 4 | 32 | (-3.4*10^(-38)) ~ (3.4*10^(38)) | |
2 | 双精度浮点数 double | 8 | 64 | (-1.7*10^(-308))~ (1.7*10^(308)) | |
3 | 长双精度浮点数 long double | 16 | 128 | (-1.2*10^(-4932))~ (1.2*10^(4932)) |
(1)float型共占32位bit,其中符号位占1位bit,阶码占8位bit,尾数占23位bit 。
(2)double型共占64位bit,其中符号位占1位bit,阶码占11位bit,尾数占52位bit 。
" %f "用于以小数形式输出单精度或双精度浮点数。默认输出时,会保留小数点后6位 。
对于下面的程序
打印结果为 " No " 的原因:
数据类型差异:在C语言中,字面值 0.9 默认是双精度( double )类型 。而代码中 f 是单精度( float )类型。当进行 f == 0.9 比较时, f 会被隐式转换为双精度类型再比较,但由于此时是尾数位补29个0,与本身双精度的 0.9 在计算机种存储的数值不同(阶码,尾数都不同),所以导致打印结果为 " No " 。解决方案(要使比较工作正常):
①使用相同的数据类型:
if(f == 0.9f) // 使用f后缀表示float类型②或者将f声明为double:
double f = 0.9;
if(f == 0.9)③更安全的做法是比较浮点数的差值是否在某个很小的范围内:
if(fabs(f - 0.9) < 0.000001)总结:在做比较运算时,左右两边数据类型一定要相符。
2.5 字符型数据
2.5.1 字符常量
C语言的字符常量是用单撇号括起来的一个字符。如'a'、'x'、'D'、'?'、'$'等都是字符常量。注意,'a'和'A'是不同的字符常量。除了以上形式的字符常量外,C还允许用一种特殊形式的字符常量,就是以一个字符“\”开头的字符序列,称为转义字符。
字符形式 | 含义 | ASCII代码 |
\n | 换行,将当前位置移到下一行开头 | 10 |
\t | 水平制表,跳到下一个 Tab 位置(每一列宽度相同8bit) | 9 |
\b | 退格:将光标往前挪一格,并不删掉 | 8 |
\r | 回车:把光标从本行当前位置移到本行最左边 (区别换行:将光标从上一行挪到下一行) | 13 |
\f | 换页 | 12 |
\\ | 代表一个反斜杠字符 " \ " | 92 |
\' | 代表一个单引号(撇号)字符 | 39 |
\" | 代表一个双引号字符 | 34 |
\ddd | 1到3位八进制数所代表的字符 | |
\xhh | 1到2位十六进制数所代表的字符 |
2.5.2 字符变量(与整型兼容)
名称 | 字节数(Byte) | 比特(位)数(bit) | 取值范围 | |
char | 1 | 8 | -128 ~ 127 | |
unsigned char | 1 | 8 | 0 ~ 255 |
2.5.3 字符数据在内存中的存储形式及其使用方法
将一个字符常量放到一个字符变量中,实际上并不是把该字符本身放到内存单元中去,而是将该字符的相应的ASCII代码放到存储单元中。
常用字符 | ASCII代码 |
0~9 | 48~57 |
A~Z | 65~90 |
a~z | 97~122 |
大写字母转小写字母:' A ' + ' 32 ' 小写字母转大写字母:' a ' - ' 32 ' |
2.5.4 字符串常量
字符串常量是一对双撇号括起来的字符序列。不能把一个字符串常量赋给一个字符变量。C规定以字符'\0'作为字符串结束标志。例如字符串" CHINA"实际上在内存中是:
C | H | I | N | A | \0 |
注意,在写字符串时不必加'0',否则会画蛇添足。\0'字符是系统自动加上的。字符串"a"实际上包含2个字符:'a'和'0',因此,想把它赋给只能容纳一个字符的字符变量c显然是不行的。
2.6 变量初始化
1. 在定义变量的同时使变量初始化:int a = 10; /*对变量i的初始化*/(变量初始化效率更高)
正确初始化方法:(1)int a,b,c = 10;
(2)int a = 10,b = 10,c = 10;
错误初始化方法: int a = b = c = 10;
2.7 各类数值型数据间的混合运算
2.7.1 隐式转换
整型、浮点型、字符型数据间可以混合运算。 在进行运算时,不同类型的数据要先转换成同一类型,然后进行运算。转换的规则按上图所示。
横向向左的箭头表示必定的转换,即 char 型,short 型 必定先转换为 int 型 ;float 型 必定先转换为 double 型 ,以提高运算精度。( 即使是两个 float 型 数据相加,也先都化成double型,然后再相加)。
纵向的箭头表示当运算对象为不同类型时转换的方向,由低向高转换。 如 int 型 与 double 型 数据进行运算,先将 int 型 的数据转换成 double 型,然后在两个同类型( double 型 )数据间进行运算,结果为 double 型。
2.7.2 显式转换(强制类型转换)
通过使用强制类型转换运算符来实现的,其一般形式为:" (类型名)(表达式) "。
在这个示例中,将 float 型的变量 f 强制转换为 int 型并赋值给 i ,输出结果可以看到 f 的值保持不变,而 i 的值为 7 ,小数部分被舍去。因此强制类型转换可能会导致数据丢失或精度降低。
由于 " printf " 默认保留小数点后六位,为避免小数点后超过六位导致的输出四舍五入,可以进行如上的强制类型转换保留前六位小数,舍去后面的小数部分。
2.8 算术运算符和算术表达式
2.8.1 基本的算术运算符
1. +(加法运算符,或正值运算符,如3 + 5、+ 3);
2. - (减法运算符,或负值运算符,如5 - 2、-3);
3. *(乘法运算符,如3 * 5);
4. / (除法运算符,如5 / 3);
5. %(求余运算符,如7 % 4的值为3);
注意:(1)%求余运算符只能用在整型或整型相兼容(字符型)的数据类型;
(2)%求余运算符的最终符号由其左操作数决定;
(3)%求余运算结果一定小于右操作数;
(4)%求余运算右操作数不能为 0 。
基本算术运算符的优先级排序: ( * 、 / 、 % ) > ( + 、 - )
2.8.2 自增、自减运算符
1. 自增运算符为 ++ ,自减运算符为 --
对于一个已经定义的 i 变量:
前置自增 ++i ,先将变量 i 的值在内存中直接加 1,然后再使用 i 增加之后的值参与后续运算。(++i 无临时空间,++i 和 i 用的是同一段空间)
后置自增 i++ ,会先创建一个临时变量(匿名变量)来保存 i 原来的值,然后让 i 在内存中的值增加 1,但是表达式 i++ 的值是使用那个临时变量中保存的 i 原来的值,而不是 i 增加 1 之后的值。(i++ 有临时空间 ,i++ 的空间值未加,而 i 的值加了)
( ++i 效率高于 i++ 。在同一个表达式下,不得对同一个变量进行多次反复的自增自减运算,由于编译器不同,会导致结果不确定)
2. (1)所有的临时变量都为右值;
(2)所有的常量都为右值;
(3)对于 const int i = 10 ; 关键字const修饰的 i 为只读变量(只能被初始化,不能被赋值)只读变量为左值。( const int i = 10 ; 与 int const i = 10 ; 是一样的)
判断左 / 右值的依据:是否能被取地址,左值能被取地址,右值不能被取地址。
左值:locatiable value ;右值:readable value 。
2.9 赋值运算符
2.9.1 赋值运算符
= 、 += 、 -= 、 *= 、 /= 、 %= 、 >>= 、 <<= 、 &= 、 ^= 、 |= 优先级为14级,结合方向自右至左 ," a += 5 " 等价于 " a = a + 5 " 等复合赋值运算符原理类似。
2.9.2 类型转换
1. 低精度到高精度:从低精度类型转换为高精度类型,数据能完整保留,不会丢失信息 ,如 int 转 double 。
2. 高精度到低精度:高精度类型转换为低精度类型,会截断小数部分,导致信息丢失 ,如 double 转 int 。
3. 大范围整数到小范围整数:大范围整数类型转换为小范围整数类型,高位二进制位截断,信息丢失 ,如 long 转 short 。
4. 小范围整数到大范围整数:有符号数高位按符号位扩展,无符号数高位补零扩展 ,如 short (有符号/无符号)转 int 。
5. 有符号数到无符号数:二进制位不变,解读方式改变,数值含义可能变化 ,如 char (有符号)转 char (无符号) 。
2.10 逗号运算符和逗号表达式
优先级为15级(最低),结合方向自左至右,也称为顺序求值运算符,用在循环问题中。
int t; t = (1 + 5 , 6 - 2 , 3 * 7); printf("%d\n",t);
如上述代码,最终结果由于逗号运算符顺序求值,结果为 3 * 7 = 21 。若没有括号将表达式括起来,即 t = 1 + 5 , 6 - 2 , 3 * 7;那么输出的结果将为 1 + 5 = 6 。
另,在函数调用过程当中,出现的逗号与在一般表达式当中的逗号不一样,例printf("%d\n",t);这里的逗号用作实参与实参之间的分隔符。
printf("%d\n",1 + 5 , 6 - 2 , 3 * 7);
对于上述代码,运行会出现问题,因为此时逗号相当于四个实参间的分隔符;要解决问题就要引入括号,即
printf("%d\n",(1 + 5 , 6 - 2 , 3 * 7));
才能运行出值为21的结果,括号里的逗号才为逗号运算符。
学习总结:
在C语言中,%d 、 %u 、 %c 、 %f 、 %p 是格式说明符,用于 printf 等输出函数(部分也用于 scanf 等输入函数 )来指定数据的输入输出格式,具体用法如下:
1. %d :用于输出有符号十进制整数 。
2. %u :用于输出无符号十进制整数。无符号数不存在负数形式,其表示范围是从 0 开始的正整数 。
3. %c :用于输出单个字符。
4. %f :用于输出浮点数,默认保留6位小数。(在printf函数中单 / 双精度都可用%f ,但在scanf里对双精度必须要用%lf)
5. %p :用于输出指针的值,也就是内存地址,通常以十六进制形式显示。
大范围整数类型转换为小范围整数类型,高位二进制位截断,信息丢失 ,如 long 转 short 。
小范围整数类型转换为大范围整数:有符号数高位按符号位扩展,无符号数高位补零扩展 ,如 short (有符号/无符号)转 int 。
各类数值型数据间的混合运算,char 型,short 型 必定先转换为 int 型 ;float 型 必定先转换为 double 型 。
整型数据溢出:
原理:C 语言中不同整型数据类型有固定的存储大小和表示范围,当运算结果超出该类型能表示的最大值或最小值时,就会发生溢出。整型数据以二进制补码形式存储,有符号整型的最高位是符号位,0 表示正数,1 表示负数。当进行运算导致二进制表示超出范围时,会产生溢出。
以
int
类型为例,取值范围是 (-2^(31)) 到 (2^(31)-1) ,当执行以下代码时:#include <stdio.h> int main() { int a = 2147483647; /* 最大的int值 */ int b = a + 1; printf("b的值为: %d\n", b); return 0; }
运行结果中
b
的值会变为 -2147483648 ,这是因为 2147483647 加 1 后,二进制表示发生溢出,最高位变为 1,被解释为负数,成为 int 类型能表示的最小值。影响:溢出后,程序不会报错,但结果是错误的,可能导致程序逻辑出现问题,如循环条件判断错误等。