引入
我们知道计算机是人类用来解决某些问题的一种工具。那计算机是通过什么来解决问题的呢?计算机是解决哪一类的问题? “计算” computer
计算机通过把问题域的数据保存起来,然后通过某些运算从 而得到结果。 程序=算法 + 数据结构 计算机首先要解决数据保存的问题,在数据保存之前,我们首先 要知道这个数据的大小、取值范围、……等等数据的属性,不然我 怎么知道开辟多大的空间来保存这些数据呢? 数据的大小、取值范围,……。这些数据的属性,都是我们“数据类型” 要讨论的问题。
数据类型是用于描述不同类型的数据存放在内存中的形式
数据类型分类: 1.整型(短整型,整型,长整型,长长整型,无符号短整型,无符号整型,无符号长整形,无符号长长整型) 2.浮点型 3.构造类型 。。。
-
字节(byte B):计算机存储容量的一种单位
-
比特位(bit) :二进制数
-
二进制数 0 1
-
1字节 = 8位
-
1个千字节(KB) = 1024字节
-
1M = 1024KB
-
1G = 1024M
-
1T = 1024G
-
1.数据类型
就是数据的类型。 typeof (x) => 求对象x的类型
typeof(3) => int
typeof(3.0) => double
typeof(1+2)
在C语言中,有如下类型:
1.1基本类型
C语言已经为我们定义好的类型,主要用来保存数(整数/浮点数)
整数类型=》整型 用来表示整数的数据类型
char/unsigned char short/unsigned short int/unsigned int long/unsigned long ... 上面这些基本都是用来保存整数的, 有什么区别呢?
-
signed/unsigned 有符号(signed): 符号位(最高位) + 数值位 1 -> 负数 0 -> 0或正数 无符号(unsigned): 数值位
-
char / short / int ... 所占的内在空间大小不一样。 char -> 8bits unsigned char -> 8bits unsigned char 能保存的数值的范围是 [0, 255] unsigned char a = 260;//00000000 00000000 00000001 00000100 a ? ==》 4
-
注意:
short : 一般 16bits 2 bytes
int : 32bits的机器下,一般为32bits 4 bytes long: 32bits的机器下,一般也为32bits 4 bytes
64bits的机器下,一般也为64bits 8 bytes
...
而且在不同的编译器,同一类型的范围也不一样,
如:
keil int 16bits ubuntu 14.04 32bits int 32bits
sizeof(x) 求类型x的所占的字节数 sizeof(char) ==> 1 8bits
sizeof(4) ==>4
==》sizeof(typeof(4)) ==>sieof(int) ==>4
sizeof(long) >= sizeof(int) >= sizeof(short) >= sizeof(char)
在GCC编译器,整数默认类型是 int typeof(3) => int
sizeof(3+4) => sizeof(7) ==> sizeof(typeof(7)) ==> sizeof(int) ==> 4
格式控制符
-
int 整型:%d
-
short整型:%hd,h代表half,即一半的存储字节
-
long整型:%ld
-
long long整型:%lld
-
显示不同进制的前缀 : %#o、%#x
-
符号位:
-
有符号的整型数据,首位为符号位,0表示正数,1表示负数。
-
无符号的整型数据,没有符号位
int a = 123; printf("%#o",a);// 0173
-
浮点数 ==》浮点型 用来表示实数(有理数和无理数)的数据类型
-
float : 单精度浮点数 4
-
double : 双精度浮点数 8
-
long double : 长双精度浮点数 16 占用内存越多,能表达的精度越高
浮点类型 | 最小精度 | 最大数值 |
---|---|---|
float | 10^-37 | 10^38 |
double | 10^-307 | 10^38 |
long double | 10^-4931 | 10^4932 |
「课堂练习1」
指出下列常量的类型
'\b' 1066 99.44 0XAA 2.0e30 0x00EEABCDUL
'\b' : char字符型数据,代表退格键 1066 : int整型数据,十进制整数 99.44: double浮点型数据 0XAA : int整型数据,十六进制整数 2.0e30 : double浮点型数据,科学计数法,代表2乘以10的30次方 0x00EEABCDUL : unsigned long无符号整型数据,十六进制,末尾的UL代表无符号长整型
1.2 构造类型 :C语言允许程序自定义类型
数组:一组相同元素的数据集合。
结构体
联合体
枚举
1.3 指针类型
先wait wait, 后面有专题讲。
1.4 void类型
空类型
sizeof(void) ==> 0
void在C语言中,有三个作用: a: void* 通用指针,泛型指针 b: void当作是函数的形参,表示该函数不需要带参数 如: int func(void) { } 调用func: func();//调用该函数不需要给任何数据 c: void当作是函数的返回值类型,表示该函数无返回值。 如: void abc() { } 调用abc: int x = abc() ; //有问题的!!!! 语法错误 ==》 eg:错误提示: china@ubuntu:$ gcc int.c int.c: In function ‘main’: int.c:8:10: error: void value not ignored as it ought to be int x = func();
在研究了数据类型后,下面要保存数据啦。 在C语言中数据有两类: (1) 变量 (2) 常量
2. 变量
变量是什么? 在程序运行期间,可以改变其值的数据对象。 存储变量会对应一个存储单元 . 并且这个存储单元一定是可写的(我们要改变这个变量的值)。
2.1 变量的定义
变量在使用前必须要定义。
-
语法:
变量的类型 变量名 {= 变量的初始值}; {}可选
-
变量的类型:
所有合法的类型都可以的。
-
变量名:
一个对象的名字,标识符。 标识符:用来标识一个东东的符号。 标识符其实是一个对象(如:变量,数组,函数,类型,标号,……)的名字。 在C语言中,取名字不能乱取。要符合一定的规则,这个规则标识符的规则。
标识符:
只能由字母、下hua线、数字组成,并且第一个字符必须是字母或下hua线。 why? 你懂的。
标识符 “名如其意” int sum; int he; int sum_array; int he_shuzu;
-
变量的初始值: 定义变量时,赋值给变量的值,初始值。
-
案列:
int a; ==》 a 就是未知数
2.2 变量的属性
-
变量的类型
-
变量名
-
变量的存储单元(变量的地址)
在程序运行时,系统会为每一个变量分配一个存储空间,用来保存这个变量的值。并且这个存储空间会有一个唯一的地址, 这个地址,称之为变量的地址。
-
变量的值
变量的存储单元中的内容(无非就是高电平/低电平)每个变量一定会一个确定的值(不管你有没有赋值) why?
因为存储单元中,bit要么是1/0
bit 计算机中存储数据的最小单位,它只有两个状态 1 -> 高电平 0 -> 低电平
2.3 变量的访问
读/写 读: 从变量的存储单元中读取存储单元中的内容。 读变量的值。 写: 把一个数值写到变量所对应的存储单元中去。 “赋值”
案列: int a = 5; a = 1024; 把1024这个值写到变量a所对应的地址中去。 (1) b = a * 5; 把a的值乘以5,赋值给b (2)
上面(1) 和(2)同样是一个a, 但是它们的含义却是不一样的!!!! 结论: 在C语言中,任何变量,都有且仅有两层含义: (1) 代表变量的地址 lvalue: location value 可寻址(可写)的值 left value 左值 (2) 代表变量的值 rvalue: readable value 可读的值 right value 右值
变量的左值: 变量的地址, "="的左边 变量的右值: 变量的值, "=" 的右边
3. 整数的存储问题
整数在计算机中是如何存放的呢?
整数是以二进制的补码形式存放的。
你们知道计算机中为什么要用补码呢?
为了在计算机能够正确存储正负数,并且能够进行准确的计算
正数
正数的补码就是其原码本身。
负数:
负数的补码是其 绝对值的原码 取反 +1 得到。
一个有趣的现象: 假设 8bits来存放一个整数 -2 的存放形式是什么? 254 的存放形式是什么? 1111 1110
-3的存放形式是什么? 253的存放形式是什么? 1111 1101
-4的存放形式是什么? 252的存放形式是什么? 。。。。。。
结论:
结论1: 一个负整数会和一个比较大的正整数的补码形式一样; 一个负整数会和一个比较大的正整数在计算机中的存放形式一样的。
-x 和 (2^n - x) 一样。 2^n 表示2的n次幂 n表示什么意思?用多少bits来存储一个整数!!! n=8 -37 和 256 - 37
结论2: CPU内部是没有符号位的概念,对于CPU来说,所有的bit位, 都是数值位,都参与运算。至于是有符号的,还是无符号数, 就得看编译器的词义啦(意思就是说,你把它当作是一个有符号 数,还是一个无符号数)。
「课堂练习2」
练习: 假设int 32bits
(1) 假设计算机中用8bits来存储一个整数,已知某个整数 在计算机中的存放形式如下: 1111 1110 请问这个整数是多少? -2 or 254 到底是-2还是254?
就得看你(cpu,编译器)把它当作是一个有符号数(signed)还是一个 无符号数(unsigned)来看
(2) 分析如下程序的输出结果?
char a = -2; printf("%d\n", a); // printf("%u\n", a); //
%d把后面那个玩意,当作是一个有符号的int,按十进制输出 %u把后面那个玩意,当作是一个无符号的int,按十进制输出
(3) 分析如下程序的输出结果?
int a = -56; printf("%d\n", a); // printf("%u\n", a); //
(4) 分析如下程序的输出结果?
char a = -1u; //unsigned char a = -1; typeof(-1) => signed int typeof(-1u) => unsigned int printf("%d\n", a); // printf("%u\n", a); //
// char a = -1u; -1: unisgned int 11111111 11111111 11111111 11111111 a: char 11111111 %d: int 11111111 11111111 11111111 11111111 //(负数的补码) ==》 11111111 11111111 11111111 11111110 ==》 00000000 00000000 00000000 00000001 (绝对值的原码) %u: unsigned int 11111111 11111111 11111111 11111111 //(正数补码)
=========================================
一个特定整数类型的取值范围的问题
GNU有一个标准的头文件 stdint.h int8_t // 有符号的8bits整数类型 uint8_t //无符号的8bits整数类型 int16_t uint16_t int32_t uint32_t ... 宏,表示有符号8bits整数的最小值和最大值 INT8_MIN INT8_MAX INT16_MIN INT16_MAX .... UINT8_MAX UINT16_MAX ...
溢出
超出数据所能表达的范围,称为溢出,就像汽车里程表,最大值和最小值是相邻的
char c = 250; int d = c + 8; printf("d = %d\n", d); printf("d = %u\n", d);
4. 小数的存储问题
以补码的形式进行存储
小数的二进制 :小数部分*2, 再取整数部分 继续小数部分*2 , 一直到结果为1
float 符号位(1) 指数位(8) 尾数部分(23) double 符号位(1) 指数位(11) 尾数部分(52) 假设8.25是float类型 二进制 : 1000.01 用科学计数法表示:1.00001 * 2^3 符号位 :0 指数位 : 127+3 尾数部分:00001 float
练习: 写出float 类型10.75存储在计算机中的二进制数 写出 二进制 数字 : 0 1000111 01010001000000000000000
5. 整数之间的赋值问题
在C语言中,允许不同类型的整数之间相互赋值。
char -> int int -> char unsigned int -> int ...
有一个问题: 不同类型的整数,存储空间大小都不一样。 char 8bits short 16bits 这个问题怎么解决呢?
C标准建议: (1) 长 -> 短 长的赋值给短的,低字节直接拷贝, 高字节全部discards(丢弃),也没办法要啦。 int -> short int -> char short -> char ... (2) 短 -> 长 短的赋值给长的,低字节直接拷贝的。 高字节补什么呢? 如果短的是无符号的,高位就全部补0 如果短的是有符号的,高位就全部补符号位。
char c = 250; char d; d = c + 8; printf("d = %d\n", d);//2 printf("d = %u\n", d);//2
250:int 00000000 00000000 00000000 1111 1010 c:char 1111 1010 c:==>int 11111111 11111111 11111111 11111010 +8 00000000 00000000 00000000 00001000 ==>100000000 00000000 00000000 00000010 d:char 00000010 %d: int 00000000 00000000 00000000 00000010 %u :unsigned int 00000000 00000000 00000000 00000010
「课堂练习3」
-
分析如下程序的输出结果
(1)
unsigned char c = 250; char d; d = c + 8; printf("d = %d\n", d); printf("d = %u\n", d);
char c = 250; int d; d = c + 8; printf("d = %d\n", d); //2 printf("d = %u\n", d); //2
---------------- ```c unsigned char c = 250; int d; d = c + 8; printf("d = %d\n", d); // printf("d = %u\n", d); //
2.分析如下程序的输出结果?
char c = -3; printf("c = %d\n", c); // printf("c = %u\n", c); //
unsigned char c = -3; printf("c = %d\n", c); // printf("c = %u\n", c); //
6.常量
常量是指在程序运行期间,其值不能改变的数据对象。
在C中定义常量使用宏定义的方式
#define 宏名(常量名) 值
常量在代码中有多种情况:
6.1 整型常量
在代码文本中,代表整数的常量值。
1.八进制常量
0[0-7]* 以字符0开头后面接0个或多个0~7的字符 如: 0123 0777 0134473 088 ERROR
09 ERROR
int a = 0123; printf("%d\n", a);//83 八进制与二进制对应关系 一个八进制位对应三个二进制位 八进制 二进制 0 000 1 001 2 010 3 011 4 100 5 101 6 110 7 111
2.十六进制
0[xX][0-9A-Fa-f]+ 以0x或0X开头后面接一个或多个 0-9A-Fa-f int a = 0xf3; 十六进制与二进制位的对应关系 一个十六进制位对应四个二进制位 十六进制 二进制 0 0000 1 0001 2 0010 3 0011 4 0100 5 0101 6 0110 7 0111 8 1000 9 1001 a/A 1010 b/B 1011 c/C 1100 d/D 1101 e/E 1110 f/F 1111 255 -> 0xff
3.十进制常量
[0-9]+
进制转换
-
十进制转二进制除2取余数,倒序排序,高位补0
-
十进制转八进制
除8取余,倒叙排序
如 十进制12 转换 为 八进制
则12(10)=14(8). 在编程语言中,一般使用0开头来表示八进制,如上例中的十 进制数12就可以写成014。
-
十进制转十六进制
-
二进制八进制十六进制转十进制
6.2.浮点型常量
由整数部分、小数点、小数部分、一个e/E、一个可选的带符号的 整数指数和一个可选的表示类型的后缀(f/F/l/L)
f/F: float l/L: long double 没后缀: double
整数部分: 可以省略 小数部分: 可以省略 但是整数部分和小数部分不可以同时省略。
==>有可能与变量名 发生矛盾
具有二义性
如:
float f = 2.3E3; float f = .3E3; float f = 5E-5; float f = E5;//error 具有二义性 E5既可以作为浮点数常量又可以作为变量名
6.3.字符常量
char ch1 = 'a'; // 'a'是字符常量,代表字母a char ch2 = '\n'; // '\n'是不可见字符常量,代表回车 int d = 'a';// 把'a' 存储到 d所对应的空间 char a = 250;
计算机中存储都是1和0,因此各种字符都必须被映射位某个数字才能存储到计算机中,这种映射关系形成的表称为ASCII码表。
字符本质上就是一个单字节的整型,支持整型所有的运算。比如:
char a = 'a';// 存储在内存中是97 char b = '1';// 存储在内存中是49 char c1 = 20; char c2 = c1 + 'a'; // 等价于char c2 = 20+97; printf("%c\n",c2); // 以字符形式输出117,即 'u' printf("%d\n",c2); // 以整型形式输出117 // 小写字符转大写字符 ' ' = 32 char a7 = 'b'- 32; printf("%c\n",a7);//B
转义字符
转义字符,所有的ASCII码都可以用\加数字(一般是8进制数)来表示,而在C中 定义了一些字母前加n来表示常见的哪些不能显示ASCII字符,如\0,\t,\n称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思 当编译器遇到转义字符的时候,会用特殊的方式进行处理, '\n'表示换行 '\b' : 表示退格符 printf("abcd\b"); printf("bcde\n"); '\a': 表示告警,主板蜂鸣器 '\t': tab键 '\r'
'\ddd' ddd 表示八进制数,\后面可以接1个,2个或3个八进制数,打印出来的是该数字对应的ascii码 printf("%c\n",'\102');// 0102 八进制 printf("%c\n",'\1');// 0001 八进制 printf("%c\n",'\12'); // 0012 printf("%c\n",'\123');// 0123 printf("%c\n",'\109');//error '\xhh'hh 表示十六进制,\后面可以接1个或两个十六进制数字 打印出来的是该数字对应的ascii码 printf("%c\n",'\x41');//A
6.4.字符串
在C语言中没有字符串类型
字符串都是用“ ” 引起来
-
在内存中的存储
-
在内存中实际上是多个连续字符的组合
-
-
任何字符串都以一个'\0'作为结束标记,例如:"funny story"的内存如下
所有以 ""引起来的字符串,系统默认自动添加一个 '\0'
字符串的内部存储细节
"asdfg" ==>字符串所占空间大小 6个字节 "asd\0qwe" ==> 字符串所占空间大小 4个字节 和 4个字节 其中 \0 把该字符串分成两个字符串 "asd\0123qwe" ==> 字符串所占空间大小 9个字节 "asd\x0823qwe" ==> 字符串所占空间大小 10个字节
6.5.布尔类型数据
-
概念:布尔型数据只有真、假两种取值,非零为真,零为假。
-
语法:
bool a = 8; // 逻辑真,此处1可以取其他任何非零数值 bool b = 0; // 逻辑假 c
-
注意:
-
逻辑真除了 1 之外,其他任何非零数值都表示逻辑真,等价于 1。
-
使用布尔型 bool 定义变量时需要包含系统头文件 stdbool.h。
-
-
布尔型数据常用语逻辑判断、循环控制等场合。
6.6.枚举常量
后面讲
7.标准输入
-
概念:键盘是系统的标准输入设备,从键盘中输入数据被称为标准输入
-
相关函数:
scanf(); // 格式化输入函数 格式化:按照特定形式输入 %d :十进制数字输入 %c :字符形式输入 %f :小数输入 %s :字符串输入 int a; float f; scanf("%d", &a); // 从键盘输入一个整型,放入指定的内存地址 &a 中 scanf("%f", &f); // 从键盘输入一个浮点数,放入指定的内存地址 &f 中 // 从键盘依次输入一个整型和一个浮点型数据,用空白符隔开 scanf("%d%f", &a, &f); 注意: 函数调用的时候,给的变量名默认做 右值
-
注意1:
-
scanf() 与 printf() 不同,scanf() 的格式控制串不可乱写,尤其是结尾处的 \n
-
用户必须完全按照 scanf() 中描述的输入格式控制串来输入数据,否则将出错。
-
示例:
-
// 此处输入时必须带逗号 scanf("%d,%d", &a, &b); // 此处必须先输入a=,然后才能输入整数 scanf("a=%d", &a); // 此处结束输入时按下的回车符将被scanf()误以为格式控制符,无法正常结束输入 scanf("%d\n", &a); 注意: scanf中 \n 代表用户结束输入
「练习4」
编程实现如下功能:
-
用户输入大小写字母,则输出字母对应的ASCII码值。
-
用户输入ASCII码值,则输出对应的大小写字母。
#include <stdio.h> int main() { //定义变量 char c; //输入大小写字母 scanf("%c",&c); //输出字母对应的ASCII码值。 printf("%d\n",c); //输入ASCII码值 char c1; scanf("%d",&c1); //对应的大小写字母 printf("%c", c1); }
8.类型转换
-
概念:不一致但相互兼容的数据类型,在同一表达式中将会发生类型转换。
-
转换模式:
-
隐式转换:系统按照隐式规则自动进行的转换
-
强制转换:用户显式自定义进行的转换 显式强转
-
-
隐式规则:从小类型向大类型转换,目的是保证不丢失表达式中数据的精度
隐式转换示例代码
int a = 'a'; char b = 12; float c = 3.14;
-
强制转换:用户强行将某类型的数据转换为另一种类型,此过程可能丢失精度
char a = (char)'a'; int b = 12; float c = 3.14; //float x = a + b - (int)c; // float x = a + b - c; printf("x = %f\n", x);
不管是隐式转换,还是强制转换,变换的都是操作数在运算过程中的类型,是临时的,操作数本身的类型不会改变,也无法改变。
数据类型的本质
-
概念:各种不同的数据类型,本质上是用户与系统对某一块内存数据的解释方式的约定。
-
推论:
-
类型转换,实际上是对先前定义时候的约定,做了一个临时的打破。
-
理论上,可以对任意的数据做任意的类型转换,但转换之后的数据解释不一定有意义。
-
整型数据尺寸
-
概念:整型数据尺寸是指某种整型数据所占用内存空间的大小
-
C语言标准并未规定整型数据的具体大小,只规定了相互之间的 “ 相对大小 ” ,比如:
-
short 不可比 int 长
-
long 不可比 int 短
-
long 型数据长度等于系统字长
-
-
系统字长:CPU 一次处理的数据长度,称为字长。比如32位系统、64位系统。
-
典型尺寸:
-
char 占用1个字节
-
short 占用2个字节
-
int 在16位系统中占用2个字节,在32位和64位系统中一般都占用4个字节
-
long 的尺寸等于系统字长
-
long long 在32位系统中一般占用4个字节,在64位系统中一般占用8个字节
-
-
存在问题:
-
同样的代码,放在不同的系统中,可能会由于数据尺寸发生变化而无法正常运行。
-
因此,系统标准整型数据类型,是不可移植的,这个问题在底层代码中尤为突出。
-
可移植性整型
-
概念:不管放到什么系统,尺寸保持不变的整型数据,称为可移植性整型
-
关键:typedef
类型重命名
用法:
typedef 旧名字 新名字;
eg:
typedef int a;// 进行类型重命名,声明一个新类型,类型名为a, 与 int类型相同
a b;// int b;
typedef int int32_t; // 将类型 int 取个别名,称为 int32_t typedef long int64_t; // 将类型 long 取个别名,称为 int64_t
-
思路:
-
为所有的系统提供一组固定的、能反应数据尺寸的、统一的可移植性整型名称
-
在不同的系统中,为这些可移植性整型提供对应的 typedef 语句
-
-
系统预定义的可移植性整型:
int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t pid_t time_t size_t ... ...
「练习3」
有时候我们需要使用 int32_t 类型变量代替 int 类型变量的原因是什么?
int是系统基本数据类型,其长度尺寸在不同的平台下是不固定的,是有可能发生变化的,为了使得程序中的整型数据的尺寸不发生改变,一般使用类似 int32_t 这样的所谓可移植性数据类型来定义数据,这些类型是某个具体的平台对基本数据类型的封装,然后随系统发布,这些封装语句一般会被放在系统的头文件包中,比如在linux中,一般放在 types.h、stdint-intn.h、stdint-uintn.h等文件中。