0.问题的引入
计算机(computer)只是我们人类用来解决某些问题的一种工具而已。 "某些问题"是指哪些问题呢? 就是那些可以通过 计算来解决的问题。 计算机是通过“计算”来解决这些问题的。
计算:计算机通过把问题域中的数据抽象出来,保存,然后对这些数据进行一系列的有目的的计算从而得出结果。 如: 3 + 5 => 8
计算机首先要保存数据,在保存数据前,我们首先要知道这些数据的大小、取值范围等数据属性。 不然我怎么知道要开辟多大的空间,去存储它们呢?
数据的大小、取值范围、类型等等属性: 数据类型
数据类型:
描述数据对象的类型的。 “给数据分类” 不同类型的数据,属性不一样。
1. C语言中的数据类型
(1) 基本类型
C语言内置的,已经为我们定义好的类型,程序员可以直接用,称之为“基本类型” "基本类型"主要是用来描述“数”:整数、浮点数
-
整数 (signed) char/unsigned char: 8bits整数类型(或字符类型) (signed) short/unsigned short: 一般来说, short占16bits (signed) int /unsigned int: (signed) long/unsigned long: 区别在哪里呢? sizeof(char) == sizeof(unsigned char) = 1 byte sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long)
-
浮点数
用来保存有小数的数字 float: 4bytes(一般来说) double: 8bytes(一般来说) <<<<<< long double: 10 bytes(一般来说) 区别: 用来保存浮点数位数不一样,精度不一样!!! sizeof(long double) >= sizeof(double) >= sizeof(float) 在代码中,浮点数默认的类型是 double
(2) 构造类型
C语言中,允许程序员自定义类型, 自定义 复合/组合类型,称之为构造类型。 数组
int a[10]; //a是一个含有10个int类型元素的数组。 //上面那个定义中, int不是数组的类型,而是数组元素的类型!!! typeof(a[0]): int typeof(a): int[10] float b[200]; //b是一个含有200个float类型元素的数组。 typeof(b): float[200]
结构体 联合体 枚举
(3) 指针类型
先 wait wait, 后面有专题讲
(4) void
空类型,在C语言中,有且仅有 三个地方可以用 void
-
void 可以做函数的返回值类型, 表示该函数无返回值
//函数func没有返回值 void func(int a) { .... } // b = func(3) ; ERROR //函数sum是有返回值的,返回值是一个int int sum(int a, int b) { //... } int b = sum(3,5); //OK的
-
void 可以做函数的形式参数,表示该函数无参数
//函数abc不要参数 int abc(void) { //... } abc(5); ERROR abc();Ok的
-
void*
“通用指针”
有了数据类型后,我们在程序中,就可以定义数据对象了。 数据对象:
-
变量:在程序运行期间,其值可以被改变的数据对象,称之为变量。
-
常量:在程序运行期间,其值不能被改变的数据对象,称之为常量。
2. 变量
"先定义,后使用"
2.1 变量的定义
定义变量的格式,如下:
数据类型 变量名 {= 初始值} ; {}内表示可选的
数据类型:
代表该变量的类型,系统会根据这个类型来分配合理的空间去存储这个变量。 数据类型是任意合法的类型都可以(基本类型 或 构造类型 或 指针类型)
变量名:
就是你给变量取的一个名字。但是在C语言中,你给任何东西取名,都必须符合“C语言标识符”的规定.
标识符(“取的名字”): 必须要 字母、下划线、数字组成,并且第一个字符必须是字母或下划线。
如:
sb 可以 sb250 可以 ssb 也可以 250 不可以 _250 可以 fdfdf9090= 不可以 int 不可以。 不能和C语言已经有的关键字相同 "见名知其意" sum ; //用来保存数据的和
2.2 变量的属性
int a = 5;
在程序运行时,系统会为变量a分配一个四个字节(假设int占4bytes)的存储空间,并且把数值5存入到这个空间中去。 "存储单元":系统会为每一个存储单元(以字节为单位)分配一个唯一的编号,用于区分这些存储单元,这个存储单元的编号 称之为 存储单元的地址。
变量的属性:
变量名 变量的地址: 变量存储单元的地址(首地址) 变量的值: 变量存储单元中的内容
练习:
-
分析如下代码的输出结果
int main() { int a; printf("a = %d\n", a); //输出变量a的值 return 0; }
以下描述正确的是:__B_ A. 变量a没有值 B. 变量a一定有一个值,只是这个值,你不知道而已
2.3 变量的左值和右值
对一个变量的操作:
-
read
读一个变量的值:是从变量的存储单元中,取内容
-
write
把一个值 写入到变量的存储单元中去 C语言中,提供一个 赋值= ,把右边的值 赋值 给左边的变量
把右边的值 写入到 左边的变量的存储单元中去
如:
int a = 250; int b; a = 1024; //把数值 1024 写入到变量a的地址中去 //a: "变量a的地址" b = a; //把变量a的值,赋值给 b //a: "变量a的值"
上面两条语句,同样是a, 含义是不一样的!!! 由此得出结论: 在C语言中,任何变量都有且仅有两层含义: (1)右值
rvalue: readable value 可读的值 rvalue: 变量的值
(2)左值
lvalue: location value 可寻址的地址,可写的地址 lvalue: 变量的地址
请问:
-
变量何时代表变量的值(右值,rvalue), 何时代表变量的地址(左值,lvalue)呢?
"在赋值号= 的左边",代表地址(lvalue,左值) “其他情况,赋值号= 的右边”,代表的是变量的值(rvalue, 右值)
例子:
int a; int b; a = 1024;//a的左值: 变量a的地址 a + 3; //a右值: 变量a的值 // => {int tmp = a + 3;} a = a * 3; //左边的a: a的左值,变量a的地址 //右边的a: a的右值, 变量a的值 //...
3. 整型变量
// char/short/int/long int a = 5;
在程序运行时,系统会为a分配空间,并存储“数值5” 整数值 在计算机中是如何存放的呢?
3.1 整数在存储器(计算机)中的存放形式
整数在计算机中是以 “二进制补码” 形式存放的
-
正整数的补码
正数的补码就是其原码本身
//假设以 8bits 来存放整数 13: 8bits 把十进制的13转换成二进制即可 13 = 8 + 4 + 1 00001101 13的原码/13的补码 255: 8bits 255 = 256 - 1 11111111 255的原码/255的补码
-
负整数的补码
负数的补码是 其绝对值的原码取反+1得到
//以8bits来存放整数 -13的补码 13的原码 00001101 13的原码 11110010 取反 11110011 +1 11110011 -> -13在计算机中的表示形式(8bits)
练习:
-
假设计算机中用 32bits 来存放整数,请写出 123和 -123的存放形式。
123: 64 + 32 + 16 + 8 + 2 + 1 00000000 00000000 00000000 01111011 <- 123在计算机中的存放形式(32bits) -123 123: 00000000 00000000 00000000 01111011 取反: 11111111 11111111 11111111 10000100 +1: 11111111 11111111 11111111 10000101 <- -123在计算机中的存放形式(32bits)
-
假设计算机中用8bits来存放整数 请写出 -1 和 255 的存放形式 请写出 -2 和 254 的存放形式 请写出 -3 和 253 的存放形式 请写出 -4 和 252 的存放形式 .... 直到你找到规律为止。
-
结论1
一个负整数在计算机中的存放形式,会和另外一个正整数的存放形式相同。 -x 与 2^n - x (2的n次幂 - x) n为存放整数的bit位数
练习:
-
假设一个整数在计算机中的存放形式(32bits)为: 11111111 11111111 11111111 11111111 请问这个整数是多少? -1 或 2^32 -1
-
结论2
> 在CPU底层是没有符号位的概念的,都是数值位,都参与运算,至于这个数到底是正数还是负数, > 就得看你编译器的词义啦,意思是说,你把这个数当作是一个有符号(signed),还是无符号(unsigned)来解释。 编辑器(逻辑层面) 有符号的(signed) 符号位(最高位) + 数值位(其他位) 1 负数 0 正数 or 0 无符号的(unsigned) 所有的bit位,都是数值位
练习:假设 int 占 32bits
-
分析如下程序的输出结果
int main() { int a = -3; printf("%d\n", a); // -3 printf("%u\n", a); // 2^32 -3 return 0; // %d: 把后面那个玩意当作是一个 有符号的数,按十进制输出 // %u: 把后面那个玩意当作是一个 无符号的数,按十进制输出 }
-
分析如下程序的输出结果
int main() { int a = 4; printf("%d\n", a); //4 printf("%u\n", a); //4 return 0; }
3.分析如下程序的输出结果
int main() { unsigned int a = -4u; printf("%d\n", a); //-4 printf("%u\n", a); // 2^32 -4 return 0; } // -4/-4u // 4 00000000 00000000 00000000 00000100 // 11111111 11111111 11111111 11111011 // 11111111 11111111 11111111 11111100 -4/-4u 在计算机中的存放形式(32bits) // typeof(-4): int // typeof(-4u): unsigned int
-
分析如下程序的输出结果
int main() { unsigned char c = 250; char d; d = c + 9; printf("%d\n", d); printf("%u\n", d); }
3.2 不同整型之间的赋值问题
C语言允许不同类型的整型之间相互赋值
char a; //8bits short b; //16bits int c; //32bits long d; //32 or 64bits a = b; d = c; c = a; //.... //允许不同整数类型之间相互赋值
有人提出:
长度都不一样,如何来赋值呢?
C语言标准委员会建议采用如下策略:
-
(1) 长的 -> 短的
低字节直接拷贝,高字节直接 discards(丢弃、抛弃)
-
(2) 短的 -> 长的
低字节直接拷贝, 高位补什么呢?
如果短的是无符号数,高位就全部补0 如果短的是有符号数,高位 就全部补 符号位!!!
练习:
-
分析如下程序的输出结果,假设机器(int)是32bits
int main() { // char c; // signed char c; char c = 250; printf("%d\n", c); //-6 printf("%u\n", c); //2^32 - 6 typeof(250): int 250: 00000000 00000000 00000000 11111010 c = 250 int -> char 长->短 c: 11111010 %d: int %d: c -> int 短->长 11111111 11111111 11111111 11111010 -1 11111111 11111111 11111111 11111001 取反 00000000 00000000 00000000 00000110 c: 11111010 %u: unsigned int c -> unsigned int 短->长 11111111 11111111 11111111 11111010 return 0; }
-
分析如下程序的输出结果,假设机器(int)是32bits
1. 分析如下程序的输出结果,假设机器(int)是32bits ```c++ int main() { unsigned char c = 250; printf("%d\n", c); //250 printf("%u\n", c); //250 typeof(250): int 250: 00000000 00000000 00000000 11111010 c = 250 int -> char 长->短 c: 11111010 %d: c -> int 短->长 00000000 00000000 000000000 11111011 %u: c-> unsigned int 短->长 00000000 00000000 00000000 11111011 return 0; }
思考:
C程序中,有些时候需要定义一个 8bits的整数, char/unsigned char 有些时候需要定义一个 16bits or 32bits or 64bits,这个时候怎么办呢? 16bits: short/unsigned short ??? sizeof(char) == sizeof(unsigned char) == 1 byte short 不一定就是 16bits的,int也不一定是 32bits, ... 因为这些类型(short,int,long)的长度是跟机器和编译器相关(Machine Dependent)
那这个问题怎么解决呢?
3.3 GNU下标准的整数类型
有哪些系统或环境是符合GNU:
Linux Unix GNU有一个标准的头文件: stdint.h, 这个头文件 定义GNU下标准的整型。 在头文件 stdint.h 中定义如下整数类型
int8_t //有符号的8bits的整数类型 char uint8_t //无符号的8bits的整数类型 unsigned char int16_t //有符号的16bits的整数类型 uint16_t //无符号的16bits的整数类型 int32_t uint32_t int64_t uint64_t // ...
这些整数类型的最值:
MIN MAX uint8_t 0 255 int8_t -128 127 uint16_t 0 65535 int16_t -32768 32767 uint32_t ... int32_t ...
在头文件 stdint.h 中,已经为我们定义了这些“最值”的宏,你直接用就可以啦
INT8_MAX //8bits有符号整数的最大值 INT8_MIN //8bits有符号整数的最小值 INT16_MAX //16bits有符号整数的最大值 INT16_MIN //16bits有符号整数的最小值 INT32_MAX //32bits有符号整数的最大值 INT32_MIN //32bits有符号整数的最小值 ... UINT8_MAX UINT16_MAX UINT32_MAX UINT64_MAX ...
4.常量
常量: 在程序运行期间,其值不能被改变的数据对象
4.1 整型常量
在代码文本中,代表整数的常量值
-
八进制整型常量 0 1 2 3 4 5 6 7
0[0-7]*: 以数字0开头,后面接0个或多个01234567 如: 0123 //八进制的123 07777 //八进制的 7777 012345677777 088 //ERROR
八进制与二进制的对应关系
一位八进制数字对应三位二进制数字
八进制 二进制 0 000 1 001 2 010 3 011 4 100 5 101 6 110 7 111
-
十六进制整型常量 0 1 2 3 4 5 6 7 8 9 a(A) b(B) c(C) d(D) e(E) f(F)
0xX+ : 以0x或0X开头,后面接至少一个16进制字符 如: 0xff 0x ERROR 0x0 0x123 0x2356G //ERROR
十六进制与二进制对应关系
一位十六进制对应四位二进制
十六进制 二进制 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
-
十进制整型常量 [0-9]+
4.2 字符型常量
字符常量是用 单引用 '' 引起来的一个或多个字符的序列。表示一个字符!! 用来表示字符的。
如:
'A' 字符A '\n' 换行符 '\x12' 也代表的是一个字符,哪个字符呢? wait wait 'fdlkjfhd' 错误,没有这个字符!!! ...
问题1:
我们要在计算机中保存 字符a b c d ..., 计算机保存字符是保存字符的形状吗? 字符保存的是 字符的ASCII码.
ASCII码: American Standard Code for Information Interchange 美国标准信息交换码
给每一个英文字符一个唯一的整数值,这个整数值就是字符的ASCII码 美国人用的所有字符加起来不超过256个,编码 0-255,这个整数值就只需要 8bits整数类型就可以保存啦。 char/unsigned char
Oct Dec Hex Char Oct Dec Hex Char ──────────────────────────────────────────────────────────────────────── 000 0 00 NUL '\0' (null character) 100 64 40 @ 001 1 01 SOH (start of heading) 101 65 41 A 002 2 02 STX (start of text) 102 66 42 B 003 3 03 ETX (end of text) 103 67 43 C 004 4 04 EOT (end of transmission) 104 68 44 D 005 5 05 ENQ (enquiry) 105 69 45 E 006 6 06 ACK (acknowledge) 106 70 46 F 007 7 07 BEL '\a' (bell) 107 71 47 G 010 8 08 BS '\b' (backspace) 110 72 48 H 011 9 09 HT '\t' (horizontal tab) 111 73 49 I 012 10 0A LF '\n' (new line) 112 74 4A J 013 11 0B VT '\v' (vertical tab) 113 75 4B K 014 12 0C FF '\f' (form feed) 114 76 4C L 015 13 0D CR '\r' (carriage ret) 115 77 4D M 016 14 0E SO (shift out) 116 78 4E N 017 15 0F SI (shift in) 117 79 4F O 020 16 10 DLE (data link escape) 120 80 50 P 021 17 11 DC1 (device control 1) 121 81 51 Q 022 18 12 DC2 (device control 2) 122 82 52 R 023 19 13 DC3 (device control 3) 123 83 53 S 024 20 14 DC4 (device control 4) 124 84 54 T 025 21 15 NAK (negative ack.) 125 85 55 U 026 22 16 SYN (synchronous idle) 126 86 56 V 027 23 17 ETB (end of trans. blk) 127 87 57 W 030 24 18 CAN (cancel) 130 88 58 X 031 25 19 EM (end of medium) 131 89 59 Y 032 26 1A SUB (substitute) 132 90 5A Z 033 27 1B ESC (escape) 133 91 5B [ 034 28 1C FS (file separator) 134 92 5C \ '\\' 035 29 1D GS (group separator) 135 93 5D ] 036 30 1E RS (record separator) 136 94 5E ^ 037 31 1F US (unit separator) 137 95 5F _ 040 32 20 SPACE 140 96 60 ` 041 33 21 ! 141 97 61 a 042 34 22 " 142 98 62 b 043 35 23 # 143 99 63 c 044 36 24 $ 144 100 64 d 045 37 25 % 145 101 65 e 046 38 26 & 146 102 66 f 047 39 27 ' 147 103 67 g 050 40 28 ( 150 104 68 h 051 41 29 ) 151 105 69 i 052 42 2A * 152 106 6A j 053 43 2B + 153 107 6B k 054 44 2C , 154 108 6C l 055 45 2D - 155 109 6D m 056 46 2E . 156 110 6E n 057 47 2F / 157 111 6F o 060 48 30 0 160 112 70 p 061 49 31 1 161 113 71 q 062 50 32 2 162 114 72 r 063 51 33 3 163 115 73 s 064 52 34 4 164 116 74 t 065 53 35 5 165 117 75 u 066 54 36 6 166 118 76 v 067 55 37 7 167 119 77 w 070 56 38 8 170 120 78 x 071 57 39 9 171 121 79 y 072 58 3A : 172 122 7A z 073 59 3B ; 173 123 7B { 074 60 3C < 174 124 7C | 075 61 3D = 175 125 7D } ...
'A' ~ 'Z' ASCII码是连续的 'A' 65 'B' 66 'a' ~ 'z' ASCII码是连续的 'a' 97 'b' 98
'0' ~ '9' ASCII码是连续的 '0' 48
把字符分为两类:
-
普通字符: 可以打印的字符,有形状的字符
如: 'a' - 'z' 'A' - 'Z' '0' - '9' ... 普通字符的赋值 char c = '一个普通字符' or char c = 普通字符的ASCII码(十进制/十六进制/八进制) char c = 'A'; char c = 65; char c = 0x41; char c = 0101; 上面四行代码,是没有任何区别的 char a = 0; char a = '0'; 上面这两行代码,有“天壤之别”。你们知道为什么吗?
-
特殊字符(转义字符):不可以打印的字符,没有形状
如:
'\n' : 换行符 '\r' : 回车符 '\t' : 制表符 ... 还有一些,键盘都不对应的字符,怎么弄? '\ooo': 由\后跟1个、2个、3个八进制数字组成,这些八进制数字用来代表所期望的那个字符的ASCII码 如: 'A' 的ASCII码的八进制为 101 'A' 和 '\101' 是一个意思!!!! '\xhh': 由\x后面跟1个、2个十六进制数字组成,这些十六进制数字用来代表所期望的那个字符的ASCII码 如: 'A' 的ASCII码的十六进制为 0x41 'A' 和 '\x41' 是一个意思
char a = 'A'; char a = '\101'; char a = '\x41'; char a = 65; char a = 0101; char a = 0x41; //上面这几行代码是一个意思!!!(从CPU底层存储上来讲)
注意:
-
null字符: ASCII码为0的那个字符, 一般用来表示字符串的结束。 在C代码中,如何描述 null字符呢?
char c = '\0'; //'\0'代表一个字符,该字符的ASCII码的八进制为 0 char c = 0; // char c = '0'; <=> char c = 48; 要区分 '\0' 和 '0' 是两个完全不同的字符!!!!
-
字符常量 和 字符串常量 不一样的
字符常量表示的是一个字符,是用 ''(单引用) 引起来的 字符串常量表示的是一串字符,是用 " "(双引用) 引起来的
'0' '\101' -> 单个字符, 字符常量 "0" "abc" "" => 字符串 char c = "0"; //ERROR
4.3 浮点型常量
浮点型常量是由 整数部分、小数点、小数部分、一个e/E、一个可选的带符号的整型指数 和 一个可选的表示类型的后缀(f/F/l/L) 整数部分:可以省略 小数部分:也可以省略 但是整数部分和小数部分不能同时省略。 e/E: 可以省略,在没有指数的情况 后缀 f/F: float 后缀 l/L: long double 没有后续: double 默认类型为 double
typeof(1.0): double typeof(1.0f): float typeof(1.0l): long double typeof(1): int
来看一个例子:
int main() { // double f = 0.345; //ok // float f = .3e3; //正确(整数部分省略 0.3e3) // float f = 5e3; //正确的(小数部分省略 5.0e3) // float f = e3; //不正确。 整数部分和小数部分不能同时省略,这个时候,编译器会把 // // //e3当作是一个变量名 // float f = 3; //正确的 int a = 3.5; //C语言中,也可以的 //编译器会隐含把 int a = (int)3.5; // double f = 15.10000; // %f: double .6 // %lf: double // %g : 输出浮点数,但是不输出末尾多余的0 printf("%lf\n", f); printf("%g\n", f); return 0; }
4.4 枚举常量和宏常量
后面再讲
练习:
-
分析如下程序的输出结果,假设机器32bits
char ch = 253; ch = ch + 7; ch: 11111101 11111111 11111111 11111111 11111101 00000000 00000000 00000000 00000111 1 00000000 00000000 00000000 00000100 ch: 00000100 printf("%d\n",ch); //4 printf("%u\n",ch); //4 printf("%c\n",ch); //%c把字符形状打印出来 printf("%x\n",ch); //4 //%x按十六进制输出
unsigned char ch = 253; ch = ch + 7; printf("%d\n",ch); printf("%u\n",ch); printf("%c\n",ch); //%c把字符形状打印出来 printf("%x\n", ch); //%x按十六进制输出
作业:
-
分析如下程序的输出结果,假设 int 占32bits
#include <stdio.h> int main() { char c = 253; char d; d = c + 192; 253: 00000000 00000000 00000000 11111101 c : 11111101 (int)c: 11111111 11111111 11111111 11111101 192: 00000000 00000000 00000000 11000000 1 00000000 00000000 00000000 10111101 d: 10111101 %d: d -> int 短 -> 长 11111111 11111111 11111111 10111101 -1 11111111 11111111 11111111 10111100 取反 00000000 00000000 00000000 01000011 %u: d -> unsigned int 短 -> 长 11111111 11111111 11111111 10111101 printf("%d\n", d); // -67 printf("%u\n", d); // 2^32 - 67 return 0; }
-
分析如下程序的输出结果,假设 int 占32bits
#include <stdio.h> int main() { printf("%d\n", -1); //-1 printf("%u\n", -1); //2^32 - 1 // -1: 11111111 11111111 11111111 1111111 // %d: int 有符号数 // %u: unsigned int 无符号数 printf("%d\n", 255); //255 printf("%u\n", 255); //255 255: 00000000 00000000 00000000 11111111 %d: %u: printf("%d\n",(char) -1); //-1 printf("%u\n", (char)-1); //2^32 - 1 -1: 11111111 11111111 11111111 1111111 (char)-1: int -> char 长->短 (char)-1: 11111111 %d: (char)-1 -> int 短->长 11111111 11111111 11111111 1111111 %u: (char)-1 -> unsigned int 短->长 11111111 11111111 11111111 1111111 printf("%d\n", (unsigned char) -1); //255 printf("%u\n", (unsigned char)-1); //255 -1: 11111111 11111111 11111111 1111111 (unsigned char)-1: int -> unsigned char 长->短 (unsigned char)-1: 11111111 %d: (unsigned char)-1 -> int 短->长 00000000 00000000 00000000 11111111 %u: (unsigned char)-1 -> unsigned int 短->长 00000000 00000000 00000000 11111111 printf("%d\n",(char) 255); //-1 printf("%u\n", (char)255); //2^32 - 1 255: 00000000 00000000 00000000 11111111 (char )255: int -> char 11111111 %d: (char)255 -> int 短->长 11111111 11111111 11111111 11111111 %u: (char)255 -> int 短->长 11111111 11111111 11111111 11111111 printf("%d\n",(unsigned char) 255); //255 printf("%u\n", (unsigned char)255); //255 255: 00000000 00000000 00000000 11111111 (unsigned char)255: 11111111 %d: (unsigned char)255 -> int 短->长 00000000 00000000 00000000 11111111 %u: (unsigned char)255 -> unsigned int 短->长 00000000 00000000 00000000 11111111 return 0; }