【C语言入门到精通】04 数据类型

【C语言入门到精通】04 数据类型

If a program manipulates a large amount of data, it does so in a small number of ways.1

码字不易,对你有帮助 **点赞/转发↪️/关注 ** 支持一下作者
微信搜公众号:不会编程的程序圆
看更多干货,获取第一时间更新

思维导图


【C语言入门到精通】04 数据类型_第1张图片

✉️写在前面


如果只是写个人学习总结的博客很容易,简单写一些感悟然后贴上代码走人就可以了,甚至不用校审。但是我命名本系列为【C语言必知必会】帮助你从入门到精通 C语言,那势必要“事无巨细”一些:既要考虑到没有基础的初学者,又不能止于基础。所以本教程适合各类人群学习,只要你看就一定会有帮助。

本教程是本人纯手打并排版,校审由我与我的搭档汤圆君一起完成的。你们看这一篇文章我要写好几个小时。如果文章对你有帮助,请不要“白嫖”。支持一下作者,作者会发更多干货好文。

特别鸣谢:汤圆君(公众号:【Cc听香水榭】 长期更新高质量英语教学)关注她表示对她工作的认可吧!

▶️ 此符号表示该内容以后的章节会讲解,此章节内不要求理解。

目录


文章目录

    • 【C语言入门到精通】04 数据类型
      • :world_map:思维导图
      • :email:写在前面
      • :globe_with_meridians:目录
      • :banana:概述
        • 关键字
        • 位,字节和字
        • 整数
        • 浮点数
        • 整数与浮点数的区别:
      • 整数类型
        • 有符号整数和无符号整数
        • 整数的类型
        • C99 中的整数类型
        • 整数常量
            • 8 进制 与 16 进制
        • C99 中的整数常量
        • 整数溢出
        • 读/写整数
            • 改进程序
      • 浮点类型
        • 浮点常量
        • 读/写浮点数
      • 字符类型
        • 字符操作
        • 有符号字符 和 无符号字符
        • 算数类型
        • 转义序列
            • 数字转义序列
            • 转义序列示例
        • 用 scanf 和 printf 读/写字符
        • 用 getchar 和 putchar 读/写字符
        • :warning:
            • 程序:确定消息的长度
      • 类型定义
        • 类型定义的优点
        • 类型定义的可移植性
      • sizeof 运算符

概述


关键字

C语言的数据类型关键字

最初 K&R 给出的关键字 C90标准添加的关键字 C99标准添加的关键字
int signed _Bool (布尔型)
short void _Complex(复数)
long _Imaginary(虚数)
unsigned
char
float
double

通过这些关键字创建的类型,按计算机的存储方式可分为两大基本类型:整数类型浮点数类型

位,字节和字

位,字节和字

位(bit): 最小的存储单元,也称比特位。可以存储 0 或 1(或者说,位用于存储“开”或“关”)

字节(byte): 1 byte = 8 bit 既然 1 位可以表示 0 或 1,那么 1 字节就有 256 (2^8)种 0/1 组合,通过二进制编码(仅用 0/1 便表示数字),便可表示 0 ~ 255 的整数或一组字符。(以后会详细讲解

字(word): 是设计计算机时给定的自然存储单位。对于 8 位 的微型计算机(如:最初的苹果机),1 字长 只有 8 位,从那以后,个人计算机的字长增至 16 位,32位,直至目前的 64位。计算机字长越大,其数据转移越快,允许访问的内存越多。

整数

整数 7 以二进制形式表示是:111 ,用一个字节存储可表示为:

【C语言入门到精通】04 数据类型_第2张图片

浮点数

浮点数相比我们都不陌生,本节后面还会做更详细的介绍。现在我们介绍一种浮点数的表示方法:e记数法。

如 3.16E+007 表示 3.16 * 10^7(3.16乘以10的七次方)。007 表示 10^7;+ 表示 10 的指数 7 为正数。

其中,E 可以写成 e;表示正次数时,+ 号可以省略;007也可以省略为7。即:3.16e7。

浮点数和整数的存储方案是不同的。计算机将浮点数分成小数部分和指数部分来表示,而且分开存储这两部分。因此,虽然 7.0 和 7 在数值上相同,但它们的存储方式不同。下图演示了一个存储浮点数的例子。后面我们会做更详细的解释

【C语言入门到精通】04 数据类型_第3张图片

整数与浮点数的区别:

  • 整数没有小数部分,浮点数有小数部分
  • 浮点数可以表示的范围比整数大
  • 对于一些算术运算(如,两个很大的数相减),浮点数损失的精度更多
  • 因为在任何区间内都存在无穷多个实数,所以计算机的浮点数不能表示区间内的所有值。浮点数通常只是实际值的近似值。(例如,7.0 可能被存储为浮点值 6.99999)
  • 过去,浮点数运算比整数运算慢。不过现在许多CPU都包含了浮点数处理器,缩小了速度上的差距。

整数类型


有符号整数和无符号整数

有符号整数如果为零或正数,那么最左边的位(符号位,只表示符号,不表示数值)为 0 ;如果为负数,则符号位为 1。如:最大的 16 位整数(2个字节)的二进制表示形式是 01111111 11111111,对应的数值是 32767(即:2^15 - 1)

无符号整数 不带符号位(最左边的位是数值的一部分)。因此,最大的 16 位整数的二进制表示形式是:11111111 11111111(即:2^16 - 1)

默认情况下,C语言中的整型变量都是有符号的,也就是说最左位保留符号位。若要告诉编译器变量没有符号位,需要把他声明成 unsigned 类型。

整数的类型

short int

unsigned short int

int

unsigned int

long int

unsigned long int

整数的类型归根结底只有这 6 种,其他组合都是上述某种类型的同义词。

例如:long signed int 与 long int 是一样的;unsigned short int 与 short unsigned int 是一样的

C语言允许通过省略单词 int 来缩写整数类型的名称。

例如:unsigned short int 可以缩写为 unsigned short ;而 long int 可以缩写为 long。

C程序员经常省略 int 。

6 种 整数类型每一种所表示的取值范围都会根据机器的不同而不同,但是有两条所有编译器都必须遵守的原则。

  • C 标准要求 short,int,long 中的每一种类型都要覆盖一个确定的最小取值范围(后面会详细讲解
  • int 类型不能比 short 类型短,long 类型不能比 int 类型短

这也就是说:short 的大小可以与 int 相等;int 的大小可以与 long 相等

16位,32位,64位机器的整数类型都各有些不同,我们常用的是 32 位机器(严格来说是编译器,我的电脑是 64 位,但是VS2019用的最多的是 32位模式),我们就以 32 位机器为例

32位机器整数类型

类型 最小值 最大值
short -32768( - 2^15 ) 32767(2^15 -1 )
unsigned short 0 65535 (2^16 - 1)
int - 2147483648(- 2^31) 2147483647(2^31 - 1)
unsigned int 0 4294967295
long - 2147483648 2147483647
unsigned long 0 4294967295

可以看出,32位机器上,int 与 long的大小是一样的,都是 4 个字节。

16位机器上,int 与 short 大小是一样的,都是 2 个字节。

64位机器上,与 32 位机器不同的是,long 是 8 个字节。

但是,上述所说的规律并不是 C标准规定的,会随着编译器的不同而不同。可以检查头文件,来查看每种整数类型的最大值和最小值。(下面给出我的VS2019的 limits.h 头文件)

limits.h

#pragma once
#define _INC_LIMITS

#include 

_CRT_BEGIN_C_HEADER



#define CHAR_BIT      8         // number of bits in a char 
#define SCHAR_MIN   (-128)      // minimum signed char value
#define SCHAR_MAX     127       // maximum signed char value
#define UCHAR_MAX     0xff      // maximum unsigned char value

#ifndef _CHAR_UNSIGNED
    #define CHAR_MIN    SCHAR_MIN   // mimimum char value
    #define CHAR_MAX    SCHAR_MAX   // maximum char value
#else
    #define CHAR_MIN    0
    #define CHAR_MAX    UCHAR_MAX
#endif

#define MB_LEN_MAX    5             // max. # bytes in multibyte char
#define SHRT_MIN    (-32768)        // minimum (signed) short value
#define SHRT_MAX      32767         // maximum (signed) short value
#define USHRT_MAX     0xffff        // maximum unsigned short value
#define INT_MIN     (-2147483647 - 1) // minimum (signed) int value
#define INT_MAX       2147483647    // maximum (signed) int value
#define UINT_MAX      0xffffffff    // maximum unsigned int value
#define LONG_MIN    (-2147483647L - 1) // minimum (signed) long value
#define LONG_MAX      2147483647L   // maximum (signed) long value
#define ULONG_MAX     0xffffffffUL  // maximum unsigned long value
#define LLONG_MAX     9223372036854775807i64       // maximum signed long long int value
#define LLONG_MIN   (-9223372036854775807i64 - 1)  // minimum signed long long int value
#define ULLONG_MAX    0xffffffffffffffffui64       // maximum unsigned long long int value

#define _I8_MIN     (-127i8 - 1)    // minimum signed 8 bit value
#define _I8_MAX       127i8         // maximum signed 8 bit value
#define _UI8_MAX      0xffui8       // maximum unsigned 8 bit value

#define _I16_MIN    (-32767i16 - 1) // minimum signed 16 bit value
#define _I16_MAX      32767i16      // maximum signed 16 bit value
#define _UI16_MAX     0xffffui16    // maximum unsigned 16 bit value

#define _I32_MIN    (-2147483647i32 - 1) // minimum signed 32 bit value
#define _I32_MAX      2147483647i32 // maximum signed 32 bit value
#define _UI32_MAX     0xffffffffui32 // maximum unsigned 32 bit value

// minimum signed 64 bit value
#define _I64_MIN    (-9223372036854775807i64 - 1)
// maximum signed 64 bit value
#define _I64_MAX      9223372036854775807i64
// maximum unsigned 64 bit value
#define _UI64_MAX     0xffffffffffffffffui64

#if _INTEGRAL_MAX_BITS >= 128
    // minimum signed 128 bit value
    #define _I128_MIN   (-170141183460469231731687303715884105727i128 - 1)
    // maximum signed 128 bit value
    #define _I128_MAX     170141183460469231731687303715884105727i128
    // maximum unsigned 128 bit value
    #define _UI128_MAX    0xffffffffffffffffffffffffffffffffui128
#endif

#ifndef SIZE_MAX
    #ifdef _WIN64
        #define SIZE_MAX _UI64_MAX
    #else
        #define SIZE_MAX UINT_MAX
    #endif
#endif

#if __STDC_WANT_SECURE_LIB__
    #ifndef RSIZE_MAX
        #define RSIZE_MAX (SIZE_MAX >> 1)
    #endif
#endif



_CRT_END_C_HEADER

C99 中的整数类型

C99 提供了两个额外的整数类型:long long intunsigned long long int

整数常量

整数常量:在程序中以文本形式出现的数,而不是读,或计算出来的数。

C语言允许用 十进制(基数为 10),八进制(基数为 8),十六进制(基数为 16)的形式书写整数常量

8 进制 与 16 进制

8 进制数是用数字 0 ~ 7 书写的。八进制的每一位表示一个 8 的幂(这就如同 10 进制每一位表示 10 的幂一样)。因此,八进制数 237 表示成 10 进制数就是 2 * 8^2 + 3 * 8^1 + 7 * 8^0 = 128 + 24 + 7 = 159

16 进制数使用数字 0 ~ 9 加上字符 A ~ F 书写的,其中字符 A ~ F 表示 10 ~ 15 的数。16进制数每一位表示一个 16 的幂,16进制数 1AF 的十进制数值是 1 x 16^2 + 10 * 16^1 + 15 * 16^0 = 256 + 160 + 15 = 431

如果上面的描述你还是没有懂,可以参考下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ziyXqpwQ-1585358279189)(C:\Users\1\Desktop\素材\27.png)]

  • 十进制常量包含 0 ~ 9 的数字,但是不能以 0 开头

    15 255 32767

  • 八进制常量包含 0 ~ 7 的数字,必须要以 0 开头

    017 0377 077777

  • 十六进制常量包含 0 ~ 9 的数字 和 A ~ F 的字母,总是以 0x 开头

    0xf 0xff 0x7fff

    十六进制常量中的字母可以是大写也可以是小写

#include

int main(void) {

	int x = 100;

	printf("decimal = %d	octonary = %o	hexadecimal = %x \n", x, x, x);
	printf("decimal = %d	octonary = %#o	hexadecimal = %#x \n", x, x, x);
	return 0;
}

输出:

decimal = 100   octonary = 144  hexadecimal = 64
decimal = 100   octonary = 0144 hexadecimal = 0x64

八进制与十六进制只是书写数的方式,他们不会对数的实际存储方式产生影响整数都是以二进制形式存储的)。任何时候都可以从一种书写方式切换的另一种,甚至可以混合使用:10 + 015 + 0x20 = 55 。八进制和十六进制更适合底层程序的编写(以后会详细讲到)。

十进制整数常量的类型通常是 int ,但如果常量过大,就用 long int 类型,如果还不够用,编译器会用 unsigned long int 做最后尝试。

八进制和十六进制常量编译器会依次尝试:int,unsigned int,long int 和 unsigned long int 类型,知道找到能表示该常量的类型。

为了强制编译器把常量作为长整数来处理,只需要在后面加上一个字母L(或l,字母l比较像数字1所以建议大写):

15L 0377L 0x7ffffL

为了指明是无符号常量,可以在常量后面加上字母U(或u):

15U 0377U 0x7ffffU

L 与 U 可以结合使用:0xffffffffLU(L 与 U 的书写顺序无所谓)

C99 中的整数常量

在 C99 中,以 LL 或 ll (字母大小写要一致)结尾的整数常量是 long long int 类型。在 LL 或 ll 前面或后面加上 U(u)表示 unsigned long long int 类型。

C99 与 C89 在确定整数常量类型规则上有些不同。

对于没有后缀的十进制常量,其类型是 int ,long int,long long int 中能表示该值的 最小类型。

对于八进制和十六进制常量,可能的类型顺序为:int,unsigned int,long int,unsigned long int,long long int,unsigned long long int。

常量后面任何后缀都会改变可能的类型列表。

整数溢出

对整数执行算数运算时,其结果可能太大而无法表示。例如,对两个 int 值进行算数运算时,其结果必须仍然能用 int 来表示;否则(表示结果所需要的数位(二进制)太多),就会发生溢出

有符号整数的溢出时,程序的行为时未定义的。未定义行为的结果是不确定的。最有可能的结果是,仅仅是运算出错,但是程序也有可能崩溃,或者出现其他意想不到的情况。

无符号整数溢出时,结果是有定义的:*对 2^n 取模,其中 n 是用于存储结果的位数。*例如:如果对无符的 16 位数 65535 加 1,其结果可以保证为 0 。

请看下面的程序,也许可以帮助你理解。

tobig.c —— 超出系统最大 int 值

#include

int main(void) {

	int i = 2147483647;
	unsigned int j = 4294967295;

	printf("%d	%d	%d\n", i, i + 1, i + 2);
	printf("%u	%u	%u\n", j, j + 1, j + 2);

	return 0;
}

输出:

2147483647      -2147483648     -2147483647
4294967295      0       1

可以将无符号整型 j 看作是汽车的里程表。当达到他能表示的最大值时,会重新从起点开始。整数 i 也是类似的情况。它们的主要区别是,在超过最大值时, unsigned int 类型的变量 j 从 0 开始;而 int 型的变量则从 -2147483648 开始。注意,当 i 超出(溢出)其相对类型所能表示的最大值时,系统并未通知用户。因此必须自己注意这类问题。

读/写整数

读写无符号整数

unsigned int a;

  • 十进制

    scanf("%u", &a);

    printf("%u", a);

  • 八进制

    scanf("%o", &a);

    printf("%o", a);

  • 十六进制

    scanf("%x", &a);

    printf("%x", a);

读写**短整型*数:在 d,u,o,x 前加上 h

short b

  • scanf("%hd", &b);

    printf("%hd", b);

读写长整数:在 d,u,o,x 前加上 l

long c

  • scanf("%ld", &c);

    printf("%ld", c);

读写长长整数: 在 d,u,o,x 前加上 ll

long long int d

  • scanf("%lld", &d);

    printf("%lld", d);

改进程序
#include

int main(void){
    
    int a, b, sum;
    
    printf("Enter two integers:\n");
    scanf("%d %d", &a, &b);
    
    sum = a + b;
    
    printf("The sum is %d\n", sum);
    return 0;
}

观察上述程序,请思考:两个 int 型的变量的和可能超过 int型变量允许的最大值。因此,为了改进这个程序,我们可以将 int 型的 a,b,sum 都变为 long long 型(考虑到 32 位机器的 long 与 int 大小是相同的。)如下:

int main(void) {

	long long a, b, sum;

	printf("Enter two integers:\n");
	scanf("%lld %lld", &a, &b);

	sum = a + b;

	printf("The sum is %lld\n", sum);
	return 0;
}

浮点类型


C语言提供了三种浮点类型,对应着不同的浮点格式:

  • float:单精度浮点数
  • double:双精度浮点数
  • long double:扩展精度浮点数

通常我们用到的是 double

自己编译器 的浮点特征(浮点类型的范围)可以在float.h头文件内查看。下面给出我的 VS2019 的 float 头文件的部分内容。

float.h

#define DBL_DECIMAL_DIG  17                      // # of decimal digits of rounding precision
#define DBL_DIG          15                      // # of decimal digits of precision
#define DBL_EPSILON      2.2204460492503131e-016 // smallest such that 1.0+DBL_EPSILON != 1.0
#define DBL_HAS_SUBNORM  1                       // type does support subnormal numbers
#define DBL_MANT_DIG     53                      // # of bits in mantissa
#define DBL_MAX          1.7976931348623158e+308 // max value
#define DBL_MAX_10_EXP   308                     // max decimal exponent
#define DBL_MAX_EXP      1024                    // max binary exponent
#define DBL_MIN          2.2250738585072014e-308 // min positive value
#define DBL_MIN_10_EXP   (-307)                  // min decimal exponent
#define DBL_MIN_EXP      (-1021)                 // min binary exponent
#define _DBL_RADIX       2                       // exponent radix
#define DBL_TRUE_MIN     4.9406564584124654e-324 // min positive value

#define FLT_DECIMAL_DIG  9                       // # of decimal digits of rounding precision
#define FLT_DIG          6                       // # of decimal digits of precision
#define FLT_EPSILON      1.192092896e-07F        // smallest such that 1.0+FLT_EPSILON != 1.0
#define FLT_HAS_SUBNORM  1                       // type does support subnormal numbers
#define FLT_GUARD        0
#define FLT_MANT_DIG     24                      // # of bits in mantissa
#define FLT_MAX          3.402823466e+38F        // max value
#define FLT_MAX_10_EXP   38                      // max decimal exponent
#define FLT_MAX_EXP      128                     // max binary exponent
#define FLT_MIN          1.175494351e-38F        // min normalized positive value
#define FLT_MIN_10_EXP   (-37)                   // min decimal exponent
#define FLT_MIN_EXP      (-125)                  // min binary exponent
#define FLT_NORMALIZE    0
#define FLT_RADIX        2                       // exponent radix
#define FLT_TRUE_MIN     1.401298464e-45F        // min positive value

如果你的编译器 float 和 double 的最大值和最小值和我的一样,说明你的编译器也是支持 IEEE标准的(大多数计算机都是遵循 IEEE 754标准)。

浮点常量

浮点常量可以有多种写法。例如,下面这些写法都表示数 57.0

57.0 57. 57.0e0 5.7e1 5.7e+1 .57e2 570.e-1

浮点常量必须包含小数点或指数

**默认情况下,浮点常量都以双精度的形式存储。**换句话说,当 C语言的编译器在程序中发现常量 57.0 时,它会安排数据以 double 类型变量的格式存储在内存中。

如果只需要单精度,可以在常量末尾加上 Ff(如 57.0F);如果想以 long double 格式存储,在常量尾加上 Ll(如 57.0L)

读/写浮点数

  • float: %e %f %g

  • double: %lf

    • scanf("%lf", &varible);

    • printf("%f", varible);

      lf格式串 只能在 scanf 中使用;在用 printf 输出 double 时,格式串可以使用 e,f,g

  • long double: %Lf

    • scanf("%Lf", &varible);
    • printf("%Lf", varible);

字符类型


字符类型(字符型):char

char 类型的值可以根据计算机的不同而不同,因为不同的计算机可能会有不同的字符集。

字符集:当今最常用的字符集是 ASCII (美国信息交换标准码)字符集。

【C语言入门到精通】04 数据类型_第4张图片

字符操作

C语言把字符当作小整数进行处理

所有字符都是以二进制形式进行编码的。

在标准的 ASCII 码中,字符的取值范围是 00000000 ~ 01111111,可以看成是 0 ~ 127 。例如,字符 ‘A’ 的值是 65,‘a’ 的值是 97,‘0’ 的值是48,’ ’ 的值是 32 。

许多字符集都超出了 127,甚至多余 255(unsigned char 类型,二进制序列为:1111 1111 )。

C语言中,字符和整数的关联是很强的,字符常量事实上是 int 类型而非 char 类型

请看下面的例子,你会更深的理解 字符型与整型的关联(字符集位ASCII)

char ch;
int i;

i = 'a';// i is now 97
ch = 65;//'ch' is now 'A'
ch = ch + 1;//'ch' now is 'B'

因此,字符就有了数的一些特征。比如可以像数一样比较,可以当作条件应用于 if语句,for循环。这是一个很便利的事情。

但是,以数的形式处理字符 可能降低程序的可移植性(不同机器使用的字符集不同) 和 导致编译器无法检查出来的多种编程错误(‘a’ + ‘b’ * ‘c’ 这类没有意义的表达式等)。

有符号字符 和 无符号字符

有符号字符signed char:取值范围:-128 ~ 127

无符号字符unsigned char: 取值范围:0 ~ 255

可移植性技巧:不要假设 char 类型默认为 signed 或 unsigned 。如果有区别,用 signed char 和 unsigned char 代替 char 。

算数类型

整数类型浮点类型 统称为 算数类型。以下为 C89 中对算数类型的分类

  • 整数类型
    • 字符类型(char)
    • 有符号整型(signed char, short int, int, long)
    • 无符号整型(unsigned char, unsigned short int, unsigned int, unsigned long int)
    • 枚举类型
  • 浮点类型(float,double,long double)

转义序列

正如前面我们所看到的那样,字符常量通常是用单引号扩起来的单个字符。然而,一些特殊符号(如换行符)是无法采用上述方法书写的,因此它们不可见(非打印字符),或者无法从键盘输入。因此,为了使程序可以处理字符集中的每一个字符,C语言提供了一种特殊的表示法——转义序列(escape sequence)。

转义序列有两种:字符转义序列(character escape)和 数字转义序列(numeric escape)。

字符转义序列(粗体比较常用,需要注意)

名称 转义序列 名称 转义序列
换行符 \n 回退符 \b
水平制表符 \t 垂直制表符 \v
单引号 \’ 换页符 \f
双引号 \" 问号 ?
回车符 \r 报警(响铃)符 \a
反斜杠 \\
数字转义序列

为了将特殊字符写成数字转义序列,首先要在 ASCII 码表上查找字符的 八进制或十六进制值。比如某个 ASCII 码转义字符(十进制为 27)八进制为 33 ,十六进制为 1B。

  • 八进制转义序列由字符 \和跟随其后的一个最多含有三位数字的八进制数组成(此书必须表示为无符号字符,最大值的八进制为 377)。例如,可以将转义字符写成 \33\033。和八进制常量不同,转义序列的八进制数不一定要用 0 开头
  • 十六进制转义序列\x 和跟随其后的一个十六进制数组成。(标准C对十六进制数的位数没有限制,但必须表示为无符号字符,所以最大值为 FF。)若采用这种方法,可以把转义字符写成 \x1b\x1B 的形式。字符 x必须小写,但是十六进制数字不限大小写。

作为字符常量使用时,转义序列必须用一对单引号括起来。例如,可以将转义字符写为 \033\x1B 这种形式。转移序列可能有些隐晦,所以采用 #define 的方式给他们命名是一种不错的主意:如果你不懂,可以标记下来,然后跳过

#define ESC '\33' // ASCII escape character

转移序列也可以嵌在字符串中使用。

请打印出下面一行的内容:

Gramps	sez,"a \ is a backslash."
printf("Gramps sez, \" a \\ is a backslash.\"\n");

数字转义序列嵌入字符串,

printf("Hello!\007\n");// \007 打印警报
printf("\x48\x45\x4C\x4C\x4F\n");//HELLO

关于转义序列

  • 上面的例子中,为何没有用单引号将转义序列括起来?

    无论是普通字符还是转义字符,只要是双引号扩起来的字符集合,就无需再用单引号括起来。双引号中的字符集合叫做字符串(▶️后面会讲)

  • 何时使用 ASCII码?何时使用转义序列?

    如果要在转义序列(比如,’\f’)和 ASCII中(’\014’)之间选择,请选择前者(’\f’)。这样的写法不仅好记,而且可移植性更高。’\f’在不使用 ASCII 码的系统中,仍然有效。

  • 如果要使用 ASCII 码,为何要写成 ‘\032’ 而不是 032?

    首先,’\032’能清晰的表达程序员使用字符编码的意图。其次,这样的序列可以嵌入 C 的字符串中。比如上面的例题。

转义序列示例
#include

int main(void) {

	float salary;

	printf("\aEnter your desired monthly salary:");
	printf("$_____\b\b\b\b\b");//5 个退格符
	scanf("%f", &salary);
	printf("\t%.2f amonth is $%.2f a year", salary, 12 * salary);
	printf("\rGee!");//回到行首
	return 0;
}

尝试思考一下这个程序会输出什么。

用 scanf 和 printf 读/写字符

转换说明 %c 允许 scanf 函数和 printf 函数对单个字符进行 读/写 操作:

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

读入字符前,scanf 函数不会跳过空白字符。我们不妨做以下测试:

程序如下,我们输入“ a”(空格 + 字母 a)

#include

int main(void) {

	char ch;

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

	return 0;
}

我们发现 printf 函数没有输出任何东西,其实是只打印了一个空格

现在,我们对 scanf 函数做一点小改动:

scanf(" %c", &ch);//在转换说明 %c 前加一个空格

再次运行程序,这时不管我们在字母 a 前输入多少空格,printf 函数都会打印出字母 a

scanf格式串中的空白表示“跳过零个或多个空白字符”

以下内容不要求初学者理解

我们可以用 scanf 函数来检测输入行的结尾:检查读入的字符是否为换行符(如果是,则表示当前行结尾)。例如,下面的循环将读入并且忽略当前输入行剩下的所有字符:

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

下次调用 scanf 函数时,将读入下一个输入行中的第一个字符。

用 getchar 和 putchar 读/写字符

putchar 函数用于写单个字符:

putchar(ch);

每次调用 getchar函数时,它都会读入一个字符并将其返回。为了保存这个字符,必须使用赋值操作符将其存储到变量中。

ch = getchar();// reads a character and stores it in ch

事实上,getchar 函数返回的是一个 int 类型的值而不是 char 类型的值(原因在后面会讲解)。因此,如果一个变量用于存储 getchar 函数读取的字符,其类型设置为 int 而不是 char 也理所当然。和 scanf 函数一样 getchar 函数也不会跳过空白字符

执行程序时,getchar 与 putchar 比 scanf 和 printf 更加高效。原因如下:

  1. 这两个函数比 scanf函数 和 printf函数 简单的多。 因为 scanf 和 printf 是设计用来按不同的格式读/写多种不同类型的数的。
  2. 为了额外提升速度,通常 getchar函数和 putchar函数是作为宏(▶️后面会讲)来实现的。

以下内容不要求初者理解

getchar 另一个优势是:返回的是读入的字符

对于上面用 scanf 跳过当前输入行的程序,我们可以用 getchar 来改写

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

我们可以让程序更为精简:

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

惯用法

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

getchar 还可以跳过不定数量空格字符:

惯用法

while(getchar() == ' ') //skips blanks
    ;

当循环终止时,变量 ch 的值为 getchar 遇到的第一个非空白字符。


⚠️

如果一个程序中混合使用 scanf 和 getchar ,请小心。请看下面的程序,这个程序会发生什么?

printf("Enter an integer: ");
scanf("%d", &i);
printf("Enter an command: ");
command = getchar();

输入 i 后,scanf 函数会留下没有消耗掉的任意字符,包括(但不限于)换行符。getchar 函数随后将取回第一个剩余的字符(这个程序中是换行符),这不是我们所希望的结果。


程序:确定消息的长度

为了说明字符的读取方式,下面编写一个程序来计算消息的长度。用户输入消息后,程序显示长度:

Enter a message: Hello World!

Your message was 12 character(s) long.

消息的长度包含 空格和标点符号,但是不包含结尾的换行符。

length.c

#include

int main() {

	int ch = 0;//定义变量时,如果这个变量没有值初始化,可以将其初始化为 0 。
	int count = 0;

	printf("Enter a message: ");//这里可以将你要输入的信息一次性输入完,getchar 会负责一个一个的去取
	ch = getchar();

	while (ch != '\n') {
		count++;//这个语句的意思就是 count = count + 1(将count加1后的值再赋值给count,实现count增加1)
		ch = getchar();
	}

	printf("Your message was %d character(s) long\n", count);

	return 0;
}

简化一下:

length2.c

#include

int main() {

	int count = 0;

	printf("Enter a message: ");

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

	printf("Your message was %d character(s) long\n", count);

	return 0;
}

如果小黄有多条 message 想显示长度,如果每次测完都要重新运行程序就太麻烦了。请你改写程序,满足小黄这一要求。自己尝试编写。

类型定义


类型定义(type definition)

#include
typedef int int32;

int main(void) {
	int32 a;
	scanf("%d", &a);
	printf("%d", a);
	return 0;
}

编译器会把 int32 类型看作 int 类型,因此 a 就是一个普通的 int 型变量。

类型定义的优点

类型定义使得程序更容易理解(选择有意义的类型名)。例如,假设 cash_in 和 cash_out 用于存储美元数量。

typedef float Dollars 

随后可以这样定义 cash_in 和 cash_out:

Dollars cash_in,cash_out;

上面的写法比这样写更有意义:

float cash_in,cash_out;

类型定义还可以使程序更容易修改 如果稍后觉得 Dollars 实际应该该外 double 类型的,

typedef double Dollars 

如果没有定义Dollars ,则需要找到所有用 float 定义美金数量的地方,这显然不是一件容易的工作(对大型程序而言)。

类型定义的可移植性

类型定义时编写可移植性程序的重要工具。程序从一台计算机移动到另一台计算机可能引发的问题就是不同计算机上的类型取值范围可能不同。例如,如果 int i = 100000 这在 32 位机器上是没有问题的,但是在 16位机器上就会出错。

这时,在 32 位机器上我们可以这样定义:

typedef int Quantity;
Quantity a;

把程序转到 16 位机器上:

typedef long Quantity;

当然只这么做是不够的,Quantity 定义的变化可能影响类型变量的使用方式。至少我们需要改变 printf 和 scanf 中的格式串(%d 改为 %ld)。

sizeof 运算符

表达式(而非函数)sizeof(类型)的值是一个无符号整型,表示存储属于 类型名 的值所需要的字节数

在自己的计算机上敲一下下面的代码,看看你的机器上每个数据类型 sizeof 求出来的值,顺便复习一下本节的数剧类型

#include

int main(void) {
	printf("sizeof(signed char)        = %u byte \n", sizeof(signed char));
	printf("sizeof(unsigned char)      = %u byte \n", sizeof(unsigned char));
	printf("\n");
	printf("sizeof(short)              = %u byte \n", sizeof(short));
	printf("sizeof(unsigned short)     = %u byte \n", sizeof(unsigned short));
	printf("\n");
	printf("sizeof(int)                = %u byte \n", sizeof(int));
	printf("sizeof(unsigned int)       = %u byte \n", sizeof(unsigned int));
	printf("\n");
	printf("sizeof(long)               = %u byte \n", sizeof(long));
	printf("sizeof(unsigned long)      = %u byte \n", sizeof(unsigned long));
	printf("\n");
	printf("sizeof(long long)          = %u byte \n", sizeof(long long));
	printf("sizeof(unsigned long long) = %u byte \n", sizeof(unsigned long long));
	printf("\n");
	printf("sizeof(float)              = %u byte\n", sizeof(float));
	printf("sizeof(double)             = %u byte\n", sizeof(double));
	printf("sizeof(long double)        = %u byte\n", sizeof(long double));

	return 0;
}

为什么要用 %u 这个格式呢?

因为在我的机器上 sizeof 的值是 unsigned int 类型,每个机器可能不一样。

但是C99 和 C11 标准为 sizeof 包括 strlen (▶️) 的返回类型添加了 zd转换说明(z 表示 size_t类型)。对于早期的C,sizeof 和 strlen 的返回类型通常是 unsignedunsigned long

通常情况下,sizeof运算符也可以用于常量,变量,和表达式。

#include

int main(void) {
	
	short a = 3;
	int b = 1, c = 2;

	printf("sizeof(1.)        = %u byte \n", sizeof(1.));
	printf("sizeof(1)         = %u byte \n", sizeof(1));
	printf("sizeof(a)      	  = %u byte \n", sizeof(a));
	printf("sizeof(a + b)     = %u byte \n", sizeof(a + b));
	printf("sizeof(b + c)     = %u byte \n", sizeof(b + c));


	return 0;
}
//输出
sizeof(1.)        = 8 byte
sizeof(1)         = 4 byte
sizeof(a)         = 2 byte
sizeof(a + b)     = 4 byte
sizeof(b + c)     = 4 byte

sizeof(类型)不同的是, sizeof应用于表达式时可以省略括号。例如,可以用 sizeof i代替 sizeof(i) ;但是由于运算符优先级的问题,圆括号有时候还是需要的。编译器会将 sizeof i + j解释为 sizeof(i) + j。这是因为 sizeof 作为一元运算符 的优先级高于 二元运算符 + 。为了避免出现这种问题,建议还是保留圆括号。

参考资料:《C Primer Plus》《C语言程序设计:现代方法》


本文GitHub已收录,所有教学和练习代码都会上传上去。

https://github.com/hairrrrr/C-CrashCourse

如果对你有帮助,请点一个 star⭐️ 呦~ ​ 感谢!

以上就是本次的内容。

如果文章有错误欢迎指正和补充,感谢!

最后,如果你还有什么问题或者想知道到的,可以在评论区告诉我呦,我可以在后面的文章加上你们的真知灼见。

关注我,看更多干货!

我是程序圆,我们下次再见。


  1. 如果一个程序用于处理大量数据,它就没几种选择了 —— 《epigrams-on-programming》。 ↩︎

你可能感兴趣的:(c语言入门到精通)