C Programming: A Modern Approach

第7章 基本数据类型

2020-02-26  本文已影响0人  橡树人

英文原版:P125

截止目前,我们仅使用过两种内置的基本数据类型:int和float。
本章将描述剩余的基本数据类型,并讨论有关数据类型的重要主题。

本章的主要内容有:

7.1节 整数类型 Integer Types

C语言有哪些整数类型?6种

取值范围
32位机器:
short int -2^15 2^15-1
unsigned short int 0 2^16-1
int -2^31 2^31-1
unsigned int 0 2^32-1
long int -2^31 2^31-1
unsigned int 0 2^32-1

64位机器:
short int -2^15 2^15-1
unsigned short int 0 2^16-1
int -2^31 2^31-1
unsigned int 0 2^32-1
long int -2^63 2^63-1
unsigned int 0 2^64-1

所有的编译器都必须遵守2个规则:

  1. short int、int、long int必须能覆盖最小的数值范围。
  2. int不小于short int,long int不小于int,有可能short int跟int有相同的取值范围,int有可能跟long int有相同的取值范围。

如何判断某种整数类型具体实现的整数范围?
检查标准库里的头文件<limits.h>,在这个头文件里定义了宏,表示每种整数类型的最小值和最大值。

整数常量

C语言规定:

注意:

编译器是如何判断不同进制整数常量的类型的?

可强制编译器将一个常量当做长整型long int:

15L 0377L 0x7fffL

可强制编译器将一个常量当做无符号的:

15U 0377U 0x7fffU

整数溢出

什么时候会出现整数溢出?
对整数进行算术运算时,有可能结果太大而无法表示。比如,当对两个int整数执行算术运算,结果必须能使用int来表示。如果不能,则表示发生了溢出

发生整数溢出时该怎么办跟操作数是有符号还是无符号有关。

当对有符号整数执行运算后产生了溢出,结果是没有定义的。最可能的情形就是运算的结果是错的,但程序可能因此崩溃或者表现出其他的未知的行为。

当对无符号整数执行运算后产生了溢出,结果是有定义的:正确结果对2^n取模,其中n是用来存储正确结果的位数。比如对无符号16位数65,535(2^{16}-1)加1,其结果为0。

整数的读取和写入

假设一个程序因为它的某个int变量值溢出而无法工作了。
解决方法:

  1. 将该变量的数据类型修改为long int。
  2. 必须检查该变量是否被用在printf或者scanf的调用里;

读写无符号整数、短整型数、长整型需要几条新的转换说明:

程序示例:计算一系列数的和

源文件sum2.c

/* Sums a serial of numbers (using long variales) */

#include <stdio.h>

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

        printf("This program sums a serial 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;
}

7.2节 浮点数

整数类型并不能满足所有应用。有时需要一些变量,这些变量可存储带小数点的数,或者非常大的数,或者非常小的数。像这样的数是按照浮点数格式存储的。

C语言提供了3种浮点数类型:
单精度浮点数float
双精度浮点数double
拓展精度浮点数double double

精度 有效位数

当对精度要求不高时,使用float

double提供的精度可满足大部分应用

double double极少被使用

由于不同的计算机存储浮点数的方式不同,所以C语言标准并没有描述float double double double能提供多高的精度。

大部分计算机都遵循IEEE 754标准

7.3节 字符型char

char类型值跟计算机有关,因为不同的计算机默认的字符集不一样。

可把任意单个字符赋值给char类型变量,比如

chat ch;

ch='a';
ch='A'
ch='0';
ch=' ';

注意

字符操作

基本事实:

例1 字符运算

char ch;
int i;

i = 'a';
ch = 65;
ch = ch + 1;
ch++;

例2 字符比较

if ('a' <= ch && ch <= 'z') {
  ch = ch + 'A' - 'a';
}

例3 字符变量作为循环变量

for(ch = 'A'; ch <= 'Z'; ch++){
  ...
}

有符号字符和无符号字符

有符号字符的取值范围为-128到127。
无符号字符的取值范围为0到255。

由于C语言标准没有规定普通字符串是有符号的还是无符号的,所以有些编译器把普通字符当做有符号类型,其他则当做无符号类型。

建议:

别假设char类型是有符号还是无符号。如果有区别,则使用unsigned char或者signed char来代替char

转义序列

由于存在不能从键盘上输入的或者不可见的字符,为了方便程序统一处理默认字符集里的每个字符,C语言标准提供了一种特殊的表示法:转义序列

有两类转义序列:

字符转义序列不能表示所有的无法打印的字符,只包含了最常用的字符。

数字转义序列解决了字符转义序列存在的问题,可表示所有的字符。

如何使用数字转义序列来表示特殊字符?
首先,从表中查询该字符对应的八进制表示或者十六进制表示。
然后,使用前述八进制码或者十六进制码来表示特殊字符。
最后,要注意,使用十六进制码来表示数字转义序列时,其中的x必须小写

作为字符常量使用时,转义字符必须用一对单括号括起来,比如'\33'或者'\x1b'。如有必要,可以使用#define来定义,比如:

#define ESC '\33'

例1 数字转义序列
假设某个特殊字符对应的十进制编码为27,八进制编码为33,十六进制编码为1B,则该特殊字符对应的字符转义序列为\33或者\033或者\x1B或者\x1b

处理字符的函数

C语言的库函数toupper

要调用该函数,需在程序开头包含如下预处理指令:

#include <ctype.h>

例1 将一个小写字符转换成大写的两种方式:

if ('a' <= ch && ch <= 'z') {
  ch = ch + 'A' - 'a';
}

ch = toupper(ch);

使用scanf和printf来读写字符

char ch;

scanf("%c", &ch);//在读取一个字符前,不会跳过空格符
printf("%c", ch);

scanf(" %c", &ch);//在读取一个字符前,跳过空格符
printf("%c", ch);

例2 利用scanf通常不会跳过空格的性质来检测输入的结束

char ch;

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

使用getchar和putchar来读写字符

  1. 程序执行时,使用getchar和putchar要比scanf和printf快,理由有2个:
    • getchar和putchar比scanf和printf更简单;
    • 为了额外的速度提升,getchar和putchar是作为宏来实现的;

函数getchar

例1 跳过所有的换行符:

char ch;

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

答:

char ch;

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

或者

char ch;

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

例2 跳过所有的空白

while ((ch = getchar()) == ' ') {
  ;
}

程序示例:输出一条消息的长度

需求:用户输入一条消息,程序输出这条消息的长度
范例输出:

Enter a message: china
Your message was 5 character(s) long.

源文件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 character(s) long.\n", len);

    return 0;
}

优化版length2.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 character(s) long.\n", len);

    return 0;
}

7.4节 类型转换

C语言有哪些算术类型?

C语言有哪两种类型转换?

什么是隐式转换?
计算机要比C语言对待算术更严格。

让计算机做算术运算,两个操作数必须有相同的大小,相同的存储方式。计算机允许两个16位的数相加,但不允许一个16位整数和一个32位整数相加、一个32位整数和一个32位浮点数相加。

C语言允许表达式中出现基本类型的混合。我们可将整数、浮点数、甚至字符整合到一个单独的表达式中。编译器可能要生成一些指令,将某些操作数转换成不同类型,以便硬件能对该表达式求值。比如,将一个16位的短short整型数和一个32位的int整数相加,编译器会安排16位的短整型转换成32位。将一个int数和float数相加,编译器将安排int转换成float。

由于编译器会自动地处理这些转换,所以被称为隐式转换

有哪些场合需要执行隐式地类型转换?

算术运算类型转换

普通的算术类型转换可应用到大部分二目运算符的操作数上,比如算术运算符、关系运算符、判等运算符等。比如,我们假设f代表类型float,i代表类型int。表达式f+i的转换规则是将int转换成float。

提醒:尽可能多地使用无符号数,永远不要混合使用无符号数和有符号数

C语言中是如何处理有符号数和无符号数混合情形的?

it i = -10;
unsigned int u = 10;

i < j;//该表达式的值是0,理由负10不能被表示成无符号数,则将负10转换成-10+2^31+1=4294967286,显然大于10

例1 算术类型转换

char c;
short int s;
int i;
unsigned int u;
long int l;
unsigned long int ul;
float f;
double d;
long double ld;

i = i + c;
i = i + s;
u = u + i;
l = l + u;
ul = ul + l;
f = f + ul;
d = d + f;
ld = ld + d;

赋值过程中的转换规则

规则:将赋值运算符右边表达式的值转换成左边表达式的值

例1 赋值类型转换

char c;
int i;
float f;
double d;

i = c;
f = i;
d = f;

例2 浮点数赋值给整数

int i;

i = 842.97;//i现在是842
i = -842.97;//i现在是-842

例3 错误示例

char c;
int i;
float f;

c = 10000;//出错
i = 1.0e20;//出错
f = 1.0e100;//出错

注解

强转符

强转表达式形式:

(类型名) 表达式

例1 计算一个float数的小数部分

float f, frac_part;

frac_part = f - (int) f;

例2 强制编译器做转换

float quotient;
int dividend, divisor;

quotient = dividend / divisor;//将int转换成float

quotient = (float)dividend / divisor;

例3 使用强转来防止溢出

long i;
int j = 1000;

i = (long) j * j;

7.5节 类型定义

格式

typedef int Bool;

注解:

作用

使用type def来定义Bool会导致编译器将Bool类型加入其能识别的类型名列表中。

使用type def来定义Bool后,跟内置类型名一样,Bool可被用在变量声明,类型转换表达式,及其他地方。比如:

Bool flag;//等价于 int flag

优点

  1. 类型定义可使一个程序更容易理解。
  2. 类型定义可使一个程序修改起来更容易。
  3. 类型定义可提高一个程序的可移植性。

例1 假设使用变量cash_in和cash_out来存储美元量。
先声明Dollars为

typedef float Dollars;

然后声明:

Dollars cash_in, cash_out;

float cash_in, cash_out;

更有实际意义。

例2 后面我们要修改Dollars为double,仅需修改类型定义行,声明为Dollar类型变量的地方就不用修改。

typedef double Dollars;

例3 如果i是一个int变量,则虽然赋值语句

i = 100000;

在32位机器上运行正常,但在16位的机器上就运行失败(2^15-1=32767)。
此时,可考虑使用typedef来定义整数类型

例4 假设我们正在编写一个程序,该程序需要变量来存储产量,其中产量的数值范围为0-5000。
方法一:使用long来定义该变量;
方法二:使用int定义该变量;
方法三:使用typedef来定义产量类型

typedef int Quantity;

使用Quantity类型来声明变量:

Quantity q;

当移植到较短整数机器上时,只需修改产量的类型定义即可:

typedef long Quantity;

例5 C语言库使用typedef来为那些因C语言实现不同而改变的类型来创建类型名,比如_tptrdiff_tsize_twchar_t等,可参考头文件<stdint.h>

typedef long int ptrdiff_t;
typedef unsigned long int size_t;
typedef int wchar_t;

7.6节 sizeof运算符

作用:计算存储一个类型的值需要多大的内存

格式

sizeof(类型名)

解释:
该表达式的值是一个无符号整数,表示要存储属于类型名的值需要的字节数。

sizeof运算符了可用在哪些类型名上?

打印输出sizeof表达式值时要小心,因为size of表达式的类型是与C语言实现有关size_t类型。建议在输出前,将表达式的值转换成已知类型,比如

printf("Size of int is %lu\n", (unsigned long) sizeof(int));
上一篇 下一篇

猜你喜欢

热点阅读