课程目标:

数据类型的概念
数据类型的分类

1. 数据类型的概念

数据类型是按被定义变量的性质,表示形式,占据存储空间的多少,构造特点来划分的。
在 C 语言中,数据类型可分为:基本数据类型、构造数据类型、指针类型、空类型四大类型。C 语言中有自带的数据类型,也可以自定义数据类型。
数据类型 - 图1

  • 基本数据类型最主要的特点是其值不可以再分解为其它类型,即基本数据类型是自我说明的。
  • 构造类型是根据已定义的一个或多个数据类型用构造的方法来定义的。即一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。
  • 指针是一种特殊的且重要的数据类型,其值用来表示某个变量在内存中的地址。
  • 空类型说明符为void, 一般用于函数的返回类型,表示函数元返回值。

    2. 整型

    整型数据在内存中的存放形式是以二进制补码表示,最高位是符号位0表示正数,1表示负数。

  • 正数的补码和原码相同:假如int型占32字节,int i = 10;的补码为0000 0000 0000 0000 0000 0000 0000 1010,十六进制表示方式为0xa

  • 负数的补码的最高位是符号位1,除符号位外每一位求反后加1:假如int型占32字节,int j = -10;的补码为1111 1111 1111 1111 1111 1111 1111 0110,十六进制表示方式为0xfffffff6

案例:使用 GDB 查看整型变量在内存中存放形式(二进制补码)

  1. b07@SB:~/c/chapter4$ cat src/complement.c
  2. #include <stdio.h>
  3. int main() {
  4. int i = 10;
  5. int j = -10;
  6. return 0;
  7. }
  8. b07@SB:~/c/chapter4$ gcc -Wall -g src/complement.c -o bin/complement
  9. src/complement.c: In function main’:
  10. src/complement.c:5:6: warning: unused variable j [-Wunused-variable]
  11. int j = -10;
  12. ^
  13. src/complement.c:4:6: warning: unused variable i [-Wunused-variable]
  14. int i = 10;
  15. ^
  16. b07@SB:~/c/chapter4$ gdb ./bin/complement
  17. GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
  18. Copyright (C) 2018 Free Software Foundation, Inc.
  19. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  20. This is free software: you are free to change and redistribute it.
  21. There is NO WARRANTY, to the extent permitted by law. Type "show copying"
  22. and "show warranty" for details.
  23. This GDB was configured as "x86_64-linux-gnu".
  24. Type "show configuration" for configuration details.
  25. For bug reporting instructions, please see:
  26. <http://www.gnu.org/software/gdb/bugs/>.
  27. Find the GDB manual and other documentation resources online at:
  28. <http://www.gnu.org/software/gdb/documentation/>.
  29. For help, type "help".
  30. Type "apropos word" to search for commands related to "word"...
  31. Reading symbols from ./bin/complement...done.

使用GDB提供的disassemble进行反汇编 main 函数

  1. (gdb) disassemble main
  2. Dump of assembler code for function main:
  3. 0x00000000000005fa <+0>: push %rbp
  4. 0x00000000000005fb <+1>: mov %rsp,%rbp
  5. 0x00000000000005fe <+4>: movl $0xa,-0x8(%rbp) # i = 10
  6. 0x0000000000000605 <+11>: movl $0xfffffff6,-0x4(%rbp) # j = -10
  7. 0x000000000000060c <+18>: mov $0x0,%eax
  8. 0x0000000000000611 <+23>: pop %rbp
  9. 0x0000000000000612 <+24>: retq
  10. End of assembler dump.

我们可以使用 display /i $pc 边看 C 语言代码和汇编代码,编译后变量名变为符号地址。

  1. (gdb) display /i $pc
  2. 1: x/i $pc
  3. <error: No registers.>
  4. (gdb) break main
  5. Breakpoint 1 at 0x5fe: file src/complement.c, line 4.
  6. (gdb) r
  7. Starting program: /home/b07/c/chapter4/bin/complement
  8. Breakpoint 1, main () at src/complement.c:4
  9. 4 int i = 10;
  10. 1: x/i $pc
  11. => 0x80005fe <main+4>: movl $0xa,-0x8(%rbp) # 0xa是10的补码,-0x8是变量 i 地址在寄存器偏移量
  12. (gdb) step
  13. 5 int j = -10;
  14. 1: x/i $pc
  15. => 0x8000605 <main+11>: movl $0xfffffff6,-0x4(%rbp) # 0xfffffff6是-10的补码
  16. # 使用 print &i 打印变量内存地址,可见它们在内存上恰好相差 4 字节
  17. (gdb) print &i
  18. $1 = (int *) 0x7ffffffee0e8
  19. (gdb) print &j
  20. $2 = (int *) 0x7ffffffee0ec

2.1 整型分类

整型还可进一步划分:

  1. 根据存储空间大小分为short, int, long:C 语言没有规定不同类型范围,但至少要保证short <= int <= long,具体实现随机器而异,我们可在标准库<limits.h>中查看当前机器上对应不同整型的范围。
  2. 根据现实是否需要负数,可用unsigned修饰整型为无符号,也就是将二进制补码的符号位失效,因此可表示范围就越大。

案例:使用sizeof运算符获得不同整型变量存储空间大小。

  1. b07@SB:~/c/chapter4$ cat src/integer.c
  2. #include <stdio.h>
  3. #include <limits.h>
  4. int main() {
  5. printf("short:%ld, range:[%d, %d]\n", sizeof(short), SHRT_MIN, SHRT_MAX);
  6. printf("unsigned short:%ld, max:%ud\n", sizeof(unsigned short), USHRT_MAX);
  7. printf("int:%ld, range:[%d, %d]\n", sizeof(int), INT_MIN, INT_MAX);
  8. printf("unsigned int:%ld, max:%ud\n", sizeof(unsigned int), UINT_MAX);
  9. printf("long:%ld, range:[%ld, %ld]\n", sizeof(long int), LONG_MIN, LONG_MAX);
  10. printf("unsigned long:%ld, max:%lu\n", sizeof(unsigned long), ULONG_MAX);
  11. return 0;
  12. }
  13. b07@SB:~/c/chapter4$ gcc -Wall src/integer.c -o bin/integer
  14. b07@SB:~/c/chapter4$ ./bin/integer
  15. short:2, range:[-32768, 32767]
  16. unsigned short:2, max:65535d
  17. int:4, range:[-2147483648, 2147483647]
  18. unsigned int:4, max:4294967295d
  19. long:8, range:[-9223372036854775808, 9223372036854775807]
  20. unsigned long:8, max:18446744073709551615

从上面得到编译器为short, int, long分别分配2, 4, 8个字节,因此决定它们范围的不同。假设整型分配字节长度为n,那么有符号类型范围:数据类型 - 图2,无符号类型范围:数据类型 - 图3

整型类型 全称类型说明符 缩写类型说明符 位数 范围
短整型 [signed] short int short 16(2字节) 数据类型 - 图4
无符号短整型 unsigned short int unsigned short 16(2字节) 数据类型 - 图5
整型 [signed] int int 32(4字节) 数据类型 - 图6
无符号整型 unsigned int unsigned int 32(4字节) 数据类型 - 图7
长整型 [signed] long int long 64(8字节) 数据类型 - 图8
无符号长整型 unsigned long int unsigned long 64(8字节) 数据类型 - 图9

如果给某个类型变量赋的值超过该类型范围就会发生溢出。
案例:整型溢出

  1. b07@SB:~/c/chapter4$ cat src/integerOverflow.c
  2. #include <stdio.h>
  3. int main() {
  4. // 编译能通过,但是会发生溢出:下面 int->short 直接截断
  5. signed short m = 32768;
  6. unsigned short n = -1; // 字面量不带后缀默认为 int,发生隐式类型转换
  7. printf("m:%d n:%d\n", m, n);
  8. return 0;
  9. }
  10. b07@SB:~/c/chapter4$ gcc -Wall src/integerOverflow.c -o bin/integerOverflow
  11. b07@SB:~/c/chapter4$ ./bin/integerOverflow
  12. m:-32768 n:65535

使用GDB进行反汇编查看mn的补码:

  1. b07@SB:~/c/chapter4$ gcc -Wall -g src/integerOverflow.c -o bin/integerOverflow
  2. b07@SB:~/c/chapter4$ gdb bin/integerOverflow
  3. GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
  4. Copyright (C) 2018 Free Software Foundation, Inc.
  5. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  6. This is free software: you are free to change and redistribute it.
  7. There is NO WARRANTY, to the extent permitted by law. Type "show copying"
  8. and "show warranty" for details.
  9. This GDB was configured as "x86_64-linux-gnu".
  10. Type "show configuration" for configuration details.
  11. For bug reporting instructions, please see:
  12. <http://www.gnu.org/software/gdb/bugs/>.
  13. Find the GDB manual and other documentation resources online at:
  14. <http://www.gnu.org/software/gdb/documentation/>.
  15. For help, type "help".
  16. Type "apropos word" to search for commands related to "word"...
  17. Reading symbols from bin/integerOverflow...done.
  18. (gdb) disassemble main
  19. Dump of assembler code for function main:
  20. 0x000000000000064a <+0>: push %rbp
  21. 0x000000000000064b <+1>: mov %rsp,%rbp
  22. 0x000000000000064e <+4>: sub $0x10,%rsp
  23. 0x0000000000000652 <+8>: movw $0x8000,-0x4(%rbp)
  24. 0x0000000000000658 <+14>: movw $0xffff,-0x2(%rbp)
  25. 0x000000000000065e <+20>: movzwl -0x2(%rbp),%edx
  26. 0x0000000000000662 <+24>: movswl -0x4(%rbp),%eax
  27. 0x0000000000000666 <+28>: mov %eax,%esi
  28. 0x0000000000000668 <+30>: lea 0x95(%rip),%rdi # 0x704
  29. 0x000000000000066f <+37>: mov $0x0,%eax
  30. 0x0000000000000674 <+42>: callq 0x520 <printf@plt>
  31. 0x0000000000000679 <+47>: mov $0x0,%eax
  32. 0x000000000000067e <+52>: leaveq
  33. 0x000000000000067f <+53>: retq
  34. End of assembler dump.

设置main函数断点并查看汇编代码:

  1. (gdb) display /i $pc
  2. 1: x/i $pc
  3. <error: No registers.>
  4. (gdb) break main
  5. Breakpoint 1 at 0x652: file src/integerOverflow.c, line 5.
  6. (gdb) run
  7. Starting program: /home/b07/c/chapter4/bin/integerOverflow
  8. Breakpoint 1, main () at src/integerOverflow.c:5
  9. 5 signed short m = 32768;
  10. 1: x/i $pc
  11. => 0x8000652 <main+8>: movw $0x8000,-0x4(%rbp)
  12. (gdb) step
  13. 6 unsigned short n = -1; // 字面量不带后缀默认为 int,发生隐式类型转换
  14. 1: x/i $pc
  15. => 0x8000658 <main+14>: movw $0xffff,-0x2(%rbp)

很明显看到m的补码为0x8000,即(1000 0000 0000 0000)2,由于其被signed修饰,因此程序以有符号形式“解释”补码,所以为-32768
n的补码为0xffff,即(1111 1111 1111 1111)2,由于其被unsigned修饰,因此程序以无符号形式“解释”补码,所以为65535

2.2 整型常量

十进制表示:除表示整数 0 外,不以 0 开头,因为以 0 开头的数字串会被解释成八进制数。

  • 负数在前面加负号
  • 后缀lL表示长整型,uU表示无符号数。如:345, 31684, 0, -23456, 459L, 356l, 56789u, 567ul

八进制表示:以数字 0 开头的一个连续数字序列。

  • 负数在前面加负号
  • 序列中只能有 0-7 这八个数字。如: 045, -076, 06745l, 0177777u

十六进制表示:以 0X0x 开头的连续数字和字母序列。

  • 序列中只能有 0-9、A-F 和 a-f 这些数字和字母。
  • 字母a、 b、c、d、e、f 分别对应数字10、11、12、13、14、15,大小写均可。如: 0x10, 0X255, 0xd4ef, 0X6a7bL

    2.3 整形变量

  1. 变量须先定义后使用,常量是可以不经说明而直接使用的。
  2. 在同一语句中可以定义多个类型相同的变量,各变量之间用逗号间隔。最后一个变量之后必须以;号结尾。如: int x, y=0, z=10;``x会被随机初始化(是OS分给程序这个地址前的旧数据没被覆盖)

    3. 字符型

  • C 语言中字符是用单引号括起来
    • 如: ‘a’ 、’1’、’A’等
  • 字符型用于存储字符,对应ASCII编码的二进制形式存储,占用1个字节。
    • 如:字符’a’的ASCII代码为97
  • C语言把字符类型看作一种特别短的整数类型,允许参与算术运算。
    • 有符号字符型表示的数值范围-128-127,类型说明符[signed] char
    • 无符号字符型表示的数值范围0-255,类型说明符unsigned char
    • ‘A’+ 2表示将’A’的ASCII码65加2,结果67是字符’C’的ASCIl码。

ASCII编码:

  • 美国信息互换标准代码(American Standard Code for Information Interchange)的简写,是基于英语的一种编码方式,用于计算机的信息传输。
  • ASCII共定义了256个代码(从0-255),从0-32位为控制字符(ASCII control characters),从33-127位为 可打印字符(ASCII printable characters)。从0-127 是标准的ASCII编码,从128-255是扩展的ASCIl编码。

数据类型 - 图10
其中需要特殊记忆:

  1. 0是空字符'\0',这在之后字符串中非常重要;
  2. 数字字符0-9的ASCII码范围为48-57
  3. 大小写字母:大写字母A-Z范围为65-90;小写字母a-z范围为97-122;大小写字母差值为32(大小写转换使用)
    ```c

    include

int main() { printf(“%c, %c, %c, %c, %c, %c\n”, ‘a’, ‘b’, ‘A’, ‘B’, ‘0’, ‘1’); printf(“%d, %d, %d, %d, %d, %d\n”, ‘a’, ‘b’, ‘A’, ‘B’, ‘0’, ‘1’); printf(“%c, %d, %c, %d, %c, %d\n”, ‘a’ + 3, ‘a’ + 3, ‘A’ + 2, ‘A’ + 2, ‘0’ + 5, ‘0’ + 5); printf(“%d, %d, %c, %c\n”, ‘a’ - ‘A’, ‘9’ - ‘0’, ‘c’ - 32, ‘C’ + 32); return 0; }

  1. 编译运行:
  2. ```bash
  3. b07@SB:~/c/chapter4$ gcc -Wall src/char.c -o bin/char
  4. b07@SB:~/c/chapter4$ ./bin/char
  5. a, b, A, B, 0, 1
  6. 97, 98, 65, 66, 48, 49
  7. d, 100, C, 67, 5, 53
  8. 32, 9, C, c

3.1 字符型分类

长宽字符

3.2 字符型常量

字符常量:用单引号括起来的一个字符。如: ‘a’、 ‘b’、 ‘=’、’+’、’?’都是合法字符常量。
字符常量的特点

  • 字符常量只能用单引号括起来,不能用双引号或其它括号。
  • 字符常量只能是单个字符,不能是字符串。
  • 字符可以是字符集中的任意字符

转义字符是一种特殊的字符常量

  • 以反斜线”\”开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。
  • 转义字符主要用来表示那些用一般字符不便于表示的控制代码 | 转义字符 | 转义字符的意义 | ASCII码 | | —- | —- | —- | | \n | 回车换行 | 10 | | \t | 水平制表(横向跳到下一制表位置) | 9 | | \v | 垂直制表(垂直跳到下一制表位置) | 11 | | \b | 退格 | 8 | | \r | 回车 | 13 | | \f | 换页 | 12 | | \\ | 反斜线符”\“ | 92 | | \‘ | 单引号符 | 39 | | \“ | 双引号符(字符串嵌套) | 34 | | \? | 问号 | 63 | | \a | 鸣铃 | 7 | | \ddd | 1~3位八进制数所代表的字符 | | | \xhh | 1~2位十六进制数所代表的字符 | |

广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。表中的\ddd\xhh正是为此而提出的。dddhh分别为八进制和十六进制的ASCII代码。如: '\1101'表示字母’A’,\102表示字母’B’,'\134'表示反斜线,'\XOA'表示换行等。

  1. #include <stdio.h>
  2. int main() {
  3. /* 转义字符 */
  4. printf("hello\nworld\n");
  5. printf("I'm newbie\t\"double quotation\"\n");
  6. putchar('\''); // 输出'
  7. putchar(10); // 输出换行
  8. return 0;
  9. }

编译运行:

  1. b07@SB:~/c/chapter4$ bin/escapeChar
  2. hello
  3. world
  4. I'm newbie "double quotation"
  5. '

3.3 字符变量

字符变量用来存储字符常量,即单个字符。

  • 字符变量的类型说明符是char。字符变量类型定义的格式和书写规则都与整型变量相同。如: char a, b;
  • 每个字符变量被分配一个字节的内存空间,因此只能存放一一个字符。
  • 字符值是以ASCII码的形式存放在变量的内存单元之中的。如: x的十进制ASCII码是120,y的十进制ASCII码是121。对字符变量a和b赋予’x’和’y’值: a = 'x'; b = 'y'; 实际上是在a, b两个单元内存放120和121的二进制代码
  • 可以把字符型看成是整型。C语言允许对整型变量赋以字符值,也允许对字符变量赋以整型值。在输出时,允许把字符变量按整型输出,也允许把整型按字符型输出。
  • 允许字符型参与数值运算,即用字符的ASCII码参与运算。

字符型和字符串型的区别

  1. 字符串常量是由一对双引号括起的字符序列。如:"CHINA", "C program", "$12.5" 等都是合法的字符串常量。
  2. 字符串常量和字符常量是不同的,主要有以下区别:
    1. 字符常量由单引号括起来,字符串常量由双引号括起来。
    2. 字符常量只能是单个字符,字符串常量则可以含一个或多个字符。
    3. 可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一一个字符变量。在C语言中没有相应的字符串变量,但是可以用一个字符数组来存放一个字符串常量。
    4. 字符常量占一个字节的内存空间。字符串常量占的内存字节数等于字符串中字节数加一。增加的一个字节中存放字符'\0'(ASCII码为0),这是字符串结束的标志。字符串"C program"的内存空间为10个字节。 :::info 字符串在学到数组的时候才讲到,其以字符数组形式存储。 ::: ```c

      include

int main() { / 字符变量 / char c1 = ‘b’; char c2 = ‘\055’; char c3 = ‘\xff’; char c4 = ‘\177’; char c5 = 97; printf(“c1:%d,c2:%d,c3:%d,c4:%d,c5:%c\n”, c1, c2, c3, c4, c5); printf(“char:%ld, c1:%ld, c2:%ld\n”, sizeof(char), sizeof(c1), sizeof(c2));

  1. /* 字符串常量 */
  2. char str[] = "hello world"; // 字符数组
  3. printf("%s\n", str);
  4. printf("size:%ld\n", sizeof str); // 数组所占字节大小
  5. return 0;

}

  1. 编译运行:
  2. ```shell
  3. b07@SB:~/c/chapter4$ gcc -Wall src/charVariable.c -o bin/charVariable
  4. b07@SB:~/c/chapter4$ ./bin/charVariable
  5. c1:98,c2:45,c3:-1,c4:127,c5:a
  6. char:1, c1:1, c2:1
  7. hello world
  8. size:12

4. 浮点型

4.1 浮点型的概念

浮点型也称为实型。在计算机系统的发展过程中,曾经出现过多种方法表示实数,但是到目前为止使用最广泛的是浮点表示法。浮点数利用指数使小数点的位置可以根据需要而上下浮动,从而可以灵活地表达更大范围的实数。

4.2 浮点型的分类

  • 实型常量也称为实数或者浮点数。在C语言中,实数只采用十进制。它有二种形式:十进制小数形式和指数形式(科学计数法)
  • 实型变量分为单精度(float型)、双精度(double型)和长双精度(long double型)三类

    4.3 浮点型常量

  1. 小数形式:由数字和小数点组成,必须有小数点。如: 4.23, 0.15, .56, 78., 0.0
  2. 指数形式:以幂的形式表示,以字母e或E后跟一个以10为底的幂数。字母e或E之前必须要有数字;字母e或E后面的指数必须为整数;字母e或E的前后及数字之间不得有空格。如: 2.3e5, 500e-2, .5E3, 4.5e0

后缀字母表示类型:不写默认是double型,后缀为“f/F” 即表示该数为float型,后缀“I/L”表示long double型。如34.2f, .5F, 12.56L, 2.5E3L

单精度(float型) 双精度(double型) 长双精度(long double型)
占用内存空间 4个字节(32位) 8个字节(64位) ≥8个字节(64位)
精度 6(6位小数) 15(15位小数)
可表示数的范围 1.17549x10-38~3.40282x10^38 2.22507x10^-308~1.79769x10^308
  1. #include <stdio.h>
  2. int main() {
  3. /* 浮点实数 */
  4. printf("float:%ld double:%ld long double:%ld\n",
  5. sizeof(float), sizeof(double), sizeof(long double));
  6. float f1 = 3.145555556F; // float没有这么高的精度
  7. float f2 = 5.5555556; // 隐式类型转换导致精度丢失
  8. float f3 = 5.3e2; // 隐式类型转换导致精度丢失
  9. float f4 = 5E3; // 隐式类型转换导致精度丢失
  10. float f5 = 5.; // 隐式类型转换导致精度丢失
  11. float f6 = .6; // 隐式类型转换导致精度丢失
  12. float f7 = 10;
  13. double d = 10.88888888;
  14. long double ld = 1e4L;
  15. // f6格式化精度过高;f7实际长度>3
  16. printf("f1:%f f2:%f f3:%f f4:%f f5:%f f6:%.8f f7:%3.2f\n",
  17. f1, f2, f3, f4, f5, f6, f7);
  18. printf("double:%Lf, %f\n", d, d); // double用%f,但scanf只能%lf输入
  19. printf("long double ld:%Lf\n", ld); // long double必须用%lf
  20. return 0;
  21. }

编译运行: :::warning b07@SB:~/c/chapter4$ gcc -Wall src/numeric.c -o bin/numeric
src/numeric.c: In function ‘main’:
src/numeric.c:19:19: warning: format ‘%Lf’ expects argument of type ‘long double’, but argument 2 has type ‘double’ [-Wformat=]
printf(“double:%Lf, %f\n”, d, d); // double可用%f或%lf输出,但scanf只能%lf输入
~~^
%f
b07@SB:~/c/chapter4$ ./bin/numeric
float:4 double:8 long double:16
f1:3.145555 f2:5.555556 f3:530.000000 f4:5000.000000 f5:5.000000 f6:0.60000002 f7:10.00
double:-nan, 10.888889
long double ld:10000.000000 ::: 其中f1赋值发生截断(不是四舍五入),并自动输入6位精度;f6指定以%.8f解释float f6变量,因此可见其后两位小数不准确;f7指定输出宽度为3,但是实际整数部分+小数点+小数部分长度为5大于3double d变量不能以%lf形式输出,其结果为-nan

4.4 浮点型的存储方式[了解]

image.png

  1. #include <stdio.h>
  2. int main() {
  3. /* 浮点实数存储方式 */
  4. float i = 5.5f;
  5. float j = -5.5F;
  6. return 0;
  7. }

编译运行: :::warning b07@SB:~/c/chapter4$ gcc -Wall -g src/numericStore.c -o bin/numericStore
src/numericStore.c: In function ‘main’:
src/numericStore.c:6:8: warning: unused variable ‘j’ [-Wunused-variable]
float j = -5.5f;
^
src/numericStore.c:5:8: warning: unused variable ‘i’ [-Wunused-variable]
float i = 5.5F;
^ ::: 使用gdb调试 :::warning b07@SB:~/c/chapter4$ gdb ./bin/numericStore
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later [http://gnu.org/licenses/gpl.html](http://gnu.org/licenses/gpl.html>)
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “x86_64-linux-gnu”.
Type “show configuration” for configuration details.
For bug reporting instructions, please see:
[http://www.gnu.org/software/gdb/bugs/.](http://www.gnu.org/software/gdb/bugs/>.)
Find the GDB manual and other documentation resources online at:
[http://www.gnu.org/software/gdb/documentation/.](http://www.gnu.org/software/gdb/documentation/>.)
For help, type “help”.
Type “apropos word” to search for commands related to “word”…
Reading symbols from ./bin/numericStore…done.
(gdb) display /i $pc
1: x/i $pc

(gdb) break main
Breakpoint 1 at 0x79e: file src/numericStore.c, line 5.
(gdb) run
Starting program: /home/b07/c/chapter4/bin/numericStore
[Thread debugging using libthread_db enabled]
Using host libthread_db library “/lib/x86_64-linux-gnu/libthread_db.so.1”.

Breakpoint 1, main () at src/numericStore.c:5
5 float i = 5.5F;
1: x/i $pc
=> 0x800079e : movss 0xae(%rip),%xmm0 # 0x8000854
(gdb) step
6 float j = -5.5f;
1: x/i $pc
=> 0x80007ab : movss 0xa5(%rip),%xmm0 # 0x8000858 ::: 结果好像和老师的不太一样。
image.png

5. 只读变量、常量的定义和区别

只读变量:只能读的普通变量,可以称其为“不能改变的变量”。但可以通过指针修改。
定义: :::info 类型 const 只读变量名(标识符) = 值;
⚠如果类型不是指针类型,那么const和类型位置可互换;指针类型则有不同含义 ::: 由于const只是修饰该标识符,也就是不能通过该标识符修改其对应内存中的值,但是在C语言中可通过指针直接操作内存地址来修改只读变量的值,但编译时会抛出警告。在C++中是不允许来修改只读变量的值,编译无法通过。
作用

  • 给读你代码的人传达非常有用的信息。如:声明一个参数为const是为了告诉用户这个参数的应用目的。
  • 通过给编译器一些附加的信息,使用const也许能产生更紧凑的代码。
  • 合理地使用const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现 ```c

    include

int main() { / 只读变量 / int const a = 1; const int b = 2; // 常用写法 // a = 2; // 不能修改只读变量 printf(“a:%d, b:%d\n”, a, b); // 使用指针修改 printf(“Please input a: “); scanf(“%d”, &a); // (&b) = 99; error: assignment of read-only variable ‘b’ int p = &b; *p = 99; // 直接用指针变量存储该只读变量的地址值,然后通过地址修改 printf(“a:%d, b:%d\n”, a, b); return 0; }

  1. 编译运行:
  2. :::warning
  3. b07@SB:~/c/chapter4$ gcc -Wall src/const.c -o bin/const<br />src/const.c: In function main’:<br />src/const.c:11:2: warning: writing into constant object (argument 2) [-Wformat=]<br /> scanf("%d", &a);<br /> ^~~~~<br />src/const.c:13:11: warning: initialization discards const qualifier from pointer target type [-Wdiscarded-qualifiers]<br /> int *p = &b;<br /> ^<br />b07@SB:~/c/chapter4$ ./bin/const<br />a:1, b:2<br />Please input a: 5<br />a:5, b:99
  4. :::
  5. 只读变量和常量的区别:
  6. 1. const修饰的不是常量而是只读**变量**,只能读取不能被修改。
  7. 1. 只读变量是个**变量**,定义时需要给它分配内存空间或者说是缓冲。而常量不是变量,是一个静态的值,不需要为它分配内存空间。
  8. 1. 只读变量不能用来作为定义数组的维数,也不能放在case关键字后面,而常量可以。
  9. <a name="VhghP"></a>
  10. # 6. 枚举类型:一次性定义多个相关符号常量
  11. 枚举类型的声明:
  12. :::info
  13. enum 枚举类型名{枚举值1,...枚举值n};
  14. :::
  15. 在枚举值表中应列出所有可用值,这些值也称为枚举元素
  16. 枚举变量的定义:
  17. 1. **enum 枚举类型名** 枚举变量列表;
  18. 1. 同前面定义一样,只不过此时 **enum 枚举类型名** 就是一种类型
  19. 2. **enum [枚举类型名]{枚举值1,..,枚举值n}** 枚举变量列表;
  20. 1. 声明同时并定义变量,其中枚举类型名可省略,缺点是之后无法使用该枚举类型**定义变量**。
  21. 枚举类型的注意点
  22. 1. 枚举类型也属于整型
  23. 1. 枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为01, 2...
  24. 1. 枚举值是**符号常量不是变量**,不能在程序中用赋值语句再对它赋值。
  25. 1. 枚举变量可以通过枚举值对其进行赋值
  26. 1. 在声明枚举类型可同时对枚举值赋值
  27. 1. `enum Weekday{SUN=0,MON=1,TUE=2,WED=3,THU=4,FRI=5,SAT=6};`
  28. 1. `enum Weekday{SUN=0,MON=1,TUE,WED=6,THU,FRI,SAT};`当前枚举成员的值是前一个成员的值`+1`;第一个成员若无初始化就是`0`
  29. ```c
  30. #include <stdio.h>
  31. int main() {
  32. /* 枚举变量定义方式1:先声明再定义 */
  33. enum Car{BENZ, BMW, FORD, AUDI, DAZONG}; // 声明枚举类型:默认0开始
  34. enum Car myCar = DAZONG, yourCar; // 定义枚举变量,yourCar未进行初始化
  35. printf("Car myCar: %d FORD: %d, youCar: %d\n",
  36. myCar, FORD, yourCar);
  37. /* 枚举变量定义方式2:声明同时定义 */
  38. enum Weekday { // 不省略枚举名
  39. SUN=0,
  40. MON=1,
  41. TUE, // 2
  42. WED=6,
  43. THU, // 7
  44. FRI, // 8
  45. SAT // 9
  46. } today = FRI;
  47. printf("enum Weekday today: %d\n", today);
  48. enum { // 省略枚举名
  49. A=97,
  50. B, // 98
  51. C, // 99
  52. D // 100
  53. } choose1 = C, choose2 = 101; // 超过枚举值范围
  54. printf("choose1: %d, choose2: %d\n", choose1, choose2);
  55. return 0;
  56. }

编译运行: :::warning b07@SB:~/c/chapter4$ gcc -Wall src/enumerate.c -o bin/enumerate
src/enumerate.c: In function ‘main’:
src/enumerate.c:7:2: warning: ‘yourCar’ is used uninitialized in this function [-Wuninitialized]
printf(“Car myCar: %d FORD: %d, youCar: %d\n”,
^~~~~~~~~~
myCar, FORD, yourCar);
~~~~~
b07@SB:~/c/chapter4$ ./bin/numeric
float:4 double:8 long double:16
f1:3.145555 f2:5.555556 f3:530.000000 f4:5000.000000 f5:5.000000 f6:0.60000002 f7:10.00
double:-nan, 10.888889
long double ld:10000.000000
b07@SB:~/c/chapter4$ ./bin/enumerate
Car myCar: 4 FORD: 2, youCar: -1074963680
enum Weekday today: 8
choose1: 99, choose2: 101 ::: 可以看到youCar没有进行初始化,其值是随机的(不一定在枚举成员内);同时可对枚举类型变量附上枚举值之外的数字。

枚举类型的优点:

  1. 枚举值是常量,由编译程序自动生成,程序员不需要用手工对常量一一赋值。
  2. 枚举常量使程序更清晰易读,因为在枚举类型声明的同时也为枚举值定义了一个名字,因此枚举常量也是名字常量或符号常量。
  3. 在调试程序时通常可以检查枚举常量,这一点是非常有用的,尤其在不得不手工检查头文件中的常量值时。

缺点:枚举值只能是整数,符号常量不可修改,可拓展性不强。
应用:分类问题

  1. 扑克牌花色
  2. 简易哈希映射