7.1 整数类型

两种类型:

  • 有符号数: 使用补码表示, 需要最高位作为符号位.
  • 无符号数: 使用原码表示, 只能表示正整数.
特性说明代码
类型实际长度是不确定的由机器和编译器决定, 但是通常:
1. int: 32位
2. short: 16位
3. long: 64位

可以通过<limits.h>头查看长度定义的宏
整型int unsigned int
短整型short short int unsigned short int
长整型long long int unsigned long int
超长型 #C99long long unsigned long long

长度: 至少64位
整数常量数制 (立即数)三个类型:
1. 十进制: 一定不能以0开头
2. 八进制: 以0开头
3. 十六进制: 以0x开头 (字母可以随意大小写)
整数常量存储后缀:
1. 21L表示作为长整数存储
2. 15U表示作为无符号常量存储
3. 455UL表示作为无符号长整数存储
4. 345LL表示超长整数 #C99

整数常量自动类型匹配(编译器):
5. int (如果是8或者16进制多匹配unsigned int)
6. long int
7. unsigned long int
整数溢出两种情况:
1. 操作数有符号: 未定义行为
2. 操作数无符号: 对$2^n$取模, $n$为类型位长
unsigned short情况下: 65535 + 1 = 0

7.1.5 读写整数

读写对应的整数需要补充转换说明符

类型说明符
无符号整数1. %u表示十进制无符号整型
2. %o表示八进制无符号整型
3. %x表示十六进制无符号整型
短整型修饰符h: %hd %hu %ho %hx
长整型修饰符l: %ld %lu %lo %lx

(程序) 数列求和

将数列元素换成长度更长的类型, 防止溢出, 但是需要修改相应的scanf转换说明符

sum2.c

#include <stdio.h>

int main(void) {
    long n, sum = 0;

    printf("This program sums a series of integers.\n");
    printf("Enter integers (0 to terminate): ");

    scanf("%ld", &n);
    while (n != 0) {
        sum += n;
        scanf("%ld", &n);
    }
    printf("The sum is: %ld\n", sum);

    return 0;
}

运行:

This program sums a series of integers.
Enter integers (0 to terminate): 10000 20000 30000 0
The sum is: 60000

7.2 浮点类型

特性说明代码
浮点类型1. float: 单精度浮点数 (一般32位)
2. double: 双精度浮点数 (默认类型, 一般64位)
3. long double: 扩展精度浮点数 (80或128)
浮点常量 (立即数)小数计数法: 57.0 57.
科学计数法: 57e0 5.7e1 570.e-1

后缀:
1. float: f或者F
2. long double: l或者L

7.2.2 读写浮点数

特性说明
float%e %f %g
double%le %lf %lg
long double%Le %Lf %Lg

7.3 字符类型

特性说明代码
字符集使用ASCII
类型char默认有无符号是不确定的, 根据编译器定义.
所以最好显式定义有无符号:

signed char:
unsigned char:
字符常量 (立即数)整数型: char a = 1;
字符型: char b = 'B'
转义序列常用的:
换行: \n
水平制表符: \t
问号: \?
单双引号: ' "
wiki链接: [(C语言程序设计-现代方法) Chapter3-格式化输入和输出#3 1 2 转义序列]((C语言程序设计-现代方法) Chapter3-格式化输入和输出#3 1 2 转义序列 "wikilink")
数字转义序列八进制转义序列: '\EE'
十六进制转义序列: '\xHH'
常用函数小写->大写: ch = toupper(ch)所在库: <ctype.h>

7.3.3 算数类型

整数类型和浮点类型通常为算数类型, 以下是对比C89和C99之间的算数类型范围的差别

C89C99
1. 整数类型:

1. 字符类型: char
2. 有符号整型: signed char, short, int, long
3. 无符号整型: unsigned char, unsigned shor, unsigned int, unsigned long
4. 枚举类型
1. 整数类型:

1. 字符类型: char
2. 有符号整型: signed char, short, int, long, long long
3. 无符号整型: unsigned char, unsigned shor, unsigned int, unsigned long, unsigned long long, _Bool
4. 枚举类型
2. 浮点类型: float, double, long double1. 浮点类型:
1. 实数浮点类型: float, double long double
2. 复数类型: float_Complex, double_Complex, long double_Complex

7.3.6 scanf和printf读写字符

scanf("%c", &ch);
printf("%c", ch);

特殊点: 读取char数据时不会跳过空白符(空格, 换行), 如果缓冲区中保留了空白符 (比如: 上次调用了scanf读取一个整数, 缓冲区中残留了换行符), 那么ch会读取到一个换行符.

解决方法:

// 方法一: 前方加入空格, 表示跳过空白符

scanf(" %c", &ch);

// 方法二: 使用getchar()读取缓冲区, 直到无残留

do {
    scanf("%c", &ch);
} while (ch != '\n');

// 或者

do {
    ch = getchar();
} while (ch != '\n');

// 惯用法

while (getchar() != '\n');

7.3.7 getchar和putchar读写字符

特性:

  1. 返回值类型: int
  2. 不会跳过空白符
  3. 由于是宏实现, 开销较低
putchar(ch);

ch = getchar();

(程序) 获取字符串长度

length.c

#include <stdio.h>

int main(void) {
    char ch;
    int len = 0;

    printf("Enter a message: ");
    ch = getchar();
    while (ch != '\n') {
        len++;
        ch = getchar();
    }

    printf("Your message was %d chracter(s) long.\n", len);

    return 0;
}

精简写法 length2.c

#include <stdio.h>

int main(void) {
    int len = 0;

    printf("Enter a message: ");

    while (getchar() != '\n')
        len++;

    printf("Your message was %d character(s) long.\n", len);
    
    return 0;
}

7.4 类型转换

C89中的转换

特性说明代码
隐式转换发生情况:
1. 算数(逻辑)表达式类型不同时
2. 赋值运算符两侧类型不同时
3. 函数调用实参形参不同时
4. return类型不同时
显式转换
算数类型转换原则原则: 转换成可以安全使用两个数值的"最狭小的"类型
1. 所有操作数均为整值的提升: int -> unsigned int -> long -> unsigned long (char和short发生提升时无条件转换为int)
2. 任意操作数为浮点的提升: float -> double -> long double
赋值类型转换原则原则: 转换为左侧类型
1. 等宽类型: 没有问题
2. 非等宽类型: float->int丢失精度
3. 下降转换: int->char无意义结果
类型不匹配的危害1. 比较错误: -10 (int) < 10 (unsigned int), 其中int会被转换为unsigned int, 未模式不发生改变, 导致转化为一个非常大的数, 导致最终结果为0

7.4.3 C99中的隐式转换

整数转换登记表

等级类型
1long long, unsigned long long
2long, unsigned long
3int, unsigned int
4short, unsigned short
5char, signed char, unsigned char
6_Bool
特性说明代码
整数提升任何等级低于int和unsigned int的类型均转换为int
全整数情况1.整数提升
2.如果结果是类型相同, 则结束; 如果类型不同, 则继续

1.均有符号 或者 均无符号: 向上提升
2.无符号等级 大于 有符号等级: 转换为等级最高的类型
3.有符号可以表示无符号: 转换为有符号
4.其他情况: 均转换为有符号数类型对应的无符号类型
存在浮点情况与C89相同

7.4.4 强制类型转换

强制类型转换的两个功能:

  1. 强制转化类型
  2. 显式的表示隐式转换的目标类型

7.5 类型定义

使用场景:

  • 可移植性: 针对不同的环境, 修改类型定义 (局限: 无法改变使用方式, 后续仍然需要修改代码)
#define BOOL int // 注意顺序与typedef相反

typedef int Bool; // 更优

7.6 sizeof运算符

使用建议: sizeof返回类型为size_t, 建议强制类型转换为已知的无符号整数类型

sizeof(类型名/常量/变量/表达式)
sizeof 变量 // 可以不加括号, 但是需要考虑优先级, 所以推荐使用括号

sizeof(int)
sizeof(a)
sizeof(1)
sizeof int

练习题

答案链接:

  • http://knking.com/books/c2/answers/c7.html
  • https://github.com/williamgherman/c-solutions/tree/master/07

记号说明:

  • 加粗(错误): 表示错误的答案或者遗漏的知识点
  • 在代码块中(错误): 表示错误的答案

Practice1

给出下列整数常量的十进制值。

  1. 077
  2. 0x77
  3. 0XABC

回答:

7 * 8 + 7 = 63
7 * 16 + 7 = 119
10 * 256 + 11 * 16 + 12 = 2748

Practice2

下列哪些常量在C语言中不是合法的?区分每一个合法的常量是整数还是浮点数。

  1. 010E2
  2. 32.1E+5
  3. 0790
  4. 100_000
  5. 3.978e-2

回答:

1. 不确定: 八进制是否存在科学计数法形式 (订正: 合法)
2. 合法, 浮点数
3. 合法, 整数 (不合法: 八进制没有9)
4. 不确定: (只记得rust里面是合法的) (不合法: c中不能有下划线)
5. 合法, 浮点数

Practice3

下列哪些类型在C语言中不是合法的?

  1. short unsigned int
  2. short float
  3. long double
  4. unsigned long

回答:

1.合法
2.不合法
3.合法
4.合法

Practice4

如果变量c 是char 类型,那么下列哪条语句是非法的?

  1. i += c; /* i has type int */
  2. c = 2 * c - 1;
  3. putchar(c);
  4. printf(c);

回答:

1. 合法
2. 合法
3. 合法
4. 非法: 需要格式串

Practice5

下列哪条不是书写数65的合法方式?(假设字符集是ASCII。)

  1. 'A'
  2. 0b1000001
  3. 0101
  4. 0x41

回答:

1.合法
2.不确定: 好像没有二进制立即数 (排除法确定错误)
3.合法
4.合法

Practice6

对于下面的数据项,指明char 、short 、int 、long 类型中哪种类型是足以存储数据的最小类型。

  1. 一个月的天数
  2. 一年的天数
  3. 一天的分钟数
  4. 一天的秒数

回答:

char
short
short
int (错误: long)

Practice7

对于下面的字符转义,给出等价的八进制转义。(假定字符集是 ASCII。)可以参考附录E,其中列出了ASCII字符的数值码。

  1. \b
  2. \n
  3. \r
  4. \t

回答:

010
012
015
011

Practice8

重复练习题7,给出等价的十六进制转义。

回答:

0x08
0x0A
0x0D
0x09

Practice9

假设变量i 和变量j 都是int 类型,那么表达式i / j + 'a' 是什么类型?

回答:

(i / j) + 'a'
左操作数(i / j)运算后仍为int, 右操作数'a'为char
整数提升: 操作数为int

Practice10

假设变量i 是int 类型,变量j 是long int 类型,并且变量k 是unsigned int 类型,那么表达式i + (int)j * k 是什么类型?

回答:

强制类型转换为一元运算符, 优先级最高
i + ((int)j * k)
int + (int * unsigned int)
全为整数的情况, 且同级: 全部转换为无符号类型
最终: unsigned int

Practice11

假设变量i 是int 类型,变量f 是float 类型,变量d 是 double 类型,那么表达式i * f / d 是什么类型?

回答:

左结合性可得: 
(i * f) / d
(int * float) / double
最终: double

Practice12

假设变量i 是int 类型,变量f 是float 类型,变量d 是 double 类型,请解释在执行下列语句时发生了什么转换?

d = i + f

回答:

右侧表达式: i + f含有浮点数, 转换为float类型
赋值运算符: 类型转换为左侧类型, double

Practice13

假设程序包含下列声明:

char c = '\1'; 
short s = 2; 
int i = -3; 
long m = 5; 
float f = 6.5f; 
double d = 7.5;

给出下列每个表达式的值和类型:

  1. c * i = -3, int
  2. s + m = 7, long
  3. f / c = 6.5f, float
  4. d / s = 3.75, double
  5. f - d = -1.0, double
  6. (int) f = 6, int

回答:

Practice14

下列语句是否总是可以正确地计算出f 的小数部分(假设f 和 frac_part 都是float 类型的变量)?

如果不是, 那出了什么问题?

frac_part = f - (int) f;

回答:

可以 (错误: float比int的值域更大, 可能会溢出)

Practice15

使用typedef 创建名为Int8 、Int16 和Int32 的类型。定义这些类型以便它们可以在你的机器上分别表示8位、16位和32位的整数。

回答:

typedef char Int8;
typedef short Int16;
typedef int Int32;

代码题

code1-max

如果i * i 超出了int 类型的最大取值,那么6.3节的程序 square2.c 将失败(通常会显示奇怪的答案)。运行该程序,并确定导致失败的n 的最小值。尝试把变量i 的类型改成short 并再次运行该程序。(不要忘记更新printf 函数调用中的转换说明!)然后尝试改成long 。从这些实验中,你能总结出在你的机器上用于存储整数类型的位数是多少吗?

代码如下

#include <stdio.h>

int main(void) {
    short i, n;

    printf("This program prints a table of squares.\n");
    printf("Enter number of entries in table: ");
    scanf("%hd", &n);

    for (i = 1; i <= n; i++) {
        printf("%10hd%10hd\n", i, i * i);
    }

    return 0;
}

编译并运行

This program prints a table of squares.
Enter number of entries in table: 1000
         1         1
         2         4
         3         9
         4        16
         5        25

....

       178     31684
       179     32041
       180     32400
       181     32761
       182    -32412

code2-square3

修改6.3节的程序square2.c ,每24次平方后暂停并显示下列信息:

Press Enter to continue...

代码如下

#include <stdio.h>

int main(void) {
    short i, n;
    char counter;

    printf("This program prints a table of squares.\n");
    printf("Enter number of entries in table: ");
    scanf("%hd", &n);

    while (getchar() != '\n');

    for (i = 1, counter = 0; i <= n; i++) {
        printf("%10hd%10hd\n", i, i * i);
        counter++;

        if (counter == 24) {
            printf("Press Enter to continue...\n");
            getchar();
            counter = 0;
        }
    }

    return 0;
}

编译并运行

code3-sum3

修改7.1节的程序sum2.c ,对double 型值组成的数列求和。

代码如下

#include <stdio.h>

int main(void) {
    double n, sum = 0;

    printf("This program sums a series of double.\n");
    printf("Enter integers (0 to terminate): ");

    scanf("%lf", &n);
    while (n != 0) {
        sum += n;
        scanf("%lf", &n);
    }
    printf("The sum is: %f\n", sum);

    return 0;
}

编译并运行

This program sums a series of double.
Enter integers (0 to terminate): 11.2 22.1 33.3 0
The sum is: 66.600000

code4-transform

编写程序可以把字母格式的电话号码翻译成数值格式:

(如果没有电话在身边,参考这里给出的字母在键盘上的对应关系:2=ABC ,3=DEF ,4=GHI ,5=JKL ,6=MNO ,7=PQRS , 8=TUV ,9=WXYZ 。)原始电话号码中的非字母字符(例如数字或标点符号)保持不变:

可以假设任何用户输入的字母都是大写字母。

实例一

Enter phone number: CALLATT 

2255288

实例二

Enter phone number: 1-800-COL-LECT 

1-800-265-5328

代码如下

#include <stdio.h>

int main(void) {
    char ch;

    printf("Enter phone number: ");
    ch = getchar();

    while (ch != '\n') {
        switch(ch) {
            case 'A': case 'B': case 'C': 
                ch = '2'; break;
            case 'D': case 'E': case 'F': 
                ch = '3'; break;
            case 'G': case 'H': case 'I': 
                ch = '4'; break;
            case 'J': case 'K': case 'L': 
                ch = '5'; break;
            case 'M': case 'N': case 'O': 
                ch = '6'; break;
            case 'P': case 'Q': case 'R': case 'S': 
                ch = '7'; break;
            case 'T': case 'U': case 'V': 
                ch = '8'; break;
            case 'W': case 'X': case 'Y': case 'Z': 
                ch = '9'; break;
        }

        printf("%c", ch);
        ch = getchar();
    }

    printf("\n");

    return 0;
}

编译并运行

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter7/code4-transform 
╰─$ cd "/Users/lamecrow/TRY/code/c/Retest/C-Chapter7/code4-transform/" && gcc transfo
rm.c -o transform && "/Users/lamecrow/TRY/code/c/Retest/C-Chapter7/code4-transform/"t
ransform
Enter phone number: CALLATT
2255288
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter7/code4-transform 
╰─$ cd "/Users/lamecrow/TRY/code/c/Retest/C-Chapter7/code4-transform/" && gcc transfo
rm.c -o transform && "/Users/lamecrow/TRY/code/c/Retest/C-Chapter7/code4-transform/"t
ransform
Enter phone number: 1-800-COL-LECT
1-800-265-5328

code5-character

在十字拼字游戏中,玩家利用小卡片组成单词,每个卡片包含字母和面值。面值根据字母稀缺程度的不同而不同。(面值有:1------ AEILNORSTU,2------DG,3------BCMP,4------FHVWY,5------K,8------JX, 10------QZ。)编写程序通过对单词中字母的面值求和来计算单词的值:

编写的程序应该允许单词中混合出现大小写字母。提示 :使用 toupper 库函数。

Enter a word: pitfall 

Scrabble value: 12

代码如下

#include <stdio.h>
#include <ctype.h>

int main(void) {
    char ch;
    int sum = 0;

    printf("Enter a word: ");
    ch = getchar();

    while (ch != '\n') {
        ch = toupper(ch); 
        switch(ch) {
            case 'A': case 'E': case 'I': case 'L': case 'N': 
            case 'O': case 'R': case 'S': case 'T': case 'U': 
                sum += 1; break;
            case 'D': case 'G': 
                sum += 2; break;
            case 'B': case 'C': case 'M': case 'P': 
                sum += 3; break;
            case 'F': case 'H': case 'V': case 'W': case 'Y': 
                sum += 4; break;
            case 'K': 
                sum += 5; break;
            case 'J': case 'X': 
                sum += 8;
            case 'Q': case 'Z': 
                sum += 10;
        }

        ch = getchar();
    }

    printf("\nScrabble value: %d\n", sum);

    return 0;
}

编译并运行

Enter a word: pitfall 

Scrabble value: 12

code6-sizeof

编写程序显示sizeof(int) 、sizeof(short) 、 sizeof(long) 、sizeof(float) 、sizeof(double) 和 sizeof(long double) 的值。

代码如下

#include <stdio.h>

int main(void) {
    printf("int : %lu\n", sizeof(int));
    printf("short: %lu\n", sizeof (short));
    printf("long: %lu\n", sizeof(long));
    printf("float: %lu\n", sizeof(float));
    printf("double: %lu\n", sizeof(double));
    printf("long double: %lu\n", sizeof(long double));

    return 0;
}

编译并运行

int : 4
short: 2
long: 8
float: 4
double: 8
long double: 8

code7-compute

修改第3章的编程题6,使得用户可以对两个分数进行加、减、乘、除运算(在两个分数之间输入+ 、- 、* 或/ 符号)。

代码如下

#include <stdio.h>

int main(void) {
    int m1, s1, m2, s2, rm, rs;
    char operator;

    printf("Enter two fractions separated by a plus sign: ");
    scanf("%d/%d %c %d/%d", &s1, &m1, &operator, &s2, &m2);

    switch(operator) {
        case '+':
            rs = s1 * m2 + s2 * m1;
            rm = m1 * m2;
            break;
        case '-': 
            rs = s1 * m2 - s2 * m1;
            rm = m1 * m2;
            break;
        case '*': 
            rs = s1 * s2;
            rm = m1 * m2;
            break;
        case '/':
            rs = s1 * m2;
            rm = m1 * s2;
            break;
    }

    printf("\nThe result is %d/%d\n", rs, rm);

    return 0;
}

编译并运行

Enter two fractions separated by a plus sign: 3/1 - 2/1

The result is 1/1