C语言学习_变量和数据类型

1. 变量和数据类型

1.1 在屏幕上输出各种类型的数据

puts 是 output string 的缩写,只能用来输出字符串,不能输出整数、小数、字符等,我们需要用另外一个函数,那就是 printf。

printf 比 puts 更加强大,不仅可以输出字符串,还可以输出整数、小数、单个字符等,并且输出格式也可以自己定义,例如:

  • 以十进制、八进制、十六进制形式输出;
  • 要求输出的数字占 n 个字符的位置;
  • 控制小数的位数。

printf 是 print format 的缩写,意思是“格式化打印”。这里所谓的“打印”就是在屏幕上显示内容,与“输出”的含义相同,所以我们一般称 printf 是用来格式化输出的。

%d称为格式控制符,它指明了以何种形式输出数据。格式控制符均以%开头,后跟其他字符。%d 表示以十进制形式输出一个整数。除了 %d,printf 支持更多的格式控制,例如:

  • %c:输出一个字符。c 是 character 的简写。
  • %s:输出一个字符串。s 是 string 的简写。
  • %f:输出一个小数。f 是 float 的简写。
  • 示例代码:
#include <stdio.h>
int main()
{
    int n = 100;
    char c = '@';  //字符用单引号包围,字符串用双引号包围
    float money = 93.96;
    printf("n=%d, c=%c, money=%f\n", n, c, money);

    return 0;
}

我们也可以不用变量,将数据直接输出:

#include <stdio.h>
int main()
{
    float money = 93.96;
    printf("n=%d, c=%c, money=%f\n", 100, '@', money);

    return 0;
}

1.2 C语言中的整数(short,int,long)

  • 让整数占用更少的内存可以在 int 前边加 short,让整数占用更多的内存可以在 int 前边加 long,例如:
short int a = 10;
short int b, c = 99;
long int m = 102023;
long int n, p = 562131;

这样 a、b、c 只占用 2 个字节的内存,而 m、n、p 可能会占用 8 个字节的内存。

  • 也可以将 int 省略,只写 short 和 long,如下所示:
short a = 10;
short b, c = 99;
long m = 102023;
long n, p = 562131;

这样的写法更加简洁,实际开发中常用。

  • int 是基本的整数类型,short 和 long 是在 int 的基础上进行的扩展,short 可以节省内存,long 可以容纳更大的值。

  • short、int、long 是C语言中常见的整数类型,其中 int 称为整型,short 称为短整型,long 称为长整型。

整型的长度

  • 一种数据类型占用的字节数,称为该数据类型的长度。例如,short 占用 2 个字节的内存,那么它的长度就是 2。

  • 实际情况也确实如此,C语言并没有严格规定 short、int、long 的长度,只做了宽泛的限制:short 至少占用 2 个字节。

  • int 建议为一个机器字长。32 位环境下机器字长为 4 字节,64 位环境下机器字长为 8 字节。

  • short 的长度不能大于 int,long 的长度不能小于 int。

  • 总结起来,它们的长度(所占字节数)关系为:
    2 ≤ short ≤ int ≤ long

  • 这就意味着,short 并不一定真的”短“,long 也并不一定真的”长“,它们有可能和 int 占用相同的字节数。

sizeof 操作符

获取某个数据类型的长度可以使用 sizeof 操作符,如下所示:

#include <stdio.h>
int main()
{
    short a = 10;
    int b = 100;
   
    int short_length = sizeof a;
    int int_length = sizeof(b);
    int long_length = sizeof(long);
    int char_length = sizeof(char);
   
    printf("short=%d, int=%d, long=%d, char=%d\n", short_length, int_length, long_length, char_length);
   
    return 0;
}

在 32 位环境以及 Win64 环境下的运行结果为:
short=2, int=4, long=4, char=1

在 64 位 Linux 和 Mac OS 下的运行结果为:
short=2, int=4, long=8, char=1

  • sizeof 用来获取某个数据类型或变量所占用的字节数,如果后面跟的是变量名称,那么可以省略( ),如果跟的是数据类型,就必须带上( )。
  • 需要注意的是,sizeof 是C语言中的操作符,不是函数,所以可以不带( )

不同整型的输出

使用不同的格式控制符可以输出不同类型的整数,它们分别是:
%hd用来输出 short int 类型,hd 是 short decimal 的简写;
%d用来输出 int 类型,d 是 decimal 的简写;
%ld用来输出 long int 类型,ld 是 long decimal 的简写。

下面的例子演示了不同整型的输出:

#include <stdio.h>
int main()
{
    short a = 10;
    int b = 100;
    long c = 9437;
    printf("a=%hd, b=%d, c=%ld\n", a, b, c);
    return 0;
}

运行结果:
a=10, b=100, c=9437

  • 当使用%d输出 short,或者使用%ld输出 short、int 时,不管值有多大,都不会发生错误,因为格式控制符足够容纳这些值。

  • 当使用%hd输出 int、long,或者使用%d输出 long 时,如果要输出的值比较小(就像上面的情况),一般也不会发生错误,如果要输出的值比较大,就很有可能发生错误,例如:

#include <stdio.h>
int main()
{
    int m = 306587;
    long n = 28166459852;
    printf("m=%hd, n=%hd\n", m, n);
    printf("n=%d\n", n);
    return 0;
}

在 64 位 Linux 和 Mac OS 下(long 的长度为 8)的运行结果为:
m=-21093, n=4556
n=-1898311220

输出结果完全是错误的,这是因为%hd容纳不下 m 和 n 的值,%d也容纳不下 n 的值。

二进制数、八进制数和十六进制数的表示

二进制

  • 二进制由 0 和 1 两个数字组成,使用时必须以0b或0B(不区分大小写)开头
//合法的二进制
int a = 0b101;  //换算成十进制为 5
int b = -0b110010;  //换算成十进制为 -50
int c = 0B100001;  //换算成十进制为 33

//非法的二进制
int m = 101010;  //无前缀 0B,相当于十进制
int n = 0B410;  //4不是有效的二进制数字

八进制

  • 八进制由 0~7 八个数字组成,使用时必须以0开头(注意是数字 0,不是字母 o)
//合法的八进制数
int a = 015;  //换算成十进制为 13
int b = -0101;  //换算成十进制为 -65
int c = 0177777;  //换算成十进制为 65535

//非法的八进制
int m = 256;  //无前缀 0,相当于十进制
int n = 03A2;  //A不是有效的八进制数字

十六进制

  • 十六进制由数字 0~9、字母 A~F 或 a~f(不区分大小写)组成,使用时必须以0x或0X(不区分大小写)开头
//合法的十六进制
int a = 0X2A;  //换算成十进制为 42
int b = -0XA0;  //换算成十进制为 -160
int c = 0xffff;  //换算成十进制为 65535

//非法的十六进制
int m = 5A;  //没有前缀 0X,是一个无效数字
int n = 0X3H;  //H不是有效的十六进制数字

二进制数、八进制数和十六进制数的输出

  • C语言中常用的整数有 short、int 和 long 三种类型,通过 printf 函数,可以将它们以八进制、十进制和十六进制的形式输出。上节我们讲解了如何以十进制的形式输出,这节我们重点讲解如何以八进制和十六进制的形式输出,下表列出了不同类型的整数、以不同进制的形式输出时对应的格式控制符:
                   short	                 int                  	long
- 八进制	       %ho	                     %o	                    %lo
- 十进制	       %hd	                     %d	                    %ld
- 十六进制         %hx 或者%hX	             %x 或者 %X	            %lx 或者 %lX
  • 十六进制数字的表示用到了英文字母,有大小写之分,要在格式控制符中体现出来:
    %hx、%x 和 %lx 中的x小写,表明以小写字母的形式输出十六进制数;
    %hX、%X 和 %lX 中的X大写,表明以大写字母的形式输出十六进制数。

  • 八进制数字和十进制数字不区分大小写,所以格式控制符都用小写形式。如果你比较叛逆,想使用大写形式,那么行为是未定义的,请你慎重:
    有些编译器支持大写形式,只不过行为和小写形式一样;
    有些编译器不支持大写形式,可能会报错,也可能会导致奇怪的输出。

  • 注意,虽然部分编译器支持二进制数字的表示,但是却不能使用 printf 函数输出二进制,这一点比较遗憾。当然,通过转换函数可以将其它进制数字转换成二进制数字,并以字符串的形式存储,然后在 printf 函数中使用%s输出即可。

  • 【实例】以不同进制的形式输出整数:

#include <stdio.h>
int main()
{
    short a = 0b1010110;  //二进制数字
    int b = 02713;  //八进制数字
    long c = 0X1DAB83;  //十六进制数字
   
    printf("a=%ho, b=%o, c=%lo\n", a, b, c);  //以八进制形似输出
    printf("a=%hd, b=%d, c=%ld\n", a, b, c);  //以十进制形式输出
    printf("a=%hx, b=%x, c=%lx\n", a, b, c);  //以十六进制形式输出(字母小写)
    printf("a=%hX, b=%X, c=%lX\n", a, b, c);  //以十六进制形式输出(字母大写)
    return 0;
}

运行结果:
a=126, b=2713, c=7325603
a=86, b=1483, c=1944451
a=56, b=5cb, c=1dab83
a=56, b=5CB, c=1DAB83

输出时加上前缀

请读者注意观察上面的例子,会发现有一点不完美,如果只看输出结果:

  • 对于八进制数字,它没法和十进制、十六进制区分,因为八进制、十进制和十六进制都包含 0~7 - 这几个数字。
  • 对于十进制数字,它没法和十六进制区分,因为十六进制也包含 0~9 这几个数字。如果十进制数字中还不包含 8 和 9,那么也不能和八进制区分了。
  • 对于十六进制数字,如果没有包含 a~f 或者 A~F,那么就无法和十进制区分,如果还不包含 8 和 9,那么也不能和八进制区分了。

区分不同进制数字的一个简单办法就是,在输出时带上特定的前缀。在格式控制符中加上#即可输出前缀,例如 %#x、%#o、%#lX、%#ho 等,请看下面的代码:

#include <stdio.h>
int main()
{
    short a = 0b1010110;  //二进制数字
    int b = 02713;  //八进制数字
    long c = 0X1DAB83;  //十六进制数字
   
    printf("a=%#ho, b=%#o, c=%#lo\n", a, b, c);  //以八进制形似输出
    printf("a=%hd, b=%d, c=%ld\n", a, b, c);  //以十进制形式输出
    printf("a=%#hx, b=%#x, c=%#lx\n", a, b, c);  //以十六进制形式输出(字母小写)
    printf("a=%#hX, b=%#X, c=%#lX\n", a, b, c);  //以十六进制形式输出(字母大写)
   
    return 0;
}

运行结果:
a=0126, b=02713, c=07325603
a=86, b=1483, c=1944451
a=0x56, b=0x5cb, c=0x1dab83
a=0X56, b=0X5CB, c=0X1DAB83

  • 十进制数字没有前缀,所以不用加#。如果你加上了,那么它的行为是未定义的,有的编译器支持十进制加#,只不过输出结果和没有加#一样,有的编译器不支持加#,可能会报错,也可能会导致奇怪的输出;但是,大部分编译器都能正常输出,不至于当成一种错误。

1.3 C语言中的正负数及其输出

  • 在数学中,数字有正负之分。在C语言中也是一样,short、int、long 都可以带上正负号,例如:
//负数
short a1 = -10;
short a2 =-0x2dc9;  //十六进制
//正数
int b1 = +10;
int b2 = +0174;  //八进制
int b3 = 22910;
//负数和正数相加
long c = (-9) + (+12);

如果不带正负号,默认就是正数。

无符号数的输出

无符号数可以以八进制、十进制和十六进制的形式输出,它们对应的格式控制符分别为:
 	    unsigned short	     unsigned int	     unsigned long
八进制	%ho	                 %o	                 %lo
十进制	%hu	                 %u	                 %lu
十六进制	%hx 或者 %hX	     %x 或者 %X	         %lx 或者 %lX

严格来说,格式控制符和整数的符号是紧密相关的,具体就是:

  • %d 以十进制形式输出有符号数;
  • %u 以十进制形式输出无符号数;
  • %o 以八进制形式输出无符号数;
  • %x 以十六进制形式输出无符号数。
  • 下表全面地总结了不同类型的整数,以不同进制的形式输出时对应的格式控制符(–表示没有对应的格式控制符)。
            short	    int	    long	        unsigned short	     unsigned int	        unsigned long
八进制	    --	         --     --           	%ho	                 %o	                    %lo
十进制	    %hd	         %d	    %ld	            %hu	                 %u      	            %lu
十六进制	    --           --	    --	            %hx 或者 %hX	     %x 或者 %X	            %lx 或者 %lX

  • 代码示例:
 int b = -0x1;  //十六进制
    long c = 720;  //十进制
   
    unsigned short m = 0xffff;  //十六进制
    unsigned int n = 0x80000000;  //十六进制
    unsigned long p = 100;  //十进制
   
    //以无符号的形式输出有符号数#include <stdio.h>
int main()
{
    short a = 0100;  //八进制
    printf("a=%#ho, b=%#x, c=%ld\n", a, b, c);
    //以有符号数的形式输出无符号类型(只能以十进制形式输出)
    printf("m=%hd, n=%d, p=%ld\n", m, n, p);

    return 0;
}

无符号数的取值范围

     	unsigned char	   unsigned short	          unsigned int(4字节)  	        unsigned long(8字节)
最小值	0	               0	                      0	                                0
最大值	28 - 1 = 255	   216 - 1 = 65,535 ≈ 6.5万	  232 - 1 = 4,294,967,295 ≈ 42亿	264 - 1 ≈ 1.84×1019

有符号数的取值范围

有符号数以补码的形式存储,计算取值范围也要从补码入手。我们以 char 类型为例,从下表中找出它的取值范围:

补码	    反码	    原码	    值
1111 1111	1111 1110	1000 0001	-1
1111 1110	1111 1101	1000 0010	-2
1111 1101	1111 1100	1000 0011	-3
……	        ……	        ……	        ……
1000 0011	1000 0010	1111 1101	-125
1000 0010	1000 0001	1111 1110	-126
1000 0001	1000 0000	1111 1111	-127
1000 0000	--	        --	        -128
0111 1111	0111 1111	0111 1111	 127
0111 1110	0111 1110	0111 1110	 126
0111 1101	0111 1101	0111 1101	 125
……	        ……	        ……	         ……
0000 0010	0000 0010	0000 0010	 2
0000 0001	0000 0001	0000 0001	 1
0000 0000	0000 0000	0000 0000	 0

小数的输出

  • 小数也可以使用 printf 函数输出,包括十进制形式和指数形式,它们对应的格式控制符分别是:
%f 以十进制形式输出 float 类型;
%lf 以十进制形式输出 double 类型;
%e 以指数形式输出 float 类型,输出结果中的 e 小写;
%E 以指数形式输出 float 类型,输出结果中的 E 大写;
%le 以指数形式输出 double 类型,输出结果中的 e 小写;
%lE 以指数形式输出 double 类型,输出结果中的 E 大写。
  • 示例代码:
#include <stdio.h>
#include <stdlib.h>
int main()
{
    float a = 0.302;
    float b = 128.101;
    double c = 123;
    float d = 112.64E3;
    double e = 0.7623e-2;
    float f = 1.23002398;
    printf("a=%e \nb=%f \nc=%lf \nd=%lE \ne=%lf \nf=%f\n", a, b, c, d, e, f);
   
    return 0;
}
  • 对代码的说明:
  1. %f 和 %lf 默认保留六位小数,不足六位以 0 补齐,超过六位按四舍五入截断。
  2. 将整数赋值给 float 变量时会变成小数。
  3. 以指数形式输出小数时,输出结果为科学计数法;也就是说,尾数部分的取值为:0 ≤ 尾数 < 10。
  4. b 的输出结果让人费解,才三位小数,为什么不能精确输出,而是输出一个近似值呢?这和小数在内存中的存储形式有关,很多简单的小数压根不能精确存储,所以也就不能精确输出
  • 另外,小数还有一种更加智能的输出方式,就是使用%g。%g 会对比小数的十进制形式和指数形式,以最短的方式来输出小数,让输出结果更加简练。所谓最短,就是输出结果占用最少的字符。
  • %g 使用示例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
    float a = 0.00001;
    float b = 30000000;
    float c = 12.84;
    float d = 1.229338455;
    printf("a=%g \nb=%g \nc=%g \nd=%g\n", a, b, c, d);
   
    return 0;
}

1.4 在C语言中使用英文字符

字符的表示

字符类型由单引号’ '包围,字符串由双引号" "包围。
下面的例子演示了如何给 char 类型的变量赋值:

//正确的写法
char a = '1';
char b = '$';
char c = 'X';
char d = ' ';  // 空格也是一个字符

//错误的写法
char x = '中';  //char 类型不能包含 ASCII 编码之外的字符
char y = 'A';  //A 是一个全角字符
char z = "t";  //字符类型应该由单引号包围

字符的输出

  • 输出 char 类型的字符有两种方法,分别是:
  1. 使用专门的字符输出函数 putchar;
  2. 使用通用的格式化输出函数 printf,char 对应的格式控制符是%c。
  • 请看下面的演示:
#include <stdio.h>
int main() {
    char a = '1';
    char b = '$';
    char c = 'X';
    char d = ' ';

    //使用 putchar 输出
    putchar(a); putchar(d);
    putchar(b); putchar(d);
    putchar(c); putchar('\n');
    //使用 printf 输出
    printf("%c %c %c\n", a, b, c);

    return 0;
}

运行结果:
1 $ X
1 $ X

  • put char 函数每次只能输出一个字符,输出多个字符需要调用多次。

字符与整数

  • 代码示例:
#include <stdio.h>
int main()
{
    char a = 'E';
    char b = 70;
    int c = 71;
    int d = 'H';
    printf("a: %c, %d\n", a, a);
    printf("b: %c, %d\n", b, b);
    printf("c: %c, %d\n", c, c);
    printf("d: %c, %d\n", d, d);
    return 0;
}

输出结果:
a: E, 69
b: F, 70
c: G, 71
d: H, 72

  • 在 ASCII 码表中,字符 ‘E’、‘F’、‘G’、‘H’ 对应的编号分别是 69、70、71、72。
a、b、c、d 实际上存储的都是整数:
当给 a、d 赋值一个字符时,字符会先转换成 ASCII 码再存储;
当给 b、c 赋值一个整数时,不需要任何转换,直接存储就可以;
当以 %c 输出 a、b、c、d 时,会根据 ASCII 码表将整数转换成对应的字符;
当以 %d 输出 a、b、c、d 时,不需要任何转换,直接输出就可以。
  • 可以说,是 ASCII 码表将英文字符和整数关联了起来。

1.5 C语言转义字符

  • 转义字符以\或者\x开头,以\开头表示后跟八进制形式的编码值,以\x开头表示后跟十六进制形式的编码值。对于转义字符来说,只能使用八进制或者十六进制
  • 字符 1、2、3、a、b、c 对应的 ASCII 码的八进制形式分别是 61、62、63、141、142、143,十六进制形式分别是 31、32、33、61、62、63。
  • 下面的例子演示了转义字符的用法:
char a = '\61';  //字符1
char b = '\141';  //字符a
char c = '\x31';  //字符1
char d = '\x61';  //字符a
char *str1 = "\x31\x32\x33\x61\x62\x63";  //字符串"123abc"
char *str2 = "\61\62\63\141\142\143";  //字符串"123abc"
char *str3 = "The string is: \61\62\63\x61\x62\x63"  //混用八进制和十六进制形式

对于 ASCII 编码,0~31(十进制)范围内的字符为控制字符,它们都是看不见的,不能在显示器上显示,甚至无法从键盘输入,只能用转义字符的形式来表示。不过,直接使用 ASCII 码记忆不方便,也不容易理解,所以,针对常用的控制字符,C语言又定义了简写方式,完整的列表如下:

转义字符	                         意义	                                 ASCII码值(十进制)
\a	                             响铃(BEL)	                             007
\b	                             退格(BS) ,将当前位置移到前一列	         008
\f	                             换页(FF),将当前位置移到下页开头	         012
\n                           	 换行(LF) ,将当前位置移到下一行开头	     010
\r	                             回车(CR) ,将当前位置移到本行开头 	     013
\t	                             水平制表(HT) 	                         009
\v	                             垂直制表(VT)                             011
\'	                             单引号	                                 039
\"	                             双引号	                                 034
\\	                             反斜杠	                                 092
  • \n和\t是最常用的两个转义字符:
\n用来换行,让文本从下一行的开头输出,前面的章节中已经多次使用;
\t用来占位,一般相当于四个空格,或者 tab 键的功能。
  • 引号、双引号、反斜杠是特殊的字符,不能直接表示:
单引号是字符类型的开头和结尾,要使用\'表示,也即'\'';
双引号是字符串的开头和结尾,要使用\"表示,也即"abc\"123";
反斜杠是转义字符的开头,要使用\\表示,也即'\\',或者"abc\\123"。
  • 转义字符示例:
#include <stdio.h>
int main(){
    puts("C\tC++\tJava\n\"C\" first appeared!");
    return 0;
}

运行结果:
C C++ Java
“C” first appeared!

1.6 C语言加减乘除运算

  • 加减乘除是常见的数学运算,C语言当然支持,不过,C语言中的运算符号与数学中的略有不同,请见下表。
	          加法	  减法	  乘法	  除法	  求余数(取余)
数学	      +	      -	      ×	      ÷	          无
C语言 	      +	      -       *	      /	          %
  • C语言中的加号、减号与数学中的一样,乘号、除号不同;另外C语言还多了一个求数的运算符,就是 %。

下面的代码演示了如何在C语言中进行加减乘除运算:

#include <stdio.h>
int main()
{
    int a = 12;
    int b = 100;
    float c = 8.5;

    int m = a + b;
    float n = b * c;
    double p = a / c;
    int q = b % a;

    printf("m=%d, n=%f, p=%lf, q=%d\n", m, n, p, q);

    return 0;
}

输出结果:
m=112, n=850.000000, p=1.411765, q=4

  • 你也可以让数字直接参与运算:
#include <stdio.h>
int main()
{
    int a = 12;
    int b = 100;
    float c = 8.9;
    int m = a - b;  // 变量参与运算
    int n = a + 239;  // 有变量也有数字
    double p = 12.7 * 34.3;  // 数字直接参与运算
    printf("m=%d, n=%d, p=%lf\n", m, n, p);
    printf("m*2=%d, 6/3=%d, m*n=%ld\n", m*2, 6/3, m*n);
    return 0;
}

输出结果:
m=-88, n=251, p=435.610000
m2=-176, 6/3=2, mn=-22088

对除法的说明

  • C语言中的除法运算有点奇怪,不同类型的除数和被除数会导致不同类型的运算结果:
当除数和被除数都是整数时,运算结果也是整数;如果不能整除,那么就直接丢掉小数部分,只保留整数部分,这跟将小数赋值给整数类型是一个道理。
一旦除数和被除数中有一个是小数,那么运算结果也是小数,并且是 double 类型的小数。

请看下面的代码:

#include <stdio.h>
int main()
{
    int a = 100;
    int b = 12;
    float c = 12.0;
   
    double p = a / b;
    double q = a / c;
   
    printf("p=%lf, q=%lf\n", p, q);
   
    return 0;
}

运行结果:
p=8.000000, q=8.333333

  • a 和 b 都是整数,a / b 的结果也是整数,所以赋值给 p 变量的也是一个整数,这个整数就是 8。

  • 另外需要注意的一点是除数不能为 0,因为任何一个数字除以 0 都没有意义。

对取余运算的说明

  • 取余,也就是求余数,使用的运算符是 %。C语言中的取余运算只能针对整数,也就是说,% 的两边都必须是整数,不能出现小数,否则编译器会报错。
  • 另外,余数可以是正数也可以是负数,由 % 左边的整数决定:
如果 % 左边是正数,那么余数也是正数;
如果 % 左边是负数,那么余数也是负数。

请看下面的例子:

#include <stdio.h>
int main()
{
    printf(
        "100%%12=%d \n100%%-12=%d \n-100%%12=%d \n-100%%-12=%d \n",
        100%12, 100%-12, -100%12, -100%-12
    );
    return 0;
}

运行结果:
100%12=4
100%-12=4
-100%12=-4
-100%-12=-4

  • 在 printf 中,% 是格式控制符的开头,是一个特殊的字符,不能直接输出;要想输出 %,必须在它的前面再加一个 %,这个时候 % 就变成了普通的字符,而不是用来表示格式控制符了。

加减乘除运算的简写

  • 有时候我们希望对一个变量进行某种运算,然后再把运算结果赋值给变量本身,请看下面的例子:
#include <stdio.h>
int main()
{
    int a = 12;
    int b = 10;
    printf("a=%d\n", a);
    a = a + 8;
    printf("a=%d\n", a);
    a = a * b;
    printf("a=%d\n", a);
    return 0;
}

输出结果:
a=12
a=20
a=200

  • a = a + 8相当于用原来 a 的值(也即12)加上 8,再把运算结果(也即20)赋值给 a,此时 a 的值就变成了 20。

  • a = a * b相当于用原来 a 的值(也即20)乘以 b 的值(也即10),再把运算结果(也即200)赋值给 a,此时 a 的值就变成了 200。

以上的操作,可以理解为对变量本身进行某种运算。

  • 在C语言中,对变量本身进行运算可以有简写形式。假设用 # 来表示某种运算符,那么
a = a # b

可以简写为:

a #= b
  • #表示 +、-、*、/、% 中的任何一种运算符。

  • 上例中a = a + 8可以简写为a += 8,a = a * b可以简写为a *= b。

下面的简写形式也是正确的:

int a = 10, b = 20;
a += 10;  //相当于 a = a + 10;
a *= (b-10);  //相当于 a = a * (b-10);
a -= (a+20);  //相当于 a = a - (a+20);

注意:a #= b 仅是一种简写形式,不会影响程序的执行效率。

1.7 C语言自增(++)和自减(–)

  • 一个整数类型的变量自身加 1 可以这样写:
a = a + 1;

或者

a += 1;

不过,C语言还支持另外一种更加简洁的写法,就是:

a++;

或者

++a;
  • 这种写法叫做自加或自增,意思很明确,就是每次自身加 1。
  • 相应的,也有a–和–a,它们叫做自减,表示自身减 1。
  • ++和–分别称为自增运算符和自减运算符,它们在循环结构(后续章节会讲解)中使用很频繁。
  • 自增和自减的示例:
#include <stdio.h>
int main()
{
    int a = 10, b = 20;
    printf("a=%d, b=%d\n", a, b);
    ++a;
    --b;
    printf("a=%d, b=%d\n", a, b);
    a++;
    b--;
    printf("a=%d, b=%d\n", a, b);
    return 0;
}

运行结果:
a=10, b=20
a=11, b=19
a=12, b=18

  • 自增自减完成后,会用新值替换旧值,将新值保存在当前变量中。

  • 自增自减的结果必须得有变量来接收,所以自增自减只能针对变量,不能针对数字,例如10++就是错误的。

  • 需要重点说明的是,++ 在变量前面和后面是有区别的:

++ 在前面叫做前自增(例如 ++a)。前自增先进行自增运算,再进行其他操作。
++ 在后面叫做后自增(例如 a++)。后自增先进行其他操作,再进行自增运算。
  • 自减(–)也一样,有前自减和后自减之分。

  • 下面的例子能更好地说明前自增(前自减)和后自增(后自减)的区别:

#include <stdio.h>
int main()
{
    int a = 10, b = 20, c = 30, d = 40;
    int a1 = ++a, b1 = b++, c1 = --c, d1 = d--;
   
    printf("a=%d, a1=%d\n", a, a1);
    printf("b=%d, b1=%d\n", b, b1);
    printf("c=%d, c1=%d\n", c, c1);
    printf("d=%d, d1=%d\n", d, d1);
   
    return 0;
}
输出结果:
a=11, a1=11
b=21, b1=20
c=29, c1=29
d=39, d1=40
  • a、b、c、d 的输出结果相信大家没有疑问,下面重点分析a1、b1、c1、d1:
  1. 对于a1=++a,先执行 ++a,结果为 11,再将 11 赋值给 a1,所以 a1 的最终值为11。而 a 经过自增,最终的值也为 11。

  2. 对于b1=b++,b 的值并不会立马加 1,而是先把 b 原来的值交给 b1,然后再加 1。b 原来的值为 20,所以 b1 的值也就为 20。而 b 经过自增,最终值为 21。

  3. 对于c1=–c,先执行 --c,结果为 29,再将 29 赋值给c1,所以 c1 的最终值为 29。而 c 经过自减,最终的值也为 29。

  4. 对于d1=d–,d 的值并不会立马减 1,而是先把 d 原来的值交给 d1,然后再减 1。d 原来的值为 40,所以 d1 的值也就为 40。而 d 经过自减,最终值为 39。

  • 可以看出:a1=++a;会先进行自增操作,再进行赋值操作;而b1=b++;会先进行赋值操作,再进行自增操作。c1=–c;和d1=d–;也是如此。

  • 为了强化记忆,我们再来看一个自增自减的综合示例:

#include <stdio.h>
int main()
{
    int a = 12, b = 1;
    int c = a - (b--);  // ①
    int d = (++a) - (--b);  // ②
    printf("c=%d, d=%d\n", c, d);
    return 0;
}

输出结果:
c=11, d=14

我们来分析一下:

  1. 执行语句①时,因为是后自减,会先进行a-b运算,结果是 11,然后 b 再自减,就变成了 0;最后再将a-b的结果(也就是11)交给 c,所以 c 的值是 11。

  2. 执行语句②之前,b 的值已经变成 0。对于d=(++a)-(–b),a 会先自增,变成 13,然后 b 再自减,变成 -1,最后再计算13-(-1),结果是 14,交给 d,所以 d 最终是 14。

1.9 C语言数据类型转换(自动类型转换+强制类型转换)

自动类型转换

  • 自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换,这种转换不需要程序员干预,会自动发生。
  1. 将一种类型的数据赋值给另外一种类型的变量时就会发生自动类型转换,例如:
    float f = 100;
    100 是 int 类型的数据,需要先转换为 float 类型才能赋值给变量 f。再如:
    int n = f;
    f 是 float 类型的数据,需要先转换为 int 类型才能赋值给变量 n。
  • 在赋值运算中,赋值号两边的数据类型不同时,需要把右边表达式的类型转换为左边变量的类型,这可能会导致数据失真,或者精度降低;所以说,自动类型转换并不一定是安全的。对于不安全的类型转换,编译器一般会给出警告。
  1. 在不同类型的混合运算中,编译器也会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算。转换的规则如下:
  • 转换按数据长度增加的方向进行,以保证数值不失真,或者精度不降低。例如,int 和 long 参与运算时,先把 int 类型的数据转成 long 类型后再进行运算。

  • 所有的浮点运算都是以双精度进行的,即使运算中只有 float 类型,也要先转换为 double 类型,才能进行运算。

  • char 和 short 参与运算时,必须先转换成 int 类型。

  • unsigned 也即 unsigned int,此时可以省略 int,只写 unsigned。

  • 自动类型转换示例:

#include<stdio.h>
int main(){
    float PI = 3.14159;
    int s1, r = 5;
    double s2;
    s1 = r * r * PI;
    s2 = r * r * PI;
    printf("s1=%d, s2=%f\n", s1, s2);
    return 0;
}

运行结果:
s1=78, s2=78.539749

  • 在计算表达式rrPI时,r 和 PI 都被转换成 double 类型,表达式的结果也是 double 类型。但由于 s1 为整型,所以赋值运算的结果仍为整型,舍去了小数部分,导致数据失真。

强制类型转换

  • 自动类型转换是编译器根据代码的上下文环境自行判断的结果,有时候并不是那么“智能”,不能满足所有的需求。如果需要,程序员也可以自己在代码中明确地提出要进行类型转换,这称为强制类型转换。

  • 自动类型转换是编译器默默地、隐式地进行的一种类型转换,不需要在代码中体现出来;强制类型转换是程序员明确提出的、需要通过特定格式的代码来指明的一种类型转换。换句话说,自动类型转换不需要程序员干预,强制类型转换必须有程序员干预。

  • 强制类型转换的格式为:

(type_name) expression

type_name为新类型名称,expression为表达式。例如:

(float) a;  //将变量 a 转换为 float 类型
(int)(x+y);  //把表达式 x+y 的结果转换为 int 整型
(float) 100;  //将数值 100(默认为int类型)转换为 float 类型
  • 下面是一个需要强制类型转换的经典例子:
#include <stdio.h>
int main(){
    int sum = 103;  //总数
    int count = 7;  //数目
    double average;  //平均数
    average = (double) sum / count;
    printf("Average is %lf!\n", average);
    return 0;
}

运行结果:
Average is 14.714286!

类型转换只是临时性的

  • 无论是自动类型转换还是强制类型转换,都只是为了本次运算而进行的临时性转换,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。
  • 请看下面的例子:
#include <stdio.h>
int main(){
    double total = 400.8;  //总价
    int count = 5;  //数目
    double unit;  //单价
    int total_int = (int)total;
    unit = total / count;
    printf("total=%lf, total_int=%d, unit=%lf\n", total, total_int, unit);
    return 0;
}

运行结果:
total=400.800000, total_int=400, unit=80.160000

  • 注意看第 6 行代码,total 变量被转换成了 int 类型才赋值给 total_int 变量,而这种转换并未影响 total 变量本身的类型和值。如果 total 的值变了,那么 total 的输出结果将变为 400.000000;如果 total 的类型变了,那么 unit 的输出结果将变为 80.000000。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值